https://github.com/arendst/Sonoff-MQTT-OTA-Arduino
[Arduino] / sonoff / support.ino
1 /*********************************************************************************************\
2  * Config
3 \*********************************************************************************************/
4
5 SYSCFG myCfg;
6
7 void CFG_Default()
8 {
9   addLog(LOG_LEVEL_INFO, "Config: Use default configuration");
10   memset(&sysCfg, 0x00, sizeof(SYSCFG));
11   memset(&myCfg, 0x00, sizeof(SYSCFG));
12   sysCfg.cfg_holder = CFG_HOLDER;
13   sysCfg.saveFlag = 0;
14   sysCfg.version = VERSION;
15   sysCfg.seriallog_level = SERIAL_LOG_LEVEL;
16   sysCfg.syslog_level = SYS_LOG_LEVEL;
17   strlcpy(sysCfg.syslog_host, SYS_LOG_HOST, sizeof(sysCfg.syslog_host));
18   strlcpy(sysCfg.sta_ssid, STA_SSID, sizeof(sysCfg.sta_ssid));
19   strlcpy(sysCfg.sta_pwd, STA_PASS, sizeof(sysCfg.sta_pwd));
20   strlcpy(sysCfg.otaUrl, OTA_URL, sizeof(sysCfg.otaUrl));
21   strlcpy(sysCfg.mqtt_host, MQTT_HOST, sizeof(sysCfg.mqtt_host));
22   strlcpy(sysCfg.mqtt_grptopic, MQTT_GRPTOPIC, sizeof(sysCfg.mqtt_grptopic));
23   strlcpy(sysCfg.mqtt_topic, MQTT_TOPIC, sizeof(sysCfg.mqtt_topic));
24   strlcpy(sysCfg.mqtt_topic2, "0", sizeof(sysCfg.mqtt_topic2));
25   strlcpy(sysCfg.mqtt_subtopic, MQTT_SUBTOPIC, sizeof(sysCfg.mqtt_subtopic));
26   sysCfg.timezone = APP_TIMEZONE;
27   sysCfg.power = APP_POWER;
28   sysCfg.ledstate = APP_LEDSTATE;
29   CFG_Save();
30 }
31
32 extern "C" {
33 #include "spi_flash.h"
34 }
35 extern "C" uint32_t _SPIFFS_end;
36 #define CFG_LOCATION (((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE) - 2  // End of SPIFFS area
37
38 void CFG_Save()
39 {
40   char log[LOGSZ];
41   
42   if (memcmp(&myCfg, &sysCfg, sizeof(SYSCFG))) {
43     noInterrupts();
44     if (sysCfg.saveFlag == 0) {  // Handle default and rollover
45       spi_flash_erase_sector(CFG_LOCATION + (sysCfg.saveFlag &1));
46       spi_flash_write((CFG_LOCATION + (sysCfg.saveFlag &1)) * SPI_FLASH_SEC_SIZE, (uint32 *)&sysCfg, sizeof(SYSCFG));
47     }
48     sysCfg.saveFlag++;
49     spi_flash_erase_sector(CFG_LOCATION + (sysCfg.saveFlag &1));
50     spi_flash_write((CFG_LOCATION + (sysCfg.saveFlag &1)) * SPI_FLASH_SEC_SIZE, (uint32 *)&sysCfg, sizeof(SYSCFG));
51     interrupts();
52     snprintf_P(log, sizeof(log), PSTR("Config: Saved configuration to flash at %X and count %d"), CFG_LOCATION + (sysCfg.saveFlag &1), sysCfg.saveFlag);
53     addLog(LOG_LEVEL_DEBUG, log);
54     myCfg = sysCfg;
55   }
56 }
57
58 void CFG_Load()
59 {
60   char log[LOGSZ];
61
62   noInterrupts();
63   spi_flash_read((CFG_LOCATION) * SPI_FLASH_SEC_SIZE, (uint32 *)&sysCfg, sizeof(SYSCFG));
64   spi_flash_read((CFG_LOCATION + 1) * SPI_FLASH_SEC_SIZE, (uint32 *)&myCfg, sizeof(SYSCFG));
65   interrupts();
66   if (sysCfg.saveFlag < myCfg.saveFlag) sysCfg = myCfg;
67   snprintf_P(log, sizeof(log), PSTR("Config: Loaded configuration from flash at %X and count %d"), CFG_LOCATION + (sysCfg.saveFlag &1), sysCfg.saveFlag);
68   addLog(LOG_LEVEL_DEBUG, log);
69   if (sysCfg.cfg_holder != CFG_HOLDER) CFG_Default();
70   myCfg = sysCfg;
71 }
72
73 void CFG_Erase()
74 {
75   char log[LOGSZ];
76   SpiFlashOpResult result;
77
78   uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1;
79   uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE;
80   byte seriallog_level = sysCfg.seriallog_level;
81
82   snprintf_P(log, sizeof(log), PSTR("Config: Erasing %d flash sectors"), _sectorEnd - _sectorStart);
83   addLog(LOG_LEVEL_DEBUG, log);
84
85   for (uint32_t _sector = _sectorStart; _sector < _sectorEnd; _sector++) {
86     noInterrupts();
87     result = spi_flash_erase_sector(_sector);
88     interrupts();
89     if (LOG_LEVEL_DEBUG_MORE <= seriallog_level) {
90       Serial.print(F("Flash: Erased sector "));
91       Serial.print(_sector);
92       if (result == SPI_FLASH_RESULT_OK)
93         Serial.println(F(" OK"));
94       else
95         Serial.println(F(" Error"));
96       delay(10);
97     }
98   }
99 }
100
101 /*********************************************************************************************\
102  * Wifi
103 \*********************************************************************************************/
104
105 #define WIFI_SMARTSEC  60   // seconds
106 #define WIFI_CHECKSEC  20   // seconds
107 #define WIFI_RETRY     16
108
109 uint8_t wificounter;
110 uint8_t wifiretry = WIFI_RETRY;
111 uint8_t smartcounter = 0;
112
113 void WIFI_smartconfig()
114 {
115   smartcounter = WIFI_SMARTSEC;   // Allow up to WIFI_SMARTSECS seconds for phone to provide ssid/pswd
116   wificounter = smartcounter +5;
117   addLog(LOG_LEVEL_INFO, "Smartconfig: Started and active for 1 minute");
118   WiFi.beginSmartConfig();
119 }
120
121 void WIFI_check_ip()
122 {
123   if ((WiFi.status() == WL_CONNECTED) && (static_cast<uint32_t>(WiFi.localIP()) != 0)) {
124     wificounter = WIFI_CHECKSEC;
125     wifiretry = WIFI_RETRY;
126   } else {
127     switch (WiFi.status()) {
128       case WL_NO_SSID_AVAIL:
129       case WL_CONNECT_FAILED:
130         addLog(LOG_LEVEL_DEBUG, "Wifi: STATION_CONNECT_FAIL");
131         WIFI_smartconfig();
132         break;
133       default:
134         addLog(LOG_LEVEL_DEBUG, "Wifi: STATION_IDLE");
135         if (wifiretry == (WIFI_RETRY / 2)) WiFi.begin();
136         wifiretry--;
137         if (wifiretry)
138           wificounter = 1;
139         else
140           WIFI_smartconfig();
141         break;
142     }
143   }
144 }
145
146 void WIFI_Check(uint8_t param)
147 {
148   char log[LOGSZ];
149   
150   wificounter--;
151   switch (param) {
152     case WIFI_SMARTCONFIG:
153       WIFI_smartconfig();
154       break;
155     default:
156       if (wificounter <= 0) {
157         addLog(LOG_LEVEL_DEBUG_MORE, "Wifi: Check connection");
158         wificounter = WIFI_CHECKSEC;
159         WIFI_check_ip();
160       }
161       if (smartcounter) {
162         smartcounter--;
163         if (smartcounter) {
164           if (WiFi.smartConfigDone()) {
165             smartcounter = 0;
166             memcpy(sysCfg.sta_ssid, WiFi.SSID().c_str(), strlen(WiFi.SSID().c_str())+1);
167             memcpy(sysCfg.sta_pwd, WiFi.psk().c_str(), strlen(WiFi.psk().c_str())+1);
168             snprintf_P(log, sizeof(log), PSTR("Smartconfig: SSID %s and Password %s"), sysCfg.sta_ssid, sysCfg.sta_pwd);
169             addLog(LOG_LEVEL_INFO, log);
170           }
171         }
172         if (smartcounter == 0) {
173           WiFi.stopSmartConfig();
174           restartflag = 2;     
175         }
176       }
177       break;
178   }
179 }
180
181 void WIFI_Connect(char *Hostname)
182 {
183   char log[LOGSZ];
184
185   WiFi.persistent(false);   // Solve possible wifi init errors
186   WiFi.hostname(Hostname);
187   snprintf_P(log, sizeof(log), PSTR("Wifi: Connecting to %s as %s"), sysCfg.sta_ssid, Hostname);
188   addLog(LOG_LEVEL_DEBUG, log);
189   WiFi.setAutoConnect(true);
190   WiFi.mode(WIFI_STA);     // Disable AP mode
191   WiFi.begin(sysCfg.sta_ssid, sysCfg.sta_pwd);
192   wificounter = 1;
193 }
194
195 /*********************************************************************************************\
196  * Real Time Clock
197 \*********************************************************************************************/
198
199 #define NTP_SERVER1 "pool.ntp.org"
200 #define NTP_SERVER2 "nl.pool.ntp.org"
201 #define NTP_SERVER3 "0.nl.pool.ntp.org"
202
203 extern "C" {
204 #include "sntp.h"
205 }
206
207 #define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
208
209 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
210
211 static const char monthNames[37] = { "JanFebMrtAprMayJunJulAugSepOctNovDec" };
212
213 uint32_t myrtc = 0;
214 uint8_t ntpsync = 0;
215
216 void convertTime()
217 {
218   // given myrtc as time in Linux format break into time components
219   // this is a more compact version of the C library localtime function
220   // note that internally the year is offset from 1970 - compensated at the end
221
222   uint8_t year;
223   uint8_t month, monthLength;
224   uint32_t time;
225   unsigned long days;
226
227   time = (uint32_t) myrtc;
228   rtcTime.Second = time % 60;
229   time /= 60; // now it is minutes
230   rtcTime.Minute = time % 60;
231   time /= 60; // now it is hours
232   rtcTime.Hour = time % 24;
233   time /= 24; // now it is days
234   rtcTime.Wday = ((time + 4) % 7) + 1;  // Sunday is day 1
235
236   year = 0;
237   days = 0;
238   while ((unsigned) (days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
239     year++;
240   }
241   rtcTime.Year = year; // year is offset from 1970
242
243   days -= LEAP_YEAR(year) ? 366 : 365;
244   time -= days; // now it is days in this year, starting at 0
245
246   days = 0;
247   month = 0;
248   monthLength = 0;
249   for (month = 0; month < 12; month++) {
250     if (month == 1) { // february
251       if (LEAP_YEAR(year)) {
252         monthLength = 29;
253       }
254       else {
255         monthLength = 28;
256       }
257     }
258     else {
259       monthLength = monthDays[month];
260     }
261
262     if (time >= monthLength) {
263       time -= monthLength;
264     }
265     else {
266       break;
267     }
268   }
269   strlcpy(rtcTime.MonthName, monthNames +(month *3), sizeof(rtcTime.MonthName));
270   rtcTime.Month = month + 1;  // jan is month 1
271   rtcTime.Day = time + 1;     // day of month
272   rtcTime.Year = rtcTime.Year + 1970;
273 }
274
275 void rtc_second()
276 {
277   char log[LOGSZ];
278   
279   // NTP Sync every hour at x:0:10
280   if (rtcTime.Minute == 0) {
281     if ((rtcTime.Second >= 10) && !ntpsync) {
282       myrtc = sntp_get_current_timestamp();
283       ntpsync = (myrtc) ? 1 : 0;
284       snprintf_P(log, sizeof(log), PSTR("RTC: sntp %d, %s"), myrtc, sntp_get_real_time(myrtc));
285       addLog(LOG_LEVEL_DEBUG, log);
286     }
287     if (rtcTime.Second == 40) ntpsync = 0;
288   }
289   myrtc++;
290   convertTime();
291 }
292
293 void rtc_timezone(uint8_t timezone)
294 {
295   sntp_stop();
296 //  if (true == sntp_set_timezone(sysCfg.timezone))
297   sntp_set_timezone(timezone);
298   sntp_init();
299   myrtc = sntp_get_current_timestamp();
300   ntpsync = 0;
301 }
302
303 void rtc_init(uint8_t timezone)
304 {
305   sntp_setservername(0, NTP_SERVER1);
306   sntp_setservername(1, NTP_SERVER2);
307 //  sntp_setservername(2, NTP_SERVER3);
308   sntp_stop();
309 //  if (true == sntp_set_timezone(sysCfg.timezone))
310   sntp_set_timezone(timezone);
311   sntp_init();
312   myrtc = 0;
313 }
314
315 /*********************************************************************************************\
316  * Syslog
317 \*********************************************************************************************/
318
319 void syslog(const char *message)
320 {
321   char mess[MESSZ], str[TOPSZ+MESSZ];
322
323   portUDP.beginPacket(sysCfg.syslog_host, SYS_LOG_PORT);
324   strlcpy(mess, message, sizeof(mess));
325   mess[sizeof(mess)-1] = 0;
326   snprintf_P(str, sizeof(str), PSTR("%s %s"), Hostname, mess);
327   portUDP.write(str);
328   portUDP.endPacket();
329 }
330
331 void addLog(byte loglevel, const char *line)
332 {
333 #ifdef DEBUG_ESP_PORT
334   DEBUG_ESP_PORT.printf("DebugMsg %s\n", line);  
335 #endif
336 #ifdef SERIAL_IO
337   if (loglevel <= sysCfg.seriallog_level) Serial.println(line);
338 #endif
339   if ((WiFi.status() == WL_CONNECTED) && (loglevel <= sysCfg.syslog_level)) syslog(line);
340 }
341
342 void addLog(byte loglevel, String& string)
343 {
344   addLog(loglevel, string.c_str());
345 }
346
347 /*********************************************************************************************\
348  * 
349 \*********************************************************************************************/