diff --git a/html/management.html b/html/management.html index 28d40765..f30cc1b9 100644 --- a/html/management.html +++ b/html/management.html @@ -154,7 +154,7 @@ Restart Shutdown Log - Infos + Info
+ value="" pattern="^[0-9a-zA-Z][0-9a-zA-Z\-]{0,30}[0-9a-zA-Z]" required>


@@ -301,7 +301,7 @@ + data-slider-value="" value="" onchange="sendVolume(this.value)"> @@ -387,7 +387,7 @@
+ placeholder="" name="rfidIdMusic" required>

-
+
@@ -631,34 +630,34 @@
+ data-slider-value="" value="" pattern="^\d{1,2}(\.\d{1,3})?" required>

+ data-slider-value="" value="" pattern="^\d{1,2}(\.\d{1,3})?" required>

+ data-slider-value="" value="" pattern="^\d{1,2}(\.\d{1,3})?" required>

+ data-slider-value="" name="voltageCheckInterval" value="" required>

-   +  
@@ -1387,8 +1386,10 @@ socket.onopen = function () { setInterval(ping, 15000); - getTrack(); - getCoverimage(); + socket.send('{"settings":{}}'); // request settings + socket.send('{"ssids":{}}'); // request SSIDs + socket.send('{"trackinfo":{}}'); // get current track + socket.send('{"coverimg":{}}'); // get cover image }; socket.onclose = function (e) { @@ -1450,21 +1451,123 @@ } } if ("coverimg" in socketMsg) { document.getElementById('coverimg').src = "/cover?" + new Date().getTime(); - } + } if ("settings" in socketMsg) { + fillSettings(socketMsg.settings); + } }; } - const wifiListSavedSSIDs = document.getElementById("wifiListSavedSSIDs"); - async function rebuildSSIDList() { - let ssidList = await (await fetch("/savedSSIDs")).json(); - let ssidActiveObj = await (await fetch("/activeSSID")).json(); - - console.log(ssidActiveObj); - let ssidActive = ssidActiveObj.active; + async function fillSettings(settings) { + if (!settings) { + return false + } + // general + let genSettings = settings.general; + if (genSettings) { + $('#initialVolume').bootstrapSlider('setValue', genSettings.initVolume); + $('#maxVolumeSpeaker').bootstrapSlider('setValue', genSettings.maxVolumeSp); + $('#maxVolumeHeadphone').bootstrapSlider('setValue', genSettings.maxVolumeHp); + $('#inactivityTime').bootstrapSlider('setValue', genSettings.sleepInactivity); + } + // current values + let currSettings = settings.current; + if (currSettings) { + document.getElementById("rfidIdMusic").value = currSettings.rfidTagId; + volumeSlider.setValue(currSettings.volume); + } + // default (factory) settings + let defSettings = settings.defaults; + if (defSettings) { + $('#initialVolume').bootstrapSlider('setValue', defSettings.initVolume); + $('#maxVolumeSpeaker').bootstrapSlider('setValue', defSettings.maxVolumeSp); + $('#maxVolumeHeadphone').bootstrapSlider('setValue', defSettings.maxVolumeHp); + $('#inactivityTime').bootstrapSlider('setValue', defSettings.sleepInactivity); + $('#initBrightness').bootstrapSlider('setValue', defSettings.initBrightness); + $('#nightBrightness').bootstrapSlider('setValue', defSettings.nightBrightness); + $('#warningLowVoltage').bootstrapSlider('setValue', defSettings.warnLowVoltage); + $('#voltageIndicatorLow').bootstrapSlider('setValue', defSettings.indicatorLow); + $('#voltageIndicatorHigh').bootstrapSlider('setValue', defSettings.indicatorHi); + $('#voltageCheckInterval').bootstrapSlider('setValue', defSettings.voltageCheckInterval); + } + // wifi + let wifiSettings = settings.wifi; + if (wifiSettings) { + document.getElementById("hostname").value = wifiSettings.hostname; + document.getElementById("scan_wifi_on_start").checked = wifiSettings.scanOnStart; + } + // ssids + let ssidSettings = settings.ssids; + if (ssidSettings) { + rebuildSSIDList(ssidSettings); + } + // led + let ledSettings = settings.led; + if (ledSettings) { + document.getElementById('neopixelConfig').setAttribute('data-visible', true); + $('#initBrightness').bootstrapSlider('setValue', ledSettings.initBrightness); + $('#nightBrightness').bootstrapSlider('setValue', ledSettings.nightBrightness); + } + // battery + let batSettings = settings.battery; + if (batSettings) { + document.getElementById('batteryConfig').setAttribute('data-visible', true); + $('#warningLowVoltage').bootstrapSlider('setValue', batSettings.warnLowVoltage); + $('#voltageIndicatorLow').bootstrapSlider('setValue', batSettings.indicatorLow); + $('#voltageIndicatorHigh').bootstrapSlider('setValue', batSettings.indicatorHi); + $('#voltageCheckInterval').bootstrapSlider('setValue', batSettings.voltageCheckInterval); + } + // ftp + let ftpSettings = settings.ftp; + if (ftpSettings) { + document.getElementById('nav-ftp-tab').setAttribute('data-visible', true); + document.getElementById("ftpUser").value = ftpSettings.username; + document.getElementById("ftpUser").setAttribute('maxlength', ftpSettings.maxUserLength); + document.getElementById("ftpPwd").value = ftpSettings.password; + document.getElementById("ftpPwd").setAttribute('maxlength', ftpSettings.maxPwdLength); + } + // mqtt + let mqttSettings = settings.mqtt; + if (mqttSettings) { + // todo: get the bootstrap sliders to work: + document.getElementById('nav-mqtt-tab').setAttribute('data-visible', true); + document.getElementById('mqttEnable').checked = mqttSettings.enable; + document.getElementById('mqttClientId').value = mqttSettings.clientID; + document.getElementById('mqttClientId').setAttribute('maxlength', mqttSettings.maxClientIdLength); + document.getElementById('mqttServer').value = mqttSettings.server; + document.getElementById('mqttServer').setAttribute('maxlength', mqttSettings.maxServerLength); + document.getElementById('mqttUser').value = mqttSettings.username; + document.getElementById('mqttUser').setAttribute('maxlength', mqttSettings.maxUserLength); + document.getElementById('mqttPwd').value = mqttSettings.password; + document.getElementById('mqttPwd').setAttribute('maxlength', mqttSettings.maxPwdLength); + document.getElementById('mqttPort').value = mqttSettings.port; + } + // bluetooth + let btSettings = settings.bluetooth; + if (btSettings) { + document.getElementById('nav-bt-tab').setAttribute('data-visible', true); + document.getElementById('btDeviceName').value = btSettings.deviceName; + document.getElementById('btPinCode').value = btSettings.pinCode; + } + } - console.log(ssidList); - console.log(ssidActive); + async function resetSettings() { + let defaults = await (await fetch("/settings?section=defaults")).json(); + fillSettings(defaults); + } + const wifiListSavedSSIDs = document.getElementById("wifiListSavedSSIDs"); + async function rebuildSSIDList(data) { + var ssidList; + var ssidActive; + if (data) { + ssidList = data.savedSSIDs; + ssidActive = data.active; + } else { + ssidList = await (await fetch("/savedSSIDs")).json(); + let ssidActiveObj = await (await fetch("/activeSSID")).json(); + console.log(ssidActiveObj); + ssidActive = ssidActiveObj.active; + } wifiListSavedSSIDs.innerHTML = ""; for (let ssid of ssidList) { @@ -1497,27 +1600,7 @@ ssidElem.appendChild(deleteSpan); wifiListSavedSSIDs.appendChild(ssidElem); - } - } - - async function updateWifiFields() { - let hostnameElem = document.getElementById("hostname"); - let scanWifiElem = document.getElementById("scan_wifi_on_start"); - - let config = await (await fetch("/wificonfig")).json(); - - if (config.hostname && config.hostname !== "") { - hostnameElem.value = config.hostname; - hostnameElem.defaultValue = config.hostname; - } - scanWifiElem.checked = config.scanwifionstart; - } - - function onWifiTabOpened() { - console.log("wifi tab opened!") - rebuildSSIDList(); - - updateWifiFields(); + } } async function deleteSSID(ssid) { @@ -1528,20 +1611,6 @@ } - const config = {attributes: true}; - const wifiTab = document.getElementById("nav-wifi-tab"); - var wifiTabWasActive = false; - const wifiTabObserver = new MutationObserver((mutationList, observer) => { - if (!wifiTabWasActive && wifiTab.classList.contains("active")) { - wifiTabWasActive = true; - onWifiTabOpened(); - } else { - wifiTabWasActive = false; - } - }) - wifiTabObserver.observe(wifiTab, config); - - function ping() { var myObj = { "ping": { @@ -1559,41 +1628,22 @@ clearTimeout(tm); } - function getTrack() { - var myObj = { - "trackinfo": { - trackinfo: 'trackinfo' - } - }; - var myJSON = JSON.stringify(myObj); - socket.send(myJSON); - } - - function getCoverimage() { - var myObj = { - "coverimg": { - coverimg: 'coverimg' - } - }; - var myJSON = JSON.stringify(myObj); - socket.send(myJSON); - } - - function genSettings(clickedId) { lastIdclicked = clickedId; var myObj = { "general": { - iVol: document.getElementById('initialVolume').value, - mVolSpeaker: document.getElementById('maxVolumeSpeaker').value, - mVolHeadphone: document.getElementById('maxVolumeHeadphone').value, - iBright: document.getElementById('initBrightness').value, - nBright: document.getElementById('nightBrightness').value, - iTime: document.getElementById('inactivityTime').value, - vWarning: document.getElementById('warningLowVoltage').value, - vIndLow: document.getElementById('voltageIndicatorLow').value, - vIndHi: document.getElementById('voltageIndicatorHigh').value, - vInt: document.getElementById('voltageCheckInterval').value + initVolume: document.getElementById('initialVolume').value, + maxVolumeSp: document.getElementById('maxVolumeSpeaker').value, + maxVolumeHp: document.getElementById('maxVolumeHeadphone').value, + sleepInactivity: document.getElementById('inactivityTime').value}, + "led": { + initBrightness: document.getElementById('initBrightness').value, + nightBrightness: document.getElementById('nightBrightness').value}, + "battery": { + warnLowVoltage: document.getElementById('warningLowVoltage').value, + indicatorLow: document.getElementById('voltageIndicatorLow').value, + indicatorHi: document.getElementById('voltageIndicatorHigh').value, + voltageCheckInterval: document.getElementById('voltageCheckInterval').value } }; var myJSON = JSON.stringify(myObj); @@ -1604,8 +1654,8 @@ lastIdclicked = clickedId; var myObj = { "ftp": { - ftpUser: document.getElementById('ftpUser').value, - ftpPwd: document.getElementById('ftpPwd').value + username: document.getElementById('ftpUser').value, + password: document.getElementById('ftpPwd').value } }; var myJSON = JSON.stringify(myObj); @@ -1632,12 +1682,12 @@ } var myObj = { "mqtt": { - mqttEnable: val, - mqttClientId: document.getElementById('mqttClientId').value, - mqttServer: document.getElementById('mqttServer').value, - mqttUser: document.getElementById('mqttUser').value, - mqttPwd: document.getElementById('mqttPwd').value, - mqttPort: document.getElementById('mqttPort').value + enable: val, + clientID: document.getElementById('mqttClientId').value, + server: document.getElementById('mqttServer').value, + username: document.getElementById('mqttUser').value, + password: document.getElementById('mqttPwd').value, + port: document.getElementById('mqttPort').value } }; var myJSON = JSON.stringify(myObj); @@ -1692,7 +1742,7 @@ var myObj = { hostname: hostname, - scanwifionstart: scanWifi + scanOnStart: scanWifi }; await fetch("/wificonfig", { diff --git a/processHtml.py b/processHtml.py index cba9bbf1..0adc27c6 100644 --- a/processHtml.py +++ b/processHtml.py @@ -24,13 +24,12 @@ ) # pylint: disable=undefined-variable HTML_DIR = Path("html").absolute() # List of files, which will only be minifed but not compressed (f.e. html files with templates) -WWW_FILES = [ - Path("management.html"), - Path("accesspoint.html"), -] +WWW_FILES = [] # list of all files, which shall be compressed before embedding # files with ".json" ending will be minifed before compression, ".js" will not be changed! BINARY_FILES =[ + Path("management.html"), + Path("accesspoint.html"), Path("js/i18next.min.js"), Path("js/i18nextHttpBackend.min.js"), Path("js/loc_i18next.min.js"), diff --git a/src/Web.cpp b/src/Web.cpp index 9338aaee..3d8b5ccb 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -28,8 +28,6 @@ #include "Rfid.h" #include "HallEffectSensor.h" -#include "HTMLaccesspoint.h" -#include "HTMLmanagement.h" #include "HTMLbinary.h" typedef struct { @@ -73,11 +71,15 @@ static void handleGetWiFiConfig(AsyncWebServerRequest *request); static void handlePostWiFiConfig(AsyncWebServerRequest *request, JsonVariant &json); static void handleCoverImageRequest(AsyncWebServerRequest *request); static void handleWiFiScanRequest(AsyncWebServerRequest *request); +static void handleGetSettings(AsyncWebServerRequest *request); +static void handlePostSettings(AsyncWebServerRequest *request, JsonVariant &json); + static bool Web_DumpNvsToSd(const char *_namespace, const char *_destFile); static void onWebsocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); -static String templateProcessor(const String &templ); +static void settingsToJSON(JsonObject obj, String section); +static bool JSONToSettings(JsonObject obj); static void webserverStart(void); // If PSRAM is available use it allocate memory for JSON-objects @@ -205,8 +207,10 @@ void Web_Init(void) { const bool etag = false; if (etag) response = request->beginResponse(304); - else - response = request->beginResponse_P(200, "text/html", accesspoint_HTML); + else { + response = request->beginResponse_P(200, "text/html", (const uint8_t *)accesspoint_BIN, sizeof(accesspoint_BIN)); + response->addHeader("Content-Encoding", "gzip"); + } // response->addHeader("Cache-Control", "public, max-age=31536000, immutable"); // response->addHeader("ETag", gitRevShort); // use git revision as digest request->send(response); @@ -311,9 +315,11 @@ void webserverStart(void) { response = request->beginResponse(304); else { if (gFSystem.exists("/.html/index.htm")) - response = request->beginResponse(gFSystem, "/.html/index.htm", String(), false, templateProcessor); - else - response = request->beginResponse_P(200, "text/html", management_HTML, templateProcessor); + response = request->beginResponse(gFSystem, "/.html/index.htm", String(), false); + else { + response = request->beginResponse_P(200, "text/html", (const uint8_t *)management_BIN, sizeof(management_BIN)); + response->addHeader("Content-Encoding", "gzip"); + } } // response->addHeader("Cache-Control", "public, max-age=31536000, immutable"); // response->addHeader("ETag", gitRevShort); // use git revision as digest @@ -504,7 +510,10 @@ void webserverStart(void) { }; request->redirect("https://espuino.de/espuino/favicon.ico"); }); - // Init HallEffectSensor Value + // ESPuino settings + wServer.on("/settings", HTTP_GET, handleGetSettings); + wServer.addHandler(new AsyncCallbackJsonWebHandler("/settings", handlePostSettings)); + // Init HallEffectSensor Value #ifdef HALLEFFECT_SENSOR_ENABLE wServer.on("/inithalleffectsensor", HTTP_GET, [](AsyncWebServerRequest *request) { bool bres = gHallEffectSensor.saveActualFieldValue2NVS(); @@ -524,194 +533,64 @@ void webserverStart(void) { } } -// Used for substitution of some variables/templates of html-files. Is called by webserver's template-engine -String templateProcessor(const String &templ) { - if (templ == "FTP_USER") { - return gPrefsSettings.getString("ftpuser", "-1"); - } else if (templ == "FTP_PWD") { - return gPrefsSettings.getString("ftppassword", "-1"); - } else if (templ == "FTP_USER_LENGTH") { - return String(ftpUserLength - 1); - } else if (templ == "FTP_PWD_LENGTH") { - return String(ftpPasswordLength - 1); - } else if (templ == "SHOW_FTP_TAB") { // Only show FTP-tab if FTP-support was compiled - #ifdef FTP_ENABLE - return "true"; - #else - return "false"; - #endif - } else if (templ == "SHOW_BLUETOOTH_TAB") { // Only show Bluetooth-tab if Bluetooth-support was compiled - #ifdef BLUETOOTH_ENABLE - return "true"; - #else - return "false"; - #endif - } else if (templ == "INIT_LED_BRIGHTNESS") { - return String(gPrefsSettings.getUChar("iLedBrightness", 0)); - } else if (templ == "NIGHT_LED_BRIGHTNESS") { - return String(gPrefsSettings.getUChar("nLedBrightness", 0)); - } else if (templ == "MAX_INACTIVITY") { - return String(gPrefsSettings.getUInt("mInactiviyT", 0)); - } else if (templ == "INIT_VOLUME") { - return String(gPrefsSettings.getUInt("initVolume", 0)); - } else if (templ == "CURRENT_VOLUME") { - return String(AudioPlayer_GetCurrentVolume()); - } else if (templ == "MAX_VOLUME_SPEAKER") { - return String(gPrefsSettings.getUInt("maxVolumeSp", 0)); - } else if (templ == "MAX_VOLUME_HEADPHONE") { - return String(gPrefsSettings.getUInt("maxVolumeHp", 0)); -#ifdef BATTERY_MEASURE_ENABLE - #ifdef MEASURE_BATTERY_VOLTAGE - } else if (templ == "WARNING_LOW_VOLTAGE") { - return String(gPrefsSettings.getFloat("wLowVoltage", warningLowVoltage)); - } else if (templ == "VOLTAGE_INDICATOR_LOW") { - return String(gPrefsSettings.getFloat("vIndicatorLow", voltageIndicatorLow)); - } else if (templ == "VOLTAGE_INDICATOR_HIGH") { - return String(gPrefsSettings.getFloat("vIndicatorHigh", voltageIndicatorHigh)); - #endif - #ifdef MEASURE_BATTERY_OTHER // placeholder - } else if (templ == "todo") { - return "todo"; - #endif - } else if (templ == "BATTERY_CHECK_INTERVAL") { - return String(gPrefsSettings.getUInt("vCheckIntv", batteryCheckInterval)); -#else - // TODO: hide battery config -#endif - } else if (templ == "MQTT_CLIENTID") { - if (gPrefsSettings.isKey("mqttClientId")) { - return gPrefsSettings.getString("mqttClientId", "-1"); - } else { - return "-1"; - } - } else if (templ == "MQTT_SERVER") { - if (gPrefsSettings.isKey("mqttServer")) { - return gPrefsSettings.getString("mqttServer", "-1"); - } else { - return "-1"; - } - } else if (templ == "SHOW_MQTT_TAB") { // Only show MQTT-tab if MQTT-support was compiled - #ifdef MQTT_ENABLE - return "true"; - #else - return "false"; - #endif - } else if (templ == "MQTT_ENABLE") { - if (Mqtt_IsEnabled()) { - return "checked=\"checked\""; - } else { - return String(); - } - } else if (templ == "MQTT_USER") { - if (gPrefsSettings.isKey("mqttUser")) { - return gPrefsSettings.getString("mqttUser", "-1"); - } else { - return "-1"; - } - } else if (templ == "MQTT_PWD") { - if (gPrefsSettings.isKey("mqttPassword")) { - return gPrefsSettings.getString("mqttPassword", "-1"); - } else { - return "-1"; - } - } else if (templ == "MQTT_USER_LENGTH") { - return String(mqttUserLength - 1); - } else if (templ == "MQTT_PWD_LENGTH") { - return String(mqttPasswordLength - 1); - } else if (templ == "MQTT_CLIENTID_LENGTH") { - return String(mqttClientIdLength - 1); - } else if (templ == "MQTT_SERVER_LENGTH") { - return String(mqttServerLength - 1); - } else if (templ == "MQTT_PORT") { -#ifdef MQTT_ENABLE - return String(gMqttPort); -#endif - } else if (templ == "BT_DEVICE_NAME") { - if (gPrefsSettings.isKey("btDeviceName")) { - return gPrefsSettings.getString("btDeviceName", ""); - } - } else if (templ == "BT_PIN_CODE") { - if (gPrefsSettings.isKey("btPinCode")) { - return gPrefsSettings.getString("btPinCode", ""); - } - } else if (templ == "IPv4") { - return WiFi.localIP().toString(); - } else if (templ == "RFID_TAG_ID") { - return String(gCurrentRfidTagId); - } else if (templ == "HOSTNAME") { - return Wlan_GetHostname(); - } - - return String(); -} -// Takes inputs from webgui, parses JSON and saves values in NVS -// If operation was successful (NVS-write is verified) true is returned -bool processJsonRequest(char *_serialJson) { - if (!_serialJson) { +// process JSON to settings +bool JSONToSettings(JsonObject doc) { + if (!doc) { + Log_Println("JSONToSettings: doc unassigned", LOGLEVEL_DEBUG); return false; } - #ifdef BOARD_HAS_PSRAM - SpiRamJsonDocument doc(1000); - #else - StaticJsonDocument<1000> doc; - #endif - - DeserializationError error = deserializeJson(doc, _serialJson); - - if (error) { - Log_Printf(LOGLEVEL_ERROR, jsonErrorMsg, error.c_str()); - return false; + if (doc.containsKey("general")) { + // general settings + if (gPrefsSettings.putUInt("initVolume", doc["general"]["initVolume"].as()) == 0 || + gPrefsSettings.putUInt("maxVolumeSp", doc["general"]["maxVolumeSp"].as()) == 0 || + gPrefsSettings.putUInt("maxVolumeHp", doc["general"]["maxVolumeHp"].as()) == 0 || + gPrefsSettings.putUInt("mInactiviyT", doc["general"]["sleepInactivity"].as()) == 0 ) { + Log_Println("Failed to save general settings", LOGLEVEL_ERROR); + return false; + } } - - JsonObject object = doc.as(); - - if (doc.containsKey("general")) { - uint8_t iVol = doc["general"]["iVol"].as(); - uint8_t mVolSpeaker = doc["general"]["mVolSpeaker"].as(); - uint8_t mVolHeadphone = doc["general"]["mVolHeadphone"].as(); - uint8_t iBright = doc["general"]["iBright"].as(); - uint8_t nBright = doc["general"]["nBright"].as(); - uint8_t iTime = doc["general"]["iTime"].as(); - float vWarning = doc["general"]["vWarning"].as(); - float vIndLow = doc["general"]["vIndLow"].as(); - float vIndHi = doc["general"]["vIndHi"].as(); - uint8_t vInt = doc["general"]["vInt"].as(); - - gPrefsSettings.putUInt("initVolume", iVol); - gPrefsSettings.putUInt("maxVolumeSp", mVolSpeaker); - gPrefsSettings.putUInt("maxVolumeHp", mVolHeadphone); - gPrefsSettings.putUChar("iLedBrightness", iBright); - gPrefsSettings.putUChar("nLedBrightness", nBright); - gPrefsSettings.putUInt("mInactiviyT", iTime); - gPrefsSettings.putFloat("wLowVoltage", vWarning); - gPrefsSettings.putFloat("vIndicatorLow", vIndLow); - gPrefsSettings.putFloat("vIndicatorHigh", vIndHi); - gPrefsSettings.putUInt("vCheckIntv", vInt); - - // Check if settings were written successfully - if (gPrefsSettings.getUInt("initVolume", 0) != iVol || - gPrefsSettings.getUInt("maxVolumeSp", 0) != mVolSpeaker || - gPrefsSettings.getUInt("maxVolumeHp", 0) != mVolHeadphone || - gPrefsSettings.getUChar("iLedBrightness", 0) != iBright || - gPrefsSettings.getUChar("nLedBrightness", 0) != nBright || - gPrefsSettings.getUInt("mInactiviyT", 0) != iTime || - gPrefsSettings.getFloat("wLowVoltage", 999.99) != vWarning || - gPrefsSettings.getFloat("vIndicatorLow", 999.99) != vIndLow || - gPrefsSettings.getFloat("vIndicatorHigh", 999.99) != vIndHi || - gPrefsSettings.getUInt("vCheckIntv", 17777) != vInt) { + if (doc.containsKey("wifi")) { + // WiFi settings + static String hostName = doc["wifi"]["hostname"]; + if (!Wlan_ValidateHostname(hostName)) { + Log_Println("Invalid hostname", LOGLEVEL_ERROR); + return false; + } + if (((!Wlan_SetHostname(hostName)) || gPrefsSettings.putBool("ScanWiFiOnStart", doc["wifi"]["scanOnStart"].as()) == 0)) { + Log_Println("Failed to save wifi settings", LOGLEVEL_ERROR); return false; } + } + if (doc.containsKey("led")) { + // Neopixel settings + if (gPrefsSettings.putUInt("iLedBrightness", doc["led"]["initBrightness"].as()) == 0 || + gPrefsSettings.putUInt("nLedBrightness", doc["led"]["nightBrightness"].as()) == 0 ) { + Log_Println("Failed to save LED settings", LOGLEVEL_ERROR); + return false; + } + } + if (doc.containsKey("battery")) { + // Battery settings + if (gPrefsSettings.putFloat("wLowVoltage", doc["battery"]["warnLowVoltage"].as()) == 0 || + gPrefsSettings.putFloat("vIndicatorLow", doc["battery"]["indicatorLow"].as()) == 0 || + gPrefsSettings.putFloat("vIndicatorHigh", doc["battery"]["indicatorHi"].as()) == 0 || + gPrefsSettings.putUInt("vCheckIntv", doc["battery"]["voltageCheckInterval"].as()) == 0 ) { + Log_Println("Failed to save battery settings", LOGLEVEL_ERROR); + return false; + } Battery_Init(); - } else if (doc.containsKey("ftp")) { - const char *_ftpUser = doc["ftp"]["ftpUser"]; - const char *_ftpPwd = doc["ftp"]["ftpPwd"]; + } + if (doc.containsKey("ftp")) { + const char *_ftpUser = doc["ftp"]["username"]; + const char *_ftpPwd = doc["ftp"]["password"]; gPrefsSettings.putString("ftpuser", (String)_ftpUser); gPrefsSettings.putString("ftppassword", (String)_ftpPwd); - + // Check if settings were written successfully if (!(String(_ftpUser).equals(gPrefsSettings.getString("ftpuser", "-1")) || String(_ftpPwd).equals(gPrefsSettings.getString("ftppassword", "-1")))) { + Log_Println("Failed to save ftp settings", LOGLEVEL_ERROR); return false; } } else if (doc.containsKey("ftpStatus")) { @@ -719,13 +598,14 @@ bool processJsonRequest(char *_serialJson) { if (_ftpStart == 1) { // ifdef FTP_ENABLE is checked in Ftp_EnableServer() Ftp_EnableServer(); } - } else if (doc.containsKey("mqtt")) { - uint8_t _mqttEnable = doc["mqtt"]["mqttEnable"].as(); - const char *_mqttClientId = object["mqtt"]["mqttClientId"]; - const char *_mqttServer = object["mqtt"]["mqttServer"]; - const char *_mqttUser = doc["mqtt"]["mqttUser"]; - const char *_mqttPwd = doc["mqtt"]["mqttPwd"]; - uint16_t _mqttPort = doc["mqtt"]["mqttPort"].as(); + } + if (doc.containsKey("mqtt")) { + uint8_t _mqttEnable = doc["mqtt"]["enable"].as(); + const char *_mqttClientId = doc["mqtt"]["clientID"]; + const char *_mqttServer = doc["mqtt"]["server"]; + const char *_mqttUser = doc["mqtt"]["username"]; + const char *_mqttPwd = doc["mqtt"]["password"]; + uint16_t _mqttPort = doc["mqtt"]["port"].as(); gPrefsSettings.putUChar("enableMQTT", _mqttEnable); gPrefsSettings.putString("mqttClientId", (String)_mqttClientId); @@ -736,17 +616,25 @@ bool processJsonRequest(char *_serialJson) { if ((gPrefsSettings.getUChar("enableMQTT", 99) != _mqttEnable) || (!String(_mqttServer).equals(gPrefsSettings.getString("mqttServer", "-1")))) { + Log_Println("Failed to save mqtt settings", LOGLEVEL_ERROR); return false; } - } else if (doc.containsKey("bluetooth")) { + } + if (doc.containsKey("bluetooth")) { // bluetooth settings const char *_btDeviceName = doc["bluetooth"]["deviceName"]; gPrefsSettings.putString("btDeviceName", (String)_btDeviceName); const char *btPinCode = doc["bluetooth"]["pinCode"]; gPrefsSettings.putString("btPinCode", (String)btPinCode); + // Check if settings were written successfully + if (gPrefsSettings.getString("btDeviceName", "") != _btDeviceName || + gPrefsSettings.getString("btPinCode", "") != btPinCode) { + Log_Println("Failed to save bluetooth settings", LOGLEVEL_ERROR); + return false; + } } else if (doc.containsKey("rfidMod")) { - const char *_rfidIdModId = object["rfidMod"]["rfidIdMod"]; - uint8_t _modId = object["rfidMod"]["modId"]; + const char *_rfidIdModId = doc["rfidMod"]["rfidIdMod"]; + uint8_t _modId = doc["rfidMod"]["modId"]; char rfidString[12]; if (_modId <= 0) { gPrefsRfid.remove(_rfidIdModId); @@ -761,10 +649,10 @@ bool processJsonRequest(char *_serialJson) { } Web_DumpNvsToSd("rfidTags", backupFile); // Store backup-file every time when a new rfid-tag is programmed } else if (doc.containsKey("rfidAssign")) { - const char *_rfidIdAssinId = object["rfidAssign"]["rfidIdMusic"]; + const char *_rfidIdAssinId = doc["rfidAssign"]["rfidIdMusic"]; char _fileOrUrlAscii[MAX_FILEPATH_LENTGH]; - convertFilenameToAscii(object["rfidAssign"]["fileOrUrl"], _fileOrUrlAscii); - uint8_t _playMode = object["rfidAssign"]["playMode"]; + convertFilenameToAscii(doc["rfidAssign"]["fileOrUrl"], _fileOrUrlAscii); + uint8_t _playMode = doc["rfidAssign"]["playMode"]; char rfidString[275]; snprintf(rfidString, sizeof(rfidString) / sizeof(rfidString[0]), "%s%s%s0%s%u%s0", stringDelimiter, _fileOrUrlAscii, stringDelimiter, stringDelimiter, _playMode, stringDelimiter); gPrefsRfid.putString(_rfidIdAssinId, rfidString); @@ -781,10 +669,10 @@ bool processJsonRequest(char *_serialJson) { Web_SendWebsocketData(0, 20); return false; } else if (doc.containsKey("controls")) { - if (object["controls"].containsKey("set_volume")) { + if (doc["controls"].containsKey("set_volume")) { uint8_t new_vol = doc["controls"]["set_volume"].as(); AudioPlayer_VolumeToQueueSender(new_vol, true); - } if (object["controls"].containsKey("action")) { + } if (doc["controls"].containsKey("action")) { uint8_t cmd = doc["controls"]["action"].as(); Cmd_Action(cmd); } @@ -794,19 +682,189 @@ bool processJsonRequest(char *_serialJson) { Web_SendWebsocketData(0, 40); } else if (doc.containsKey("volume")) { Web_SendWebsocketData(0, 50); + } else if (doc.containsKey("settings")) { + Web_SendWebsocketData(0, 60); + } else if (doc.containsKey("ssids")) { + Web_SendWebsocketData(0, 70); } return true; } +// process settings to JSON object +static void settingsToJSON(JsonObject obj, String section) { + if ((section == "") || (section == "current")) { + // current values + JsonObject curObj = obj.createNestedObject("current"); + curObj["volume"].set(AudioPlayer_GetCurrentVolume()); + curObj["rfidTagId"] = String(gCurrentRfidTagId); + } + if ((section == "") || (section == "general")) { + // general settings + JsonObject generalObj = obj.createNestedObject("general"); + generalObj["initVolume"].set(gPrefsSettings.getUInt("initVolume", 0)); + generalObj["maxVolumeSp"].set(gPrefsSettings.getUInt("maxVolumeSp", 0)); + generalObj["maxVolumeHp"].set(gPrefsSettings.getUInt("maxVolumeHp", 0)); + generalObj["sleepInactivity"].set(gPrefsSettings.getUInt("mInactiviyT", 0)); + } + if ((section == "") || (section == "wifi")) { + // WiFi settings + JsonObject wifiObj = obj.createNestedObject("wifi"); + wifiObj["hostname"] = Wlan_GetHostname(); + wifiObj["scanOnStart"].set(gPrefsSettings.getBool("ScanWiFiOnStart", false)); + } + if (section == "ssids") { + // saved SSID's + JsonObject ssidsObj = obj.createNestedObject("ssids"); + static String ssids[10]; + + JsonArray ssidArr = ssidsObj.createNestedArray("savedSSIDs"); + size_t len = Wlan_GetSSIDs(ssids, 10); + if (len > 0) { + for (int i = 0; i < len; i++) { + ssidArr.add(ssids[i]); + } + } + // active SSID + if (Wlan_IsConnected()) { + ssidsObj["active"] = Wlan_GetCurrentSSID(); + } + } + #ifdef NEOPIXEL_ENABLE + if ((section == "") || (section == "led")) { + // LED settings + JsonObject ledObj = obj.createNestedObject("led"); + ledObj["initBrightness"].set(gPrefsSettings.getUInt("iLedBrightness", 0)); + ledObj["nightBrightness"].set(gPrefsSettings.getUInt("nLedBrightness", 0)); + } + #endif + #ifdef MEASURE_BATTERY_VOLTAGE + if ((section == "") || (section == "battery")) { + // battery settings + JsonObject batteryObj = obj.createNestedObject("battery"); + batteryObj["warnLowVoltage"].set(gPrefsSettings.getFloat("wLowVoltage", s_warningLowVoltage)); + batteryObj["indicatorLow"].set(gPrefsSettings.getFloat("vIndicatorLow", s_voltageIndicatorLow)); + batteryObj["indicatorHi"].set(gPrefsSettings.getFloat("vIndicatorHigh", s_voltageIndicatorHigh)); + batteryObj["voltageCheckInterval"].set(gPrefsSettings.getUInt("vCheckIntv", s_batteryCheckInterval)); + } + #endif + if (section == "defaults") { + // default factory settings + JsonObject defaultsObj = obj.createNestedObject("defaults"); + defaultsObj["initVolume"].set(3u); // AUDIOPLAYER_VOLUME_INIT + defaultsObj["maxVolumeSp"].set(21u); // AUDIOPLAYER_VOLUME_MAX + defaultsObj["maxVolumeHp"].set(18u); // gPrefsSettings.getUInt("maxVolumeHp", 0)); + defaultsObj["sleepInactivity"].set(10u); // System_MaxInactivityTime + #ifdef NEOPIXEL_ENABLE + defaultsObj["initBrightness"].set(16u); // LED_INITIAL_BRIGHTNESS + defaultsObj["nightBrightness"].set(2u); // LED_INITIAL_NIGHT_BRIGHTNESS + #endif + #ifdef MEASURE_BATTERY_VOLTAGE + defaultsObj["warnLowVoltage"].set(s_warningLowVoltage); + defaultsObj["indicatorLow"].set(s_voltageIndicatorLow); + defaultsObj["indicatorHi"].set(s_voltageIndicatorHigh); + defaultsObj["voltageCheckInterval"].set(s_batteryCheckInterval); + #endif + } + // FTP + #ifdef FTP_ENABLE + if ((section == "") || (section == "ftp")) { + JsonObject ftpObj = obj.createNestedObject("ftp"); + ftpObj["username"] = gPrefsSettings.getString("ftpuser", "-1"); + ftpObj["password"] = gPrefsSettings.getString("ftppassword", "-1"); + ftpObj["maxUserLength"].set(ftpUserLength - 1); + ftpObj["maxPwdLength"].set(ftpUserLength - 1); + } + #endif + // MQTT + #ifdef MQTT_ENABLE + if ((section == "") || (section == "mqtt")) { + JsonObject mqttObj = obj.createNestedObject("mqtt"); + mqttObj["enable"].set(Mqtt_IsEnabled()); + mqttObj["clientID"] = gPrefsSettings.getString("mqttClientId", "-1"); + mqttObj["server"] = gPrefsSettings.getString("mqttServer", "-1"); + mqttObj["port"].set(gPrefsSettings.getUInt("mqttPort", 0)); + mqttObj["username"] = gPrefsSettings.getString("mqttUser", "-1"); + mqttObj["password"] = gPrefsSettings.getString("mqttPassword", "-1"); + mqttObj["maxUserLength"].set(mqttUserLength - 1); + mqttObj["maxPwdLength"].set(mqttPasswordLength - 1); + mqttObj["maxClientIdLength"].set(mqttClientIdLength - 1); + mqttObj["maxServerLength"].set(mqttServerLength - 1); + } + #endif + // Bluetooth + #ifdef BLUETOOTH_ENABLE + if ((section == "") || (section == "bluetooth")) { + JsonObject btObj = obj.createNestedObject("bluetooth"); + btObj["deviceName"] = gPrefsSettings.getString("btDeviceName", ""); + btObj["pinCode"] = gPrefsSettings.getString("btPinCode", ""); + } + #endif +} + +// handle get settings +void handleGetSettings(AsyncWebServerRequest *request) { + + // param to get a single settings section + String section = ""; + if (request->hasParam("section")) { + section = request->getParam("section")->value(); + } + #ifdef BOARD_HAS_PSRAM + SpiRamJsonDocument doc(8192); + #else + StaticJsonDocument<8192> doc; + #endif + JsonObject settingsObj = doc.createNestedObject("settings"); + settingsToJSON(settingsObj, section); + String serializedJsonString; + serializeJson(settingsObj, serializedJsonString); + request->send(200, "application/json; charset=utf-8", serializedJsonString); +} + +// handle post settings +void handlePostSettings(AsyncWebServerRequest *request, JsonVariant &json) { + const JsonObject& jsonObj = json.as(); + bool succ = JSONToSettings(jsonObj); + if (succ) { + request->send(200); + } else { + request->send(500, "text/plain; charset=utf-8", "error saving settings"); + } +} + + + +// Takes inputs from webgui, parses JSON and saves values in NVS +// If operation was successful (NVS-write is verified) true is returned +bool processJsonRequest(char *_serialJson) { + if (!_serialJson) { + return false; + } + #ifdef BOARD_HAS_PSRAM + SpiRamJsonDocument doc(1000); + #else + StaticJsonDocument<1000> doc; + #endif + + DeserializationError error = deserializeJson(doc, _serialJson); + + if (error) { + Log_Printf(LOGLEVEL_ERROR, jsonErrorMsg, error.c_str()); + return false; + } + + JsonObject obj = doc.as(); + return JSONToSettings(obj); +} + + // Sends JSON-answers via websocket void Web_SendWebsocketData(uint32_t client, uint8_t code) { if (!webserverStarted) return; - char *jBuf = (char *) x_calloc(255, sizeof(char)); - - const size_t CAPACITY = JSON_OBJECT_SIZE(1) + 200; - StaticJsonDocument doc; + char *jBuf = (char *) x_calloc(1024, sizeof(char)); + StaticJsonDocument<1024> doc; JsonObject object = doc.to(); if (code == 1) { @@ -831,9 +889,16 @@ void Web_SendWebsocketData(uint32_t client, uint8_t code) { object["coverimg"] = "coverimg"; } else if (code == 50) { object["volume"] = AudioPlayer_GetCurrentVolume(); + } else if (code == 60) { + JsonObject entry = object.createNestedObject("settings"); + settingsToJSON(entry, ""); + } else if (code == 70) { + JsonObject entry = object.createNestedObject("settings"); + settingsToJSON(entry, "ssids"); }; - serializeJson(doc, jBuf, 255); + + serializeJson(doc, jBuf, 1024); if (client == 0) { ws.printfAll(jBuf); @@ -1410,7 +1475,7 @@ void handleGetWiFiConfig(AsyncWebServerRequest *request) { bool scanWifiOnStart = gPrefsSettings.getBool("ScanWiFiOnStart", false); obj["hostname"] = Wlan_GetHostname(); - obj["scanwifionstart"].set(scanWifiOnStart); + obj["scanOnStart"].set(scanWifiOnStart); response->setLength(); request->send(response); @@ -1420,41 +1485,23 @@ void handlePostWiFiConfig(AsyncWebServerRequest *request, JsonVariant &json){ const JsonObject& jsonObj = json.as(); // always perform perform a WiFi scan on startup? - static bool alwaysScan = (bool) jsonObj["scanwifionstart"]; + bool alwaysScan = jsonObj["scanOnStart"]; gPrefsSettings.putBool("ScanWiFiOnStart", alwaysScan); // hostname String strHostname = jsonObj["hostname"]; - size_t len = strHostname.length(); - const char *hostname = strHostname.c_str(); - - // validation: first char alphanumerical, then alphanumerical or '-', last char alphanumerical - // These rules are mainly for mDNS purposes, a "pretty" hostname could have far fewer restrictions - bool validated = true; - if(len < 2 || len > 32) { - validated = false; - } - - if(!isAlphaNumeric(hostname[0]) || !isAlphaNumeric(hostname[len-1])) { - validated = false; - } - - for(int i = 0; i < len; i++) { - if(!isAlphaNumeric(hostname[i]) && hostname[i] != '-') { - validated = false; - break; - } - } - - if (!validated) { + if (!Wlan_ValidateHostname(strHostname)) { + Log_Println("hostname validation failed", LOGLEVEL_ERROR); request->send(400, "text/plain; charset=utf-8", "hostname validation failed"); return; } - bool succ = Wlan_SetHostname(String(hostname)); + bool succ = Wlan_SetHostname(strHostname); if (succ) { - request->send(200, "text/plain; charset=utf-8", hostname); + Log_Println("WiFi configuration saved.", LOGLEVEL_NOTICE); + request->send(200, "text/plain; charset=utf-8", strHostname); } else { + Log_Println("error setting hostname", LOGLEVEL_ERROR); request->send(500, "text/plain; charset=utf-8", "error setting hostname"); } } diff --git a/src/Wlan.cpp b/src/Wlan.cpp index f40244aa..902eae8a 100644 --- a/src/Wlan.cpp +++ b/src/Wlan.cpp @@ -436,10 +436,37 @@ void Wlan_Cyclic(void) { } } + +bool Wlan_ValidateHostname(String newHostname) { + size_t len = newHostname.length(); + const char *hostname = newHostname.c_str(); + + // validation: first char alphanumerical, then alphanumerical or '-', last char alphanumerical + // These rules are mainly for mDNS purposes, a "pretty" hostname could have far fewer restrictions + bool validated = true; + if(len < 2 || len > 32) { + validated = false; + } + + if(!isAlphaNumeric(hostname[0]) || !isAlphaNumeric(hostname[len-1])) { + validated = false; + } + + for(int i = 0; i < len; i++) { + if(!isAlphaNumeric(hostname[i]) && hostname[i] != '-') { + validated = false; + break; + } + } + + return validated; +} + bool Wlan_SetHostname(String newHostname) { // hostname should just be applied after reboot gPrefsSettings.putString("Hostname", newHostname); - return true; + // check if hostname is written + return (gPrefsSettings.getString("Hostname", "-1") == newHostname); } bool Wlan_AddNetworkSettings(WiFiSettings settings) { diff --git a/src/Wlan.h b/src/Wlan.h index a6adc459..3f6fb287 100644 --- a/src/Wlan.h +++ b/src/Wlan.h @@ -20,6 +20,7 @@ size_t Wlan_GetSSIDs(String*, size_t); const String Wlan_GetCurrentSSID(); const String Wlan_GetHostname(); bool Wlan_DeleteNetwork(String); +bool Wlan_ValidateHostname(String); bool Wlan_SetHostname(String); bool Wlan_IsConnected(void); void Wlan_ToggleEnable(void);