#include "wled.h" /* * Methods to handle saving and loading presets to/from the filesystem */ #ifdef ARDUINO_ARCH_ESP32 static char *tmpRAMbuffer = nullptr; #endif static volatile byte presetToApply = 0; static volatile byte callModeToApply = 0; bool applyPreset(byte index, byte callMode, bool fromJson) { DEBUG_PRINT(F("Request to apply preset: ")); DEBUG_PRINTLN(index); presetToApply = index; callModeToApply = callMode; // 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(bool force) { bool changePreset = false; uint8_t tmpPreset = presetToApply; // store temporary since deserializeState() may call applyPreset() uint8_t tmpMode = callModeToApply; if (tmpPreset == 0 || (fileDoc && !force)) return; // JSON buffer already allocated and not force apply or no preset waiting JsonObject fdo; const char *filename = tmpPreset < 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 && tmpPreset < 255) { DEBUG_PRINT(F("Force applying preset: ")); DEBUG_PRINTLN(presetToApply); presetToApply = 0; //clear request for preset callModeToApply = 0; // 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, tmpPreset, fileDoc) ? ERR_NONE : ERR_FS_PLOAD; JsonObject fdo = fileDoc->as(); //HTTP API commands const char* httpwin = fdo["win"]; if (httpwin) { String apireq = "win"; // reduce flash string usage apireq += F("&IN&"); // internal call apireq += httpwin; handleSet(nullptr, apireq, false); // may call applyPreset() via PL= setValuesFromFirstSelectedSeg(); // fills legacy values changePreset = true; } else { if (!fdo["seg"].isNull()) unloadPlaylist(); // if preset contains "seg" we must unload playlist if (!fdo["seg"].isNull() || !fdo["on"].isNull() || !fdo["bri"].isNull() || !fdo["ps"].isNull() || !fdo[F("playlist")].isNull()) changePreset = true; fdo.remove("ps"); //remove load request for presets to prevent recursive crash deserializeState(fdo, tmpMode, tmpPreset); // may call applyPreset() which will overwrite presetToApply } if (!errorFlag && changePreset) currentPreset = tmpPreset; colorUpdated(tmpMode); return; } if (force) return; // something went wrong with force option (most likely WS request), quit and wait for async load // allocate buffer if (!requestJSONBufferLock(9)) return; // will also assign fileDoc presetToApply = 0; //clear request for preset callModeToApply = 0; DEBUG_PRINTLN(F("Applying preset: ")); DEBUG_PRINTLN(tmpPreset); #ifdef ARDUINO_ARCH_ESP32 if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { deserializeJson(*fileDoc,tmpRAMbuffer); errorFlag = ERR_NONE; } else #endif { errorFlag = readObjectFromFileUsingId(filename, tmpPreset, fileDoc) ? ERR_NONE : ERR_FS_PLOAD; } fdo = fileDoc->as(); //HTTP API commands const char* httpwin = fdo["win"]; if (httpwin) { String apireq = "win"; // reduce flash string usage apireq += F("&IN&"); // internal call apireq += httpwin; handleSet(nullptr, apireq, false); // may call applyPreset() via PL= setValuesFromFirstSelectedSeg(); // fills legacy values changePreset = true; } else { if (!fdo["seg"].isNull() || !fdo["on"].isNull() || !fdo["bri"].isNull() || !fdo["nl"].isNull() || !fdo["ps"].isNull() || !fdo[F("playlist")].isNull()) changePreset = true; fdo.remove("ps"); //remove load request for presets to prevent recursive crash deserializeState(fdo, CALL_MODE_NO_NOTIFY, tmpPreset); // may change presetToApply by calling applyPreset() } if (!errorFlag && tmpPreset < 255 && changePreset) currentPreset = tmpPreset; #if defined(ARDUINO_ARCH_ESP32) //Aircoookie recommended not to delete buffer if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { free(tmpRAMbuffer); tmpRAMbuffer = nullptr; } #endif releaseJSONBufferLock(); // will also clear fileDoc colorUpdated(tmpMode); updateInterfaces(tmpMode); } //called from handleSet(PS=) [network callback (fileDoc==nullptr), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)] void savePreset(byte index, const char* pname, JsonObject saveobj) { if (index == 0 || (index > 250 && index < 255)) return; char tmp[12]; JsonObject sObj = saveobj; bool bufferAllocated = false; bool persist = (index != 255); const char *filename = persist ? "/presets.json" : "/tmp.json"; if (!fileDoc) { // called from handleSet() HTTP API if (!requestJSONBufferLock(10)) return; sObj = fileDoc->to(); bufferAllocated = true; } if (sObj["n"].isNull() && pname == nullptr) { sprintf_P(tmp, PSTR("Preset %d"), index); sObj["n"] = tmp; } else if (pname) sObj["n"] = pname; sObj.remove(F("psave")); sObj.remove(F("v")); if (!sObj["o"]) { DEBUGFS_PRINTLN(F("Serialize current state")); if (sObj["ib"].isNull() && sObj["sb"].isNull()) serializeState(sObj, true); else serializeState(sObj, true, sObj["ib"], sObj["sb"]); if (persist) currentPreset = index; } sObj.remove("o"); sObj.remove("ib"); sObj.remove("sb"); sObj.remove(F("sc")); sObj.remove(F("error")); sObj.remove(F("time")); #if defined(ARDUINO_ARCH_ESP32) if (index==255) { if (tmpRAMbuffer!=nullptr) free(tmpRAMbuffer); size_t len = measureJson(*fileDoc) + 1; DEBUG_PRINTLN(len); // if possible use SPI RAM on ESP32 #ifdef WLED_USE_PSRAM if (psramFound()) tmpRAMbuffer = (char*) ps_malloc(len); else #endif tmpRAMbuffer = (char*) malloc(len); if (tmpRAMbuffer!=nullptr) { serializeJson(*fileDoc, tmpRAMbuffer, len); } else { writeObjectToFileUsingId(filename, index, fileDoc); } } else #endif writeObjectToFileUsingId(filename, index, fileDoc); if (persist) presetsModifiedTime = toki.second(); //unix time if (bufferAllocated) releaseJSONBufferLock(); updateFSInfo(); } void deletePreset(byte index) { StaticJsonDocument<24> empty; writeObjectToFileUsingId("/presets.json", index, &empty); presetsModifiedTime = toki.second(); //unix time updateFSInfo(); }