From: Roman Bazalevskiy Date: Fri, 18 Nov 2022 16:21:43 +0000 (+0300) Subject: Сохранение/восстановление/сброс настроек через веб-интерфейс. X-Git-Url: https://git.rvb.name/esp-clock.git/commitdiff_plain/3a386826fc7eec89b50beda371dcef0ad3adc2a5?hp=--cc Сохранение/восстановление/сброс настроек через веб-интерфейс. --- 3a386826fc7eec89b50beda371dcef0ad3adc2a5 diff --git a/config.cpp b/config.cpp index 0835802..9c46b95 100644 --- a/config.cpp +++ b/config.cpp @@ -556,6 +556,7 @@ char* distConfig PROGMEM = void reset() { messageModal(F("Сбрасываю настройки")); + delay(2000); if (File f = LittleFS.open(F("/config.txt"),"w")) { f.print(distConfig); f.close(); diff --git a/data/web/script.js b/data/web/script.js index cb4fa7e..ee43439 100644 --- a/data/web/script.js +++ b/data/web/script.js @@ -251,6 +251,25 @@ function sendTime(id) { } } +function downloadFile(url, name) { + const a = document.createElement('a') + a.href = url + a.download = name?name:url.split('/').pop() + document.body.appendChild(a) + a.click() + document.body.removeChild(a) +} + +function uploadConfig() { + var elem = document.getElementById('_config_file') + if (elem.value) { + var data = elem.files[0] + console.log(data) + fetch('/config/put', {method:'PUT',body:data}); + elem.value = null + } +} + function elementHTML(element) { var value if (parameters[element.id] || !isNaN(parameters[element.id])) { @@ -326,8 +345,8 @@ function elementHTML(element) { now.setMinutes(now.getMinutes() - now.getTimezoneOffset()) value = now.toISOString().slice(0, -1); return '
' - +'
' - + '
->
' + +'' + + '
->
' case 'text': return '

' + encode(value) + '

' case 'number': @@ -340,6 +359,13 @@ function elementHTML(element) { + '' + case 'config': + return '
' + + '
' + + '' + + '' + + '' + + '
' case 'table': default: return '
' @@ -432,9 +458,11 @@ GetUI() function initES() { if (!!window.EventSource) { var source = new EventSource('/events'); + openMsg('Соединение установлено') source.onerror = function(e) { if (source.readyState == 2) { + openMsg('Соединение прервано') setTimeout(initES, 5000); } }; diff --git a/data/web/style.css b/data/web/style.css index fad4817..243c659 100644 --- a/data/web/style.css +++ b/data/web/style.css @@ -1388,10 +1388,6 @@ label.switch.socket input[type=checkbox]:checked + span.slider:after { /* Time setter */ -.timesetter { - display: inline; -} - .inline-input.inline-input.inline-input { display: inline-block; width: calc(100% - 5em) @@ -1468,6 +1464,28 @@ input[type=number] { padding-right: 1em; } +/* Config */ + +input[type=file]::file-selector-button { + display: none; +} + +input[type=file]::-webkit-file-upload-button { + display: block; + width: 0; + height: 0; + margin-left: -100%; +} + +input[type=file]::-ms-browse { + display: none; +} + +.row-button.row-button.row-button { + display: inline-block; + margin: 0 2em 0 2em +} + /* fonts */ @font-face { diff --git a/panel.cpp b/panel.cpp index a02b52a..fc07261 100644 --- a/panel.cpp +++ b/panel.cpp @@ -133,6 +133,7 @@ void drawScroll() { void message(const char* str, int priority) { if (priority > currentPriority) return; + currentPriority = priority; utf8rus(str, msgBuf, 255); Panel->displayClear(); Panel->setFont(RomanCyrillic); @@ -143,6 +144,7 @@ void message(const char* str, int priority) { void message(const __FlashStringHelper* str, int priority) { if (priority > currentPriority) return; + currentPriority = priority; char buf[256]; strncpy_P(buf, (PGM_P)str, 255); utf8rus(buf, msgBuf, 255); @@ -155,12 +157,12 @@ void message(const __FlashStringHelper* str, int priority) { void messageModal(const char* str) { message(str); - while (!(Panel->displayAnimate())) { delay(50); } + while (!(Panel->displayAnimate())) { delay(10); } } void messageModal(const __FlashStringHelper* str) { message(str); - while (!(Panel->displayAnimate())) { delay(50); } + while (!(Panel->displayAnimate())) { delay(10); } } void scroll(const char* str, bool force) { diff --git a/ui.yml b/ui.yml index b54720d..ec261c2 100644 --- a/ui.yml +++ b/ui.yml @@ -1,6 +1,6 @@ project: name: WiFi Clock - version: 0.1.2 + version: 0.2.0 contacts: - mailto:rvb@rvb.name - tg:rvbglas @@ -412,6 +412,9 @@ pages: title: Погода icon: "" elements: + - id: enable_weather + label: Использовать погодный сервис + type: checkbox - id: weather_url label: URL погодного сервиса type: input @@ -469,6 +472,12 @@ pages: type: button label: Сменить пароль - type: hr + - type: text + value: Конфигурация + - id: _config + type: config + label: Сохранение и восстановление настроек + - type: hr - type: text value: Синхронизация времени - id: _timeset diff --git a/web.cpp b/web.cpp index 7d017b5..13e7916 100644 --- a/web.cpp +++ b/web.cpp @@ -3,6 +3,8 @@ #include #include #include +#include "ArduinoJson.h" +#include "AsyncJson.h" bool isApEnabled = false; bool isWebStarted = false; @@ -144,7 +146,11 @@ void setupWeb() { } if(request->hasParam("name")) { const char* action = request->getParam("name")->value().c_str(); - if (strcmp(action,"restart") == 0) { + if (strcmp(action,"reset") == 0) { + millisScheduled = millis(); + actionScheduled = "reset"; + request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Сбрасываю настройки и перезагружаюсь\"}"); + } else if (strcmp(action,"restart") == 0) { if (pendingWiFi) { request->send(200,"application/json", "{\"result\":\"FAILED\",\"message\":\"Не применены настройки WiFi\", \"page\":\"wifi\"}"); } else if (pendingAuth) { @@ -181,7 +187,7 @@ void setupWeb() { unsigned long timestamp = atoi(request->getParam("timestamp")->value().c_str()); if (timestamp) { timeval tv = { timestamp, 0 }; - settimeofday(&tv, nullptr); + settimeofday(&tv, nullptr); if (isRTCEnabled) { Serial.println(F("Время установлено вручную")); RTC.adjust(DateTime(timestamp)); @@ -302,6 +308,42 @@ void setupWeb() { } }); + AsyncCallbackJsonWebHandler* configUploadHandler = new AsyncCallbackJsonWebHandler("/config/put", [](AsyncWebServerRequest *request, JsonVariant &json) { + cfg.clear(); + // first - set values + for( JsonPair kv : json.as() ) { + const char* name = kv.key().c_str(); + if (kv.value().is()) { + cfg.setValue(name, kv.value().as()); + } else if (kv.value().is()) { + cfg.setValue(name, kv.value().as()); + } else if (kv.value().is()) { + cfg.setValue(name, kv.value().as()); + } else if (kv.value().is()) { + cfg.setValue(name, kv.value().as()); + } else { + Serial.print(F("Неопознанный тип значения параметра ")); Serial.print(name); Serial.print(": "); Serial.println(kv.value().as().c_str()); + cfg.clear(); + setupConfig(); + request->send(500, "text/plain", "Unknown parameter type"); + } + } + // second - handle all changes + for( JsonPair kv : json.as() ) { + apply(kv.key().c_str()); + } + pendingWiFi = false; + pendingAuth = false; + saveConfig(); + message(F("Применены сохраненные настройки"),5); + reportMessage(F("Применены сохраненные настройки")); + millisScheduled = millis() + 10000; + actionScheduled = "restart"; + request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Настройки восстановлены из резервной копии\"}"); + }); + + server.addHandler(configUploadHandler).setAuthentication(auth_user,auth_pwd); + server.serveStatic("ui", LittleFS, "/ui.json").setAuthentication(auth_user,auth_pwd); server.serveStatic("/", LittleFS, "/web/").setDefaultFile("index.html").setAuthentication(auth_user,auth_pwd); @@ -334,7 +376,10 @@ void tickWeb() { if (actionScheduled && millis()>millisScheduled+300) { Serial.print(F("Запланированная операция ")); Serial.println(actionScheduled); // - if (strcmp(actionScheduled,"restart") == 0) { + if (strcmp(actionScheduled,"reset") == 0) { + server.end(); + reset(); + } else if (strcmp(actionScheduled,"restart") == 0) { server.end(); reboot(); } else if (strcmp(actionScheduled,"auth") == 0) {