kopia lustrzana https://github.com/Aircoookie/WLED
				
				
				
			
		
			
				
	
	
		
			250 wiersze
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			250 wiersze
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C++
		
	
	
| #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;
 | |
| static volatile byte presetToSave = 0;
 | |
| static volatile int8_t saveLedmap = -1;
 | |
| static char quickLoad[9];
 | |
| static char saveName[33];
 | |
| static bool includeBri = true, segBounds = true, selectedOnly = false, playlistSave = false;;
 | |
| 
 | |
| static const char *getFileName(bool persist = true) {
 | |
|   return persist ? "/presets.json" : "/tmp.json";
 | |
| }
 | |
| 
 | |
| static void doSaveState() {
 | |
|   bool persist = (presetToSave < 251);
 | |
|   const char *filename = getFileName(persist);
 | |
| 
 | |
|   if (!requestJSONBufferLock(10)) return; // will set fileDoc
 | |
| 
 | |
|   initPresetsFile(); // just in case if someone deleted presets.json using /edit
 | |
|   JsonObject sObj = doc.to<JsonObject>();
 | |
| 
 | |
|   DEBUG_PRINTLN(F("Serialize current state"));
 | |
|   if (playlistSave) {
 | |
|     serializePlaylist(sObj);
 | |
|     if (includeBri) sObj["on"] = true;
 | |
|   } else {
 | |
|     serializeState(sObj, true, includeBri, segBounds, selectedOnly);
 | |
|   }
 | |
|   sObj["n"] = saveName;
 | |
|   if (quickLoad[0]) sObj[F("ql")] = quickLoad;
 | |
|   if (saveLedmap >= 0) sObj[F("ledmap")] = saveLedmap;
 | |
| /*
 | |
|   #ifdef WLED_DEBUG
 | |
|     DEBUG_PRINTLN(F("Serialized preset"));
 | |
|     serializeJson(doc,Serial);
 | |
|     DEBUG_PRINTLN();
 | |
|   #endif
 | |
| */
 | |
|   #if defined(ARDUINO_ARCH_ESP32)
 | |
|   if (!persist) {
 | |
|     if (tmpRAMbuffer!=nullptr) free(tmpRAMbuffer);
 | |
|     size_t len = measureJson(*fileDoc) + 1;
 | |
|     DEBUG_PRINTLN(len);
 | |
|     // if possible use SPI RAM on ESP32
 | |
|     #if defined(BOARD_HAS_PSRAM) && defined(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, presetToSave, fileDoc);
 | |
|     }
 | |
|   } else
 | |
|   #endif
 | |
|   writeObjectToFileUsingId(filename, presetToSave, fileDoc);
 | |
| 
 | |
|   if (persist) presetsModifiedTime = toki.second(); //unix time
 | |
|   releaseJSONBufferLock();
 | |
|   updateFSInfo();
 | |
| 
 | |
|   // clean up
 | |
|   saveLedmap   = -1;
 | |
|   presetToSave = 0;
 | |
|   saveName[0]  = '\0';
 | |
|   quickLoad[0] = '\0';
 | |
|   playlistSave = false;
 | |
| }
 | |
| 
 | |
| bool getPresetName(byte index, String& name)
 | |
| {
 | |
|   if (!requestJSONBufferLock(9)) return false;
 | |
|   bool presetExists = false;
 | |
|   if (readObjectFromFileUsingId(getFileName(), index, &doc))
 | |
|   {
 | |
|     JsonObject fdo = doc.as<JsonObject>();
 | |
|     if (fdo["n"]) {
 | |
|       name = (const char*)(fdo["n"]);
 | |
|       presetExists = true;
 | |
|     }
 | |
|   }
 | |
|   releaseJSONBufferLock();
 | |
|   return presetExists;
 | |
| }
 | |
| 
 | |
| void initPresetsFile()
 | |
| {
 | |
|   if (WLED_FS.exists(getFileName())) return;
 | |
| 
 | |
|   StaticJsonDocument<64> doc;
 | |
|   JsonObject sObj = doc.to<JsonObject>();
 | |
|   sObj.createNestedObject("0");
 | |
|   File f = WLED_FS.open(getFileName(), "w");
 | |
|   if (!f) {
 | |
|     errorFlag = ERR_FS_GENERAL;
 | |
|     return;
 | |
|   }
 | |
|   serializeJson(doc, f);
 | |
|   f.close();
 | |
| }
 | |
| 
 | |
| bool applyPreset(byte index, byte callMode)
 | |
| {
 | |
|   DEBUG_PRINT(F("Request to apply preset: "));
 | |
|   DEBUG_PRINTLN(index);
 | |
|   presetToApply = index;
 | |
|   callModeToApply = callMode;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // apply preset or fallback to a effect and palette if it doesn't exist
 | |
| void applyPresetWithFallback(uint8_t index, uint8_t callMode, uint8_t effectID, uint8_t paletteID)
 | |
| {
 | |
|   applyPreset(index, callMode);
 | |
|   //these two will be overwritten if preset exists in handlePresets()
 | |
|   effectCurrent = effectID;
 | |
|   effectPalette = paletteID;
 | |
| }
 | |
| 
 | |
| void handlePresets()
 | |
| {
 | |
|   if (presetToSave) {
 | |
|     doSaveState();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (presetToApply == 0 || fileDoc) return; // no preset waiting to apply, or JSON buffer is already allocated, return to loop until free
 | |
| 
 | |
|   bool changePreset = false;
 | |
|   uint8_t tmpPreset = presetToApply; // store temporary since deserializeState() may call applyPreset()
 | |
|   uint8_t tmpMode   = callModeToApply;
 | |
| 
 | |
|   JsonObject fdo;
 | |
|   const char *filename = getFileName(tmpPreset < 255);
 | |
| 
 | |
|   // allocate buffer
 | |
|   if (!requestJSONBufferLock(9)) return;  // will also assign fileDoc
 | |
| 
 | |
|   presetToApply = 0; //clear request for preset
 | |
|   callModeToApply = 0;
 | |
| 
 | |
|   DEBUG_PRINT(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<JsonObject>();
 | |
| 
 | |
|   //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;
 | |
|     if (!(tmpMode == CALL_MODE_BUTTON_PRESET && fdo["ps"].is<const char *>() && strchr(fdo["ps"].as<const char *>(),'~') != strrchr(fdo["ps"].as<const char *>(),'~')))
 | |
|       fdo.remove("ps"); // remove load request for presets to prevent recursive crash (if not called by button and contains preset cycling string "1~5~")
 | |
|     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
 | |
|   if (changePreset) notify(tmpMode); // force UDP notification
 | |
|   stateUpdated(tmpMode);  // was colorUpdated() if anything breaks
 | |
|   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 sObj)
 | |
| {
 | |
|   if (index == 0 || (index > 250 && index < 255)) return;
 | |
|   if (pname) strlcpy(saveName, pname, 33);
 | |
|   else {
 | |
|     if (sObj["n"].is<const char*>()) strlcpy(saveName, sObj["n"].as<const char*>(), 33);
 | |
|     else                             sprintf_P(saveName, PSTR("Preset %d"), index);
 | |
|   }
 | |
| 
 | |
|   DEBUG_PRINT(F("Saving preset (")); DEBUG_PRINT(index); DEBUG_PRINT(F(") ")); DEBUG_PRINTLN(saveName);
 | |
| 
 | |
|   presetToSave = index;
 | |
|   playlistSave = false;
 | |
|   if (sObj[F("ql")].is<const char*>()) strlcpy(quickLoad, sObj[F("ql")].as<const char*>(), 9); // client limits QL to 2 chars, buffer for 8 bytes to allow unicode
 | |
| 
 | |
|   if (sObj.size()==0 || sObj["o"].isNull()) { // no "o" means not a playlist or custom API call, saving of state is async (not immediately)
 | |
|     includeBri   = sObj["ib"].as<bool>() || sObj.size()==0 || index==255; // temporary preset needs brightness
 | |
|     segBounds    = sObj["sb"].as<bool>() || sObj.size()==0 || index==255; // temporary preset needs bounds
 | |
|     selectedOnly = sObj[F("sc")].as<bool>();
 | |
|     saveLedmap   = sObj[F("ledmap")] | -1;
 | |
|   } else {
 | |
|     // this is a playlist or API call
 | |
|     if (sObj[F("playlist")].isNull()) {
 | |
|       // we will save API call immediately (often causes presets.json corruption)
 | |
|       presetToSave = 0;
 | |
|       if (index > 250 || !fileDoc) return; // cannot save API calls to temporary preset (255)
 | |
|       sObj.remove("o");
 | |
|       sObj.remove("v");
 | |
|       sObj.remove("time");
 | |
|       sObj.remove(F("error"));
 | |
|       sObj.remove(F("psave"));
 | |
|       if (sObj["n"].isNull()) sObj["n"] = saveName;
 | |
|       initPresetsFile(); // just in case if someone deleted presets.json using /edit
 | |
|       writeObjectToFileUsingId(getFileName(index<255), index, fileDoc);
 | |
|       presetsModifiedTime = toki.second(); //unix time
 | |
|       updateFSInfo();
 | |
|     } else {
 | |
|       // store playlist
 | |
|       // WARNING: playlist will be loaded in json.cpp after this call and will have repeat counter increased by 1
 | |
|       includeBri   = true; // !sObj["on"].isNull();
 | |
|       playlistSave = true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void deletePreset(byte index) {
 | |
|   StaticJsonDocument<24> empty;
 | |
|   writeObjectToFileUsingId(getFileName(), index, &empty);
 | |
|   presetsModifiedTime = toki.second(); //unix time
 | |
|   updateFSInfo();
 | |
| } |