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.
This commit is contained in:
Blaz Kristan 2022-04-16 16:28:43 +02:00
parent f915201a27
commit 0f6b1e4ae1
6 changed files with 83 additions and 62 deletions

View File

@ -105,7 +105,6 @@ void sendImprovInfoResponse();
void sendImprovRPCResponse(uint8_t commandId); void sendImprovRPCResponse(uint8_t commandId);
//ir.cpp //ir.cpp
//bool decodeIRCustom(uint32_t code);
void applyRepeatActions(); void applyRepeatActions();
byte relativeChange(byte property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); byte relativeChange(byte property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF);
void decodeIR(uint32_t code); void decodeIR(uint32_t code);
@ -190,7 +189,7 @@ int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0);
void handlePlaylist(); void handlePlaylist();
//presets.cpp //presets.cpp
void handlePresets(); void handlePresets(bool force = false);
bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE, bool fromJson = false); bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE, bool fromJson = false);
inline bool applyTemporaryPreset() {return applyPreset(255);}; inline bool applyTemporaryPreset() {return applyPreset(255);};
void savePreset(byte index, const char* pname = nullptr, JsonObject saveobj = JsonObject()); void savePreset(byte index, const char* pname = nullptr, JsonObject saveobj = JsonObject());

View File

@ -220,13 +220,13 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
// seg.setOption(SEG_OPTION_FREEZE, false); // seg.setOption(SEG_OPTION_FREEZE, false);
} }
// send UDP if not in preset and something changed that is not just selection // 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 or segment power/opacity
// send UDP if something changed that is not just selection if ((seg.differs(prev) & 0x7E) && seg.getOption(SEG_OPTION_ON)==prev.getOption(SEG_OPTION_ON)) stateChanged = true;
if (seg.differs(prev) & 0x7F) stateChanged = true;
return; return;
} }
// deserializes WLED state (fileDoc points to doc object if called from web server) // 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 deserializeState(JsonObject root, byte callMode, byte presetId)
{ {
bool stateResponse = root[F("v")] | false; bool stateResponse = root[F("v")] | false;
@ -357,7 +357,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
ps = presetCycCurr; ps = presetCycCurr;
if (getVal(root["ps"], &ps, presetCycMin, presetCycMax)) { //load preset (clears state request!) if (getVal(root["ps"], &ps, presetCycMin, presetCycMax)) { //load preset (clears state request!)
if (ps >= presetCycMin && ps <= presetCycMax) presetCycCurr = ps; 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; 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 //do not notify here, because the first playlist entry will do
if (root["on"].isNull()) callMode = CALL_MODE_NO_NOTIFY; 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 callMode = CALL_MODE_DIRECT_CHANGE; // possible bugfix for playlist only containing HTTP API preset FX=~
} else {
interfaceUpdateCallMode = CALL_MODE_WS_SEND;
} }
stateUpdated(callMode); stateUpdated(callMode);

View File

@ -171,17 +171,14 @@ void updateInterfaces(uint8_t callMode)
callMode != CALL_MODE_NO_NOTIFY) updateBlynk(); callMode != CALL_MODE_NO_NOTIFY) updateBlynk();
#endif #endif
doPublishMqtt = true; doPublishMqtt = true;
interfaceUpdateCallMode = 0; //disable
} }
void handleTransitions() void handleTransitions()
{ {
//handle still pending interface update //handle still pending interface update
if (interfaceUpdateCallMode && millis() - lastInterfaceUpdate > INTERFACE_UPDATE_COOLDOWN) if (interfaceUpdateCallMode && millis() - lastInterfaceUpdate > INTERFACE_UPDATE_COOLDOWN) updateInterfaces(interfaceUpdateCallMode);
{
updateInterfaces(interfaceUpdateCallMode);
interfaceUpdateCallMode = 0; //disable
}
if (doPublishMqtt) publishMqtt(); if (doPublishMqtt) publishMqtt();
if (transitionActive && transitionDelayTemp > 0) if (transitionActive && transitionDelayTemp > 0)

View File

@ -17,16 +17,41 @@ bool applyPreset(byte index, byte callMode, bool fromJson)
presetToApply = index; presetToApply = index;
callModeToApply = callMode; callModeToApply = callMode;
checkPlaylist = fromJson; 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; 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; JsonObject fdo;
const char *filename = presetToApply < 255 ? "/presets.json" : "/tmp.json"; 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<JsonObject>();
// 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 // allocate buffer
DEBUG_PRINTLN(F("Apply preset JSON buffer requested.")); DEBUG_PRINTLN(F("Apply preset JSON buffer requested."));
if (!requestJSONBufferLock(9)) return; // will also assign fileDoc if (!requestJSONBufferLock(9)) return; // will also assign fileDoc
@ -46,8 +71,10 @@ void handlePresets()
const char* httpwin = fdo["win"]; const char* httpwin = fdo["win"];
if (httpwin) { if (httpwin) {
String apireq = "win"; apireq += '&'; // reduce flash string usage String apireq = "win"; apireq += '&'; // reduce flash string usage
apireq += F("IN&"); // interenal call
apireq += httpwin; apireq += httpwin;
handleSet(nullptr, apireq, false); handleSet(nullptr, apireq, false);
setValuesFromFirstSelectedSeg(); // fills legacy values
} else { } else {
fdo.remove("ps"); //remove load request for presets to prevent recursive crash 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 // if we applyPreset from JSON and preset contains "seg" we must unload playlist

View File

@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // 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 //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG

View File

@ -34,34 +34,32 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
client->text(F("pong")); client->text(F("pong"));
return; 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); bool verboseResponse = false;
JsonObject root = doc.as<JsonObject>(); DEBUG_PRINTLN(F("WS JSON receive buffer requested."));
if (error || root.isNull()) { if (!requestJSONBufferLock(11)) return;
releaseJSONBufferLock();
return; DeserializationError error = deserializeJson(doc, data, len);
} JsonObject root = doc.as<JsonObject>();
if (root["v"] && root.size() == 1) { if (error || root.isNull()) {
//if the received value is just "{"v":true}", send only to this client releaseJSONBufferLock();
verboseResponse = true; return;
} else if (root.containsKey("lv")) }
{ if (root["v"] && root.size() == 1) {
wsLiveClientId = root["lv"] ? client->id() : 0; //if the received value is just "{"v":true}", send only to this client
} else { verboseResponse = true;
verboseResponse = deserializeState(root); } else if (root.containsKey("lv")) {
if (!interfaceUpdateCallMode) { wsLiveClientId = root["lv"] ? client->id() : 0;
//special case, only on playlist load, avoid sending twice in rapid succession } else {
if (millis() - lastInterfaceUpdate > (INTERFACE_UPDATE_COOLDOWN -300)) verboseResponse = false; verboseResponse = deserializeState(root);
} }
} releaseJSONBufferLock(); // will clean fileDoc
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 { } else {
//message is comprised of multiple frames or the frame is split into multiple packets //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; if (!ws.count()) return;
AsyncWebSocketMessageBuffer * buffer; AsyncWebSocketMessageBuffer * buffer;
{ //scope JsonDocument so it releases its buffer DEBUG_PRINTLN(F("WS JSON send buffer requested."));
DEBUG_PRINTLN(F("WS JSON send buffer requested.")); if (!requestJSONBufferLock(12)) return;
if (!requestJSONBufferLock(12)) return;
JsonObject state = doc.createNestedObject("state"); JsonObject state = doc.createNestedObject("state");
serializeState(state); serializeState(state);
JsonObject info = doc.createNestedObject("info"); JsonObject info = doc.createNestedObject("info");
serializeInfo(info); serializeInfo(info);
DEBUG_PRINTF("JSON buffer size: %u for WS request.\n", doc.memoryUsage());
size_t len = measureJson(doc); DEBUG_PRINTF("JSON buffer size: %u for WS request.\n", doc.memoryUsage());
size_t heap1 = ESP.getFreeHeap(); size_t len = measureJson(doc);
buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes
size_t heap2 = ESP.getFreeHeap(); size_t heap1 = ESP.getFreeHeap();
if (!buffer || heap1-heap2<len) { buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes
releaseJSONBufferLock(); size_t heap2 = ESP.getFreeHeap();
ws.closeAll(1013); //code 1013 = temporary overload, try again later if (!buffer || heap1-heap2<len) {
ws.cleanupClients(0); //disconnect all clients to release memory
return; //out of memory
}
serializeJson(doc, (char *)buffer->get(), len +1);
releaseJSONBufferLock(); releaseJSONBufferLock();
} ws.closeAll(1013); //code 1013 = temporary overload, try again later
ws.cleanupClients(0); //disconnect all clients to release memory
return; //out of memory
}
serializeJson(doc, (char *)buffer->get(), len +1);
releaseJSONBufferLock();
DEBUG_PRINT(F("Sending WS data ")); DEBUG_PRINT(F("Sending WS data "));
if (client) { if (client) {
client->text(buffer); client->text(buffer);