/* ===== pfod Command for Menu_1 ==== pfodApp msg {.} --> {,<+4>~Arduino Data`1000~V1|!A`775~A0 ~V`4095`0~3.3~0~|!E<-6>~|!B`1~Door is ~~Closed\Open~t|C`0~PWM Setting ~%`4095`0~100~0~|D<+6>`0~Led is ~~Off\On~t|F<+4>~Voltage Plot} */ // Using Teensy 3.2 + Adafruit Adaptor + Adafruit Radio Feather Wing, RFM9x LoRa configured as a radio server (pfodDevice) using RadioHead library for low level support // Select Teensy 3.2 as the board to compile this sketch with. /* Code generated by pfodDesignerV3 V3.0.3410 */ /* * (c)2014-2018 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 * provided this copyright is maintained. */ #include #include // download the libraries from http://www.forward.com.au/pfod/pfodParserLibraries/index.html // pfodParser.zip V3.31+ contains pfodParser, pfodSecurity, pfodDelay, pfodBLEBufferedSerial, pfodSMS and pfodRadio #include #include int swap01(int); // method prototype for slider end swaps float getPlotVarScaling(long varMax, long varMin, float displayMax, float displayMin); // uncomment the following to get debug output to Serial //#define DEBUG // for convience use last byte of LoRa address as nodeID, but need to check it is unique in this situation. // the server address for clients to connect to const uint8_t NODE_ID = 0x68; // this node's address can be any address from 1 to 0xff, the server address for clients to connect to // Change to 434.0 or other frequency, must match the TX's, pfodClient's, freq! AND must match the range allowed in your country. #define RF95_FREQ 915.0 // add your pfod Password here for 128bit security // eg "b0Ux9akSiwKkwCtcnjTnpWp" but generate your own key, "" means no pfod password #define pfodSecurityCode "" // see http://www.forward.com.au/pfod/ArduinoWiFi_simple_pfodDevice/index.html for more information and an example // and QR image key generator. // ====================== // used to suppress warning #define pfod_MAYBE_UNUSED(x) (void)(x) /** Radio sends are broken up into blocks of <255 bytes, with measured tx time of 460mS per 255 byte block so sends from Server (max 1023 bytes) can take upto 2secs to be received at the client Each radio msg is acked. The default ack timeout is 500mS, i.e. random timeout in the range 500mS to 1000mS (use setAckTimeout(mS) to change this) This time allows for another, interfering, radio to finish its attempted transmission After 5 retries the link is marked as failed. (use setNoOfRetries() to change this number) Most pfod commands are very short, <20 bytes (max 254 bytes), but responses from the pfodServer (pfodDevice) can be upto 1023 bytes. Since only one radio can transmit at a time, you should only send RawData from the pfodServer while the pfodClient is waiting for a response That is only send RawData when the server receives a command and before it sends its response. */ //==================================== pfodRHDriver ================ // The class to interface with the low lever radio driver. Must extend from pfodRadioDrive interface // class implementation is at the bottom of this file. class pfodRHDriver : public pfodRadioDriver { public: pfodRHDriver(RHGenericDriver* _driver); // RHGenericDriver is the low lever RadioHead class used to talk to the radio module bool init(); int16_t lastRssi(); int getMode(); uint8_t getMaxMessageLength(); void setThisAddress(uint8_t addr); bool receive(uint8_t* buf, uint8_t* len); bool send(const uint8_t* data, uint8_t len); uint8_t headerTo(); uint8_t headerFrom(); uint8_t headerId(); uint8_t headerFlags(); void setHeaderTo(uint8_t to); void setHeaderFrom(uint8_t from); void setHeaderId(uint8_t id); void setHeaderFlags(uint8_t flags); private: RHGenericDriver* driver; }; //============= end of pfodRHDriver header // defines for RadioHead driver for Teensy 3.2 on Adafruit Adaptor with Adafruit Radio Feather Wing // on RF9x wing wire RST to A, CS to B and IRQ to C #define RFM95_RST 9 // "A" #define RFM95_CS 10 // "B" #define RFM95_INT 4 // "C" // MISO, SCK, MOSI hardwired to the correct pins on the Radio feather wing // MOSI: pin 11 MISO: pin 12 SCK: pin 13 <<<< NOTE DO NOT DRIVE THE LED!! // Singleton instance of the low level radio driver RH_RF95 driver(RFM95_CS, RFM95_INT); pfodRHDriver pfodDriver(&driver); // create the pfodRadioDriver class pfodRadio radio(&pfodDriver, NODE_ID); // the high level pfod radio driver class pfodSecurity parser("V2"); // create a parser with menu version string to handle the pfod messages // set up a ring byte buffer to buffer data points // as can only safely send data after we have recieved a cmd and // pfodApp is waiting for the response // It would use less RAM if we just stored the data points // but pfod library already has a byte ring buffer // and there is RAM to spare on Teensy 3.2, 64K RAM pfodRingBuffer plotDataRingBuffer; // note pfodRadio only as 1024 byte raw data send buffer which discards extra bytes when full const size_t plotDataBufferSize = 1023; uint8_t plotDataBuffer[plotDataBufferSize]; void bufferPlotData(); // method prototype void sendRawData(); // method prototype void manualResetRadio() { // manual radio reset digitalWrite(RFM95_RST, LOW); delay(20); digitalWrite(RFM95_RST, HIGH); delay(100); // wait 0.1 sec } // give the board pins names, if you change the pin number here you will change the pin controlled int cmd_A_var; // name the variable for 'A0' pfodDelay cmd_A_adcTimer; // ADC timer unsigned long cmd_A_ADC_READ_INTERVAL = 1000;// 1sec, edit this to change adc read interval const int cmd_A_pin = A0; // name the pin for 'A0' int cmd_B_var; // name the variable for 'Door is' 0=Closed 1=Open const int cmd_B_pin = 6; // name the input pin for 'Door is' int cmd_C_var; // name the variable for 'PWM Setting' const int cmd_C_pin = 5; // name the output pin for 'PWM Setting' int cmd_D_var; // name the variable for 'Led is' 0=Off 1=On unsigned long cmd_D_pulseStartTime=0; // the time when cmd_D pulse started bool cmd_D_pulseRunning = false; // true when cmd_D pulse running unsigned long cmd_D_PULSE_LENGTH = 10000; // 10.0 secs const int cmd_D_pin = 8; // name the output pin for 'Led is' // 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 pfodDelay plotDataTimer; // 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() { #ifdef DEBUG Serial.begin(115200); delay(100); for (int i = 10; i > 0; i--) { Serial.print(i); Serial.print(' '); delay(500); } Serial.println(); #endif cmd_A_var = 0; cmd_B_var = 0; pinMode(cmd_B_pin, INPUT_PULLUP); // edit this to just pinMode(..,INPUT); if you don't want the internal pullup enabled // or pinMode(..,INPUT_PULLDOWN); if you want an internal pulldown enabled cmd_C_var = 0; analogWrite(cmd_C_pin,cmd_C_var); // set output cmd_D_var = 0; pinMode(cmd_D_pin, OUTPUT); // output for 'Led is' is initially LOW, digitalWrite(cmd_D_pin,cmd_D_var); // set output // set up for Chart // set pinMode for plot pins // calculate the plot vars scaling here once to reduce computation plot_1_scaling = getPlotVarScaling(4095,plot_1_varMin,3.3,plot_1_varDisplayMin); // set default ADC reference and resolution analogWriteRes(12); // pwm 12 bit 0 to 4095 analogReadRes(12); // ADC 12 bit 0 to 4095, default VREF 3.3V // initialize ringBuffer to buffer plot data plotDataRingBuffer.init(plotDataBuffer, plotDataBufferSize); // initialize ring byte buffer // setup radio driver #ifdef DEBUG //radio.setDebugStream(&Serial); // need to enable DEBUG in pfodRadio.cpp file as well //parser.setDebugStream(&Serial); // need to enable DEBUG in pfodSecurity.cpp file as well #endif driver.setPromiscuous(false); // only accept our address from low level driver pinMode(RFM95_RST, OUTPUT); digitalWrite(RFM95_RST, HIGH); bool radioInitialized = false; bool freqChanged = false; bool configSet = false; while ((!radioInitialized) || (!freqChanged) || (!configSet)) { // loop here until radio inits manualResetRadio(); // delays 0.1sec after radioInitialized = radio.init(); if (!radioInitialized) { #ifdef DEBUG Serial.println("LoRa radio init failed"); #endif delay(1000); // pause and then try again continue; } // setup LoRa radio // Defaults after init are 434.0MHz, modulation Bw125Cr45Sf128, +13dbM freqChanged = driver.setFrequency(RF95_FREQ); if (!freqChanged) { #ifdef DEBUG Serial.println("LoRa radio freq set failed"); #endif delay(1000); // pause and then try again continue; } configSet = driver.setModemConfig(RH_RF95::Bw125Cr45Sf128); // default if (!configSet) { #ifdef DEBUG Serial.println("LoRa radio modem config set failed"); #endif delay(1000); // pause and then try again continue; } } #ifdef DEBUG Serial.println("LoRa radio init OK!"); #endif driver.setTxPower(23, false); // set max Tx power driver.setModemConfig(RH_RF95::Bw125Cr45Sf128); // default ~683 bytes/sec radio.listen(); // set as server listening for msg address to NODE_ID parser.connect(&radio, F(pfodSecurityCode)); // connect the parser to the i/o stream cmd_A_adcTimer.start(cmd_A_ADC_READ_INTERVAL); // start ADC timer plotDataTimer.start(PLOT_DATA_INTERVAL); // start plot timer // <<<<<<<<< Your extra setup code goes here } // the loop routine runs over and over again forever: void loop() { 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 } sendRawData(); // can safely send raw data now as other end is listening for response to the command uint8_t* pfodFirstArg = parser.getFirstArg(); // may point to \0 if no arguments in this msg. pfod_MAYBE_UNUSED(pfodFirstArg); // may not be used, just suppress warning long pfodLongRtn; // used for parsing long return arguments, if any pfod_MAYBE_UNUSED(pfodLongRtn); // may not be used, just suppress warning 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 -- 'A0' // // in the main Menu of Menu_1 // } else if('E'==cmd) { // this is a label. pfodApp NEVER sends this cmd -- '' // // in the main Menu of Menu_1 // } else if('B'==cmd) { // this is a label. pfodApp NEVER sends this cmd -- 'Door is' // // in the main Menu of Menu_1 } else if('C'==cmd) { // user moved PWM slider -- 'PWM Setting' // in the main Menu of Menu_1 parser.parseLong(pfodFirstArg,&pfodLongRtn); // parse first arg as a long cmd_C_var = (int)pfodLongRtn; // set variable analogWrite(cmd_C_pin,cmd_C_var); // set PWM output sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if('D'==cmd) { // user moved slider -- 'Led is' // in the main Menu of Menu_1 // set output based on slider 0=Off 1=On parser.parseLong(pfodFirstArg,&pfodLongRtn); // parse first arg as a long cmd_D_var = (int)pfodLongRtn; // set variable digitalWrite(cmd_D_pin,cmd_D_var); // set output if(cmd_D_var == 1) { cmd_D_pulseStartTime = millis(); // high pulse cmd_D_pulseRunning = true; } else { cmd_D_pulseRunning = false; } sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if('F'==cmd) { // user pressed -- 'Voltage Plot' // in the main Menu of Menu_1 // return plotting msg. parser.print(F("{=Voltage at A0|time (secs)|A0~3.3~0.0~Volts||}")); } else if ('!' == cmd) { // CloseConnection command closeConnection(parser.getPfodAppStream()); } else { // unknown command parser.print(F("{}")); // always send back a pfod msg otherwise pfodApp will disconnect. } } cmd_A_readADC(); cmd_B_var = digitalRead(cmd_B_pin); // read digital input cmd_D_checkPulse(); bufferPlotData(); // <<<<<<<<<<< Your other loop() code goes here } void closeConnection(Stream *io) { // nothing special here } /** Send any buffered data */ void sendRawData() { // send all the stored data bytes now if (plotDataRingBuffer.available()) { // new raw data to send while (plotDataRingBuffer.available()) { parser.write(plotDataRingBuffer.read()); } parser.flush(); // send the data now } } void bufferPlotData() { if (plotDataTimer.isFinished()) { plotDataTimer.repeat(); // restart plot data timer, without drift plotDataRingBuffer.markWrite(); // the pfodRingBuffer implements the Stream interface so can just write to it. // assign values to plot variables from your loop variables or read ADC inputs plot_1_var = analogRead(A0); // read input to plot // 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 plotDataRingBuffer.print(((float)plotDataTimer.getStartTime())/1000.0); // time in secs plotDataRingBuffer.print(','); plotDataRingBuffer.print(((float)(plot_1_var-plot_1_varMin)) * plot_1_scaling + plot_1_varDisplayMin); plotDataRingBuffer.print(','); // Plot 2 is hidden. No data sent. plotDataRingBuffer.print(','); // Plot 3 is hidden. No data sent. if (plotDataRingBuffer.availableForWrite() < 2) { // not enought space for terminating // last data record filled buffer and was truncate // so remove the partially written data point // doing the check this way avoids the need to count print bytes plotDataRingBuffer.resetWrite(); // remove last partial record } else { // all the data written and space for the // terminate this data record plotDataRingBuffer.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 cmd_A_readADC() { if (cmd_A_adcTimer.isFinished()) { cmd_A_adcTimer.repeat(); // restart timer, without drift cmd_A_var = analogRead(cmd_A_pin); // read ADC input } } void cmd_D_checkPulse() { if (cmd_D_pulseRunning && ((millis() - cmd_D_pulseStartTime) > cmd_D_PULSE_LENGTH)) { cmd_D_pulseRunning = false; // timer finished cmd_D_var = 0; // return output to LOW digitalWrite(cmd_D_pin,cmd_D_var); // update output pin } } 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("<+4>~Arduino Data`1000")); parser.sendVersion(); // send the menu version // send menu items parser.print(F("|!A")); parser.print('`'); parser.print(cmd_A_var); // output the current ADC reading parser.print(F("~A0 ~V`4095`0~3.3~0~")); parser.print(F("|!E<-6>")); parser.print(F("~")); parser.print(F("|!B")); parser.print('`'); parser.print(cmd_B_var); // output the current state of the input parser.print(F("~Door is ~~Closed\\Open~t")); // Note the \\ inside the "'s to send \ ... parser.print(F("|C")); parser.print('`'); parser.print(cmd_C_var); // output the current PWM setting parser.print(F("~PWM Setting ~%`4095`0~100~0~")); parser.print(F("|D<+6>")); parser.print('`'); parser.print(cmd_D_var); // output the current state 0 Low or 1 High parser.print(F("~Led is ~~Off\\On~t")); // Note the \\ inside the "'s to send \ ... parser.print(F("|F<+4>")); parser.print(F("~Voltage Plot")); 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('`'); parser.print(cmd_A_var); // output the current ADC reading parser.print(F("|B")); parser.print('`'); parser.print(cmd_B_var); // output the current state of the input parser.print(F("|C")); parser.print('`'); parser.print(cmd_C_var); // output the current PWM setting parser.print(F("|D")); parser.print('`'); parser.print(cmd_D_var); // output the current state 0 Low or 1 High parser.print(F("}")); // close pfod message // ============ end of menu =========== } // implementation of pfodRHDriver pfodRHDriver::pfodRHDriver(RHGenericDriver * _driver) { driver = _driver; } bool pfodRHDriver::init() { return driver->init(); } int16_t pfodRHDriver::lastRssi() { return driver->lastRssi(); } int pfodRHDriver::getMode() { return driver->mode(); } uint8_t pfodRHDriver::getMaxMessageLength() { return driver->maxMessageLength(); // it is important this is set } void pfodRHDriver::setThisAddress(uint8_t addr) { driver->setThisAddress(addr); } bool pfodRHDriver::receive(uint8_t* buf, uint8_t* len) { return driver->recv(buf, len); } bool pfodRHDriver::send(const uint8_t* data, uint8_t len) { return driver->send(data, len); } uint8_t pfodRHDriver::headerTo() { return driver->headerTo(); } uint8_t pfodRHDriver::headerFrom() { return driver->headerFrom(); } uint8_t pfodRHDriver::headerId() { return driver->headerId(); } uint8_t pfodRHDriver::headerFlags() { return driver->headerFlags(); } void pfodRHDriver::setHeaderTo(uint8_t to) { driver->setHeaderTo(to); } void pfodRHDriver::setHeaderFrom(uint8_t from) { driver->setHeaderFrom(from); } void pfodRHDriver::setHeaderId(uint8_t id) { driver->setHeaderId(id); } void pfodRHDriver::setHeaderFlags(uint8_t flags) { driver->setHeaderFlags(flags, 0xff); } // end of pfodRHDriver implementation int swap01(int in) { return (in==0)?1:0; } // ============= end generated code =========