#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; bool enabled = true; // configurable parameters #ifdef AUTOSAVE_AFTER_SEC uint16_t autoSaveAfterSec = AUTOSAVE_AFTER_SEC; #else uint16_t autoSaveAfterSec = 15; // 15s by default #endif #ifdef AUTOSAVE_PRESET_NUM uint8_t autoSavePreset = AUTOSAVE_PRESET_NUM; #else uint8_t autoSavePreset = 250; // last possible preset #endif #ifdef USERMOD_AUTO_SAVE_ON_BOOT bool applyAutoSaveOnBoot = USERMOD_AUTO_SAVE_ON_BOOT; #else bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot? #endif // 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 _autoSaveEnabled[]; 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)); cacheInvalidate++; // force reload of presets savePreset(autoSavePreset, presetNameBuffer); } void inline displayOverlay() { #ifdef USERMOD_FOUR_LINE_DISPLAY if (display != nullptr) { display->wakeDisplay(); display->overlay("Settings", "Auto Saved", 1500); } #endif } void enable(bool enable) { enabled = enable; } 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; if (enabled && applyAutoSaveOnBoot) applyPreset(autoSavePreset); knownBrightness = bri; knownEffectSpeed = effectSpeed; knownEffectIntensity = effectIntensity; knownMode = strip.getMainSegment().mode; knownPalette = strip.getMainSegment().palette; } // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here void connected() {} /* * Da loop. */ void loop() { if (!autoSaveAfterSec || !enabled || strip.isUpdating() || currentPreset>0) return; // setting 0 as autosave seconds disables autosave unsigned long now = millis(); uint8_t currentMode = strip.getMainSegment().mode; uint8_t currentPalette = strip.getMainSegment().palette; 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 infoArr = user.createNestedArray(FPSTR(_name)); // name String uiDomString = F(""); infoArr.add(uiDomString); } /* * 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() bool en = enabled; JsonObject um = root[FPSTR(_name)]; if (!um.isNull()) { if (um[FPSTR(_autoSaveEnabled)].is()) { en = um[FPSTR(_autoSaveEnabled)].as(); } else { String str = um[FPSTR(_autoSaveEnabled)]; // checkbox -> off or on en = (bool)(str!="off"); // off is guaranteed to be present } if (en != enabled) enable(en); } } /* * 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(_autoSaveEnabled)] = enabled; 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 :) * * The function should return true if configuration was successfully loaded or false if there was no configuration. */ bool readFromConfig(JsonObject& root) { // we look for JSON object: {"Autosave": {"enabled": true, "autoSaveAfterSec": 10, "autoSavePreset": 250, ...}} JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); return false; } enabled = top[FPSTR(_autoSaveEnabled)] | enabled; autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)] | autoSaveAfterSec; autoSaveAfterSec = (uint16_t) min(3600,max(10,(int)autoSaveAfterSec)); // bounds checking autoSavePreset = top[FPSTR(_autoSavePreset)] | autoSavePreset; autoSavePreset = (uint8_t) min(250,max(100,(int)autoSavePreset)); // bounds checking applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)] | applyAutoSaveOnBoot; DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINTLN(F(" config (re)loaded.")); // use "return !top["newestParameter"].isNull();" when updating Usermod with new features 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() { 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"; const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset"; const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot";