From 6ba1795ded7f43269df30a2efefc5a4f075f95bc Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 22 Apr 2021 22:34:43 +0200 Subject: [PATCH] Flash optimizations & class texts. --- .../usermod_PIR_sensor_switch.h | 159 +++--- usermods/Temperature/usermod_temperature.h | 43 +- .../usermod_v2_auto_save.h | 457 +++++++++--------- .../usermod_v2_four_line_display.h | 69 +-- wled00/wled.h | 2 +- 5 files changed, 399 insertions(+), 331 deletions(-) diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index bd92b9d9..11277593 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -33,9 +33,6 @@ * 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp */ -// MQTT topic for sensor values -const char MQTT_TOPIC[] = "/motion"; - class PIRsensorSwitch : public Usermod { public: @@ -76,15 +73,22 @@ private: // notification mode for colorUpdated() const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE // delay before switch off after the sensor state goes LOW - uint32_t m_switchOffDelay = 600000; + uint32_t m_switchOffDelay = 600000; // 10min // off timer start time uint32_t m_offTimerStart = 0; // current PIR sensor pin state byte m_PIRsensorPinState = LOW; // PIR sensor enabled - ISR attached bool m_PIRenabled = true; - // state if serializeConfig() should be called - bool m_updateConfig = false; + // status of initialisation + bool initDone = false; + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _switchOffDelay[]; + static const char _enabled[]; + static const char _active[]; + static const char _inactive[]; /** * return or change if new PIR sensor state is available @@ -125,7 +129,7 @@ private: if (mqtt != nullptr){ char subuf[64]; strcpy(subuf, mqttDeviceTopic); - strcat(subuf, MQTT_TOPIC); + strcat_P(subuf, PSTR("/motion")); mqtt->publish(subuf, 0, true, state); } } @@ -192,12 +196,12 @@ public: } else { // PIR Sensor mode INPUT_PULLUP pinMode(PIRsensorPin, INPUT_PULLUP); - if (m_PIRenabled) - { + if (m_PIRenabled) { // assign interrupt function and set CHANGE mode attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); } } + initDone = true; } /** @@ -213,14 +217,8 @@ public: */ void loop() { - if (!updatePIRsensorState()) - { + if (!updatePIRsensorState()) { handleOffTimer(); - if (m_updateConfig) - { - serializeConfig(); - m_updateConfig = false; - } } } @@ -237,33 +235,32 @@ public: if (user.isNull()) user = root.createNestedObject("u"); - JsonArray infoArr = user.createNestedArray(" PIR sensor state"); //name - String uiDomString = ""; + uiDomString += F(""); infoArr.add(uiDomString); //value if (m_PIRenabled) { //this code adds "u":{"⏲ switch off timer":uiDomString} to the info object - uiDomString = " switch off timer\ -after  switch off timerafter min"; + uiDomString += F("\" onchange=\"requestJson({PIRoffSec:parseInt(this.value)*60});\">min"); infoArr = user.createNestedArray(uiDomString); //name // off timer @@ -274,7 +271,7 @@ after = 3600) { uiDomString += (offSeconds / 3600); - uiDomString += " hours "; + uiDomString += F("h "); offSeconds %= 3600; } if (offSeconds >= 60) @@ -288,14 +285,14 @@ after 0) { - uiDomString += " min "; + uiDomString += F("min "); } uiDomString += (offSeconds); - infoArr.add(uiDomString + " sec"); + infoArr.add(uiDomString + F("s")); } else { - infoArr.add("inactive"); + infoArr.add(FPSTR(_inactive)); } } } @@ -308,8 +305,8 @@ after ()))); - m_updateConfig = true; + if (root[FPSTR(_switchOffDelay)] != nullptr) { + m_switchOffDelay = (1000 * max(60UL, min(43200UL, root[FPSTR(_switchOffDelay)].as()))); } - - if (root["pin"] != nullptr) - { +/* + if (root["pin"] != nullptr) { int8_t pin = (int)root["pin"]; // check if pin is OK if (pin != PIRsensorPin && pin>=0 && pinManager.allocatePin(pin,false)) { @@ -344,23 +338,17 @@ after = 0) { attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); newPIRsensorState(true, true); - } - else if (m_PIRenabled) - { + } else if (m_PIRenabled && PIRsensorPin >= 0) { detachInterrupt(PIRsensorPin); } - m_PIRenabled = root[F("PIRenabled")]; - m_updateConfig = true; + m_PIRenabled = root[FPSTR(_enabled)]; } } @@ -369,10 +357,11 @@ after ())); // check bounds + } + + if (top[FPSTR(_enabled)] != nullptr) { + if (top[FPSTR(_enabled)].is()) { + m_PIRenabled = top[FPSTR(_enabled)].as(); // reading from cfg.json + } else { + // change from settings page + String str = top[FPSTR(_enabled)]; // checkbox -> off or on + m_PIRenabled = (bool)(str!="off"); // off is guaranteed to be present + } + } + + if (top[FPSTR(_switchOffDelay)] != nullptr) { + m_switchOffDelay = (top[FPSTR(_switchOffDelay)].as() * 1000); + } + + if (!initDone) { + // reading config prior to setup() + DEBUG_PRINTLN(F("PIR config loaded.")); + } else { + if (oldPin != PIRsensorPin || oldEnabled != m_PIRenabled) { + if (oldEnabled) { + // remove old ISR if disabling usermod + detachInterrupt(oldPin); + } + // check if pin is OK + if (oldPin != PIRsensorPin && oldPin >= 0) { + // if we are changing pin in settings page + // deallocate old pin + pinManager.deallocatePin(oldPin); + if (pinManager.allocatePin(PIRsensorPin,false)) { + pinMode(PIRsensorPin, INPUT_PULLUP); + } else { + // allocation failed + PIRsensorPin = -1; + m_PIRenabled = false; + } + } + if (m_PIRenabled) { + attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); + newPIRsensorState(true, true); + } + DEBUG_PRINTLN(F("PIR config (re)loaded.")); + } } - m_PIRenabled = (top[F("PIRenabled")] != nullptr ? top[F("PIRenabled")] : true); - m_switchOffDelay = top[F("PIRoffSec")] | m_switchOffDelay; } /** @@ -425,4 +461,11 @@ PIRsensorSwitch *PIRsensorSwitch::PIRsensorSwitchInstance(PIRsensorSwitch *pInst s_pPIRsensorSwitch = pInstance; } return s_pPIRsensorSwitch; -} +}; + +// strings to reduce flash memory usage (used more than twice) +const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch"; +const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec"; +const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled"; +const char PIRsensorSwitch::_active[] PROGMEM = "active"; +const char PIRsensorSwitch::_inactive[] PROGMEM = "inactive"; diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 3e756b69..176fc3dd 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -23,9 +23,6 @@ #define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000 #endif -// strings -const char _um_Temperature[] PROGMEM = "Temperature"; - class UsermodTemperature : public Usermod { private: @@ -54,6 +51,11 @@ class UsermodTemperature : public Usermod { // temperature if flashed to a board without a sensor attached bool disabled = false; + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _readInterval[]; + //Dallas sensor quick reading. Credit to - Author: Peter Scargill, August 17th, 2013 int16_t readDallas() { byte i; @@ -180,7 +182,7 @@ class UsermodTemperature : public Usermod { JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); - JsonArray temp = user.createNestedArray(FPSTR(_um_Temperature)); + JsonArray temp = user.createNestedArray(FPSTR(_name)); //temp.add(F("Loaded.")); if (!getTemperatureComplete) { @@ -215,20 +217,20 @@ class UsermodTemperature : public Usermod { * Values in the state object may be modified by connected clients * Read "_" from json state and and change settings (i.e. GPIO pin) used. */ - void readFromJsonState(JsonObject &root) { - if (!initDone) return; // prevent crash on boot applyPreset() - } + //void readFromJsonState(JsonObject &root) { + // if (!initDone) return; // prevent crash on boot applyPreset() + //} /** * addToConfig() (called from set.cpp) stores persistent properties to cfg.json */ void addToConfig(JsonObject &root) { // we add JSON object: {"Temperature": {"pin": 0, "degC": true}} - JsonObject top = root.createNestedObject(FPSTR(_um_Temperature)); // usermodname - top[F("enabled")] = !disabled; + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + top[FPSTR(_enabled)] = !disabled; top["pin"] = temperaturePin; // usermodparam top["degC"] = degC; // usermodparam - top[F("read-interval-s")] = readingInterval / 1000; + top[FPSTR(_readInterval)] = readingInterval / 1000; DEBUG_PRINTLN(F("Temperature config saved.")); } @@ -237,27 +239,31 @@ class UsermodTemperature : public Usermod { */ void readFromConfig(JsonObject &root) { // we look for JSON object: {"Temperature": {"pin": 0, "degC": true}} - JsonObject top = root[FPSTR(_um_Temperature)]; + JsonObject top = root[FPSTR(_name)]; int8_t newTemperaturePin = temperaturePin; + if (!top.isNull() && top["pin"] != nullptr) { - if (top[F("enabled")].is()) { - disabled = !top[F("enabled")].as(); + if (top[FPSTR(_enabled)].is()) { + disabled = !top[FPSTR(_enabled)].as(); } else { - String str = top[F("enabled")]; // checkbox -> off or on + String str = top[FPSTR(_enabled)]; // checkbox -> off or on disabled = (bool)(str=="off"); // off is guaranteed to be present } newTemperaturePin = min(39,max(-1,top["pin"].as())); if (top["degC"].is()) { + // reading from cfg.json degC = top["degC"].as(); } else { + // new configuration from set.cpp String str = top["degC"]; // checkbox -> off or on degC = (bool)(str!="off"); // off is guaranteed to be present } - readingInterval = min(120,max(10,top[F("read-interval-s")].as())) * 1000; - DEBUG_PRINTLN(F("Temperature config loaded.")); + readingInterval = min(120,max(10,top[FPSTR(_readInterval)].as())) * 1000; // convert to ms + DEBUG_PRINTLN(F("Temperature config (re)loaded.")); } else { DEBUG_PRINTLN(F("No config found. (Using defaults.)")); } + if (!initDone) { // first run: reading from cfg.json temperaturePin = newTemperaturePin; @@ -279,3 +285,8 @@ class UsermodTemperature : public Usermod { return USERMOD_ID_TEMPERATURE; } }; + +// strings to reduce flash memory usage (used more than twice) +const char UsermodTemperature::_name[] PROGMEM = "Temperature"; +const char UsermodTemperature::_enabled[] PROGMEM = "enabled"; +const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s"; \ No newline at end of file diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index 443fd4aa..63b32f7d 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -1,226 +1,231 @@ -#pragma once - -#include "wled.h" - -// v2 Usermod to automatically save settings -// to configurable preset after a change to any of -// -// * brightness -// * effect speed -// * effect intensity -// * mode (effect) -// * palette -// -// but it will wait for configurable number of seconds, a "settle" -// period in case there are other changes (any change will -// extend the "settle" window). -// -// It can be configured to load auto saved preset at startup, -// during the first `loop()`. -// -// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod -// is installed, it will notify the user of the saved changes. - -// format: "~ MM-DD HH:MM:SS ~" -#define PRESET_NAME_BUFFER_SIZE 25 - -// strings -const char _um_AutoSave[] PROGMEM = "Autosave"; -const char _autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec"; -const char _autoSavePreset[] PROGMEM = "autoSavePreset"; -const char _autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot"; - -class AutoSaveUsermod : public Usermod { - - private: - - bool firstLoop = true; - bool initDone = false; - - // configurable parameters - unsigned long autoSaveAfterSec = 15; // 15s by default - uint8_t autoSavePreset = 250; // last possible preset - 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. - unsigned long autoSaveAfter = 0; - - uint8_t knownBrightness = 0; - uint8_t knownEffectSpeed = 0; - uint8_t knownEffectIntensity = 0; - uint8_t knownMode = 0; - uint8_t knownPalette = 0; - - #ifdef USERMOD_FOUR_LINE_DISPLAY - FourLineDisplayUsermod* display; - #endif - - void inline saveSettings() { - char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; - updateLocalTime(); - sprintf_P(presetNameBuffer, - PSTR("~ %02d-%02d %02d:%02d:%02d ~"), - month(localTime), day(localTime), - hour(localTime), minute(localTime), second(localTime)); - savePreset(autoSavePreset, true, presetNameBuffer); - } - - void inline displayOverlay() { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display != nullptr) { - display->wakeDisplay(); - display->overlay("Settings", "Auto Saved", 1500); - } - #endif - } - - public: - - // gets called once at boot. Do all initialization that doesn't depend on - // network here - void setup() { - #ifdef USERMOD_FOUR_LINE_DISPLAY - // This Usermod has enhanced funcionality if - // FourLineDisplayUsermod is available. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); - #endif - initDone = true; - } - - // gets called every time WiFi is (re-)connected. Initialize own network - // interfaces here - void connected() {} - - /* - * Da loop. - */ - void loop() { - if (!autoSaveAfterSec) return; // setting 0 as autosave seconds disables autosave - - unsigned long now = millis(); - uint8_t currentMode = strip.getMode(); - uint8_t currentPalette = strip.getSegment(0).palette; - if (firstLoop) { - firstLoop = false; - if (applyAutoSaveOnBoot) applyPreset(autoSavePreset); - knownBrightness = bri; - knownEffectSpeed = effectSpeed; - knownEffectIntensity = effectIntensity; - knownMode = currentMode; - knownPalette = currentPalette; - return; - } - - unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000; - if (knownBrightness != bri) { - knownBrightness = bri; - autoSaveAfter = wouldAutoSaveAfter; - } else if (knownEffectSpeed != effectSpeed) { - knownEffectSpeed = effectSpeed; - autoSaveAfter = wouldAutoSaveAfter; - } else if (knownEffectIntensity != effectIntensity) { - knownEffectIntensity = effectIntensity; - autoSaveAfter = wouldAutoSaveAfter; - } else if (knownMode != currentMode) { - knownMode = currentMode; - autoSaveAfter = wouldAutoSaveAfter; - } else if (knownPalette != currentPalette) { - knownPalette = currentPalette; - autoSaveAfter = wouldAutoSaveAfter; - } - - if (autoSaveAfter && now > autoSaveAfter) { - autoSaveAfter = 0; - // Time to auto save. You may have some flickry? - saveSettings(); - displayOverlay(); - } - } - - /* - * 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. - * Below it is shown how this could be used for e.g. a light sensor - */ - //void addToJsonInfo(JsonObject& root) { - //JsonObject user = root["u"]; - //if (user.isNull()) user = root.createNestedObject("u"); - //JsonArray data = user.createNestedArray(F("Autosave")); - //data.add(F("Loaded.")); - //} - - /* - * 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 - */ - //void addToJsonState(JsonObject& root) { - //} - - /* - * 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 - */ - //void readFromJsonState(JsonObject& root) { - // 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. - * 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(). - * - * CAUTION: serializeConfig() will initiate a filesystem write operation. - * 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! - * - * 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. - * - * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! - */ - void addToConfig(JsonObject& root) { - // we add JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} - JsonObject top = root.createNestedObject(FPSTR(_um_AutoSave)); // usermodname - top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam - top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam - top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot; - DEBUG_PRINTLN(F("Autosave config saved.")); - } - - /* - * 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) - * - * 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. - * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) - */ - void readFromConfig(JsonObject& root) { - // we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} - JsonObject top = root[FPSTR(_um_AutoSave)]; - if (!top.isNull() && top[FPSTR(_autoSaveAfterSec)] != nullptr) { - autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)].as(); - autoSavePreset = top[FPSTR(_autoSavePreset)].as(); - if (top[FPSTR(_autoSaveApplyOnBoot)].is()) { - // reading from cfg.json - applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as(); - } else { - // reading from POST message - String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on - applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present - } - DEBUG_PRINTLN(F("Autosave config (re)loaded.")); - } else { - DEBUG_PRINTLN(F("No config found. (Using defaults.)")); - } - } - - /* - * 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() { - return USERMOD_ID_AUTO_SAVE; - } - -}; +#pragma once + +#include "wled.h" + +// v2 Usermod to automatically save settings +// to configurable preset after a change to any of +// +// * brightness +// * effect speed +// * effect intensity +// * mode (effect) +// * palette +// +// but it will wait for configurable number of seconds, a "settle" +// period in case there are other changes (any change will +// extend the "settle" window). +// +// It can be configured to load auto saved preset at startup, +// during the first `loop()`. +// +// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod +// is installed, it will notify the user of the saved changes. + +// format: "~ MM-DD HH:MM:SS ~" +#define PRESET_NAME_BUFFER_SIZE 25 + +class AutoSaveUsermod : public Usermod { + + private: + + bool firstLoop = true; + bool initDone = false; + + // configurable parameters + unsigned long autoSaveAfterSec = 15; // 15s by default + uint8_t autoSavePreset = 250; // last possible preset + 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. + unsigned long autoSaveAfter = 0; + + uint8_t knownBrightness = 0; + uint8_t knownEffectSpeed = 0; + uint8_t knownEffectIntensity = 0; + uint8_t knownMode = 0; + uint8_t knownPalette = 0; + + #ifdef USERMOD_FOUR_LINE_DISPLAY + FourLineDisplayUsermod* display; + #endif + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _autoSaveAfterSec[]; + static const char _autoSavePreset[]; + static const char _autoSaveApplyOnBoot[]; + + void inline saveSettings() { + char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; + updateLocalTime(); + sprintf_P(presetNameBuffer, + PSTR("~ %02d-%02d %02d:%02d:%02d ~"), + month(localTime), day(localTime), + hour(localTime), minute(localTime), second(localTime)); + savePreset(autoSavePreset, true, presetNameBuffer); + } + + void inline displayOverlay() { + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display != nullptr) { + display->wakeDisplay(); + display->overlay("Settings", "Auto Saved", 1500); + } + #endif + } + + public: + + // gets called once at boot. Do all initialization that doesn't depend on + // network here + void setup() { + #ifdef USERMOD_FOUR_LINE_DISPLAY + // This Usermod has enhanced funcionality if + // FourLineDisplayUsermod is available. + display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + #endif + initDone = true; + } + + // gets called every time WiFi is (re-)connected. Initialize own network + // interfaces here + void connected() {} + + /* + * Da loop. + */ + void loop() { + if (!autoSaveAfterSec) return; // setting 0 as autosave seconds disables autosave + + unsigned long now = millis(); + uint8_t currentMode = strip.getMode(); + uint8_t currentPalette = strip.getSegment(0).palette; + if (firstLoop) { + firstLoop = false; + if (applyAutoSaveOnBoot) applyPreset(autoSavePreset); + knownBrightness = bri; + knownEffectSpeed = effectSpeed; + knownEffectIntensity = effectIntensity; + knownMode = currentMode; + knownPalette = currentPalette; + return; + } + + unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000; + if (knownBrightness != bri) { + knownBrightness = bri; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownEffectSpeed != effectSpeed) { + knownEffectSpeed = effectSpeed; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownEffectIntensity != effectIntensity) { + knownEffectIntensity = effectIntensity; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownMode != currentMode) { + knownMode = currentMode; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownPalette != currentPalette) { + knownPalette = currentPalette; + autoSaveAfter = wouldAutoSaveAfter; + } + + if (autoSaveAfter && now > autoSaveAfter) { + autoSaveAfter = 0; + // Time to auto save. You may have some flickry? + saveSettings(); + displayOverlay(); + } + } + + /* + * 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. + * Below it is shown how this could be used for e.g. a light sensor + */ + //void addToJsonInfo(JsonObject& root) { + //JsonObject user = root["u"]; + //if (user.isNull()) user = root.createNestedObject("u"); + //JsonArray data = user.createNestedArray(F("Autosave")); + //data.add(F("Loaded.")); + //} + + /* + * 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 + */ + //void addToJsonState(JsonObject& root) { + //} + + /* + * 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 + */ + //void readFromJsonState(JsonObject& root) { + // 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. + * 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(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * 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! + * + * 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. + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ + void addToConfig(JsonObject& root) { + // we add JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam + top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam + top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot; + DEBUG_PRINTLN(F("Autosave config saved.")); + } + + /* + * 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) + * + * 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. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + */ + void readFromConfig(JsonObject& root) { + // we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} + JsonObject top = root[FPSTR(_name)]; + if (!top.isNull() && top[FPSTR(_autoSaveAfterSec)] != nullptr) { + autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)].as(); + autoSavePreset = top[FPSTR(_autoSavePreset)].as(); + if (top[FPSTR(_autoSaveApplyOnBoot)].is()) { + // reading from cfg.json + applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as(); + } else { + // reading from POST message + String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on + applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present + } + DEBUG_PRINTLN(F("Autosave config (re)loaded.")); + } else { + DEBUG_PRINTLN(F("No config found. (Using defaults.)")); + } + } + + /* + * 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() { + return USERMOD_ID_AUTO_SAVE; + } +}; + +// strings to reduce flash memory usage (used more than twice) +const char AutoSaveUsermod::_name[] PROGMEM = "Autosave"; +const char AutoSaveUsermod::_autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec"; +const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset"; +const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot"; diff --git a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h index 14df1694..445c7dba 100644 --- a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h +++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h @@ -67,15 +67,6 @@ typedef enum { SH1106 // U8X8_SH1106_128X64_WINSTAR_HW_I2C } DisplayType; -// strings -const char _um_4LineDisplay[] PROGMEM = "4LineDisplay"; -const char _4LD_contrast[] PROGMEM = "contrast"; -const char _4LD_refreshRate[] PROGMEM = "refreshRate"; -const char _4LD_screenTimeOut[] PROGMEM = "screenTimeOut"; -const char _4LD_flip[] PROGMEM = "flip"; -const char _4LD_sleepMode[] PROGMEM = "sleepMode"; -const char _4LD_clockMode[] PROGMEM = "clockMode"; - class FourLineDisplayUsermod : public Usermod { private: @@ -118,6 +109,15 @@ class FourLineDisplayUsermod : public Usermod { // Set to 2 or 3 to mark lines 2 or 3. Other values ignored. byte markLineNum = 0; + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _contrast[]; + static const char _refreshRate[]; + static const char _screenTimeOut[]; + static const char _flip[]; + static const char _sleepMode[]; + static const char _clockMode[]; + // If display does not work or looks corrupted check the // constructor reference: // https://github.com/olikraus/u8g2/wiki/u8x8setupcpp @@ -647,17 +647,17 @@ class FourLineDisplayUsermod : public Usermod { * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ void addToConfig(JsonObject& root) { - JsonObject top = root.createNestedObject(FPSTR(_um_4LineDisplay)); + JsonObject top = root.createNestedObject(FPSTR(_name)); JsonArray i2c_pin = top.createNestedArray("pin"); i2c_pin.add(sclPin); i2c_pin.add(sdaPin); top["type"] = type; - top[FPSTR(_4LD_flip)] = (bool) flip; - top[FPSTR(_4LD_contrast)] = contrast; - top[FPSTR(_4LD_refreshRate)] = refreshRate/1000; - top[FPSTR(_4LD_screenTimeOut)] = screenTimeout/1000; - top[FPSTR(_4LD_sleepMode)] = (bool) sleepMode; - top[FPSTR(_4LD_clockMode)] = (bool) clockMode; + top[FPSTR(_flip)] = (bool) flip; + top[FPSTR(_contrast)] = contrast; + top[FPSTR(_refreshRate)] = refreshRate/1000; + top[FPSTR(_screenTimeOut)] = screenTimeout/1000; + top[FPSTR(_sleepMode)] = (bool) sleepMode; + top[FPSTR(_clockMode)] = (bool) clockMode; DEBUG_PRINTLN(F("4 Line Display config saved.")); } @@ -675,33 +675,33 @@ class FourLineDisplayUsermod : public Usermod { int8_t newScl = sclPin; int8_t newSda = sdaPin; - JsonObject top = root[FPSTR(_um_4LineDisplay)]; + JsonObject top = root[FPSTR(_name)]; if (!top.isNull() && top["pin"] != nullptr) { newScl = top["pin"][0]; newSda = top["pin"][1]; newType = top["type"]; lineHeight = type==SH1106 ? 2 : 1; - if (top[FPSTR(_4LD_flip)].is()) { - flip = top[FPSTR(_4LD_flip)].as(); + if (top[FPSTR(_flip)].is()) { + flip = top[FPSTR(_flip)].as(); } else { - String str = top[FPSTR(_4LD_flip)]; // checkbox -> off or on + String str = top[FPSTR(_flip)]; // checkbox -> off or on flip = (bool)(str!="off"); // off is guaranteed to be present needRedraw |= true; } - contrast = top[FPSTR(_4LD_contrast)].as(); - refreshRate = top[FPSTR(_4LD_refreshRate)].as() * 1000; - screenTimeout = top[FPSTR(_4LD_screenTimeOut)].as() * 1000; - if (top[FPSTR(_4LD_sleepMode)].is()) { - sleepMode = top[FPSTR(_4LD_sleepMode)].as(); + contrast = top[FPSTR(_contrast)].as(); + refreshRate = top[FPSTR(_refreshRate)].as() * 1000; + screenTimeout = top[FPSTR(_screenTimeOut)].as() * 1000; + if (top[FPSTR(_sleepMode)].is()) { + sleepMode = top[FPSTR(_sleepMode)].as(); } else { - String str = top[FPSTR(_4LD_sleepMode)]; // checkbox -> off or on + String str = top[FPSTR(_sleepMode)]; // checkbox -> off or on sleepMode = (bool)(str!="off"); // off is guaranteed to be present needRedraw |= true; } - if (top[FPSTR(_4LD_clockMode)].is()) { - clockMode = top[FPSTR(_4LD_clockMode)].as(); + if (top[FPSTR(_clockMode)].is()) { + clockMode = top[FPSTR(_clockMode)].as(); } else { - String str = top[FPSTR(_4LD_clockMode)]; // checkbox -> off or on + String str = top[FPSTR(_clockMode)]; // checkbox -> off or on clockMode = (bool)(str!="off"); // off is guaranteed to be present needRedraw |= true; } @@ -746,4 +746,13 @@ class FourLineDisplayUsermod : public Usermod { uint16_t getId() { return USERMOD_ID_FOUR_LINE_DISP; } -}; \ No newline at end of file +}; + +// strings to reduce flash memory usage (used more than twice) +const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay"; +const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast"; +const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate"; +const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOut"; +const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip"; +const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode"; +const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode"; diff --git a/wled00/wled.h b/wled00/wled.h index 0ec6dd4a..4098ed2c 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2104210 +#define VERSION 2104221 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG