From 0f6b1e4ae19ec58cdb2acd562e585b1fb7e2e1f4 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 16 Apr 2022 16:28:43 +0200 Subject: [PATCH] Synchronus applyPreset() from HTTP JSON API call. Bugfix for HTTP API preset. WS multiple broadcast fix. Turning segment on/off will not reset currentPreset/cause stateChanged. --- wled00/fcn_declare.h | 3 +- wled00/json.cpp | 10 ++--- wled00/led.cpp | 7 +--- wled00/presets.cpp | 31 ++++++++++++++- wled00/wled.h | 2 +- wled00/ws.cpp | 92 ++++++++++++++++++++++---------------------- 6 files changed, 83 insertions(+), 62 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 0794c4474..315fb36f5 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -105,7 +105,6 @@ void sendImprovInfoResponse(); void sendImprovRPCResponse(uint8_t commandId); //ir.cpp -//bool decodeIRCustom(uint32_t code); void applyRepeatActions(); byte relativeChange(byte property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); void decodeIR(uint32_t code); @@ -190,7 +189,7 @@ int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0); void handlePlaylist(); //presets.cpp -void handlePresets(); +void handlePresets(bool force = false); bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE, bool fromJson = false); inline bool applyTemporaryPreset() {return applyPreset(255);}; void savePreset(byte index, const char* pname = nullptr, JsonObject saveobj = JsonObject()); diff --git a/wled00/json.cpp b/wled00/json.cpp index 8ce902727..6cfa23e76 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -220,13 +220,13 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) // seg.setOption(SEG_OPTION_FREEZE, false); } // send UDP if not in preset and something changed that is not just selection - //if (!presetId && (seg.differs(prev) & 0x7F)) stateChanged = true; - // send UDP if something changed that is not just selection - if (seg.differs(prev) & 0x7F) stateChanged = true; + // send UDP if something changed that is not just selection or segment power/opacity + if ((seg.differs(prev) & 0x7E) && seg.getOption(SEG_OPTION_ON)==prev.getOption(SEG_OPTION_ON)) stateChanged = true; return; } // deserializes WLED state (fileDoc points to doc object if called from web server) +// presetId is non-0 if called from handlePreset() bool deserializeState(JsonObject root, byte callMode, byte presetId) { bool stateResponse = root[F("v")] | false; @@ -357,7 +357,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) ps = presetCycCurr; if (getVal(root["ps"], &ps, presetCycMin, presetCycMax)) { //load preset (clears state request!) if (ps >= presetCycMin && ps <= presetCycMax) presetCycCurr = ps; - applyPreset(ps, callMode, true); + applyPreset(ps, callMode, !presetId); // may clear root object and replace it by preset content if presetId==0 return stateResponse; } @@ -375,8 +375,6 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) //do not notify here, because the first playlist entry will do if (root["on"].isNull()) callMode = CALL_MODE_NO_NOTIFY; else callMode = CALL_MODE_DIRECT_CHANGE; // possible bugfix for playlist only containing HTTP API preset FX=~ - } else { - interfaceUpdateCallMode = CALL_MODE_WS_SEND; } stateUpdated(callMode); diff --git a/wled00/led.cpp b/wled00/led.cpp index c63c01ee6..9ad855322 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -171,17 +171,14 @@ void updateInterfaces(uint8_t callMode) callMode != CALL_MODE_NO_NOTIFY) updateBlynk(); #endif doPublishMqtt = true; + interfaceUpdateCallMode = 0; //disable } void handleTransitions() { //handle still pending interface update - if (interfaceUpdateCallMode && millis() - lastInterfaceUpdate > INTERFACE_UPDATE_COOLDOWN) - { - updateInterfaces(interfaceUpdateCallMode); - interfaceUpdateCallMode = 0; //disable - } + if (interfaceUpdateCallMode && millis() - lastInterfaceUpdate > INTERFACE_UPDATE_COOLDOWN) updateInterfaces(interfaceUpdateCallMode); if (doPublishMqtt) publishMqtt(); if (transitionActive && transitionDelayTemp > 0) diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 8e8493243..530b68fc7 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -17,16 +17,41 @@ bool applyPreset(byte index, byte callMode, bool fromJson) presetToApply = index; callModeToApply = callMode; checkPlaylist = fromJson; + // the following is needed in case of HTTP JSON API call to return correct state to the caller + // fromJson is true in case when deserializeState() was called with presetId==0 + if (fromJson) handlePresets(true); // force immediate processing return true; } -void handlePresets() +void handlePresets(bool force) { - if (presetToApply == 0 || fileDoc) return; //JSON buffer allocated (apply preset in next cycle) or no preset waiting + if (presetToApply == 0 || (fileDoc && !force)) return; // JSON buffer already allocated and not force apply or no preset waiting JsonObject fdo; const char *filename = presetToApply < 255 ? "/presets.json" : "/tmp.json"; + //crude way to determine if this was called by a network request + uint8_t core = 1; + #ifdef ARDUINO_ARCH_ESP32 + core = xPortGetCoreID(); + #endif + //only allow use of fileDoc from the core responsible for network requests (AKA HTTP JSON API) + //do not use active network request doc from preset called by main loop (playlist, schedule, ...) + if (fileDoc && core && force && presetToApply < 255) { + // this will overwrite doc with preset content but applyPreset() is the last in such case and content of doc is no longer needed + errorFlag = readObjectFromFileUsingId(filename, presetToApply, fileDoc) ? ERR_NONE : ERR_FS_PLOAD; + JsonObject fdo = fileDoc->as(); + // if we applyPreset from JSON and preset contains "seg" we must unload playlist + if (checkPlaylist && !fdo["seg"].isNull()) unloadPlaylist(); + fdo.remove("ps"); //remove load request for presets to prevent recursive crash + deserializeState(fdo, callModeToApply, presetToApply); + if (!errorFlag) currentPreset = presetToApply; + presetToApply = 0; //clear request for preset + callModeToApply = 0; + checkPlaylist = false; + return; + } + // allocate buffer DEBUG_PRINTLN(F("Apply preset JSON buffer requested.")); if (!requestJSONBufferLock(9)) return; // will also assign fileDoc @@ -46,8 +71,10 @@ void handlePresets() const char* httpwin = fdo["win"]; if (httpwin) { String apireq = "win"; apireq += '&'; // reduce flash string usage + apireq += F("IN&"); // interenal call apireq += httpwin; handleSet(nullptr, apireq, false); + setValuesFromFirstSelectedSeg(); // fills legacy values } else { fdo.remove("ps"); //remove load request for presets to prevent recursive crash // if we applyPreset from JSON and preset contains "seg" we must unload playlist diff --git a/wled00/wled.h b/wled00/wled.h index 9b3625d17..ae47c2994 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2204112 +#define VERSION 2204161 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 64d8b3887..454e4cebb 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -34,34 +34,32 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp client->text(F("pong")); return; } - bool verboseResponse = false; - { //scope JsonDocument so it releases its buffer - DEBUG_PRINTLN(F("WS JSON receive buffer requested.")); - if (!requestJSONBufferLock(11)) return; - DeserializationError error = deserializeJson(doc, data, len); - JsonObject root = doc.as(); - if (error || root.isNull()) { - releaseJSONBufferLock(); - return; - } - if (root["v"] && root.size() == 1) { - //if the received value is just "{"v":true}", send only to this client - verboseResponse = true; - } else if (root.containsKey("lv")) - { - wsLiveClientId = root["lv"] ? client->id() : 0; - } else { - verboseResponse = deserializeState(root); - if (!interfaceUpdateCallMode) { - //special case, only on playlist load, avoid sending twice in rapid succession - if (millis() - lastInterfaceUpdate > (INTERFACE_UPDATE_COOLDOWN -300)) verboseResponse = false; - } - } - releaseJSONBufferLock(); // will clean fileDoc + bool verboseResponse = false; + DEBUG_PRINTLN(F("WS JSON receive buffer requested.")); + if (!requestJSONBufferLock(11)) return; + + DeserializationError error = deserializeJson(doc, data, len); + JsonObject root = doc.as(); + if (error || root.isNull()) { + releaseJSONBufferLock(); + return; + } + if (root["v"] && root.size() == 1) { + //if the received value is just "{"v":true}", send only to this client + verboseResponse = true; + } else if (root.containsKey("lv")) { + wsLiveClientId = root["lv"] ? client->id() : 0; + } else { + verboseResponse = deserializeState(root); + } + releaseJSONBufferLock(); // will clean fileDoc + + // force broadcast in 500ms after upadting client + if (verboseResponse) { + sendDataWs(client); + lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500); } - //update if it takes longer than 300ms until next "broadcast" - if (verboseResponse && (millis() - lastInterfaceUpdate < (INTERFACE_UPDATE_COOLDOWN -300) || !interfaceUpdateCallMode)) sendDataWs(client); } } else { //message is comprised of multiple frames or the frame is split into multiple packets @@ -99,28 +97,30 @@ void sendDataWs(AsyncWebSocketClient * client) if (!ws.count()) return; AsyncWebSocketMessageBuffer * buffer; - { //scope JsonDocument so it releases its buffer - DEBUG_PRINTLN(F("WS JSON send buffer requested.")); - if (!requestJSONBufferLock(12)) return; + DEBUG_PRINTLN(F("WS JSON send buffer requested.")); + if (!requestJSONBufferLock(12)) return; - JsonObject state = doc.createNestedObject("state"); - serializeState(state); - JsonObject info = doc.createNestedObject("info"); - serializeInfo(info); - DEBUG_PRINTF("JSON buffer size: %u for WS request.\n", doc.memoryUsage()); - size_t len = measureJson(doc); - size_t heap1 = ESP.getFreeHeap(); - buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes - size_t heap2 = ESP.getFreeHeap(); - if (!buffer || heap1-heap2get(), len +1); + JsonObject state = doc.createNestedObject("state"); + serializeState(state); + JsonObject info = doc.createNestedObject("info"); + serializeInfo(info); + + DEBUG_PRINTF("JSON buffer size: %u for WS request.\n", doc.memoryUsage()); + size_t len = measureJson(doc); + + size_t heap1 = ESP.getFreeHeap(); + buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes + size_t heap2 = ESP.getFreeHeap(); + if (!buffer || heap1-heap2get(), len +1); + releaseJSONBufferLock(); + DEBUG_PRINT(F("Sending WS data ")); if (client) { client->text(buffer);