/* Heavily inspired by: https://github.com/idreamsi/SonoffBoilerplate and https://github.com/tzapu/WiFiManager Converted from Blynk to Cayenne by WJ4IoT Preparation: - follow instructions: https://github.com/mirko/SonOTA - create hex from verified sketch and upload though: https://github.com/arendst/Sonoff-Tasmota/wiki - continue inside Arduino IDE Remarks: - no need to connect first to eWelink - DO NOT INSTALL LATEST FIRMWARE eWelink (did once result divice could not use SonOTA) - Make sketch non distructive comment out things like reset(), restart() if you loop into this you are screwed! - preferred testing sketch on easy access ESP8266 device like Wemos D1 R2 - a restart normally goes into error and freeze so it is also a Cayenne button Objectives: - Solderless programming - Over the Air updating sketch - No private credentials in sketch - On the fly change of Wi-Fi and MQTT credentials and store in EEPROM (sometimes required a second time) - Synchronisation of Sonoff Relay, button and Cayenne Actuator (regardless initiator) - Including: - time (from NTP-server) - Dailight Saving Time / Summertime - Scheduling based on sunset and sunrise - Display Wifi signal - uptime in days, hours minutes (bit stupid) - version displayed (bit more stupid) HOLDING PHYSICAL BUTTON SONOFF: - shorter than 1.5 seconds -> toggle relay and sync - between 1.5 - 5.0 seconds -> going into AP-modus - longer -> restart SonOff SONOFF details: - Upload as Generic ESP8266 - Flash mode: DOUT - size: 1MB (64 SPIFFS) - Sonoff header (if you are not working solderless anymore) 1 - vcc 3v3 2 - rx 3 - tx 4 - gnd 5 - gpio 14 */ // standard available in SonOff Basic: #define SONOFF_BUTTON 0 // gpio 0 - button #define SONOFF_RELAY 12 // gpio 12 - relay #define SONOFF_LED 13 // gpio 13 - green led - active low #define SONOFF_INPUT 14 // gpio 14 - pin 5 on header // WiFi OTA Access Point #include #include #include #include //https://github.com/tzapu/WiFiManager (install by Library manager) RTFM // WiFi network & Cayenne authentication info. #include //https://github.com/myDevicesIoT/Cayenne-MQTT-ESP // some Cayenne stuff for Wemos D1 R2 with MQTT #include #define CAYANNE_DEBUG #define CAYANNE_PRINT Serial #include #define EEPROM_SALT 12663 typedef struct { int salt = EEPROM_SALT; char MQTT_username[37] ; // MQTT username char MQTT_Password[41] ; // MQTT password char MQTT_ClientID[37] ; // MQTT Client ID } WMSettings; WMSettings settings; //for (flickering) LED status #include Ticker ticker; // Library to connnect to time-servers in NL https://github.com/SensorsIot/SNTPtime #include SNTPtime NTPnl("nl.pool.ntp.org"); strDateTime dateTime; strDateTime dateTime_wo_DST; int check_DST; //timelord tardis libary for sunset/sunrise Info Tardis http://forum.arduino.cc/index.php?topic=129249.msg972860#msg972860 #include //various variables static long startPress = 0; //used to determine how long fysical SonOff Button is pressed unsigned long lastMillis = 0; //used to delay the Cayenne loop const char *AP_hostname = "SonOff_2"; //SonOff device details const char *AP_password = "123456789"; //SonOff device details int time_now = 0; //set time funtion int time_sunset = 0; //sunset time funtion int time_sunrise = 0; //sunrise time funtion //int time_offset = 0; //to calculate the twilight zone float time_now_d, time_sunset_d, time_sunrise_d; //to display time without a colon only a point int int_scheduling = 1; // actuator to activate internal sceduling //Define variables before setup for routine publishSystemUpTime() : int updays = 0; int updays_old = 0; //gets called when WiFiManager enters configuration mode void configModeCallback (WiFiManager *myWiFiManager) { Serial.println("Entered config mode"); Serial.println(WiFi.softAPIP()); //if you used auto generated SSID, print it Serial.println(myWiFiManager->getConfigPortalSSID()); //entered config mode, make led toggle faster ticker.attach(0.2, tick); } //callback notifying us of the need to save config bool shouldSaveConfig = false; //flag for saving data void saveConfigCallback () { Serial.println("Should save config"); shouldSaveConfig = true; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void tick() { //flash SonOff led on/off int state = digitalRead(SONOFF_LED); // get the current state of GPIO1 pin digitalWrite(SONOFF_LED, !state); // set pin to the opposite state } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void restart() { // likely not working (sytem will hang) see comments: https://github.com/esp8266/Arduino/issues/1622 // based upon a comment in https://github.com/esp8266/Arduino/issues/1722 // WiFi.forceSleepBegin(); wdt_reset(); ESP.restart(); while(1)wdt_reset(); // but not working ESP.reset(); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void reset() { //reset wifi credentials WiFi.disconnect(); delay(1000); //ESP.reset(); ESP.restart(); delay(5000); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void toggle(){ int value = digitalRead(SONOFF_LED); digitalWrite(SONOFF_RELAY, value); delay(500); Sync_LEDs(); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void Sync_LEDs() { int value = digitalRead(SONOFF_RELAY); Cayenne.virtualWrite(0, value); digitalWrite(SONOFF_LED, !value ); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void PRESS_SONOFF_BUTTON() { long duration = 0; // reset duration long StartPress = millis(); // required to calculate how long SonOff button is pressed Cayenne.virtualWrite(51, 0); //where ended? do { duration = millis() - StartPress; //how long is the button pressed? } while( digitalRead(SONOFF_BUTTON) == LOW ); if ( digitalRead(SONOFF_BUTTON) == HIGH ){ // button released again if (duration < 1500) { Serial.println("short press - toggle relay"); Cayenne.virtualWrite(51, 1); //where ended? toggle(); } else if (duration < 7000) { Serial.println("medium press - reset"); Cayenne.virtualWrite(51, 2); //where ended? reset(); } else if (duration < 60000) { Serial.println("long press - reset settings"); Cayenne.virtualWrite(51, 3); //where ended? //restart(); //goes automatic in a restart? } } Cayenne.virtualWrite(50, duration); //duration } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void publishSystemUpTime(){ /* //Define variables before setup for routine publishSystemUpTime(): int updays = 0; int updays_old = 0; */ //source: http://community.mydevices.com/t/system-uptime-millis-to-dd-hh-mm-code-optimization-question-esp8266/4434 long millisecs = millis(); int systemUpTimeDy = int((millisecs / (1000*60*60*24)) % 365); float uptime = int((millis() / (1000*60*60)) % 24) + int((millis() / (1000*60)) % 60) / 100.0; if ( updays > updays_old + systemUpTimeDy ) { updays_old = updays_old + updays; } updays = updays_old + systemUpTimeDy; /* Serial.print("millisecs : "); Serial.println(millisecs); Serial.print("uptime : "); Serial.println(uptime); Serial.print("updays_old : "); Serial.println(updays_old); Serial.print("systemUpTimeDy : "); Serial.println(systemUpTimeDy); Serial.print("updays : "); Serial.println(updays); */ Cayenne.virtualWrite(37, uptime); Cayenne.virtualWrite(36, updays); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void Timeroutine() { // first parameter: Time zone; second parameter: 1 for European summer time; 2 for US daylight saving time (not implemented yet) dateTime = NTPnl.getTime(1.0, 1); // get time from internal clock //NTPnl.printDateTime(dateTime); time_now = dateTime.hour * 60 + dateTime.minute; time_now_d = dateTime.hour * 1.0 + ( dateTime.minute / 100.0); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void Time_DST_refresh(){ //monthly reset of time based upon NTP-servers Serial.println("inside Time_DST_refresh()"); // NTP-server part Serial.println("Connecting to NTP-server NL"); while (!NTPnl.setSNTPtime()) Serial.print("."); // set internal clock Serial.print("Time set : "); // first parameter: Time zone; second parameter: 1 for European summer time; 2 for US daylight saving time (not implemented yet) dateTime = NTPnl.getTime(1.0, 1); // get time from internal clock NTPnl.printDateTime(dateTime); byte actualHour = dateTime.hour; /* byte actualMinute = dateTime.minute; byte actualsecond = dateTime.second; int actualyear = dateTime.year; byte actualMonth = dateTime.month; byte actualday = dateTime.day; byte actualdayofWeek = dateTime.dayofWeek; */ // DST-part dateTime_wo_DST = NTPnl.getTime(1.0, 0); // get time from internal clock with out DST correction byte Hour_wo_DST = dateTime_wo_DST.hour; if (Hour_wo_DST == dateTime.hour) { check_DST = 0; } else { check_DST = 1; } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void sunsetrise() { /* Calculate when sunrises and sunsets based upon GSP-coordinates and date of the year * using TimeLord.h library. * dates are stores as integer and float whereas float simulates a time-notation without semi-column. */ float const LONGITUDE = 4.26; //change this according to your own location float const LATITUDE = 52.08; //ditto TimeLord tardis; tardis.TimeZone((60) + check_DST * 60); // tell TimeLord what timezone your RTC / NTP-server is synchronized to. You can ignore DST // as long as the RTC never changes back and forth between DST and non-DST tardis.Position(LATITUDE, LONGITUDE); // tell TimeLord where in the world we are //byte today[] = { 0, 0, 12, 21, 8, 2017 }; // store manual date (at noon) in an array for TimeLord to use byte today[] = { 0, 0, 12, dateTime.day, dateTime.month, dateTime.year }; // store RTC date (at noon) in an array for TimeLord to use if (tardis.SunRise(today)) // if the sun will rise today (it might not, in the [ant]arctic) { time_sunrise = today[tl_hour] * 60 + today[tl_minute]; time_sunrise_d = today[tl_hour] * 1.0 + today[tl_minute] / 100.0; } if (tardis.SunSet(today)) // if the sun will set today (it might not, in the [ant]arctic) { time_sunset = today[tl_hour] * 60 + today[tl_minute]; time_sunset_d = today[tl_hour] * 1.0 + today[tl_minute] / 100.0; } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void obsolete_scheduling() { // Serial.println("inside obsolete_scheduling"); // relay on at sunset !!!! if( time_now == time_sunset ) { digitalWrite( SONOFF_RELAY, HIGH ); Sync_LEDs(); } // relay off at sunrise !!!! if( time_now == time_sunrise ) { digitalWrite( SONOFF_RELAY, LOW ); Sync_LEDs(); } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void setup(){ Serial.begin(115200); //define pin modes pinMode(SONOFF_BUTTON, INPUT); //setup button pinMode(SONOFF_RELAY, OUTPUT); //setup relay pinMode(SONOFF_LED, OUTPUT); // start ticker with 0.5 because we start in AP mode and try to connect ticker.attach(0.6, tick); WiFiManager wifiManager; //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode wifiManager.setAPCallback(configModeCallback); //custom params EEPROM.begin(512); EEPROM.get(0, settings); EEPROM.end(); Serial.println("EEPROM settings are : "); Serial.println(settings.MQTT_username); Serial.println(settings.MQTT_Password); Serial.println(settings.MQTT_ClientID); WiFiManagerParameter custom_Cayenne_text("Cayenne config.
"); wifiManager.addParameter(&custom_Cayenne_text); WiFiManagerParameter custom_Cayenne_Username("Cayenne-Username", "Cayenne Username", settings.MQTT_username, 37); wifiManager.addParameter(&custom_Cayenne_Username); WiFiManagerParameter custom_Cayenne_Password("Cayenne-Password", "Cayenne Password", settings.MQTT_Password, 41); wifiManager.addParameter(&custom_Cayenne_Password); WiFiManagerParameter custom_Cayenne_ClientID("Cayenne-ClientID", "Cayenne ClientID", settings.MQTT_ClientID, 37); wifiManager.addParameter(&custom_Cayenne_ClientID); //set config save notify callback wifiManager.setSaveConfigCallback(saveConfigCallback); if (!wifiManager.autoConnect(AP_hostname, AP_password)) { Serial.println("failed to connect and hit timeout"); //reset and try again, or maybe put it to deep sleep ESP.reset(); delay(1000); } //save the custom parameters to FS if (shouldSaveConfig) { Serial.println("Saving config"); strcpy(settings.MQTT_username, custom_Cayenne_Username.getValue()); strcpy(settings.MQTT_Password, custom_Cayenne_Password.getValue()); strcpy(settings.MQTT_ClientID, custom_Cayenne_ClientID.getValue()); Serial.println(settings.MQTT_username); Serial.println(settings.MQTT_Password); Serial.println(settings.MQTT_ClientID); EEPROM.begin(512); EEPROM.put(0, settings); EEPROM.end(); } // standard lenght of Cayenne MQTT settings (?) are 36 + 40 + 36 = 112 so if the total size not equal to 112 reset (go into AP-mode) int sizeme = strlen(settings.MQTT_username) + strlen(settings.MQTT_Password) + strlen(settings.MQTT_ClientID); Serial.print("lenght setting MQTT strings : "); Serial.println(sizeme); if ( sizeme != 112) { reset(); } Serial.println(settings.MQTT_username); Serial.println(settings.MQTT_Password); Serial.println(settings.MQTT_ClientID); Serial.print("Connected to: "); Serial.print(WiFi.SSID()); Serial.print(", Signal: "); Serial.println(WiFi.RSSI()); delay(1000); //Cayenne.begin(settings.MQTT_username, settings.MQTT_Password, settings.MQTT_ClientID); // we are already logged to Wi-Fi so no need for ssid, wifiPassword Cayenne.begin(settings.MQTT_username, settings.MQTT_Password,"04eb62d0-ea5e-11e7-848a-61efd1c01e7d"); // we are already logged to Wi-Fi so no need for ssid, wifiPassword //OTA "Over The Air" ArduinoOTA.onStart([]() { Serial.println("Start OTA"); }); ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); ArduinoOTA.setHostname(AP_hostname); ArduinoOTA.setPassword(AP_password); ArduinoOTA.begin(); //if you get here you have connected to the WiFi Serial.println("connected..."); ticker.detach(); //set time, DST, sunset and sunrise Timeroutine(); Time_DST_refresh(); sunsetrise(); Cayenne.virtualWrite(40, time_now_d); Cayenne.virtualWrite(43, check_DST, "digital_sensor", "d"); Cayenne.virtualWrite(38, time_sunset_d); Cayenne.virtualWrite(39, time_sunrise_d); Sync_LEDs(); //synchronise based upon status relay LED Sonoff and Actuator Cayenne.virtualWrite(31, 0.51); //display version of this sketch // Set internal scheduling to true Serial.print("int_scheduling : " ); Serial.println(int_scheduling); Cayenne.virtualWrite(26, int_scheduling); Serial.println("done setup"); } void loop(){ ArduinoOTA.handle(); //ota loop Cayenne.loop(); if (digitalRead(SONOFF_BUTTON) == LOW) { // button on SonOff is pressed PRESS_SONOFF_BUTTON(); } //Publish data every 10 seconds (60000 milliseconds). Change this value to publish at a different interval. if (millis() - lastMillis > 10000) { lastMillis = millis(); //Misc Cayenne.virtualWrite(32, WiFi.RSSI()); //display signal WiFi connection Serial.println(WiFi.RSSI()); //source: https://diyprojects.io/portable-wifi-scanner-oled-display-esp8266-signal-strength-connection-test-server/#.Whacbjco-Uk //refresh present time Timeroutine(); Cayenne.virtualWrite(40, time_now_d); if ( dateTime.dayofWeek == 1 && dateTime.hour == 3 && dateTime.minute == 0 ) { // weekly reset of time based upon NTP-servers and DST-check Time_DST_refresh(); Cayenne.virtualWrite(43, check_DST, "digital_sensor", "d"); } if ( dateTime.hour == 0 && dateTime.minute == 0 ) { // daily reset of sunrise and sunsut sunsetrise(); Cayenne.virtualWrite(38, time_sunset_d); Cayenne.virtualWrite(39, time_sunrise_d); } publishSystemUpTime(); //calculate and display system up time Serial.print("int_scheduling : " ); Serial.println(int_scheduling); if ( int_scheduling == 1) { // is the internal scheduling requested? obsolete_scheduling(); } } } CAYENNE_IN(0) { toggle() ; } // toggle relay and sync leds CAYENNE_IN(1) { restart() ; } // do not use!! CAYENNE_IN(2) { reset() ; } // into AP modes CAYENNE_IN(26) { digitalWrite(int_scheduling, getValue.asInt()); } // set internal scheduling