Working deletion

Added HTTP API support to JSON API
This commit is contained in:
cschwinne 2020-10-03 00:29:36 +02:00
parent 8a713b2bbb
commit 606cd18dc4
7 changed files with 153 additions and 43 deletions

View File

@ -142,7 +142,7 @@ void _drawOverlayCronixie();
void _setRandomColor(bool _sec,bool fromButton=false); void _setRandomColor(bool _sec,bool fromButton=false);
bool isAsterisksOnly(const char* str, byte maxLen); bool isAsterisksOnly(const char* str, byte maxLen);
void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); 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); int getNumVal(const String* req, uint16_t pos);
bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); 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 loadSettingsFromEEPROM(bool first);
void savedToPresets(); void savedToPresets();
bool applyPreset(byte index, bool loadBri = true); 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 loadMacro(byte index, char* m);
void applyMacro(byte index); void applyMacro(byte index);
void saveMacro(byte index, const String& mc, bool persist = true); //only commit on single save, not in settings void saveMacro(byte index, const String& mc, bool persist = true); //only commit on single save, not in settings

View File

@ -6,6 +6,21 @@
#ifndef WLED_DISABLE_FILESYSTEM #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. //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 //Significantly faster, f.find(key) can take SECONDS for multi-kB files
bool bufferedFind(const char *target, File f) { bool bufferedFind(const char *target, File f) {
@ -21,11 +36,11 @@ bool bufferedFind(const char *target, File f) {
size_t index = 0; size_t index = 0;
byte c; byte c;
uint16_t bufsize = 0, count = 0; uint16_t bufsize = 0, count = 0;
byte buf[256]; byte buf[FS_BUFSIZE];
f.seek(0); f.seek(0);
while (f.position() < f.size() -1) { while (f.position() < f.size() -1) {
bufsize = f.read(buf, 256); bufsize = f.read(buf, FS_BUFSIZE);
count = 0; count = 0;
while (count < bufsize) { while (count < bufsize) {
if(buf[count] != target[index]) if(buf[count] != target[index])
@ -56,11 +71,11 @@ bool bufferedFindSpace(uint16_t targetLen, File f) {
uint16_t index = 0; uint16_t index = 0;
uint16_t bufsize = 0, count = 0; uint16_t bufsize = 0, count = 0;
byte buf[256]; byte buf[FS_BUFSIZE];
f.seek(0); f.seek(0);
while (f.position() < f.size() -1) { while (f.position() < f.size() -1) {
bufsize = f.read(buf, 256); bufsize = f.read(buf, FS_BUFSIZE);
count = 0; count = 0;
while (count < bufsize) { while (count < bufsize) {
@ -81,15 +96,71 @@ bool bufferedFindSpace(uint16_t targetLen, File f) {
return false; 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) bool appendObjectToFile(File f, const char* key, JsonDocument* content, uint32_t s)
{ {
#ifdef WLED_DEBUG_FS #ifdef WLED_DEBUG_FS
DEBUGFS_PRINTLN("Append"); DEBUGFS_PRINTLN(F("Append"));
uint32_t s1 = millis(); uint32_t s1 = millis();
#endif #endif
uint32_t pos = 0; uint32_t pos = 0;
if (!f) return false; 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 //if there is enough empty space in file, insert there instead of appending
uint32_t contentLen = measureJson(*content); 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) bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content)
{ {
char objKey[10]; char objKey[10];
sprintf(objKey, "\"%ld\":", id); sprintf(objKey, "\"%d\":", id);
writeObjectToFile(file, objKey, content); 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+"); File f = WLED_FS.open(file, "r+");
if (!f && !WLED_FS.exists(file)) f = WLED_FS.open(file, "w+"); if (!f && !WLED_FS.exists(file)) f = WLED_FS.open(file, "w+");
if (!f) { if (!f) {
DEBUGFS_PRINTLN("Failed to open!"); DEBUGFS_PRINTLN(F("Failed to open!"));
return false; return false;
} }
@ -166,34 +237,24 @@ bool writeObjectToFile(const char* file, const char* key, JsonDocument* content)
//exists //exists
pos = f.position(); pos = f.position();
//measure out end of old object //measure out end of old object
StaticJsonDocument<1024> doc; bufferedFindObjectEnd(f);
deserializeJson(doc, f);
uint32_t pos2 = f.position(); uint32_t pos2 = f.position();
uint32_t oldLen = pos2 - pos; uint32_t oldLen = pos2 - pos;
#ifdef WLED_DEBUG_FS DEBUGFS_PRINTF("Old obj len %d\n", oldLen);
DEBUGFS_PRINTF("Old obj len %d >>> ", oldLen);
serializeJson(doc, Serial);
DEBUGFS_PRINTLN();
#endif
if (!content->isNull() && measureJson(*content) <= oldLen) //replace if (!content->isNull() && measureJson(*content) <= oldLen) //replace
{ {
DEBUGFS_PRINTLN("replace"); DEBUGFS_PRINTLN(F("replace"));
f.seek(pos); f.seek(pos);
serializeJson(*content, f); serializeJson(*content, f);
//pad rest writeSpace(f, pos2 - f.position());
for (uint32_t i = f.position(); i < pos2; i++) {
f.write(' ');
}
} else { //delete } else { //delete
DEBUGFS_PRINTLN("delete"); DEBUGFS_PRINTLN(F("delete"));
pos -= strlen(key); pos -= strlen(key);
if (pos > 3) pos--; //also delete leading comma if not first object if (pos > 3) pos--; //also delete leading comma if not first object
f.seek(pos); f.seek(pos);
for (uint32_t i = pos; i < pos2; i++) { writeSpace(f, pos2 - pos);
f.write(' ');
}
if (!content->isNull()) return appendObjectToFile(f, key, content, s); if (!content->isNull()) return appendObjectToFile(f, key, content, s);
} }
f.close(); 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) bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest)
{ {
char objKey[10]; char objKey[10];
sprintf(objKey, "\"%ld\":", id); sprintf(objKey, "\"%d\":", id);
readObjectFromFile(file, objKey, dest); readObjectFromFile(file, objKey, dest);
} }

View File

@ -1,5 +1,9 @@
#include "wled.h" #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 * JSON API (De)serialization
*/ */
@ -148,6 +152,14 @@ bool deserializeState(JsonObject root)
strip.applyToAllSelected = false; strip.applyToAllSelected = false;
bool stateResponse = root[F("v")] | 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; int ps = root[F("ps")] | -1;
if (ps >= 0) applyPreset(ps); 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 bool noNotification = udpn[F("nn")]; //send no notification just for this request
int timein = root[F("time")] | -1; int timein = root[F("time")] | -1;
if (timein != -1) setTime(timein); if (timein != -1 && millis() - ntpLastSyncTime > 50000000L) setTime(timein);
doReboot = root[F("rb")] | doReboot; doReboot = root[F("rb")] | doReboot;
realtimeOverride = root[F("lor")] | realtimeOverride; realtimeOverride = root[F("lor")] | realtimeOverride;
@ -244,7 +256,14 @@ bool deserializeState(JsonObject root)
bool persistSaves = !(root[F("np")] | false); bool persistSaves = !(root[F("np")] | false);
ps = root[F("psave")] | -1; ps = root[F("psave")] | -1;
if (ps >= 0) savePreset(ps, persistSaves, root["n"], root["p"] | 50, root["o"].as<JsonObject>()); if (ps > 0) {
savePreset(ps, persistSaves, root["n"], root["p"] | 50, root["o"].as<JsonObject>());
} else {
ps = root[F("pdel")] | -1; //deletion
if (ps > 0) {
deletePreset(ps);
}
}
return stateResponse; return stateResponse;
} }
@ -422,10 +441,18 @@ void serializeInfo(JsonObject root)
wifi_info[F("channel")] = WiFi.channel(); wifi_info[F("channel")] = WiFi.channel();
JsonObject fs_info = root.createNestedObject("fs"); JsonObject fs_info = root.createNestedObject("fs");
FSInfo fsi; #ifdef ARDUINO_ARCH_ESP32
WLED_FS.info(fsi); size_t used, total;
fs_info["u"] = fsi.usedBytes; esp_spiffs_info(nullptr, &total, &used);
fs_info["t"] = fsi.totalBytes; 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 ARDUINO_ARCH_ESP32
#ifdef WLED_DEBUG #ifdef WLED_DEBUG

View File

@ -375,7 +375,7 @@ bool updateVal(const String* req, const char* key, byte* val, byte minv, byte ma
//HTTP API request parser //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; 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 //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 //internal call, does not send XML response
pos = req.indexOf(F("IN")); pos = req.indexOf(F("IN"));
if (pos < 1) XML_response(request); if (pos < 1) XML_response(request);

View File

@ -177,10 +177,17 @@ void WLED::setup()
DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap());
#ifndef WLED_DISABLE_FILESYSTEM #ifndef WLED_DISABLE_FILESYSTEM
bool fsinit = false;
DEBUGFS_PRINTLN(F("Mount FS"));
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
WLED_FS.begin(true); fsinit = WLED_FS.begin(true);
#else
fsinit = WLED_FS.begin();
#endif #endif
WLED_FS.begin(); if (!fsinit) {
DEBUGFS_PRINTLN(F("FS failed!"));
errorFlag = ERR_FS_BEGIN;
}
#endif #endif
DEBUG_PRINTLN(F("Load EEPROM")); DEBUG_PRINTLN(F("Load EEPROM"));

View File

@ -36,8 +36,8 @@
#endif #endif
//#define WLED_DISABLE_FILESYSTEM // FS used by new preset functionality //#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_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_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 // to toggle usb serial debug (un)comment the following line
//#define WLED_DEBUG //#define WLED_DEBUG
@ -474,9 +474,11 @@ WLED_GLOBAL uint16_t olen _INIT(0);
// presets // presets
WLED_GLOBAL uint16_t savedPresets _INIT(0); 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 bool isPreset _INIT(false);
WLED_GLOBAL unsigned long presetsModifiedTime _INIT(0L);
WLED_GLOBAL byte errorFlag _INIT(0); WLED_GLOBAL byte errorFlag _INIT(0);
WLED_GLOBAL String messageHead, messageSub; WLED_GLOBAL String messageHead, messageSub;

View File

@ -633,8 +633,12 @@ bool applyPreset(byte index, bool loadBri)
errorFlag = readObjectFromFileUsingId("/presets.json", index, &temp) ? ERR_NONE : ERR_FS_PLOAD; errorFlag = readObjectFromFileUsingId("/presets.json", index, &temp) ? ERR_NONE : ERR_FS_PLOAD;
serializeJson(temp, Serial); serializeJson(temp, Serial);
deserializeState(temp.as<JsonObject>()); deserializeState(temp.as<JsonObject>());
//presetToApply = index; if (!errorFlag) {
return true; currentPreset = index;
isPreset = true;
return true;
}
return false;
if (index == 255 || index == 0) if (index == 255 || index == 0)
{ {
loadSettingsFromEEPROM(false);//load boot defaults loadSettingsFromEEPROM(false);//load boot defaults
@ -688,6 +692,7 @@ void savePreset(byte index, bool persist, const char* pname, byte priority, Json
if (saveobj.isNull()) { if (saveobj.isNull()) {
DEBUGFS_PRINTLN("Save current state"); DEBUGFS_PRINTLN("Save current state");
serializeState(doc.to<JsonObject>(), true); serializeState(doc.to<JsonObject>(), true);
currentPreset = index;
} else { } else {
DEBUGFS_PRINTLN("Save custom"); DEBUGFS_PRINTLN("Save custom");
sObj.set(saveobj); sObj.set(saveobj);
@ -695,9 +700,8 @@ void savePreset(byte index, bool persist, const char* pname, byte priority, Json
sObj["p"] = priority; sObj["p"] = priority;
if (pname) sObj["n"] = pname; if (pname) sObj["n"] = pname;
//serializeJson(doc, Serial);
writeObjectToFileUsingId("/presets.json", index, &doc); writeObjectToFileUsingId("/presets.json", index, &doc);
//Serial.println("Done!"); presetsModifiedTime = now(); //unix time
return; return;
if (index > 16) return; if (index > 16) return;
@ -736,6 +740,12 @@ void savePreset(byte index, bool persist, const char* pname, byte priority, Json
isPreset = true; isPreset = true;
} }
void deletePreset(byte index) {
StaticJsonDocument<24> empty;
writeObjectToFileUsingId("/presets.json", index, &empty);
presetsModifiedTime = now(); //unix time
}
void loadMacro(byte index, char* m) void loadMacro(byte index, char* m)
{ {