/* ===== pfod Command for Menu_1 ==== pfodApp msg {.} --> {,~BLE Nano Control`0~V2|A<+4>`0~Led is ~~Off\On~|H~Chart of A3, A4, A5|C<-3>`0~D5 PWM Setting ~%`255`0~100~0~|D<+5>`0~D6 is ~~Low\High~t|F`0~D7 is ~~Low\High~ |!E<-6>~|!B`1~D4 is ~~D4 Low\D4 High~s} */ // Using RedBearLabs BLE Nano with BLEPeripheral library V0.4.0 // Using Arduino V1.8.2 IDE /* Code generated by pfodDesignerV2 V3.0.3181 */ /* * (c)2014-2017 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. */ // This sketch needs the BLEPeripheral library V0.4.0 from and compile using Arduino V1.8.2 IDE // from https://github.com/sandeepmistry/arduino-BLEPeripheral #include // define pins for RedBeardLab BLE Nano (none used) #define BLE_REQ -1 #define BLE_RDY -1 #define BLE_RST -1 // download the pfodParser library V3.10+ from http://www.forward.com.au/pfod/pfodParserLibraries/index.html // pfodParse.h includes WStream and then defines Stream as WStream to match the Redbear BLE hardware files and to avoid conflict with mbed::Stream #include int swap01(int); // method prototype for slider end swaps float getPlotVarScaling(long varMax, long varMin, float displayMax, float displayMin); // =========== pfodBLESerial definitions const char* localName = "Redbear BLE"; // <<<<<< change this string to customize the adverised name of your board (max 20 chars) class pfodBLESerial : public BLEPeripheral, public Stream { public: pfodBLESerial(unsigned char req, unsigned char rdy, unsigned char rst); void begin(); void poll(); size_t write(uint8_t); size_t write(const uint8_t*, size_t); int read(); int available(); void flush(); int peek(); void close(); bool isConnected(); private: const static uint8_t pfodEOF[1]; const static char* pfodCloseConnection; static const int BLE_MAX_LENGTH = 20; static const int BLE_RX_MAX_LENGTH = 256; static volatile size_t rxHead; static volatile size_t rxTail; volatile static uint8_t rxBuffer[BLE_RX_MAX_LENGTH]; volatile static bool connected; size_t txIdx; uint8_t txBuffer[BLE_MAX_LENGTH]; static void connectHandler(BLECentral& central); static void disconnectHandler(BLECentral& central); static void receiveHandler(BLECentral& central, BLECharacteristic& rxCharacteristic); static void addReceiveBytes(const uint8_t* bytes, size_t len); // ==== create Nordic UART service ========= BLEService uartService = BLEService("6E400001B5A3F393E0A9E50E24DCCA9E"); // create characteristic BLECharacteristic txCharacteristic = BLECharacteristic("6E400003B5A3F393E0A9E50E24DCCA9E", BLENotify , BLE_MAX_LENGTH); // == RX on central (android app) BLECharacteristic rxCharacteristic = BLECharacteristic("6E400002B5A3F393E0A9E50E24DCCA9E", BLEWrite, BLE_MAX_LENGTH); // == TX on central (android app) }; volatile size_t pfodBLESerial::rxHead = 0; volatile size_t pfodBLESerial::rxTail = 0; volatile uint8_t pfodBLESerial::rxBuffer[BLE_RX_MAX_LENGTH]; const uint8_t pfodBLESerial::pfodEOF[1] = {(uint8_t) - 1}; const char* pfodBLESerial::pfodCloseConnection = "{!}"; volatile bool pfodBLESerial::connected = false; // =========== end pfodBLESerial definitions pfodParser parser("V3"); // create a parser to handle the pfod messages pfodBLESerial bleSerial(BLE_REQ, BLE_RDY, BLE_RST); // create a BLE serial connection // 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 'Led is' 0=Off 1=On const int cmd_A_pin = 13; // name the output D13 for 'Led is' int cmd_C_var; // name the variable for 'D5 PWM Setting' const int cmd_C_pin = 5; // name the output D5 for 'D5 PWM Setting' int cmd_D_var; // name the variable for 'D6 is' 0=Low 1=High const int cmd_D_pin = 6; // name the output D6 for 'D6 is' int cmd_F_var; // name the variable for 'D7 is' 0=Low 1=High const int cmd_F_pin = 7; // name the output D7 for 'D7 is' int cmd_B_var; // name the variable for 'D4 is' 0=D4 Low 1=D4 High const int cmd_B_pin = 4; // name the input D4 for 'D4 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; int plot_2_varMin = 0; int plot_2_var = plot_2_varMin; float plot_2_scaling; float plot_2_varDisplayMin = 0.0; int plot_3_varMin = 0; int plot_3_var = plot_3_varMin; float plot_3_scaling; float plot_3_varDisplayMin = 0.0; 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() { for (int i=3; i>0; i--) { // wait a few secs to see if we are being programmed delay(1000); } cmd_A_var = 0; //pinMode(cmd_A_pin, INPUT_PULLUP); pinMode(cmd_A_pin, OUTPUT); // output for 'Led is' is initially LOW, //uncomment INPUT_PULLUP line above and set variable to 1, if you want it initially HIGH digitalWrite(cmd_A_pin,cmd_A_var); // set output cmd_C_var = 0; pinMode(cmd_C_pin, OUTPUT); // output for 'D5 PWM Setting' is initially LOW, analogWrite(cmd_C_pin,cmd_C_var); // set output cmd_D_var = 0; //pinMode(cmd_D_pin, INPUT_PULLUP); pinMode(cmd_D_pin, OUTPUT); // output for 'D6 is' is initially LOW, //uncomment INPUT_PULLUP line above and set variable to 1, if you want it initially HIGH digitalWrite(cmd_D_pin,cmd_D_var); // set output cmd_F_var = 0; //pinMode(cmd_F_pin, INPUT_PULLUP); pinMode(cmd_F_pin, OUTPUT); // output for 'D7 is' is initially LOW, //uncomment INPUT_PULLUP line above and set variable to 1, if you want it initially HIGH digitalWrite(cmd_F_pin,cmd_F_var); // set output 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 // calculate the plot vars scaling here once to reduce computation plot_1_scaling = getPlotVarScaling(1023,plot_1_varMin,3.3,plot_1_varDisplayMin); plot_2_scaling = getPlotVarScaling(1023,plot_2_varMin,3.3,plot_2_varDisplayMin); plot_3_scaling = getPlotVarScaling(1023,plot_3_varMin,3.3,plot_3_varDisplayMin); // set advertised local name and service UUID // begin initialization bleSerial.begin(); parser.connect(&bleSerial); // <<<<<<<<< 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 } 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) { // 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_A_var = swap01((int)pfodLongRtn); // set variable digitalWrite(cmd_A_pin,cmd_A_var); // set output sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if('H'==cmd) { // user pressed -- 'Chart of A3, A4, A5' // in the main Menu of Menu_1 // return plotting msg. parser.print(F("{=Chart Label|time (secs)|A3~3.3~0.0~A3 Volts|A4~3.3~0.0~A4 Vol")); parser.print(F("ts|A5~3.3~0.0~A5 Volts}")); } else if('C'==cmd) { // user moved PWM slider -- 'D5 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 -- 'D6 is' // in the main Menu of Menu_1 // set output based on slider 0=Low 1=High 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 sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if('F'==cmd) { // user moved slider -- 'D7 is' // in the main Menu of Menu_1 // set output based on slider 0=Low 1=High parser.parseLong(pfodFirstArg,&pfodLongRtn); // parse first arg as a long cmd_F_var = (int)pfodLongRtn; // set variable digitalWrite(cmd_F_pin,cmd_F_var); // set output sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. // } 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 -- 'D4 is' // // in the main Menu of Menu_1 } 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_B_var = digitalRead(cmd_B_pin); // read digital input sendData(); // <<<<<<<<<<< Your other loop() code goes here } void closeConnection(Stream *io) { // add any special code here to force connection to be dropped ((pfodBLESerial*)io)->close(); } 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 = analogRead(A3); // read input to plot plot_2_var = analogRead(A4); // read input to plot plot_3_var = analogRead(A5); // read input to plot // send plot data in CSV format parser.print(((float)plotDataTimer)/1000.0); // time in secs parser.print(','); parser.print(((float)(plot_1_var-plot_1_varMin)) * plot_1_scaling + plot_1_varDisplayMin); parser.print(','); parser.print(((float)(plot_2_var-plot_2_varMin)) * plot_2_scaling + plot_2_varDisplayMin); parser.print(','); parser.print(((float)(plot_3_var-plot_3_varMin)) * plot_3_scaling + plot_3_varDisplayMin); 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("~BLE Nano Control`0")); parser.sendVersion(); // send the menu version // send menu items parser.print(F("|A<+4>")); parser.print('`'); parser.print(swap01(cmd_A_var)); // output the current state 0 Low or 1 High (swapped) parser.print(F("~Led is ~~Off\\On~")); // Note the \\ inside the "'s to send \ ... parser.print(F("|H")); parser.print(F("~Chart of A3, A4, A5")); parser.print(F("|C<-3>")); parser.print('`'); parser.print(cmd_C_var); // output the current PWM setting parser.print(F("~D5 PWM Setting ~%`255`0~100~0~")); parser.print(F("|D<+5>")); parser.print('`'); parser.print(cmd_D_var); // output the current state 0 Low or 1 High parser.print(F("~D6 is ~~Low\\High~t")); // Note the \\ inside the "'s to send \ ... parser.print(F("|F")); parser.print('`'); parser.print(cmd_F_var); // output the current state 0 Low or 1 High parser.print(F("~D7 is ~~Low\\High~ ")); // Note the \\ inside the "'s to send \ ... 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("~D4 is ~~D4 Low\\D4 High~s")); // Note the \\ inside the "'s to send \ ... 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(swap01(cmd_A_var)); // output the current state 0 Low or 1 High (swapped) 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("|F")); parser.print('`'); parser.print(cmd_F_var); // output the current state 0 Low or 1 High parser.print(F("|B")); parser.print('`'); parser.print(cmd_B_var); // output the current state of the input parser.print(F("}")); // close pfod message // ============ end of menu =========== } // ========== pfodBLESerial methods pfodBLESerial::pfodBLESerial(unsigned char req, unsigned char rdy, unsigned char rst) : BLEPeripheral(req, rdy, rst) { setLocalName(localName); addAttribute(uartService); setAdvertisedServiceUuid(uartService.uuid()); addAttribute(rxCharacteristic); rxCharacteristic.setEventHandler(BLEWritten, pfodBLESerial::receiveHandler); setEventHandler(BLEConnected, pfodBLESerial::connectHandler); setEventHandler(BLEDisconnected, pfodBLESerial::disconnectHandler); addAttribute(txCharacteristic); }; bool pfodBLESerial::isConnected() { return (connected && txCharacteristic.subscribed()); } void pfodBLESerial::begin() { BLEPeripheral::begin(); } void pfodBLESerial::close() { BLEPeripheral::disconnect(); } void pfodBLESerial::poll() { BLEPeripheral::poll(); } size_t pfodBLESerial::write(const uint8_t* bytes, size_t len) { for (size_t i = 0; i < len; i++) { write(bytes[i]); } return len; // just assume it is all written } size_t pfodBLESerial::write(uint8_t b) { BLEPeripheral::poll(); delay(10); // <<<<<<<<<<<<<< this delay seems to make the writes reliable if (!isConnected()) { return 1; } txBuffer[txIdx++] = b; if ((txIdx == sizeof(txBuffer)) || (b == ((uint8_t)'\n')) || (b == ((uint8_t)'}')) ) { flush(); // send this buffer if full or end of msg or rawdata newline } return 1; } int pfodBLESerial::read() { if (rxTail == rxHead) { return -1; } // note increment rxHead befor writing // so need to increment rxTail befor reading rxTail = (rxTail + 1) % sizeof(rxBuffer); uint8_t b = rxBuffer[rxTail]; return b; } // called as part of parser.parse() so will poll() each loop() int pfodBLESerial::available() { BLEPeripheral::poll(); flush(); // send any pending data now. This happens at the top of each loop() int rtn = ((rxHead + sizeof(rxBuffer)) - rxTail ) % sizeof(rxBuffer); return rtn; } void pfodBLESerial::flush() { if (txIdx == 0) { return; } txCharacteristic.setValue(txBuffer, txIdx); txIdx = 0; BLEPeripheral::poll(); } int pfodBLESerial::peek() { BLEPeripheral::poll(); if (rxTail == rxHead) { return -1; } size_t nextIdx = (rxTail + 1) % sizeof(rxBuffer); uint8_t byte = rxBuffer[nextIdx]; return byte; } void pfodBLESerial::connectHandler(BLECentral& central) { // clear parser with -1 incase partial message left // should not be one addReceiveBytes(pfodEOF, sizeof(pfodEOF)); connected = true; } void pfodBLESerial::disconnectHandler(BLECentral& central) { // parser.closeConnection(); // clear parser with -1 and insert {!} incase connection just lost addReceiveBytes(pfodEOF, sizeof(pfodEOF)); addReceiveBytes((const uint8_t*)pfodCloseConnection, sizeof(pfodCloseConnection)); connected = false; } void pfodBLESerial::addReceiveBytes(const uint8_t* bytes, size_t len) { // note increment rxHead befor writing // so need to increment rxTail befor reading for (size_t i = 0; i < len; i++) { rxHead = (rxHead + 1) % sizeof(rxBuffer); rxBuffer[rxHead] = bytes[i]; } } void pfodBLESerial::receiveHandler(BLECentral& central, BLECharacteristic& rxCharacteristic) { size_t len = rxCharacteristic.valueLength(); const unsigned char *data = rxCharacteristic.value(); addReceiveBytes((const uint8_t*)data, len); } //======================= end pfodBLESerial methods int swap01(int in) { return (in==0)?1:0; } // ============= end generated code =========