// pfod_meter.ino for Arduino Mega2560 // uses Serial to connect to communication shield Bluetooth, BLE or Wifi // and uses Serial1 to receive the multimeter data at 2400baud // #include /* ===== pfod Command for Menu_1 ==== pfodApp msg {.} --> {,<+3>~Remote Multimeter`1000~V1|!A<+7>~Label|B<+6>~Chart} */ // Using Serial and 9600 baud for send and receive // Serial D0 (RX) and D1 (TX) on Arduino Uno, Micro, ProMicro, Due, Mega, Mini, Nano, Pro and Ethernet // This code uses Serial so remove shield when programming the board /* Code generated by pfodDesignerV2 V2.0.2220 (c)2014-2016 Forward Computing and Control Pty. Ltd. NSW Australia, www.forward.com.au This code is not warranted to be fit for any purpose. You may only use it at your own risk. This generated code may be freely used for both private and commercial use Provide this copyright is maintained. */ int swap01(int); // method prototype for slider end swaps float getPlotVarScaling(long varMax, long varMin, float displayMax, float displayMin); // ====================== // this is the pfodParser.h V2 file with the class renamed pfodParser_codeGenerated and with comments, constants and un-used methods removed class pfodParser_codeGenerated: public Print { public: pfodParser_codeGenerated(const char* version); pfodParser_codeGenerated(); void connect(Stream* ioPtr); void closeConnection(); byte parse(); bool isRefresh(); const char* getVersion(); void setVersion(const char* version); void sendVersion(); byte* getCmd(); byte* getFirstArg(); byte* getNextArg(byte *start); byte getArgsCount(); byte* parseLong(byte* idxPtr, long *result); byte getParserState(); void setCmd(byte cmd); void setDebugStream(Print* debugOut); size_t write(uint8_t c); int available(); int read(); int peek(); void flush(); void setIdleTimeout(unsigned long timeout); Stream* getPfodAppStream(); void init(); byte parse(byte in); private: Stream* io; byte emptyVersion[1] = {0}; byte argsCount; byte argsIdx; byte parserState; byte args[255 + 1]; byte *versionStart; byte *cmdStart; bool refresh; const char *version; }; //============= end of pfodParser_codeGenerated.h pfodParser_codeGenerated parser("V1"); // create a parser to handle the pfod messages pfodVC820MeterParser meter; // plotting data variables int plot_1_varMin = 0; int plot_1_var = plot_1_varMin; float plot_1_scaling; float plot_1_varDisplayMin = 0.0; // plot 2 is hidden // plot 3 is hidden unsigned long plotDataTimer = 0; // plot data timer unsigned long PLOT_DATA_INTERVAL = 1000;// mS == 1 sec, edit this to change the plot data interval // the setup routine runs once on reset: void setup() { Serial.begin(9600); for (int i = 3; i > 0; i--) { // wait a few secs to see if we are being programmed delay(1000); } parser.connect(&Serial); // connect the parser to the i/o stream // calculate the plot vars scaling here once to reduce computation plot_1_scaling = getPlotVarScaling(1023, plot_1_varMin, 1023.0, plot_1_varDisplayMin); // <<<<<<<<< Your extra setup code goes here Serial1.begin(2400); meter.connect(&Serial1); } unsigned long validReadingTimer = 0; const unsigned long VALID_READINGS_TIMEOUT = 5000; // 5secs (Hz readings take about 4sec) bool haveValidReadings = true; // set to true when have valid readings int measurementType = meter.NO_READING; // the loop routine runs over and over again forever: void loop() { if (meter.haveReading()) { if (meter.isValid()) { validReadingTimer = millis(); haveValidReadings = true; } int newType = meter.getType(); if (measurementType != newType) { // output new datalogging titles parser.print(F("sec,")); parser.println(meter.getTypeAsStr()); } measurementType = newType; } if ((millis() - validReadingTimer) > VALID_READINGS_TIMEOUT) { haveValidReadings = false; // no new valid reading in last 5 sec } uint8_t cmd = parser.parse(); // parse incoming data from connection // parser returns non-zero when a pfod command is fully parsed if (cmd != 0) { // have parsed a complete msg { to } uint8_t* pfodFirstArg = parser.getFirstArg(); // may point to \0 if no arguments in this msg. long pfodLongRtn; // used for parsing long return arguments, if any if ('.' == cmd) { // pfodApp has connected and sent {.} , it is asking for the main menu if (!parser.isRefresh()) { sendMainMenu(); // send back the menu designed } else { sendMainMenuUpdate(); // menu is cached just send update } // now handle commands returned from button/sliders // } else if('A'==cmd) { // this is a label. pfodApp NEVER sends this cmd -- 'Label' // // in the main Menu of Menu_1 } else if ('B' == cmd) { // user pressed -- 'Chart' // in the main Menu of Menu_1 // return plotting msg. parser.print(F("{=Multimeter|time (secs)|Meter Reading~~~")); parser.print(meter.getTypeAsStr()); parser.print(F("||}")); } else if ('!' == cmd) { // CloseConnection command closeConnection(parser.getPfodAppStream()); } else { // unknown command parser.print(F("{}")); // always send back a pfod msg otherwise pfodApp will disconnect. } } if (haveValidReadings) { sendData(); } // <<<<<<<<<<< Your other loop() code goes here } void closeConnection(Stream *io) { // add any special code here to force connection to be dropped } void sendData() { if ((millis() - plotDataTimer) > PLOT_DATA_INTERVAL) { plotDataTimer = millis(); // restart plot data timer // assign values to plot variables from your loop variables or read ADC inputs plot_1_var = plot_1_varMin; //<<< replace this Min value with your actual data // plot_2_var plot Hidden so no data assigned here // plot_3_var plot Hidden so no data assigned here // send plot data in CSV format parser.print(((float)plotDataTimer) / 1000.0); // time in secs parser.print(','); parser.print(meter.getAsStr()); parser.print(','); // Plot 2 is hidden. No data sent. parser.print(','); // Plot 3 is hidden. No data sent. parser.println(); // end of CSV data record } } float getPlotVarScaling(long varMax, long varMin, float displayMax, float displayMin) { long varRange = varMax - varMin; if (varRange == 0) { varRange = 1; // prevent divide by zero } return (displayMax - displayMin) / ((float)varRange); } void sendMainMenu() { // !! Remember to change the parser version string // every time you edit this method parser.print(F("{,")); // start a Menu screen pfod message // send menu background, format, prompt, refresh and version parser.print(F("<+3>~Remote Multimeter`1000")); parser.sendVersion(); // send the menu version // send menu items parser.print(F("|!A<+7>")); parser.print(F("~")); if (haveValidReadings) { parser.print(meter.getDigits()); parser.print(meter.getScalingAsStr()); parser.print(meter.getTypeAsUnicode()); } else { parser.print(F("- - -")); } parser.print(F("|B<+6>")); parser.print(F("~Chart")); parser.print(F("}")); // close pfod message } void sendMainMenuUpdate() { parser.print(F("{;")); // start an Update Menu pfod message // send menu items parser.print(F("|!A")); parser.print(F("~")); if (haveValidReadings) { parser.print(meter.getDigits()); parser.print(meter.getScalingAsStr()); parser.print(meter.getTypeAsUnicode()); } else { parser.print(F("- - -")); } parser.print(F("}")); // close pfod message // ============ end of menu =========== } /* You can remove from here on if you have the pfodParser V2 library installed from http://www.forward.com.au/pfod/pfodParserLibraries/index.html and add #include at the top of this file and replace the line pfodParser_codeGenerated parser("V1"); // create a parser to handle the pfod messages with pfodParser parser("V1"); */ // this is the pfodParser.cpp V2 file with the class renamed pfodParser_codeGenerated and with comments, constants and un-used methods removed pfodParser_codeGenerated::pfodParser_codeGenerated() { pfodParser_codeGenerated(""); } pfodParser_codeGenerated::pfodParser_codeGenerated(const char *_version) { setVersion(_version); io = NULL; init(); } void pfodParser_codeGenerated::init() { argsCount = 0; argsIdx = 0; args[0] = 0; args[1] = 0; cmdStart = args; versionStart = emptyVersion; parserState = ((byte)0xff); refresh = false; } 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; } return io->write(c); } int pfodParser_codeGenerated::available() { return 0; } int pfodParser_codeGenerated::read() { return 0; } int pfodParser_codeGenerated::peek() { return 0; } void pfodParser_codeGenerated::flush() { if (!io) { return; } // nothing here for now } void pfodParser_codeGenerated::setIdleTimeout(unsigned long timeout) { } void pfodParser_codeGenerated::setCmd(byte cmd) { init(); args[0] = cmd; args[1] = 0; cmdStart = args; versionStart = emptyVersion; } byte* pfodParser_codeGenerated::getCmd() { return cmdStart; } bool pfodParser_codeGenerated::isRefresh() { return refresh; } const char* pfodParser_codeGenerated::getVersion() { return version; } void pfodParser_codeGenerated::setVersion(const char* _version) { version = _version; } void pfodParser_codeGenerated::sendVersion() { print('~'); print(getVersion()); } byte* pfodParser_codeGenerated::getFirstArg() { byte* idxPtr = cmdStart; while (*idxPtr != 0) { ++idxPtr; } if (argsCount > 0) { ++idxPtr; } return idxPtr; } byte* pfodParser_codeGenerated::getNextArg(byte *start) { byte* idxPtr = start; while ( *idxPtr != 0) { ++idxPtr; } if (idxPtr != start) { ++idxPtr; } return idxPtr; } byte pfodParser_codeGenerated::getArgsCount() { return argsCount; } byte pfodParser_codeGenerated::getParserState() { if ((parserState == ((byte)0xff)) || (parserState == ((byte)'{')) || (parserState == 0) || (parserState == ((byte)'}')) ) { return parserState; } return 0; } 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) { if (rtn == '!') { closeConnection(); } return rtn; } } return rtn; } byte pfodParser_codeGenerated::parse(byte in) { if (in == 0xff) { // note 0xFF is not a valid utf-8 char // but is returned by underlying stream if start or end of connection // NOTE: Stream.read() is wrapped in while(Serial.available()) so should not get this // unless explicitlly added to stream buffer init(); // clean out last partial msg if any return 0; } 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 = 0; if (in == ((byte)':')) { refresh = true; return 0; } } if ((in == ((byte)':')) && (versionStart != args)) { args[argsIdx++] = 0; versionStart = args; cmdStart = args + argsIdx; refresh = (strcmp((const char*)versionStart, version) == 0); return 0; } if ((in == ((byte)'}')) || (in == ((byte)'|')) || (in == ((byte)'~')) || (in == ((byte)'`'))) { args[argsIdx++] = 0; if (parserState == ((byte)0xfe)) { argsCount++; } if (in == ((byte)'}')) { parserState = ((byte)'}'); args[argsIdx++] = 0; return cmdStart[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 == ((byte)'-')) { neg = true; } else { rtn = (rtn << 3) + (rtn << 1); rtn = rtn + (*idxPtr - '0'); } ++idxPtr; } if (neg) { rtn = -rtn; } *result = rtn; return ++idxPtr; } void pfodParser_codeGenerated::setDebugStream(Print* debugOut) { } int swap01(int in) { return (in == 0) ? 1 : 0; } // ============= end generated code =========