7d017b5721771165d94993165ebde5511a7dd2a8
[esp-clock.git] / web.cpp
1 #include "Clock.h"
2 #include <LittleFS.h>
3 #include <ESPAsyncWebServer.h>
4 #include <StreamString.h>
5 #include <Ticker.h>
6
7 bool isApEnabled = false;
8 bool isWebStarted = false;
9
10 bool pendingWiFi = false;
11 bool pendingAuth = false;
12
13 AsyncWebServer server(80);
14 AsyncEventSource events("/events");
15
16 Ticker tKeepalive;
17
18 char auth_user[32];
19 char auth_pwd[32];
20
21 void reportChange(const __FlashStringHelper* name) {
22   char buf[256];
23   ConfigParameter* param = cfg.getParam(name);
24   if (param) {
25     switch (param->getType()) {
26       case 'B':
27         sprintf(buf,"{\"%s\":%s}", name, param->getBoolValue()?"true":"false");
28         break;
29       case 'I':
30         sprintf(buf,"{\"%s\":%d}", name, param->getIntValue());
31         break;
32       case 'F':
33         sprintf(buf,"{\"%s\":%f}", name, param->getFloatValue());
34         break;
35       case 'S':
36         sprintf(buf,"{\"%s\":\"%s\"}", name, param->getCharValue());
37         break;
38     }
39     events.send(buf,"update",millis());
40   }
41 }
42
43 void reportMessage(const __FlashStringHelper* msg) {
44   char buf[256];
45   strcpy_P(buf, (PGM_P)msg);
46   events.send(buf,"message",millis());
47 }
48
49 void sendInitial(AsyncEventSourceClient *client) {
50   String mac = WiFi.macAddress();
51   char buf[256];
52   sprintf(buf,"{\"_mac\":\"%s\",\"_weather\":\"%s\"}", mac.c_str(), weatherData);
53   client->send(buf,"update",millis());
54   mac = String();
55 }
56
57 void sendWeather() {
58   char buf[256];
59   sprintf(buf,"{\"_weather\":\"%s\"}",weatherData);
60   events.send(buf,"update",millis());
61 }
62
63 void sendKeepalive() {
64   static unsigned long lastMillis = 0;
65   static unsigned long uptimeCorrection = 0;
66   unsigned long currentMillis = millis();
67   unsigned long uptime = millis()/1000;
68   if (currentMillis < lastMillis) {
69     uptimeCorrection += lastMillis/1000;
70   }
71   lastMillis = millis();
72   uptime = uptime + uptimeCorrection;
73   int days = uptime / 86400;
74   uptime = uptime % 86400;
75   int hrs = uptime / 3600;
76   uptime = uptime % 3600;
77   int mins = uptime / 60;
78   uptime = uptime % 60;
79   int heap = ESP.getFreeHeap();
80   int rssi = WiFi.RSSI();
81   struct tm* timeinfo = localtime(&last_sync);
82   bool changed = cfg.getTimestamp() != 0;
83   char sync[16] = "--:--:--";
84   if (last_sync) {
85     sprintf(sync, "%02d:%02d:%02d", timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
86   }
87   char buf[256];
88   if (days) {
89     sprintf(buf,"{\"_uptime\":\"%d д %d ч %d % м %d с\", \"_date\":\"%02d.%2d.%04d\", \"_time\":\"%02d:%02d\",\"_heap\":\"%d б\", \"_rssi\":\"%d\",  \"_last_sync\":\"%s\", \"_changed\":%s}", days, hrs, mins, uptime, dd, mm, yy, hh, mi, heap, rssi, sync, changed?"true":"false");
90   } else if (hrs) {
91     sprintf(buf,"{\"_uptime\":\"%d ч %d % м %d с\", \"_date\":\"%02d.%2d.%04d\", \"_time\":\"%02d:%02d\",\"_heap\":\"%d б\", \"_rssi\":\"%d\",  \"_last_sync\":\"%s\", \"_changed\":%s}", hrs, mins, uptime, dd, mm, yy, hh, mi, heap, rssi, sync, changed?"true":"false");
92   } else {
93     sprintf(buf,"{\"_uptime\":\"%d м %d с\", \"_date\":\"%02d.%2d.%04d\", \"_time\":\"%02d:%02d\",\"_heap\":\"%d б\", \"_rssi\":\"%d\",  \"_last_sync\":\"%s\", \"_changed\":%s}", mins, uptime, dd, mm, yy, hh, mi, heap, rssi, sync, changed?"true":"false");
94   }
95   events.send(buf,"update",millis());
96 }
97
98 void apply(const char* name) {
99   if (strcmp(name,"sta_ssid") == 0 || strcmp(name,"sta_psk") == 0) {
100     if (!isApEnabled) {
101       pendingWiFi = true;
102     }
103   } else if (strcmp(name,"ap_ssid") == 0 || strcmp(name,"ap_psk") == 0) {
104     if (isApEnabled) {
105       pendingWiFi = true;
106     }
107   } else if (strcmp(name,"auth_user") == 0 || strcmp(name,"auth_pwd") == 0) {
108     pendingAuth = true;
109   } else if (strcmp(name,"ntp_server") == 0 || strcmp(name,"tz") == 0) {
110     setupTime();
111   } else if (strcmp(name,"pin_sda") == 0 || strcmp(name,"pin_scl") == 0 ||
112     strcmp(name,"i2c_speed") == 0 || strcmp(name,"enable_rtc") == 0 ||
113     strcmp(name,"enable_button") == 0 || strcmp(name,"button_pin") == 0 || strcmp(name,"button_inversed") == 0 ||
114     strcmp(name,"enable_buzzer") == 0 || strcmp(name,"buzzer_pin") == 0) {
115     setupHardware();
116   } else if (strcmp(name,"pin_din") == 0 || strcmp(name,"pin_clk") == 0 ||
117     strcmp_P(name,"pin_cs") == 0 || strcmp(name,"led_modules") == 0){
118     setupPanel();
119   } else if (strcmp(name,"panel_brightness_day") == 0 || strcmp_P(name,"panel_brightness_night") == 0 ||
120     strcmp_P(name,"day_from") == 0 || strcmp(name,"night_from") == 0){
121     setPanelBrightness();
122   }
123 }
124
125 char* actionScheduled = nullptr;
126 unsigned long millisScheduled = 0;
127
128 void setupWeb() {
129   char buf[256];
130
131   if (isWebStarted) {
132     tKeepalive.detach();
133     server.end();
134   }
135
136   isWebStarted = true;
137
138   strncpy(auth_user,cfg.getCharValue(F("auth_user")),31);
139   strncpy(auth_pwd,cfg.getCharValue(F("auth_pwd")),31);
140
141   server.on("/action", HTTP_GET, [](AsyncWebServerRequest* request) {
142     if (auth_user && auth_pwd && auth_user[0] && auth_pwd[0] && !request->authenticate(auth_user, auth_pwd)) {
143       return request-> requestAuthentication();
144     }
145     if(request->hasParam("name")) {
146       const char* action = request->getParam("name")->value().c_str();
147       if (strcmp(action,"restart") == 0) {
148         if (pendingWiFi) {
149           request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки WiFi\", \"page\":\"wifi\"}");
150         } else if (pendingAuth) {
151           request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки авторизации\", \"page\":\"system\"}");
152         } else {
153           request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Перезагружаюсь\"}");
154           millisScheduled = millis();
155           actionScheduled = "restart";
156         }
157       } else if (strcmp(action,"wifi") == 0) {
158         if (pendingAuth) {
159           request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки авторизации\", \"page\":\"system\"}");
160         } else {
161           request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Применяю настройки\"}");
162           millisScheduled = millis();
163           actionScheduled = "wifi";
164         }
165       } else if (strcmp(action,"auth") == 0) {
166         request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Применяю настройки\"}");
167         millisScheduled = millis();
168         actionScheduled = "auth";
169       } else if (strcmp(action,"save") == 0) {
170         if (pendingWiFi) {
171           request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки WiFi\", \"page\":\"wifi\"}");
172         } else if (pendingAuth) {
173           request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки авторизации\", \"page\":\"system\"}");
174         } else {
175           request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Сохраняю настройки\"}");
176           millisScheduled = millis();
177           actionScheduled = "save";
178         }
179       } else if (strcmp(action,"time") == 0) {
180         if(request->hasParam("timestamp")) {
181           unsigned long timestamp = atoi(request->getParam("timestamp")->value().c_str());
182           if (timestamp) {
183             timeval tv = { timestamp, 0 };
184             settimeofday(&tv, nullptr);  
185             if (isRTCEnabled) {
186               Serial.println(F("Время установлено вручную"));
187               RTC.adjust(DateTime(timestamp));
188             }
189           }
190           request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Устанавливаю время\"}");
191         } else {
192           request->send(500, "text/plain", "{\"result\":\"FAILED\",\"message\":\"Not all parameters set\"}");
193         }
194       } else {
195         request->send(500, "text/plain", "{\"result\":\"FAILED\",\"message\":\"Unsupported action\"}");
196       }
197     } else {
198       request->send(500, "text/plain", "{\"result\":\"FAILED\",\"message\":\"Not all parameters set\"}");
199     }
200   });
201
202   server.on("/wifi/scan", HTTP_GET, [](AsyncWebServerRequest* request) {
203     if (auth_user && auth_pwd && auth_user[0] && auth_pwd[0] && !request->authenticate(auth_user, auth_pwd)) {
204       return request-> requestAuthentication();
205     }
206     String json = "[";
207     int n = WiFi.scanComplete();
208     if (n == -2) {
209       WiFi.scanNetworks(true);
210     } else if (n) {
211       for (int i = 0; i < n; ++i) {
212         if (i) json += ",";
213         json += "{";
214         json += "\"rssi\":" + String(WiFi.RSSI(i));
215         json += ",\"ssid\":\"" + WiFi.SSID(i) + "\"";
216         json += ",\"bssid\":\"" + WiFi.BSSIDstr(i) + "\"";
217         json += ",\"channel\":" + String(WiFi.channel(i));
218         json += ",\"secure\":" + String(WiFi.encryptionType(i));
219         json += ",\"hidden\":" + String(WiFi.isHidden(i) ? "true" : "false");
220         json += "}";
221       }
222       WiFi.scanDelete();
223       if (WiFi.scanComplete() == -2) {
224         WiFi.scanNetworks(true);
225       }
226     }
227     json += "]";
228     request->send(200, "application/json", json);
229     json = String();
230   });
231
232   server.on("/config/get", HTTP_GET, [](AsyncWebServerRequest* request) {
233     if (auth_user && auth_pwd && auth_user[0] && auth_pwd[0] && !request->authenticate(auth_user, auth_pwd)) {
234       return request-> requestAuthentication();
235     }
236     AsyncResponseStream* s = request->beginResponseStream("application/json");
237     s->print("{");
238     for (int i = 0; i < cfg.getParametersCount(); i++) {
239       ConfigParameter* param = cfg.getParameter(i);
240       if (i) s->print(",");
241       s->print("\"");
242       s->print(param->getID());
243       s->print("\":");
244       switch (param->getType()) {
245         case 'B':
246           s->print(param->getBoolValue() ? "true" : "false");
247           break;
248         case 'I':
249           s->print(param->getIntValue());
250           break;
251         case 'F':
252           s->print(param->getFloatValue());
253           break;
254         case 'S':
255           s->print("\"");
256           s->print(param->getCharValue());
257           s->print("\"");
258           break;
259       }
260     }
261     s->print("}");
262     request->send(s);
263   });
264
265   server.on("/config/set", HTTP_GET, [](AsyncWebServerRequest* request) {
266     if (auth_user && auth_pwd && auth_user[0] && auth_pwd[0] && !request->authenticate(auth_user, auth_pwd)) {
267       return request-> requestAuthentication();
268     }
269     if(request->hasParam("name") && (request->hasParam("value"))) {
270       const char* name = request->getParam("name")->value().c_str();
271       const char* value = request->getParam("value")->value().c_str();
272       Serial.print(name); Serial.print(" = "); Serial.println(value);
273       char buf[256];
274       ConfigParameter* param = cfg.getParam(name);
275       if (param) {
276         switch (param->getType()) {
277           case 'B':
278             cfg.setValue(name, strcmp(value, "true")==0);
279             sprintf(buf,"{\"%s\":%s}", name, param->getBoolValue()?"true":"false");
280             break;
281           case 'I':
282             cfg.setValue(name, atoi(value));
283             sprintf(buf,"{\"%s\":%d}", name, param->getIntValue());
284             break;
285           case 'F':
286             cfg.setValue(name, atof(value));
287             sprintf(buf,"{\"%s\":%f}", name, param->getFloatValue());
288             break;
289           case 'S':
290             cfg.setValue(name, value);
291             sprintf(buf,"{\"%s\":\"%s\"}", name, param->getCharValue());
292             break;
293         }
294         apply(name);
295       } else {
296           request->send(500, "text/plain", "Unknown parameter name");
297           return;
298       }
299       request->send(200,"application/json",buf);
300      } else {
301       request->send(500, "text/plain", "Not all parameters set");
302     }
303   });
304
305   server.serveStatic("ui", LittleFS, "/ui.json").setAuthentication(auth_user,auth_pwd);
306
307   server.serveStatic("/", LittleFS, "/web/").setDefaultFile("index.html").setAuthentication(auth_user,auth_pwd);
308
309   server.onNotFound([](AsyncWebServerRequest *request){
310     request->send(404,"text/plain","Not found");
311   });
312
313   events.onConnect([](AsyncEventSourceClient *client){
314     sendInitial(client);
315   });
316
317   events.setAuthentication(auth_user,auth_pwd);
318
319   server.addHandler(&events);
320
321   server.begin();
322
323   tKeepalive.attach(2, sendKeepalive);
324
325 }
326
327 #define CFG_AUTOSAVE 15
328
329 void tickWeb() {
330   static char storedSSID[64];
331   static char storedPSK[64];
332   static bool connectInProgress = false;
333   static unsigned long connectMillis = 0;
334   if (actionScheduled && millis()>millisScheduled+300) {
335     Serial.print(F("Запланированная операция ")); Serial.println(actionScheduled);
336     //
337     if (strcmp(actionScheduled,"restart") == 0) {
338       server.end();
339       reboot();
340     } else if (strcmp(actionScheduled,"auth") == 0) {
341       Serial.println("Логин/пароль изменены");
342       strncpy(auth_user,cfg.getCharValue(F("auth_user")),31);
343       strncpy(auth_pwd,cfg.getCharValue(F("auth_pwd")),31);
344       pendingAuth = false;
345     } else if (strcmp(actionScheduled,"wifi") == 0) {
346       Serial.println("Применяю настройки сети");
347       strcpy(storedSSID,WiFi.SSID().c_str());
348       strcpy(storedPSK,WiFi.psk().c_str());
349       WiFi.mode(WIFI_STA);
350       WiFi.begin(cfg.getCharValue("sta_ssid"),cfg.getCharValue("sta_psk"));
351       connectInProgress = true;
352       connectMillis = millis();
353     } else if (strcmp(actionScheduled,"save") == 0 && !pendingWiFi && !pendingAuth) {
354       saveConfig();
355     }
356     actionScheduled = nullptr;
357   }
358   if (connectInProgress && (millis() > connectMillis + 1000)) {
359     if (WiFi.status() == WL_CONNECTED) {
360       char buf[64];
361       sprintf(buf,"Подключен к %s, IP=%s", WiFi.SSID(), WiFi.localIP().toString().c_str());
362       Serial.println(buf);
363       message(buf,1);
364       pendingWiFi = false;
365       connectInProgress = false;
366     } else if (WiFi.status() == WL_CONNECT_FAILED || WiFi.status() == WL_NO_SSID_AVAIL || ((WiFi.status() == WL_CONNECTION_LOST || WiFi.status() == WL_DISCONNECTED) && (millis()>connectMillis+1000*cfg.getIntValue("sta_wait")))) {
367       Serial.println(F("Подключение не удалось, возвращаю прежние настройки"));
368       message(F("Подключение не удалось, возвращаю прежние настройки"),1);
369       WiFi.begin(storedSSID, storedPSK);
370       connectInProgress = false;
371     }
372   }
373
374   if (!pendingWiFi && !pendingAuth && cfg.getTimestamp() && cfg.getTimestamp() < now - CFG_AUTOSAVE) {
375     saveConfig();
376     reportMessage(F("Настройки сохранены"));
377     Serial.println(F("Настройки сохранены"));
378   }
379 }