https://github.com/arendst/Sonoff-MQTT-OTA-Arduino
[Arduino] / sonoff / sonoff.ino
1 /*
2  * Sonoff and Wkaku by Theo Arends
3  * 
4  * ---------------------------------------------
5  * >>>> Tools - Flash size: 1M (64K SPIFFS) <<<<
6  * ---------------------------------------------
7  *
8  * ESP-12F connections (Wkaku)
9  * 3V3                                                     5V
10  *                   |-------------------|       |---------|
11  *  |                |   -------------   |    |1N4001|  |Relay|
12  *  |                | -|          Tx |- |       |---------|
13  *  |                | -|          Rx |- |                /
14  *  |-------------------| En          |- |---| 1k|------|<  BC547B
15  *  |                | -|             |-                  \
16  *  |                | -|        IO00 |------|Switch|------|
17  *  |                ---| IO12   IO02 |--- LED (ESP-12E/F) |
18  *  |---| 1k|---|LED|---| IO13   IO15 |------|10k|---------|
19  *  |-------------------| Vcc     Gnd |--------------------|
20  *                       -------------                     |
21  *                        | | | | | |                     Gnd
22 */
23
24 #define VERSION                0x01001000   // 1.0.16
25
26 #define LOG_LEVEL_NONE         0
27 #define LOG_LEVEL_ERROR        1
28 #define LOG_LEVEL_INFO         2
29 #define LOG_LEVEL_DEBUG        3
30 #define LOG_LEVEL_DEBUG_MORE   4
31
32 #include "user_config.h"
33
34 #define SERIAL_IO                           // Enable serial command line
35 #define STATES                 10           // loops per second
36 #define MQTT_RETRY_SECS        10           // Seconds to retry MQTT connection
37
38 //#define LED_PIN                2            // GPIO 2 = Blue Led (0 = On, 1 = Off) - ESP-12
39 #define LED_PIN                13           // GPIO 13 = Green Led (0 = On, 1 = Off) - Sonoff
40 //#define LED_PIN                16           // NodeMCU
41 #define REL_PIN                12           // GPIO 12 = Red Led and Relay (0 = Off, 1 = On)
42 #define KEY_PIN                0            // GPIO 00 = Button
43 #define PRESSED                0
44 #define NOT_PRESSED            1
45
46 #define WIFI_STATUS            0
47 #define WIFI_SMARTCONFIG       1
48
49 #define TOPSZ                  40           // Max number of characters in topic string
50 #define MESSZ                  200          // Max number of characters in message string (Syntax string)
51 #define LOGSZ                  80           // Max number of characters in log string
52
53 #include <ESP8266WiFi.h>
54 #include <ESP8266HTTPClient.h>
55 #include <ESP8266httpUpdate.h>
56 #include <PubSubClient.h>
57
58 extern "C" uint32_t _SPIFFS_start;
59
60 struct SYSCFG {
61   unsigned long cfg_holder;
62   unsigned long saveFlag;
63   unsigned long version;
64   byte          seriallog_level;
65   byte          syslog_level;
66   char          syslog_host[32];
67   char          sta_ssid[32];
68   char          sta_pwd[64];
69   char          otaUrl[80];
70   char          mqtt_host[32];
71   char          mqtt_grptopic[32];
72   char          mqtt_topic[32];
73   char          mqtt_topic2[32];
74   char          mqtt_subtopic[32];
75   int8_t        timezone;
76   uint8_t       power;
77   uint8_t       ledstate;
78 } sysCfg;
79
80 struct TIME_T {
81   uint8_t       Second;
82   uint8_t       Minute;
83   uint8_t       Hour;
84   uint8_t       Wday;   // day of week, sunday is day 1
85   uint8_t       Day;
86   uint8_t       Month;
87   char          MonthName[4];
88   uint16_t      Year;
89   unsigned long Valid;
90 } rtcTime;
91
92 char Version[16];
93 char Hostname[32];
94 uint8_t mqttcounter = 0;
95 unsigned long timerxs = 0, timersec = 0;
96 int state = 0;
97 int otaflag = 0;
98 int restartflag = 0;
99 int smartconfigflag = 0;
100 int heartbeatflag = 0;
101 int heartbeat = 0;
102
103 WiFiClient espClient;
104 PubSubClient mqttClient(espClient);
105 WiFiUDP portUDP;   // syslog
106
107 int blinks = 1;
108 uint8_t blinkstate = 1;
109
110 uint8_t lastbutton = NOT_PRESSED;
111 uint8_t holdcount = 0;
112 uint8_t multiwindow = 0;
113 uint8_t multipress = 0;
114
115 /********************************************************************************************/
116
117 void mqtt_publish(const char* topic, const char* data)
118 {
119   char log[TOPSZ+MESSZ];
120   
121   mqttClient.publish(topic, data);
122   snprintf_P(log, sizeof(log), PSTR("MQTT: %s = %s"), strchr(topic,'/')+1, data);     // Skip topic prefix
123   addLog(LOG_LEVEL_INFO, log);
124   mqttClient.loop();  // Solve LmacRxBlk:1 messages
125   blinks++;
126 }
127
128 void mqtt_connected()
129 {
130   char stopic[TOPSZ], svalue[TOPSZ];
131
132   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/#"), SUB_PREFIX, sysCfg.mqtt_topic);
133   mqttClient.subscribe(stopic);
134   mqttClient.loop();  // Solve LmacRxBlk:1 messages
135   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/#"), SUB_PREFIX, sysCfg.mqtt_grptopic);
136   mqttClient.subscribe(stopic);
137   mqttClient.loop();  // Solve LmacRxBlk:1 messages
138   snprintf_P(stopic, sizeof(stopic), PSTR("%s/"MQTT_CLIENT_ID"/#"), SUB_PREFIX, ESP.getChipId());  // Fall back topic
139   mqttClient.subscribe(stopic);
140   mqttClient.loop();  // Solve LmacRxBlk:1 messages
141
142   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/NAME"), PUB_PREFIX, sysCfg.mqtt_topic);
143   snprintf_P(svalue, sizeof(svalue), PSTR("Sonoff switch"));
144   mqtt_publish(stopic, svalue);
145   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/VERSION"), PUB_PREFIX, sysCfg.mqtt_topic);
146   snprintf_P(svalue, sizeof(svalue), PSTR("%s"), Version);
147   mqtt_publish(stopic, svalue);
148   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/FALLBACKTOPIC"), PUB_PREFIX, sysCfg.mqtt_topic);
149   snprintf_P(svalue, sizeof(svalue), PSTR(MQTT_CLIENT_ID), ESP.getChipId());
150   mqtt_publish(stopic, svalue);
151 }
152
153 void mqtt_reconnect()
154 {
155   char stopic[TOPSZ], svalue[TOPSZ], log[LOGSZ];
156
157   mqttcounter = MQTT_RETRY_SECS;
158   addLog(LOG_LEVEL_INFO, "MQTT: Attempting connection");
159   snprintf(svalue, sizeof(svalue), MQTT_CLIENT_ID, ESP.getChipId());
160   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/lwt"), PUB_PREFIX, sysCfg.mqtt_topic);
161   if (mqttClient.connect(svalue, MQTT_USER, MQTT_PASS, stopic, 0, 0, "offline")) {
162     addLog(LOG_LEVEL_INFO, "MQTT: Connected");
163     mqttcounter = 0;
164     mqtt_connected();
165   } else {
166     snprintf_P(log, sizeof(log), PSTR("MQTT: Connect failed, rc %d. Retry in %d seconds"), mqttClient.state(), mqttcounter);
167     addLog(LOG_LEVEL_DEBUG, log);
168   }
169 }
170
171 void mqttDataCb(char* topic, byte* data, unsigned int data_len)
172 {
173   int i, grpflg = 0;
174   char *str, *p, *mtopic = NULL, *type = NULL;
175   char stopic[TOPSZ], svalue[MESSZ];
176
177   int topic_len = strlen(topic);
178   char topicBuf[topic_len +1]; 
179   char dataBuf[data_len +1]; 
180   char dataBufUc[data_len +1]; 
181   
182   memcpy(topicBuf, topic, topic_len);
183   topicBuf[topic_len] = 0;
184
185   memcpy(dataBuf, data, data_len);
186   dataBuf[data_len] = 0;
187
188   snprintf_P(svalue, sizeof(svalue), PSTR("MQTT: Receive topic %s, data %s"), topicBuf, dataBuf);
189   addLog(LOG_LEVEL_DEBUG, svalue);
190
191   i = 0;
192   for (str = strtok_r(topicBuf, "/", &p); str && i < 3; str = strtok_r(NULL, "/", &p)) {
193     switch (i++) {
194     case 0:  // cmnd
195       break;
196     case 1:  // Topic / GroupTopic / DVES_123456
197       mtopic = str;
198       break;
199     case 2:  // Text
200       type = str;
201     }
202   }
203   if (!strcmp(mtopic, sysCfg.mqtt_grptopic)) grpflg = 1;
204   if (type != NULL) for(i = 0; i < strlen(type); i++) type[i] = toupper(type[i]);
205
206   for(i = 0; i <= data_len; i++) dataBufUc[i] = toupper(dataBuf[i]);
207
208   snprintf_P(svalue, sizeof(svalue), PSTR("MQTT: DataCb Topic %s, Group %d, Type %s, data %s (%s)"),
209     mtopic, grpflg, type, dataBuf, dataBufUc);
210   addLog(LOG_LEVEL_DEBUG, svalue);
211
212   if (type != NULL) {
213     snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), PUB_PREFIX, sysCfg.mqtt_topic, type);
214     strlcpy(svalue, "Error", sizeof(svalue));
215
216     uint16_t payload = atoi(dataBuf);
217     if (!strcmp(dataBufUc,"OFF")) payload = 0;
218     if (!strcmp(dataBufUc,"ON")) payload = 1;
219     if (!strcmp(dataBufUc,"TOGGLE")) payload = 2;
220
221     if (!strcmp(type,"STATUS")) {
222       if ((data_len == 0) || (payload < 0) || (payload > 6)) payload = 7;
223       if ((payload == 0) || (payload == 7)) {
224         snprintf_P(svalue, sizeof(svalue), PSTR("%s, %s, %s, %s, %d, %d"),
225           Version, sysCfg.mqtt_topic, sysCfg.mqtt_topic2, sysCfg.mqtt_subtopic, sysCfg.power, sysCfg.timezone);
226         if (payload == 0) mqtt_publish(stopic, svalue);
227       }
228       if ((payload == 0) || (payload == 1)) {
229         snprintf_P(svalue, sizeof(svalue), PSTR("PRM: %s, "MQTT_CLIENT_ID", %s, %s, %d, %d"),
230           sysCfg.mqtt_grptopic, ESP.getChipId(), sysCfg.otaUrl, sysCfg.mqtt_host, heartbeat, sysCfg.saveFlag);
231         if (payload == 0) mqtt_publish(stopic, svalue);
232       }          
233       if ((payload == 0) || (payload == 2)) {
234         snprintf_P(svalue, sizeof(svalue), PSTR("FWR: Version %s, Boot %d, SDK %s"),
235           Version, ESP.getBootVersion(), ESP.getSdkVersion());
236         if (payload == 0) mqtt_publish(stopic, svalue);
237       }          
238       if ((payload == 0) || (payload == 3)) {
239         snprintf_P(svalue, sizeof(svalue), PSTR("LOG: Seriallog %d, Syslog %d, LogHost %s, SSId %s, Password %s"),
240           sysCfg.seriallog_level, sysCfg.syslog_level, sysCfg.syslog_host, sysCfg.sta_ssid, sysCfg.sta_pwd);
241         if (payload == 0) mqtt_publish(stopic, svalue);
242       }          
243       if ((payload == 0) || (payload == 4)) {
244         snprintf_P(svalue, sizeof(svalue), PSTR("MEM: Sketch size %d, Free %d (Heap %d), Spiffs start %d, Flash size %d (%d)"),
245           ESP.getSketchSize(), ESP.getFreeSketchSpace(), ESP.getFreeHeap(), (uint32_t)&_SPIFFS_start - 0x40200000,
246           ESP.getFlashChipRealSize(), ESP.getFlashChipSize());
247         if (payload == 0) mqtt_publish(stopic, svalue);
248       }          
249       if ((payload == 0) || (payload == 5)) {
250         IPAddress ip = WiFi.localIP();
251         IPAddress gw = WiFi.gatewayIP();
252         IPAddress nm = WiFi.subnetMask();
253         snprintf_P(svalue, sizeof(svalue), PSTR("NET: Hostname %s, IP %u.%u.%u.%u, Gateway %u.%u.%u.%u, Subnetmask %u.%u.%u.%u"),
254           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]);
255         if (payload == 0) mqtt_publish(stopic, svalue);
256       }          
257       if ((payload == 0) || (payload == 6)) {
258         snprintf_P(svalue, sizeof(svalue), PSTR("MQT: Host %s, MAX_PACKET_SIZE %d, KEEPALIVE %d"),
259           sysCfg.mqtt_host, MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE); 
260       }      
261     }
262     else if (!grpflg && !strcmp(type,"UPGRADE")) {
263       if ((data_len > 0) && (payload == 1)) {
264         otaflag = 3;
265         snprintf_P(svalue, sizeof(svalue), PSTR("Upgrade %s"), Version);
266       }
267       else
268         snprintf_P(svalue, sizeof(svalue), PSTR("1 to upgrade"));
269     }
270     else if (!grpflg && !strcmp(type,"OTAURL")) {
271       if ((data_len > 0) && (data_len < 80))
272         strlcpy(sysCfg.otaUrl, (payload == 1) ? OTA_URL : dataBuf, sizeof(sysCfg.otaUrl));
273       snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.otaUrl);
274     }
275     else if (!strcmp(type,"SERIALLOG")) {
276       if ((data_len > 0) && (payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_DEBUG_MORE)) {
277         sysCfg.seriallog_level = payload;
278       }
279       snprintf_P(svalue, sizeof(svalue), PSTR("%d"), sysCfg.seriallog_level);
280     }
281     else if (!strcmp(type,"SYSLOG")) {
282       if ((data_len > 0) && (payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_DEBUG_MORE)) {
283         sysCfg.syslog_level = payload;
284       }
285       snprintf_P(svalue, sizeof(svalue), PSTR("%d"), sysCfg.syslog_level);
286     }
287     else if (!strcmp(type,"LOGHOST")) {
288       if ((data_len > 0) && (data_len < 32)) {
289         strlcpy(sysCfg.syslog_host, (payload == 1) ? SYS_LOG_HOST : dataBuf, sizeof(sysCfg.syslog_host));
290         restartflag = 2;
291       }
292       snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.syslog_host);
293     }
294     else if (!grpflg && !strcmp(type,"SSID")) {
295       if ((data_len > 0) && (data_len < 32)) {
296         strlcpy(sysCfg.sta_ssid, (payload == 1) ? STA_SSID : dataBuf, sizeof(sysCfg.sta_ssid));
297         restartflag = 2;
298       }
299       snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.sta_ssid);
300     }
301     else if (!grpflg && !strcmp(type,"PASSWORD")) {
302       if ((data_len > 0) && (data_len < 64)) {
303         strlcpy(sysCfg.sta_pwd, (payload == 1) ? STA_PASS : dataBuf, sizeof(sysCfg.sta_pwd));
304         restartflag = 2;
305       }
306       snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.sta_pwd);
307     }
308     else if (!grpflg && !strcmp(type,"MQTTHOST")) {
309       if ((data_len > 0) && (data_len < 32)) {
310         strlcpy(sysCfg.mqtt_host, (payload == 1) ? MQTT_HOST : dataBuf, sizeof(sysCfg.mqtt_host));
311         restartflag = 2;
312       }
313       snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.mqtt_host);
314     }
315     else if (!strcmp(type,"GROUPTOPIC")) {
316       if ((data_len > 0) && (data_len < 32)) {
317         for(i = 0; i <= data_len; i++)
318           if ((dataBuf[i] == '/') || (dataBuf[i] == '+') || (dataBuf[i] == '#')) dataBuf[i] = '_';
319         snprintf_P(svalue, sizeof(svalue), PSTR(MQTT_CLIENT_ID), ESP.getChipId());
320         if (!strcmp(dataBuf, svalue)) payload = 1;
321         strlcpy(sysCfg.mqtt_grptopic, (payload == 1) ? MQTT_GRPTOPIC : dataBuf, sizeof(sysCfg.mqtt_grptopic));
322         restartflag = 2;
323       }
324       snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.mqtt_grptopic);
325     }
326     else if (!grpflg && !strcmp(type,"TOPIC")) {
327       if ((data_len > 0) && (data_len < 32)) {
328         for(i = 0; i <= data_len; i++)
329           if ((dataBuf[i] == '/') || (dataBuf[i] == '+') || (dataBuf[i] == '#')) dataBuf[i] = '_';
330         snprintf_P(svalue, sizeof(svalue), PSTR(MQTT_CLIENT_ID), ESP.getChipId());
331         if (!strcmp(dataBuf, svalue)) payload = 1;
332         strlcpy(sysCfg.mqtt_topic, (payload == 1) ? MQTT_TOPIC : dataBuf, sizeof(sysCfg.mqtt_topic));
333         restartflag = 2;
334       }
335       snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.mqtt_topic);
336     }
337     else if (!grpflg && !strcmp(type,"BUTTONTOPIC")) {
338       if ((data_len > 0) && (data_len < 32)) {
339         for(i = 0; i <= data_len; i++)
340           if ((dataBuf[i] == '/') || (dataBuf[i] == '+') || (dataBuf[i] == '#')) dataBuf[i] = '_';
341         snprintf_P(svalue, sizeof(svalue), PSTR(MQTT_CLIENT_ID), ESP.getChipId());
342         if (!strcmp(dataBuf, svalue)) payload = 1;
343         strlcpy(sysCfg.mqtt_topic2, (payload == 1) ? MQTT_TOPIC : dataBuf, sizeof(sysCfg.mqtt_topic2));
344       }
345       snprintf_P(svalue, sizeof(svalue), PSTR("%s"), sysCfg.mqtt_topic2);
346     }
347     else if (!grpflg && !strcmp(type,"SMARTCONFIG")) {
348       if ((data_len > 0) && (payload == 1)) {
349         blinks = 1999;
350         smartconfigflag = 1;
351         snprintf_P(svalue, sizeof(svalue), PSTR("Smartconfig started"));
352       } else
353         snprintf_P(svalue, sizeof(svalue), PSTR("1 to start smartconfig"));
354     }
355     else if (!grpflg && !strcmp(type,"RESTART")) {
356       if ((data_len > 0) && (payload == 1)) {
357         restartflag = 2;
358         snprintf_P(svalue, sizeof(svalue), PSTR("Restarting"));
359       } else
360         snprintf_P(svalue, sizeof(svalue), PSTR("1 to restart"));
361     }
362     else if (!grpflg && !strcmp(type,"RESET")) {
363       switch (payload) {
364       case 1: 
365         restartflag = 11;
366         snprintf_P(svalue, sizeof(svalue), PSTR("Reset and Restarting"));
367         break;
368       case 2:
369         restartflag = 12;
370         snprintf_P(svalue, sizeof(svalue), PSTR("Erase, Reset and Restarting"));
371         break;
372       default:
373         snprintf_P(svalue, sizeof(svalue), PSTR("1 to reset"));
374       }
375     }
376     else if (!strcmp(type,"TIMEZONE")) {
377       if ((data_len > 0) && (payload >= -12) && (payload <= 12)) {
378         sysCfg.timezone = payload;
379         rtc_timezone(sysCfg.timezone);
380       }
381       snprintf_P(svalue, sizeof(svalue), PSTR("%d"), sysCfg.timezone);
382     }
383     else if ((!strcmp(type,"LIGHT")) || (!strcmp(type,"POWER"))) {
384       snprintf_P(sysCfg.mqtt_subtopic, sizeof(sysCfg.mqtt_subtopic), PSTR("%s"), type);
385       if ((data_len > 0) && (payload >= 0) && (payload <= 2)) {
386         switch (payload) {
387         case 0: // Off
388         case 1: // On
389           sysCfg.power = payload;
390           break;
391         case 2: // Toggle
392           sysCfg.power ^= 1;
393           break;
394         }
395         digitalWrite(REL_PIN, sysCfg.power);
396       }
397       strlcpy(svalue, (sysCfg.power) ? "On" : "Off", sizeof(svalue));
398     }
399     else if (!strcmp(type,"LEDSTATE")) {
400       if ((data_len > 0) && (payload >= 0) && (payload <= 1)) {
401         sysCfg.ledstate = payload;
402       }
403       strlcpy(svalue, (sysCfg.ledstate) ? "On" : "Off", sizeof(svalue));
404     }
405     else {
406       type = NULL;
407     }
408     if (type == NULL) {
409       blinks = 1;
410       snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/SYNTAX"), PUB_PREFIX, sysCfg.mqtt_topic);
411       if (!grpflg)
412 //        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
413         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"));
414       else
415 //        strlcpy(svalue, "Status, GroupTopic, Timezone, Light, Power, Ledstate", sizeof(svalue));  // Fails progmem access
416         snprintf_P(svalue, sizeof(svalue), PSTR("Status, GroupTopic, Timezone, Light, Power, Ledstate"));
417     }
418     mqtt_publish(stopic, svalue);
419   }
420 }
421
422 /********************************************************************************************/
423
424 void send_button(char *cmnd)
425 {
426   char stopic[128], svalue[128];
427   char *token;
428
429   token = strtok(cmnd, " ");
430   if ((!strcmp(token,"light")) || (!strcmp(token,"power"))) strcpy(token, sysCfg.mqtt_subtopic);
431   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), SUB_PREFIX, sysCfg.mqtt_topic2, token);
432   token = strtok(NULL, "");
433   snprintf_P(svalue, sizeof(svalue), PSTR("%s"), (token == NULL) ? "" : token);
434   mqtt_publish(stopic, svalue);
435 }
436
437 void do_cmnd(char *cmnd)
438 {
439   char stopic[128], svalue[128];
440   char *token;
441
442   token = strtok(cmnd, " ");
443   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), SUB_PREFIX, sysCfg.mqtt_topic, token);
444   token = strtok(NULL, "");
445   snprintf_P(svalue, sizeof(svalue), PSTR("%s"), (token == NULL) ? "" : token);
446   mqttDataCb(stopic, (byte*)svalue, strlen(svalue));
447 }
448
449 void send_power()
450 {
451   char stopic[TOPSZ], svalue[TOPSZ];
452
453   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), PUB_PREFIX, sysCfg.mqtt_topic, sysCfg.mqtt_subtopic);
454   strlcpy(svalue, (sysCfg.power == 0) ? "Off" : "On", sizeof(svalue));
455   mqtt_publish(stopic, svalue);
456 }
457
458 void send_updateStatus(const char* svalue)
459 {
460   char stopic[TOPSZ];
461   
462   snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/UPGRADE"), PUB_PREFIX, sysCfg.mqtt_topic);
463   mqtt_publish(stopic, svalue);
464 }
465
466 void every_second()
467 {
468   char stopic[TOPSZ], svalue[TOPSZ];
469
470   if (heartbeatflag) {
471     heartbeatflag = 0;
472     heartbeat++;
473     snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/HEARTBEAT"), PUB_PREFIX, sysCfg.mqtt_topic);
474     snprintf_P(svalue, sizeof(svalue), PSTR("%d"), heartbeat);
475     mqtt_publish(stopic, svalue);
476   }
477 }
478
479 const char commands[6][14] PROGMEM = {
480   {"reset 1"},        // Hold button for more than 4 seconds
481   {"light 2"},        // Press button once
482   {"light 2"},        // Press button twice
483   {"smartconfig 1"},  // Press button three times
484   {"upgrade 1"},      // Press button four times
485   {"restart 1"}};     // Press button five times
486
487 void stateloop()
488 {
489   uint8_t button;
490   char scmnd[20], log[LOGSZ];
491   
492   timerxs = millis() + (1000 / STATES);
493   state++;
494   if (state == STATES) {             // Every second
495     state = 0;
496     every_second();
497   }
498
499   button = digitalRead(KEY_PIN);
500   if ((button == PRESSED) && (lastbutton == NOT_PRESSED)) {
501     multipress = (multiwindow) ? multipress +1 : 1;
502     snprintf_P(log, sizeof(log), PSTR("APP: Multipress %d"), multipress);
503     addLog(LOG_LEVEL_DEBUG, log);
504     blinks = 1;
505     multiwindow = STATES /2;         // 1/2 second multi press window
506   }
507   lastbutton = button;
508   if (button == NOT_PRESSED) {
509     holdcount = 0;
510   } else {
511     holdcount++;
512     if (holdcount == (STATES *4)) {  // 4 seconds button hold
513 //      strlcpy(scmnd, commands[0], sizeof(scmnd));  // Fails progmem access
514       snprintf_P(scmnd, sizeof(scmnd), commands[0]);
515       multipress = 0;
516       do_cmnd(scmnd);
517     }
518   }
519   if (multiwindow) {
520     multiwindow--;
521   } else {
522     if ((!holdcount) && (multipress >= 1) && (multipress <= 5)) {
523 //      strlcpy(scmnd, commands[multipress], sizeof(scmnd));  // Fails progmem access
524       snprintf_P(scmnd, sizeof(scmnd), commands[multipress]);
525       if (strcmp(sysCfg.mqtt_topic2,"0") && (multipress == 1) && mqttClient.connected())
526         send_button(scmnd);          // Execute command via MQTT using ButtonTopic to sync external clients
527       else
528         do_cmnd(scmnd);              // Execute command internally 
529       multipress = 0;
530     }
531   }
532
533   if (!(state % ((STATES/10)*2))) {
534     if (blinks || restartflag || otaflag) {
535       if (restartflag || otaflag)
536         blinkstate = 0;   // Stay lit
537       else
538         blinkstate ^= 1;  // Blink
539       digitalWrite(LED_PIN, blinkstate);
540       if (blinkstate) blinks--;
541     } else {
542       if (sysCfg.ledstate) digitalWrite(LED_PIN, !sysCfg.power);
543     }
544   }
545
546   switch (state) {
547   case (STATES/10)*2:
548     if (otaflag) {
549       otaflag--;
550       if (otaflag <= 0) {
551         otaflag = 255;
552         ESPhttpUpdate.update(sysCfg.otaUrl);
553         send_updateStatus(ESPhttpUpdate.getLastErrorString().c_str());
554         restartflag = 2;
555       }
556     }
557     break;
558   case (STATES/10)*4:
559     CFG_Save();
560     if (restartflag) {
561       if (restartflag == 11) {
562         CFG_Default();
563         restartflag = 2;
564       }
565       if (restartflag == 12) {
566         CFG_Erase();
567         restartflag = 1;
568       }
569       restartflag--;
570       if (restartflag <= 0) ESP.restart();
571     }
572     break;
573   case (STATES/10)*6:
574     if (smartconfigflag) {
575       smartconfigflag = 0;
576       WIFI_Check(WIFI_SMARTCONFIG);
577     } else {
578       WIFI_Check(WIFI_STATUS);
579     }
580     break;
581   case (STATES/10)*8:
582     if ((WiFi.status() == WL_CONNECTED) && (!mqttClient.connected())) {
583       if (!mqttcounter)
584         mqtt_reconnect();
585       else
586         mqttcounter--;
587     }
588     break;
589   }
590 }
591
592 #ifdef SERIAL_IO
593 #define INPUT_BUFFER_SIZE          128
594
595 byte SerialInByte;
596 int SerialInByteCounter = 0;
597 char serialInBuf[INPUT_BUFFER_SIZE + 2];
598
599 void serial()
600 {
601   while (Serial.available())
602   {
603     yield();
604     SerialInByte = Serial.read();
605     if (SerialInByte > 127) // binary data...
606     {
607       Serial.flush();
608       SerialInByteCounter = 0;
609       return;
610     }
611     if (isprint(SerialInByte))
612     {
613       if (SerialInByteCounter < INPUT_BUFFER_SIZE) // add char to string if it still fits
614         serialInBuf[SerialInByteCounter++] = SerialInByte;
615       else
616         SerialInByteCounter = 0;
617     }
618     if (SerialInByte == '\n')
619     {
620       serialInBuf[SerialInByteCounter] = 0; // serial data completed
621       Serial.println(serialInBuf);
622       SerialInByteCounter = 0;
623       do_cmnd(serialInBuf);
624     }
625   }
626 }
627 #endif
628
629 /********************************************************************************************/
630
631 void setup()
632 {
633   char log[128];
634
635   Serial.begin(115200);
636   delay(10);
637   Serial.println();
638
639   snprintf_P(Version, sizeof(Version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff);
640   if (VERSION & 0x1f) {
641     byte idx = strlen(Version);
642     Version[idx] = 96 + (VERSION & 0x1f);
643     Version[idx +1] = 0;
644   }
645   CFG_Load();
646   if (sysCfg.version != VERSION) {      // Fix version dependent changes
647     if (sysCfg.version < 0x01000D00) {  // 1.0.13 - Add ledstate
648       sysCfg.ledstate = APP_LEDSTATE;
649     }
650     
651     sysCfg.version = VERSION;
652   }
653
654   snprintf_P(Hostname, sizeof(Hostname), PSTR(WIFI_HOSTNAME), ESP.getChipId(), sysCfg.mqtt_topic);
655   WIFI_Connect(Hostname);
656
657   mqttClient.setServer(sysCfg.mqtt_host, MQTT_PORT);
658   mqttClient.setCallback(mqttDataCb);
659
660   pinMode(LED_PIN, OUTPUT);
661   digitalWrite(LED_PIN, blinkstate);
662
663   pinMode(REL_PIN, OUTPUT);
664   digitalWrite(REL_PIN, sysCfg.power);
665
666   pinMode(KEY_PIN, INPUT_PULLUP);
667
668   rtc_init(sysCfg.timezone);
669
670   snprintf_P(log, sizeof(log), PSTR("App: Project %s (Topic %s, Fallback "MQTT_CLIENT_ID", GroupTopic %s) Version %s"),
671     PROJECT, sysCfg.mqtt_topic, ESP.getChipId(), sysCfg.mqtt_grptopic, Version);
672   addLog(LOG_LEVEL_INFO, log);
673 }
674
675 void loop()
676 {
677   if (millis() >= timersec) {
678     timersec = millis() + 1000;
679     rtc_second();
680     if ((rtcTime.Minute == 2) && (rtcTime.Second == 30)) heartbeatflag = 1;
681   }
682
683   if (millis() >= timerxs) stateloop();
684
685   mqttClient.loop();
686
687 #ifdef SERIAL_IO
688   if (Serial.available()) serial();
689 #endif
690   
691   yield();
692 }
693