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);
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

View File

@ -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);
}

View File

@ -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<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;
}
@ -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

View File

@ -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);

View File

@ -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"));

View File

@ -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;

View File

@ -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<JsonObject>());
//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<JsonObject>(), 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)
{