3 #include <ESPAsyncWebServer.h>
4 #include <StreamString.h>
6 #include "ArduinoJson.h"
9 bool isApEnabled = false;
10 bool isWebStarted = false;
12 bool pendingWiFi = false;
13 bool pendingAuth = false;
15 AsyncWebServer server(80);
16 AsyncEventSource events("/events");
23 void reportChange(const __FlashStringHelper* name) {
25 ConfigParameter* param = cfg.getParam(name);
27 switch (param->getType()) {
29 sprintf(buf,"{\"%s\":%s}", name, param->getBoolValue()?"true":"false");
32 sprintf(buf,"{\"%s\":%d}", name, param->getIntValue());
35 sprintf(buf,"{\"%s\":%f}", name, param->getFloatValue());
38 sprintf(buf,"{\"%s\":\"%s\"}", name, param->getCharValue());
41 events.send(buf,"update",millis());
45 void reportMessage(const __FlashStringHelper* msg) {
47 strcpy_P(buf, (PGM_P)msg);
48 events.send(buf,"message",millis());
51 void sendInitial(AsyncEventSourceClient *client) {
52 String mac = WiFi.macAddress();
54 sprintf(buf,"{\"_mac\":\"%s\",\"_weather\":\"%s\"}", mac.c_str(), weatherData);
55 client->send(buf,"update",millis());
61 sprintf(buf,"{\"_weather\":\"%s\"}",weatherData);
62 events.send(buf,"update",millis());
65 void sendKeepalive() {
66 static unsigned long lastMillis = 0;
67 static unsigned long uptimeCorrection = 0;
68 unsigned long currentMillis = millis();
69 unsigned long uptime = millis()/1000;
70 if (currentMillis < lastMillis) {
71 uptimeCorrection += lastMillis/1000;
73 lastMillis = millis();
74 uptime = uptime + uptimeCorrection;
75 int days = uptime / 86400;
76 uptime = uptime % 86400;
77 int hrs = uptime / 3600;
78 uptime = uptime % 3600;
79 int mins = uptime / 60;
81 int heap = ESP.getFreeHeap();
82 int rssi = WiFi.RSSI();
83 struct tm* timeinfo = localtime(&last_sync);
84 bool changed = cfg.getTimestamp() != 0;
85 char sync[16] = "--:--:--";
87 sprintf(sync, "%02d:%02d:%02d", timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
91 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");
93 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");
95 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");
97 events.send(buf,"update",millis());
100 void apply(const char* name) {
101 if (strcmp(name,"sta_ssid") == 0 || strcmp(name,"sta_psk") == 0) {
105 } else if (strcmp(name,"ap_ssid") == 0 || strcmp(name,"ap_psk") == 0) {
109 } else if (strcmp(name,"auth_user") == 0 || strcmp(name,"auth_pwd") == 0) {
111 } else if (strcmp(name,"ntp_server") == 0 || strcmp(name,"tz") == 0) {
113 } else if (strcmp(name,"pin_sda") == 0 || strcmp(name,"pin_scl") == 0 ||
114 strcmp(name,"i2c_speed") == 0 || strcmp(name,"enable_rtc") == 0 ||
115 strcmp(name,"enable_button") == 0 || strcmp(name,"button_pin") == 0 || strcmp(name,"button_inversed") == 0 ||
116 strcmp(name,"enable_buzzer") == 0 || strcmp(name,"buzzer_pin") == 0) {
118 } else if (strcmp(name,"pin_din") == 0 || strcmp(name,"pin_clk") == 0 ||
119 strcmp_P(name,"pin_cs") == 0 || strcmp(name,"led_modules") == 0){
121 } else if (strcmp(name,"panel_brightness_day") == 0 || strcmp_P(name,"panel_brightness_night") == 0 ||
122 strcmp_P(name,"day_from") == 0 || strcmp(name,"night_from") == 0){
123 setPanelBrightness();
127 char* actionScheduled = nullptr;
128 unsigned long millisScheduled = 0;
140 strncpy(auth_user,cfg.getCharValue(F("auth_user")),31);
141 strncpy(auth_pwd,cfg.getCharValue(F("auth_pwd")),31);
143 server.on("/action", HTTP_GET, [](AsyncWebServerRequest* request) {
144 if (auth_user && auth_pwd && auth_user[0] && auth_pwd[0] && !request->authenticate(auth_user, auth_pwd)) {
145 return request-> requestAuthentication();
147 if(request->hasParam("name")) {
148 const char* action = request->getParam("name")->value().c_str();
149 if (strcmp(action,"reset") == 0) {
150 millisScheduled = millis();
151 actionScheduled = "reset";
152 request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Сбрасываю настройки и перезагружаюсь\"}");
153 } else if (strcmp(action,"restart") == 0) {
155 request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки WiFi\", \"page\":\"wifi\"}");
156 } else if (pendingAuth) {
157 request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки авторизации\", \"page\":\"system\"}");
159 request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Перезагружаюсь\"}");
160 millisScheduled = millis();
161 actionScheduled = "restart";
163 } else if (strcmp(action,"wifi") == 0) {
165 request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки авторизации\", \"page\":\"system\"}");
167 request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Применяю настройки\"}");
168 millisScheduled = millis();
169 actionScheduled = "wifi";
171 } else if (strcmp(action,"auth") == 0) {
172 request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Применяю настройки\"}");
173 millisScheduled = millis();
174 actionScheduled = "auth";
175 } else if (strcmp(action,"save") == 0) {
177 request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки WiFi\", \"page\":\"wifi\"}");
178 } else if (pendingAuth) {
179 request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки авторизации\", \"page\":\"system\"}");
181 request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Сохраняю настройки\"}");
182 millisScheduled = millis();
183 actionScheduled = "save";
185 } else if (strcmp(action,"time") == 0) {
186 if(request->hasParam("timestamp")) {
187 unsigned long timestamp = atoi(request->getParam("timestamp")->value().c_str());
189 timeval tv = { timestamp, 0 };
190 settimeofday(&tv, nullptr);
192 Serial.println(F("Время установлено вручную"));
193 RTC.adjust(DateTime(timestamp));
196 request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Устанавливаю время\"}");
198 request->send(500, "text/plain", "{\"result\":\"FAILED\",\"message\":\"Not all parameters set\"}");
201 request->send(500, "text/plain", "{\"result\":\"FAILED\",\"message\":\"Unsupported action\"}");
204 request->send(500, "text/plain", "{\"result\":\"FAILED\",\"message\":\"Not all parameters set\"}");
208 server.on("/wifi/scan", HTTP_GET, [](AsyncWebServerRequest* request) {
209 if (auth_user && auth_pwd && auth_user[0] && auth_pwd[0] && !request->authenticate(auth_user, auth_pwd)) {
210 return request-> requestAuthentication();
213 int n = WiFi.scanComplete();
215 WiFi.scanNetworks(true);
217 for (int i = 0; i < n; ++i) {
220 json += "\"rssi\":" + String(WiFi.RSSI(i));
221 json += ",\"ssid\":\"" + WiFi.SSID(i) + "\"";
222 json += ",\"bssid\":\"" + WiFi.BSSIDstr(i) + "\"";
223 json += ",\"channel\":" + String(WiFi.channel(i));
224 json += ",\"secure\":" + String(WiFi.encryptionType(i));
225 json += ",\"hidden\":" + String(WiFi.isHidden(i) ? "true" : "false");
229 if (WiFi.scanComplete() == -2) {
230 WiFi.scanNetworks(true);
234 request->send(200, "application/json", json);
238 server.on("/config/get", HTTP_GET, [](AsyncWebServerRequest* request) {
239 if (auth_user && auth_pwd && auth_user[0] && auth_pwd[0] && !request->authenticate(auth_user, auth_pwd)) {
240 return request-> requestAuthentication();
242 AsyncResponseStream* s = request->beginResponseStream("application/json");
244 for (int i = 0; i < cfg.getParametersCount(); i++) {
245 ConfigParameter* param = cfg.getParameter(i);
246 if (i) s->print(",");
248 s->print(param->getID());
250 switch (param->getType()) {
252 s->print(param->getBoolValue() ? "true" : "false");
255 s->print(param->getIntValue());
258 s->print(param->getFloatValue());
262 s->print(param->getCharValue());
271 server.on("/config/set", HTTP_GET, [](AsyncWebServerRequest* request) {
272 if (auth_user && auth_pwd && auth_user[0] && auth_pwd[0] && !request->authenticate(auth_user, auth_pwd)) {
273 return request-> requestAuthentication();
275 if(request->hasParam("name") && (request->hasParam("value"))) {
276 const char* name = request->getParam("name")->value().c_str();
277 const char* value = request->getParam("value")->value().c_str();
278 Serial.print(name); Serial.print(" = "); Serial.println(value);
280 ConfigParameter* param = cfg.getParam(name);
282 switch (param->getType()) {
284 cfg.setValue(name, strcmp(value, "true")==0);
285 sprintf(buf,"{\"%s\":%s}", name, param->getBoolValue()?"true":"false");
288 cfg.setValue(name, atoi(value));
289 sprintf(buf,"{\"%s\":%d}", name, param->getIntValue());
292 cfg.setValue(name, atof(value));
293 sprintf(buf,"{\"%s\":%f}", name, param->getFloatValue());
296 cfg.setValue(name, value);
297 sprintf(buf,"{\"%s\":\"%s\"}", name, param->getCharValue());
302 request->send(500, "text/plain", "Unknown parameter name");
305 request->send(200,"application/json",buf);
307 request->send(500, "text/plain", "Not all parameters set");
311 AsyncCallbackJsonWebHandler* configUploadHandler = new AsyncCallbackJsonWebHandler("/config/put", [](AsyncWebServerRequest *request, JsonVariant &json) {
313 // first - set values
314 for( JsonPair kv : json.as<JsonObject>() ) {
315 const char* name = kv.key().c_str();
316 if (kv.value().is<bool>()) {
317 cfg.setValue(name, kv.value().as<bool>());
318 } else if (kv.value().is<int>()) {
319 cfg.setValue(name, kv.value().as<int>());
320 } else if (kv.value().is<double>()) {
321 cfg.setValue(name, kv.value().as<double>());
322 } else if (kv.value().is<char*>()) {
323 cfg.setValue(name, kv.value().as<char*>());
325 Serial.print(F("Неопознанный тип значения параметра ")); Serial.print(name); Serial.print(": "); Serial.println(kv.value().as<String>().c_str());
328 request->send(500, "text/plain", "Unknown parameter type");
331 // second - handle all changes
332 for( JsonPair kv : json.as<JsonObject>() ) {
333 apply(kv.key().c_str());
338 message(F("Применены сохраненные настройки"),5);
339 reportMessage(F("Применены сохраненные настройки"));
340 millisScheduled = millis() + 10000;
341 actionScheduled = "restart";
342 request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Настройки восстановлены из резервной копии\"}");
345 server.addHandler(configUploadHandler).setAuthentication(auth_user,auth_pwd);
347 server.serveStatic("ui", LittleFS, "/ui.json").setAuthentication(auth_user,auth_pwd);
349 server.serveStatic("/", LittleFS, "/web/").setDefaultFile("index.html").setAuthentication(auth_user,auth_pwd);
351 server.onNotFound([](AsyncWebServerRequest *request){
352 request->send(404,"text/plain","Not found");
355 events.onConnect([](AsyncEventSourceClient *client){
359 events.setAuthentication(auth_user,auth_pwd);
361 server.addHandler(&events);
365 tKeepalive.attach(2, sendKeepalive);
369 #define CFG_AUTOSAVE 15
372 static char storedSSID[64];
373 static char storedPSK[64];
374 static bool connectInProgress = false;
375 static unsigned long connectMillis = 0;
376 if (actionScheduled && millis()>millisScheduled+300) {
377 Serial.print(F("Запланированная операция ")); Serial.println(actionScheduled);
379 if (strcmp(actionScheduled,"reset") == 0) {
382 } else if (strcmp(actionScheduled,"restart") == 0) {
385 } else if (strcmp(actionScheduled,"auth") == 0) {
386 Serial.println("Логин/пароль изменены");
387 strncpy(auth_user,cfg.getCharValue(F("auth_user")),31);
388 strncpy(auth_pwd,cfg.getCharValue(F("auth_pwd")),31);
390 } else if (strcmp(actionScheduled,"wifi") == 0) {
391 Serial.println("Применяю настройки сети");
392 strcpy(storedSSID,WiFi.SSID().c_str());
393 strcpy(storedPSK,WiFi.psk().c_str());
395 WiFi.begin(cfg.getCharValue("sta_ssid"),cfg.getCharValue("sta_psk"));
396 connectInProgress = true;
397 connectMillis = millis();
398 } else if (strcmp(actionScheduled,"save") == 0 && !pendingWiFi && !pendingAuth) {
401 actionScheduled = nullptr;
403 if (connectInProgress && (millis() > connectMillis + 1000)) {
404 if (WiFi.status() == WL_CONNECTED) {
406 sprintf(buf,"Подключен к %s, IP=%s", WiFi.SSID(), WiFi.localIP().toString().c_str());
410 connectInProgress = false;
411 } 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")))) {
412 Serial.println(F("Подключение не удалось, возвращаю прежние настройки"));
413 message(F("Подключение не удалось, возвращаю прежние настройки"),1);
414 WiFi.begin(storedSSID, storedPSK);
415 connectInProgress = false;
419 if (!pendingWiFi && !pendingAuth && cfg.getTimestamp() && cfg.getTimestamp() < now - CFG_AUTOSAVE) {
421 reportMessage(F("Настройки сохранены"));
422 Serial.println(F("Настройки сохранены"));