From 1cd56decab82d863c61aede1af08936d37c06d82 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 17 Apr 2021 17:04:36 +0200 Subject: [PATCH] Rewritten usermod config saving/reloading. Changed temperature reading (to work on ESP32 more reliably). Added Animated staircase usermod to the collection. --- .../Animated_Staircase/Animated_Staircase.h | 349 ++++++++++-------- usermods/Temperature/usermod_temperature.h | 147 ++++---- .../usermod_v2_auto_save.h | 32 +- .../usermod_v2_four_line_display.h | 130 +++---- wled00/set.cpp | 25 +- wled00/usermods_list.cpp | 8 + wled00/wled.h | 2 +- 7 files changed, 380 insertions(+), 313 deletions(-) diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 9717589d..ff108de5 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -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 -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 -unsigned int topMaxTimeUs = 1749; // default echo timout, top -#endif -#ifndef BOTTOM_PIR_PIN -unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom -#endif - -// Time between checking of the sensors -const int scanDelay = 50; - 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 + 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 + unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom + + /* runtime variables */ + bool initDone = false; + + // Time between checking of the sensors + const int scanDelay = 50; + // 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()); - topSensorWrite = topSensorRead || (staircase["top-sensor"].as()); + JsonObject staircase = root[F("staircase")]; + bottomSensorWrite = bottomSensorRead || (staircase[F("bottom-sensor")].as()); + topSensorWrite = topSensorRead || (staircase[F("top-sensor")].as()); } 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()) { + enabled = staircase[F("enabled")].as(); + } 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()) { + enabled = staircase[F("enabled")].as(); + } 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()) { + useUSSensorTop = staircase[F("useTopUltrasoundSensor")].as(); + } 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(); + if (staircase[F("useBottomUltrasoundSensor")].is()) { + useUSSensorBottom = staircase[F("useBottomUltrasoundSensor")].as(); + } 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 } } }; \ No newline at end of file diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 569c31b2..3e756b69 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -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(); - lastTemperaturesRequest = millis(); - waitingForConversion = true; + 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()) { + disabled = !top[F("enabled")].as(); + } 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())); + if (top["degC"].is()) { + degC = top["degC"].as(); + } 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())) * 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() diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index 3f966ada..443fd4aa 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -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(); + autoSavePreset = top[FPSTR(_autoSavePreset)].as(); + if (top[FPSTR(_autoSaveApplyOnBoot)].is()) { + // reading from cfg.json + applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as(); + } else { + // reading from POST message + String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on + applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present + } + DEBUG_PRINTLN(F("Autosave config (re)loaded.")); } else { DEBUG_PRINTLN(F("No config found. (Using defaults.)")); } diff --git a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h index 261f8e0b..14df1694 100644 --- a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h +++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h @@ -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)); - if (type==SH1106) delete (static_cast(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()) { + flip = top[FPSTR(_4LD_flip)].as(); + } 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(); + refreshRate = top[FPSTR(_4LD_refreshRate)].as() * 1000; + screenTimeout = top[FPSTR(_4LD_screenTimeOut)].as() * 1000; + if (top[FPSTR(_4LD_sleepMode)].is()) { + sleepMode = top[FPSTR(_4LD_sleepMode)].as(); + } 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()) { + clockMode = top[FPSTR(_4LD_clockMode)].as(); + } 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)); + if (type==SH1106) delete (static_cast(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); + } } /* diff --git a/wled00/set.cpp b/wled00/set.cpp index 3c595fd1..7bfb440e 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -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 _ + 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 ar = um.createNestedArray(name); + if (!mod[name].is()) { + 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) diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 39e34b41..3553d7a7 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -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 } \ No newline at end of file diff --git a/wled00/wled.h b/wled00/wled.h index be91e3f1..ccb3ad8b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -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