Merge branch 'master' into dev

This commit is contained in:
Blaz Kristan 2021-06-25 15:52:59 +02:00
commit 2424df0d18
23 changed files with 1045 additions and 986 deletions

View File

@ -2,6 +2,20 @@
### Builds after release 0.12.0 ### Builds after release 0.12.0
#### Build 2106250
- Fixed preset only disabling on second effect/color change
#### Build 2106241
- BREAKING: Added ability for usermods to force a config save if config incomplete. `readFromConfig()` needs to return a `bool` to indicate if the config is complete
- Updated usermods implementing `readFromConfig()`
- Auto-create segments based on configured busses
#### Build 2106200
- Added 2 Ethernet boards and split Ethernet configs into separate file
#### Build 2106180 #### Build 2106180
- Fixed DOS on Chrome tab restore causing reboot - Fixed DOS on Chrome tab restore causing reboot

View File

@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x200000,
app1, app, ota_1, 0x210000,0x200000,
spiffs, data, spiffs, 0x410000,0xBE0000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x200000
5 app1 app ota_1 0x210000 0x200000
6 spiffs data spiffs 0x410000 0xBE0000

View File

@ -427,7 +427,7 @@ class Animated_Staircase : public Usermod {
/* /*
* Reads the configuration to internal flash memory before setup() is called. * Reads the configuration to internal flash memory before setup() is called.
*/ */
void readFromConfig(JsonObject& root) { bool readFromConfig(JsonObject& root) {
bool oldUseUSSensorTop = useUSSensorTop; bool oldUseUSSensorTop = useUSSensorTop;
bool oldUseUSSensorBottom = useUSSensorBottom; bool oldUseUSSensorBottom = useUSSensorBottom;
int8_t oldTopAPin = topPIRorTriggerPin; int8_t oldTopAPin = topPIRorTriggerPin;
@ -435,6 +435,8 @@ class Animated_Staircase : public Usermod {
int8_t oldBottomAPin = bottomPIRorTriggerPin; int8_t oldBottomAPin = bottomPIRorTriggerPin;
int8_t oldBottomBPin = bottomEchoPin; int8_t oldBottomBPin = bottomEchoPin;
bool configComplete = true;
JsonObject staircase = root[FPSTR(_name)]; JsonObject staircase = root[FPSTR(_name)];
if (!staircase.isNull()) { if (!staircase.isNull()) {
if (staircase[FPSTR(_enabled)].is<bool>()) { if (staircase[FPSTR(_enabled)].is<bool>()) {
@ -468,6 +470,7 @@ class Animated_Staircase : public Usermod {
bottomMaxDist = min(150,max(30,staircase[FPSTR(_bottomEchoCm)].as<int>())); // max distance ~1.5m (a lag of 9ms may be expected) bottomMaxDist = min(150,max(30,staircase[FPSTR(_bottomEchoCm)].as<int>())); // max distance ~1.5m (a lag of 9ms may be expected)
} else { } else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)")); DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
configComplete = false;
} }
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
@ -490,6 +493,7 @@ class Animated_Staircase : public Usermod {
} }
if (changed) setup(); if (changed) setup();
} }
return configComplete;
} }
/* /*

View File

@ -133,11 +133,28 @@ class MyExampleUsermod : public Usermod {
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :) * If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*
* Return true in case your config was complete, or false if you'd like WLED to save your defaults to disk
*
* This function is guaranteed to be called on boot, but could also be called every time settings are updated
*/ */
void readFromConfig(JsonObject& root) bool readFromConfig(JsonObject& root)
{ {
JsonObject top = root["top"]; userVar0 = 42; //set your variables to their boot default value (this can also be done when declaring the variable)
userVar0 = top["great"] | 42; //The value right of the pipe "|" is the default value in case your setting was not present in cfg.json (e.g. first boot)
JsonObject top = root["exampleUsermod"];
if (!top.isNull()) {
bool configComplete = true;
//check if value is there
if (top.containsKey("great")) {
//convert value to the correct type
userVar0 = top["great"].as<int>();
} else configComplete = false;
if (configComplete) return true;
}
return false;
} }

View File

@ -149,11 +149,13 @@ Delay <input type=\"number\" min=\"5\" max=\"300\" value=\"";
/** /**
* restore the changeable values * restore the changeable values
*/ */
void readFromConfig(JsonObject &root) bool readFromConfig(JsonObject &root)
{ {
JsonObject top = root["FixUnreachableNetServices"]; JsonObject top = root["FixUnreachableNetServices"];
if (top.isNull()) return false;
m_pingDelayMs = top["PingDelayMs"] | m_pingDelayMs; m_pingDelayMs = top["PingDelayMs"] | m_pingDelayMs;
m_pingDelayMs = max(5000UL, min(18000000UL, m_pingDelayMs)); m_pingDelayMs = max(5000UL, min(18000000UL, m_pingDelayMs));
return true;
} }
/** /**

View File

@ -320,13 +320,13 @@ public:
* restore the changeable values * restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*/ */
void readFromConfig(JsonObject &root) bool readFromConfig(JsonObject &root)
{ {
bool oldEnabled = enabled; bool oldEnabled = enabled;
int8_t oldPin = PIRsensorPin; int8_t oldPin = PIRsensorPin;
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) return; if (top.isNull()) return false;
if (top["pin"] != nullptr) { if (top["pin"] != nullptr) {
PIRsensorPin = min(39,max(-1,top["pin"].as<int>())); // check bounds PIRsensorPin = min(39,max(-1,top["pin"].as<int>())); // check bounds
@ -398,6 +398,8 @@ public:
DEBUG_PRINTLN(F("PIR config (re)loaded.")); DEBUG_PRINTLN(F("PIR config (re)loaded."));
} }
} }
return true;
} }
/** /**

View File

@ -169,7 +169,7 @@ public:
/** /**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*/ */
void readFromConfig(JsonObject &root) bool readFromConfig(JsonObject &root)
{ {
// we look for JSON object. // we look for JSON object.
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
@ -196,7 +196,9 @@ public:
else else
{ {
DEBUG_PRINTLN(F("No config found. (Using defaults.)")); DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
return false;
} }
return true;
} }
}; };

View File

@ -234,12 +234,13 @@ class UsermodTemperature : public Usermod {
/** /**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*/ */
void readFromConfig(JsonObject &root) { bool readFromConfig(JsonObject &root) {
// we look for JSON object: {"Temperature": {"pin": 0, "degC": true}} // we look for JSON object: {"Temperature": {"pin": 0, "degC": true}}
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
int8_t newTemperaturePin = temperaturePin; int8_t newTemperaturePin = temperaturePin;
if (top.isNull()) return; if (top.isNull()) return true;
bool configComplete = true;
if (top["pin"] != nullptr) { if (top["pin"] != nullptr) {
if (top[FPSTR(_enabled)].is<bool>()) { if (top[FPSTR(_enabled)].is<bool>()) {
disabled = !top[FPSTR(_enabled)].as<bool>(); disabled = !top[FPSTR(_enabled)].as<bool>();
@ -282,6 +283,7 @@ class UsermodTemperature : public Usermod {
DEBUG_PRINTLN(F("Temperature config loaded.")); DEBUG_PRINTLN(F("Temperature config loaded."));
} else { } else {
DEBUG_PRINTLN(F("Temperature config re-loaded.")); DEBUG_PRINTLN(F("Temperature config re-loaded."));
configComplete = false;
// changing paramters from settings page // changing paramters from settings page
if (newTemperaturePin != temperaturePin) { if (newTemperaturePin != temperaturePin) {
// deallocate pin and release memory // deallocate pin and release memory
@ -292,6 +294,7 @@ class UsermodTemperature : public Usermod {
setup(); setup();
} }
} }
return configComplete;
} }
uint16_t getId() uint16_t getId()

View File

@ -348,11 +348,11 @@ class MultiRelay : public Usermod {
* restore the changeable values * restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*/ */
void readFromConfig(JsonObject &root) { bool readFromConfig(JsonObject &root) {
int8_t oldPin[MULTI_RELAY_MAX_RELAYS]; int8_t oldPin[MULTI_RELAY_MAX_RELAYS];
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) return; if (top.isNull()) return false;
if (top[FPSTR(_enabled)] != nullptr) { if (top[FPSTR(_enabled)] != nullptr) {
if (top[FPSTR(_enabled)].is<bool>()) { if (top[FPSTR(_enabled)].is<bool>()) {
@ -413,6 +413,7 @@ class MultiRelay : public Usermod {
} }
DEBUG_PRINTLN(F("MultiRelay config (re)loaded.")); DEBUG_PRINTLN(F("MultiRelay config (re)loaded."));
} }
return true;
} }
/** /**

View File

@ -82,18 +82,6 @@ class StairwayWipeUsermod : public Usermod {
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
} }
void addToConfig(JsonObject& root)
{
JsonObject top = root.createNestedObject("exampleUsermod");
top["great"] = userVar0; //save this var persistently whenever settings are saved
}
void readFromConfig(JsonObject& root)
{
JsonObject top = root["top"];
userVar0 = top["great"] | 42; //The value right of the pipe "|" is the default value in case your setting was not present in cfg.json (e.g. first boot)
}
uint16_t getId() uint16_t getId()
{ {
return USERMOD_ID_EXAMPLE; return USERMOD_ID_EXAMPLE;

View File

@ -1,244 +1,247 @@
#pragma once #pragma once
#include "wled.h" #include "wled.h"
// v2 Usermod to automatically save settings // v2 Usermod to automatically save settings
// to configurable preset after a change to any of // to configurable preset after a change to any of
// //
// * brightness // * brightness
// * effect speed // * effect speed
// * effect intensity // * effect intensity
// * mode (effect) // * mode (effect)
// * palette // * palette
// //
// but it will wait for configurable number of seconds, a "settle" // but it will wait for configurable number of seconds, a "settle"
// period in case there are other changes (any change will // period in case there are other changes (any change will
// extend the "settle" window). // extend the "settle" window).
// //
// It can be configured to load auto saved preset at startup, // It can be configured to load auto saved preset at startup,
// during the first `loop()`. // during the first `loop()`.
// //
// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod // AutoSaveUsermod is standalone, but if FourLineDisplayUsermod
// is installed, it will notify the user of the saved changes. // is installed, it will notify the user of the saved changes.
// format: "~ MM-DD HH:MM:SS ~" // format: "~ MM-DD HH:MM:SS ~"
#define PRESET_NAME_BUFFER_SIZE 25 #define PRESET_NAME_BUFFER_SIZE 25
class AutoSaveUsermod : public Usermod { class AutoSaveUsermod : public Usermod {
private: private:
bool firstLoop = true; bool firstLoop = true;
bool initDone = false; bool initDone = false;
bool enabled = true; bool enabled = true;
// configurable parameters // configurable parameters
unsigned long autoSaveAfterSec = 15; // 15s by default unsigned long autoSaveAfterSec = 15; // 15s by default
uint8_t autoSavePreset = 250; // last possible preset uint8_t autoSavePreset = 250; // last possible preset
bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot? bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot?
// If we've detected the need to auto save, this will be non zero. // If we've detected the need to auto save, this will be non zero.
unsigned long autoSaveAfter = 0; unsigned long autoSaveAfter = 0;
uint8_t knownBrightness = 0; uint8_t knownBrightness = 0;
uint8_t knownEffectSpeed = 0; uint8_t knownEffectSpeed = 0;
uint8_t knownEffectIntensity = 0; uint8_t knownEffectIntensity = 0;
uint8_t knownMode = 0; uint8_t knownMode = 0;
uint8_t knownPalette = 0; uint8_t knownPalette = 0;
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
FourLineDisplayUsermod* display; FourLineDisplayUsermod* display;
#endif #endif
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
static const char _name[]; static const char _name[];
static const char _autoSaveEnabled[]; static const char _autoSaveEnabled[];
static const char _autoSaveAfterSec[]; static const char _autoSaveAfterSec[];
static const char _autoSavePreset[]; static const char _autoSavePreset[];
static const char _autoSaveApplyOnBoot[]; static const char _autoSaveApplyOnBoot[];
void inline saveSettings() { void inline saveSettings() {
char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; char presetNameBuffer[PRESET_NAME_BUFFER_SIZE];
updateLocalTime(); updateLocalTime();
sprintf_P(presetNameBuffer, sprintf_P(presetNameBuffer,
PSTR("~ %02d-%02d %02d:%02d:%02d ~"), PSTR("~ %02d-%02d %02d:%02d:%02d ~"),
month(localTime), day(localTime), month(localTime), day(localTime),
hour(localTime), minute(localTime), second(localTime)); hour(localTime), minute(localTime), second(localTime));
savePreset(autoSavePreset, true, presetNameBuffer); savePreset(autoSavePreset, true, presetNameBuffer);
} }
void inline displayOverlay() { void inline displayOverlay() {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display != nullptr) { if (display != nullptr) {
display->wakeDisplay(); display->wakeDisplay();
display->overlay("Settings", "Auto Saved", 1500); display->overlay("Settings", "Auto Saved", 1500);
} }
#endif #endif
} }
public: public:
// gets called once at boot. Do all initialization that doesn't depend on // gets called once at boot. Do all initialization that doesn't depend on
// network here // network here
void setup() { void setup() {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
// This Usermod has enhanced funcionality if // This Usermod has enhanced funcionality if
// FourLineDisplayUsermod is available. // FourLineDisplayUsermod is available.
display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
#endif #endif
initDone = true; initDone = true;
} }
// gets called every time WiFi is (re-)connected. Initialize own network // gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here // interfaces here
void connected() {} void connected() {}
/* /*
* Da loop. * Da loop.
*/ */
void loop() { void loop() {
if (!autoSaveAfterSec || !enabled || strip.isUpdating()) return; // setting 0 as autosave seconds disables autosave if (!autoSaveAfterSec || !enabled || strip.isUpdating()) return; // setting 0 as autosave seconds disables autosave
unsigned long now = millis(); unsigned long now = millis();
uint8_t currentMode = strip.getMode(); uint8_t currentMode = strip.getMode();
uint8_t currentPalette = strip.getSegment(0).palette; uint8_t currentPalette = strip.getSegment(0).palette;
if (firstLoop) { if (firstLoop) {
firstLoop = false; firstLoop = false;
if (applyAutoSaveOnBoot) applyPreset(autoSavePreset); if (applyAutoSaveOnBoot) applyPreset(autoSavePreset);
knownBrightness = bri; knownBrightness = bri;
knownEffectSpeed = effectSpeed; knownEffectSpeed = effectSpeed;
knownEffectIntensity = effectIntensity; knownEffectIntensity = effectIntensity;
knownMode = currentMode; knownMode = currentMode;
knownPalette = currentPalette; knownPalette = currentPalette;
return; return;
} }
unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000; unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000;
if (knownBrightness != bri) { if (knownBrightness != bri) {
knownBrightness = bri; knownBrightness = bri;
autoSaveAfter = wouldAutoSaveAfter; autoSaveAfter = wouldAutoSaveAfter;
} else if (knownEffectSpeed != effectSpeed) { } else if (knownEffectSpeed != effectSpeed) {
knownEffectSpeed = effectSpeed; knownEffectSpeed = effectSpeed;
autoSaveAfter = wouldAutoSaveAfter; autoSaveAfter = wouldAutoSaveAfter;
} else if (knownEffectIntensity != effectIntensity) { } else if (knownEffectIntensity != effectIntensity) {
knownEffectIntensity = effectIntensity; knownEffectIntensity = effectIntensity;
autoSaveAfter = wouldAutoSaveAfter; autoSaveAfter = wouldAutoSaveAfter;
} else if (knownMode != currentMode) { } else if (knownMode != currentMode) {
knownMode = currentMode; knownMode = currentMode;
autoSaveAfter = wouldAutoSaveAfter; autoSaveAfter = wouldAutoSaveAfter;
} else if (knownPalette != currentPalette) { } else if (knownPalette != currentPalette) {
knownPalette = currentPalette; knownPalette = currentPalette;
autoSaveAfter = wouldAutoSaveAfter; autoSaveAfter = wouldAutoSaveAfter;
} }
if (autoSaveAfter && now > autoSaveAfter) { if (autoSaveAfter && now > autoSaveAfter) {
autoSaveAfter = 0; autoSaveAfter = 0;
// Time to auto save. You may have some flickry? // Time to auto save. You may have some flickry?
saveSettings(); saveSettings();
displayOverlay(); displayOverlay();
} }
} }
/* /*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor * Below it is shown how this could be used for e.g. a light sensor
*/ */
//void addToJsonInfo(JsonObject& root) { //void addToJsonInfo(JsonObject& root) {
//JsonObject user = root["u"]; //JsonObject user = root["u"];
//if (user.isNull()) user = root.createNestedObject("u"); //if (user.isNull()) user = root.createNestedObject("u");
//JsonArray data = user.createNestedArray(F("Autosave")); //JsonArray data = user.createNestedArray(F("Autosave"));
//data.add(F("Loaded.")); //data.add(F("Loaded."));
//} //}
/* /*
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
//void addToJsonState(JsonObject& root) { //void addToJsonState(JsonObject& root) {
//} //}
/* /*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
//void readFromJsonState(JsonObject& root) { //void readFromJsonState(JsonObject& root) {
// if (!initDone) return; // prevent crash on boot applyPreset() // if (!initDone) return; // prevent crash on boot applyPreset()
//} //}
/* /*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
* It will be called by WLED when settings are actually saved (for example, LED settings are saved) * It will be called by WLED when settings are actually saved (for example, LED settings are saved)
* If you want to force saving the current state, use serializeConfig() in your loop(). * If you want to force saving the current state, use serializeConfig() in your loop().
* *
* CAUTION: serializeConfig() will initiate a filesystem write operation. * CAUTION: serializeConfig() will initiate a filesystem write operation.
* It might cause the LEDs to stutter and will cause flash wear if called too often. * It might cause the LEDs to stutter and will cause flash wear if called too often.
* Use it sparingly and always in the loop, never in network callbacks! * Use it sparingly and always in the loop, never in network callbacks!
* *
* addToConfig() will also not yet add your setting to one of the settings pages automatically. * addToConfig() will also not yet add your setting to one of the settings pages automatically.
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
* *
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/ */
void addToConfig(JsonObject& root) { void addToConfig(JsonObject& root) {
// we add JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} // we add JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}}
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_autoSaveEnabled)] = enabled; top[FPSTR(_autoSaveEnabled)] = enabled;
top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam
top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam
top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot; top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot;
DEBUG_PRINTLN(F("Autosave config saved.")); DEBUG_PRINTLN(F("Autosave config saved."));
} }
/* /*
* readFromConfig() can be used to read back the custom settings you added with addToConfig(). * readFromConfig() can be used to read back the custom settings you added with addToConfig().
* This is called by WLED when settings are loaded (currently this only happens once immediately after boot) * This is called by WLED when settings are loaded (currently this only happens once immediately after boot)
* *
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :) * If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*/ */
void readFromConfig(JsonObject& root) { bool readFromConfig(JsonObject& root) {
// we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} // we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}}
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) { if (top.isNull()) {
DEBUG_PRINTLN(F("No config found. (Using defaults.)")); DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
return; return false;
} }
if (top[FPSTR(_autoSaveEnabled)].is<bool>()) { bool configComplete = true;
// reading from cfg.json
enabled = top[FPSTR(_autoSaveEnabled)].as<bool>(); if (top[FPSTR(_autoSaveEnabled)].is<bool>()) {
} else { // reading from cfg.json
// reading from POST message enabled = top[FPSTR(_autoSaveEnabled)].as<bool>();
String str = top[FPSTR(_autoSaveEnabled)]; // checkbox -> off or on } else {
enabled = (bool)(str!="off"); // off is guaranteed to be present // reading from POST message
} String str = top[FPSTR(_autoSaveEnabled)]; // checkbox -> off or on
autoSaveAfterSec = min(3600,max(10,top[FPSTR(_autoSaveAfterSec)].as<int>())); enabled = (bool)(str!="off"); // off is guaranteed to be present
autoSavePreset = min(250,max(100,top[FPSTR(_autoSavePreset)].as<int>())); }
if (top[FPSTR(_autoSaveApplyOnBoot)].is<bool>()) { autoSaveAfterSec = min(3600,max(10,top[FPSTR(_autoSaveAfterSec)].as<int>()));
// reading from cfg.json autoSavePreset = min(250,max(100,top[FPSTR(_autoSavePreset)].as<int>()));
applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as<bool>(); if (top[FPSTR(_autoSaveApplyOnBoot)].is<bool>()) {
} else { // reading from cfg.json
// reading from POST message applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as<bool>();
String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on } else {
applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present // reading from POST message
} String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on
DEBUG_PRINTLN(F("Autosave config (re)loaded.")); applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present
} }
DEBUG_PRINTLN(F("Autosave config (re)loaded."));
/* return true;
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). }
* This could be used in the future for the system to determine whether your usermod is installed.
*/ /*
uint16_t getId() { * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
return USERMOD_ID_AUTO_SAVE; * This could be used in the future for the system to determine whether your usermod is installed.
} */
}; uint16_t getId() {
return USERMOD_ID_AUTO_SAVE;
// strings to reduce flash memory usage (used more than twice) }
const char AutoSaveUsermod::_name[] PROGMEM = "Autosave"; };
const char AutoSaveUsermod::_autoSaveEnabled[] PROGMEM = "enabled";
const char AutoSaveUsermod::_autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec"; // strings to reduce flash memory usage (used more than twice)
const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset"; const char AutoSaveUsermod::_name[] PROGMEM = "Autosave";
const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot"; const char AutoSaveUsermod::_autoSaveEnabled[] PROGMEM = "enabled";
const char AutoSaveUsermod::_autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec";
const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset";
const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot";

View File

@ -60,6 +60,10 @@
#define DEFAULT_LED_TYPE TYPE_WS2812_RGB #define DEFAULT_LED_TYPE TYPE_WS2812_RGB
#endif #endif
#if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES
#error "Max segments must be at least max number of busses!"
#endif
//do not call this method from system context (network callback) //do not call this method from system context (network callback)
void WS2812FX::finalizeInit(void) void WS2812FX::finalizeInit(void)
{ {

View File

@ -405,7 +405,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
DEBUG_PRINTLN(F("Starting usermod config.")); DEBUG_PRINTLN(F("Starting usermod config."));
JsonObject usermods_settings = doc["um"]; JsonObject usermods_settings = doc["um"];
if (!usermods_settings.isNull()) usermods.readFromConfig(usermods_settings); if (!usermods_settings.isNull()) {
bool allComplete = usermods.readFromConfig(usermods_settings);
if (!allComplete && fromFS) serializeConfig();
}
if (fromFS) return false; if (fromFS) return false;
doReboot = doc[F("rb")] | doReboot; doReboot = doc[F("rb")] | doReboot;
@ -413,11 +416,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
} }
void deserializeConfigFromFS() { void deserializeConfigFromFS() {
bool fromeep = false;
bool success = deserializeConfigSec(); bool success = deserializeConfigSec();
if (!success) { //if file does not exist, try reading from EEPROM if (!success) { //if file does not exist, try reading from EEPROM
deEEPSettings(); deEEPSettings();
fromeep = true; return;
} }
DynamicJsonDocument doc(JSON_BUFFER_SIZE); DynamicJsonDocument doc(JSON_BUFFER_SIZE);
@ -426,7 +428,7 @@ void deserializeConfigFromFS() {
success = readObjectFromFile("/cfg.json", nullptr, &doc); success = readObjectFromFile("/cfg.json", nullptr, &doc);
if (!success) { //if file does not exist, try reading from EEPROM if (!success) { //if file does not exist, try reading from EEPROM
if (!fromeep) deEEPSettings(); deEEPSettings();
return; return;
} }

View File

@ -27,7 +27,6 @@
color: white; color: white;
border: 0px solid white; border: 0px solid white;
border-radius: 25px; border-radius: 25px;
filter: drop-shadow(0px 0px 1px #000);
} }
img { img {

View File

@ -192,7 +192,7 @@ class Usermod {
virtual void addToJsonInfo(JsonObject& obj) {} virtual void addToJsonInfo(JsonObject& obj) {}
virtual void readFromJsonState(JsonObject& obj) {} virtual void readFromJsonState(JsonObject& obj) {}
virtual void addToConfig(JsonObject& obj) {} virtual void addToConfig(JsonObject& obj) {}
virtual void readFromConfig(JsonObject& obj) {} virtual bool readFromConfig(JsonObject& obj) { return true; } //Heads up! readFromConfig() now needs to return a bool
virtual void onMqttConnect(bool sessionPresent) {} virtual void onMqttConnect(bool sessionPresent) {}
virtual bool onMqttMessage(char* topic, char* payload) { return false; } virtual bool onMqttMessage(char* topic, char* payload) { return false; }
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
@ -211,7 +211,7 @@ class UsermodManager {
void addToJsonInfo(JsonObject& obj); void addToJsonInfo(JsonObject& obj);
void readFromJsonState(JsonObject& obj); void readFromJsonState(JsonObject& obj);
void addToConfig(JsonObject& obj); void addToConfig(JsonObject& obj);
void readFromConfig(JsonObject& obj); bool readFromConfig(JsonObject& obj);
void onMqttConnect(bool sessionPresent); void onMqttConnect(bool sessionPresent);
bool onMqttMessage(char* topic, char* payload); bool onMqttMessage(char* topic, char* payload);
bool add(Usermod* um); bool add(Usermod* um);

View File

@ -55,7 +55,7 @@ Please do not close or refresh the page :)</div></body></html>)=====";
const char PAGE_welcome[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta charset="utf-8"><meta const char PAGE_welcome[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta charset="utf-8"><meta
content="width=device-width" name="viewport"><meta name="theme-color" content="width=device-width" name="viewport"><meta name="theme-color"
content="#222222"><title>Welcome!</title><style> content="#222222"><title>Welcome!</title><style>
body{font-family:Verdana,Helvetica,sans-serif;text-align:center;background-color:#222;margin:0;color:#fff}button{outline:0;cursor:pointer;padding:8px;margin:10px;width:230px;text-transform:uppercase;font-family:helvetica;font-size:19px;background-color:#333;color:#fff;border:0 solid #fff;border-radius:25px;filter:drop-shadow(0 0 1px #000)}img{width:950px;max-width:82%;image-rendering:pixelated;image-rendering:crisp-edges;margin:4vh 0 0 0;animation:fi 1s}@keyframes fi{from{opacity:0}to{opacity:1}}.main{animation:fi 1.5s .7s both} body{font-family:Verdana,Helvetica,sans-serif;text-align:center;background-color:#222;margin:0;color:#fff}button{outline:0;cursor:pointer;padding:8px;margin:10px;width:230px;text-transform:uppercase;font-family:helvetica;font-size:19px;background-color:#333;color:#fff;border:0 solid #fff;border-radius:25px}img{width:950px;max-width:82%;image-rendering:pixelated;image-rendering:crisp-edges;margin:4vh 0 0 0;animation:fi 1s}@keyframes fi{from{opacity:0}to{opacity:1}}.main{animation:fi 1.5s .7s both}
</style></head><body><img alt="" </style></head><body><img alt=""
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG0AAAAfCAMAAADazLOuAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABLUExURQAAAAB81gCU/zKq///mo7sWMN8bO+ZIYtZaAP9rAP+HMsCiG+TAIOnMS0KqNU7KPnLUZOrq6v///4CAgGhoaL+/v6CgoExMTAAAAAlm4O8AAAAZdFJOU////////////////////////////////wABNAq3AAAACXBIWXMAAA7DAAAOwwHHb6hkAAACN0lEQVRIS73VjVLCMBAEYIr8CYKkrdj3f1J37zaXFCpTO+piaDgbPq9px9VQ0qyrvKj4q6m0Zr1h+M7xF1zRmnWzqV9/0d2jttGotO1uv9dUObwej5oqp7fzWVPl8n69aprzoOUUbbvdIbV3OLwitXc6vSG1d7m8I3feSEN0j2CeNbOY4MxigjOLCc4sZsTV2l1cCyy4wIILLLjAxtykltq2rbTU+qi01N5rXNO2leaFORoija2l5MM5a02ac9Ya16Sk5tgaPrUpjZub0BL6YqSxKwbH77XUUmSkJXSl8QtaMuyJhq5maL5nTKVpZC13VmtMpTFT2g4vJjTuGfMzzXftiUZnhdtgb1xofvypRon5TjNnxYN9zJo6K5ruSIzQtGuVZn0x91rKvdHBvm39E7SyZ4y06Gz8BDBFKzsXmhcwyfsGZ9VpbhoiCinaxPNmGWmWWrNU2jB0q6HvOhN1JUtCixQtp2g51ZVUXIPS2RMAD++T2nY/DrDjOMDO4wC7jmNYj3d73nrXug8Yt9uNB8xNU1cKNXWlUFNXCjV1pZhGTE83m2vWfYf/NGj4Bg1zu5JD3/MnH5ZWfLOksbmGWGjgXMN5/C2GXYGFFW9Nmtle6Xut0Gm+JsayCj8z0nhjGvYJzVf4aSzmNYsr+u7Q2JIdoX3YOQjOslmsW1jJ3120nE9gfo79hTaNdcsqVR610lvO47pllae9ReZ805zKo2a3iaY5c75pTmVCA6dJ5H7N0sr/asPwBehb7ifEhusRAAAAAElFTkSuQmCC"> src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG0AAAAfCAMAAADazLOuAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABLUExURQAAAAB81gCU/zKq///mo7sWMN8bO+ZIYtZaAP9rAP+HMsCiG+TAIOnMS0KqNU7KPnLUZOrq6v///4CAgGhoaL+/v6CgoExMTAAAAAlm4O8AAAAZdFJOU////////////////////////////////wABNAq3AAAACXBIWXMAAA7DAAAOwwHHb6hkAAACN0lEQVRIS73VjVLCMBAEYIr8CYKkrdj3f1J37zaXFCpTO+piaDgbPq9px9VQ0qyrvKj4q6m0Zr1h+M7xF1zRmnWzqV9/0d2jttGotO1uv9dUObwej5oqp7fzWVPl8n69aprzoOUUbbvdIbV3OLwitXc6vSG1d7m8I3feSEN0j2CeNbOY4MxigjOLCc4sZsTV2l1cCyy4wIILLLjAxtykltq2rbTU+qi01N5rXNO2leaFORoija2l5MM5a02ac9Ya16Sk5tgaPrUpjZub0BL6YqSxKwbH77XUUmSkJXSl8QtaMuyJhq5maL5nTKVpZC13VmtMpTFT2g4vJjTuGfMzzXftiUZnhdtgb1xofvypRon5TjNnxYN9zJo6K5ruSIzQtGuVZn0x91rKvdHBvm39E7SyZ4y06Gz8BDBFKzsXmhcwyfsGZ9VpbhoiCinaxPNmGWmWWrNU2jB0q6HvOhN1JUtCixQtp2g51ZVUXIPS2RMAD++T2nY/DrDjOMDO4wC7jmNYj3d73nrXug8Yt9uNB8xNU1cKNXWlUFNXCjV1pZhGTE83m2vWfYf/NGj4Bg1zu5JD3/MnH5ZWfLOksbmGWGjgXMN5/C2GXYGFFW9Nmtle6Xut0Gm+JsayCj8z0nhjGvYJzVf4aSzmNYsr+u7Q2JIdoX3YOQjOslmsW1jJ3120nE9gfo79hTaNdcsqVR610lvO47pllae9ReZ805zKo2a3iaY5c75pTmVCA6dJ5H7N0sr/asPwBehb7ifEhusRAAAAAElFTkSuQmCC">
<div class="main"><h1>Welcome to WLED!</h1><h3> <div class="main"><h1>Welcome to WLED!</h1><h3>

View File

@ -111,8 +111,7 @@ void colorUpdated(int callMode)
{ {
effectChanged = false; effectChanged = false;
if (realtimeTimeout == UINT32_MAX) realtimeTimeout = 0; if (realtimeTimeout == UINT32_MAX) realtimeTimeout = 0;
if (isPreset) {isPreset = false;} currentPreset = -1; //something changed, so we are no longer in the preset
else {currentPreset = -1;}
notify(callMode); notify(callMode);

View File

@ -29,7 +29,6 @@ bool applyPreset(byte index)
if (!errorFlag) { if (!errorFlag) {
currentPreset = index; currentPreset = index;
isPreset = true;
return true; return true;
} }
return false; return false;

View File

@ -13,7 +13,13 @@ void UsermodManager::addToJsonState(JsonObject& obj) { for (byte i = 0; i < n
void UsermodManager::addToJsonInfo(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToJsonInfo(obj); } void UsermodManager::addToJsonInfo(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToJsonInfo(obj); }
void UsermodManager::readFromJsonState(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->readFromJsonState(obj); } void UsermodManager::readFromJsonState(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->readFromJsonState(obj); }
void UsermodManager::addToConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToConfig(obj); } void UsermodManager::addToConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToConfig(obj); }
void UsermodManager::readFromConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->readFromConfig(obj); } bool UsermodManager::readFromConfig(JsonObject& obj) {
bool allComplete = true;
for (byte i = 0; i < numMods; i++) {
if (!ums[i]->readFromConfig(obj)) allComplete = false;
}
return allComplete;
}
void UsermodManager::onMqttConnect(bool sessionPresent) { for (byte i = 0; i < numMods; i++) ums[i]->onMqttConnect(sessionPresent); } void UsermodManager::onMqttConnect(bool sessionPresent) { for (byte i = 0; i < numMods; i++) ums[i]->onMqttConnect(sessionPresent); }
bool UsermodManager::onMqttMessage(char* topic, char* payload) { bool UsermodManager::onMqttMessage(char* topic, char* payload) {
for (byte i = 0; i < numMods; i++) if (ums[i]->onMqttMessage(topic, payload)) return true; for (byte i = 0; i < numMods; i++) if (ums[i]->onMqttMessage(topic, payload)) return true;

View File

@ -9,7 +9,8 @@
* || || || * || || ||
* \/ \/ \/ * \/ \/ \/
*/ */
//#include "usermod_v2_example.h" //#include "../usermods/EXAMPLE_v2/usermod_v2_example.h"
#ifdef USERMOD_DALLASTEMPERATURE #ifdef USERMOD_DALLASTEMPERATURE
#include "../usermods/Temperature/usermod_temperature.h" #include "../usermods/Temperature/usermod_temperature.h"
#endif #endif

View File

@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2106202 #define VERSION 2106251
//uncomment this if you have a "my_config.h" file you'd like to use //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG
@ -558,7 +558,6 @@ WLED_GLOBAL bool doCloseFile _INIT(false);
// presets // presets
WLED_GLOBAL int16_t currentPreset _INIT(-1); WLED_GLOBAL int16_t currentPreset _INIT(-1);
WLED_GLOBAL bool isPreset _INIT(false);
WLED_GLOBAL byte errorFlag _INIT(0); WLED_GLOBAL byte errorFlag _INIT(0);

View File

@ -479,5 +479,9 @@ void deEEPSettings() {
loadSettingsFromEEPROM(); loadSettingsFromEEPROM();
EEPROM.end(); EEPROM.end();
//call readFromConfig() with an empty object so that usermods can initialize to defaults prior to saving
JsonObject empty = JsonObject();
usermods.readFromConfig(empty);
serializeConfig(); serializeConfig();
} }