diff --git a/platformio.ini b/platformio.ini index 825584ef3..7b53b2cd5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -178,6 +178,7 @@ lib_deps = #U8g2@~2.27.2 #For Dallas sensor uncomment following 2 lines #OneWire@~2.3.5 + #milesburton/DallasTemperature@^3.9.0 #For BME280 sensor uncomment following #BME280@~3.0.0 lib_ignore = @@ -227,6 +228,7 @@ platform = ${common.platform_wled_default} upload_speed = 921600 board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} +monitor_filters = esp8266_exception_decoder [env:heltec_wifi_kit_8] board = d1_mini @@ -373,7 +375,7 @@ build_flags = ${common.build_flags_esp8266} ${common.debug_flags} ${common.build [env:travis_esp32] extends = env:esp32dev -build_type = debug +; build_type = debug build_flags = ${common.build_flags_esp32} ${common.debug_flags} ${common.build_flags_all_features} # ------------------------------------------------------------------------------ diff --git a/wled00/FX.cpp b/wled00/FX.cpp index b38884888..f47a33798 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2732,7 +2732,7 @@ uint16_t WS2812FX::candle(bool multi) //max. flicker range controlled by intensity uint8_t valrange = SEGMENT.intensity; - uint8_t rndval = valrange >> 1; + uint8_t rndval = valrange >> 1; //max 127 //step (how much to move closer to target per frame) coarsely set by speed uint8_t speedFactor = 4; @@ -2769,9 +2769,9 @@ uint16_t WS2812FX::candle(bool multi) } if (newTarget) { - s_target = random8(rndval) + random8(rndval); + s_target = random8(rndval) + random8(rndval); //between 0 and rndval*2 -2 = 252 if (s_target < (rndval >> 1)) s_target = (rndval >> 1) + random8(rndval); - uint8_t offset = (255 - valrange) >> 1; + uint8_t offset = (255 - valrange); s_target += offset; uint8_t dif = (s_target > s) ? s_target - s : s - s_target; diff --git a/wled00/const.h b/wled00/const.h index a68101201..88ad49749 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -105,6 +105,15 @@ #define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed #define SEG_OPTION_TRANSITIONAL 7 +// WLED Error modes +#define ERR_NONE 0 // All good :) +#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?) +#define ERR_JSON 9 // JSON parsing failed (input too large?) +#define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?) +#define ERR_FS_QUOTA 11 // The FS is full or the maximum file size is reached +#define ERR_FS_PLOAD 12 // It was attempted to load a preset that does not exist +#define ERR_FS_GENERAL 19 // A general unspecified filesystem error occured + //Timer mode types #define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness #define NL_MODE_FADE 1 //Fade to target brightness gradually @@ -127,6 +136,7 @@ #define ABL_MILLIAMPS_DEFAULT 850; // auto lower brightness to stay close to milliampere limit + #define TOUCH_THRESHOLD 32 // limit to recognize a touch, higher value means more sensitive // Size of buffer for API JSON object (increase for more segments) diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 8b532de8e..18415357f 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -1432,7 +1432,8 @@ function populateSegments(s) function updateTrail(e, slidercol) { if (e==null) return; - var progress = e.value *100 /255; + var max = e.hasAttribute('max') ? e.attributes['max'].value : 255; + var progress = e.value * 100 / max; progress = parseInt(progress); var scol; switch (slidercol) { diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 12e2ba85c..b4252d345 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -24,15 +24,16 @@ void handleDDPPacket(e131_packet_t* p) { } } - uint32_t offsetLeds = htonl(p->channelOffset) /3; - uint16_t packetLeds = htons(p->dataLen) /3; + uint32_t start = htonl(p->channelOffset) /3; + start += DMXAddress /3; + uint16_t stop = start + htons(p->dataLen) /3; uint8_t* data = p->data; uint16_t c = 0; if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); - for (uint16_t i = offsetLeds; i < offsetLeds + packetLeds; i++) { + for (uint16_t i = start; i < stop; i++) { setRealtimePixel(i, data[c++], data[c++], data[c++], 0); } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 4c22152be..9fada5cd7 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -48,6 +48,12 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); //file.cpp bool handleFileRead(AsyncWebServerRequest*, String path); +bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content); +bool writeObjectToFile(const char* file, const char* key, JsonDocument* content); +bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest); +bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest); +void updateFSInfo(); +void closeFile(); //hue.cpp void handleHue(); @@ -85,8 +91,8 @@ void handleIR(); void deserializeSegment(JsonObject elem, byte it); bool deserializeState(JsonObject root); -void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id); -void serializeState(JsonObject root); +void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); +void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true); void serializeInfo(JsonObject root); void serveJson(AsyncWebServerRequest* request); bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); @@ -138,7 +144,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); @@ -193,10 +199,11 @@ void clearEEPROM(); void writeStringToEEPROM(uint16_t pos, char* str, uint16_t len); void readStringFromEEPROM(uint16_t pos, char* str, uint16_t len); void saveSettingsToEEPROM(); -void loadSettingsFromEEPROM(bool first); +void loadSettingsFromEEPROM(); void savedToPresets(); -bool applyPreset(byte index, bool loadBri = true); -void savePreset(byte index, bool persist = true); +bool applyPreset(byte index); +void savePreset(byte index, bool persist = true, const char* pname = nullptr, 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 eb0dcb931..65b1bbc8c 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -4,15 +4,368 @@ * Utility for SPIFFS filesystem */ -//filesystem #ifndef WLED_DISABLE_FILESYSTEM -#include + #ifdef ARDUINO_ARCH_ESP32 -#include "SPIFFS.h" -#endif -#include "SPIFFSEditor.h" +#include "esp_spiffs.h" //FS info bare IDF function until FS wrapper is available for ESP32 #endif +#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 + */ + +// There are no consecutive spaces longer than this in the file, so if more space is required, findSpace() can return false immediately +// Actual space may be lower +uint16_t knownLargestSpace = UINT16_MAX; + +File f; + +//wrapper to find out how long closing takes +void closeFile() { + DEBUGFS_PRINT(F("Close -> ")); + uint32_t s = millis(); + f.close(); + DEBUGFS_PRINTF("took %d ms\n", millis() - s); + doCloseFile = false; +} + +//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, bool fromStart = true) { + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINT("Find "); + DEBUGFS_PRINTLN(target); + uint32_t s = millis(); + #endif + + if (!f || !f.size()) return false; + size_t targetLen = strlen(target); + + size_t index = 0; + byte c; + uint16_t bufsize = 0, count = 0; + byte buf[FS_BUFSIZE]; + if (fromStart) f.seek(0); + + while (f.position() < f.size() -1) { + bufsize = f.read(buf, FS_BUFSIZE); + count = 0; + while (count < bufsize) { + if(buf[count] != target[index]) + index = 0; // reset index if any char does not match + + if(buf[count] == target[index]) { + if(++index >= targetLen) { // return true if all chars in the target match + f.seek((f.position() - bufsize) + count +1); + DEBUGFS_PRINTF("Found 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; +} + +//find empty spots in file stream in 256-byte blocks. +bool bufferedFindSpace(uint16_t targetLen, bool fromStart = true) { + + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTF("Find %d spaces\n", targetLen); + uint32_t s = millis(); + #endif + + if (knownLargestSpace < targetLen) { + DEBUGFS_PRINT(F("No match, KLS ")); + DEBUGFS_PRINTLN(knownLargestSpace); + return false; + } + + if (!f || !f.size()) return false; + + uint16_t index = 0; + uint16_t bufsize = 0, count = 0; + byte buf[FS_BUFSIZE]; + if (fromStart) f.seek(0); + + while (f.position() < f.size() -1) { + bufsize = f.read(buf, FS_BUFSIZE); + count = 0; + + while (count < bufsize) { + if(buf[count] == ' ') { + if(++index >= targetLen) { // return true if space long enough + if (fromStart) { + f.seek((f.position() - bufsize) + count +1 - targetLen); + knownLargestSpace = UINT16_MAX; //there may be larger spaces after, so we don't know + } + DEBUGFS_PRINTF("Found at pos %d, took %d ms", f.position(), millis() - s); + return true; + } + } else { + if (!fromStart) return false; + if (index) { + if (knownLargestSpace < index || knownLargestSpace == UINT16_MAX) knownLargestSpace = index; + index = 0; // reset index if not space + } + } + + count++; + } + } + DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); + return false; +} + +//find the closing bracket corresponding to the opening bracket at the file pos when calling this function +bool bufferedFindObjectEnd() { + #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[FS_BUFSIZE]; + + 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(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; + } + + if (knownLargestSpace < l) knownLargestSpace = l; +} + +bool appendObjectToFile(const char* key, JsonDocument* content, uint32_t s, uint32_t contentLen = 0) +{ + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTLN(F("Append")); + uint32_t s1 = millis(); + #endif + uint32_t pos = 0; + if (!f) return false; + + if (f.size() < 3) { + char init[10]; + strcpy_P(init, PSTR("{\"0\":{}}")); + f.print(init); + } + + if (content->isNull()) { + doCloseFile = true; + return true; //nothing to append + } + + //if there is enough empty space in file, insert there instead of appending + if (!contentLen) contentLen = measureJson(*content); + DEBUGFS_PRINTF("CLen %d\n", contentLen); + if (bufferedFindSpace(contentLen + strlen(key) + 1)) { + if (f.position() > 2) f.write(','); //add comma if not first object + f.print(key); + serializeJson(*content, f); + DEBUGFS_PRINTF("Inserted, took %d ms (total %d)", millis() - s1, millis() - s); + doCloseFile = true; + return true; + } + + //not enough space, append at end + + //permitted space for presets exceeded + updateFSInfo(); + + if (f.size() + 9000 > (fsBytesTotal - fsBytesUsed)) { //make sure there is enough space to at least copy the file once + errorFlag = ERR_FS_QUOTA; + doCloseFile = true; + return false; + } + + //check if last character in file is '}' (typical) + f.seek(1, SeekEnd); + if (f.read() == '}') pos = f.size() -1; + + if (pos == 0) //not found + { + DEBUGFS_PRINTLN("not }"); + while (bufferedFind("}", false)) //find last closing bracket in JSON if not last char + { + pos = f.position(); + } + } + DEBUGFS_PRINT("pos "); DEBUGFS_PRINTLN(pos); + if (pos > 2) + { + f.seek(pos, SeekSet); + f.write(','); + } else { //file content is not valid JSON object + f.seek(0, SeekSet); + f.write('{'); //start JSON + } + + f.print(key); + + //Append object + serializeJson(*content, f); + f.write('}'); + + doCloseFile = true; + DEBUGFS_PRINTF("Appended, took %d ms (total %d)", millis() - s1, millis() - s); + return true; +} + +bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content) +{ + char objKey[10]; + sprintf(objKey, "\"%d\":", id); + return writeObjectToFile(file, objKey, content); +} + +bool writeObjectToFile(const char* file, const char* key, JsonDocument* content) +{ + uint32_t s = 0; //timing + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTF("Write to %s with key %s >>>\n", file, key); + serializeJson(*content, Serial); DEBUGFS_PRINTLN(); + s = millis(); + #endif + + uint32_t pos = 0; + f = WLED_FS.open(file, "r+"); + if (!f && !WLED_FS.exists(file)) f = WLED_FS.open(file, "w+"); + if (!f) { + DEBUGFS_PRINTLN(F("Failed to open!")); + return false; + } + + if (!bufferedFind(key)) //key does not exist in file + { + return appendObjectToFile(key, content, s); + } + + //an object with this key already exists, replace or delete it + pos = f.position(); + //measure out end of old object + bufferedFindObjectEnd(); + uint32_t pos2 = f.position(); + + uint32_t oldLen = pos2 - pos; + DEBUGFS_PRINTF("Old obj len %d\n", oldLen); + + //Three cases: + //1. The new content is null, overwrite old obj with spaces + //2. The new content is smaller than the old, overwrite and fill diff with spaces + //3. The new content is larger than the old, but smaller than old + trailing spaces, overwrite with new + //4. The new content is larger than old + trailing spaces, delete old and append + + uint32_t contentLen = 0; + if (!content->isNull()) contentLen = measureJson(*content); + + if (contentLen && contentLen <= oldLen) { //replace and fill diff with spaces + DEBUGFS_PRINTLN(F("replace")); + f.seek(pos); + serializeJson(*content, f); + writeSpace(pos2 - f.position()); + } else if (contentLen && bufferedFindSpace(contentLen - oldLen, false)) { //enough leading spaces to replace + DEBUGFS_PRINTLN(F("replace (trailing)")); + f.seek(pos); + serializeJson(*content, f); + } else { + DEBUGFS_PRINTLN(F("delete")); + pos -= strlen(key); + if (pos > 3) pos--; //also delete leading comma if not first object + f.seek(pos); + writeSpace(pos2 - pos); + if (contentLen) return appendObjectToFile(key, content, s, contentLen); + } + + doCloseFile = true; + DEBUGFS_PRINTF("Replaced/deleted, took %d ms\n", millis() - s); + return true; +} + +bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest) +{ + char objKey[10]; + sprintf(objKey, "\"%d\":", id); + return readObjectFromFile(file, objKey, dest); +} + +bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest) +{ + if (doCloseFile) closeFile(); + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTF("Read from %s with key %s >>>\n", file, key); + uint32_t s = millis(); + #endif + f = WLED_FS.open(file, "r"); + if (!f) return false; + + if (!bufferedFind(key)) //key does not exist in file + { + f.close(); + dest->clear(); + DEBUGFS_PRINTLN(F("Obj not found.")); + return false; + } + + deserializeJson(*dest, f); + + f.close(); + DEBUGFS_PRINTF("Read, took %d ms\n", millis() - s); + return true; +} + +void updateFSInfo() { + #ifdef ARDUINO_ARCH_ESP32 + esp_spiffs_info(nullptr, &fsBytesTotal, &fsBytesUsed); + #else + FSInfo fsi; + WLED_FS.info(fsi); + fsBytesUsed = fsi.usedBytes; + fsBytesTotal = fsi.totalBytes; + #endif +} +#endif #if !defined WLED_DISABLE_FILESYSTEM && defined WLED_ENABLE_FS_SERVING //Un-comment any file types you need @@ -37,14 +390,15 @@ String getContentType(AsyncWebServerRequest* request, String filename){ bool handleFileRead(AsyncWebServerRequest* request, String path){ DEBUG_PRINTLN("FileRead: " + path); if(path.endsWith("/")) path += "index.htm"; + if(path.indexOf("sec") > -1) return false; String contentType = getContentType(request, path); - String pathWithGz = path + ".gz"; - if(SPIFFS.exists(pathWithGz)){ - request->send(SPIFFS, pathWithGz, contentType); + /*String pathWithGz = path + ".gz"; + if(WLED_FS.exists(pathWithGz)){ + request->send(WLED_FS, pathWithGz, contentType); return true; - } - if(SPIFFS.exists(path)) { - request->send(SPIFFS, path, contentType); + }*/ + if(WLED_FS.exists(path)) { + request->send(WLED_FS, path, contentType); return true; } return false; diff --git a/wled00/json.cpp b/wled00/json.cpp index 62c6a334d..ae1f6b455 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -147,9 +147,6 @@ bool deserializeState(JsonObject root) { strip.applyToAllSelected = false; bool stateResponse = root[F("v")] | false; - - int ps = root[F("ps")] | -1; - if (ps >= 0) applyPreset(ps); bri = root["bri"] | bri; @@ -192,7 +189,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; @@ -240,21 +237,35 @@ bool deserializeState(JsonObject root) colorUpdated(noNotification ? NOTIFIER_CALL_MODE_NO_NOTIFY : NOTIFIER_CALL_MODE_DIRECT_CHANGE); - //write presets to flash directly? - bool persistSaves = !(root[F("np")] | false); + int ps = root[F("psave")] | -1; + if (ps > 0) { + savePreset(ps, true, nullptr, root); + } else { + ps = root[F("pdel")] | -1; //deletion + if (ps > 0) { + deletePreset(ps); + } + ps = root[F("ps")] | -1; //load preset (clears state request!) + if (ps >= 0) applyPreset(ps); - ps = root[F("psave")] | -1; - if (ps >= 0) savePreset(ps, persistSaves); + //HTTP API commands + const char* httpwin = root["win"]; + if (httpwin) { + String apireq = "win&"; + apireq += httpwin; + handleSet(nullptr, apireq, false); + } + } return stateResponse; } -void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id) +void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset, bool segmentBounds) { root[F("id")] = id; root[F("start")] = seg.start; root[F("stop")] = seg.stop; - root[F("len")] = seg.stop - seg.start; + if (!forPreset) root[F("len")] = seg.stop - seg.start; root[F("grp")] = seg.grouping; root[F("spc")] = seg.spacing; root["on"] = seg.getOption(SEG_OPTION_ON); @@ -291,39 +302,42 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id) root[F("mi")] = seg.getOption(SEG_OPTION_MIRROR); } +void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segmentBounds) +{ + if (includeBri) { + root["on"] = (bri > 0); + root["bri"] = briLast; + root[F("transition")] = transitionDelay/100; //in 100ms + } -void serializeState(JsonObject root) -{ - if (errorFlag) root[F("error")] = errorFlag; - - root["on"] = (bri > 0); - root["bri"] = briLast; - root[F("transition")] = transitionDelay/100; //in 100ms + if (!forPreset) { + if (errorFlag) root[F("error")] = errorFlag; + + root[F("ps")] = currentPreset; + root[F("pss")] = savedPresets; + root[F("pl")] = (presetCyclingEnabled) ? 0: -1; + + usermods.addToJsonState(root); - root[F("ps")] = currentPreset; - root[F("pss")] = savedPresets; - root[F("pl")] = (presetCyclingEnabled) ? 0: -1; + //temporary for preset cycle + JsonObject ccnf = root.createNestedObject("ccnf"); + ccnf[F("min")] = presetCycleMin; + ccnf[F("max")] = presetCycleMax; + ccnf[F("time")] = presetCycleTime; - usermods.addToJsonState(root); + JsonObject nl = root.createNestedObject("nl"); + nl["on"] = nightlightActive; + nl[F("dur")] = nightlightDelayMins; + nl[F("fade")] = (nightlightMode > NL_MODE_SET); //deprecated + nl[F("mode")] = nightlightMode; + nl[F("tbri")] = nightlightTargetBri; - //temporary for preset cycle - JsonObject ccnf = root.createNestedObject("ccnf"); - ccnf[F("min")] = presetCycleMin; - ccnf[F("max")] = presetCycleMax; - ccnf[F("time")] = presetCycleTime; - - JsonObject nl = root.createNestedObject("nl"); - nl["on"] = nightlightActive; - nl[F("dur")] = nightlightDelayMins; - nl[F("fade")] = (nightlightMode > NL_MODE_SET); //deprecated - nl[F("mode")] = nightlightMode; - nl[F("tbri")] = nightlightTargetBri; - - JsonObject udpn = root.createNestedObject("udpn"); - udpn[F("send")] = notifyDirect; - udpn[F("recv")] = receiveNotifications; + JsonObject udpn = root.createNestedObject("udpn"); + udpn[F("send")] = notifyDirect; + udpn[F("recv")] = receiveNotifications; - root[F("lor")] = realtimeOverride; + root[F("lor")] = realtimeOverride; + } root[F("mainseg")] = strip.getMainSegmentId(); @@ -334,7 +348,7 @@ void serializeState(JsonObject root) if (sg.isActive()) { JsonObject seg0 = seg.createNestedObject(); - serializeSegment(seg0, sg, s); + serializeSegment(seg0, sg, s, forPreset, segmentBounds); } } } @@ -417,6 +431,11 @@ void serializeInfo(JsonObject root) wifi_info[F("rssi")] = qrssi; wifi_info[F("signal")] = getSignalQuality(qrssi); wifi_info[F("channel")] = WiFi.channel(); + + JsonObject fs_info = root.createNestedObject("fs"); + fs_info["u"] = fsBytesUsed / 1000; + fs_info["t"] = fsBytesTotal / 1000; + fs_info[F("pmt")] = presetsModifiedTime; #ifdef ARDUINO_ARCH_ESP32 #ifdef WLED_DEBUG @@ -444,6 +463,7 @@ void serializeInfo(JsonObject root) root[F("freeheap")] = ESP.getFreeHeap(); root[F("uptime")] = millis()/1000 + rolloverMillis*4294967; + usermods.addToJsonInfo(root); byte os = 0; diff --git a/wled00/led.cpp b/wled00/led.cpp index 395cd8493..d1f120eef 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -296,7 +296,7 @@ void handleNightlight() if (bri == 0 || nightlightActive) return; if (presetCycCurr < presetCycleMin || presetCycCurr > presetCycleMax) presetCycCurr = presetCycleMin; - applyPreset(presetCycCurr,presetApplyBri); + applyPreset(presetCycCurr); presetCycCurr++; if (presetCycCurr > 16) presetCycCurr = 1; colorUpdated(NOTIFIER_CALL_MODE_PRESET_CYCLE); diff --git a/wled00/set.cpp b/wled00/set.cpp index f87ea9338..f8ebd1898 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; @@ -486,15 +486,12 @@ bool handleSet(AsyncWebServerRequest *request, const String& req) if (v > 100) presetCycleTime = v/100; } - pos = req.indexOf(F("PA=")); //apply brightness from preset - if (pos > 0) presetApplyBri = (req.charAt(pos+3) != '0'); - pos = req.indexOf(F("PS=")); //saves current in preset - if (pos > 0) savePreset(getNumVal(&req, pos), persistSaves); + if (pos > 0) savePreset(getNumVal(&req, pos)); //apply preset if (updateVal(&req, "PL=", &presetCycCurr, presetCycleMin, presetCycleMax)) { - applyPreset(presetCycCurr, presetApplyBri); + applyPreset(presetCycCurr); } //set brightness @@ -746,6 +743,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 91f2943a4..8f2b74656 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -70,6 +70,10 @@ void WLED::loop() if (doReboot) reset(); + if (doCloseFile) { + closeFile(); + yield(); + } if (!realtimeMode || realtimeOverride) // block stuff if WARLS/Adalight is enabled { @@ -84,6 +88,12 @@ void WLED::loop() handleHue(); handleBlynk(); + + /*if (presetToApply) { + applyPreset(presetToApply); + presetToApply = 0; + }*/ + yield(); if (!offMode) @@ -169,10 +179,18 @@ void WLED::setup() DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); #ifndef WLED_DISABLE_FILESYSTEM + bool fsinit = false; + DEBUGFS_PRINTLN(F("Mount FS")); #ifdef ARDUINO_ARCH_ESP32 - SPIFFS.begin(true); + fsinit = WLED_FS.begin(true); + #else + fsinit = WLED_FS.begin(); #endif - SPIFFS.begin(); + if (!fsinit) { + DEBUGFS_PRINTLN(F("FS failed!")); + errorFlag = ERR_FS_BEGIN; + } + updateFSInfo(); #endif #if STATUSLED && STATUSLED != LEDPIN @@ -180,7 +198,7 @@ void WLED::setup() #endif DEBUG_PRINTLN(F("Load EEPROM")); - loadSettingsFromEEPROM(true); + loadSettingsFromEEPROM(); beginStrip(); userSetup(); usermods.setup(); @@ -240,8 +258,7 @@ void WLED::beginStrip() pinMode(BTNPIN, INPUT_PULLUP); #endif - if (bootPreset > 0) - applyPreset(bootPreset, turnOnAtBoot); + if (bootPreset > 0) applyPreset(bootPreset); colorUpdated(NOTIFIER_CALL_MODE_INIT); // init relay pin diff --git a/wled00/wled.h b/wled00/wled.h index c6f80731f..605dc224c 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,11 +8,11 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2010020 +#define VERSION 2010030 -// ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). +// ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. -// ESP8266-01 (black) has 1MB flash and can thus fit the whole program. Use 1M(64K SPIFFS). +// ESP8266-01 (black) has 1MB flash and can thus fit the whole program, although OTA update is not possible. Use 1M(128K SPIFFS). // Uncomment some of the following lines to disable features to compile for ESP8266-01 (max flash size 434kB): // Alternatively, with platformio pass your chosen flags to your custom build target in platformio.ini.override @@ -35,19 +35,23 @@ #define WLED_ENABLE_WEBSOCKETS #endif -#define WLED_DISABLE_FILESYSTEM // SPIFFS is not used by any WLED feature yet -//#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_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 // to toggle usb serial debug (un)comment the following line //#define WLED_DEBUG +// filesystem specific debugging +#define WLED_DEBUG_FS + // Library inclusions. #include #ifdef ESP8266 #include #include #include + #include extern "C" { #include @@ -121,25 +125,18 @@ #include #endif +//Filesystem to use for preset and config files. SPIFFS or LittleFS on ESP8266, SPIFFS only on ESP32 +#ifdef ESP8266 + #define WLED_FS LittleFS +#else + #define WLED_FS SPIFFS +#endif + // remove flicker because PWM signal of RGB channels can become out of phase (part of core as of Arduino core v2.7.0) //#if defined(WLED_USE_ANALOG_LEDS) && defined(ESP8266) // #include "src/dependencies/arduino/core_esp8266_waveform.h" //#endif -// enable additional debug output -#ifdef WLED_DEBUG - #ifndef ESP8266 - #include - #endif - #define DEBUG_PRINT(x) Serial.print(x) - #define DEBUG_PRINTLN(x) Serial.println(x) - #define DEBUG_PRINTF(x) Serial.printf(x) -#else - #define DEBUG_PRINT(x) - #define DEBUG_PRINTLN(x) - #define DEBUG_PRINTF(x) -#endif - // GLOBAL VARIABLES // both declared and defined in header (solution from http://www.keil.com/support/docs/1868.htm) // @@ -160,8 +157,8 @@ #endif // Global Variable definitions -WLED_GLOBAL char versionString[] _INIT("0.10.2"); -#define WLED_CODENAME "Fumikiri" +WLED_GLOBAL char versionString[] _INIT("0.11.0p"); +#define WLED_CODENAME "Mirai" // AP and OTA default passwords (for maximum security change them!) WLED_GLOBAL char apPass[65] _INIT(DEFAULT_AP_PASS); @@ -170,8 +167,12 @@ WLED_GLOBAL char otaPass[33] _INIT(DEFAULT_OTA_PASS); // Hardware CONFIG (only changeble HERE, not at runtime) // LED strip pin, button pin and IR pin changeable in NpbWrapper.h! +//WLED_GLOBAL byte presetToApply _INIT(0); + +#if AUXPIN >= 0 WLED_GLOBAL byte auxDefaultState _INIT(0); // 0: input 1: high 2: low WLED_GLOBAL byte auxTriggeredState _INIT(0); // 0: input 1: high 2: low +#endif WLED_GLOBAL char ntpServerName[33] _INIT("0.wled.pool.ntp.org"); // NTP server to use // WiFi CONFIG (all these can be changed via web UI, no need to set them here) @@ -426,7 +427,6 @@ WLED_GLOBAL byte presetCycleMin _INIT(1), presetCycleMax _INIT(5); WLED_GLOBAL uint16_t presetCycleTime _INIT(12); WLED_GLOBAL unsigned long presetCycledTime _INIT(0); WLED_GLOBAL byte presetCycCurr _INIT(presetCycleMin); -WLED_GLOBAL bool presetApplyBri _INIT(true); WLED_GLOBAL bool saveCurrPresetCycConf _INIT(false); // realtime @@ -473,9 +473,16 @@ WLED_GLOBAL uint16_t rolloverMillis _INIT(0); WLED_GLOBAL char* obuf; WLED_GLOBAL uint16_t olen _INIT(0); +// General filesystem +WLED_GLOBAL size_t fsBytesUsed _INIT(0); +WLED_GLOBAL size_t fsBytesTotal _INIT(0); +WLED_GLOBAL unsigned long presetsModifiedTime _INIT(0L); +WLED_GLOBAL JsonDocument* fileDoc; +WLED_GLOBAL bool doCloseFile _INIT(false); + // 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 byte errorFlag _INIT(0); @@ -513,6 +520,30 @@ WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager()); WLED_GLOBAL bool ledStatusState _INIT(0); // the current LED state #endif +// enable additional debug output +#ifdef WLED_DEBUG + #ifndef ESP8266 + #include + #endif + #define DEBUG_PRINT(x) Serial.print(x) + #define DEBUG_PRINTLN(x) Serial.println(x) + #define DEBUG_PRINTF(x...) Serial.printf(x) +#else + #define DEBUG_PRINT(x) + #define DEBUG_PRINTLN(x) + #define DEBUG_PRINTF(x) +#endif + +#ifdef WLED_DEBUG_FS + #define DEBUGFS_PRINT(x) Serial.print(x) + #define DEBUGFS_PRINTLN(x) Serial.println(x) + #define DEBUGFS_PRINTF(x...) Serial.printf(x) +#else + #define DEBUGFS_PRINT(x) + #define DEBUGFS_PRINTLN(x) + #define DEBUGFS_PRINTF(x) +#endif + // debug macro variable definitions #ifdef WLED_DEBUG WLED_GLOBAL unsigned long debugTime _INIT(0); @@ -521,7 +552,6 @@ WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager()); WLED_GLOBAL int loops _INIT(0); #endif - #define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) #define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) #define WLED_MQTT_CONNECTED (mqtt != nullptr && mqtt->connected()) diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 40c37ddf6..1e918a12c 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -34,7 +34,7 @@ void commit() { - if (!EEPROM.commit()) errorFlag = 2; + if (!EEPROM.commit()) errorFlag = ERR_EEP_COMMIT; } /* @@ -249,7 +249,7 @@ void saveSettingsToEEPROM() EEPROM.write(2207, (presetCycleTime >> 8) & 0xFF); EEPROM.write(2208, presetCycleMin); EEPROM.write(2209, presetCycleMax); - EEPROM.write(2210, presetApplyBri); + // was EEPROM.write(2210, presetApplyBri); // was EEPROM.write(2211, presetApplyCol); // was EEPROM.write(2212, presetApplyFx); saveCurrPresetCycConf = false; @@ -295,7 +295,7 @@ void saveSettingsToEEPROM() /* * Read all configuration from flash */ -void loadSettingsFromEEPROM(bool first) +void loadSettingsFromEEPROM() { if (EEPROM.read(233) != 233) //first boot/reset to default { @@ -343,7 +343,7 @@ void loadSettingsFromEEPROM(bool first) staticSubnet[3] = EEPROM.read(245); briS = EEPROM.read(249); bri = briS; - if (!EEPROM.read(369) && first) + if (!EEPROM.read(369)) { bri = 0; briLast = briS; } @@ -559,7 +559,7 @@ void loadSettingsFromEEPROM(bool first) if (lastEEPROMversion < 21) presetCycleTime /= 100; //was stored in ms, now is in tenths of a second presetCycleMin = EEPROM.read(2208); presetCycleMax = EEPROM.read(2209); - presetApplyBri = EEPROM.read(2210); + //was presetApplyBri = EEPROM.read(2210); //was presetApplyCol = EEPROM.read(2211); //was presetApplyFx = EEPROM.read(2212); } @@ -627,9 +627,31 @@ void savedToPresets() } } -bool applyPreset(byte index, bool loadBri) +bool applyPreset(byte index) { - if (index == 255 || index == 0) + if (fileDoc) { + errorFlag = readObjectFromFileUsingId("/presets.json", index, fileDoc) ? ERR_NONE : ERR_FS_PLOAD; + #ifdef WLED_DEBUG_FS + serializeJson(*fileDoc, Serial); + #endif + deserializeState(fileDoc->as()); + } else { + WLED_DEBUG_FS(F("Make read buf")); + DynamicJsonDocument fDoc(JSON_BUFFER_SIZE); + errorFlag = readObjectFromFileUsingId("/presets.json", index, &fDoc) ? ERR_NONE : ERR_FS_PLOAD; + #ifdef WLED_DEBUG_FS + serializeJson(fDoc, Serial); + #endif + deserializeState(fDoc.as()); + } + + if (!errorFlag) { + currentPreset = index; + isPreset = true; + return true; + } + return false; + /*if (index == 255 || index == 0) { loadSettingsFromEEPROM(false);//load boot defaults return true; @@ -671,12 +693,41 @@ bool applyPreset(byte index, bool loadBri) } currentPreset = index; isPreset = true; - return true; + return true;*/ } -void savePreset(byte index, bool persist) +void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj) { - if (index > 16) return; + bool docAlloc = fileDoc; + JsonObject sObj = saveobj; + + if (!docAlloc) { + DEBUGFS_PRINTLN(F("Allocating saving buffer")); + fileDoc = new DynamicJsonDocument(JSON_BUFFER_SIZE); + sObj = fileDoc->to(); + if (pname) sObj["n"] = pname; + } else { + DEBUGFS_PRINTLN(F("Reuse recv buffer")); + sObj.remove(F("psave")); + sObj.remove(F("v")); + } + + if (!sObj["o"]) { + DEBUGFS_PRINTLN(F("Save current state")); + serializeState(sObj, true, sObj["ib"], sObj["sb"]); + currentPreset = index; + } + sObj.remove("o"); + sObj.remove("ib"); + sObj.remove("sb"); + sObj.remove(F("error")); + + writeObjectToFileUsingId("/presets.json", index, fileDoc); + if (!docAlloc) delete fileDoc; + presetsModifiedTime = now(); //unix time + updateFSInfo(); + + /*if (index > 16) return; if (index < 1) {saveSettingsToEEPROM();return;} uint16_t i = 380 + index*20;//min400 @@ -709,7 +760,14 @@ void savePreset(byte index, bool persist) if (persist) commit(); savedToPresets(); currentPreset = index; - isPreset = true; + isPreset = true;*/ +} + +void deletePreset(byte index) { + StaticJsonDocument<24> empty; + writeObjectToFileUsingId("/presets.json", index, &empty); + presetsModifiedTime = now(); //unix time + updateFSInfo(); } diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 072ead158..74b2aa372 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -85,7 +85,9 @@ void initServer() if (error || root.isNull()) { request->send(400, "application/json", F("{\"error\":9}")); return; } + fileDoc = &jsonBuffer; verboseResponse = deserializeState(root); + fileDoc = nullptr; } if (verboseResponse) { //if JSON contains "v" serveJson(request); return; @@ -124,13 +126,13 @@ void initServer() if (!otaLock){ #if !defined WLED_DISABLE_FILESYSTEM && defined WLED_ENABLE_FS_EDITOR #ifdef ARDUINO_ARCH_ESP32 - server.addHandler(new SPIFFSEditor(SPIFFS));//http_username,http_password)); + server.addHandler(new SPIFFSEditor(WLED_FS));//http_username,http_password)); #else - server.addHandler(new SPIFFSEditor());//http_username,http_password)); + server.addHandler(new SPIFFSEditor("","",WLED_FS));//http_username,http_password)); #endif #else server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, "Not implemented", F("The SPIFFS editor is disabled in this build."), 254); + serveMessage(request, 501, "Not implemented", F("The FS editor is disabled in this build."), 254); }); #endif //init ota page