// DFRobot version of Power Switch // using pins plugged into bread board // i.e. Pin 2 and 3 to drive latching relay // and pin 31 to turn the Latching Relay board power supply on/off // VCC measurement pin 29 // manual push button (optional) pin 30 // // Power Usage: // approx. 60uA advertising, approx 50uA connected updating menu every 10secs. // ie. >3years for 2 x AA alkaline batteries. // /* ===== pfod Command for Power Switch ==== pfodApp msg {.} --> {,<+9>~Power is ?`0~V5|A<+6>`0~Switch Power On~~\~t|B<+6>`0~Switch Power Off~~\~t|!C`775~Battery ~V`1023`0~3~0~t} */ // Using pfod_lp_nrf52_2022 addon for Generic nRF52832 bare modules // Using Arduino V1.8.19 IDE /* Code generated by pfodDesignerV3 V3.0.4181 */ /* (c)2014-2021 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. */ // This sketch needs the pfod_lp_nrf52_2022 add on to be installed and compile using Arduino V1.8.19 IDE // see https://www.forward.com.au/pfod/BLE/LowPower_2022/index.html #include #include #include // download the pfodParser library V3.52+ from http://www.forward.com.au/pfod/pfodParserLibraries/index.html #include bool PowerOn = true; // assume switch is on to start, so that if startup volts are low it can be turned off in ADC_handler(..) int minVoltsCount = 1023 * 24 / 30; // 2.4V // download the libraries from http://www.forward.com.au/pfod/pfodParserLibraries/index.html int swap01(int); // method prototype for slider end swaps pfodParser parser("V6"); // create a parser to handle the pfod messages lp_BLESerial bleSerial; // 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 'Switch Power On' 0= 1= lp_timer cmd_A_timer; // the pulse timer unsigned long cmd_A_PULSE_LENGTH = 100; // 0.1 secs const int cmd_A_pin = 3; // for DFRobot build was 7; // name the output pin for 'Switch Power On' int cmd_B_var; // name the variable for 'Switch Power Off' 0= 1= lp_timer cmd_B_timer; // the pulse timer unsigned long cmd_B_PULSE_LENGTH = 100; // 0.1 secs const int cmd_B_pin = 2; // for DFRobot build was 6; // name the output pin for 'Switch Power Off' int cmd_C_var; // name the variable for 'Battery' lp_timer cmd_C_adcTimer; // ADC timer unsigned long cmd_C_ADC_READ_INTERVAL = 60ul * 1000; // 1min, edit this to change adc read interval const int cmd_C_pin = 29; // name the pin for 'Battery' unsigned long plot_msOffset = 0; // set by {@} response bool clearPlot = false; // set by the {@} response code const int relaySupplyPin = 31; // button debounce int buttonPin = 30; lp_timer debounceTimer; uint32_t debounceTimeOut = 20; // ms int lastButtonPinState = -1; // not set initially int lastButtonState = -1; // not set initially int buttonState = -1; // not set initially // called when pin changes state, pinState is state detected, HIGH or LOW void handlePinLevelChange(int pinState) { if (pinState != lastButtonPinState) { // pin level changed from last one recorded lastButtonPinState = pinState; } // always start debounce on every pin change as may have dropped off some if the queue was full // if queue fills up the last place is ALWAYS filled with the most current pinState, so on debounce timeout the lastButtonPinState will be correct debounceTimer.stop(); // stop last timer if any debounceTimer.startDelay(debounceTimeOut, handleDebounceTimeout); } // button state has settled check if has changed void handleDebounceTimeout() { if (lastButtonPinState != buttonState) { // button state changed buttonState = lastButtonPinState; // input has settled if (buttonState == HIGH) { // toggle switch togglePowerSwitch(); } } } void togglePowerSwitch() { if (PowerOn) { // turn off digitalWrite(relaySupplyPin, HIGH); // set output LOW for relay supply on digitalWrite(cmd_B_pin, HIGH); // set output cmd_B_timer.startDelay(cmd_B_PULSE_LENGTH, cmd_B_checkPulse); // high pulse PowerOn = false; } else { // power off turn on if (cmd_C_var >= minVoltsCount) { // <<<<<<<<< check supply volts digitalWrite(relaySupplyPin, HIGH); // set output LOW for relay supply on digitalWrite(cmd_A_pin, HIGH); // set output cmd_A_timer.startDelay(cmd_A_PULSE_LENGTH, cmd_A_checkPulse); // high pulse PowerOn = true; } else { // ignore this ON cmd if low volts } } } // the setup routine runs once on reset: void setup() { cmd_A_var = 0; pinMode(cmd_A_pin, OUTPUT); // output for 'Switch Power On' is initially LOW, digitalWrite(cmd_A_pin, cmd_A_var); // set output cmd_B_var = 0; pinMode(cmd_B_pin, OUTPUT); // output for 'Switch Power Off' is initially LOW, digitalWrite(cmd_B_pin, cmd_B_var); // set output cmd_C_var = 0; pinMode(relaySupplyPin, OUTPUT); // output for Relay power supply is initially HIGH, digitalWrite(relaySupplyPin, LOW); // set output LOW for relay supply off // set advertised name bleSerial.setName("BLE Power Switch"); // <<<<<<<< set your device name here // begin initialization bleSerial.begin(); parser.connect(&bleSerial); cmd_C_var = analogRead(cmd_C_pin); // do initial read of ADC input ADC_handler(cmd_C_var); // call handler to check voltage level cmd_C_adcTimer.startTimer(cmd_C_ADC_READ_INTERVAL, cmd_C_readADC); // start ADC timer pinMode(buttonPin, INPUT_PULLDOWN); // set compare pin with INPUT_PULLUP or INPUT_PULLDOWN to prevent floating pin triggers // will connect to VDD via button to pull High on button press lp_comparator_start(buttonPin, REF_8_16Vdd, handlePinLevelChange); // always triggers pin LOW first, then if pin HIGH, will trigger HIGH // <<<<<<<<< Your extra setup code goes here } // the loop routine runs over and over again forever: void loop() { sleep(); // wait here for a trigger // check ble serial when triggered by anything 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 } // handle {@} request } else if ('@' == cmd) { // pfodApp requested 'current' time plot_msOffset = millis(); // capture current millis as offset rawdata timestamps clearPlot = true; // clear plot on reconnect as have new plot_msOffset parser.print(F("{@`0}")); // return `0 as 'current' raw data milliseconds // now handle commands returned from button/sliders } else if ('A' == cmd) { // user moved slider -- 'Switch Power On' // in the main Menu of Power Switch // set output based on slider 0= 1= if (cmd_C_var >= minVoltsCount) { // <<<<<<<<< check supply volts digitalWrite(relaySupplyPin, HIGH); // set output LOW for relay supply on digitalWrite(cmd_A_pin, HIGH); // set output cmd_A_timer.startDelay(cmd_A_PULSE_LENGTH, cmd_A_checkPulse); // high pulse PowerOn = true; } else { // ignore this ON cmd if low volts } sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if ('B' == cmd) { // user moved slider -- 'Switch Power Off' // in the main Menu of Power Switch // set output based on slider 0= 1= digitalWrite(relaySupplyPin, HIGH); // set output LOW for relay supply on digitalWrite(cmd_B_pin, HIGH); // set output cmd_B_timer.startDelay(cmd_B_PULSE_LENGTH, cmd_B_checkPulse); // high pulse PowerOn = false; sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. // } else if('C'==cmd) { // this is a label. pfodApp NEVER sends this cmd -- 'Battery' // // in the main Menu of Power Switch // add toggle cmd for single button remote control } else if ('T' == cmd) { // toggle cmd togglePowerSwitch(); parser.print(F("{~Power is\n")); parser.print(PowerOn ? "ON" : "OFF"); parser.print("}"); } else if ('!' == cmd) { // CloseConnection command closeConnection(parser.getPfodAppStream()); } else { // unknown command parser.print(F("{}")); // always send back a pfod msg otherwise pfodApp will disconnect. } } } void closeConnection(Stream * io) { // add any special code here to force connection to be dropped ((lp_BLESerial*)io)->close(); } void cmd_A_checkPulse() { // pulse timer timed out cmd_A_var = 0; // return output to LOW digitalWrite(cmd_A_pin, cmd_A_var); // update output pin digitalWrite(relaySupplyPin, LOW); // set output HIGH for relay supply off } void cmd_B_checkPulse() { // pulse timer timed out cmd_B_var = 0; // return output to LOW digitalWrite(cmd_B_pin, cmd_B_var); // update output pin digitalWrite(relaySupplyPin, LOW); // set output HIGH for relay supply off } void cmd_C_readADC() { // cmd_C_var = analogRead(cmd_C_pin); // read ADC input lp_ADC_start(cmd_C_pin, ADC_handler); // use non-blocking low power adc } void ADC_handler(int count) { // handle lp_ADC result cmd_C_var = count; if ((cmd_C_var < minVoltsCount) && PowerOn) { // copied from cmd B cmd_B_timer.startDelay(cmd_B_PULSE_LENGTH, cmd_B_checkPulse); // high pulse PowerOn = false; } } 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("<+9>~Power is\n")); parser.print(PowerOn ? "ON" : "OFF"); parser.print("`10000"); // request refresh menu every 10sec parser.sendVersion(); // send the menu version // send menu items parser.print(F("|A<+6>")); parser.print('`'); parser.print(cmd_A_var); // output the current state 0 Low or 1 High parser.print(F("~Switch Power\nOn~~\\~t")); // Note the \\ inside the "'s to send \ ... parser.print(F("|B<+6>")); parser.print('`'); parser.print(cmd_B_var); // output the current state 0 Low or 1 High parser.print(F("~Switch Power\nOff~~\\~t")); // Note the \\ inside the "'s to send \ ... parser.print(F("|!C")); parser.print('`'); parser.print(cmd_C_var); // output the current ADC reading parser.print(F("~Battery ~V`1023`0~3~0~t")); parser.print(F("}")); // close pfod message } void sendMainMenuUpdate() { parser.print(F("{;")); // start an Update Menu pfod message parser.print(F("<+9>~Power is\n")); parser.print(PowerOn ? "ON" : "OFF"); parser.print("`10000"); // request refresh menu every 10 sec // send menu items parser.print(F("|A")); parser.print('`'); parser.print(cmd_A_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 0 Low or 1 High parser.print(F("|!C")); parser.print('`'); parser.print(cmd_C_var); // output the current ADC reading parser.print(F("}")); // close pfod message // ============ end of menu =========== } int swap01(int in) { return (in == 0) ? 1 : 0; } // ============= end generated code =========