Usermod settings v2

- added POST field parsing
- simpler handling in readFromConfig()
This commit is contained in:
Blaz Kristan 2021-06-27 15:32:33 +02:00
parent 0ae0f40628
commit 9e8aadb750
12 changed files with 400 additions and 433 deletions

View File

@ -426,6 +426,8 @@ 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.
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject& root) { bool readFromConfig(JsonObject& root) {
bool oldUseUSSensorTop = useUSSensorTop; bool oldUseUSSensorTop = useUSSensorTop;
@ -435,49 +437,41 @@ 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 top = root[FPSTR(_name)];
if (top.isNull()) {
JsonObject staircase = root[FPSTR(_name)]; DEBUG_PRINT(FPSTR(_name));
if (!staircase.isNull()) { DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
if (staircase[FPSTR(_enabled)].is<bool>()) { return false;
enabled = staircase[FPSTR(_enabled)].as<bool>();
} else {
String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
segment_delay_ms = min(10000,max(10,staircase[FPSTR(_segmentDelay)].as<int>())); // max delay 10s
on_time_ms = min(900,max(10,staircase[FPSTR(_onTime)].as<int>())) * 1000; // min 10s, max 15min
if (staircase[FPSTR(_useTopUltrasoundSensor)].is<bool>()) {
useUSSensorTop = staircase[FPSTR(_useTopUltrasoundSensor)].as<bool>();
} else {
String str = staircase[FPSTR(_useTopUltrasoundSensor)]; // checkbox -> off or on
useUSSensorTop = (bool)(str!="off"); // off is guaranteed to be present
} }
topPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_topPIRorTrigger_pin)].as<int>())); enabled = top[FPSTR(_enabled)] | enabled;
topEchoPin = min(39,max(-1,staircase[FPSTR(_topEcho_pin)].as<int>()));
if (staircase[FPSTR(_useBottomUltrasoundSensor)].is<bool>()) { segment_delay_ms = top[FPSTR(_segmentDelay)] | segment_delay_ms;
useUSSensorBottom = staircase[FPSTR(_useBottomUltrasoundSensor)].as<bool>(); segment_delay_ms = (unsigned long) min((unsigned long)10000,max((unsigned long)10,(unsigned long)segment_delay_ms)); // max delay 10s
} else {
String str = staircase[FPSTR(_useBottomUltrasoundSensor)]; // checkbox -> off or on on_time_ms = top[FPSTR(_onTime)] | on_time_ms/1000;
useUSSensorBottom = (bool)(str!="off"); // off is guaranteed to be present on_time_ms = min(900,max(10,(int)on_time_ms)) * 1000; // min 10s, max 15min
}
bottomPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_bottomPIRorTrigger_pin)].as<int>())); useUSSensorTop = top[FPSTR(_useTopUltrasoundSensor)] | useUSSensorTop;
bottomEchoPin = min(39,max(-1,staircase[FPSTR(_bottomEcho_pin)].as<int>())); topPIRorTriggerPin = top[FPSTR(_topPIRorTrigger_pin)] | topPIRorTriggerPin;
topMaxDist = min(150,max(30,staircase[FPSTR(_topEchoCm)].as<int>())); // max distnace ~1.5m (a lag of 9ms may be expected) topEchoPin = top[FPSTR(_topEcho_pin)] | topEchoPin;
bottomMaxDist = min(150,max(30,staircase[FPSTR(_bottomEchoCm)].as<int>())); // max distance ~1.5m (a lag of 9ms may be expected)
} else { useUSSensorBottom = top[FPSTR(_useBottomUltrasoundSensor)] | useUSSensorBottom;
DEBUG_PRINTLN(F("No config found. (Using defaults.)")); bottomPIRorTriggerPin = top[FPSTR(_bottomPIRorTrigger_pin)] | bottomPIRorTriggerPin;
configComplete = false; bottomEchoPin = top[FPSTR(_bottomEcho_pin)] | bottomEchoPin;
}
topMaxDist = top[FPSTR(_topEchoCm)] | topMaxDist;
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distnace ~1.5m (a lag of 9ms may be expected)
bottomMaxDist = top[FPSTR(_bottomEchoCm)] | bottomMaxDist;
bottomMaxDist = min(150,max(30,(int)bottomMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
DEBUG_PRINTLN(F("Staircase config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
// changing paramters from settings page // changing paramters from settings page
DEBUG_PRINTLN(F("Staircase config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
bool changed = false; bool changed = false;
if ((oldUseUSSensorTop != useUSSensorTop) || if ((oldUseUSSensorTop != useUSSensorTop) ||
(oldUseUSSensorBottom != useUSSensorBottom) || (oldUseUSSensorBottom != useUSSensorBottom) ||
@ -493,7 +487,8 @@ class Animated_Staircase : public Usermod {
} }
if (changed) setup(); if (changed) setup();
} }
return configComplete; // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
} }
/* /*

View File

@ -23,8 +23,12 @@
//class name. Use something descriptive and leave the ": public Usermod" part :) //class name. Use something descriptive and leave the ": public Usermod" part :)
class MyExampleUsermod : public Usermod { class MyExampleUsermod : public Usermod {
private: private:
// sample usermod default value for variable (you can also use constructor)
int userVar0 = 42;
//Private class members. You can declare variables and functions only accessible to your usermod here //Private class members. You can declare variables and functions only accessible to your usermod here
unsigned long lastTime = 0; unsigned long lastTime = 0;
public: public:
//Functions called by WLED //Functions called by WLED
@ -140,21 +144,14 @@ class MyExampleUsermod : public Usermod {
*/ */
bool readFromConfig(JsonObject& root) bool readFromConfig(JsonObject& root)
{ {
userVar0 = 42; //set your variables to their boot default value (this can also be done when declaring the variable) //set defaults for variables when declaring the variable (class definition or constructor)
JsonObject top = root["exampleUsermod"]; JsonObject top = root["exampleUsermod"];
if (!top.isNull()) { if (!top.isNull()) return false;
bool configComplete = true;
//check if value is there userVar0 = top["great"] | userVar0;
if (top.containsKey("great")) {
//convert value to the correct type
userVar0 = top["great"].as<int>();
} else configComplete = false;
if (configComplete) return true; // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
} return true;
return false;
} }

View File

@ -155,6 +155,7 @@ Delay <input type=\"number\" min=\"5\" max=\"300\" value=\"";
if (top.isNull()) return false; 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));
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true; return true;
} }

View File

@ -222,9 +222,8 @@ public:
void loop() void loop()
{ {
// only check sensors 10x/s // only check sensors 10x/s
unsigned long now = millis(); if (millis() - lastLoop < 100 || strip.isUpdating()) return;
if (now - lastLoop < 100) return; lastLoop = millis();
lastLoop = now;
if (!updatePIRsensorState()) { if (!updatePIRsensorState()) {
handleOffTimer(); handleOffTimer();
@ -320,6 +319,8 @@ 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
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject &root) bool readFromConfig(JsonObject &root)
{ {
@ -327,57 +328,31 @@ public:
int8_t oldPin = PIRsensorPin; int8_t oldPin = PIRsensorPin;
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) return false; if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
if (top["pin"] != nullptr) { DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
PIRsensorPin = min(39,max(-1,top["pin"].as<int>())); // check bounds return false;
} }
if (top[FPSTR(_enabled)] != nullptr) { PIRsensorPin = top["pin"] | PIRsensorPin;
if (top[FPSTR(_enabled)].is<bool>()) {
enabled = top[FPSTR(_enabled)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
}
if (top[FPSTR(_switchOffDelay)] != nullptr) { enabled = top[FPSTR(_enabled)] | enabled;
m_switchOffDelay = (top[FPSTR(_switchOffDelay)].as<int>() * 1000);
}
if (top[FPSTR(_onPreset)] != nullptr) { m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
m_onPreset = max(0,min(250,top[FPSTR(_onPreset)].as<int>()));
}
if (top[FPSTR(_offPreset)] != nullptr) { m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
m_offPreset = max(0,min(250,top[FPSTR(_offPreset)].as<int>())); m_onPreset = max(0,min(250,(int)m_onPreset));
}
if (top[FPSTR(_nightTime)] != nullptr) { m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
if (top[FPSTR(_nightTime)].is<bool>()) { m_offPreset = max(0,min(250,(int)m_offPreset));
m_nightTimeOnly = top[FPSTR(_nightTime)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[FPSTR(_nightTime)]; // checkbox -> off or on
m_nightTimeOnly = (bool)(str!="off"); // off is guaranteed to be present
}
}
if (top[FPSTR(_mqttOnly)] != nullptr) { m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
if (top[FPSTR(_mqttOnly)].is<bool>()) { m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
m_mqttOnly = top[FPSTR(_mqttOnly)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[FPSTR(_mqttOnly)]; // checkbox -> off or on
m_mqttOnly = (bool)(str!="off"); // off is guaranteed to be present
}
}
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// reading config prior to setup() // reading config prior to setup()
DEBUG_PRINTLN(F("PIR config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
if (oldPin != PIRsensorPin || oldEnabled != enabled) { if (oldPin != PIRsensorPin || oldEnabled != enabled) {
// check if pin is OK // check if pin is OK
@ -396,10 +371,10 @@ public:
if (enabled) { if (enabled) {
sensorPinState = digitalRead(PIRsensorPin); sensorPinState = digitalRead(PIRsensorPin);
} }
DEBUG_PRINTLN(F("PIR config (re)loaded."));
} }
DEBUG_PRINTLN(F(" config (re)loaded."));
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true; return true;
} }

View File

@ -173,31 +173,22 @@ public:
{ {
// we look for JSON object. // we look for JSON object.
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
if (!top.isNull()) DEBUG_PRINT(FPSTR(_name));
{ DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
if (top[FPSTR(_enabled)].is<bool>())
{
disabled = !top[FPSTR(_enabled)].as<bool>();
}
else
{
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
disabled = (bool)(str == "off"); // off is guaranteed to be present
};
readingInterval = min(120, max(10, top[FPSTR(_readInterval)].as<int>())) * 1000; // convert to ms
referenceVoltage = top[FPSTR(_referenceVoltage)].as<float>();
resistorValue = top[FPSTR(_resistorValue)].as<float>();
adcPrecision = top[FPSTR(_adcPrecision)].as<float>();
offset = top[FPSTR(_offset)].as<int>();
DEBUG_PRINTLN(F("Photoresistor config (re)loaded."));
}
else
{
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
return false; return false;
} }
disabled = !(top[FPSTR(_enabled)] | !disabled);
readingInterval = (top[FPSTR(_readInterval)] | readingInterval/1000) * 1000; // convert to ms
referenceVoltage = top[FPSTR(_referenceVoltage)] | referenceVoltage;
resistorValue = top[FPSTR(_resistorValue)] | resistorValue;
adcPrecision = top[FPSTR(_adcPrecision)] | adcPrecision;
offset = top[FPSTR(_offset)] | offset;
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(" config (re)loaded."));
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true; return true;
} }
}; };

View File

@ -17,11 +17,6 @@
#define USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL 60000 #define USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL 60000
#endif #endif
// how many seconds after boot to take first measurement, 20 seconds
#ifndef USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT
#define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000
#endif
class UsermodTemperature : public Usermod { class UsermodTemperature : public Usermod {
private: private:
@ -32,9 +27,12 @@ class UsermodTemperature : public Usermod {
int8_t temperaturePin = TEMPERATURE_PIN; int8_t temperaturePin = TEMPERATURE_PIN;
// measurement unit (true==°C, false==°F) // measurement unit (true==°C, false==°F)
bool degC = true; bool degC = true;
// using parasite power on the sensor
bool parasite = false;
// how often do we read from sensor?
unsigned long readingInterval = USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL; unsigned long readingInterval = USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL;
// set last reading as "40 sec before boot", so first reading is taken after 20 sec // set last reading as "40 sec before boot", so first reading is taken after 20 sec
unsigned long lastMeasurement = UINT32_MAX - (USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT); unsigned long lastMeasurement = UINT32_MAX - USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL;
// last time requestTemperatures was called // last time requestTemperatures was called
// used to determine when we can read the sensors temperature // used to determine when we can read the sensors temperature
// we have to wait at least 93.75 ms after requestTemperatures() is called // we have to wait at least 93.75 ms after requestTemperatures() is called
@ -42,37 +40,32 @@ class UsermodTemperature : public Usermod {
float temperature = -100; // default to -100, DS18B20 only goes down to -50C float temperature = -100; // default to -100, DS18B20 only goes down to -50C
// indicates requestTemperatures has been called but the sensor measurement is not complete // indicates requestTemperatures has been called but the sensor measurement is not complete
bool waitingForConversion = false; bool waitingForConversion = false;
// flag to indicate we have finished the first readTemperature call
// allows this library to report to the user how long until the first
// measurement
bool readTemperatureComplete = false;
// flag set at startup if DS18B20 sensor not found, avoids trying to keep getting // flag set at startup if DS18B20 sensor not found, avoids trying to keep getting
// temperature if flashed to a board without a sensor attached // temperature if flashed to a board without a sensor attached
bool disabled = false; bool enabled = true;
// 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 _enabled[]; static const char _enabled[];
static const char _readInterval[]; static const char _readInterval[];
static const char _parasite[];
//Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013
int16_t readDallas() { float readDallas() {
byte i; byte i;
byte data[2]; byte data[2];
int16_t result; // raw data from sensor int16_t result; // raw data from sensor
oneWire->reset(); if (!oneWire->reset()) return -127.0f; // send reset command and fail fast
oneWire->write(0xCC); // skip ROM oneWire->skip(); // skip ROM
oneWire->write(0xBE); // read (temperature) from EEPROM oneWire->write(0xBE); // read (temperature) from EEPROM
for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature
for (i=2; i < 8; i++) oneWire->read(); // read unused bytes for (i=2; i < 8; i++) oneWire->read(); // read unused bytes
result = (data[1]<<8) | data[0]; result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning
result >>= 4; // 9-bit precision accurate to 1°C (/16) if (data[1]&0x80) result |= 0xFF00; // fix negative value
if (data[1]&0x80) result |= 0xF000; // fix negative value
//if (data[0]&0x08) ++result;
oneWire->reset(); oneWire->reset();
oneWire->write(0xCC); // skip ROM oneWire->skip(); // skip ROM
oneWire->write(0x44,0); // request new temperature reading (without parasite power) oneWire->write(0x44,parasite); // request new temperature reading (without parasite power)
return result; return (float)result + ((data[0]&0x0008) ? 0.5f : 0.0f);
} }
void requestTemperatures() { void requestTemperatures() {
@ -86,7 +79,6 @@ class UsermodTemperature : public Usermod {
temperature = readDallas(); temperature = readDallas();
lastMeasurement = millis(); lastMeasurement = millis();
waitingForConversion = false; waitingForConversion = false;
readTemperatureComplete = true;
DEBUG_PRINTF("Read temperature %2.1f.\n", temperature); DEBUG_PRINTF("Read temperature %2.1f.\n", temperature);
} }
@ -118,23 +110,23 @@ class UsermodTemperature : public Usermod {
// pin retrieved from cfg.json (readFromConfig()) prior to running setup() // pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (!pinManager.allocatePin(temperaturePin,false)) { if (!pinManager.allocatePin(temperaturePin,false)) {
temperaturePin = -1; // allocation failed temperaturePin = -1; // allocation failed
disabled = true; enabled = false;
DEBUG_PRINTLN(F("Temperature pin allocation failed.")); DEBUG_PRINTLN(F("Temperature pin allocation failed."));
} else { } else {
if (!disabled) { if (enabled) {
// config says we are enabled // config says we are enabled
oneWire = new OneWire(temperaturePin); oneWire = new OneWire(temperaturePin);
if (!oneWire->reset()) if (!oneWire->reset())
disabled = true; // resetting 1-Wire bus yielded an error enabled = false; // resetting 1-Wire bus yielded an error
else else
while ((disabled=!findSensor()) && retries--) delay(25); // try to find sensor while ((enabled=findSensor()) && retries--) delay(25); // try to find sensor
} }
} }
initDone = true; initDone = true;
} }
void loop() { void loop() {
if (disabled || strip.isUpdating()) return; if (!enabled || strip.isUpdating()) return;
unsigned long now = millis(); unsigned long now = millis();
@ -189,7 +181,7 @@ class UsermodTemperature : public Usermod {
*/ */
void addToJsonInfo(JsonObject& root) { void addToJsonInfo(JsonObject& root) {
// dont add temperature to info if we are disabled // dont add temperature to info if we are disabled
if (disabled) return; if (!enabled) return;
JsonObject user = root["u"]; JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u"); if (user.isNull()) user = root.createNestedObject("u");
@ -197,14 +189,6 @@ class UsermodTemperature : public Usermod {
JsonArray temp = user.createNestedArray(FPSTR(_name)); JsonArray temp = user.createNestedArray(FPSTR(_name));
//temp.add(F("Loaded.")); //temp.add(F("Loaded."));
if (!readTemperatureComplete) {
// if we haven't read the sensor yet, let the user know
// that we are still waiting for the first measurement
temp.add((USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - millis()) / 1000);
temp.add(F(" sec until read"));
return;
}
if (temperature <= -100) { if (temperature <= -100) {
temp.add(0); temp.add(0);
temp.add(F(" Sensor Error!")); temp.add(F(" Sensor Error!"));
@ -239,51 +223,44 @@ class UsermodTemperature : public Usermod {
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(_name)); // usermodname JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = !disabled; top[FPSTR(_enabled)] = enabled;
top["pin"] = temperaturePin; // usermodparam top["pin"] = temperaturePin; // usermodparam
top["degC"] = degC; // usermodparam top["degC"] = degC; // usermodparam
top[FPSTR(_readInterval)] = readingInterval / 1000; top[FPSTR(_readInterval)] = readingInterval / 1000;
top[FPSTR(_parasite)] = parasite;
DEBUG_PRINTLN(F("Temperature config saved.")); DEBUG_PRINTLN(F("Temperature config saved."));
} }
/** /**
* 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
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool 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)];
int8_t newTemperaturePin = temperaturePin; int8_t newTemperaturePin = temperaturePin;
bool configComplete = true; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
if (!top.isNull() && top["pin"] != nullptr) { DEBUG_PRINT(FPSTR(_name));
if (top[FPSTR(_enabled)].is<bool>()) { DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
disabled = !top[FPSTR(_enabled)].as<bool>(); return false;
} else {
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<int>()));
if (top["degC"].is<bool>()) {
// reading from cfg.json
degC = top["degC"].as<bool>();
} 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[FPSTR(_readInterval)].as<int>())) * 1000; // convert to ms
DEBUG_PRINTLN(F("Temperature config (re)loaded."));
} else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
configComplete = false;
} }
enabled = top[FPSTR(_enabled)] | enabled;
newTemperaturePin = top["pin"] | newTemperaturePin;
degC = top["degC"] | degC;
readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000;
readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms
parasite = top[FPSTR(_parasite)] | parasite;
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
temperaturePin = newTemperaturePin; temperaturePin = newTemperaturePin;
DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
// changing parameters from settings page // changing paramters from settings page
if (newTemperaturePin != temperaturePin) { if (newTemperaturePin != temperaturePin) {
// deallocate pin and release memory // deallocate pin and release memory
delete oneWire; delete oneWire;
@ -292,8 +269,10 @@ class UsermodTemperature : public Usermod {
// initialise // initialise
setup(); setup();
} }
DEBUG_PRINTLN(F(" config (re)loaded."));
} }
return configComplete; // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_parasite)].isNull();
} }
uint16_t getId() uint16_t getId()
@ -306,3 +285,4 @@ class UsermodTemperature : public Usermod {
const char UsermodTemperature::_name[] PROGMEM = "Temperature"; const char UsermodTemperature::_name[] PROGMEM = "Temperature";
const char UsermodTemperature::_enabled[] PROGMEM = "enabled"; const char UsermodTemperature::_enabled[] PROGMEM = "enabled";
const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s"; const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s";
const char UsermodTemperature::_parasite[] PROGMEM = "parasite-pwr";

View File

@ -35,7 +35,7 @@ class MultiRelay : public Usermod {
// switch timer start time // switch timer start time
uint32_t _switchTimerStart = 0; uint32_t _switchTimerStart = 0;
// old brightness // old brightness
uint8_t _oldBrightness = 0; bool _oldBrightness = 0;
// usermod enabled // usermod enabled
bool enabled = false; // needs to be configured (no default config) bool enabled = false; // needs to be configured (no default config)
@ -265,7 +265,7 @@ class MultiRelay : public Usermod {
_relay[i].active = false; _relay[i].active = false;
} }
} }
_oldBrightness = bri; _oldBrightness = (bool)bri;
initDone = true; initDone = true;
} }
@ -288,8 +288,8 @@ class MultiRelay : public Usermod {
lastUpdate = millis(); lastUpdate = millis();
//set relay when LEDs turn on //set relay when LEDs turn on
if (_oldBrightness != bri) { if (_oldBrightness != (bool)bri) {
_oldBrightness = bri; _oldBrightness = (bool)bri;
_switchTimerStart = millis(); _switchTimerStart = millis();
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin>=0) _relay[i].active = true; if (_relay[i].pin>=0) _relay[i].active = true;
@ -347,55 +347,35 @@ 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
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool 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 false; if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
if (top[FPSTR(_enabled)] != nullptr) { enabled = top[FPSTR(_enabled)] | enabled;
if (top[FPSTR(_enabled)].is<bool>()) {
enabled = top[FPSTR(_enabled)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
}
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-"; String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-";
oldPin[i] = _relay[i].pin; oldPin[i] = _relay[i].pin;
if (top[parName+"pin"] != nullptr) _relay[i].pin = min(39,max(-1,top[parName+"pin"].as<int>())); _relay[i].pin = top[parName+"pin"] | _relay[i].pin;
_relay[i].mode = top[parName+FPSTR(_activeHigh)] | _relay[i].mode;
if (top[parName+FPSTR(_activeHigh)] != nullptr) { _relay[i].external = top[parName+FPSTR(_external)] | _relay[i].external;
if (top[parName+FPSTR(_activeHigh)].is<bool>()) { _relay[i].delay = top[parName+FPSTR(_delay_str)] | _relay[i].delay;
_relay[i].mode = top[parName+FPSTR(_activeHigh)].as<bool>(); // reading from cfg.json _relay[i].delay = min(600,max(0,abs((int)_relay[i].delay))); // bounds checking max 10min
} else {
// change from settings page
String str = top[parName+FPSTR(_activeHigh)]; // checkbox -> off or on
_relay[i].mode = (bool)(str!="off"); // off is guaranteed to be present
}
}
if (top[parName+FPSTR(_external)] != nullptr) {
if (top[parName+FPSTR(_external)].is<bool>()) {
_relay[i].external = top[parName+FPSTR(_external)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[parName+FPSTR(_external)]; // checkbox -> off or on
_relay[i].external = (bool)(str!="off"); // off is guaranteed to be present
}
}
_relay[i].delay = min(600,max(0,abs(top[parName+FPSTR(_delay_str)].as<int>())));
} }
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// reading config prior to setup() // reading config prior to setup()
DEBUG_PRINTLN(F("MultiRelay config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
// deallocate all pins 1st // deallocate all pins 1st
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
@ -411,8 +391,9 @@ class MultiRelay : public Usermod {
} }
_relay[i].active = false; _relay[i].active = false;
} }
DEBUG_PRINTLN(F("MultiRelay config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true; return true;
} }

View File

@ -33,12 +33,12 @@ class AutoSaveUsermod : public Usermod {
bool enabled = true; bool enabled = true;
// configurable parameters // configurable parameters
unsigned long autoSaveAfterSec = 15; // 15s by default uint16_t 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; uint16_t autoSaveAfter = 0;
uint8_t knownBrightness = 0; uint8_t knownBrightness = 0;
uint8_t knownEffectSpeed = 0; uint8_t knownEffectSpeed = 0;
@ -97,7 +97,7 @@ class AutoSaveUsermod : public Usermod {
* Da loop. * Da loop.
*/ */
void loop() { void loop() {
if (!autoSaveAfterSec || !enabled) 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();
@ -197,34 +197,28 @@ class AutoSaveUsermod : 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 :)
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject& root) { bool readFromConfig(JsonObject& root) {
// we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} // we look for JSON object: {"Autosave": {"enabled": true, "autoSaveAfterSec": 10, "autoSavePreset": 250, ...}}
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_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false; return false;
} }
if (top[FPSTR(_autoSaveEnabled)].is<bool>()) { enabled = top[FPSTR(_autoSaveEnabled)] | enabled;
// reading from cfg.json autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)] | autoSaveAfterSec;
enabled = top[FPSTR(_autoSaveEnabled)].as<bool>(); autoSaveAfterSec = (uint16_t) min(3600,max(10,(int)autoSaveAfterSec)); // bounds checking
} else { autoSavePreset = top[FPSTR(_autoSavePreset)] | autoSavePreset;
// reading from POST message autoSavePreset = (uint8_t) min(250,max(100,(int)autoSavePreset)); // bounds checking
String str = top[FPSTR(_autoSaveEnabled)]; // checkbox -> off or on applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)] | applyAutoSaveOnBoot;
enabled = (bool)(str!="off"); // off is guaranteed to be present DEBUG_PRINT(FPSTR(_name));
} DEBUG_PRINTLN(F(" config (re)loaded."));
autoSaveAfterSec = min(3600,max(10,top[FPSTR(_autoSaveAfterSec)].as<int>()));
autoSavePreset = min(250,max(100,top[FPSTR(_autoSavePreset)].as<int>())); // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
if (top[FPSTR(_autoSaveApplyOnBoot)].is<bool>()) {
// reading from cfg.json
applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as<bool>();
} 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."));
return true; return true;
} }

View File

@ -54,18 +54,21 @@
#define LINE_BUFFER_SIZE 16+1 #define LINE_BUFFER_SIZE 16+1
typedef enum { typedef enum {
FLD_LINE_4_BRIGHTNESS = 0, FLD_LINE_BRIGHTNESS = 0,
FLD_LINE_4_EFFECT_SPEED, FLD_LINE_EFFECT_SPEED,
FLD_LINE_4_EFFECT_INTENSITY, FLD_LINE_EFFECT_INTENSITY,
FLD_LINE_4_MODE, FLD_LINE_MODE,
FLD_LINE_4_PALETTE FLD_LINE_PALETTE,
FLD_LINE_TIME
} Line4Type; } Line4Type;
typedef enum { typedef enum {
NONE = 0, NONE = 0,
SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C
SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C
SSD1306_64 // U8X8_SSD1306_128X64_NONAME_HW_I2C SSD1306_64, // U8X8_SSD1306_128X64_NONAME_HW_I2C
SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C
SSD1305_64 // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C
} DisplayType; } DisplayType;
class FourLineDisplayUsermod : public Usermod { class FourLineDisplayUsermod : public Usermod {
@ -87,9 +90,6 @@ class FourLineDisplayUsermod : public Usermod {
bool sleepMode = true; // allow screen sleep? bool sleepMode = true; // allow screen sleep?
bool clockMode = false; // display clock bool clockMode = false; // display clock
// needRedraw marks if redraw is required to prevent often redrawing.
bool needRedraw = true;
// Next variables hold the previous known values to determine if redraw is // Next variables hold the previous known values to determine if redraw is
// required. // required.
String knownSsid = ""; String knownSsid = "";
@ -106,7 +106,7 @@ class FourLineDisplayUsermod : public Usermod {
unsigned long lastUpdate = 0; unsigned long lastUpdate = 0;
unsigned long lastRedraw = 0; unsigned long lastRedraw = 0;
unsigned long overlayUntil = 0; unsigned long overlayUntil = 0;
Line4Type lineFourType = FLD_LINE_4_BRIGHTNESS; Line4Type lineType = FLD_LINE_BRIGHTNESS;
// 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;
@ -130,7 +130,7 @@ class FourLineDisplayUsermod : public Usermod {
// 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() {
if (type==NONE) return; if (type == NONE) return;
if (!pinManager.allocatePin(sclPin)) { sclPin = -1; type = NONE; return;} if (!pinManager.allocatePin(sclPin)) { sclPin = -1; type = NONE; return;}
if (!pinManager.allocatePin(sdaPin)) { pinManager.deallocatePin(sclPin); sclPin = sdaPin = -1; type = NONE; return; } if (!pinManager.allocatePin(sdaPin)) { pinManager.deallocatePin(sclPin); sclPin = sdaPin = -1; type = NONE; return; }
switch (type) { switch (type) {
@ -141,6 +141,7 @@ class FourLineDisplayUsermod : public Usermod {
else else
#endif #endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
lineHeight = 1;
break; break;
case SH1106: case SH1106:
#ifdef ESP8266 #ifdef ESP8266
@ -149,6 +150,7 @@ class FourLineDisplayUsermod : public Usermod {
else else
#endif #endif
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
lineHeight = 2;
break; break;
case SSD1306_64: case SSD1306_64:
#ifdef ESP8266 #ifdef ESP8266
@ -157,6 +159,25 @@ class FourLineDisplayUsermod : public Usermod {
else else
#endif #endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
lineHeight = 2;
break;
case SSD1305:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
lineHeight = 1;
break;
case SSD1305_64:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
lineHeight = 2;
break; break;
default: default:
u8x8 = nullptr; u8x8 = nullptr;
@ -179,9 +200,7 @@ class FourLineDisplayUsermod : public Usermod {
* Da loop. * Da loop.
*/ */
void loop() { void loop() {
if (millis() - lastUpdate < (clockMode?1000:refreshRate)) { if (millis() - lastUpdate < (clockMode?1000:refreshRate) || strip.isUpdating()) return;
return;
}
lastUpdate = millis(); lastUpdate = millis();
redraw(false); redraw(false);
@ -233,9 +252,12 @@ class FourLineDisplayUsermod : public Usermod {
* or if forceRedraw). * or if forceRedraw).
*/ */
void redraw(bool forceRedraw) { void redraw(bool forceRedraw) {
static bool showName = false;
unsigned long now = millis();
if (type==NONE) return; if (type==NONE) return;
if (overlayUntil > 0) { if (overlayUntil > 0) {
if (millis() >= overlayUntil) { if (now >= overlayUntil) {
// Time to display the overlay has elapsed. // Time to display the overlay has elapsed.
overlayUntil = 0; overlayUntil = 0;
forceRedraw = true; forceRedraw = true;
@ -247,74 +269,63 @@ class FourLineDisplayUsermod : public Usermod {
} }
// Check if values which are shown on display changed from the last time. // Check if values which are shown on display changed from the last time.
if (forceRedraw) { if (forceRedraw ||
needRedraw = true; (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) ||
} else if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : Network.localIP())) ||
needRedraw = true; (knownBrightness != bri) ||
} else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { (knownEffectSpeed != effectSpeed) ||
needRedraw = true; (knownEffectIntensity != effectIntensity) ||
} else if (knownBrightness != bri) { (knownMode != strip.getMode()) ||
needRedraw = true; (knownPalette != strip.getSegment(0).palette)) {
} else if (knownEffectSpeed != effectSpeed) { knownHour = 99; // force time update
needRedraw = true; clear();
} else if (knownEffectIntensity != effectIntensity) { } else if (sleepMode && !displayTurnedOff && ((now - lastRedraw)/1000)%5 == 0) {
needRedraw = true; // change line every 5s
} else if (knownMode != strip.getMode()) { showName = !showName;
needRedraw = true; switch (lineType) {
} else if (knownPalette != strip.getSegment(0).palette) { case FLD_LINE_BRIGHTNESS:
needRedraw = true; lineType = FLD_LINE_EFFECT_SPEED;
break;
case FLD_LINE_MODE:
lineType = FLD_LINE_BRIGHTNESS;
break;
case FLD_LINE_PALETTE:
lineType = clockMode ? FLD_LINE_MODE : FLD_LINE_BRIGHTNESS;
break;
case FLD_LINE_EFFECT_SPEED:
lineType = FLD_LINE_EFFECT_INTENSITY;
break;
case FLD_LINE_EFFECT_INTENSITY:
lineType = FLD_LINE_PALETTE;
break;
default:
lineType = FLD_LINE_MODE;
break;
} }
knownHour = 99; // force time update
if (!needRedraw) { } else {
// Nothing to change. // Nothing to change.
// Turn off display after 3 minutes with no change. // Turn off display after 3 minutes with no change.
if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) { if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) {
// We will still check if there is a change in redraw() // We will still check if there is a change in redraw()
// and turn it back on if it changed. // and turn it back on if it changed.
knownHour = 99; // force screen clear clear(); // force screen clear
sleepOrClock(true); sleepOrClock(true);
} else if (displayTurnedOff && clockMode) { } else if (displayTurnedOff && clockMode) {
showTime(); showTime();
} else if ((millis() - lastRedraw)/1000%3 == 0) {
// change 4th line every 3s
switch (lineFourType) {
case FLD_LINE_4_BRIGHTNESS:
setLineFourType(FLD_LINE_4_EFFECT_SPEED);
break;
case FLD_LINE_4_MODE:
setLineFourType(FLD_LINE_4_BRIGHTNESS);
break;
case FLD_LINE_4_PALETTE:
setLineFourType(clockMode ? FLD_LINE_4_MODE : FLD_LINE_4_BRIGHTNESS);
break;
case FLD_LINE_4_EFFECT_SPEED:
setLineFourType(FLD_LINE_4_EFFECT_INTENSITY);
break;
case FLD_LINE_4_EFFECT_INTENSITY:
setLineFourType(FLD_LINE_4_PALETTE);
break;
default:
break;
}
drawLineFour();
} }
return; return;
} else {
knownHour = 99; // force time display
clear();
} }
needRedraw = false; // do not update lastRedraw marker if just switching row contenet
lastRedraw = millis(); if (((now - lastRedraw)/1000)%5 != 0) lastRedraw = now;
if (displayTurnedOff) {
// Turn the display back on // Turn the display back on
sleepOrClock(false); if (displayTurnedOff) sleepOrClock(false);
}
// Update last known values. // Update last known values.
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); knownIp = apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
knownBrightness = bri; knownBrightness = bri;
knownMode = strip.getMode(); knownMode = strip.getMode();
knownPalette = strip.getSegment(0).palette; knownPalette = strip.getSegment(0).palette;
@ -324,7 +335,7 @@ class FourLineDisplayUsermod : public Usermod {
// Do the actual drawing // Do the actual drawing
// First row with Wifi name // First row with Wifi name
drawGlyph(0, 0, 80, u8x8_font_open_iconic_embedded_1x1); // wifi icon drawGlyph(0, 0, 80, u8x8_font_open_iconic_embedded_1x1); // home icon
String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0);
drawString(1, 0, ssidString.c_str()); drawString(1, 0, ssidString.c_str());
// Print `~` char to indicate that SSID is longer, than our display // Print `~` char to indicate that SSID is longer, than our display
@ -333,46 +344,54 @@ class FourLineDisplayUsermod : public Usermod {
} }
// Second row with IP or Psssword // Second row with IP or Psssword
drawGlyph(0, lineHeight, 68, u8x8_font_open_iconic_embedded_1x1); // home icon drawGlyph(0, lineHeight, 68, u8x8_font_open_iconic_embedded_1x1); // wifi icon
// Print password in AP mode and if led is OFF. // Print password in AP mode and if led is OFF.
if (apActive && bri == 0) { if (apActive && bri == 0) {
drawString(1, lineHeight, apPass); drawString(1, lineHeight, apPass);
} else { } else {
drawString(1, lineHeight, (knownIp.toString()).c_str()); // alternate IP address and server name
String secondLine = knownIp.toString();
if (showName && strcmp(serverDescription, "WLED") != 0) {
secondLine = serverDescription;
}
for (uint8_t i=secondLine.length(); i<getCols()-1; i++) secondLine += ' ';
drawString(1, lineHeight, secondLine.c_str());
} }
// Third row with mode name or current time // draw third and fourth row
if (clockMode) showTime(false); drawLine(2, clockMode ? lineType : FLD_LINE_MODE);
else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 2); drawLine(3, clockMode ? FLD_LINE_TIME : lineType);
// Fourth row
drawLineFour();
drawGlyph(0, 2*lineHeight, 66 + (bri > 0 ? 3 : 0), u8x8_font_open_iconic_weather_2x2); // sun/moon icon drawGlyph(0, 2*lineHeight, 66 + (bri > 0 ? 3 : 0), u8x8_font_open_iconic_weather_2x2); // sun/moon icon
//if (markLineNum>1) drawGlyph(2, markLineNum*lineHeight, 66, u8x8_font_open_iconic_arrow_1x1); // arrow icon //if (markLineNum>1) drawGlyph(2, markLineNum*lineHeight, 66, u8x8_font_open_iconic_arrow_1x1); // arrow icon
} }
void drawLineFour() { void drawLine(uint8_t line, Line4Type lineType) {
char lineBuffer[LINE_BUFFER_SIZE]; char lineBuffer[LINE_BUFFER_SIZE];
switch(lineFourType) { switch(lineType) {
case FLD_LINE_4_BRIGHTNESS: case FLD_LINE_BRIGHTNESS:
sprintf_P(lineBuffer, PSTR("Brightness %3d"), bri); sprintf_P(lineBuffer, PSTR("Brightness %3d"), bri);
drawString(2, 3*lineHeight, lineBuffer); drawString(2, line*lineHeight, lineBuffer);
break; break;
case FLD_LINE_4_EFFECT_SPEED: case FLD_LINE_EFFECT_SPEED:
sprintf_P(lineBuffer, PSTR("FX Speed %3d"), effectSpeed); sprintf_P(lineBuffer, PSTR("FX Speed %3d"), effectSpeed);
drawString(2, 3*lineHeight, lineBuffer); drawString(2, line*lineHeight, lineBuffer);
break; break;
case FLD_LINE_4_EFFECT_INTENSITY: case FLD_LINE_EFFECT_INTENSITY:
sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity); sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity);
drawString(2, 3*lineHeight, lineBuffer); drawString(2, line*lineHeight, lineBuffer);
break; break;
case FLD_LINE_4_MODE: case FLD_LINE_MODE:
showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); showCurrentEffectOrPalette(knownMode, JSON_mode_names, line);
break;
case FLD_LINE_PALETTE:
showCurrentEffectOrPalette(knownPalette, JSON_palette_names, line);
break;
case FLD_LINE_TIME:
showTime(false);
break; break;
case FLD_LINE_4_PALETTE:
default: default:
showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 3); // unknown type, do nothing
break; break;
} }
} }
@ -447,23 +466,6 @@ class FourLineDisplayUsermod : public Usermod {
overlayUntil = millis() + showHowLong; overlayUntil = millis() + showHowLong;
} }
/**
* Specify what data should be defined on line 4
* (the last line).
*/
void setLineFourType(Line4Type newLineFourType) {
if (newLineFourType == FLD_LINE_4_BRIGHTNESS ||
newLineFourType == FLD_LINE_4_EFFECT_SPEED ||
newLineFourType == FLD_LINE_4_EFFECT_INTENSITY ||
newLineFourType == FLD_LINE_4_MODE ||
newLineFourType == FLD_LINE_4_PALETTE) {
lineFourType = newLineFourType;
} else {
// Unknown value
lineFourType = FLD_LINE_4_BRIGHTNESS;
}
}
/** /**
* Line 3 or 4 (last two lines) can be marked with an * Line 3 or 4 (last two lines) can be marked with an
* arrow in the first column. Pass 2 or 3 to this to * arrow in the first column. Pass 2 or 3 to this to
@ -487,8 +489,7 @@ class FourLineDisplayUsermod : public Usermod {
if (clockMode) showTime(); if (clockMode) showTime();
else setPowerSave(1); else setPowerSave(1);
displayTurnedOff = true; displayTurnedOff = true;
} } else {
else {
setPowerSave(0); setPowerSave(0);
displayTurnedOff = false; displayTurnedOff = false;
} }
@ -510,7 +511,7 @@ class FourLineDisplayUsermod : public Usermod {
// Time hasn't changed. // Time hasn't changed.
if (!fullScreen) return; if (!fullScreen) return;
} else { } else {
if (fullScreen) clear(); //if (fullScreen) clear();
} }
knownMinute = minuteCurrent; knownMinute = minuteCurrent;
knownHour = hourCurrent; knownHour = hourCurrent;
@ -520,7 +521,7 @@ class FourLineDisplayUsermod : public Usermod {
if (fullScreen) if (fullScreen)
draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays
else else
drawString(2, lineHeight*2, lineBuffer); drawString(2, lineHeight*3, lineBuffer);
byte showHour = hourCurrent; byte showHour = hourCurrent;
boolean isAM = false; boolean isAM = false;
@ -544,11 +545,12 @@ class FourLineDisplayUsermod : public Usermod {
if (fullScreen) { if (fullScreen) {
draw2x2String(TIME_INDENT+2, lineHeight*2, lineBuffer); draw2x2String(TIME_INDENT+2, lineHeight*2, lineBuffer);
sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent);
if (!useAMPM) drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line
} else {
drawString(9+(useAMPM?0:2), lineHeight*2, lineBuffer);
}
if (useAMPM) drawString(12+(fullScreen?0:2), lineHeight*2, (isAM ? "AM" : "PM"), true); if (useAMPM) drawString(12+(fullScreen?0:2), lineHeight*2, (isAM ? "AM" : "PM"), true);
else drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line
} else {
drawString(9+(useAMPM?0:2), lineHeight*3, lineBuffer);
if (useAMPM) drawString(12+(fullScreen?0:2), lineHeight*3, (isAM ? "AM" : "PM"), true);
}
} }
/* /*
@ -621,55 +623,34 @@ class FourLineDisplayUsermod : public Usermod {
int8_t newScl = sclPin; int8_t newScl = sclPin;
int8_t newSda = sdaPin; int8_t newSda = sdaPin;
bool configComplete = true;
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (!top.isNull() && top["pin"] != nullptr) { if (top.isNull()) {
newScl = top["pin"][0]; DEBUG_PRINT(FPSTR(_name));
newSda = top["pin"][1]; DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
newType = top["type"]; return false;
if (top[FPSTR(_flip)].is<bool>()) {
flip = top[FPSTR(_flip)].as<bool>();
} else {
String str = top[FPSTR(_flip)]; // checkbox -> off or on
flip = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true;
}
contrast = top[FPSTR(_contrast)].as<int>();
refreshRate = top[FPSTR(_refreshRate)].as<int>() * 1000;
screenTimeout = top[FPSTR(_screenTimeOut)].as<int>() * 1000;
if (top[FPSTR(_sleepMode)].is<bool>()) {
sleepMode = top[FPSTR(_sleepMode)].as<bool>();
} else {
String str = top[FPSTR(_sleepMode)]; // checkbox -> off or on
sleepMode = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true;
}
if (top[FPSTR(_clockMode)].is<bool>()) {
clockMode = top[FPSTR(_clockMode)].as<bool>();
} else {
String str = top[FPSTR(_clockMode)]; // checkbox -> off or on
clockMode = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true;
}
DEBUG_PRINTLN(F("4 Line Display config (re)loaded."));
} else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
configComplete = false;
} }
newScl = top["pin"][0] | newScl;
newSda = top["pin"][1] | newSda;
newType = top["type"] | newType;
flip = top[FPSTR(_flip)] | flip;
contrast = top[FPSTR(_contrast)] | contrast;
refreshRate = (top[FPSTR(_refreshRate)] | refreshRate/1000) * 1000;
screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000;
sleepMode = top[FPSTR(_sleepMode)] | sleepMode;
clockMode = top[FPSTR(_clockMode)] | clockMode;
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
sclPin = newScl; sclPin = newScl;
sdaPin = newSda; sdaPin = newSda;
type = newType; type = newType;
lineHeight = type==SSD1306 ? 1 : 2; DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
// changing paramters from settings page // changing paramters from settings page
if (sclPin!=newScl || sdaPin!=newSda || type!=newType) { if (sclPin!=newScl || sdaPin!=newSda || type!=newType) {
if (type==SSD1306) delete (static_cast<U8X8*>(u8x8)); if (type != NONE) delete (static_cast<U8X8*>(u8x8));
if (type==SH1106) delete (static_cast<U8X8*>(u8x8));
if (type==SSD1306_64) delete (static_cast<U8X8*>(u8x8));
pinManager.deallocatePin(sclPin); pinManager.deallocatePin(sclPin);
pinManager.deallocatePin(sdaPin); pinManager.deallocatePin(sdaPin);
sclPin = newScl; sclPin = newScl;
@ -677,18 +658,17 @@ class FourLineDisplayUsermod : public Usermod {
if (newScl<0 || newSda<0) { if (newScl<0 || newSda<0) {
type = NONE; type = NONE;
return true; return true;
} else } else type = newType;
type = newType;
lineHeight = type==SSD1306 ? 1 : 2;
setup(); setup();
needRedraw |= true; needsRedraw |= true;
} }
setContrast(contrast); setContrast(contrast);
setFlipMode(flip); setFlipMode(flip);
if (needsRedraw && !wakeDisplay()) redraw(true); if (needsRedraw && !wakeDisplay()) redraw(true);
DEBUG_PRINTLN(F(" config (re)loaded."));
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return configComplete; return true;
} }
/* /*

View File

@ -29,7 +29,10 @@
if (numM > 0 || locip) ldS(); if (numM > 0 || locip) ldS();
else gId("um").innerHTML = "No Usermods installed."; else gId("um").innerHTML = "No Usermods installed.";
} }
function check(o,k) { // https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer
function isF(n) { return n === +n && n !== (n|0); }
function isI(n) { return n === +n && n === (n|0); }
function check(o,k) { // input object, pin owner key
var n = o.name.replace("[]","").substr(-3); var n = o.name.replace("[]","").substr(-3);
if (o.type=="number" && n.substr(0,3)=="pin") { if (o.type=="number" && n.substr(0,3)=="pin") {
for (var i=0; i<pins.length; i++) { for (var i=0; i<pins.length; i++) {
@ -58,10 +61,12 @@
} }
} }
} }
function addField(k,f,o,a=false) { function addField(k,f,o,a=false) { //key, field, (sub)object, isArray
if (isO(o)) { if (isO(o)) {
for (const [s,v] of Object.entries(o)) { for (const [s,v] of Object.entries(o)) {
addField(k,s,v); // possibility to nest objects (only 1 level)
if (f!=='unknown' && !k.includes(":")) addField(k+":"+f,s,v);
else addField(k,s,v);
} }
} else if (Array.isArray(o)) { } else if (Array.isArray(o)) {
for (var j=0; j<o.length; j++) { for (var j=0; j<o.length; j++) {
@ -71,17 +76,29 @@
var t,c; var t,c;
switch (typeof o) { switch (typeof o) {
case "boolean": case "boolean":
t = "checkbox"; c = o ? `checked value="on"` : ""; break; t = "checkbox"; c = o ? `checked value="true"` : "";
break;
case "number": case "number":
t = "number"; c = `value="${parseInt(o,10)}"`; break; c = `value="${o}"`;
case "string": if (isF(o)) {
t = "text"; c = `value="${o}"`; break; c += ` step="0.01" class="xxl"`;
default: t = "float";
t = "text"; c = `value="${o}"`; break; } else {
if (f.substr(-3)==="pin") c += ' max="39" min="-1" class="small"';
else c += ` class="big"`;
t = "int";
} }
break;
default:
t = "text"; c = `value="${o}" style="width:150px;"`;
break;
}
if (k.includes(":")) urows += k.substr(k.indexOf(":")+1);
urows += ` ${f}: `;
// https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes // https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes
if (t=="checkbox") urows += `<input type="hidden" name="${k}_${f}${a?"[]":""}" value="off">`; if (t=="checkbox") urows += `<input type="hidden" name="${k}:${f}${a?"[]":""}" value="false">`;
urows += `${f}: <input type="${t}" name="${k}_${f}${a?"[]":""}" ${c} oninput="check(this,'${k}')"><br>`; else if (!a) urows += `<input type="hidden" name="${k}:${f}${a?"[]":""}" value="${t}">`;
urows += `<input type="${t==="float"||t==="int"?"number":t}" name="${k}:${f}${a?"[]":""}" ${c} oninput="check(this,'${k.substr(k.indexOf(":")+1)}')"><br>`;
} }
} }
function ldS() { function ldS() {

View File

@ -3,7 +3,7 @@ body {
text-align: center; text-align: center;
background: #222; background: #222;
color: #fff; color: #fff;
line-height: 200%; line-height: 200%%; /* %% because of AsyncWebServer */
margin: 0; margin: 0;
} }
hr { hr {
@ -31,8 +31,38 @@ input {
font-family: Verdana, sans-serif; font-family: Verdana, sans-serif;
border: 0.5ch solid #333; border: 0.5ch solid #333;
} }
input[type="text"] {
font-size: medium;
}
input[type="number"] { input[type="number"] {
width: 4em; width: 4em;
font-size: medium;
margin: 2px;
}
input[type="number"].xxl {
width: 100px;
}
input[type="number"].big {
width: 85px;
}
input[type="number"].med {
width: 55px;
}
input[type="number"].small {
width: 40px;
}
select {
margin: 2px;
font-size: medium;
}
input[type="checkbox"] {
/* Double-sized Checkboxes */
-ms-transform: scale(2); /* IE */
-moz-transform: scale(2); /* FF */
-webkit-transform: scale(2); /* Safari and Chrome */
-o-transform: scale(2); /* Opera */
transform: scale(2);
margin-right: 10px;
} }
select { select {
background: #333; background: #333;

View File

@ -431,8 +431,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
String value = request->arg(i); String value = request->arg(i);
// POST request parameters are combined as <usermodname>_<usermodparameter> // POST request parameters are combined as <usermodname>_<usermodparameter>
uint8_t umNameEnd = name.indexOf("_"); int umNameEnd = name.indexOf(":");
if (!umNameEnd) break; // parameter does not contain "_" -> wrong if (umNameEnd<1) break; // parameter does not contain ":" or on 1st place -> wrong
JsonObject mod = um[name.substring(0,umNameEnd)]; // get a usermod JSON object JsonObject mod = um[name.substring(0,umNameEnd)]; // get a usermod JSON object
if (mod.isNull()) { if (mod.isNull()) {
@ -442,30 +442,57 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
DEBUG_PRINT(":"); DEBUG_PRINT(":");
name = name.substring(umNameEnd+1); // remove mod name from string name = name.substring(umNameEnd+1); // remove mod name from string
// if the resulting name still contains ":" this means nested object
JsonObject subObj;
int umSubObj = name.indexOf(":");
DEBUG_PRINTF("(%d):",umSubObj);
if (umSubObj>0) {
subObj = mod[name.substring(0,umSubObj)];
if (subObj.isNull())
subObj = mod.createNestedObject(name.substring(0,umSubObj));
name = name.substring(umSubObj+1); // remove nested object name from string
} else {
subObj = mod;
}
DEBUG_PRINT(name);
// check if parameters represent array // check if parameters represent array
if (name.endsWith("[]")) { if (name.endsWith("[]")) {
name.replace("[]",""); name.replace("[]","");
if (!mod[name].is<JsonArray>()) { if (!subObj[name].is<JsonArray>()) {
JsonArray ar = mod.createNestedArray(name); JsonArray ar = subObj.createNestedArray(name);
ar.add(value); ar.add(value.toInt());
j=0; j=0;
} else { } else {
mod[name].add(value); subObj[name].add(value.toInt());
j++; j++;
} }
DEBUG_PRINT(name);
DEBUG_PRINT("["); DEBUG_PRINT("[");
DEBUG_PRINT(j); DEBUG_PRINT(j);
DEBUG_PRINT("] = "); DEBUG_PRINT("] = ");
DEBUG_PRINTLN(value); DEBUG_PRINTLN(value);
} else { } else {
mod.remove(name); // checkboxes get two fields (first is always "off", existence of second depends on checkmark and may be "on") // we are using a hidden field with the same name as our parameter (!before the actual parameter!)
mod[name] = value; // to describe the type of parameter (text,float,int), for boolean patameters the first field contains "off"
DEBUG_PRINT(name); // so checkboxes have one or two fields (first is always "false", existence of second depends on checkmark and may be "true")
if (subObj[name].isNull()) {
// the first occurence of the field describes the parameter type (used in next loop)
if (value == "false") subObj[name] = false; // checkboxes may have only one field
else subObj[name] = value;
} else {
String type = subObj[name].as<String>(); // get previously stored value as a type
if (subObj[name].is<bool>()) subObj[name] = true; // checkbox/boolean
else if (type == "float") subObj[name] = value.toDouble();
else if (type == "int") subObj[name] = value.toInt();
else subObj[name] = value; // text fields
}
DEBUG_PRINT(" = "); DEBUG_PRINT(" = ");
DEBUG_PRINTLN(value); DEBUG_PRINTLN(value);
} }
} }
#ifdef WLED_DEBUG
serializeJson(um,Serial);
#endif
usermods.readFromConfig(um); // force change of usermod parameters usermods.readFromConfig(um); // force change of usermod parameters
} }
@ -643,7 +670,6 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
nightlightActive = false; //always disable nightlight when toggling nightlightActive = false; //always disable nightlight when toggling
} }
} }
#endif #endif
//set hue //set hue