From: Roman Bazalevskiy Date: Fri, 18 Nov 2022 09:49:20 +0000 (+0300) Subject: Переработан веб-интерфейс. Сделаны всплывающие уведомления. X-Git-Url: https://git.rvb.name/esp-clock.git/commitdiff_plain/c8479d5c8f2df8ff0395a0b49da752328581e5b1 Переработан веб-интерфейс. Сделаны всплывающие уведомления. --- diff --git a/Clock.h b/Clock.h index f1cb6b5..cd3620d 100644 --- a/Clock.h +++ b/Clock.h @@ -197,4 +197,5 @@ extern bool isApEnabled; void setupWeb(); void tickWeb(); void reportChange(const __FlashStringHelper* name); +void reportMessage(const __FlashStringHelper* msg); void sendWeather(); diff --git a/alarm.cpp b/alarm.cpp index e609dba..2964418 100644 --- a/alarm.cpp +++ b/alarm.cpp @@ -24,6 +24,7 @@ void checkAlarm() { int alarm_silent_ms = cfg.getIntValue(F("alarm_silent_ms")); if ( enable_alarm && (hh == alarm_hour) && (mi == alarm_minute) && (alarm_days[dw?dw-1:6]!='0')) { beep(alarm_tone, alarm_length * 1000, alarm_beep_ms, alarm_silent_ms); + reportMessage(F("Будильник сработал!")); } } diff --git a/data/web/index.html b/data/web/index.html index a2ec589..0688a49 100644 --- a/data/web/index.html +++ b/data/web/index.html @@ -11,37 +11,40 @@
- - - - - + + + + + - diff --git a/data/web/script.js b/data/web/script.js index c40f7e1..cb4fa7e 100644 --- a/data/web/script.js +++ b/data/web/script.js @@ -1,6 +1,7 @@ var pages var parameters = {} var daynames = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] +var msgT function toggleMenu(e) { active = (document.getElementById('menuLink').className.indexOf('active') !== -1) @@ -28,22 +29,22 @@ function encode(r){ } function getAnchor() { - return window.location.hash; + return window.location.hash.slice(1); } -function DrawHeader(project) { +function drawHeader(project) { var menu_header = document.getElementById('_ui_menu_header') menu_header.innerHTML = project.name document.title = project.name + '/' + project.version } -function ParseJsonQ(url, callback) { +function parseJsonQ(url, callback) { var req = new XMLHttpRequest(); req.onreadystatechange = function () { if (this.readyState != 4) return; if (this.status != 200 && this.status != 500 && this.status != 404) { - setTimeout(ParseJsonQ(url, callback),30000); + setTimeout(parseJsonQ(url, callback),30000); return; } var json = JSON.parse(this.responseText) @@ -55,7 +56,7 @@ function ParseJsonQ(url, callback) { } -function UpdateElement(id, value) { +function updateElement(id, value) { var element = document.getElementById("_ui_element_"+id) if (!element) return; var ui_class = element.dataset.ui_class; @@ -87,17 +88,17 @@ function UpdateElement(id, value) { } } -function UpdateValues(json) { +function updateValues(json) { for (var key in json) { var obj = document.getElementById("_ui_element_"+key) if (obj) { - UpdateElement(key, json[key]) + updateElement(key, json[key]) } parameters[key] = json[key] } var notification = document.getElementById('_ui_notification'); if (parameters['_changed']) { - notification.innerHTML = '' + notification.innerHTML = '' notification.removeAttribute('hidden') } else { notification.innerHTML = '' @@ -114,35 +115,39 @@ function sendUpdate(id) { case 'number': case 'range': if (input.checkValidity() && input.value != parameters[id]) { - ParseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.value), function(json) { - UpdateValues(json) + parseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.value), function(json) { + updateValues(json) }) } break case 'select': - ParseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.selectedOptions[0].value), function(json) { - UpdateValues(json) + parseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.selectedOptions[0].value), function(json) { + updateValues(json) }) break; case 'checkbox': - ParseJsonQ('/config/set?name=' + id + '&value=' + (input.checked?'true':'false'), function(json) { - UpdateValues(json) + parseJsonQ('/config/set?name=' + id + '&value=' + (input.checked?'true':'false'), function(json) { + updateValues(json) }) break; case 'week': - ParseJsonQ('/config/set?name=' + id + '&value=' + input.dataset.value, function(json) { - UpdateValues(json) + parseJsonQ('/config/set?name=' + id + '&value=' + input.dataset.value, function(json) { + updateValues(json) }) break; } } -function sendAction(name) { - ParseJsonQ('/action?name=' + name, function(json) { +function sendAction(name, params = {}) { + var url = '/action?name=' + name + for (var param in params) { + url += '&'+param+'='+encodeURIComponent(params[param]) + } + parseJsonQ(url, function(json) { if (json.result == 'FAILED') { alert(json.message) if (json.page) { - DrawPage(json.page) + drawPage(json.page) } } else { location.reload() @@ -150,7 +155,7 @@ function sendAction(name) { }) } -function ShowPwd(id) { +function showPwd(id) { var x = document.getElementById('_ui_element_' + id) if (x.type === "password") { x.type = "text"; @@ -159,20 +164,41 @@ function ShowPwd(id) { } } -function OpenSelect(id) { +function openSelect(id) { var selector = document.getElementById('_ui_elemmodal_'+id); selector.removeAttribute("hidden") } -function CloseSelect(id) { +function closeSelect(id) { var selector = document.getElementById('_ui_elemmodal_'+id); selector.hidden = true } -function SelectWiFi(id, ssid) { - CloseSelect(id); +function closeMsg() { + document.getElementById("_ui_message").hidden = true; +} + +function fadeMsg() { + var msg = document.getElementById('_ui_message'); + msg.classList.add("fadeout") + msgT = setTimeout(()=> { closeMsg() }, 5000); +} + +function openMsg(msgText) { + document.getElementById("_ui_message_text").innerText = msgText; + document.getElementById("_ui_message").classList.remove("fadeout"); + document.getElementById("_ui_message").removeAttribute('hidden'); + + if (msgT) { + window.clearTimeout(msgT); + } + msgT = setTimeout(()=> { fadeMsg(); }, 5000); +} + +function selectWiFi(id, ssid) { + closeSelect(id); var x = document.getElementById('_ui_element_' + id) - UpdateElement(x,ssid); + updateElement(x,ssid); sendUpdate(id) } @@ -188,13 +214,13 @@ function getWiFi(id) { return; } var json = JSON.parse(this.responseText) - var table = '' + var table = '
SSIDBSSIDRSSIКаналЗащита
' if (!json.length) { setTimeout(getWiFi(id),5000); } for (idx in json) { var encryption = json[idx].secure == 2? "TKIP" : json[idx].secure == 5? "WEP" : json[idx].secure == 4? "CCMP" : json[idx].secure == 7? "нет" : json[idx].secure == 8? "Автоматически" : "Не определено"; - table += '' + table += '' } table += '
SSIDBSSIDRSSIКаналЗащита
'+json[idx].ssid+''+json[idx].bssid+''+json[idx].rssi+''+json[idx].channel+''+encryption+'
'+json[idx].ssid+''+json[idx].bssid+''+json[idx].rssi+''+json[idx].channel+''+encryption+'
' @@ -203,10 +229,9 @@ function getWiFi(id) { req.open("GET", "/wifi/scan", true); req.send() - } -function ClickDay(id, day) { +function clickDay(id, day) { value = parameters[id].split('') day_value = value[day] day_value = (day_value=='0')?'1':'0' @@ -217,7 +242,16 @@ function ClickDay(id, day) { sendUpdate(id); } -function ElementHTML(element) { +function sendTime(id) { + var value = document.getElementById('_ui_element_' + id).value + if (value) { + var date = new Date(value) + var timestamp = Math.floor(date.getTime()/1000); + sendAction('time',{"timestamp":timestamp}) + } +} + +function elementHTML(element) { var value if (parameters[element.id] || !isNaN(parameters[element.id])) { value = parameters[element.id] @@ -230,13 +264,13 @@ function ElementHTML(element) { case 'hr': return '

' case 'button': - return '
' + return '
' case 'password': return '
' - + '' - + '
' + + '
' + + '
' case 'input': var pattern = "" if (element.pattern) { @@ -251,15 +285,15 @@ function ElementHTML(element) { pattern = ' pattern="' + encode(element.pattern) + '"' } return '
' - + '' - + '
' + + '
' + + '
' + '' + + '
' + + '
' case 'checkbox': return '
' return days + case 'timeset': + var now = new Date() + now.setMinutes(now.getMinutes() - now.getTimezoneOffset()) + value = now.toISOString().slice(0, -1); + return '
' + +'
' + + '
->
' case 'text': return '

' + encode(value) + '

' case 'number': @@ -301,13 +342,13 @@ function ElementHTML(element) { + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" />' case 'table': default: - return '
' 
-        + encode(element.label)+ '
' + encode(value) + '
' + return '
' + + encode(element.label)+ '' + encode(value) + '
' } } -function DrawPage(id) { +function drawPage(id) { var idx =0, i=0 for (const page of pages) { var menu_link = document.getElementById('_ui_pglink_' + page.id) @@ -324,25 +365,25 @@ function DrawPage(id) { var page_content = document.getElementById("_ui_page_content"); var content = '' for (const element of pages[idx].elements) { - content = content + ElementHTML(element) + '\n' + content = content + elementHTML(element) + '\n' } page_content.innerHTML = content window.location.hash = id } -function DrawNavigator(project, pages) { +function drawNavigator(project, pages) { var menu = document.getElementById('_ui_menu_list'); var list = '' for (const page of pages) { list = list + '' } menu.innerHTML = list } -function DrawContacts(contacts) { +function drawContacts(contacts) { if (!contacts) return; var contact_list = '

Контакты

' for (contact of contacts) { @@ -369,21 +410,21 @@ function DrawContacts(contacts) { footer.innerHTML = contact_list } -function DrawUI(ui) { - DrawHeader(ui.project) +function drawUI(ui) { + drawHeader(ui.project) pages = ui.pages - DrawNavigator(ui.project, pages) - DrawContacts(ui.project.contacts) + drawNavigator(ui.project, pages) + drawContacts(ui.project.contacts) var anchor = getAnchor() if (anchor) { - DrawPage(anchor) + drawPage(anchor) } else { - DrawPage(pages[0].id) + drawPage(pages[0].id) } } function GetUI() { - ParseJsonQ("/ui", DrawUI); + parseJsonQ("/ui", drawUI); } GetUI() @@ -398,24 +439,23 @@ function initES() { } }; - source.addEventListener('keepalive', function(e) { - UpdateValues(JSON.parse(e.data)); - }, false); - source.addEventListener('update', function(e) { - UpdateValues(JSON.parse(e.data)); + updateValues(JSON.parse(e.data)); + }, false); + source.addEventListener('message', function(e) { + openMsg(e.data); }, false); } } initES(); -function DrawConfig(cfg) { - UpdateValues(cfg) +function drawConfig(cfg) { + updateValues(cfg) } function GetCfg() { - ParseJsonQ("/config/get", DrawConfig); + parseJsonQ("/config/get", drawConfig); } GetCfg() diff --git a/data/web/style.css b/data/web/style.css index 3bb931c..fad4817 100644 --- a/data/web/style.css +++ b/data/web/style.css @@ -1,81 +1,54 @@ -/* pure-min */ - -.pure-button:focus, -a:active, -a:hover { - outline:0 -} -.pure-table, -table { - border-collapse:collapse; - border-spacing:0 -} -html { - font-family:sans-serif; - -ms-text-size-adjust:100%; +/*! +Pure v3.0.0 +Copyright 2013 Yahoo! +Licensed under the BSD License. +https://github.com/pure-css/pure/blob/master/LICENSE +*/ +/*! +normalize.css v | MIT License | https://necolas.github.io/normalize.css/ +Copyright (c) Nicolas Gallagher and Jonathan Neal +*/ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html { + line-height:1.15; -webkit-text-size-adjust:100% } body { margin:0 } -pre { - font-family:inherit; - font-weight:700; - display:contents; - white-space:pre-wrap; - margin:0 -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { +main { display:block } -audio, -canvas, -progress, -video { - display:inline-block; - vertical-align:baseline +h1 { + font-size:2em; + margin:.67em 0 } -audio:not([controls]) { - display:none; - height:0 +hr { + box-sizing:content-box; + height:0; + overflow:visible } -[hidden], -template { - display:none +pre { + font-family:monospace,monospace; + font-size:1em } a { background-color:transparent } abbr[title] { - border-bottom:1px dotted + border-bottom:none; + text-decoration:underline; + -webkit-text-decoration:underline dotted; + text-decoration:underline dotted } b, -optgroup, strong { - font-weight:700 -} -dfn { - font-style:italic + font-weight:bolder } -h1 { - font-size:2em; - margin:.67em 0 -} -mark { - background:#ff0; - color:#000 +code, +kbd, +samp { + font-family:monospace,monospace; + font-size:1em } small { font-size:80% @@ -87,117 +60,103 @@ sup { position:relative; vertical-align:baseline } -sup { - top:-.5em -} sub { bottom:-.25em } -img { - border:0 -} -svg:not(:root) { - overflow:hidden -} -figure { - margin:1em 40px -} -hr { - border:0; - height:2px; - width:80%; - background-image:-webkit-linear-gradient(left,#292929,#1f8dd6,#292929); - background-image:-moz-linear-gradient(left,#292929,#1f8dd6,#292929); - background-image:-ms-linear-gradient(left,#292929,#1f8dd6,#292929); - background-image:-o-linear-gradient(left,#292929,#1f8dd6,#292929) -} -textarea { - overflow:auto +sup { + top:-.5em } -code, -kbd, -samp { - font-family:monospace,monospace; - font-size:1em +img { + border-style:none } button, input, optgroup, select, textarea { - color:inherit; - font:inherit + font-family:inherit; + font-size:100%; + line-height:1.15; + margin:0 } -.pure-button, +button, input { - line-height:normal -} -button { overflow:visible } button, select { text-transform:none } -button, -html input[type=button], -input[type=reset], -input[type=submit] { - -webkit-appearance:button; - cursor:pointer; - letter-spacing:.05em; - background:transparent; - text-transform:uppercase; - color:#aaa; - font-size:90%; - border:2px solid #2497e3; - border-radius:6px; - box-shadow:inset 0 1px 11px 0 #1f8dd6 -} -button[disabled], -html input[disabled] { - cursor:default +[type=button], +[type=reset], +[type=submit], +button { + -webkit-appearance:button } -button::-moz-focus-inner, -input::-moz-focus-inner { - border:0; +[type=button]::-moz-focus-inner, +[type=reset]::-moz-focus-inner, +[type=submit]::-moz-focus-inner, +button::-moz-focus-inner { + border-style:none; padding:0 } -input[type=checkbox], -input[type=radio] { +[type=button]:-moz-focusring, +[type=reset]:-moz-focusring, +[type=submit]:-moz-focusring, +button:-moz-focusring { + outline:1px dotted ButtonText +} +fieldset { + padding:.35em .75em .625em +} +legend { + box-sizing:border-box; + color:inherit; + display:table; + max-width:100%; + padding:0; + white-space:normal +} +progress { + vertical-align:baseline +} +textarea { + overflow:auto +} +[type=checkbox], +[type=radio] { box-sizing:border-box; - vertical-align:middle; padding:0 } -input[type=number]::-webkit-inner-spin-button, -input[type=number]::-webkit-outer-spin-button { +[type=number]::-webkit-inner-spin-button, +[type=number]::-webkit-outer-spin-button { height:auto } -input[type=search] { +[type=search] { -webkit-appearance:textfield; - box-sizing:content-box -} -.pure-button, -.pure-form input:not([type]), -.pure-menu { - box-sizing:border-box + outline-offset:-2px } -input[type=search]::-webkit-search-cancel-button, -input[type=search]::-webkit-search-decoration { +[type=search]::-webkit-search-decoration { -webkit-appearance:none } -fieldset { - border:1px solid silver; - margin:0 2px; - padding:.35em .625em .75em +::-webkit-file-upload-button { + -webkit-appearance:button; + font:inherit } -legend, -td, -th { - padding:0 +details { + display:block } -legend { - border:0 +summary { + display:list-item +} +template { + display:none +} +[hidden] { + display:none +} +html { + font-family:sans-serif } .hidden, [hidden] { @@ -209,30 +168,14 @@ legend { display:block } .pure-g { - letter-spacing:-.31em; - text-rendering:optimizespeed; - font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif; - display:-webkit-box; - display:-webkit-flex; - display:-ms-flexbox; display:flex; - -webkit-flex-flow:row wrap; - -ms-flex-flow:row wrap; flex-flow:row wrap; - -webkit-align-content:flex-start; - -ms-flex-line-pack:start; align-content:flex-start } -@media all and (-ms-high-contrast:none),(-ms-high-contrast:active) { - table .pure-g { - display:block - } -} -.opera-only :-o-prefocus, -.pure-g { - word-spacing:-.43em +.pure-u { + display:inline-block; + vertical-align:top } -.pure-u, .pure-u-1, .pure-u-1-1, .pure-u-1-12, @@ -279,16 +222,11 @@ legend { .pure-u-7-8, .pure-u-8-24, .pure-u-9-24 { + display:inline-block; letter-spacing:normal; word-spacing:normal; - vertical-align:text-top; - text-rendering:auto; - display:inline-block; - zoom:1; - margin:20px 0 0 -} -.pure-g [class*=pure-u] { - font-family:sans-serif + vertical-align:top; + text-rendering:auto } .pure-u-1-24 { width:4.1667% @@ -394,16 +332,15 @@ legend { } .pure-button { display:inline-block; - zoom:1; + line-height:normal; white-space:nowrap; vertical-align:middle; text-align:center; cursor:pointer; -webkit-user-drag:none; -webkit-user-select:none; - -moz-user-select:none; - -ms-user-select:none; - user-select:none + user-select:none; + box-sizing:border-box } .pure-button::-moz-focus-inner { padding:0; @@ -415,32 +352,36 @@ legend { } .opera-only :-o-prefocus, .pure-button-group { - word-spacing:-.43em + word-spacing:-0.43em +} +.pure-button-group .pure-button { + letter-spacing:normal; + word-spacing:normal; + vertical-align:top; + text-rendering:auto } .pure-button { font-family:inherit; font-size:100%; padding:.5em 1em; - color:#444; color:rgba(0,0,0,.8); - border:1px solid #999; - border:transparent; - background-color:#E6E6E6; + border:none transparent; + background-color:#e6e6e6; text-decoration:none; border-radius:2px } .pure-button-hover, +.pure-button:focus, .pure-button:hover { - border:2px solid #61c6ff; - box-shadow:inset 0 1px 15px 0 #1f8dd6; - filter:alpha(opacity=90); - background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1)); background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1)) } +.pure-button:focus { + outline:0 +} .pure-button-active, .pure-button:active { box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset; - border-color:#000\9 + border-color:#000 } .pure-button-disabled, .pure-button-disabled:active, @@ -449,7 +390,6 @@ legend { .pure-button[disabled] { border:none; background-image:none; - filter:alpha(opacity=40); opacity:.4; cursor:not-allowed; box-shadow:none; @@ -462,17 +402,12 @@ legend { .pure-button-selected, a.pure-button-primary, a.pure-button-selected { - background-color :#0078e7; + background-color:#0078e7; color:#fff } .pure-button-group .pure-button { - letter-spacing:normal; - word-spacing:normal; - vertical-align:top; - text-rendering:auto; margin:0; border-radius:0; - border-right:1px solid #111; border-right:1px solid rgba(0,0,0,.2) } .pure-button-group .pure-button:first-child { @@ -484,28 +419,27 @@ a.pure-button-selected { border-bottom-right-radius:2px; border-right:none } -.pure-form input[type=password], -.pure-form input[type=email], -.pure-form input[type=url], +.pure-form input[type=color], .pure-form input[type=date], -.pure-form input[type=month], -.pure-form input[type=time], -.pure-form input[type=datetime], .pure-form input[type=datetime-local], -.pure-form input[type=week], -.pure-form input[type=tel], -.pure-form input[type=color], +.pure-form input[type=datetime], +.pure-form input[type=email], +.pure-form input[type=month], .pure-form input[type=number], +.pure-form input[type=password], .pure-form input[type=search], +.pure-form input[type=tel], .pure-form input[type=text], +.pure-form input[type=time], +.pure-form input[type=url], +.pure-form input[type=week], .pure-form select, .pure-form textarea { - background:#292828; padding:.5em .6em; display:inline-block; border:1px solid #ccc; - box-shadow:inset 0 1px 10px #1f8dd6; - border-radius:8px; + box-shadow:inset 0 1px 3px #ddd; + border-radius:4px; vertical-align:middle; box-sizing:border-box } @@ -514,61 +448,71 @@ a.pure-button-selected { display:inline-block; border:1px solid #ccc; box-shadow:inset 0 1px 3px #ddd; - border-radius:4px + border-radius:4px; + box-sizing:border-box } .pure-form input[type=color] { - padding:.1em .4em + padding:.2em .5em } -.pure-form input:not([type]):focus, -.pure-form input[type=password]:focus, -.pure-form input[type=email]:focus, -.pure-form input[type=url]:focus, +.pure-form input[type=color]:focus, .pure-form input[type=date]:focus, -.pure-form input[type=month]:focus, -.pure-form input[type=time]:focus, -.pure-form input[type=datetime]:focus, .pure-form input[type=datetime-local]:focus, -.pure-form input[type=week]:focus, -.pure-form input[type=tel]:focus, -.pure-form input[type=color]:focus, +.pure-form input[type=datetime]:focus, +.pure-form input[type=email]:focus, +.pure-form input[type=month]:focus, .pure-form input[type=number]:focus, +.pure-form input[type=password]:focus, .pure-form input[type=search]:focus, +.pure-form input[type=tel]:focus, .pure-form input[type=text]:focus, +.pure-form input[type=time]:focus, +.pure-form input[type=url]:focus, +.pure-form input[type=week]:focus, .pure-form select:focus, .pure-form textarea:focus { outline:0; - border-color:#129FEA + border-color:#129fea +} +.pure-form input:not([type]):focus { + outline:0; + border-color:#129fea } -.pure-form input[type=file]:focus, .pure-form input[type=checkbox]:focus, +.pure-form input[type=file]:focus, .pure-form input[type=radio]:focus { - outline:#129FEA auto 1px + outline:thin solid #129FEA; + outline:1px auto #129FEA } .pure-form .pure-checkbox, .pure-form .pure-radio { + margin:.5em 0; display:block } -.pure-form input:not([type])[disabled], -.pure-form input[type=password][disabled], -.pure-form input[type=email][disabled], -.pure-form input[type=url][disabled], +.pure-form input[type=color][disabled], .pure-form input[type=date][disabled], -.pure-form input[type=month][disabled], -.pure-form input[type=time][disabled], -.pure-form input[type=datetime][disabled], .pure-form input[type=datetime-local][disabled], -.pure-form input[type=week][disabled], -.pure-form input[type=tel][disabled], -.pure-form input[type=color][disabled], +.pure-form input[type=datetime][disabled], +.pure-form input[type=email][disabled], +.pure-form input[type=month][disabled], .pure-form input[type=number][disabled], +.pure-form input[type=password][disabled], .pure-form input[type=search][disabled], +.pure-form input[type=tel][disabled], .pure-form input[type=text][disabled], +.pure-form input[type=time][disabled], +.pure-form input[type=url][disabled], +.pure-form input[type=week][disabled], .pure-form select[disabled], .pure-form textarea[disabled] { cursor:not-allowed; background-color:#eaeded; color:#cad2d3 } +.pure-form input:not([type])[disabled] { + cursor:not-allowed; + background-color:#eaeded; + color:#cad2d3 +} .pure-form input[readonly], .pure-form select[readonly], .pure-form textarea[readonly] { @@ -582,15 +526,15 @@ a.pure-button-selected { color:#b94a48; border-color:#e9322d } -.pure-form input[type=file]:focus:invalid:focus, .pure-form input[type=checkbox]:focus:invalid:focus, +.pure-form input[type=file]:focus:invalid:focus, .pure-form input[type=radio]:focus:invalid:focus { outline-color:#e9322d } .pure-form select { height:2.25em; border:1px solid #ccc; - background:#292929 + background-color:#fff } .pure-form select[multiple] { height:auto @@ -611,29 +555,31 @@ a.pure-button-selected { color:#333; border-bottom:1px solid #e5e5e5 } -.pure-form-stacked input:not([type]), -.pure-form-stacked input[type=password], -.pure-form-stacked input[type=email], -.pure-form-stacked input[type=url], +.pure-form-stacked input[type=color], .pure-form-stacked input[type=date], -.pure-form-stacked input[type=month], -.pure-form-stacked input[type=time], -.pure-form-stacked input[type=datetime], .pure-form-stacked input[type=datetime-local], -.pure-form-stacked input[type=week], -.pure-form-stacked input[type=tel], -.pure-form-stacked input[type=color], +.pure-form-stacked input[type=datetime], +.pure-form-stacked input[type=email], .pure-form-stacked input[type=file], +.pure-form-stacked input[type=month], .pure-form-stacked input[type=number], +.pure-form-stacked input[type=password], .pure-form-stacked input[type=search], +.pure-form-stacked input[type=tel], .pure-form-stacked input[type=text], +.pure-form-stacked input[type=time], +.pure-form-stacked input[type=url], +.pure-form-stacked input[type=week], .pure-form-stacked label, .pure-form-stacked select, .pure-form-stacked textarea { display:block; - margin:0 0 5px + margin:.25em 0 +} +.pure-form-stacked input:not([type]) { + display:block; + margin:.25em 0 } -.pure-form-aligned .pure-help-inline, .pure-form-aligned input, .pure-form-aligned select, .pure-form-aligned textarea, @@ -717,7 +663,6 @@ a.pure-button-selected { .pure-form .pure-input-1-4 { width:25% } -.pure-form .pure-help-inline, .pure-form-message-inline { display:inline-block; padding-left:.3em; @@ -730,44 +675,44 @@ a.pure-button-selected { color:#666; font-size:.875em } -@media only screen and (max-width : 480px) { +@media only screen and (max-width :480px) { .pure-form button[type=submit] { margin:.7em 0 0 } .pure-form input:not([type]), - .pure-form input[type=password], - .pure-form input[type=email], - .pure-form input[type=url], + .pure-form input[type=color], .pure-form input[type=date], - .pure-form input[type=month], - .pure-form input[type=time], - .pure-form input[type=datetime], .pure-form input[type=datetime-local], - .pure-form input[type=week], - .pure-form input[type=tel], - .pure-form input[type=color], + .pure-form input[type=datetime], + .pure-form input[type=email], + .pure-form input[type=month], .pure-form input[type=number], + .pure-form input[type=password], .pure-form input[type=search], + .pure-form input[type=tel], .pure-form input[type=text], + .pure-form input[type=time], + .pure-form input[type=url], + .pure-form input[type=week], .pure-form label { margin-bottom:.3em; display:block } .pure-group input:not([type]), - .pure-group input[type=password], - .pure-group input[type=email], - .pure-group input[type=url], + .pure-group input[type=color], .pure-group input[type=date], - .pure-group input[type=month], - .pure-group input[type=time], - .pure-group input[type=datetime], .pure-group input[type=datetime-local], - .pure-group input[type=week], - .pure-group input[type=tel], - .pure-group input[type=color], + .pure-group input[type=datetime], + .pure-group input[type=email], + .pure-group input[type=month], .pure-group input[type=number], + .pure-group input[type=password], .pure-group input[type=search], - .pure-group input[type=text] { + .pure-group input[type=tel], + .pure-group input[type=text], + .pure-group input[type=time], + .pure-group input[type=url], + .pure-group input[type=week] { margin-bottom:0 } .pure-form-aligned .pure-control-group label { @@ -777,9 +722,8 @@ a.pure-button-selected { width:100% } .pure-form-aligned .pure-controls { - margin:1.5em 0 0 + margin:1.5em 0 0 0 } - .pure-form .pure-help-inline, .pure-form-message, .pure-form-message-inline { display:block; @@ -787,6 +731,9 @@ a.pure-button-selected { padding:.2em 0 .8em } } +.pure-menu { + box-sizing:border-box +} .pure-menu-fixed { position:fixed; left:0; @@ -824,7 +771,6 @@ a.pure-button-selected { .pure-menu-horizontal .pure-menu-item, .pure-menu-horizontal .pure-menu-separator { display:inline-block; - zoom:1; vertical-align:middle } .pure-menu-item .pure-menu-item { @@ -871,13 +817,8 @@ a.pure-button-selected { white-space:nowrap; overflow-y:hidden; overflow-x:auto; - -ms-overflow-style:none; - -webkit-overflow-scrolling:touch; padding:.5em 0 } -.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar { - display:none -} .pure-menu-horizontal .pure-menu-children .pure-menu-separator, .pure-menu-separator { background-color:#ccc; @@ -903,7 +844,6 @@ a.pure-button-selected { .pure-menu-children { background-color:#fff } -.pure-menu-disabled, .pure-menu-heading, .pure-menu-link { padding:.5em 1em @@ -912,18 +852,21 @@ a.pure-button-selected { opacity:.5 } .pure-menu-disabled .pure-menu-link:hover { - background-color:transparent + background-color:transparent; + cursor:default } .pure-menu-active>.pure-menu-link, .pure-menu-link:focus, .pure-menu-link:hover { background-color:#eee } -.pure-menu-selected .pure-menu-link, -.pure-menu-selected .pure-menu-link:visited { +.pure-menu-selected>.pure-menu-link, +.pure-menu-selected>.pure-menu-link:visited { color:#000 } .pure-table { + border-collapse:collapse; + border-spacing:0; empty-cells:show; border:1px solid #cbcbcb } @@ -942,10 +885,6 @@ a.pure-button-selected { overflow:visible; padding:.5em 1em } -.pure-table td:first-child, -.pure-table th:first-child { - border-left-width:0 -} .pure-table thead { background-color:#e0e0e0; color:#000; @@ -955,7 +894,9 @@ a.pure-button-selected { .pure-table td { background-color:transparent } -.pure-table-odd td, +.pure-table-odd td { + background-color:#f2f2f2 +} .pure-table-striped tr:nth-child(2n-1) td { background-color:#f2f2f2 } @@ -967,21 +908,26 @@ a.pure-button-selected { } .pure-table-horizontal td, .pure-table-horizontal th { - border-width:0 0 1px; + border-width:0 0 1px 0; border-bottom:1px solid #cbcbcb } .pure-table-horizontal tbody>tr:last-child>td { border-bottom-width:0 } -:root { - --slide:250px -} /* custom styles */ +:root { + --slide: 25em; + --blue: #129fea; + --darkgray: #242424; + --lightgray: #cad2d3; + --darkergray: #191919; +} + body { color:#aaa; - background:#242424; + background: var(--darkgray); } .pure-img-responsive { @@ -1013,19 +959,20 @@ body { .content { margin:0 auto; padding:0 2em; - max-width:800px; - margin-bottom:50px; + max-width: 100em; + margin-bottom: 5em; line-height:1.6em } .header { margin:0; - color:#333; + color: var(--darkgray); text-align:center } .header h1 { - color:dodgerblue; + color: var(--blue); margin:.4em 0; - font-size:2.4em; + padding-top: 0.5em; + font-size: 2em; font-weight: bold; font-variant-caps: small-caps } @@ -1036,24 +983,25 @@ body { margin-top:0 } .content-subhead { - margin:50px 0 20px 0; + margin: 5em 0 2em 0; font-weight:300; - color:#888 + color: var(--lightgray) } #menu { margin-left:calc(var(--slide)* -1); width:var(--slide); + height: 100vh; position:fixed; top:0; left:0; bottom:0; z-index:1000; - background:#181818; + background: var(--darkergray); overflow-y:auto; -webkit-overflow-scrolling:touch } #menu a { - color:#888; + color: var(--lightgray); border:none; padding:.6em 0.3em .6em 0.6em } @@ -1064,24 +1012,24 @@ body { } #menu .pure-menu ul, #menu .pure-menu .menu-item-divided { - border-top:1px solid #333 + border-top: 0.1em solid var(--darkgray) } #menu .pure-menu li a:hover, #menu .pure-menu li a:focus { - background:#333; - color:#aaa + background: var(--darkgray); + color: var(--lightgray); } #menu .pure-menu-selected, #menu .pure-menu-heading { - background:dodgerblue + background: var(--blue); } #menu .pure-menu-selected a { - color:#fff + color: white } #menu .pure-menu-heading { letter-spacing:.15em; text-transform:uppercase; - color:#fff; + color: white; margin:0; text-align:center; padding:.6em 0.3em .6em 0.3em @@ -1091,9 +1039,9 @@ body { display:block; top:0; left:0; - background:#000; + background: black; background:rgba(0,0,0,0.42); - font-size:10px; + font-size: 1em; z-index:10; width:2em; height:auto; @@ -1101,7 +1049,7 @@ body { } .menu-link:hover, .menu-link:focus { - background:#000 + background: black; } .menu-link span { position:relative; @@ -1110,7 +1058,7 @@ body { .menu-link span, .menu-link span:before, .menu-link span:after { - background-color:dodgerblue; + background-color: var(--blue); width:100%; height:.2em } @@ -1145,101 +1093,112 @@ body { left:var(--slide) } } -@media(max-width:48em) { - #layout.active { - position:relative; - left:var(--slide) - } -} + +/* Sliders */ + input[type=range] { -webkit-appearance:none; margin:0 0 0 0; width:100%; - background:#292929 + background: transparent; } + input[type=range]:focus { outline:none } + input[type=range]::-webkit-slider-runnable-track { width:100%; - height:4px; + height: 0.4em; cursor:pointer; animate:0.2s; - box-shadow:0 0 0px #000000; - background:dodgerblue; - border-radius:3px; - border:0 solid #000000 + box-shadow: none + background: var(--blue); + border-radius: 0.3em; + border: none } + input[type=range]::-webkit-slider-thumb { - box-shadow:0 0 0px #000000; - border:1px solid rgb(148,148,148); - height:20px; - width:20px; - border-radius:40px; - background:#F0F0F0; + box-shadow: none; + border: 0.1em solid var(--lightgray) + height: 2em; + width: 2em; + border-radius: 2em; + background: var(--lightgray) cursor:pointer; -webkit-appearance:none; - margin-top:-8.5px + margin-top: 0.85em } + input[type=range]:focus::-webkit-slider-runnable-track { - background:dodgerblue + background: var(--blue) } + input[type=range]::-moz-range-track { - width:100%; - height:4px; - cursor:pointer; + width: 100%; + height: 0.4em; + cursor: pointer; animate:0.2s; - box-shadow:0 0 0px #000000; - background:dodgerblue; - border-radius:3px; - border:0 solid #000000 + box-shadow: none; + background: var(--blue); + border-radius: 0.3em; + border: none } + input[type=range]::-moz-range-thumb { - box-shadow:0 0 0px #000000; - border:1px solid dodgerblue; - height:20px; - width:20px; - border-radius:30px; - background:lightskyblue; + box-shadow: none; + border: 0.1em solid var(--blue); + height: 2em; + width: 2em; + border-radius: 2em; + background: var(--lightgray); cursor:pointer } + input[type=range]::-ms-track { width:100%; - height:4px; + height: 0.4em; cursor:pointer; animate:0.2s; background:transparent; border-color:transparent; color:transparent } + input[type=range]::-ms-fill-lower { - background:dodgerblue; - border:0 solid #000000; - border-radius:6px; - box-shadow:0 0 0px #000000 + background: var(--lightgray); + border: none; + border-radius: 0.6em; + box-shadow: none } + input[type=range]::-ms-fill-upper { - background:dodgerblue; - border:0 solid #000000; - border-radius:6px; - box-shadow:0 0 0px #000000 + background: var(--lightgray); + border: none; + border-radius: 0.6em; + box-shadow: none } + input[type=range]::-ms-thumb { - margin-top:1px; - box-shadow:0 0 0px #000000; - border:1px solid dodgerblue; - height:20px; - width:20px; - border-radius:40px; - background:lightskyblue; + margin-top:0.1em; + box-shadow: nonel + border: 0.1em solid var(--blue); + height: 2em; + width: 2em; + border-radius: 2em; + background: var(--lightgray); cursor:pointer } + input[type=range]:focus::-ms-fill-lower { - background:dodgerblue + background: var(--lightgray); } + input[type=range]:focus::-ms-fill-upper { - background:dodgerblue + background: var(--lightgray); } + + .hide { display:none } @@ -1257,12 +1216,69 @@ footer a { text-decoration: none } -/* notification button/message */ +/* notification button */ .notification { + position: fixed; + right: 0.5em; + top: 0.5em; + background-color: var(--darkgray); + z-index:200; + word-wrap: anywhere; float: right; } +/* notification message */ + +.message { + position: fixed; + right: 1em; + bottom: 0.5em; + z-index:200; + float: right; + color:#fff; +} + +.message-text { + background-color: var(--darkgray); + border:0.2em solid var(--blue); + box-shadow:inset 0 0.1em 1.1em 0 var(--blue); + word-wrap: anywhere; + font-weight: bolder; + font-size: 120%; + padding: 1em; + border-radius: 0.5em; + position: relative; +} + +.close-button { + content: 'X'; + position: absolute; + right: -1em; + top: -1em; + width: 1.2em; + padding: 0.1em 0.1em 0.1em 0.1em; + text-decoration: none; + text-shadow: none; + text-align: center; + font-weight: bold; + background: var(--darkgray); + color: white; + border: 0.2em solid var(--blue); + border-radius: 1em; + box-shadow:inset 0 0.1em 1.1em 0 var(--blue); +} + +.fadeout { + animation: fadeOut 5s ease; + opacity:0; +} + +@keyframes fadeOut { + 0% {opacity:1;} + 100% {opacity:0;} +} + /* modal dialog */ .modal { @@ -1280,15 +1296,15 @@ footer a { cursor:pointer; letter-spacing:.05em; text-transform:uppercase; - color:#aaa; + color:var(--lightgray); font-size:90%; - border:2px solid dodgerblue; + border:0.2em solid var(--blue); border-radius:6px; - box-shadow:inset 0 1px 11px 0 steelblue - padding: 1rem; + box-shadow:inset 0 0.1em 1.1em 0 var(--blue); + padding: 1em; width: max-content; min-width: 30%; - background-color: #333; + background-color: var(--darkgray); margin: 10% auto; padding: 1rem } @@ -1296,23 +1312,33 @@ footer a { font-weight: bold; font-size: 120%; text-align: center; - border-bottom: lightgray solid 1px; + border-bottom: var(--blue) solid 0.1em; } /* overlapping hint button */ +.hinted { + position: relative; +} + .hint { cursor: pointer; width: 1rem; background: transparent; - color: #aaa; - font-size: 90%; + color: var(--darkgray); + padding: .5em .6em; border: none; box-shadow: none; - display: inline-block; - vertical-align: text-bottom; - margin-left: -2rem; - z-index:1; + position: absolute; + top: 0em; + right: 0em; + line-height: 1.15em; + font-size: 100%; +} + +/* Spacing */ +fieldset .pure-u-1 { + margin-top: 1.5em } /* checkbox as a switch */ @@ -1323,7 +1349,7 @@ input[type=checkbox].switch { label.switch.socket { position: relative; width: auto; - text-indent: 2.5em; + padding-left: 4em; } span.switch.slider { display: inline-block; @@ -1331,56 +1357,90 @@ span.switch.slider { label.switch.socket span.switch.slider:before { content: ""; color: gray; - width: 4em; - height: 2em; - border-radius: 5em; + width: 3em; + height: 1.4em; + border-radius: 1em; position: absolute; left: 0; - border:2px solid dodgerblue; - box-shadow:inset 0 1px 11px 0 steelblue + top: 0; + border:0.2em solid var(--blue); + box-shadow:inset 0 0.1em 1.1em 0 var(--blue) } label.switch span.switch.slider:after { content: ""; - width: 1.5rem; - height: 1.5rem; - background: dodgerblue; - border-radius: 5.5ex; + width: 1rem; + height: 1rem; + background: var(--blue); + border-radius: 1em; position: absolute; left: 0.4rem; top: 0.4rem; transition: 0.2s; } label.switch.socket input[type=checkbox]:checked + span.slider:before { - color: White; - background: darkslategray; - content: ""; - background: D; + background: var(--lightgray); } + label.switch.socket input[type=checkbox]:checked + span.slider:after { background: white; - left: 2.4em; + left: 1.9em; +} + +/* Time setter */ + +.timesetter { + display: inline; +} + +.inline-input.inline-input.inline-input { + display: inline-block; + width: calc(100% - 5em) +} + +.send-button { + display:inline-block; + font-family:inherit; + font-size:100%; + line-height: 1.15em; + padding: .5em 1em; + color:rgba(0,0,0,.8); + border:none transparent; + background-color:#e6e6e6; + text-decoration:none; + border-radius:2px; + vertical-align: middle; + margin-left: 1em; + width: 2em; + text-align: center; } /* weekdays */ .week { width: 100%; + border-collapse: collapse; + border-spacing: 0; +} +.week td { + padding: 0 } .weekday, .weekday-selected { font-size: min(4vw, 100%rem); text-align: center; - margin: 0.5vw; - padding: 0.4em; + margin: 0.2vw; + padding: 0.2em; border-radius: 5em; - border:2px solid dodgerblue; - box-shadow:inset 0 1px 11px 0 steelblue; + border:0.2em solid var(--blue); + box-shadow:inset 0 0.1em 1.1em 0 var(--blue); color: gray } .weekday-selected { - background: darkslategray; - color: white + background: var(--lightgray); + color: black } +/* Hide arrows */ + input[type=number] { appearance: textfield; } @@ -1391,6 +1451,23 @@ input[type=number] { min-height: 100vh; } +/* Text table */ + +.texttable { + border-collapse: collapse; + border-spacing: 0; + width: 100%; +} + +.texttable td { + padding: 0 +} + +.value-name.value-name { + width: 50%; + padding-right: 1em; +} + /* fonts */ @font-face { diff --git a/hardware.cpp b/hardware.cpp index c8dbaa2..0153632 100644 --- a/hardware.cpp +++ b/hardware.cpp @@ -30,9 +30,9 @@ void beep(int tone, int length, int beep, int silent) { beepToneRequested = tone; beepLengthRequested = length; if (!silent && beep>length) { - beepMs = length; - } else { - beepMs = beep; + beepMs = length; + } else { + beepMs = beep; } silentMs = silent; } @@ -69,6 +69,7 @@ void buttonHandler(Button2& btn) { beep(800,800,200,100); click_counter++; if (click_counter>2) { + reportMessage(F("Сбрасываю настройки")); tone(1200,1000); Serial.println(F("Три тройных нажатия - сбрасываю конфигурацию")); reset(); @@ -85,7 +86,7 @@ void buttonLongClickHandler(Button2& btn) { return; } if (millis() - first_click_millis < 6000) { - if (isApEnabled) { + if (isApEnabled) { setupNet(false); } else { setupNet(true); @@ -98,10 +99,12 @@ void buttonLongClickHandler(Button2& btn) { Serial.println(F("Alarm = ")); Serial.println(enable_alarm); if (enable_alarm) { message(F("Будильник включен")); - reportChange(F("enable_alarm")); + reportChange(F("enable_alarm")); + reportMessage(F("Будильник включен")); } else { message(F("Будильник выключен")); reportChange(F("enable_alarm")); + reportMessage(F("Будильник выключен")); } } @@ -119,7 +122,7 @@ void setupHardware() { RTC.begin(); time_t rtc = RTC.now().unixtime(); timeval tv = { rtc, 0 }; - settimeofday(&tv, nullptr); + settimeofday(&tv, nullptr); isRTCEnabled = true; Serial.println(F("Время установлено по встроенным часам")); } @@ -140,7 +143,7 @@ void setupHardware() { isBuzzerEnabled = true; buzzer_pin = cfg.getIntValue(F("buzzer_pin")); pinMode(buzzer_pin,OUTPUT); - buzzer_passive = cfg.getBoolValue(F("buzzer_passive")); + buzzer_passive = cfg.getBoolValue(F("buzzer_passive")); } else { isBuzzerEnabled = true; } @@ -152,13 +155,13 @@ void doBeep(int note, int duration) { void tickHardware() { if (isButtonEnabled) { - btn.loop(); + btn.loop(); } if (isBuzzerEnabled) { static unsigned long stopBeepMillis = 0; if (stopBeepMillis && millis() >= stopBeepMillis) { beepStateRequested = false; - stopBeepMillis = 0; + stopBeepMillis = 0; } if (beepStateRequested != beepState && beepStateRequested) { stopBeepMillis = millis() + beepLengthRequested; diff --git a/time.cpp b/time.cpp index e6f81c9..4f36165 100644 --- a/time.cpp +++ b/time.cpp @@ -25,6 +25,7 @@ void timeIsSet(bool ntp) { if (ntp) { Serial.println(F("Время синхронизировано")); message(F("Время синхронизировано")); + reportMessage(F("Время синхронизировано")); if (isRTCEnabled) { RTC.adjust(DateTime(now)); } diff --git a/ui.yml b/ui.yml index fecd2bc..0bfef12 100644 --- a/ui.yml +++ b/ui.yml @@ -471,6 +471,9 @@ pages: - type: hr - type: text value: Синхронизация времени + - id: _timeset + label: Установить время вручную + type: timeset - id: ntp_server type: input label: NTP-сервер diff --git a/weather.cpp b/weather.cpp index 999f35c..dc11706 100644 --- a/weather.cpp +++ b/weather.cpp @@ -89,6 +89,7 @@ void requestCB(void* optParm, AsyncHTTPRequest* request, int readyState) { Serial.print(F("Ошибка разбора ответа: ")); Serial.println(error.c_str()); Serial.println(weather_json); + reportMessage(F("Ошибка обновления погоды")); return; } weather.clear(); @@ -97,6 +98,7 @@ void requestCB(void* optParm, AsyncHTTPRequest* request, int readyState) { delete current_weather; processTemplates(weatherData,weather_template,weather,255); sendWeather(); + reportMessage(F("Погода обновлена")); scroll(weatherData, !isNight()); } } diff --git a/web.cpp b/web.cpp index 7b7f1bc..7d017b5 100644 --- a/web.cpp +++ b/web.cpp @@ -40,18 +40,24 @@ void reportChange(const __FlashStringHelper* name) { } } +void reportMessage(const __FlashStringHelper* msg) { + char buf[256]; + strcpy_P(buf, (PGM_P)msg); + events.send(buf,"message",millis()); +} + void sendInitial(AsyncEventSourceClient *client) { String mac = WiFi.macAddress(); char buf[256]; sprintf(buf,"{\"_mac\":\"%s\",\"_weather\":\"%s\"}", mac.c_str(), weatherData); - client->send(buf,"keepalive",millis()); + client->send(buf,"update",millis()); mac = String(); } void sendWeather() { char buf[256]; sprintf(buf,"{\"_weather\":\"%s\"}",weatherData); - events.send(buf,"keepalive",millis()); + events.send(buf,"update",millis()); } void sendKeepalive() { @@ -86,7 +92,7 @@ void sendKeepalive() { } else { 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"); } - events.send(buf,"keepalive",millis()); + events.send(buf,"update",millis()); } void apply(const char* name) { @@ -170,7 +176,26 @@ void setupWeb() { millisScheduled = millis(); actionScheduled = "save"; } + } else if (strcmp(action,"time") == 0) { + if(request->hasParam("timestamp")) { + unsigned long timestamp = atoi(request->getParam("timestamp")->value().c_str()); + if (timestamp) { + timeval tv = { timestamp, 0 }; + settimeofday(&tv, nullptr); + if (isRTCEnabled) { + Serial.println(F("Время установлено вручную")); + RTC.adjust(DateTime(timestamp)); + } + } + request->send(200,"application/json", "{\"result\":\"OK\",\"message\":\"Устанавливаю время\"}"); + } else { + request->send(500, "text/plain", "{\"result\":\"FAILED\",\"message\":\"Not all parameters set\"}"); + } + } else { + request->send(500, "text/plain", "{\"result\":\"FAILED\",\"message\":\"Unsupported action\"}"); } + } else { + request->send(500, "text/plain", "{\"result\":\"FAILED\",\"message\":\"Not all parameters set\"}"); } }); @@ -348,6 +373,7 @@ void tickWeb() { if (!pendingWiFi && !pendingAuth && cfg.getTimestamp() && cfg.getTimestamp() < now - CFG_AUTOSAVE) { saveConfig(); + reportMessage(F("Настройки сохранены")); Serial.println(F("Настройки сохранены")); } }