Rewritten usermod config saving/reloading.

Changed temperature reading (to work on ESP32 more reliably).
Added Animated staircase usermod to the collection.
This commit is contained in:
Blaz Kristan 2021-04-17 17:04:36 +02:00
parent 585f8f4683
commit 1cd56decab
7 changed files with 380 additions and 313 deletions

View File

@ -12,22 +12,28 @@
#include "Animated_Staircase_config.h"
#define USERMOD_ID_ANIMATED_STAIRCASE 1011
/* Initial configuration (available in API and stored in flash) */
bool enabled = true; // Enable this usermod
class Animated_Staircase : public Usermod {
private:
/* configuration (available in API and stored in flash) */
bool enabled = false; // Enable this usermod
unsigned long segment_delay_ms = 150; // Time between switching each segment
unsigned long on_time_ms = 5 * 1000; // The time for the light to stay on
#ifndef TOP_PIR_PIN
int8_t topPIRorTriggerPin = -1; // disabled
int8_t bottomPIRorTriggerPin = -1; // disabled
int8_t topEchoPin = -1; // disabled
int8_t bottomEchoPin = -1; // disabled
bool useUSSensorTop = false; // using PIR or UltraSound sensor?
bool useUSSensorBottom = false; // using PIR or UltraSound sensor?
unsigned int topMaxTimeUs = 1749; // default echo timout, top
#endif
#ifndef BOTTOM_PIR_PIN
unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom
#endif
/* runtime variables */
bool initDone = false;
// Time between checking of the sensors
const int scanDelay = 50;
class Animated_Staircase : public Usermod {
private:
// Lights on or off.
// Flipping this will start a transition.
bool on = false;
@ -63,8 +69,6 @@ class Animated_Staircase : public Usermod {
byte maxSegmentId = 1;
byte mainSegmentId = 0;
bool saveState = false;
// These values are used by the API to read the
// last sensor state, or trigger a sensor
// through the API
@ -74,8 +78,8 @@ class Animated_Staircase : public Usermod {
bool bottomSensorWrite = false;
void updateSegments() {
mainSegmentId = strip.getMainSegmentId();
WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
// mainSegmentId = strip.getMainSegmentId();
// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
WS2812FX::Segment* segments = strip.getSegments();
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
if (!segments->isActive()) {
@ -134,17 +138,15 @@ class Animated_Staircase : public Usermod {
if ((millis() - lastScanTime) > scanDelay) {
lastScanTime = millis();
#ifdef BOTTOM_PIR_PIN
bottomSensorRead = bottomSensorWrite || (digitalRead(BOTTOM_PIR_PIN) == HIGH);
#else
bottomSensorRead = bottomSensorWrite || ultrasoundRead(BOTTOM_TRIGGER_PIN, BOTTOM_ECHO_PIN, bottomMaxTimeUs);
#endif
if (!useUSSensorBottom)
bottomSensorRead = bottomSensorWrite || (digitalRead(bottomPIRorTriggerPin) == HIGH);
else
bottomSensorRead = bottomSensorWrite || ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxTimeUs);
#ifdef TOP_PIR_PIN
topSensorRead = topSensorWrite || (digitalRead(TOP_PIR_PIN) == HIGH);
#else
topSensorRead = topSensorWrite || ultrasoundRead(TOP_TRIGGER_PIN, TOP_ECHO_PIN, topMaxTimeUs);
#endif
if (!useUSSensorTop)
topSensorRead = topSensorWrite || (digitalRead(topPIRorTriggerPin) == HIGH);
else
topSensorRead = topSensorWrite || ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxTimeUs);
// Values read, reset the flags for next API call
topSensorWrite = false;
@ -160,9 +162,9 @@ class Animated_Staircase : public Usermod {
swipe = bottomSensorRead;
if (swipe) {
Serial.println("ON -> Swipe up.");
DEBUG_PRINTLN(F("ON -> Swipe up."));
} else {
Serial.println("ON -> Swipe down.");
DEBUG_PRINTLN(F("ON -> Swipe down."));
}
if (onIndex == offIndex) {
@ -187,9 +189,9 @@ class Animated_Staircase : public Usermod {
on = false;
if (swipe) {
Serial.println("OFF -> Swipe up.");
DEBUG_PRINTLN(F("OFF -> Swipe up."));
} else {
Serial.println("OFF -> Swipe down.");
DEBUG_PRINTLN(F("OFF -> Swipe down."));
}
}
}
@ -198,8 +200,8 @@ class Animated_Staircase : public Usermod {
if ((millis() - lastTime) > segment_delay_ms) {
lastTime = millis();
byte oldOnIndex = onIndex;
byte oldOffIndex = offIndex;
// byte oldOnIndex = onIndex;
// byte oldOffIndex = offIndex;
if (on) {
// Turn on all segments
@ -217,103 +219,46 @@ class Animated_Staircase : public Usermod {
}
}
void writeSettingsToJson(JsonObject& root) {
JsonObject staircase = root["staircase"];
if (staircase.isNull()) {
staircase = root.createNestedObject("staircase");
}
staircase["enabled"] = enabled;
staircase["segment-delay-ms"] = segment_delay_ms;
staircase["on-time-s"] = on_time_ms / 1000;
#ifdef TOP_TRIGGER_PIN
staircase["top-echo-us"] = topMaxTimeUs;
#endif
#ifdef BOTTOM_TRIGGER_PIN
staircase["bottom-echo-us"] = bottomMaxTimeUs;
#endif
}
void writeSensorsToJson(JsonObject& root) {
JsonObject staircase = root["staircase"];
JsonObject staircase = root[F("staircase")];
if (staircase.isNull()) {
staircase = root.createNestedObject("staircase");
staircase = root.createNestedObject(F("staircase"));
}
staircase["top-sensor"] = topSensorRead;
staircase["bottom-sensor"] = bottomSensorRead;
}
bool readSettingsFromJson(JsonObject& root) {
JsonObject staircase = root["staircase"];
bool changed = false;
bool shouldEnable = staircase["enabled"] | enabled;
if (shouldEnable != enabled) {
enable(shouldEnable);
changed = true;
}
unsigned long c_segment_delay_ms = staircase["segment-delay-ms"] | segment_delay_ms;
if (c_segment_delay_ms != segment_delay_ms) {
segment_delay_ms = c_segment_delay_ms;
changed = true;
}
unsigned long c_on_time_ms = (staircase["on-time-s"] | (on_time_ms / 1000)) * 1000;
if (c_on_time_ms != on_time_ms) {
on_time_ms = c_on_time_ms;
changed = true;
}
#ifdef TOP_TRIGGER_PIN
unsigned int c_topMaxTimeUs = staircase["top-echo-us"] | topMaxTimeUs;
if (c_topMaxTimeUs != topMaxTimeUs) {
topMaxTimeUs = c_topMaxTimeUs;
changed = true;
}
#endif
#ifdef BOTTOM_TRIGGER_PIN
unsigned int c_bottomMaxTimeUs = staircase["bottom-echo-us"] | bottomMaxTimeUs;
if (c_bottomMaxTimeUs != bottomMaxTimeUs) {
bottomMaxTimeUs = c_bottomMaxTimeUs;
changed = true;
}
#endif
return changed;
staircase[F("top-sensor")] = topSensorRead;
staircase[F("bottom-sensor")] = bottomSensorRead;
}
void readSensorsFromJson(JsonObject& root) {
JsonObject staircase = root["staircase"];
bottomSensorWrite = bottomSensorRead || (staircase["bottom-sensor"].as<bool>());
topSensorWrite = topSensorRead || (staircase["top-sensor"].as<bool>());
JsonObject staircase = root[F("staircase")];
bottomSensorWrite = bottomSensorRead || (staircase[F("bottom-sensor")].as<bool>());
topSensorWrite = topSensorRead || (staircase[F("top-sensor")].as<bool>());
}
void enable(bool enable) {
if (enable) {
Serial.println("Animated Staircase enabled.");
Serial.print("Delay between steps: ");
Serial.print(segment_delay_ms, DEC);
Serial.print(" milliseconds.\nStairs switch off after: ");
Serial.print(on_time_ms / 1000, DEC);
Serial.println(" seconds.");
DEBUG_PRINTLN(F("Animated Staircase enabled."));
DEBUG_PRINT(F("Delay between steps: "));
DEBUG_PRINT(segment_delay_ms);
DEBUG_PRINT(F(" milliseconds.\nStairs switch off after: "));
DEBUG_PRINT(on_time_ms / 1000);
DEBUG_PRINTLN(F(" seconds."));
#ifdef BOTTOM_PIR_PIN
pinMode(BOTTOM_PIR_PIN, INPUT);
#else
pinMode(BOTTOM_TRIGGER_PIN, OUTPUT);
pinMode(BOTTOM_ECHO_PIN, INPUT);
#endif
if (!useUSSensorBottom)
pinMode(bottomPIRorTriggerPin, INPUT);
else {
pinMode(bottomPIRorTriggerPin, OUTPUT);
pinMode(bottomEchoPin, INPUT);
}
#ifdef TOP_PIR_PIN
pinMode(TOP_PIR_PIN, INPUT);
#else
pinMode(TOP_TRIGGER_PIN, OUTPUT);
pinMode(TOP_ECHO_PIN, INPUT);
#endif
if (!useUSSensorTop)
pinMode(topPIRorTriggerPin, INPUT);
else {
pinMode(topPIRorTriggerPin, OUTPUT);
pinMode(topEchoPin, INPUT);
}
} else {
// Restore segment options
WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
WS2812FX::Segment* segments = strip.getSegments();
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
if (!segments->isActive()) {
@ -323,47 +268,53 @@ class Animated_Staircase : public Usermod {
segments->setOption(SEG_OPTION_ON, 1, 1);
}
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
Serial.println("Animated Staircase disabled.");
DEBUG_PRINTLN(F("Animated Staircase disabled."));
}
enabled = enable;
}
public:
void setup() { enable(enabled); }
void setup() {
// allocate pins
if (topPIRorTriggerPin >= 0) {
if (!pinManager.allocatePin(topPIRorTriggerPin,useUSSensorTop))
topPIRorTriggerPin = -1;
}
if (topEchoPin >= 0) {
if (!pinManager.allocatePin(topEchoPin,false))
topEchoPin = -1;
}
if (bottomPIRorTriggerPin >= 0) {
if (!pinManager.allocatePin(bottomPIRorTriggerPin,useUSSensorBottom))
bottomPIRorTriggerPin = -1;
}
if (bottomEchoPin >= 0) {
if (!pinManager.allocatePin(bottomPIRorTriggerPin,false))
bottomEchoPin = -1;
}
// validate pins
if ( topPIRorTriggerPin < 0 || bottomPIRorTriggerPin < 0 ||
(useUSSensorTop && topEchoPin < 0) || (useUSSensorBottom && bottomEchoPin < 0) )
enabled = false;
enable(enabled);
initDone = true;
}
void loop() {
// Write changed settings from to flash (see readFromJsonState())
if (saveState) {
serializeConfig();
saveState = false;
}
if (!enabled) {
return;
}
if (!enabled) return;
checkSensors();
autoPowerOff();
updateSwipe();
}
uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; }
/*
* Shows configuration settings to the json API. This object looks like:
*
* "staircase" : {
* "enabled" : true
* "segment-delay-ms" : 150,
* "on-time-s" : 5
* }
*
*/
void addToJsonState(JsonObject& root) {
writeSettingsToJson(root);
writeSensorsToJson(root);
Serial.println("Staircase config exposed in API.");
// writeSettingsToJson(root);
// writeSensorsToJson(root);
// DEBUG_PRINTLN(F("Staircase config exposed in API."));
}
/*
@ -371,27 +322,103 @@ class Animated_Staircase : public Usermod {
* See void addToJsonState(JsonObject& root)
*/
void readFromJsonState(JsonObject& root) {
// The call to serializeConfig() must be done in the main loop,
// so we set a flag to signal the main loop to save state.
saveState = readSettingsFromJson(root);
readSensorsFromJson(root);
Serial.println("Staircase config read from API.");
if (!initDone) return; // prevent crash on boot applyPreset()
JsonObject staircase = root[F("staircase")];
if (!staircase.isNull()) {
if (staircase[F("enabled")].is<bool>()) {
enabled = staircase[F("enabled")].as<bool>();
} else {
String str = staircase[F("enabled")]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
}
}
/*
* Writes the configuration to internal flash memory.
*/
void addToConfig(JsonObject& root) {
writeSettingsToJson(root);
Serial.println("Staircase config saved.");
JsonObject staircase = root[F("staircase")];
if (staircase.isNull()) {
staircase = root.createNestedObject(F("staircase"));
}
staircase[F("enabled")] = enabled;
staircase[F("segment-delay-ms")] = segment_delay_ms;
staircase[F("on-time-s")] = on_time_ms / 1000;
staircase[F("useTopUltrasoundSensor")] = useUSSensorTop;
staircase[F("topPIRorTrigger_pin")] = topPIRorTriggerPin;
staircase[F("topEcho_pin")] = topEchoPin;
staircase[F("useBottomUltrasoundSensor")] = useUSSensorBottom;
staircase[F("bottomPIRorTrigger_pin")] = bottomPIRorTriggerPin;
staircase[F("bottomEcho_pin")] = bottomEchoPin;
staircase[F("top-echo-us")] = topMaxTimeUs;
staircase[F("bottom-echo-us")] = bottomMaxTimeUs;
DEBUG_PRINTLN(F("Staircase config saved."));
}
/*
* Reads the configuration to internal flash memory before setup() is called.
*/
void readFromConfig(JsonObject& root) {
readSettingsFromJson(root);
Serial.println("Staircase config loaded.");
bool oldUseUSSensorTop = useUSSensorTop;
bool oldUseUSSensorBottom = useUSSensorBottom;
int8_t oldTopAPin = topPIRorTriggerPin;
int8_t oldTopBPin = topEchoPin;
int8_t oldBottomAPin = bottomPIRorTriggerPin;
int8_t oldBottomBPin = bottomEchoPin;
JsonObject staircase = root[F("staircase")];
if (!staircase.isNull()) {
if (staircase[F("enabled")].is<bool>()) {
enabled = staircase[F("enabled")].as<bool>();
} else {
String str = staircase[F("enabled")]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
segment_delay_ms = staircase[F("segment-delay-ms")];
on_time_ms = (int)staircase[F("on-time-s")] * 1000;
if (staircase[F("useTopUltrasoundSensor")].is<bool>()) {
useUSSensorTop = staircase[F("useTopUltrasoundSensor")].as<bool>();
} else {
String str = staircase[F("useTopUltrasoundSensor")]; // checkbox -> off or on
useUSSensorTop = (bool)(str!="off"); // off is guaranteed to be present
}
topPIRorTriggerPin = staircase[F("topPIRorTrigger_pin")];
topEchoPin = staircase[F("topEcho_pin")];
useUSSensorBottom = staircase[F("useBottomUltrasoundSensor")].as<bool>();
if (staircase[F("useBottomUltrasoundSensor")].is<bool>()) {
useUSSensorBottom = staircase[F("useBottomUltrasoundSensor")].as<bool>();
} else {
String str = staircase[F("useBottomUltrasoundSensor")]; // checkbox -> off or on
useUSSensorBottom = (bool)(str!="off"); // off is guaranteed to be present
}
bottomPIRorTriggerPin = staircase[F("bottomPIRorTrigger_pin")];
bottomEchoPin = staircase[F("bottomEcho_pin")];
topMaxTimeUs = staircase[F("top-echo-us")];
bottomMaxTimeUs = staircase[F("bottom-echo-us")];
DEBUG_PRINTLN(F("Staircase config (re)loaded."));
} else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
}
if (!initDone) {
// first run: reading from cfg.json
} else {
// changing paramters from settings page
bool changed = false;
if ((oldUseUSSensorTop != useUSSensorTop) ||
(oldUseUSSensorBottom != useUSSensorBottom) ||
(oldTopAPin != topPIRorTriggerPin) ||
(oldTopBPin != topEchoPin) ||
(oldBottomAPin != bottomPIRorTriggerPin) ||
(oldBottomBPin != bottomEchoPin)) {
changed = true;
pinManager.deallocatePin(oldTopAPin);
pinManager.deallocatePin(oldTopBPin);
pinManager.deallocatePin(oldBottomAPin);
pinManager.deallocatePin(oldBottomBPin);
}
if (changed) setup();
}
}
/*
@ -405,23 +432,21 @@ class Animated_Staircase : public Usermod {
}
if (enabled) {
JsonArray usermodEnabled =
staircase.createNestedArray("Staircase enabled"); // name
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name
usermodEnabled.add("yes"); // value
JsonArray segmentDelay =
staircase.createNestedArray("Delay between stairs"); // name
JsonArray segmentDelay = staircase.createNestedArray(F("Delay between stairs")); // name
segmentDelay.add(segment_delay_ms); // value
segmentDelay.add(" milliseconds"); // unit
segmentDelay.add("ms"); // unit
JsonArray onTime =
staircase.createNestedArray("Power-off stairs after"); // name
JsonArray onTime = staircase.createNestedArray(F("Power-off stairs after")); // name
onTime.add(on_time_ms / 1000); // value
onTime.add(" seconds"); // unit
onTime.add("s"); // unit
} else {
JsonArray usermodEnabled =
staircase.createNestedArray("Staircase enabled"); // name
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name
usermodEnabled.add("no"); // value
JsonArray topPin = staircase.createNestedArray(F("Top pin")); // name
topPin.add(topPIRorTriggerPin); // value
}
}
};

View File

@ -32,16 +32,11 @@ class UsermodTemperature : public Usermod {
bool initDone = false;
OneWire *oneWire;
DallasTemperature *sensor;
// The device's unique 64-bit serial code stored in on-board ROM.
// Reading directly from the sensor device address is faster than
// reading from index. When reading by index, DallasTemperature
// must first look up the device address at the specified index.
DeviceAddress sensorDeviceAddress;
// GPIO pin used for sensor (with a default compile-time fallback)
int8_t temperaturePin = TEMPERATURE_PIN;
// measurement unit (true==°C, false==°F)
bool degC = true;
unsigned long readingInterval = USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL;
// 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);
// last time requestTemperatures was called
@ -59,51 +54,77 @@ class UsermodTemperature : public Usermod {
// temperature if flashed to a board without a sensor attached
bool disabled = false;
//Dallas sensor quick reading. Credit to - Author: Peter Scargill, August 17th, 2013
int16_t readDallas() {
byte i;
byte data[2];
int16_t result;
oneWire->reset();
oneWire->write(0xCC);
oneWire->write(0xBE);
for (i=0; i < 2; i++) data[i] = oneWire->read();
result = (data[1]<<8) | data[0];
result >>= 4;
if (data[1]&0x80) result |= 61440;
if (data[0]&0x08) ++result;
oneWire->reset();
oneWire->write(0xCC);
oneWire->write(0x44,1);
return result*10;
}
void requestTemperatures() {
// there is requestTemperaturesByAddress however it
// appears to do more work,
// TODO: measure exection time difference
sensor->requestTemperatures();
readDallas();
lastTemperaturesRequest = millis();
waitingForConversion = true;
DEBUG_PRINTLN(F("Requested temperature."));
}
void getTemperature() {
if (strip.isUpdating()) return;
if (degC) temperature = sensor->getTempC(sensorDeviceAddress);
else temperature = sensor->getTempF(sensorDeviceAddress);
temperature = readDallas()/10.0f;
if (!degC) temperature = temperature * 1.8f + 32;
lastMeasurement = millis();
waitingForConversion = false;
getTemperatureComplete = true;
DEBUG_PRINTF("Read temperature %2.1f.\n", temperature);
}
public:
void setup() {
//bool sensorFound = false;
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (!pinManager.allocatePin(temperaturePin,false)) {
temperaturePin = -1; // allocation failed
DEBUG_PRINTLN(F("Temperature pin allocation failed."));
} else {
//DeviceAddress deviceAddress;
oneWire = new OneWire(temperaturePin);
sensor = new DallasTemperature(oneWire);
if (sensor) sensor->begin();
else
DEBUG_PRINTLN(F("Temperature sensor allocation failed."));
oneWire->reset();
/*
// find out if we have DS18xxx sensor attached
oneWire->reset_search();
while (oneWire->search(deviceAddress)) {
if (oneWire->crc8(deviceAddress, 7) == deviceAddress[7]) {
switch (deviceAddress[0]) {
case 0x10: // DS18S20
case 0x22: // DS18B20
case 0x28: // DS1822
case 0x3B: // DS1825
case 0x42: // DS28EA00
sensorFound = true; // sensor found;
DEBUG_PRINTLN(F("Sensor found."));
break;
}
// get the unique 64-bit serial code stored in on-board ROM
// if getAddress returns false, the sensor was not found
disabled = (temperaturePin==-1) || !sensor->getAddress(sensorDeviceAddress, 0);
}
}
*/
}
disabled = disabled || (temperaturePin==-1);
if (!disabled) {
DEBUG_PRINTLN(F("Dallas Temperature found"));
// set the resolution for this specific device
sensor->setResolution(sensorDeviceAddress, 9, true);
// do not block waiting for reading
sensor->setWaitForConversion(false);
} else {
DEBUG_PRINTLN(F("Dallas Temperature not found"));
}
@ -118,7 +139,7 @@ class UsermodTemperature : public Usermod {
// check to see if we are due for taking a measurement
// lastMeasurement will not be updated until the conversion
// is complete the the reading is finished
if (now - lastMeasurement < USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL) return;
if (now - lastMeasurement < readingInterval) return;
// we are due for a measurement, if we are not already waiting
// for a conversion to complete, then make a new request for temps
@ -128,7 +149,7 @@ class UsermodTemperature : public Usermod {
}
// we were waiting for a conversion to complete, have we waited log enough?
if (now - lastTemperaturesRequest >= 95 /* 93.75ms per the datasheet */) {
if (now - lastTemperaturesRequest >= 800 /* 93.75ms per the datasheet but can be up to 750ms*/) {
getTemperature();
if (WLED_MQTT_CONNECTED) {
@ -196,35 +217,6 @@ class UsermodTemperature : public Usermod {
*/
void readFromJsonState(JsonObject &root) {
if (!initDone) return; // prevent crash on boot applyPreset()
if (root[F("Temperature_pin")] != nullptr) {
int8_t pin = min(39,max(0,(int)root[F("Temperature_pin")]));
if (pin != temperaturePin) {
// deallocate pin and release memory
delete sensor;
delete oneWire;
pinManager.deallocatePin(temperaturePin);
// disable usermod
temperaturePin = -1;
disabled = true;
// check if pin is OK
if (pin>=0 && pinManager.allocatePin(pin,false)) {
// allocat memory
oneWire = new OneWire(pin);
sensor = new DallasTemperature(oneWire);
if (sensor) {
temperaturePin = pin;
sensor->begin();
disabled = !sensor->getAddress(sensorDeviceAddress, 0);
} else {
pinManager.deallocatePin(pin);
}
}
}
}
if (root[F("Temperature_degC")] != nullptr) {
String strDegC = root[F("Temperature_degC")]; // checkbox -> off or on
degC = (bool) (strDegC!="off"); // off is guaranteed to be present
}
}
/**
@ -233,8 +225,11 @@ class UsermodTemperature : public Usermod {
void addToConfig(JsonObject &root) {
// we add JSON object: {"Temperature": {"pin": 0, "degC": true}}
JsonObject top = root.createNestedObject(FPSTR(_um_Temperature)); // usermodname
top[F("enabled")] = !disabled;
top["pin"] = temperaturePin; // usermodparam
top["degC"] = degC; // usermodparam
top[F("read-interval-s")] = readingInterval / 1000;
DEBUG_PRINTLN(F("Temperature config saved."));
}
/**
@ -243,12 +238,40 @@ class UsermodTemperature : public Usermod {
void readFromConfig(JsonObject &root) {
// we look for JSON object: {"Temperature": {"pin": 0, "degC": true}}
JsonObject top = root[FPSTR(_um_Temperature)];
int8_t newTemperaturePin = temperaturePin;
if (!top.isNull() && top["pin"] != nullptr) {
temperaturePin = (int)top["pin"];
degC = top["degC"] != nullptr ? top["degC"] : true;
if (top[F("enabled")].is<bool>()) {
disabled = !top[F("enabled")].as<bool>();
} else {
String str = top[F("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>()) {
degC = top["degC"].as<bool>();
} else {
String str = top["degC"]; // checkbox -> off or on
degC = (bool)(str!="off"); // off is guaranteed to be present
}
readingInterval = min(120,max(10,top[F("read-interval-s")].as<int>())) * 1000;
DEBUG_PRINTLN(F("Temperature config loaded."));
} else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
}
if (!initDone) {
// first run: reading from cfg.json
temperaturePin = newTemperaturePin;
} else {
// changing paramters from settings page
if (newTemperaturePin != temperaturePin) {
// deallocate pin and release memory
delete oneWire;
pinManager.deallocatePin(temperaturePin);
temperaturePin = newTemperaturePin;
// initialise
setup();
}
}
}
uint16_t getId()

View File

@ -160,20 +160,9 @@ class AutoSaveUsermod : public Usermod {
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject& root) {
if (!initDone) return; // prevent crash on boot applyPreset()
if (root[F("Autosave_autoSaveAfterSec")] != nullptr) {
autoSaveAfterSec = min(60,max(0,(int)root[F("Autosave_autoSaveAfterSec")]));
}
if (root[F("Autosave_autoSavePreset")] != nullptr) {
autoSavePreset = min(250,max(0,(int)root[F("Autosave_autoSavePreset")]));
}
if (root[F("Autosave_autoSaveApplyOnBoot")] != nullptr) {
String str = root[F("Autosave_autoSaveApplyOnBoot")]; // checkbox -> off or on
applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present
}
}
//void readFromJsonState(JsonObject& root) {
// if (!initDone) return; // prevent crash on boot applyPreset()
//}
/*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
@ -195,6 +184,7 @@ class AutoSaveUsermod : public Usermod {
top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam
top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam
top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot;
DEBUG_PRINTLN(F("Autosave config saved."));
}
/*
@ -209,9 +199,17 @@ class AutoSaveUsermod : public Usermod {
// we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}}
JsonObject top = root[FPSTR(_um_AutoSave)];
if (!top.isNull() && top[FPSTR(_autoSaveAfterSec)] != nullptr) {
autoSaveAfterSec = (int) top[FPSTR(_autoSaveAfterSec)];
autoSavePreset = (int) top[FPSTR(_autoSavePreset)];
applyAutoSaveOnBoot = (bool)top[FPSTR(_autoSaveApplyOnBoot)];
autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)].as<int>();
autoSavePreset = top[FPSTR(_autoSavePreset)].as<int>();
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."));
} else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
}

View File

@ -628,62 +628,9 @@ class FourLineDisplayUsermod : public Usermod {
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject& root) {
if (!initDone) return; // prevent crash on boot applyPreset()
bool needsRedraw = false;
DisplayType newType = type;
int8_t newScl = sclPin;
int8_t newSda = sdaPin;
if (root[F("4LineDisplay_type")] != nullptr) newType = (DisplayType)root[F("4LineDisplay_type")];
if (root[F("4LineDisplay_pin")] != nullptr) {
newScl = min(39,max(0,(int)root[F("4LineDisplay_pin")][0]));
newSda = min(39,max(0,(int)root[F("4LineDisplay_pin")][1]));
if (newScl==newSda) newScl = newSda = -1;
}
if (root[F("4LineDisplay_contrast")] != nullptr) {
contrast = min(255,max(1,(int)root[F("4LineDisplay_contrast")]));
setContrast(contrast);
needsRedraw |= true;
}
if (root[F("4LineDisplay_refreshRate")] != nullptr) refreshRate = min(60,max(1,(int)root[F("4LineDisplay_refreshRate")]))*1000;
if (root[F("4LineDisplay_screenTimeOut")] != nullptr) screenTimeout = min(900,max(0,(int)root[F("4LineDisplay_screenTimeOut")]))*1000;
if (root[F("4LineDisplay_flip")] != nullptr) {
String str = root[F("4LineDisplay_flip")]; // checkbox -> off or on
flip = (bool)(str!="off"); // off is guaranteed to be present
setFlipMode(flip);
needsRedraw |= true;
}
if (root[F("4LineDisplay_sleepMode")] != nullptr) {
String str = root[F("4LineDisplay_sleepMode")]; // checkbox -> off or on
sleepMode = (bool)(str!="off"); // off is guaranteed to be present
}
if (root[F("4LineDisplay_clockMode")] != nullptr) {
String str = root[F("4LineDisplay_clockMode")]; // checkbox -> off or on
clockMode = (bool)(str!="off"); // off is guaranteed to be present
setLineThreeType(clockMode ? FLD_LINE_3_MODE : FLD_LINE_3_BRIGHTNESS);
needsRedraw |= true;
}
if (sclPin!=newScl || sdaPin!=newSda || type!=newType) {
if (type==SSD1306) delete (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8));
if (type==SH1106) delete (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8));
pinManager.deallocatePin(sclPin);
pinManager.deallocatePin(sdaPin);
sclPin = newScl;
sdaPin = newSda;
if (newScl<0 || newSda<0) {
type = NONE;
return;
} else
type = newType;
lineHeight = type==SH1106 ? 2 : 1;
setup();
needRedraw |= true;
}
if (needsRedraw && !wakeDisplay()) redraw(true);
}
//void readFromJsonState(JsonObject& root) {
// if (!initDone) return; // prevent crash on boot applyPreset()
//}
/*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
@ -711,6 +658,7 @@ class FourLineDisplayUsermod : public Usermod {
top[FPSTR(_4LD_screenTimeOut)] = screenTimeout/1000;
top[FPSTR(_4LD_sleepMode)] = (bool) sleepMode;
top[FPSTR(_4LD_clockMode)] = (bool) clockMode;
DEBUG_PRINTLN(F("4 Line Display config saved."));
}
/*
@ -722,21 +670,73 @@ class FourLineDisplayUsermod : public Usermod {
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*/
void readFromConfig(JsonObject& root) {
bool needsRedraw = false;
DisplayType newType = type;
int8_t newScl = sclPin;
int8_t newSda = sdaPin;
JsonObject top = root[FPSTR(_um_4LineDisplay)];
if (!top.isNull() && top["pin"] != nullptr) {
sclPin = top["pin"][0];
sdaPin = top["pin"][1];
type = top["type"];
newScl = top["pin"][0];
newSda = top["pin"][1];
newType = top["type"];
lineHeight = type==SH1106 ? 2 : 1;
flip = top[FPSTR(_4LD_flip)];
contrast = top[FPSTR(_4LD_contrast)];
refreshRate = int(top[FPSTR(_4LD_refreshRate)])*1000;
screenTimeout = int(top[FPSTR(_4LD_screenTimeOut)])*1000;
sleepMode = top[FPSTR(_4LD_sleepMode)];
clockMode = top[FPSTR(_4LD_clockMode)];
if (top[FPSTR(_4LD_flip)].is<bool>()) {
flip = top[FPSTR(_4LD_flip)].as<bool>();
} else {
String str = top[FPSTR(_4LD_flip)]; // checkbox -> off or on
flip = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true;
}
contrast = top[FPSTR(_4LD_contrast)].as<int>();
refreshRate = top[FPSTR(_4LD_refreshRate)].as<int>() * 1000;
screenTimeout = top[FPSTR(_4LD_screenTimeOut)].as<int>() * 1000;
if (top[FPSTR(_4LD_sleepMode)].is<bool>()) {
sleepMode = top[FPSTR(_4LD_sleepMode)].as<bool>();
} else {
String str = top[FPSTR(_4LD_sleepMode)]; // checkbox -> off or on
sleepMode = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true;
}
if (top[FPSTR(_4LD_clockMode)].is<bool>()) {
clockMode = top[FPSTR(_4LD_clockMode)].as<bool>();
} else {
String str = top[FPSTR(_4LD_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.)"));
}
if (!initDone) {
// first run: reading from cfg.json
sclPin = newScl;
sdaPin = newSda;
type = newType;
} else {
// changing paramters from settings page
if (sclPin!=newScl || sdaPin!=newSda || type!=newType) {
if (type==SSD1306) delete (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8));
if (type==SH1106) delete (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8));
pinManager.deallocatePin(sclPin);
pinManager.deallocatePin(sdaPin);
sclPin = newScl;
sdaPin = newSda;
if (newScl<0 || newSda<0) {
type = NONE;
return;
} else
type = newType;
lineHeight = type==SH1106 ? 2 : 1;
setup();
needRedraw |= true;
}
setContrast(contrast);
setFlipMode(flip);
if (needsRedraw && !wakeDisplay()) redraw(true);
}
}
/*

View File

@ -419,14 +419,27 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
String name = request->argName(i);
String value = request->arg(i);
// POST request parameters are combined as <usermodname>_<usermodparameter>
uint8_t umNameEnd = name.indexOf("_");
if (!umNameEnd) break; // parameter does not contain "_" -> wrong
JsonObject mod = um[name.substring(0,umNameEnd)]; // get a usermod JSON object
if (mod.isNull()) {
mod = um.createNestedObject(name.substring(0,umNameEnd)); // if it does not exist create it
}
DEBUG_PRINT(name.substring(0,umNameEnd));
DEBUG_PRINT(":");
name = name.substring(umNameEnd+1); // remove mod name from string
// check if parameters represent array
if (name.endsWith("[]")) {
name.replace("[]","");
if (!um[name].is<JsonArray>()) {
JsonArray ar = um.createNestedArray(name);
if (!mod[name].is<JsonArray>()) {
JsonArray ar = mod.createNestedArray(name);
ar.add(value);
j=0;
} else {
um[name].add(value);
mod[name].add(value);
j++;
}
DEBUG_PRINT(name);
@ -435,14 +448,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
DEBUG_PRINT("] = ");
DEBUG_PRINTLN(value);
} else {
um.remove(name); // checkboxes get two fields (first is always "off", existence of second depends on checkmark and may be "on")
um[name] = value;
mod.remove(name); // checkboxes get two fields (first is always "off", existence of second depends on checkmark and may be "on")
mod[name] = value;
DEBUG_PRINT(name);
DEBUG_PRINT(" = ");
DEBUG_PRINTLN(value);
}
}
usermods.readFromJsonState(um);
usermods.readFromConfig(um); // force change of usermod parameters
}
if (subPage != 2 && (subPage != 6 || !doReboot)) serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init)

View File

@ -50,6 +50,10 @@
#include "../usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h"
#endif
#ifdef USERMOD_ANIMATED_STAIRCASE
#include "../usermods/Animated_Staircase/Animated_Staircase.h"
#endif
void registerUsermods()
{
/*
@ -96,4 +100,8 @@ void registerUsermods()
#ifdef USERMOD_VL53L0X_GESTURES
usermods.add(new UsermodVL53L0XGestures());
#endif
#ifdef USERMOD_ANIMATED_STAIRCASE
usermods.add(new Animated_Staircase());
#endif
}

View File

@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2104151
#define VERSION 2104171
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG