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