/* Rev 1 of Wifi OnOff This sketch works with the Retro-fitted Existing Light Switch with Remote Control (https://www.forward.com.au/pfod/HomeAutomation/BLELightSwitch/index.html) See WiFi_OnOff_sample.ino for a simple on/off cmd sketch Load this sketch to the Adafruit Feather ESP8266 module (Other ESP8266 modules can also be used) This code uses Arduino ESP8266 is V2.3.0. Other versions may not work as expected This code drives ledPin to indicate when connected Steady led == OK Fast Flash == Connection or msg problem. Slow Flash == Battery Low, connect USB supply to recharge. */ /** Wifi_OnOff https://www.forward.com.au/pfod/HomeAutomation/Wifi_OnOff/index.html (c)2017 Forward Computing and Control Pty. Ltd. This code may be freely used for both private and commerical use. Provide this copyright is maintained. */ /************************************************************************* Outline of code setup() Read battery volts. Do it here to get consistent reading before Wifi starts up. Turn on power pin to hold FET power switch on Check pushbuttons, On pushbutton take precidence. If none pressed then CHARGING, start irregular flash and return from setup Start power Off timer, 15sec to power Off Set indicator Led ON solid Connect to Network, if no connection after 6 sec fast flash Led and power of in 15sec. Connect to host/port for control. If cannot connection after 6 sec fast flash Led and power of in 15sec If loose connection later, and not already flashing an error, start fast flash and power off as normal Check battery level. If <3.65V slow flash until power off, in 15sec, but otherwise continue loop() If CHARGING check for user pressing a pushbutton and if so turn off FET power switch. else if power off timeout call powerOff, (close connection wait 1sec, turn flasher off, turn led off, make power pin low to drop supply. return if turn On/Off sequence and not flashing then finished call powerOff. else if CONNECTION_ERROR from processMsg start fast flash and reset power off timer for 15sec else if lost connection start fast flash and reset power off timer for 15sec else call processMsgs to send on/off command and check return if any */ #include #include #include #include char ssid[] = "**** ***"; char password[] = "**** *****"; char staticIP[] = "10.1.1.180"; int portNo = 4989; const int ledPin = 15; // this has 4k7 resistor to GND on Feather ESP8266 board const int pushButton1pin = 14; // ON pushbutton, input with pullup enabled const int pushButton2pin = 13; // OFF pushbutton, input with pullup enabled const int powerControlPin = 12; // output high to keep power on enum flashTypeEnum {NO_FLASH, SLOW_FLASH, FAST_FLASH, CHARGING_FLASH}; volatile flashTypeEnum flashType = NO_FLASH; const float FLASH_TIMER_INTERVAL = 0.1; // 0.1 sec const int FAST_FLASH_PERIOD = 2; // 0.2sec const int FAST_FLASH_OFF = 1; // 50% flash const int SLOW_FLASH_PERIOD = 18; // 1.8sec const int SLOW_FLASH_OFF = 9; // 50% flash volatile int flashCounter = 0; volatile int flashOff = 1; volatile int flashPeriod = 1; // 3.35V == 698 to 704 ==> 3.65 == 761 to 767 counts // 4.19V == 879 to 881 ==> 3.65 == 766 to 767 count const int batteryLowVolts = 763; // 3.65V; int batteryVolts = 0; const size_t MAX_RETURN_MSG_LEN = 255; char returnMsg[MAX_RETURN_MSG_LEN + 1]; uint8_t parseReturn(uint8_t c); void parseReturnInit(); int checkLightState(); // comment out next line to diable DEBUG messages. NOTE: will ALWAYS get ESP8266 boot messages regardless #define DEBUG WiFiClient client; // just one client reused Ticker ledFlasher; bool ledOn = false; int pushButton = -1; // none 0 off, 1 on unsigned long powerOffTimer = 0; const unsigned long POWER_TIMEOUT = 15000; // 15sec turn power off regardless, unless charging enum msgStateEnum {CONNECTED, SENT_INQUIRY, SENT_ON_OFF, CONNECTION_FINISHED, CONNECTION_ERROR, CHARGING}; msgStateEnum msgState = CONNECTED; // processMsgs not called unless connected void clearClientResponse() { parseReturnInit(); // clear out parser while (client.available()) { client.read(); } } char *REQUEST_IMAGE_UPDATE_MSG = "{L2:z`0}"; char *LIGHT_ON = "|C`1~11"; // C`1 -> circle index 1, ~11 -> YELLOW -> ON char *LIGHT_OFF = "|C`1~0"; // C`1 -> circle index 1, ~0 -> BLACK -> OFF char *LIGHT_TOGGLE = "{L2:A~a`0`0`0}"; // C`1 -> circle index 1, ~0 -> BLACK -> OFF unsigned long msgTimer = 0; const unsigned long MSG_TIMEOUT = 5000; // 5 sec int lightState = -1; // not found void processMsgs() { uint8_t cmd = 0; if ((msgState == CONNECTION_ERROR) || (msgState == CONNECTION_FINISHED)) { return; } else if (msgState == CONNECTED) { // clear input clearClientResponse(); msgState = SENT_INQUIRY; client.print(REQUEST_IMAGE_UPDATE_MSG); // ask for image z update for version L2 #ifdef DEBUG Serial.println(REQUEST_IMAGE_UPDATE_MSG); #endif msgTimer = millis(); } else if (msgState == SENT_INQUIRY) { if ((millis() - msgTimer) > MSG_TIMEOUT) { #ifdef DEBUG Serial.println("SENT_INQUIRY return timed out."); #endif msgState = CONNECTION_ERROR; // did not get return msg return; } while (client.available() && (cmd == 0)) { char c = client.read(); #ifdef DEBUG Serial.print(c); #endif cmd = parseReturn(c); } if (cmd != 0) { // have whole msg if (cmd != '+') { #ifdef DEBUG Serial.println(); Serial.println("SENT_INQUIRY return was not {+ .. "); #endif msgState = CONNECTION_ERROR; // did not get {+ msg back return; } msgState = SENT_ON_OFF; lightState = checkLightState(); // look for onOff state if (lightState < 0) { #ifdef DEBUG Serial.println("SENT_INQUIRY return did not contain light state ON/OFF "); #endif msgState = CONNECTION_ERROR; // did not find ligth state in return msg return; } #ifdef DEBUG Serial.println("Light is "); if (lightState == 0) { Serial.println("OFF"); } else { Serial.println("ON"); } #endif // else have light state now switch if ((lightState == 0) && (pushButton == 1)) { // turn on msgTimer = millis(); clearClientResponse(); client.print(LIGHT_TOGGLE); } else if ((lightState == 1) && (pushButton == 0)) { // turn off msgTimer = millis(); clearClientResponse(); client.print(LIGHT_TOGGLE); } else { #ifdef DEBUG Serial.println("Light is in correct state no need to change"); #endif msgState = CONNECTION_FINISHED; // not need to send msg delay(1000); // wait a bit to balance time with send on/off } } } else if (msgState == SENT_ON_OFF) { if ((millis() - msgTimer) > MSG_TIMEOUT) { #ifdef DEBUG Serial.println("SENT_ON_OFF return timed out."); #endif msgState = CONNECTION_ERROR; // did not get return msg return; } while (client.available() && (cmd == 0)) { char c = client.read(); #ifdef DEBUG Serial.print(c); #endif cmd = parseReturn(c); } if (cmd != 0) { // have whole msg if (cmd != '+') { #ifdef DEBUG Serial.println(); Serial.println("SENT_ON_OFF return was not {+ .. "); #endif msgState = CONNECTION_ERROR; // did not get {+ msg back return; } // else got response #ifdef DEBUG Serial.println("Light toggle responded"); #endif msgState = CONNECTION_FINISHED; // not need to send msg } } // else ignore } void handleFlash() { if (flashType == NO_FLASH) { turnLedOn(); // just turn led on } else if (flashType == CHARGING_FLASH) { flashCounter++; if (flashCounter >= 24) { flashCounter = 0; // reset turnLedOn(); // OFF ON OFF 0N OFF 0N OFF } else if ((flashCounter == 4) || (flashCounter == 6) || (flashCounter == 14) || (flashCounter == 15) || (flashCounter == 16) || (flashCounter == 17) || (flashCounter == 18)) { toggleLed(); } } else { // flash flashCounter++; if (flashCounter >= flashPeriod) { flashCounter = 0; // reset turnLedOn(); } else if (flashCounter == flashOff) { toggleLed(); } } } void turnLedOn() { ledOn = true; digitalWrite(ledPin, HIGH); } void toggleLed() { ledOn = !ledOn; if (ledOn) { digitalWrite(ledPin, HIGH); } else { digitalWrite(ledPin, LOW); } } void flashInit() { pinMode(ledPin, OUTPUT); turnLedOn(); // ON setFlash(NO_FLASH); ledFlasher.attach(FLASH_TIMER_INTERVAL, handleFlash); } /** Set flash type */ void setFlash(flashTypeEnum f_type) { if (f_type == NO_FLASH) { flashType = f_type; turnLedOn(); return; } if (f_type == flashType) { return; // no chabnge } if (f_type == CHARGING_FLASH) { flashType = f_type; turnLedOn(); flashCounter = 0; } else if (f_type > flashType) { turnLedOn(); flashCounter = 0; //up the flash speed, the above test ensures the powerOffTimer only reset once on switching to Fast Flash flashType = f_type; switch (flashType) { case FAST_FLASH: powerOffTimer = millis(); // reset power off time so will flash for full 15sec if error flashPeriod = FAST_FLASH_PERIOD; flashOff = FAST_FLASH_OFF; break; case SLOW_FLASH: flashPeriod = SLOW_FLASH_PERIOD; flashOff = SLOW_FLASH_OFF; break; default: break; } } } // pushbutton 1 (pin 14) is ON pushbutton // pushbutton 2 (pin 13) is OFF pushbutton int checkPushButtons() { // set pullups on inputs pinMode(pushButton1pin, INPUT_PULLUP); pinMode(pushButton2pin, INPUT_PULLUP); if (digitalRead(pushButton1pin) == LOW) { return 1; } if (digitalRead(pushButton2pin) == LOW) { return 0; } // else no pushbutton pushed start charging return -1; } void initVars() { ledOn = false; flashInit(); lightState = -1; //pushButton = -1; // don't reset this parseReturnInit(); msgState = CONNECTED; } void setup ( void ) { batteryVolts = analogRead(A0); // == do these first ================= pinMode(powerControlPin, OUTPUT); digitalWrite(powerControlPin, HIGH); // turn power on ON pushButton = checkPushButtons(); // -1 none pushed, 0 OFF, 1 ON pushbutton // ================================== powerOffTimer = millis(); // start power off timer initVars(); // keep this short, turns led on solid #ifdef DEBUG Serial.begin(74880); // for debug will output reboot chars (at 74880 baud) even if DEBUG not defined #endif delay(10); // Don't add any more delays here as the ESP8266 V2.3.0 library Wifi setup does not like it #ifdef DEBUG Serial.print("ADC:"); Serial.println(batteryVolts); #endif if (pushButton < 0) { // just charging msgState = CHARGING; setFlash(CHARGING_FLASH); #ifdef DEBUG Serial.println("CHARGING"); #endif return; } #ifdef DEBUG Serial.println(); Serial.println(F("Starting Setup")); #endif int countC = 0; WiFi.begin(ssid, password); while ((WiFi.status() != WL_CONNECTED) && (countC < 12)) { // max 6 sec. delay(500); #ifdef DEBUG Serial.print("."); #endif countC++; } if (WiFi.status() != WL_CONNECTED) { #ifdef DEBUG Serial.println(); Serial.println(F("Could not connect to Network!")); #endif return; } #ifdef DEBUG Serial.println(); Serial.println(F("Connected to Network!")); #endif setFlash(NO_FLASH); IPAddress hostip(pfodESP8266Utils::ipStrToNum(staticIP)); countC = 0; while ((client.status() != ESTABLISHED) && (countC < 12)) { // max 6 sec to connect client.connect(staticIP, portNo); // note this can return true when connection not complete, will return false later after first write. delay(500); #ifdef DEBUG Serial.print("+"); #endif countC++; } if (client.status() != ESTABLISHED) { #ifdef DEBUG Serial.print("connection failed to "); Serial.print(staticIP); Serial.print(":"); Serial.println(portNo); #endif return; } setFlash(NO_FLASH); // check for battery low here and turn on flasher if (batteryVolts <= batteryLowVolts) { #ifdef DEBUG Serial.print("Battery Low "); Serial.println(batteryVolts); #endif setFlash(SLOW_FLASH); } } void loop() { if (msgState == CHARGING) { // check for button pressed to stop charging int buttonPressed = checkPushButtons(); if ((buttonPressed >= 0) && (pushButton < 0)) { // if user pressed any pushbutton while charging latch it pushButton = buttonPressed; } if (pushButton >= 0) { // user presses any push button while charging // stop and turn off powerOff(); } // NOTE: power will stay on until user releases pushbutton and then turn off in <1sec } else { // NOT CHARGING if ((millis() - powerOffTimer) > POWER_TIMEOUT) { powerOff(); return; } // else not power timed out if (msgState == CONNECTION_FINISHED) { // finished if (flashType == NO_FLASH) { // no error flashing powerOff(); // turn off now } } else if (msgState == CONNECTION_ERROR) { if (flashType != FAST_FLASH) { #ifdef DEBUG Serial.println("msgState == CONNECTION_ERROR"); #endif } setFlash(FAST_FLASH); } else if (!client.connected()) { if (flashType != FAST_FLASH) { #ifdef DEBUG Serial.println("Not connected or CONNECTION_ERROR"); #endif } setFlash(FAST_FLASH); } else { processMsgs(); } } } bool poweredOff = false; void powerOff() { client.stop(); delay(500); // give tcp time to close if (!poweredOff) { #ifdef DEBUG Serial.print("power off after "); Serial.print( (float)(millis() - powerOffTimer) / 1000.0); Serial.println(" sec."); #endif } poweredOff = true; ledFlasher.detach(); digitalWrite(ledPin, LOW); // OFF // // and TURN OFF POWER PIN !! digitalWrite(powerControlPin, LOW); // ON } enum parserStateEnum {LOOKING_FOR_START, IN_MSG, PARSER_FINISHED, PARSER_ERROR}; parserStateEnum parserState = LOOKING_FOR_START; uint8_t pfodMsgStarted = '{'; uint8_t pfodMsgEnd = '}'; size_t returnMsgIdx = 0; void parseReturnInit() { returnMsgIdx = 0; returnMsg[0] = '\0'; parserState = LOOKING_FOR_START; lightState = -1; // not found } /** Look for { ... } msg, max 255, and return first char after { save whole msg in buffer (max 255) */ uint8_t parseReturn(uint8_t in) { if ((parserState == LOOKING_FOR_START) || (parserState == PARSER_FINISHED) ) { parserState = LOOKING_FOR_START; if (in == pfodMsgStarted) { // found { parseReturnInit(); // clean out last cmd parserState = IN_MSG; returnMsg[returnMsgIdx++] = in; // save { } // else ignore this char as waiting for start { return 0; } if (parserState == IN_MSG) { // else have seen { if ((returnMsgIdx >= (MAX_RETURN_MSG_LEN - 1)) && (in != pfodMsgEnd)) { // ignore this msg and reset returnMsg[returnMsgIdx++] = 0; // termimate parserState = PARSER_ERROR; #ifdef DEBUG Serial.print("Return msg not completed after "); Serial.print(MAX_RETURN_MSG_LEN); Serial.print(" bytes."); #endif } else { returnMsg[returnMsgIdx++] = in; } // look for char } if (in == pfodMsgEnd) { returnMsg[returnMsgIdx++] = 0; // termimate parserState = PARSER_FINISHED; // will reset parser on next call } } // now choose return based on current state if (parserState == PARSER_FINISHED) { #ifdef DEBUG Serial.print("parser returned '"); Serial.print((char)returnMsg[1]); Serial.println('\''); #endif return returnMsg[1]; // first char after { } else if (parserState == PARSER_ERROR) { return 0xff; // error return paser will not be rest next call will need to call returnParserInit() } else { return 0; // no complete msg yet } } /** returns 0 for Off, 1 for ON and -1 for error (not found) */ int checkLightState() { char* found = NULL; // check for ON found = strstr(returnMsg, LIGHT_ON); if (found != NULL) { return 1; } found = strstr(returnMsg, LIGHT_OFF); if (found != NULL) { return 0; } // else not found return -1; }