/* * CombinedIRTypeKTemp * Read and plot Infrared and Type K Temperature sensors from your Android mobile * * see www.pfod.com.au for other projects * * (c)2013 Forward Computing and Control Pty. Ltd. * This code may be freely used for both private and commerical use. * Provide this copyright is maintained. * * NOTE: This sketch is for FioV3 * and uses Interrupt 1 * and pins D2,D3,D4 * */ /* ===== pfod Command for Infrared Type K menu ==== {.<-1>Sensors power down when you exit.|A~<+3>Infrared 15.5℃ Type K 16.5℉ <-6>Click to reset IR average|D~<+3>Plot Readings|H~Set Infrared Averaging|C~Switch to Fahrenheit (℉)|G~Battery 4.04V} */ // Using Serial1 and 9600 for send and receive // Serial1 D0 (RX) and D1 (TX) on SparkFun FioV3 boards /* Code generated by pfodDesigner V1.2.561 * (c)2014-2015 Forward Computing and Control Pty. Ltd. * NSW Australia, www.forward.com.au * This generated code may be freely used for both private and commerical use */ #include #include #include //#define IRTERMP_DEBUG // ====================== // this is the pfodParser.h file with the class renamed pfodParser_codeGenerated and with comments, constants and un-used methods removed class pfodParser_codeGenerated: public Print { public: pfodParser_codeGenerated(); void connect(Stream* ioPtr); void closeConnection(); byte parse(); byte* getCmd(); byte* getFirstArg(); byte getArgsCount(); byte* parseLong(byte* idxPtr, long *result); size_t write(uint8_t c); void flush(); void init(); byte parse(byte in); Stream* getPfodAppStream(); private: Stream* io; byte argsCount; byte argsIdx; byte parserState; byte args[255]; }; //============= end of pfodParser_codeGenerated.h pfodParser_codeGenerated parser; // create a parser to handle the pfod messages // pins for IR temp module static const byte PIN_DATA = 2; static const byte PIN_CLOCK = 3; static const byte PIN_ACQUIRE = 4; static const byte CLOCK_INTERRUPT = 0; // D3 is connected to INT0 on FioV3 // pins for TypeK thermocouple module static const byte DI_Pin = A5; static const byte CLK_Pin = A3; static const byte CS_Pin = A4; static const byte BatteryControlPin = 7; // PIN D7 pfodIRTemp irTemp(PIN_DATA, PIN_CLOCK, PIN_ACQUIRE, CLOCK_INTERRUPT); // construct class to handle sensor // D2=input data pin, Interrupt 0 on D3 clock, D4 =aquire trigger pin, Interrupt 0 wired to D3 on FioV3 boards pfodMAX31855 typeKTemp(DI_Pin, CLK_Pin, CS_Pin); unsigned long readingTimer; static const unsigned long readingInterval = 1000; // 1sec between readings unsigned long batteryTimer; static const unsigned long BatteryDisableTime = 60000; // 60sec int lastIRTemperatureReading = 0; int tempIRTemperatureReading = 0; int lastKTemperatureReading = 0; bool fahrenheit = false; // set to true for Farinheit instead of Celsius bool plotting = false; // set to true if viewing plot. // if true do not turn off battery even if time out. unsigned long batteryVoltsTimer; #define interval 1000 // the interval in mS int batteryVoltsReading = 1023; // start with high reading // 4.14V = 586 counts // i.e. counts * 0.0071 == volts // cutoff 3.6V = 510 counts int batteryVoltsLowCount = 510; boolean batteryVoltsLow = false; int averageIRTemp; #define MAX_INITIAL_NO_SAMPLES 5 #define MAX_AVERAGE_BUFFER_SIZE (2< 0; i--) { // wait a few secs to see if we are being programmed delay(500); Serial.print(i); Serial.print(" "); } Serial.println(); #endif parser.connect(&Serial1); // connect the parser to the i/o stream // <<<<<<<<< Your extra setup code goes here readingTimer = millis(); batteryTimer = millis(); // will timeout in 60sec if no connection made or after connection closed batteryVoltsTimer = millis(); } // the loop routine runs over and over again forever: void loop() { byte cmd = parser.parse(); // pass it to the parser // parser returns non-zero when a pfod command is fully parsed if (cmd != 0) { // have parsed a complete msg { to } byte* pfodFirstArg = parser.getFirstArg(); // may point to \0 if no arguments in this msg. long pfodLongRtn; // used for parsing long return arguments, if any plotting = false; // set to true if get plotting cmd if ('.' == cmd) { // pfodApp has connected and sent {.} , it is asking for the main menu // send back the menu designed sendMainMenu(); // now handle commands returned from button/sliders } else if ('A' == cmd) { // user pressed -- 'Infrared 15.5℃ Type K 16.5℉ <-6>Click to reset IR average' // << add your action code here for this button clearAverage(lastIRTemperatureReading); averageIRTemp = lastIRTemperatureReading; sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if ('D' == cmd) { // user pressed -- 'Plot Readings' // << add your action code here for this button sendPlotMenu(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if ('H' == cmd) { // user pressed -- 'Set Infrared Averaging' // << add your action code here for this button sendIRAvgMenu(); } else if ('C' == cmd) { // user pressed -- 'Switch to Fahrenheit (℉)' // << add your action code here for this button switchScale(); sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if ('G' == cmd) { // user pressed -- 'Battery 4.04V' // << add your action code here for this button // NOTHING HERE sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if (cmd == 'a') { // user pressed -- 'Plot Infrared and Type K' plotting = true; // open Streaming Raw Data screen, starts saving to file // format 12 fields // time,Sec,TypeK,readingF,F,readingC,C,IR,readingF,F,readingC,C if (fahrenheit) { parser.print(F("{=Temp Readings|t (sec)`0|||TypeK~212~-32~Deg F`1|||||IR~212~-32`1|||}")); } else { parser.print(F("{=Temp Readings|t (sec)`0|||||TypeK~100~0~Deg C`1|||||IR~100~0`1|}")); } } else if (cmd == 'b') { // user pressed -- 'Plot Infrared' plotting = true; if (fahrenheit) { parser.print(F("{=IR Temp Readings|t (sec)||||||||IR deg F~212~-32|||}")); } else { parser.print(F("{=IR Temp Readings|t (sec)||||||||||IR deg C~100~0|}")); } } else if (cmd == 'c') { // user pressed -- 'Plot Type K' plotting = true; if (fahrenheit) { parser.print(F("{=TypeK Temp Readings|t (sec)|||TypeK deg F~212~-32||||||||}")); } else { parser.print(F("{=TypeK Temp Readings|t (sec)|||||Type K deg C~100~0||||||}")); } } else if ('d' == cmd) { // user pressed -- 'Switch to Fahrenheit (℉)' // << add your action code here for this button switchScale(); sendPlotMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if (cmd == 'm') { // user selected number of averages // parse 1 arg as a number parser.parseLong(pfodFirstArg, &pfodLongRtn); // only one arg so no need to loop initialNoSamples = (byte)pfodLongRtn; setNoReadingsToAverage((int)initialNoSamples, lastIRTemperatureReading); parser.print(F("{}")); // pfodApp will request previous menu } else if ('!' == cmd) { // CloseConnection command closeConnection(parser.getPfodAppStream()); } else { // unknown command parser.print(F("{}")); // always send back a pfod msg otherwise pfodApp will disconnect. } } // <<<<<<<<<<< Your other loop() code goes here if ((cmd != 0) || plotting) { // got something or showing plot batteryTimer = millis(); // reset battery_disable timer with each command received } checkBatteryVolts(); if ((millis() - readingTimer) >= readingInterval) { // measure once per sec readingTimer += readingInterval; irTemp.triggerSensor(); } int IRreading = irTemp.getIRTemperature(); int KTempReading = typeKTemp.getTypeKTemperature(); if ((IRreading != pfodIRTemp::NO_DATA) && (IRreading > 32)) { // have a reading above 2 deg Kelvin tempIRTemperatureReading = IRreading; // save reading for later averageIRTemp = average(IRreading); typeKTemp.triggerSensor(); // start a Type K reading available in <100uS } if (KTempReading != typeKTemp.NO_DATA) { // have a Type K reading lastKTemperatureReading = KTempReading; // save reading for main menu lastIRTemperatureReading = tempIRTemperatureReading; // output both parser.print(millis() / 1000.0); // output secs since restart // format 12 fields // time,Sec,TypeK,readingF,F,readingC,C,IR,readingF,F,readingC,C parser.print(F(",Sec,TypeK,")); // csv comma separator if (KTempReading < typeKTemp.NO_DATA) { parser.println(KTempReading); parser.print(F(" ,Invalid Reading, ,Invalid Reading, ")); } else { parser.print(typeKTemp.convertToFahrenheit(KTempReading)); parser.print(F(",F,")); parser.print(typeKTemp.convertToCelsius(KTempReading)); parser.print(F(",C")); } parser.print(F(",IR,")); // csv comma separator if (IRreading < pfodIRTemp::NO_DATA) { parser.println(F(" ,Invalid Reading, ,Invalid Reading")); // invalid reading have one less fields so are not plotted } else { parser.print(irTemp.convertToFahrenheit(lastIRTemperatureReading)); parser.print(F(",F,")); parser.print(irTemp.convertToCelsius(lastIRTemperatureReading)); parser.println(F(",C")); } } if ((millis() - batteryTimer) > BatteryDisableTime) { // have not received a connection from pfodApp in last 60sec // disconnect the battery now digitalWrite(BatteryControlPin, LOW); } } // set the new number of reading to index and // re-initialize the average void setNoReadingsToAverage(int indexSelected, int irReading) { numberOfAverages = 2 << indexSelected; #ifdef IRTERMP_DEBUG Serial.print(F("Set no readings to average to ")); Serial.println(numberOfAverages); #endif clearAverage(irReading); } // add a new reading and return the new average deg C int average(int nextReading) { if (average_buffer.buffer[0] == 0) { // fill with this reading to start with return clearAverage(nextReading); } // else int i = (unsigned int)(average_buffer.idx + 1) % numberOfAverages; average_buffer.idx = i; #ifdef IRTERMP_DEBUG Serial.print(F(" i = ")); Serial.print(i); // sub the current value, add the new value and divide by AVERAGE_BUFFER_SIZE Serial.print(F(" avg = ")); Serial.print(average_buffer.average); Serial.print(F(" buf[i] = ")); Serial.print(average_buffer.buffer[i]); Serial.print(F(" nextReading = ")); Serial.print(nextReading); #endif average_buffer.average = average_buffer.average - average_buffer.buffer[i]; average_buffer.buffer[i] = (long)nextReading; average_buffer.average = average_buffer.average + nextReading; #ifdef IRTERMP_DEBUG Serial.print(F(" new avg = ")); Serial.print(average_buffer.average); Serial.print(F(" AvgTmp = ")); Serial.println(average_buffer.average / numberOfAverages); #endif return (int)(average_buffer.average / numberOfAverages); } // this method lets the user clear out the old averages // this returns raw reading which then needs to be converted to decC or degF int clearAverage(int startingReading) { #ifdef IRTERMP_DEBUG Serial.print(F("startingReading =")); Serial.println(startingReading); #endif for (int i = 0; i < numberOfAverages; i++) { average_buffer.buffer[i] = startingReading; } average_buffer.idx = 0; average_buffer.average = ((long)startingReading) * numberOfAverages; #ifdef IRTERMP_DEBUG Serial.print(F("Cleared AvgTemp=")); Serial.println(average_buffer.average / numberOfAverages); #endif return (int)(average_buffer.average / numberOfAverages); } void closeConnection(Stream *io) { // add any special code here to force connection to be dropped } void sendMainMenu() { parser.print(F("{.")); // start a Menu screen pfod message send_menuContents(); // send the menu contents parser.print(F("}")); // close pfod message } void sendMainMenuUpdate() { parser.print(F("{:")); // start an Update Menu pfod message send_menuContents(); // send the menu contents parser.print(F("}")); // close pfod message } // modify this method if you need to update the menu to reflect state changes void send_menuContents() { // send menu prompt parser.print(F("<-1>Sensors power down\nshortly after you exit.`1000")); // send menu items parser.print(F("|A~<+3>Infrared ")); if (fahrenheit) { parser.print(irTemp.convertToFahrenheit(averageIRTemp)); parser.print(F("\342\204\211")); // F } else { parser.print(irTemp.convertToCelsius(averageIRTemp)); parser.print(F("\342\204\203")); // C } parser.print('\n'); parser.print(F(" Type K ")); if (fahrenheit) { parser.print(typeKTemp.convertToFahrenheit(lastKTemperatureReading)); parser.print(F("\342\204\211")); // F } else { parser.print(typeKTemp.convertToCelsius(lastKTemperatureReading)); parser.print(F("\342\204\203")); // C } parser.print('\n'); parser.print(F("<-6>Click to rese")); parser.print(F("t IR average")); parser.print(F("|D~<+3>Plot Readings")); parser.print(F("|H~Set Infrared Averaging")); if (fahrenheit) { parser.print(F("|C~Switch to Celsius (\342\204\203)")); } else { // currently Centigrad parser.print(F("|C~Switch to Fahrenheit (\342\204\211)")); } // parser.print(F("|G~Battery 4.04V")); sendBatteryVolts(); // ============ end of menu item =========== } // send the settings menu void sendIRAvgMenu() { parser.print(F("{?m`")); parser.print(initialNoSamples); parser.print(F("~Set number of IR readings to be averaged")); parser.print(F("|<+1>Average 2 Readings|<+1>Average 4 Readings|<+1>Average 8 Readings|<+1>Average 16 Readings|<+1>Average 32 Readings")); parser.print(F("}")); } void sendBatteryVolts() { parser.print(F("|G~Battery ")); if (batteryVoltsLow) { parser.print(F("Low\n")); } //parser.print("\n"); parser.print(0.0071 * (float)batteryVoltsReading); parser.print("V"); if (batteryVoltsLow) { parser.print(F("\n Recharge NOW")); } } /** * switch between Fahrenheit and Celsius */ void switchScale() { fahrenheit = !fahrenheit; // toggle from fahrenheit to celsius } void checkBatteryVolts() { if ((millis() - batteryVoltsTimer) >= interval) { batteryVoltsTimer += interval; //reset the timer triggerAnalogRead(A0); // start another conversion // if the last conversion not finished or the value not read yet // (by calling getAnalogReading()), then calls to triggerAnalogRead() have no effect. } if (isAnalogReadingAvailable()) { // conversion finished batteryVoltsReading = getAnalogReading(); // pick up reading // Serial.print(F(" Reading:")); // Serial.println(batteryVoltsReading); batteryVoltsLow = (batteryVoltsReading < batteryVoltsLowCount); } } void sendPlotMenu() { parser.print(F("{.")); // start a Menu screen pfod message send_plotMenuContents(); // send the menu contents parser.print(F("}")); // close pfod message } void sendPlotMenuUpdate() { parser.print(F("{:")); // start an Update Menu pfod message send_plotMenuContents(); // send the menu contents parser.print(F("}")); // close pfod message } // modify this method if you need to update the menu to reflect state changes void send_plotMenuContents() { // send menu prompt parser.print(F("Plot Readings")); // send menu items parser.print(F("|a~<+3>Plot Infrared \nand Type K")); if (fahrenheit) { parser.print(F(" (\342\204\211)")); } else { parser.print(F(" (\342\204\203)")); } parser.print(F("|b~<+3>Plot Infrared")); if (fahrenheit) { parser.print(F(" (\342\204\211)")); } else { parser.print(F(" (\342\204\203)")); } parser.print(F("|c~<+3>Plot Type K")); if (fahrenheit) { parser.print(F(" (\342\204\211)")); } else { parser.print(F(" (\342\204\203)")); } if (fahrenheit) { parser.print(F("|d~Switch to Celsius (\342\204\203)")); } else { // currently Centigrad parser.print(F("|d~Switch to Fahrenheit (\342\204\211)")); } // ============ end of menu item =========== } //========================================================================= /* You can remove from here on if you have the pfodParser library installed * and add #include * at the top of this file * and replace the line pfodParser_codeGenerated parser; // create a parser to handle the pfod messages * with pfodParser parser; */ // this is the pfodParser.cpp file with the class renamed pfodParser_codeGenerated and with comments, constants and un-used methods removed pfodParser_codeGenerated::pfodParser_codeGenerated() { io = NULL; init(); } void pfodParser_codeGenerated::init() { argsCount = 0; argsIdx = 0; args[0] = 0; args[1] = 0; parserState = ((byte)0xff); } void pfodParser_codeGenerated::connect(Stream* ioPtr) { init(); io = ioPtr; } void pfodParser_codeGenerated::closeConnection() { init(); } Stream* pfodParser_codeGenerated::getPfodAppStream() { return io; } size_t pfodParser_codeGenerated::write(uint8_t c) { if (!io) { return 1; // cannot write if io null but just pretend to } return io->write(c); } void pfodParser_codeGenerated::flush() { if (!io) { return ; // cannot write if io null but just pretend to } io->flush(); } byte* pfodParser_codeGenerated::getCmd() { return args; } byte* pfodParser_codeGenerated::getFirstArg() { byte* idxPtr = args; while ( *idxPtr != 0) { ++idxPtr; } if (argsCount > 0) { ++idxPtr; } return idxPtr; } byte pfodParser_codeGenerated::getArgsCount() { return argsCount; } byte pfodParser_codeGenerated::parse() { byte rtn = 0; if (!io) { return rtn; } while (io->available()) { int in = io->read(); rtn = parse((byte)in); if (rtn != 0) { // found msg if (rtn == '!') { closeConnection(); } return rtn; } } return rtn; } byte pfodParser_codeGenerated::parse(byte in) { if ((parserState == ((byte)0xff)) || (parserState == ((byte)'}'))) { parserState = ((byte)0xff); if (in == ((byte)'{')) { init(); parserState = ((byte)'{'); } return 0; } if ((argsIdx >= (255 - 2)) && (in != ((byte)'}'))) { init(); return 0; } if (parserState == ((byte)'{')) { parserState = ((byte)0); } if ((in == ((byte)'}')) || (in == ((byte)'|')) || (in == ((byte)'~')) || (in == ((byte)'`'))) { args[argsIdx++] = 0; if (parserState == ((byte)0xfe)) { argsCount++; } if (in == ((byte)'}')) { parserState = ((byte)'}'); // reset state return args[0]; } else { parserState = ((byte)0xfe); } return 0; } args[argsIdx++] = in; return 0; } byte* pfodParser_codeGenerated::parseLong(byte* idxPtr, long *result) { long rtn = 0; boolean neg = false; while ( *idxPtr != 0) { if (*idxPtr == '-') { neg = true; } else { rtn = (rtn << 3) + (rtn << 1); rtn = rtn + (*idxPtr - '0'); } ++idxPtr; } if (neg) { rtn = -rtn; } *result = rtn; return ++idxPtr; } // ============= end generated code =========