From d94d3d4bc5c08cf1363ec9a4892940a747dc0511 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Tue, 11 May 2021 01:11:16 +0200 Subject: [PATCH] Added experimental `/json/cfg` endpoint for changing settings from JSON --- CHANGELOG.md | 5 ++ wled00/cfg.cpp | 173 ++++++++++++++++++++++------------------- wled00/fcn_declare.h | 3 +- wled00/json.cpp | 17 ++-- wled00/wled.cpp | 2 +- wled00/wled.h | 2 +- wled00/wled_server.cpp | 21 +++-- 7 files changed, 130 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16245868..8f62c5d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ### Builds after release 0.12.0 +#### Build 2105110 + +- Added Usermod settings page and configurable usermods (PR #1951) +- Added experimental `/json/cfg` endpoint for changing settings from JSON (see #1944, not part of official API) + #### Build 2105070 - Fixed not turning on after pressing "Off" on IR remote twice (#1950) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index fcfecaae..585cd9f1 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -12,24 +12,7 @@ void getStringFromJson(char* dest, const char* src, size_t len) { if (src != nullptr) strlcpy(dest, src, len); } -void deserializeConfig() { - bool fromeep = false; - bool success = deserializeConfigSec(); - if (!success) { //if file does not exist, try reading from EEPROM - deEEPSettings(); - fromeep = true; - } - - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - - DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); - - success = readObjectFromFile("/cfg.json", nullptr, &doc); - if (!success) { //if file does not exist, try reading from EEPROM - if (!fromeep) deEEPSettings(); - return; - } - +bool deserializeConfig(JsonObject doc, bool fromFS) { //int rev_major = doc["rev"][0]; // 1 //int rev_minor = doc["rev"][1]; // 0 @@ -98,52 +81,56 @@ void deserializeConfig() { CJSON(strip.rgbwMode, hw_led[F("rgbwm")]); JsonArray ins = hw_led["ins"]; - uint8_t s = 0; //bus iterator - strip.isRgbw = false; - busses.removeAll(); - uint32_t mem = 0; - for (JsonObject elm : ins) { - if (s >= WLED_MAX_BUSSES) break; - uint8_t pins[5] = {255, 255, 255, 255, 255}; - JsonArray pinArr = elm[F("pin")]; - if (pinArr.size() == 0) continue; - pins[0] = pinArr[0]; - uint8_t i = 0; - for (int p : pinArr) { - pins[i] = p; - i++; - if (i>4) break; - } + if (fromFS || !ins.isNull()) { + uint8_t s = 0; //bus iterator + strip.isRgbw = false; + busses.removeAll(); + uint32_t mem = 0; + for (JsonObject elm : ins) { + if (s >= WLED_MAX_BUSSES) break; + uint8_t pins[5] = {255, 255, 255, 255, 255}; + JsonArray pinArr = elm[F("pin")]; + if (pinArr.size() == 0) continue; + pins[0] = pinArr[0]; + uint8_t i = 0; + for (int p : pinArr) { + pins[i] = p; + i++; + if (i>4) break; + } - uint16_t length = elm[F("len")]; - if (length==0) continue; - uint8_t colorOrder = (int)elm[F("order")]; - //only use skip from the first strip (this shouldn't have been in ins obj. but remains here for compatibility) - if (s==0) skipFirstLed = elm[F("skip")]; - uint16_t start = elm[F("start")] | 0; - if (start >= ledCount) continue; - //limit length of strip if it would exceed total configured LEDs - if (start + length > ledCount) length = ledCount - start; - uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; - bool reversed = elm["rev"]; - //RGBW mode is enabled if at least one of the strips is RGBW - strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(ledType)); - s++; - BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed); - mem += busses.memUsage(bc); - if (mem <= MAX_LED_MEMORY) busses.add(bc); + uint16_t length = elm[F("len")]; + if (length==0) continue; + uint8_t colorOrder = (int)elm[F("order")]; + //only use skip from the first strip (this shouldn't have been in ins obj. but remains here for compatibility) + if (s==0) skipFirstLed = elm[F("skip")]; + uint16_t start = elm[F("start")] | 0; + if (start >= ledCount) continue; + //limit length of strip if it would exceed total configured LEDs + if (start + length > ledCount) length = ledCount - start; + uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; + bool reversed = elm["rev"]; + //RGBW mode is enabled if at least one of the strips is RGBW + strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(ledType)); + s++; + BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed); + mem += busses.memUsage(bc); + if (mem <= MAX_LED_MEMORY) busses.add(bc); + } + strip.finalizeInit(ledCount, skipFirstLed); } - strip.finalizeInit(ledCount, skipFirstLed); if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus JsonObject hw_btn_ins_0 = hw[F("btn")][F("ins")][0]; CJSON(buttonType, hw_btn_ins_0["type"]); - int hw_btn_pin = hw_btn_ins_0[F("pin")][0]; - if (pinManager.allocatePin(hw_btn_pin,false)) { - btnPin = hw_btn_pin; - pinMode(btnPin, INPUT_PULLUP); - } else { - btnPin = -1; + int hw_btn_pin = hw_btn_ins_0[F("pin")][0] | -2; //-2 = not present in doc, keep current. -1 = disable + if (hw_btn_pin > -2) { + if (pinManager.allocatePin(hw_btn_pin,false)) { + btnPin = hw_btn_pin; + pinMode(btnPin, INPUT_PULLUP); + } else { + btnPin = -1; + } } JsonArray hw_btn_ins_0_macros = hw_btn_ins_0[F("macros")]; @@ -151,26 +138,27 @@ void deserializeConfig() { CJSON(macroLongPress,hw_btn_ins_0_macros[1]); CJSON(macroDoublePress, hw_btn_ins_0_macros[2]); - //int hw_btn_ins_0_type = hw_btn_ins_0["type"]; // 0 - #ifndef WLED_DISABLE_INFRARED - int hw_ir_pin = hw["ir"]["pin"] | -1; // 4 - if (pinManager.allocatePin(hw_ir_pin,false)) { - irPin = hw_ir_pin; - } else { - irPin = -1; + int hw_ir_pin = hw["ir"]["pin"] | -2; // 4 + if (hw_ir_pin > -2) { + if (pinManager.allocatePin(hw_ir_pin,false)) { + irPin = hw_ir_pin; + } else { + irPin = -1; + } } #endif CJSON(irEnabled, hw["ir"]["type"]); JsonObject relay = hw[F("relay")]; - - int hw_relay_pin = relay["pin"]; - if (pinManager.allocatePin(hw_relay_pin,true)) { - rlyPin = hw_relay_pin; - pinMode(rlyPin, OUTPUT); - } else { - rlyPin = -1; + int hw_relay_pin = relay["pin"] | -2; + if (hw_relay_pin > -2) { + if (pinManager.allocatePin(hw_relay_pin,true)) { + rlyPin = hw_relay_pin; + pinMode(rlyPin, OUTPUT); + } else { + rlyPin = -1; + } } if (relay.containsKey("rev")) { rlyMde = !relay["rev"]; @@ -193,12 +181,13 @@ void deserializeConfig() { CJSON(fadeTransition, light_tr[F("mode")]); int tdd = light_tr[F("dur")] | -1; if (tdd >= 0) transitionDelayDefault = tdd * 100; - CJSON(strip.paletteFade, light_tr[F("pal")]); + CJSON(strip.paletteFade, light_tr["pal"]); JsonObject light_nl = light["nl"]; CJSON(nightlightMode, light_nl[F("mode")]); + byte prev = nightlightDelayMinsDefault; CJSON(nightlightDelayMinsDefault, light_nl[F("dur")]); - nightlightDelayMins = nightlightDelayMinsDefault; + if (nightlightDelayMinsDefault != prev) nightlightDelayMins = nightlightDelayMinsDefault; CJSON(nightlightTargetBri, light_nl[F("tbri")]); CJSON(macroNl, light_nl[F("macro")]); @@ -227,11 +216,13 @@ void deserializeConfig() { CJSON(receiveNotificationBrightness, if_sync_recv["bri"]); CJSON(receiveNotificationColor, if_sync_recv["col"]); CJSON(receiveNotificationEffects, if_sync_recv[F("fx")]); + //! following line might be a problem if called after boot receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); JsonObject if_sync_send = if_sync["send"]; + prev = notifyDirectDefault; CJSON(notifyDirectDefault, if_sync_send[F("dir")]); - notifyDirect = notifyDirectDefault; + if (notifyDirectDefault != prev) notifyDirect = notifyDirectDefault; CJSON(notifyButton, if_sync_send[F("btn")]); CJSON(notifyAlexa, if_sync_send[F("va")]); CJSON(notifyHue, if_sync_send[F("hue")]); @@ -310,9 +301,10 @@ void deserializeConfig() { CJSON(latitude, if_ntp[F("lt")]); JsonObject ol = doc[F("ol")]; + prev = overlayDefault; CJSON(overlayDefault ,ol[F("clock")]); // 0 CJSON(countdownMode, ol[F("cntdwn")]); - overlayCurrent = overlayDefault; + if (prev != overlayDefault) overlayCurrent = overlayDefault; CJSON(overlayMin, ol[F("min")]); CJSON(overlayMax, ol[F("max")]); @@ -386,7 +378,32 @@ void deserializeConfig() { #endif JsonObject usermods_settings = doc["um"]; - usermods.readFromConfig(usermods_settings); + if (!usermods_settings.isNull()) usermods.readFromConfig(usermods_settings); + + if (fromFS) return false; + doReboot = doc[F("rb")] | doReboot; + return (doc["sv"] | true); +} + +void deserializeConfigFromFS() { + bool fromeep = false; + bool success = deserializeConfigSec(); + if (!success) { //if file does not exist, try reading from EEPROM + deEEPSettings(); + fromeep = true; + } + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); + + success = readObjectFromFile("/cfg.json", nullptr, &doc); + if (!success) { //if file does not exist, try reading from EEPROM + if (!fromeep) deEEPSettings(); + return; + } + + deserializeConfig(doc.as(), true); } void serializeConfig() { @@ -513,7 +530,7 @@ void serializeConfig() { JsonObject light_tr = light.createNestedObject("tr"); light_tr[F("mode")] = fadeTransition; light_tr[F("dur")] = transitionDelayDefault / 100; - light_tr[F("pal")] = strip.paletteFade; + light_tr["pal"] = strip.paletteFade; JsonObject light_nl = light.createNestedObject("nl"); light_nl[F("mode")] = nightlightMode; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 4f11852a..7e9deb8b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -26,7 +26,8 @@ void handleButton(); void handleIO(); //cfg.cpp -void deserializeConfig(); +bool deserializeConfig(JsonObject doc, bool fromFS = false); +void deserializeConfigFromFS(); bool deserializeConfigSec(); void serializeConfig(); void serializeConfigSec(); diff --git a/wled00/json.cpp b/wled00/json.cpp index db8ecd87..718c1deb 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -98,13 +98,13 @@ void deserializeSegment(JsonObject elem, byte it) effectCurrent = elem[F("fx")] | effectCurrent; effectSpeed = elem[F("sx")] | effectSpeed; effectIntensity = elem[F("ix")] | effectIntensity; - effectPalette = elem[F("pal")] | effectPalette; + effectPalette = elem["pal"] | effectPalette; } else { //permanent byte fx = elem[F("fx")] | seg.mode; if (fx != seg.mode && fx < strip.getModeCount()) strip.setMode(id, fx); seg.speed = elem[F("sx")] | seg.speed; seg.intensity = elem[F("ix")] | seg.intensity; - seg.palette = elem[F("pal")] | seg.palette; + seg.palette = elem["pal"] | seg.palette; } JsonArray iarr = elem[F("i")]; //set individual LEDs @@ -339,7 +339,7 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool fo root[F("fx")] = seg.mode; root[F("sx")] = seg.speed; root[F("ix")] = seg.intensity; - root[F("pal")] = seg.palette; + root["pal"] = seg.palette; root[F("sel")] = seg.isSelected(); root["rev"] = seg.getOption(SEG_OPTION_REVERSED); root[F("mi")] = seg.getOption(SEG_OPTION_MIRROR); @@ -729,21 +729,24 @@ void serveJson(AsyncWebServerRequest* request) const String& url = request->url(); if (url.indexOf("state") > 0) subJson = 1; else if (url.indexOf("info") > 0) subJson = 2; - else if (url.indexOf("si") > 0) subJson = 3; + else if (url.indexOf("si") > 0) subJson = 3; else if (url.indexOf("nodes") > 0) subJson = 4; - else if (url.indexOf("palx") > 0) subJson = 5; + else if (url.indexOf("palx") > 0) subJson = 5; else if (url.indexOf("live") > 0) { serveLiveLeds(request); return; } - else if (url.indexOf(F("eff")) > 0) { + else if (url.indexOf(F("eff")) > 0) { request->send_P(200, "application/json", JSON_mode_names); return; } - else if (url.indexOf(F("pal")) > 0) { + else if (url.indexOf("pal") > 0) { request->send_P(200, "application/json", JSON_palette_names); return; } + else if (url.indexOf("cfg") > 0 && handleFileRead(request, "/cfg.json")) { + return; + } else if (url.length() > 6) { //not just /json request->send( 501, "application/json", F("{\"error\":\"Not implemented\"}")); return; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index adb33ff7..08168371 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -333,7 +333,7 @@ void WLED::setup() errorFlag = ERR_FS_BEGIN; } else deEEP(); updateFSInfo(); - deserializeConfig(); + deserializeConfigFromFS(); #if STATUSLED bool lStatusLed = false; diff --git a/wled00/wled.h b/wled00/wled.h index 9d27c780..0d04f771 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2105070 +#define VERSION 2105110 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index c02e7fb4..ef90a077 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -84,6 +84,7 @@ void initServer() AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request) { bool verboseResponse = false; + bool isConfig = false; { //scope JsonDocument so it releases its buffer DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); @@ -91,12 +92,22 @@ void initServer() if (error || root.isNull()) { request->send(400, "application/json", F("{\"error\":9}")); return; } - fileDoc = &jsonBuffer; - verboseResponse = deserializeState(root); - fileDoc = nullptr; + const String& url = request->url(); + isConfig = url.indexOf("cfg") > -1; + if (!isConfig) { + fileDoc = &jsonBuffer; + verboseResponse = deserializeState(root); + fileDoc = nullptr; + } else { + verboseResponse = deserializeConfig(root); //use verboseResponse to determine whether cfg change should be saved immediately + } } - if (verboseResponse) { //if JSON contains "v" - serveJson(request); return; + if (verboseResponse) { + if (!isConfig) { + serveJson(request); return; //if JSON contains "v" + } else { + serializeConfig(); //Save new settings to FS + } } request->send(200, "application/json", F("{\"success\":true}")); });