Flash optimizations & class texts.

This commit is contained in:
Blaz Kristan 2021-04-22 22:34:43 +02:00
parent 8608c45309
commit 6ba1795ded
5 changed files with 399 additions and 331 deletions

View File

@ -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 * 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 class PIRsensorSwitch : public Usermod
{ {
public: public:
@ -76,15 +73,22 @@ private:
// notification mode for colorUpdated() // notification mode for colorUpdated()
const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE
// delay before switch off after the sensor state goes LOW // 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 // off timer start time
uint32_t m_offTimerStart = 0; uint32_t m_offTimerStart = 0;
// current PIR sensor pin state // current PIR sensor pin state
byte m_PIRsensorPinState = LOW; byte m_PIRsensorPinState = LOW;
// PIR sensor enabled - ISR attached // PIR sensor enabled - ISR attached
bool m_PIRenabled = true; bool m_PIRenabled = true;
// state if serializeConfig() should be called // status of initialisation
bool m_updateConfig = false; 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 * return or change if new PIR sensor state is available
@ -125,7 +129,7 @@ private:
if (mqtt != nullptr){ if (mqtt != nullptr){
char subuf[64]; char subuf[64];
strcpy(subuf, mqttDeviceTopic); strcpy(subuf, mqttDeviceTopic);
strcat(subuf, MQTT_TOPIC); strcat_P(subuf, PSTR("/motion"));
mqtt->publish(subuf, 0, true, state); mqtt->publish(subuf, 0, true, state);
} }
} }
@ -192,12 +196,12 @@ public:
} else { } else {
// PIR Sensor mode INPUT_PULLUP // PIR Sensor mode INPUT_PULLUP
pinMode(PIRsensorPin, INPUT_PULLUP); pinMode(PIRsensorPin, INPUT_PULLUP);
if (m_PIRenabled) if (m_PIRenabled) {
{
// assign interrupt function and set CHANGE mode // assign interrupt function and set CHANGE mode
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
} }
} }
initDone = true;
} }
/** /**
@ -213,14 +217,8 @@ public:
*/ */
void loop() void loop()
{ {
if (!updatePIRsensorState()) if (!updatePIRsensorState()) {
{
handleOffTimer(); handleOffTimer();
if (m_updateConfig)
{
serializeConfig();
m_updateConfig = false;
}
} }
} }
@ -237,33 +235,32 @@ public:
if (user.isNull()) if (user.isNull())
user = root.createNestedObject("u"); user = root.createNestedObject("u");
JsonArray infoArr = user.createNestedArray("<i class=\"icons\">&#xe08f;</i> PIR sensor state"); //name JsonArray infoArr = user.createNestedArray(F("<i class=\"icons\">&#xe08f;</i> PIR sensor state")); //name
String uiDomString = "<button class=\"btn infobtn\" onclick=\"requestJson({PIRenabled:"; String uiDomString = F("<button class=\"btn infobtn\" onclick=\"requestJson({PIRenabled:");
String sensorStateInfo; String sensorStateInfo;
// PIR sensor state // PIR sensor state
if (m_PIRenabled) if (m_PIRenabled)
{ {
uiDomString += "false"; uiDomString += "false";
sensorStateInfo = (m_PIRsensorPinState != LOW ? "active" : "inactive"); //value sensorStateInfo = (m_PIRsensorPinState != LOW ? FPSTR(_active) : FPSTR(_inactive)); //value
} }
else else
{ {
uiDomString += "true"; uiDomString += "true";
sensorStateInfo = "Disabled!"; sensorStateInfo = F("Disabled!");
} }
uiDomString += "});return false;\">"; uiDomString += F("});return false;\">");
uiDomString += sensorStateInfo; uiDomString += sensorStateInfo;
uiDomString += "</button>"; uiDomString += F("</button>");
infoArr.add(uiDomString); //value infoArr.add(uiDomString); //value
if (m_PIRenabled) if (m_PIRenabled)
{ {
//this code adds "u":{"&#x23F2; switch off timer":uiDomString} to the info object //this code adds "u":{"&#x23F2; switch off timer":uiDomString} to the info object
uiDomString = "<i class=\"icons\">&#xe325;</i> switch off timer<span style=\"display:block;padding-left:25px;\">\ uiDomString = F("<i class=\"icons\">&#xe325;</i> switch off timer<span style=\"display:block;padding-left:25px;\">after <input type=\"number\" min=\"1\" max=\"720\" value=\"");
after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
uiDomString += (m_switchOffDelay / 60000); uiDomString += (m_switchOffDelay / 60000);
uiDomString += "\" onchange=\"requestJson({PIRoffSec:parseInt(this.value)*60});\">min</span>"; uiDomString += F("\" onchange=\"requestJson({PIRoffSec:parseInt(this.value)*60});\">min</span>");
infoArr = user.createNestedArray(uiDomString); //name infoArr = user.createNestedArray(uiDomString); //name
// off timer // off timer
@ -274,7 +271,7 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
if (offSeconds >= 3600) if (offSeconds >= 3600)
{ {
uiDomString += (offSeconds / 3600); uiDomString += (offSeconds / 3600);
uiDomString += " hours "; uiDomString += F("h ");
offSeconds %= 3600; offSeconds %= 3600;
} }
if (offSeconds >= 60) if (offSeconds >= 60)
@ -288,14 +285,14 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
} }
if (uiDomString.length() > 0) if (uiDomString.length() > 0)
{ {
uiDomString += " min "; uiDomString += F("min ");
} }
uiDomString += (offSeconds); uiDomString += (offSeconds);
infoArr.add(uiDomString + " sec"); infoArr.add(uiDomString + F("s"));
} }
else else
{ {
infoArr.add("inactive"); infoArr.add(FPSTR(_inactive));
} }
} }
} }
@ -308,8 +305,8 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
*/ */
void addToJsonState(JsonObject &root) void addToJsonState(JsonObject &root)
{ {
root[F("PIRenabled")] = m_PIRenabled; root[FPSTR(_enabled)] = m_PIRenabled;
root[F("PIRoffSec")] = (m_switchOffDelay / 1000); root[FPSTR(_switchOffDelay)] = (m_switchOffDelay / 1000);
} }
/** /**
@ -320,14 +317,11 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
*/ */
void readFromJsonState(JsonObject &root) void readFromJsonState(JsonObject &root)
{ {
if (root[F("PIRoffSec")] != nullptr) if (root[FPSTR(_switchOffDelay)] != nullptr) {
{ m_switchOffDelay = (1000 * max(60UL, min(43200UL, root[FPSTR(_switchOffDelay)].as<unsigned long>())));
m_switchOffDelay = (1000 * max(60UL, min(43200UL, root[F("PIRoffSec")].as<unsigned long>())));
m_updateConfig = true;
} }
/*
if (root["pin"] != nullptr) if (root["pin"] != nullptr) {
{
int8_t pin = (int)root["pin"]; int8_t pin = (int)root["pin"];
// check if pin is OK // check if pin is OK
if (pin != PIRsensorPin && pin>=0 && pinManager.allocatePin(pin,false)) { if (pin != PIRsensorPin && pin>=0 && pinManager.allocatePin(pin,false)) {
@ -344,23 +338,17 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
newPIRsensorState(true, true); newPIRsensorState(true, true);
} }
PIRsensorPin = pin; PIRsensorPin = pin;
m_updateConfig = true;
} }
} }
*/
if (root[F("PIRenabled")] != nullptr) if (root[FPSTR(_enabled)] != nullptr) {
{ if (root[FPSTR(_enabled)] && !m_PIRenabled && PIRsensorPin >= 0) {
if (root[F("PIRenabled")] && !m_PIRenabled)
{
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
newPIRsensorState(true, true); newPIRsensorState(true, true);
} } else if (m_PIRenabled && PIRsensorPin >= 0) {
else if (m_PIRenabled)
{
detachInterrupt(PIRsensorPin); detachInterrupt(PIRsensorPin);
} }
m_PIRenabled = root[F("PIRenabled")]; m_PIRenabled = root[FPSTR(_enabled)];
m_updateConfig = true;
} }
} }
@ -369,10 +357,11 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
*/ */
void addToConfig(JsonObject &root) void addToConfig(JsonObject &root)
{ {
JsonObject top = root.createNestedObject(F("PIRsensorSwitch")); JsonObject top = root.createNestedObject(FPSTR(_name));
top[F("PIRenabled")] = m_PIRenabled; top[FPSTR(_enabled)] = m_PIRenabled;
top[F("PIRoffSec")] = m_switchOffDelay; top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
top["pin"] = PIRsensorPin; top["pin"] = PIRsensorPin;
DEBUG_PRINTLN(F("PIR config saved."));
} }
/** /**
@ -381,12 +370,59 @@ after <input type=\"number\" min=\"1\" max=\"720\" value=\"";
*/ */
void readFromConfig(JsonObject &root) void readFromConfig(JsonObject &root)
{ {
JsonObject top = root[F("PIRsensorSwitch")]; bool oldEnabled = m_PIRenabled;
if (!top.isNull() && top["pin"] != nullptr) { int8_t oldPin = PIRsensorPin;
PIRsensorPin = (int)top["pin"];
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) return;
if (top["pin"] != nullptr) {
PIRsensorPin = min(39,max(-1,top["pin"].as<int>())); // check bounds
}
if (top[FPSTR(_enabled)] != nullptr) {
if (top[FPSTR(_enabled)].is<bool>()) {
m_PIRenabled = top[FPSTR(_enabled)].as<bool>(); // 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<int>() * 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; s_pPIRsensorSwitch = pInstance;
} }
return s_pPIRsensorSwitch; 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";

View File

@ -23,9 +23,6 @@
#define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000 #define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000
#endif #endif
// strings
const char _um_Temperature[] PROGMEM = "Temperature";
class UsermodTemperature : public Usermod { class UsermodTemperature : public Usermod {
private: private:
@ -54,6 +51,11 @@ class UsermodTemperature : public Usermod {
// temperature if flashed to a board without a sensor attached // temperature if flashed to a board without a sensor attached
bool disabled = false; 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 //Dallas sensor quick reading. Credit to - Author: Peter Scargill, August 17th, 2013
int16_t readDallas() { int16_t readDallas() {
byte i; byte i;
@ -180,7 +182,7 @@ class UsermodTemperature : public Usermod {
JsonObject user = root["u"]; JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("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.")); //temp.add(F("Loaded."));
if (!getTemperatureComplete) { if (!getTemperatureComplete) {
@ -215,20 +217,20 @@ class UsermodTemperature : public Usermod {
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
* Read "<usermodname>_<usermodparam>" from json state and and change settings (i.e. GPIO pin) used. * Read "<usermodname>_<usermodparam>" from json state and and change settings (i.e. GPIO pin) used.
*/ */
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() (called from set.cpp) stores persistent properties to cfg.json * addToConfig() (called from set.cpp) stores persistent properties to cfg.json
*/ */
void addToConfig(JsonObject &root) { void addToConfig(JsonObject &root) {
// we add JSON object: {"Temperature": {"pin": 0, "degC": true}} // we add JSON object: {"Temperature": {"pin": 0, "degC": true}}
JsonObject top = root.createNestedObject(FPSTR(_um_Temperature)); // usermodname JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[F("enabled")] = !disabled; top[FPSTR(_enabled)] = !disabled;
top["pin"] = temperaturePin; // usermodparam top["pin"] = temperaturePin; // usermodparam
top["degC"] = degC; // usermodparam top["degC"] = degC; // usermodparam
top[F("read-interval-s")] = readingInterval / 1000; top[FPSTR(_readInterval)] = readingInterval / 1000;
DEBUG_PRINTLN(F("Temperature config saved.")); DEBUG_PRINTLN(F("Temperature config saved."));
} }
@ -237,27 +239,31 @@ class UsermodTemperature : public Usermod {
*/ */
void readFromConfig(JsonObject &root) { void 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(_um_Temperature)]; JsonObject top = root[FPSTR(_name)];
int8_t newTemperaturePin = temperaturePin; int8_t newTemperaturePin = temperaturePin;
if (!top.isNull() && top["pin"] != nullptr) { if (!top.isNull() && top["pin"] != nullptr) {
if (top[F("enabled")].is<bool>()) { if (top[FPSTR(_enabled)].is<bool>()) {
disabled = !top[F("enabled")].as<bool>(); disabled = !top[FPSTR(_enabled)].as<bool>();
} else { } 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 disabled = (bool)(str=="off"); // off is guaranteed to be present
} }
newTemperaturePin = min(39,max(-1,top["pin"].as<int>())); newTemperaturePin = min(39,max(-1,top["pin"].as<int>()));
if (top["degC"].is<bool>()) { if (top["degC"].is<bool>()) {
// reading from cfg.json
degC = top["degC"].as<bool>(); degC = top["degC"].as<bool>();
} else { } else {
// new configuration from set.cpp
String str = top["degC"]; // checkbox -> off or on String str = top["degC"]; // checkbox -> off or on
degC = (bool)(str!="off"); // off is guaranteed to be present degC = (bool)(str!="off"); // off is guaranteed to be present
} }
readingInterval = min(120,max(10,top[F("read-interval-s")].as<int>())) * 1000; readingInterval = min(120,max(10,top[FPSTR(_readInterval)].as<int>())) * 1000; // convert to ms
DEBUG_PRINTLN(F("Temperature config loaded.")); DEBUG_PRINTLN(F("Temperature config (re)loaded."));
} else { } else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)")); DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
} }
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
temperaturePin = newTemperaturePin; temperaturePin = newTemperaturePin;
@ -279,3 +285,8 @@ class UsermodTemperature : public Usermod {
return USERMOD_ID_TEMPERATURE; 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";

View File

@ -1,226 +1,231 @@
#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
// strings class AutoSaveUsermod : public Usermod {
const char _um_AutoSave[] PROGMEM = "Autosave";
const char _autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec"; private:
const char _autoSavePreset[] PROGMEM = "autoSavePreset";
const char _autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot"; bool firstLoop = true;
bool initDone = false;
class AutoSaveUsermod : public Usermod {
// configurable parameters
private: unsigned long autoSaveAfterSec = 15; // 15s by default
uint8_t autoSavePreset = 250; // last possible preset
bool firstLoop = true; bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot?
bool initDone = false;
// If we've detected the need to auto save, this will be non zero.
// configurable parameters unsigned long autoSaveAfter = 0;
unsigned long autoSaveAfterSec = 15; // 15s by default
uint8_t autoSavePreset = 250; // last possible preset uint8_t knownBrightness = 0;
bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot? uint8_t knownEffectSpeed = 0;
uint8_t knownEffectIntensity = 0;
// If we've detected the need to auto save, this will be non zero. uint8_t knownMode = 0;
unsigned long autoSaveAfter = 0; uint8_t knownPalette = 0;
uint8_t knownBrightness = 0; #ifdef USERMOD_FOUR_LINE_DISPLAY
uint8_t knownEffectSpeed = 0; FourLineDisplayUsermod* display;
uint8_t knownEffectIntensity = 0; #endif
uint8_t knownMode = 0;
uint8_t knownPalette = 0; // strings to reduce flash memory usage (used more than twice)
static const char _name[];
#ifdef USERMOD_FOUR_LINE_DISPLAY static const char _autoSaveAfterSec[];
FourLineDisplayUsermod* display; static const char _autoSavePreset[];
#endif 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) return; // setting 0 as autosave seconds disables autosave if (!autoSaveAfterSec) 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(_um_AutoSave)); // usermodname JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
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) { void 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(_um_AutoSave)]; JsonObject top = root[FPSTR(_name)];
if (!top.isNull() && top[FPSTR(_autoSaveAfterSec)] != nullptr) { if (!top.isNull() && top[FPSTR(_autoSaveAfterSec)] != nullptr) {
autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)].as<int>(); autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)].as<int>();
autoSavePreset = top[FPSTR(_autoSavePreset)].as<int>(); autoSavePreset = top[FPSTR(_autoSavePreset)].as<int>();
if (top[FPSTR(_autoSaveApplyOnBoot)].is<bool>()) { if (top[FPSTR(_autoSaveApplyOnBoot)].is<bool>()) {
// reading from cfg.json // reading from cfg.json
applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as<bool>(); applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as<bool>();
} else { } else {
// reading from POST message // reading from POST message
String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on
applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present
} }
DEBUG_PRINTLN(F("Autosave config (re)loaded.")); DEBUG_PRINTLN(F("Autosave config (re)loaded."));
} else { } else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)")); 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!). * 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. * This could be used in the future for the system to determine whether your usermod is installed.
*/ */
uint16_t getId() { uint16_t getId() {
return USERMOD_ID_AUTO_SAVE; 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";

View File

@ -67,15 +67,6 @@ typedef enum {
SH1106 // U8X8_SH1106_128X64_WINSTAR_HW_I2C SH1106 // U8X8_SH1106_128X64_WINSTAR_HW_I2C
} DisplayType; } 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 { class FourLineDisplayUsermod : public Usermod {
private: private:
@ -118,6 +109,15 @@ class FourLineDisplayUsermod : public Usermod {
// Set to 2 or 3 to mark lines 2 or 3. Other values ignored. // Set to 2 or 3 to mark lines 2 or 3. Other values ignored.
byte markLineNum = 0; 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 // If display does not work or looks corrupted check the
// constructor reference: // constructor reference:
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp // 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! * 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) {
JsonObject top = root.createNestedObject(FPSTR(_um_4LineDisplay)); JsonObject top = root.createNestedObject(FPSTR(_name));
JsonArray i2c_pin = top.createNestedArray("pin"); JsonArray i2c_pin = top.createNestedArray("pin");
i2c_pin.add(sclPin); i2c_pin.add(sclPin);
i2c_pin.add(sdaPin); i2c_pin.add(sdaPin);
top["type"] = type; top["type"] = type;
top[FPSTR(_4LD_flip)] = (bool) flip; top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_4LD_contrast)] = contrast; top[FPSTR(_contrast)] = contrast;
top[FPSTR(_4LD_refreshRate)] = refreshRate/1000; top[FPSTR(_refreshRate)] = refreshRate/1000;
top[FPSTR(_4LD_screenTimeOut)] = screenTimeout/1000; top[FPSTR(_screenTimeOut)] = screenTimeout/1000;
top[FPSTR(_4LD_sleepMode)] = (bool) sleepMode; top[FPSTR(_sleepMode)] = (bool) sleepMode;
top[FPSTR(_4LD_clockMode)] = (bool) clockMode; top[FPSTR(_clockMode)] = (bool) clockMode;
DEBUG_PRINTLN(F("4 Line Display config saved.")); DEBUG_PRINTLN(F("4 Line Display config saved."));
} }
@ -675,33 +675,33 @@ class FourLineDisplayUsermod : public Usermod {
int8_t newScl = sclPin; int8_t newScl = sclPin;
int8_t newSda = sdaPin; int8_t newSda = sdaPin;
JsonObject top = root[FPSTR(_um_4LineDisplay)]; JsonObject top = root[FPSTR(_name)];
if (!top.isNull() && top["pin"] != nullptr) { if (!top.isNull() && top["pin"] != nullptr) {
newScl = top["pin"][0]; newScl = top["pin"][0];
newSda = top["pin"][1]; newSda = top["pin"][1];
newType = top["type"]; newType = top["type"];
lineHeight = type==SH1106 ? 2 : 1; lineHeight = type==SH1106 ? 2 : 1;
if (top[FPSTR(_4LD_flip)].is<bool>()) { if (top[FPSTR(_flip)].is<bool>()) {
flip = top[FPSTR(_4LD_flip)].as<bool>(); flip = top[FPSTR(_flip)].as<bool>();
} else { } 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 flip = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true; needRedraw |= true;
} }
contrast = top[FPSTR(_4LD_contrast)].as<int>(); contrast = top[FPSTR(_contrast)].as<int>();
refreshRate = top[FPSTR(_4LD_refreshRate)].as<int>() * 1000; refreshRate = top[FPSTR(_refreshRate)].as<int>() * 1000;
screenTimeout = top[FPSTR(_4LD_screenTimeOut)].as<int>() * 1000; screenTimeout = top[FPSTR(_screenTimeOut)].as<int>() * 1000;
if (top[FPSTR(_4LD_sleepMode)].is<bool>()) { if (top[FPSTR(_sleepMode)].is<bool>()) {
sleepMode = top[FPSTR(_4LD_sleepMode)].as<bool>(); sleepMode = top[FPSTR(_sleepMode)].as<bool>();
} else { } 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 sleepMode = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true; needRedraw |= true;
} }
if (top[FPSTR(_4LD_clockMode)].is<bool>()) { if (top[FPSTR(_clockMode)].is<bool>()) {
clockMode = top[FPSTR(_4LD_clockMode)].as<bool>(); clockMode = top[FPSTR(_clockMode)].as<bool>();
} else { } 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 clockMode = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true; needRedraw |= true;
} }
@ -746,4 +746,13 @@ class FourLineDisplayUsermod : public Usermod {
uint16_t getId() { uint16_t getId() {
return USERMOD_ID_FOUR_LINE_DISP; return USERMOD_ID_FOUR_LINE_DISP;
} }
}; };
// 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";

View File

@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // 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 //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