https://github.com/arendst/Sonoff-MQTT-OTA-Arduino
authorDobrica Pavlinusic <dpavlin@rot13.org>
Sun, 31 Jul 2016 09:19:39 +0000 (11:19 +0200)
committerDobrica Pavlinusic <dpavlin@rot13.org>
Sun, 31 Jul 2016 09:21:45 +0000 (11:21 +0200)
sonoff/_releasenotes.ino [new file with mode: 0644]
sonoff/sonoff.ino [new file with mode: 0644]
sonoff/support.ino [new file with mode: 0644]
sonoff/user_config.h [new file with mode: 0644]

diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino
new file mode 100644 (file)
index 0000000..fb43e79
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 1.0.16 20160729
+ * Moved wifi, rtc, syslog and config to support.ino
+ * Fixed button action when buttontopic is used. Introduced with 1.0.15
+ * Better buffer overflow checks (strlcpy)
+ * 
+ * 1.0.15 20160728
+ * Removed pubsubclient config changes from sonoff.ino as it doesn't work
+ *   reapply MQTT_MAX_PACKET_SIZE 256 and MQTT_KEEPALIVE 120 to PubSubClient.h
+ * Add status 0 option displaying all status messages
+ * Change MQTT_MAX_PACKET_SIZE from 1024 to 256
+ * Add buffer overflow checks (snprintf and strncpy)
+ * Implemented common string sizes
+ *
+ * 1.0.14 20160722
+ * Seperate user config from sonoff.ino to user_config.h (pucebaboon)
+ * Change defaults from sidnas2 to domus1
+ * Add MQTT status message as status 6 (pucebaboon)
+ * Add status type to message (pucebaboon)
+ * Add pubsubclient config changes to sonoff.ino (pucebaboon)
+ * 
+ * 1.0.13 20160702
+ * Add Ledstate 1 option to show power state on led
+ *
+ * 1.0.12 20160529
+ * Allow disable of button topic using "0"
+ * 
+ * 1.0.11 20160524
+ * Provide button response if MQTT connection lost
+ * 
+ * 1.0.10 20160520
+ * Add optional button topic to assist external MQTT clients
+ * Change version notation
+ * Reset default values
+ * 
+ * 1.0.9  20160503
+ * Add more blinks
+ * Add reset 2 option erasing flash
+ * Add status 5 option displaying network info
+ * Add syslog check for Wifi connection
+ * Resize mqtt_publish log array
+ * Change Wifi smartconfig active from 100 to 60 seconds
+ * Update Wifi initialization
+ * 
+ * 1.0.8  20160430
+ * Remove use of Wifi config data from SDK
+ * Add status 3 (syslog info) and status 4 (flash info)
+ * Add restart option to button (5 quick presses)
+ * 
+ * 1.0.7  20160420
+ * Add UDP syslog support
+ * Change HOST command to MQTTHOST command
+ * Add commands SYSLOG, SERIALLOG and LOGHOST
+ * Change hostname to lower case to distinguise between open-sdk version
+ * Add support for ESP-12F used in my modified wkaku power socket switch
+ * Fix timezone command
+ * Add RTC month names for future use
+ * Modify button code
+ * Remove initialization errors by better use of MQTT loop
+ * 
+ * 1.0.6  20160406
+ * Removed Wifi AP mode
+ * Add test for Arduino IDE version >= 1.6.8
+ * Fix RTC time sync code
+ * 
+ * 1.0.5  20160310
+ * Initial public release
+ * Show debug info by selecting option from IDE Tools Debug port: Serial
+ */
diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino
new file mode 100644 (file)
index 0000000..31e910b
--- /dev/null
@@ -0,0 +1,693 @@
+/*
+ * Sonoff and Wkaku by Theo Arends
+ * 
+ * ---------------------------------------------
+ * >>>> Tools - Flash size: 1M (64K SPIFFS) <<<<
+ * ---------------------------------------------
+ *
+ * ESP-12F connections (Wkaku)
+ * 3V3                                                     5V
+ *                   |-------------------|       |---------|
+ *  |                |   -------------   |    |1N4001|  |Relay|
+ *  |                | -|          Tx |- |       |---------|
+ *  |                | -|          Rx |- |                /
+ *  |-------------------| En          |- |---| 1k|------|<  BC547B
+ *  |                | -|             |-                  \
+ *  |                | -|        IO00 |------|Switch|------|
+ *  |                ---| IO12   IO02 |--- LED (ESP-12E/F) |
+ *  |---| 1k|---|LED|---| IO13   IO15 |------|10k|---------|
+ *  |-------------------| Vcc     Gnd |--------------------|
+ *                       -------------                     |
+ *                        | | | | | |                     Gnd
+*/
+
+#define VERSION                0x01001000   // 1.0.16
+
+#define LOG_LEVEL_NONE         0
+#define LOG_LEVEL_ERROR        1
+#define LOG_LEVEL_INFO         2
+#define LOG_LEVEL_DEBUG        3
+#define LOG_LEVEL_DEBUG_MORE   4
+
+#include "user_config.h"
+
+#define SERIAL_IO                           // Enable serial command line
+#define STATES                 10           // loops per second
+#define MQTT_RETRY_SECS        10           // Seconds to retry MQTT connection
+
+//#define LED_PIN                2            // GPIO 2 = Blue Led (0 = On, 1 = Off) - ESP-12
+#define LED_PIN                13           // GPIO 13 = Green Led (0 = On, 1 = Off) - Sonoff
+//#define LED_PIN                16           // NodeMCU
+#define REL_PIN                12           // GPIO 12 = Red Led and Relay (0 = Off, 1 = On)
+#define KEY_PIN                0            // GPIO 00 = Button
+#define PRESSED                0
+#define NOT_PRESSED            1
+
+#define WIFI_STATUS            0
+#define WIFI_SMARTCONFIG       1
+
+#define TOPSZ                  40           // Max number of characters in topic string
+#define MESSZ                  200          // Max number of characters in message string (Syntax string)
+#define LOGSZ                  80           // Max number of characters in log string
+
+#include <ESP8266WiFi.h>
+#include <ESP8266HTTPClient.h>
+#include <ESP8266httpUpdate.h>
+#include <PubSubClient.h>
+
+extern "C" uint32_t _SPIFFS_start;
+
+struct SYSCFG {
+  unsigned long cfg_holder;
+  unsigned long saveFlag;
+  unsigned long version;
+  byte          seriallog_level;
+  byte          syslog_level;
+  char          syslog_host[32];
+  char          sta_ssid[32];
+  char          sta_pwd[64];
+  char          otaUrl[80];
+  char          mqtt_host[32];
+  char          mqtt_grptopic[32];
+  char          mqtt_topic[32];
+  char          mqtt_topic2[32];
+  char          mqtt_subtopic[32];
+  int8_t        timezone;
+  uint8_t       power;
+  uint8_t       ledstate;
+} sysCfg;
+
+struct TIME_T {
+  uint8_t       Second;
+  uint8_t       Minute;
+  uint8_t       Hour;
+  uint8_t       Wday;   // day of week, sunday is day 1
+  uint8_t       Day;
+  uint8_t       Month;
+  char          MonthName[4];
+  uint16_t      Year;
+  unsigned long Valid;
+} rtcTime;
+
+char Version[16];
+char Hostname[32];
+uint8_t mqttcounter = 0;
+unsigned long timerxs = 0, timersec = 0;
+int state = 0;
+int otaflag = 0;
+int restartflag = 0;
+int smartconfigflag = 0;
+int heartbeatflag = 0;
+int heartbeat = 0;
+
+WiFiClient espClient;
+PubSubClient mqttClient(espClient);
+WiFiUDP portUDP;   // syslog
+
+int blinks = 1;
+uint8_t blinkstate = 1;
+
+uint8_t lastbutton = NOT_PRESSED;
+uint8_t holdcount = 0;
+uint8_t multiwindow = 0;
+uint8_t multipress = 0;
+
+/********************************************************************************************/
+
+void mqtt_publish(const char* topic, const char* data)
+{
+  char log[TOPSZ+MESSZ];
+  
+  mqttClient.publish(topic, data);
+  snprintf_P(log, sizeof(log), PSTR("MQTT: %s = %s"), strchr(topic,'/')+1, data);     // Skip topic prefix
+  addLog(LOG_LEVEL_INFO, log);
+  mqttClient.loop();  // Solve LmacRxBlk:1 messages
+  blinks++;
+}
+
+void mqtt_connected()
+{
+  char stopic[TOPSZ], svalue[TOPSZ];
+
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/#"), SUB_PREFIX, sysCfg.mqtt_topic);
+  mqttClient.subscribe(stopic);
+  mqttClient.loop();  // Solve LmacRxBlk:1 messages
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/#"), SUB_PREFIX, sysCfg.mqtt_grptopic);
+  mqttClient.subscribe(stopic);
+  mqttClient.loop();  // Solve LmacRxBlk:1 messages
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/"MQTT_CLIENT_ID"/#"), SUB_PREFIX, ESP.getChipId());  // Fall back topic
+  mqttClient.subscribe(stopic);
+  mqttClient.loop();  // Solve LmacRxBlk:1 messages
+
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/NAME"), PUB_PREFIX, sysCfg.mqtt_topic);
+  snprintf_P(svalue, sizeof(svalue), PSTR("Sonoff switch"));
+  mqtt_publish(stopic, svalue);
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/VERSION"), PUB_PREFIX, sysCfg.mqtt_topic);
+  snprintf_P(svalue, sizeof(svalue), PSTR("%s"), Version);
+  mqtt_publish(stopic, svalue);
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/FALLBACKTOPIC"), PUB_PREFIX, sysCfg.mqtt_topic);
+  snprintf_P(svalue, sizeof(svalue), PSTR(MQTT_CLIENT_ID), ESP.getChipId());
+  mqtt_publish(stopic, svalue);
+}
+
+void mqtt_reconnect()
+{
+  char stopic[TOPSZ], svalue[TOPSZ], log[LOGSZ];
+
+  mqttcounter = MQTT_RETRY_SECS;
+  addLog(LOG_LEVEL_INFO, "MQTT: Attempting connection");
+  snprintf(svalue, sizeof(svalue), MQTT_CLIENT_ID, ESP.getChipId());
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/lwt"), PUB_PREFIX, sysCfg.mqtt_topic);
+  if (mqttClient.connect(svalue, MQTT_USER, MQTT_PASS, stopic, 0, 0, "offline")) {
+    addLog(LOG_LEVEL_INFO, "MQTT: Connected");
+    mqttcounter = 0;
+    mqtt_connected();
+  } else {
+    snprintf_P(log, sizeof(log), PSTR("MQTT: Connect failed, rc %d. Retry in %d seconds"), mqttClient.state(), mqttcounter);
+    addLog(LOG_LEVEL_DEBUG, log);
+  }
+}
+
+void mqttDataCb(char* topic, byte* data, unsigned int data_len)
+{
+  int i, grpflg = 0;
+  char *str, *p, *mtopic = NULL, *type = NULL;
+  char stopic[TOPSZ], svalue[MESSZ];
+
+  int topic_len = strlen(topic);
+  char topicBuf[topic_len +1]; 
+  char dataBuf[data_len +1]; 
+  char dataBufUc[data_len +1]; 
+  
+  memcpy(topicBuf, topic, topic_len);
+  topicBuf[topic_len] = 0;
+
+  memcpy(dataBuf, data, data_len);
+  dataBuf[data_len] = 0;
+
+  snprintf_P(svalue, sizeof(svalue), PSTR("MQTT: Receive topic %s, data %s"), topicBuf, dataBuf);
+  addLog(LOG_LEVEL_DEBUG, svalue);
+
+  i = 0;
+  for (str = strtok_r(topicBuf, "/", &p); str && i < 3; str = strtok_r(NULL, "/", &p)) {
+    switch (i++) {
+    case 0:  // cmnd
+      break;
+    case 1:  // Topic / GroupTopic / DVES_123456
+      mtopic = str;
+      break;
+    case 2:  // Text
+      type = str;
+    }
+  }
+  if (!strcmp(mtopic, sysCfg.mqtt_grptopic)) grpflg = 1;
+  if (type != NULL) for(i = 0; i < strlen(type); i++) type[i] = toupper(type[i]);
+
+  for(i = 0; i <= data_len; i++) dataBufUc[i] = toupper(dataBuf[i]);
+
+  snprintf_P(svalue, sizeof(svalue), PSTR("MQTT: DataCb Topic %s, Group %d, Type %s, data %s (%s)"),
+    mtopic, grpflg, type, dataBuf, dataBufUc);
+  addLog(LOG_LEVEL_DEBUG, svalue);
+
+  if (type != NULL) {
+    snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), PUB_PREFIX, sysCfg.mqtt_topic, type);
+    strlcpy(svalue, "Error", sizeof(svalue));
+
+    uint16_t payload = atoi(dataBuf);
+    if (!strcmp(dataBufUc,"OFF")) payload = 0;
+    if (!strcmp(dataBufUc,"ON")) payload = 1;
+    if (!strcmp(dataBufUc,"TOGGLE")) payload = 2;
+
+    if (!strcmp(type,"STATUS")) {
+      if ((data_len == 0) || (payload < 0) || (payload > 6)) payload = 7;
+      if ((payload == 0) || (payload == 7)) {
+        snprintf_P(svalue, sizeof(svalue), PSTR("%s, %s, %s, %s, %d, %d"),
+          Version, sysCfg.mqtt_topic, sysCfg.mqtt_topic2, sysCfg.mqtt_subtopic, sysCfg.power, sysCfg.timezone);
+        if (payload == 0) mqtt_publish(stopic, svalue);
+      }
+      if ((payload == 0) || (payload == 1)) {
+        snprintf_P(svalue, sizeof(svalue), PSTR("PRM: %s, "MQTT_CLIENT_ID", %s, %s, %d, %d"),
+          sysCfg.mqtt_grptopic, ESP.getChipId(), sysCfg.otaUrl, sysCfg.mqtt_host, heartbeat, sysCfg.saveFlag);
+        if (payload == 0) mqtt_publish(stopic, svalue);
+      }          
+      if ((payload == 0) || (payload == 2)) {
+        snprintf_P(svalue, sizeof(svalue), PSTR("FWR: Version %s, Boot %d, SDK %s"),
+          Version, ESP.getBootVersion(), ESP.getSdkVersion());
+        if (payload == 0) mqtt_publish(stopic, svalue);
+      }          
+      if ((payload == 0) || (payload == 3)) {
+        snprintf_P(svalue, sizeof(svalue), PSTR("LOG: Seriallog %d, Syslog %d, LogHost %s, SSId %s, Password %s"),
+          sysCfg.seriallog_level, sysCfg.syslog_level, sysCfg.syslog_host, sysCfg.sta_ssid, sysCfg.sta_pwd);
+        if (payload == 0) mqtt_publish(stopic, svalue);
+      }          
+      if ((payload == 0) || (payload == 4)) {
+        snprintf_P(svalue, sizeof(svalue), PSTR("MEM: Sketch size %d, Free %d (Heap %d), Spiffs start %d, Flash size %d (%d)"),
+          ESP.getSketchSize(), ESP.getFreeSketchSpace(), ESP.getFreeHeap(), (uint32_t)&_SPIFFS_start - 0x40200000,
+          ESP.getFlashChipRealSize(), ESP.getFlashChipSize());
+        if (payload == 0) mqtt_publish(stopic, svalue);
+      }          
+      if ((payload == 0) || (payload == 5)) {
+        IPAddress ip = WiFi.localIP();
+        IPAddress gw = WiFi.gatewayIP();
+        IPAddress nm = WiFi.subnetMask();
+        snprintf_P(svalue, sizeof(svalue), PSTR("NET: Hostname %s, IP %u.%u.%u.%u, Gateway %u.%u.%u.%u, Subnetmask %u.%u.%u.%u"),
+          Hostname, ip[0], ip[1], ip[2], ip[3], gw[0], gw[1], gw[2], gw[3], nm[0], nm[1], nm[2], nm[3]);
+        if (payload == 0) mqtt_publish(stopic, svalue);
+      }          
+      if ((payload == 0) || (payload == 6)) {
+        snprintf_P(svalue, sizeof(svalue), PSTR("MQT: Host %s, MAX_PACKET_SIZE %d, KEEPALIVE %d"),
+          sysCfg.mqtt_host, MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE); 
+      }      
+    }
+    else if (!grpflg && !strcmp(type,"UPGRADE")) {
+      if ((data_len > 0) && (payload == 1)) {
+        otaflag = 3;
+        snprintf_P(svalue, sizeof(svalue), PSTR("Upgrade %s"), Version);
+      }
+      else
+        snprintf_P(svalue, sizeof(svalue), PSTR("1 to upgrade"));
+    }
+    else if (!grpflg && !strcmp(type,"OTAURL")) {
+      if ((data_len > 0) && (data_len < 80))
+        strlcpy(sysCfg.otaUrl, (payload == 1) ? OTA_URL : dataBuf, sizeof(sysCfg.otaUrl));
+      snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.otaUrl);
+    }
+    else if (!strcmp(type,"SERIALLOG")) {
+      if ((data_len > 0) && (payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_DEBUG_MORE)) {
+        sysCfg.seriallog_level = payload;
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%d"), sysCfg.seriallog_level);
+    }
+    else if (!strcmp(type,"SYSLOG")) {
+      if ((data_len > 0) && (payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_DEBUG_MORE)) {
+        sysCfg.syslog_level = payload;
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%d"), sysCfg.syslog_level);
+    }
+    else if (!strcmp(type,"LOGHOST")) {
+      if ((data_len > 0) && (data_len < 32)) {
+        strlcpy(sysCfg.syslog_host, (payload == 1) ? SYS_LOG_HOST : dataBuf, sizeof(sysCfg.syslog_host));
+        restartflag = 2;
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.syslog_host);
+    }
+    else if (!grpflg && !strcmp(type,"SSID")) {
+      if ((data_len > 0) && (data_len < 32)) {
+        strlcpy(sysCfg.sta_ssid, (payload == 1) ? STA_SSID : dataBuf, sizeof(sysCfg.sta_ssid));
+        restartflag = 2;
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.sta_ssid);
+    }
+    else if (!grpflg && !strcmp(type,"PASSWORD")) {
+      if ((data_len > 0) && (data_len < 64)) {
+        strlcpy(sysCfg.sta_pwd, (payload == 1) ? STA_PASS : dataBuf, sizeof(sysCfg.sta_pwd));
+        restartflag = 2;
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.sta_pwd);
+    }
+    else if (!grpflg && !strcmp(type,"MQTTHOST")) {
+      if ((data_len > 0) && (data_len < 32)) {
+        strlcpy(sysCfg.mqtt_host, (payload == 1) ? MQTT_HOST : dataBuf, sizeof(sysCfg.mqtt_host));
+        restartflag = 2;
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.mqtt_host);
+    }
+    else if (!strcmp(type,"GROUPTOPIC")) {
+      if ((data_len > 0) && (data_len < 32)) {
+        for(i = 0; i <= data_len; i++)
+          if ((dataBuf[i] == '/') || (dataBuf[i] == '+') || (dataBuf[i] == '#')) dataBuf[i] = '_';
+        snprintf_P(svalue, sizeof(svalue), PSTR(MQTT_CLIENT_ID), ESP.getChipId());
+        if (!strcmp(dataBuf, svalue)) payload = 1;
+        strlcpy(sysCfg.mqtt_grptopic, (payload == 1) ? MQTT_GRPTOPIC : dataBuf, sizeof(sysCfg.mqtt_grptopic));
+        restartflag = 2;
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.mqtt_grptopic);
+    }
+    else if (!grpflg && !strcmp(type,"TOPIC")) {
+      if ((data_len > 0) && (data_len < 32)) {
+        for(i = 0; i <= data_len; i++)
+          if ((dataBuf[i] == '/') || (dataBuf[i] == '+') || (dataBuf[i] == '#')) dataBuf[i] = '_';
+        snprintf_P(svalue, sizeof(svalue), PSTR(MQTT_CLIENT_ID), ESP.getChipId());
+        if (!strcmp(dataBuf, svalue)) payload = 1;
+        strlcpy(sysCfg.mqtt_topic, (payload == 1) ? MQTT_TOPIC : dataBuf, sizeof(sysCfg.mqtt_topic));
+        restartflag = 2;
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.mqtt_topic);
+    }
+    else if (!grpflg && !strcmp(type,"BUTTONTOPIC")) {
+      if ((data_len > 0) && (data_len < 32)) {
+        for(i = 0; i <= data_len; i++)
+          if ((dataBuf[i] == '/') || (dataBuf[i] == '+') || (dataBuf[i] == '#')) dataBuf[i] = '_';
+        snprintf_P(svalue, sizeof(svalue), PSTR(MQTT_CLIENT_ID), ESP.getChipId());
+        if (!strcmp(dataBuf, svalue)) payload = 1;
+        strlcpy(sysCfg.mqtt_topic2, (payload == 1) ? MQTT_TOPIC : dataBuf, sizeof(sysCfg.mqtt_topic2));
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.mqtt_topic2);
+    }
+    else if (!grpflg && !strcmp(type,"SMARTCONFIG")) {
+      if ((data_len > 0) && (payload == 1)) {
+        blinks = 1999;
+        smartconfigflag = 1;
+        snprintf_P(svalue, sizeof(svalue), PSTR("Smartconfig started"));
+      } else
+        snprintf_P(svalue, sizeof(svalue), PSTR("1 to start smartconfig"));
+    }
+    else if (!grpflg && !strcmp(type,"RESTART")) {
+      if ((data_len > 0) && (payload == 1)) {
+        restartflag = 2;
+        snprintf_P(svalue, sizeof(svalue), PSTR("Restarting"));
+      } else
+        snprintf_P(svalue, sizeof(svalue), PSTR("1 to restart"));
+    }
+    else if (!grpflg && !strcmp(type,"RESET")) {
+      switch (payload) {
+      case 1: 
+        restartflag = 11;
+        snprintf_P(svalue, sizeof(svalue), PSTR("Reset and Restarting"));
+        break;
+      case 2:
+        restartflag = 12;
+        snprintf_P(svalue, sizeof(svalue), PSTR("Erase, Reset and Restarting"));
+        break;
+      default:
+        snprintf_P(svalue, sizeof(svalue), PSTR("1 to reset"));
+      }
+    }
+    else if (!strcmp(type,"TIMEZONE")) {
+      if ((data_len > 0) && (payload >= -12) && (payload <= 12)) {
+        sysCfg.timezone = payload;
+        rtc_timezone(sysCfg.timezone);
+      }
+      snprintf_P(svalue, sizeof(svalue), PSTR("%d"), sysCfg.timezone);
+    }
+    else if ((!strcmp(type,"LIGHT")) || (!strcmp(type,"POWER"))) {
+      snprintf_P(sysCfg.mqtt_subtopic, sizeof(sysCfg.mqtt_subtopic), PSTR("%s"), type);
+      if ((data_len > 0) && (payload >= 0) && (payload <= 2)) {
+        switch (payload) {
+        case 0: // Off
+        case 1: // On
+          sysCfg.power = payload;
+          break;
+        case 2: // Toggle
+          sysCfg.power ^= 1;
+          break;
+        }
+        digitalWrite(REL_PIN, sysCfg.power);
+      }
+      strlcpy(svalue, (sysCfg.power) ? "On" : "Off", sizeof(svalue));
+    }
+    else if (!strcmp(type,"LEDSTATE")) {
+      if ((data_len > 0) && (payload >= 0) && (payload <= 1)) {
+        sysCfg.ledstate = payload;
+      }
+      strlcpy(svalue, (sysCfg.ledstate) ? "On" : "Off", sizeof(svalue));
+    }
+    else {
+      type = NULL;
+    }
+    if (type == NULL) {
+      blinks = 1;
+      snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/SYNTAX"), PUB_PREFIX, sysCfg.mqtt_topic);
+      if (!grpflg)
+//        strlcpy(svalue, "Status, Upgrade, Otaurl, Restart, Reset, Smartconfig, Seriallog, Syslog, LogHost, SSId, Password, MqttHost, GroupTopic, Topic, ButtonTopic, Timezone, Light, Power, Ledstate", sizeof(svalue));  // Fails progmem access
+        snprintf_P(svalue, sizeof(svalue), PSTR("Status, Upgrade, Otaurl, Restart, Reset, Smartconfig, Seriallog, Syslog, LogHost, SSId, Password, MqttHost, GroupTopic, Topic, ButtonTopic, Timezone, Light, Power, Ledstate"));
+      else
+//        strlcpy(svalue, "Status, GroupTopic, Timezone, Light, Power, Ledstate", sizeof(svalue));  // Fails progmem access
+        snprintf_P(svalue, sizeof(svalue), PSTR("Status, GroupTopic, Timezone, Light, Power, Ledstate"));
+    }
+    mqtt_publish(stopic, svalue);
+  }
+}
+
+/********************************************************************************************/
+
+void send_button(char *cmnd)
+{
+  char stopic[128], svalue[128];
+  char *token;
+
+  token = strtok(cmnd, " ");
+  if ((!strcmp(token,"light")) || (!strcmp(token,"power"))) strcpy(token, sysCfg.mqtt_subtopic);
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), SUB_PREFIX, sysCfg.mqtt_topic2, token);
+  token = strtok(NULL, "");
+  snprintf_P(svalue, sizeof(svalue), PSTR("%s"), (token == NULL) ? "" : token);
+  mqtt_publish(stopic, svalue);
+}
+
+void do_cmnd(char *cmnd)
+{
+  char stopic[128], svalue[128];
+  char *token;
+
+  token = strtok(cmnd, " ");
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), SUB_PREFIX, sysCfg.mqtt_topic, token);
+  token = strtok(NULL, "");
+  snprintf_P(svalue, sizeof(svalue), PSTR("%s"), (token == NULL) ? "" : token);
+  mqttDataCb(stopic, (byte*)svalue, strlen(svalue));
+}
+
+void send_power()
+{
+  char stopic[TOPSZ], svalue[TOPSZ];
+
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), PUB_PREFIX, sysCfg.mqtt_topic, sysCfg.mqtt_subtopic);
+  strlcpy(svalue, (sysCfg.power == 0) ? "Off" : "On", sizeof(svalue));
+  mqtt_publish(stopic, svalue);
+}
+
+void send_updateStatus(const char* svalue)
+{
+  char stopic[TOPSZ];
+  
+  snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/UPGRADE"), PUB_PREFIX, sysCfg.mqtt_topic);
+  mqtt_publish(stopic, svalue);
+}
+
+void every_second()
+{
+  char stopic[TOPSZ], svalue[TOPSZ];
+
+  if (heartbeatflag) {
+    heartbeatflag = 0;
+    heartbeat++;
+    snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/HEARTBEAT"), PUB_PREFIX, sysCfg.mqtt_topic);
+    snprintf_P(svalue, sizeof(svalue), PSTR("%d"), heartbeat);
+    mqtt_publish(stopic, svalue);
+  }
+}
+
+const char commands[6][14] PROGMEM = {
+  {"reset 1"},        // Hold button for more than 4 seconds
+  {"light 2"},        // Press button once
+  {"light 2"},        // Press button twice
+  {"smartconfig 1"},  // Press button three times
+  {"upgrade 1"},      // Press button four times
+  {"restart 1"}};     // Press button five times
+
+void stateloop()
+{
+  uint8_t button;
+  char scmnd[20], log[LOGSZ];
+  
+  timerxs = millis() + (1000 / STATES);
+  state++;
+  if (state == STATES) {             // Every second
+    state = 0;
+    every_second();
+  }
+
+  button = digitalRead(KEY_PIN);
+  if ((button == PRESSED) && (lastbutton == NOT_PRESSED)) {
+    multipress = (multiwindow) ? multipress +1 : 1;
+    snprintf_P(log, sizeof(log), PSTR("APP: Multipress %d"), multipress);
+    addLog(LOG_LEVEL_DEBUG, log);
+    blinks = 1;
+    multiwindow = STATES /2;         // 1/2 second multi press window
+  }
+  lastbutton = button;
+  if (button == NOT_PRESSED) {
+    holdcount = 0;
+  } else {
+    holdcount++;
+    if (holdcount == (STATES *4)) {  // 4 seconds button hold
+//      strlcpy(scmnd, commands[0], sizeof(scmnd));  // Fails progmem access
+      snprintf_P(scmnd, sizeof(scmnd), commands[0]);
+      multipress = 0;
+      do_cmnd(scmnd);
+    }
+  }
+  if (multiwindow) {
+    multiwindow--;
+  } else {
+    if ((!holdcount) && (multipress >= 1) && (multipress <= 5)) {
+//      strlcpy(scmnd, commands[multipress], sizeof(scmnd));  // Fails progmem access
+      snprintf_P(scmnd, sizeof(scmnd), commands[multipress]);
+      if (strcmp(sysCfg.mqtt_topic2,"0") && (multipress == 1) && mqttClient.connected())
+        send_button(scmnd);          // Execute command via MQTT using ButtonTopic to sync external clients
+      else
+        do_cmnd(scmnd);              // Execute command internally 
+      multipress = 0;
+    }
+  }
+
+  if (!(state % ((STATES/10)*2))) {
+    if (blinks || restartflag || otaflag) {
+      if (restartflag || otaflag)
+        blinkstate = 0;   // Stay lit
+      else
+        blinkstate ^= 1;  // Blink
+      digitalWrite(LED_PIN, blinkstate);
+      if (blinkstate) blinks--;
+    } else {
+      if (sysCfg.ledstate) digitalWrite(LED_PIN, !sysCfg.power);
+    }
+  }
+
+  switch (state) {
+  case (STATES/10)*2:
+    if (otaflag) {
+      otaflag--;
+      if (otaflag <= 0) {
+        otaflag = 255;
+        ESPhttpUpdate.update(sysCfg.otaUrl);
+        send_updateStatus(ESPhttpUpdate.getLastErrorString().c_str());
+        restartflag = 2;
+      }
+    }
+    break;
+  case (STATES/10)*4:
+    CFG_Save();
+    if (restartflag) {
+      if (restartflag == 11) {
+        CFG_Default();
+        restartflag = 2;
+      }
+      if (restartflag == 12) {
+        CFG_Erase();
+        restartflag = 1;
+      }
+      restartflag--;
+      if (restartflag <= 0) ESP.restart();
+    }
+    break;
+  case (STATES/10)*6:
+    if (smartconfigflag) {
+      smartconfigflag = 0;
+      WIFI_Check(WIFI_SMARTCONFIG);
+    } else {
+      WIFI_Check(WIFI_STATUS);
+    }
+    break;
+  case (STATES/10)*8:
+    if ((WiFi.status() == WL_CONNECTED) && (!mqttClient.connected())) {
+      if (!mqttcounter)
+        mqtt_reconnect();
+      else
+        mqttcounter--;
+    }
+    break;
+  }
+}
+
+#ifdef SERIAL_IO
+#define INPUT_BUFFER_SIZE          128
+
+byte SerialInByte;
+int SerialInByteCounter = 0;
+char serialInBuf[INPUT_BUFFER_SIZE + 2];
+
+void serial()
+{
+  while (Serial.available())
+  {
+    yield();
+    SerialInByte = Serial.read();
+    if (SerialInByte > 127) // binary data...
+    {
+      Serial.flush();
+      SerialInByteCounter = 0;
+      return;
+    }
+    if (isprint(SerialInByte))
+    {
+      if (SerialInByteCounter < INPUT_BUFFER_SIZE) // add char to string if it still fits
+        serialInBuf[SerialInByteCounter++] = SerialInByte;
+      else
+        SerialInByteCounter = 0;
+    }
+    if (SerialInByte == '\n')
+    {
+      serialInBuf[SerialInByteCounter] = 0; // serial data completed
+      Serial.println(serialInBuf);
+      SerialInByteCounter = 0;
+      do_cmnd(serialInBuf);
+    }
+  }
+}
+#endif
+
+/********************************************************************************************/
+
+void setup()
+{
+  char log[128];
+
+  Serial.begin(115200);
+  delay(10);
+  Serial.println();
+
+  snprintf_P(Version, sizeof(Version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff);
+  if (VERSION & 0x1f) {
+    byte idx = strlen(Version);
+    Version[idx] = 96 + (VERSION & 0x1f);
+    Version[idx +1] = 0;
+  }
+  CFG_Load();
+  if (sysCfg.version != VERSION) {      // Fix version dependent changes
+    if (sysCfg.version < 0x01000D00) {  // 1.0.13 - Add ledstate
+      sysCfg.ledstate = APP_LEDSTATE;
+    }
+    
+    sysCfg.version = VERSION;
+  }
+
+  snprintf_P(Hostname, sizeof(Hostname), PSTR(WIFI_HOSTNAME), ESP.getChipId(), sysCfg.mqtt_topic);
+  WIFI_Connect(Hostname);
+
+  mqttClient.setServer(sysCfg.mqtt_host, MQTT_PORT);
+  mqttClient.setCallback(mqttDataCb);
+
+  pinMode(LED_PIN, OUTPUT);
+  digitalWrite(LED_PIN, blinkstate);
+
+  pinMode(REL_PIN, OUTPUT);
+  digitalWrite(REL_PIN, sysCfg.power);
+
+  pinMode(KEY_PIN, INPUT_PULLUP);
+
+  rtc_init(sysCfg.timezone);
+
+  snprintf_P(log, sizeof(log), PSTR("App: Project %s (Topic %s, Fallback "MQTT_CLIENT_ID", GroupTopic %s) Version %s"),
+    PROJECT, sysCfg.mqtt_topic, ESP.getChipId(), sysCfg.mqtt_grptopic, Version);
+  addLog(LOG_LEVEL_INFO, log);
+}
+
+void loop()
+{
+  if (millis() >= timersec) {
+    timersec = millis() + 1000;
+    rtc_second();
+    if ((rtcTime.Minute == 2) && (rtcTime.Second == 30)) heartbeatflag = 1;
+  }
+
+  if (millis() >= timerxs) stateloop();
+
+  mqttClient.loop();
+
+#ifdef SERIAL_IO
+  if (Serial.available()) serial();
+#endif
+  
+  yield();
+}
+
diff --git a/sonoff/support.ino b/sonoff/support.ino
new file mode 100644 (file)
index 0000000..da87ac2
--- /dev/null
@@ -0,0 +1,349 @@
+/*********************************************************************************************\
+ * Config
+\*********************************************************************************************/
+
+SYSCFG myCfg;
+
+void CFG_Default()
+{
+  addLog(LOG_LEVEL_INFO, "Config: Use default configuration");
+  memset(&sysCfg, 0x00, sizeof(SYSCFG));
+  memset(&myCfg, 0x00, sizeof(SYSCFG));
+  sysCfg.cfg_holder = CFG_HOLDER;
+  sysCfg.saveFlag = 0;
+  sysCfg.version = VERSION;
+  sysCfg.seriallog_level = SERIAL_LOG_LEVEL;
+  sysCfg.syslog_level = SYS_LOG_LEVEL;
+  strlcpy(sysCfg.syslog_host, SYS_LOG_HOST, sizeof(sysCfg.syslog_host));
+  strlcpy(sysCfg.sta_ssid, STA_SSID, sizeof(sysCfg.sta_ssid));
+  strlcpy(sysCfg.sta_pwd, STA_PASS, sizeof(sysCfg.sta_pwd));
+  strlcpy(sysCfg.otaUrl, OTA_URL, sizeof(sysCfg.otaUrl));
+  strlcpy(sysCfg.mqtt_host, MQTT_HOST, sizeof(sysCfg.mqtt_host));
+  strlcpy(sysCfg.mqtt_grptopic, MQTT_GRPTOPIC, sizeof(sysCfg.mqtt_grptopic));
+  strlcpy(sysCfg.mqtt_topic, MQTT_TOPIC, sizeof(sysCfg.mqtt_topic));
+  strlcpy(sysCfg.mqtt_topic2, "0", sizeof(sysCfg.mqtt_topic2));
+  strlcpy(sysCfg.mqtt_subtopic, MQTT_SUBTOPIC, sizeof(sysCfg.mqtt_subtopic));
+  sysCfg.timezone = APP_TIMEZONE;
+  sysCfg.power = APP_POWER;
+  sysCfg.ledstate = APP_LEDSTATE;
+  CFG_Save();
+}
+
+extern "C" {
+#include "spi_flash.h"
+}
+extern "C" uint32_t _SPIFFS_end;
+#define CFG_LOCATION (((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE) - 2  // End of SPIFFS area
+
+void CFG_Save()
+{
+  char log[LOGSZ];
+  
+  if (memcmp(&myCfg, &sysCfg, sizeof(SYSCFG))) {
+    noInterrupts();
+    if (sysCfg.saveFlag == 0) {  // Handle default and rollover
+      spi_flash_erase_sector(CFG_LOCATION + (sysCfg.saveFlag &1));
+      spi_flash_write((CFG_LOCATION + (sysCfg.saveFlag &1)) * SPI_FLASH_SEC_SIZE, (uint32 *)&sysCfg, sizeof(SYSCFG));
+    }
+    sysCfg.saveFlag++;
+    spi_flash_erase_sector(CFG_LOCATION + (sysCfg.saveFlag &1));
+    spi_flash_write((CFG_LOCATION + (sysCfg.saveFlag &1)) * SPI_FLASH_SEC_SIZE, (uint32 *)&sysCfg, sizeof(SYSCFG));
+    interrupts();
+    snprintf_P(log, sizeof(log), PSTR("Config: Saved configuration to flash at %X and count %d"), CFG_LOCATION + (sysCfg.saveFlag &1), sysCfg.saveFlag);
+    addLog(LOG_LEVEL_DEBUG, log);
+    myCfg = sysCfg;
+  }
+}
+
+void CFG_Load()
+{
+  char log[LOGSZ];
+
+  noInterrupts();
+  spi_flash_read((CFG_LOCATION) * SPI_FLASH_SEC_SIZE, (uint32 *)&sysCfg, sizeof(SYSCFG));
+  spi_flash_read((CFG_LOCATION + 1) * SPI_FLASH_SEC_SIZE, (uint32 *)&myCfg, sizeof(SYSCFG));
+  interrupts();
+  if (sysCfg.saveFlag < myCfg.saveFlag) sysCfg = myCfg;
+  snprintf_P(log, sizeof(log), PSTR("Config: Loaded configuration from flash at %X and count %d"), CFG_LOCATION + (sysCfg.saveFlag &1), sysCfg.saveFlag);
+  addLog(LOG_LEVEL_DEBUG, log);
+  if (sysCfg.cfg_holder != CFG_HOLDER) CFG_Default();
+  myCfg = sysCfg;
+}
+
+void CFG_Erase()
+{
+  char log[LOGSZ];
+  SpiFlashOpResult result;
+
+  uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1;
+  uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE;
+  byte seriallog_level = sysCfg.seriallog_level;
+
+  snprintf_P(log, sizeof(log), PSTR("Config: Erasing %d flash sectors"), _sectorEnd - _sectorStart);
+  addLog(LOG_LEVEL_DEBUG, log);
+
+  for (uint32_t _sector = _sectorStart; _sector < _sectorEnd; _sector++) {
+    noInterrupts();
+    result = spi_flash_erase_sector(_sector);
+    interrupts();
+    if (LOG_LEVEL_DEBUG_MORE <= seriallog_level) {
+      Serial.print(F("Flash: Erased sector "));
+      Serial.print(_sector);
+      if (result == SPI_FLASH_RESULT_OK)
+        Serial.println(F(" OK"));
+      else
+        Serial.println(F(" Error"));
+      delay(10);
+    }
+  }
+}
+
+/*********************************************************************************************\
+ * Wifi
+\*********************************************************************************************/
+
+#define WIFI_SMARTSEC  60   // seconds
+#define WIFI_CHECKSEC  20   // seconds
+#define WIFI_RETRY     16
+
+uint8_t wificounter;
+uint8_t wifiretry = WIFI_RETRY;
+uint8_t smartcounter = 0;
+
+void WIFI_smartconfig()
+{
+  smartcounter = WIFI_SMARTSEC;   // Allow up to WIFI_SMARTSECS seconds for phone to provide ssid/pswd
+  wificounter = smartcounter +5;
+  addLog(LOG_LEVEL_INFO, "Smartconfig: Started and active for 1 minute");
+  WiFi.beginSmartConfig();
+}
+
+void WIFI_check_ip()
+{
+  if ((WiFi.status() == WL_CONNECTED) && (static_cast<uint32_t>(WiFi.localIP()) != 0)) {
+    wificounter = WIFI_CHECKSEC;
+    wifiretry = WIFI_RETRY;
+  } else {
+    switch (WiFi.status()) {
+      case WL_NO_SSID_AVAIL:
+      case WL_CONNECT_FAILED:
+        addLog(LOG_LEVEL_DEBUG, "Wifi: STATION_CONNECT_FAIL");
+        WIFI_smartconfig();
+        break;
+      default:
+        addLog(LOG_LEVEL_DEBUG, "Wifi: STATION_IDLE");
+        if (wifiretry == (WIFI_RETRY / 2)) WiFi.begin();
+        wifiretry--;
+        if (wifiretry)
+          wificounter = 1;
+        else
+          WIFI_smartconfig();
+        break;
+    }
+  }
+}
+
+void WIFI_Check(uint8_t param)
+{
+  char log[LOGSZ];
+  
+  wificounter--;
+  switch (param) {
+    case WIFI_SMARTCONFIG:
+      WIFI_smartconfig();
+      break;
+    default:
+      if (wificounter <= 0) {
+        addLog(LOG_LEVEL_DEBUG_MORE, "Wifi: Check connection");
+        wificounter = WIFI_CHECKSEC;
+        WIFI_check_ip();
+      }
+      if (smartcounter) {
+        smartcounter--;
+        if (smartcounter) {
+          if (WiFi.smartConfigDone()) {
+            smartcounter = 0;
+            memcpy(sysCfg.sta_ssid, WiFi.SSID().c_str(), strlen(WiFi.SSID().c_str())+1);
+            memcpy(sysCfg.sta_pwd, WiFi.psk().c_str(), strlen(WiFi.psk().c_str())+1);
+            snprintf_P(log, sizeof(log), PSTR("Smartconfig: SSID %s and Password %s"), sysCfg.sta_ssid, sysCfg.sta_pwd);
+            addLog(LOG_LEVEL_INFO, log);
+          }
+        }
+        if (smartcounter == 0) {
+          WiFi.stopSmartConfig();
+          restartflag = 2;     
+        }
+      }
+      break;
+  }
+}
+
+void WIFI_Connect(char *Hostname)
+{
+  char log[LOGSZ];
+
+  WiFi.persistent(false);   // Solve possible wifi init errors
+  WiFi.hostname(Hostname);
+  snprintf_P(log, sizeof(log), PSTR("Wifi: Connecting to %s as %s"), sysCfg.sta_ssid, Hostname);
+  addLog(LOG_LEVEL_DEBUG, log);
+  WiFi.setAutoConnect(true);
+  WiFi.mode(WIFI_STA);     // Disable AP mode
+  WiFi.begin(sysCfg.sta_ssid, sysCfg.sta_pwd);
+  wificounter = 1;
+}
+
+/*********************************************************************************************\
+ * Real Time Clock
+\*********************************************************************************************/
+
+#define NTP_SERVER1 "pool.ntp.org"
+#define NTP_SERVER2 "nl.pool.ntp.org"
+#define NTP_SERVER3 "0.nl.pool.ntp.org"
+
+extern "C" {
+#include "sntp.h"
+}
+
+#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
+
+static const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // API starts months from 1, this array starts from 0
+
+static const char monthNames[37] = { "JanFebMrtAprMayJunJulAugSepOctNovDec" };
+
+uint32_t myrtc = 0;
+uint8_t ntpsync = 0;
+
+void convertTime()
+{
+  // given myrtc as time in Linux format break into time components
+  // this is a more compact version of the C library localtime function
+  // note that internally the year is offset from 1970 - compensated at the end
+
+  uint8_t year;
+  uint8_t month, monthLength;
+  uint32_t time;
+  unsigned long days;
+
+  time = (uint32_t) myrtc;
+  rtcTime.Second = time % 60;
+  time /= 60; // now it is minutes
+  rtcTime.Minute = time % 60;
+  time /= 60; // now it is hours
+  rtcTime.Hour = time % 24;
+  time /= 24; // now it is days
+  rtcTime.Wday = ((time + 4) % 7) + 1;  // Sunday is day 1
+
+  year = 0;
+  days = 0;
+  while ((unsigned) (days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
+    year++;
+  }
+  rtcTime.Year = year; // year is offset from 1970
+
+  days -= LEAP_YEAR(year) ? 366 : 365;
+  time -= days; // now it is days in this year, starting at 0
+
+  days = 0;
+  month = 0;
+  monthLength = 0;
+  for (month = 0; month < 12; month++) {
+    if (month == 1) { // february
+      if (LEAP_YEAR(year)) {
+        monthLength = 29;
+      }
+      else {
+        monthLength = 28;
+      }
+    }
+    else {
+      monthLength = monthDays[month];
+    }
+
+    if (time >= monthLength) {
+      time -= monthLength;
+    }
+    else {
+      break;
+    }
+  }
+  strlcpy(rtcTime.MonthName, monthNames +(month *3), sizeof(rtcTime.MonthName));
+  rtcTime.Month = month + 1;  // jan is month 1
+  rtcTime.Day = time + 1;     // day of month
+  rtcTime.Year = rtcTime.Year + 1970;
+}
+
+void rtc_second()
+{
+  char log[LOGSZ];
+  
+  // NTP Sync every hour at x:0:10
+  if (rtcTime.Minute == 0) {
+    if ((rtcTime.Second >= 10) && !ntpsync) {
+      myrtc = sntp_get_current_timestamp();
+      ntpsync = (myrtc) ? 1 : 0;
+      snprintf_P(log, sizeof(log), PSTR("RTC: sntp %d, %s"), myrtc, sntp_get_real_time(myrtc));
+      addLog(LOG_LEVEL_DEBUG, log);
+    }
+    if (rtcTime.Second == 40) ntpsync = 0;
+  }
+  myrtc++;
+  convertTime();
+}
+
+void rtc_timezone(uint8_t timezone)
+{
+  sntp_stop();
+//  if (true == sntp_set_timezone(sysCfg.timezone))
+  sntp_set_timezone(timezone);
+  sntp_init();
+  myrtc = sntp_get_current_timestamp();
+  ntpsync = 0;
+}
+
+void rtc_init(uint8_t timezone)
+{
+  sntp_setservername(0, NTP_SERVER1);
+  sntp_setservername(1, NTP_SERVER2);
+//  sntp_setservername(2, NTP_SERVER3);
+  sntp_stop();
+//  if (true == sntp_set_timezone(sysCfg.timezone))
+  sntp_set_timezone(timezone);
+  sntp_init();
+  myrtc = 0;
+}
+
+/*********************************************************************************************\
+ * Syslog
+\*********************************************************************************************/
+
+void syslog(const char *message)
+{
+  char mess[MESSZ], str[TOPSZ+MESSZ];
+
+  portUDP.beginPacket(sysCfg.syslog_host, SYS_LOG_PORT);
+  strlcpy(mess, message, sizeof(mess));
+  mess[sizeof(mess)-1] = 0;
+  snprintf_P(str, sizeof(str), PSTR("%s %s"), Hostname, mess);
+  portUDP.write(str);
+  portUDP.endPacket();
+}
+
+void addLog(byte loglevel, const char *line)
+{
+#ifdef DEBUG_ESP_PORT
+  DEBUG_ESP_PORT.printf("DebugMsg %s\n", line);  
+#endif
+#ifdef SERIAL_IO
+  if (loglevel <= sysCfg.seriallog_level) Serial.println(line);
+#endif
+  if ((WiFi.status() == WL_CONNECTED) && (loglevel <= sysCfg.syslog_level)) syslog(line);
+}
+
+void addLog(byte loglevel, String& string)
+{
+  addLog(loglevel, string.c_str());
+}
+
+/*********************************************************************************************\
+ * 
+\*********************************************************************************************/
diff --git a/sonoff/user_config.h b/sonoff/user_config.h
new file mode 100644 (file)
index 0000000..49170c9
--- /dev/null
@@ -0,0 +1,46 @@
+/* 
+ * user_config.h
+ * 
+ * User specific configuration parameters
+ */
+
+#define PROJECT                "sonoff"     // PROJECT is used as the default topic delimiter and OTA file name
+#define CFG_HOLDER             0x20160520   // Change this value to load default configurations
+
+// Wifi
+#define STA_SSID               "indebuurt3"      // Wifi SSID
+#define STA_PASS               "VnsqrtnrsddbrN"  // Wifi password
+#define WIFI_HOSTNAME          "esp-%06x-%s"
+
+// Syslog
+#define SYS_LOG_HOST           "domus1"
+#define SYS_LOG_PORT           514
+#define SYS_LOG_LEVEL          LOG_LEVEL_NONE
+#define SERIAL_LOG_LEVEL       LOG_LEVEL_NONE
+
+// Ota
+#if (ARDUINO >= 168)
+  #define OTA_URL              "http://domus1:80/api/arduino/"PROJECT".ino.bin"
+#else
+  #define OTA_URL              "http://domus1:80/api/arduino/"PROJECT".cpp.bin"
+#endif
+
+// MQTT
+#define MQTT_HOST              "domus1"
+#define MQTT_PORT              1883
+
+#define MQTT_CLIENT_ID         "DVES_%06X"  // Also fall back topic using Chip Id = last 6 characters of MAC address
+#define MQTT_USER              "DVES_USER"
+#define MQTT_PASS              "DVES_PASS"
+
+#define SUB_PREFIX             "cmnd"       // Sonoff devices subscribe to:- cmnd/MQTT_TOPIC and cmnd/MQTT_GRPTOPIC
+#define PUB_PREFIX             "stat"       // Sonoff devices publish to:- stat/MQTT_TOPIC
+#define MQTT_TOPIC             PROJECT
+#define MQTT_GRPTOPIC          PROJECT"s"   // Group topic
+
+// Application
+#define MQTT_SUBTOPIC          "POWER"
+#define APP_TIMEZONE           1            // +1 hour (Amsterdam)
+#define APP_POWER              0            // Saved power state Off
+#define APP_LEDSTATE           0            // Do not show power state (1 = Show power state)
+