
/* ===== pfod Command for Menu_1 ====
  pfodApp msg {.} --> {,<bg n><b><+4>~Arduino Data`1000~V3|!A`775~A0 ~V`4095`0~3.3~0~|!E<-6>~|!B<b><+4>`1~Door is ~~Closed\Open~t|!F<-6>~|C`0~PWM Setting ~%`255`0~100~0~|D<bg s><b><+6>`0~LED is ~~Off\On~t|G<+4>~Voltage Plot}
*/
// Using Adafruit Feather M0 RFM9x LoRa configured as a radio server (pfodDevice) using RadioHead library for low level support
// Select Adafruit Feather MO as the board to compile this sketch with.

/* Code generated by pfodDesignerV3 V3.0.3400
*/
/*
   (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
   provide this copyright is maintained.
*/

#include <SPI.h>
#include <RH_RF95.h>
// download the libraries from http://www.forward.com.au/pfod/pfodParserLibraries/index.html
// pfodParser.zip V3.26+ contains pfodParser, pfodSecurity, pfodBLEBufferedSerial, pfodSMS and pfodRadio

#include <pfodRadio.h>
#include <pfodSecurity.h>
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 Feather M0
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 3

// 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("V4"); // create a parser with menu version string to handle the pfod messages
pfodDwgs dwgs(&parser);  // drawing support
// Commands for drawing controls only need to unique per drawing, you can re-use cmds 'a' in another drawing.

// 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 Feather M0, 32K 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

// 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'
unsigned long cmd_A_adcStartTime = 0; // 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 = 13; // 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
unsigned long plotDataTimer = 0; // plot data timer
unsigned long PLOT_DATA_INTERVAL = 1000;// mS == 1 sec, edit this to change the plot data interval

void manualResetRadio() {
  // manual radio reset
  digitalWrite(RFM95_RST, LOW);
  delay(20);
  digitalWrite(RFM95_RST, HIGH);
  delay(100); // wait 0.1 sec
}

// 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
  analogReference(AR_DEFAULT); // 3.3V
  analogReadResolution(12); // 12 bit 0 to 4095
  // 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
  // low level drive reset pin setup
  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
  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

  // <<<<<<<<< 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('F'==cmd) { // this is a label. pfodApp NEVER sends this cmd -- ''
      //      // 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 ('G' == 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 ((millis() - plotDataTimer) > PLOT_DATA_INTERVAL) {
    plotDataTimer = millis(); // restart plot data timer
    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) / 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 <CR><LF>
      // 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 <CR><LF>
      // 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 ((millis() - cmd_A_adcStartTime) > cmd_A_ADC_READ_INTERVAL) {
    cmd_A_adcStartTime = millis(); // restart timer
    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("<bg n><b><+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<b><+4>"));
  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("|!F<-6>"));
  parser.print(F("~"));
  parser.print(F("|C"));
  parser.print('`');
  parser.print(cmd_C_var); // output the current PWM setting
  parser.print(F("~PWM Setting ~%`255`0~100~0~"));
  parser.print(F("|D<bg s><b><+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("|G<+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 =========

