From 606cd18dc4055cd72615999fabe94622d1e7e9d4 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Sat, 3 Oct 2020 00:29:36 +0200 Subject: [PATCH] Working deletion Added HTTP API support to JSON API --- wled00/fcn_declare.h | 5 +- wled00/file.cpp | 111 +++++++++++++++++++++++++++++++---------- wled00/json.cpp | 39 ++++++++++++--- wled00/set.cpp | 4 +- wled00/wled.cpp | 11 +++- wled00/wled.h | 8 +-- wled00/wled_eeprom.cpp | 18 +++++-- 7 files changed, 153 insertions(+), 43 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 1d8f32efa..4af3f2b01 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -142,7 +142,7 @@ void _drawOverlayCronixie(); void _setRandomColor(bool _sec,bool fromButton=false); bool isAsterisksOnly(const char* str, byte maxLen); void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); -bool handleSet(AsyncWebServerRequest *request, const String& req); +bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=true); int getNumVal(const String* req, uint16_t pos); bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); @@ -200,7 +200,8 @@ void saveSettingsToEEPROM(); void loadSettingsFromEEPROM(bool first); void savedToPresets(); bool applyPreset(byte index, bool loadBri = true); -void savePreset(byte index, bool persist = true, const char* pname = nullptr, byte prio = 50, JsonObject saveobj = JsonObject()); +void savePreset(byte index, bool persist = true, const char* pname = nullptr, byte prio = 5, JsonObject saveobj = JsonObject()); +void deletePreset(byte index); void loadMacro(byte index, char* m); void applyMacro(byte index); void saveMacro(byte index, const String& mc, bool persist = true); //only commit on single save, not in settings diff --git a/wled00/file.cpp b/wled00/file.cpp index f4eeef07a..67894b57b 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -6,6 +6,21 @@ #ifndef WLED_DISABLE_FILESYSTEM +#define FS_BUFSIZE 256 + +/* + * Structural requirements for files managed by writeObjectToFile() and readObjectFromFile() utilities: + * 1. File must be a string representation of a valid JSON object + * 2. File must have '{' as first character + * 3. There must not be any additional characters between a root-level key and its value object (e.g. space, tab, newline) + * 4. There must not be any characters between an root object-separating ',' and the next object key string + * 5. There may be any number of spaces, tabs, and/or newlines before such object-separating ',' + * 6. There must not be more than 5 consecutive spaces at any point except for those permitted in condition 5 + * 7. If it is desired to delete the first usable object (e.g. preset file), a dummy object '"0":{}' is inserted at the beginning. + * It shall be disregarded by receiving software. + * The reason for it is that deleting the first preset would require special code to handle commas between it and the 2nd preset + */ + //find() that reads and buffers data from file stream in 256-byte blocks. //Significantly faster, f.find(key) can take SECONDS for multi-kB files bool bufferedFind(const char *target, File f) { @@ -21,11 +36,11 @@ bool bufferedFind(const char *target, File f) { size_t index = 0; byte c; uint16_t bufsize = 0, count = 0; - byte buf[256]; + byte buf[FS_BUFSIZE]; f.seek(0); while (f.position() < f.size() -1) { - bufsize = f.read(buf, 256); + bufsize = f.read(buf, FS_BUFSIZE); count = 0; while (count < bufsize) { if(buf[count] != target[index]) @@ -56,11 +71,11 @@ bool bufferedFindSpace(uint16_t targetLen, File f) { uint16_t index = 0; uint16_t bufsize = 0, count = 0; - byte buf[256]; + byte buf[FS_BUFSIZE]; f.seek(0); while (f.position() < f.size() -1) { - bufsize = f.read(buf, 256); + bufsize = f.read(buf, FS_BUFSIZE); count = 0; while (count < bufsize) { @@ -81,15 +96,71 @@ bool bufferedFindSpace(uint16_t targetLen, File f) { return false; } +//find the closing bracket corresponding to the opening bracket at the file pos when calling this function +bool bufferedFindObjectEnd(File f) { + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTLN(F("Find obj end")); + uint32_t s = millis(); + #endif + + if (!f || !f.size()) return false; + + uint16_t objDepth = 0; //num of '{' minus num of '}'. return once 0 + uint16_t bufsize = 0, count = 0; + //size_t start = f.position(); + byte buf[256]; + + while (f.position() < f.size() -1) { + bufsize = f.read(buf, FS_BUFSIZE); + count = 0; + + while (count < bufsize) { + if (buf[count] == '{') objDepth++; + if (buf[count] == '}') objDepth--; + if (objDepth == 0) { + f.seek((f.position() - bufsize) + count +1); + DEBUGFS_PRINTF("} at pos %d, took %d ms", f.position(), millis() - s); + return true; + } + count++; + } + } + DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); + return false; +} + +//fills n bytes from current file pos with ' ' characters +void writeSpace(File f, uint16_t l) +{ + byte buf[FS_BUFSIZE]; + memset(buf, ' ', FS_BUFSIZE); + + while (l > 0) { + uint16_t block = (l>FS_BUFSIZE) ? FS_BUFSIZE : l; + f.write(buf, block); + l -= block; + } +} + bool appendObjectToFile(File f, const char* key, JsonDocument* content, uint32_t s) { #ifdef WLED_DEBUG_FS - DEBUGFS_PRINTLN("Append"); + DEBUGFS_PRINTLN(F("Append")); uint32_t s1 = millis(); #endif uint32_t pos = 0; if (!f) return false; - if (f.size() < 3) f.print("{}"); + + if (f.size() < 3) { + char init[10]; + strcpy_P(init, PSTR("{\"0\":{}}")); + f.print(init); + } + + if (content->isNull()) { + f.close(); + return true; //nothing to append + } //if there is enough empty space in file, insert there instead of appending uint32_t contentLen = measureJson(*content); @@ -137,7 +208,7 @@ bool appendObjectToFile(File f, const char* key, JsonDocument* content, uint32_t bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content) { char objKey[10]; - sprintf(objKey, "\"%ld\":", id); + sprintf(objKey, "\"%d\":", id); writeObjectToFile(file, objKey, content); } @@ -154,7 +225,7 @@ bool writeObjectToFile(const char* file, const char* key, JsonDocument* content) File f = WLED_FS.open(file, "r+"); if (!f && !WLED_FS.exists(file)) f = WLED_FS.open(file, "w+"); if (!f) { - DEBUGFS_PRINTLN("Failed to open!"); + DEBUGFS_PRINTLN(F("Failed to open!")); return false; } @@ -166,34 +237,24 @@ bool writeObjectToFile(const char* file, const char* key, JsonDocument* content) //exists pos = f.position(); //measure out end of old object - StaticJsonDocument<1024> doc; - deserializeJson(doc, f); + bufferedFindObjectEnd(f); uint32_t pos2 = f.position(); uint32_t oldLen = pos2 - pos; - #ifdef WLED_DEBUG_FS - DEBUGFS_PRINTF("Old obj len %d >>> ", oldLen); - serializeJson(doc, Serial); - DEBUGFS_PRINTLN(); - #endif + DEBUGFS_PRINTF("Old obj len %d\n", oldLen); if (!content->isNull() && measureJson(*content) <= oldLen) //replace { - DEBUGFS_PRINTLN("replace"); + DEBUGFS_PRINTLN(F("replace")); f.seek(pos); serializeJson(*content, f); - //pad rest - for (uint32_t i = f.position(); i < pos2; i++) { - f.write(' '); - } + writeSpace(f, pos2 - f.position()); } else { //delete - DEBUGFS_PRINTLN("delete"); + DEBUGFS_PRINTLN(F("delete")); pos -= strlen(key); if (pos > 3) pos--; //also delete leading comma if not first object f.seek(pos); - for (uint32_t i = pos; i < pos2; i++) { - f.write(' '); - } + writeSpace(f, pos2 - pos); if (!content->isNull()) return appendObjectToFile(f, key, content, s); } f.close(); @@ -204,7 +265,7 @@ bool writeObjectToFile(const char* file, const char* key, JsonDocument* content) bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest) { char objKey[10]; - sprintf(objKey, "\"%ld\":", id); + sprintf(objKey, "\"%d\":", id); readObjectFromFile(file, objKey, dest); } diff --git a/wled00/json.cpp b/wled00/json.cpp index 39afdc84c..71ebf0559 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1,5 +1,9 @@ #include "wled.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp_spiffs.h" //FS info bare IDF function until FS wrapper is available for ESP32 +#endif + /* * JSON API (De)serialization */ @@ -148,6 +152,14 @@ bool deserializeState(JsonObject root) strip.applyToAllSelected = false; bool stateResponse = root[F("v")] | false; + //HTTP API commands + const char* httpwin = root[F("win")]; + if (httpwin) { + String apireq = "win&"; + apireq += httpwin; + handleSet(nullptr, apireq, false); + } + int ps = root[F("ps")] | -1; if (ps >= 0) applyPreset(ps); @@ -192,7 +204,7 @@ bool deserializeState(JsonObject root) bool noNotification = udpn[F("nn")]; //send no notification just for this request int timein = root[F("time")] | -1; - if (timein != -1) setTime(timein); + if (timein != -1 && millis() - ntpLastSyncTime > 50000000L) setTime(timein); doReboot = root[F("rb")] | doReboot; realtimeOverride = root[F("lor")] | realtimeOverride; @@ -244,7 +256,14 @@ bool deserializeState(JsonObject root) bool persistSaves = !(root[F("np")] | false); ps = root[F("psave")] | -1; - if (ps >= 0) savePreset(ps, persistSaves, root["n"], root["p"] | 50, root["o"].as()); + if (ps > 0) { + savePreset(ps, persistSaves, root["n"], root["p"] | 50, root["o"].as()); + } else { + ps = root[F("pdel")] | -1; //deletion + if (ps > 0) { + deletePreset(ps); + } + } return stateResponse; } @@ -422,10 +441,18 @@ void serializeInfo(JsonObject root) wifi_info[F("channel")] = WiFi.channel(); JsonObject fs_info = root.createNestedObject("fs"); - FSInfo fsi; - WLED_FS.info(fsi); - fs_info["u"] = fsi.usedBytes; - fs_info["t"] = fsi.totalBytes; + #ifdef ARDUINO_ARCH_ESP32 + size_t used, total; + esp_spiffs_info(nullptr, &total, &used); + fs_info["u"] = used; + fs_info["t"] = total; + #else + FSInfo fsi; + WLED_FS.info(fsi); + fs_info["u"] = fsi.usedBytes; + fs_info["t"] = fsi.totalBytes; + #endif + fs_info[F("pmt")] = presetsModifiedTime; #ifdef ARDUINO_ARCH_ESP32 #ifdef WLED_DEBUG diff --git a/wled00/set.cpp b/wled00/set.cpp index f87ea9338..e263fcc46 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -375,7 +375,7 @@ bool updateVal(const String* req, const char* key, byte* val, byte minv, byte ma //HTTP API request parser -bool handleSet(AsyncWebServerRequest *request, const String& req) +bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) { if (!(req.indexOf("win") >= 0)) return false; @@ -746,6 +746,8 @@ bool handleSet(AsyncWebServerRequest *request, const String& req) } //you can add more if you need + if (!apply) return true; //when called by JSON API, do not call colorUpdated() here + //internal call, does not send XML response pos = req.indexOf(F("IN")); if (pos < 1) XML_response(request); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 383f2e9cc..913c8fc3f 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -177,10 +177,17 @@ void WLED::setup() DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); #ifndef WLED_DISABLE_FILESYSTEM + bool fsinit = false; + DEBUGFS_PRINTLN(F("Mount FS")); #ifdef ARDUINO_ARCH_ESP32 - WLED_FS.begin(true); + fsinit = WLED_FS.begin(true); + #else + fsinit = WLED_FS.begin(); #endif - WLED_FS.begin(); + if (!fsinit) { + DEBUGFS_PRINTLN(F("FS failed!")); + errorFlag = ERR_FS_BEGIN; + } #endif DEBUG_PRINTLN(F("Load EEPROM")); diff --git a/wled00/wled.h b/wled00/wled.h index 6612c3bab..3f19b70be 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -36,8 +36,8 @@ #endif //#define WLED_DISABLE_FILESYSTEM // FS used by new preset functionality -//#define WLED_ENABLE_FS_SERVING // Enable sending html file from SPIFFS before serving progmem version -//#define WLED_ENABLE_FS_EDITOR // enable /edit page for editing SPIFFS content. Will also be disabled with OTA lock +#define WLED_ENABLE_FS_SERVING // Enable sending html file from SPIFFS before serving progmem version +#define WLED_ENABLE_FS_EDITOR // enable /edit page for editing SPIFFS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line //#define WLED_DEBUG @@ -474,9 +474,11 @@ WLED_GLOBAL uint16_t olen _INIT(0); // presets WLED_GLOBAL uint16_t savedPresets _INIT(0); -WLED_GLOBAL int8_t currentPreset _INIT(-1); +WLED_GLOBAL int16_t currentPreset _INIT(-1); WLED_GLOBAL bool isPreset _INIT(false); +WLED_GLOBAL unsigned long presetsModifiedTime _INIT(0L); + WLED_GLOBAL byte errorFlag _INIT(0); WLED_GLOBAL String messageHead, messageSub; diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index ca6b471fa..755a208c7 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -633,8 +633,12 @@ bool applyPreset(byte index, bool loadBri) errorFlag = readObjectFromFileUsingId("/presets.json", index, &temp) ? ERR_NONE : ERR_FS_PLOAD; serializeJson(temp, Serial); deserializeState(temp.as()); - //presetToApply = index; - return true; + if (!errorFlag) { + currentPreset = index; + isPreset = true; + return true; + } + return false; if (index == 255 || index == 0) { loadSettingsFromEEPROM(false);//load boot defaults @@ -688,6 +692,7 @@ void savePreset(byte index, bool persist, const char* pname, byte priority, Json if (saveobj.isNull()) { DEBUGFS_PRINTLN("Save current state"); serializeState(doc.to(), true); + currentPreset = index; } else { DEBUGFS_PRINTLN("Save custom"); sObj.set(saveobj); @@ -695,9 +700,8 @@ void savePreset(byte index, bool persist, const char* pname, byte priority, Json sObj["p"] = priority; if (pname) sObj["n"] = pname; - //serializeJson(doc, Serial); writeObjectToFileUsingId("/presets.json", index, &doc); - //Serial.println("Done!"); + presetsModifiedTime = now(); //unix time return; if (index > 16) return; @@ -736,6 +740,12 @@ void savePreset(byte index, bool persist, const char* pname, byte priority, Json isPreset = true; } +void deletePreset(byte index) { + StaticJsonDocument<24> empty; + writeObjectToFileUsingId("/presets.json", index, &empty); + presetsModifiedTime = now(); //unix time +} + void loadMacro(byte index, char* m) {