diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index cd43e7ec3..a732f81e0 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -10,465 +10,508 @@ #pragma once #include "wled.h" -#define USERMOD_ID_ANIMATED_STAIRCASE 1011 - class Animated_Staircase : public Usermod { - private: + 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 + /* 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 = 30000; // 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 topMaxDist = 50; // default maximum measured distance in cm, top + unsigned int bottomMaxDist = 50; // default maximum measured distance in cm, bottom - /* runtime variables */ - bool initDone = false; + /* runtime variables */ + bool initDone = false; - // Time between checking of the sensors - const unsigned int scanDelay = 50; + // Time between checking of the sensors + const unsigned int scanDelay = 100; - // Lights on or off. - // Flipping this will start a transition. - bool on = false; + // Lights on or off. + // Flipping this will start a transition. + bool on = false; - // Swipe direction for current transition -#define SWIPE_UP true -#define SWIPE_DOWN false - bool swipe = SWIPE_UP; + // Swipe direction for current transition + #define SWIPE_UP true + #define SWIPE_DOWN false + bool swipe = SWIPE_UP; - // Indicates which Sensor was seen last (to determine - // the direction when swiping off) -#define LOWER false -#define UPPER true - bool lastSensor = LOWER; + // Indicates which Sensor was seen last (to determine + // the direction when swiping off) + #define LOWER false + #define UPPER true + bool lastSensor = LOWER; - // Time of the last transition action - unsigned long lastTime = 0; + // Time of the last transition action + unsigned long lastTime = 0; - // Time of the last sensor check - unsigned long lastScanTime = 0; + // Time of the last sensor check + unsigned long lastScanTime = 0; - // Last time the lights were switched on or off - unsigned long lastSwitchTime = 0; + // Last time the lights were switched on or off + unsigned long lastSwitchTime = 0; - // segment id between onIndex and offIndex are on. - // controll the swipe by setting/moving these indices around. - // onIndex must be less than or equal to offIndex - byte onIndex = 0; - byte offIndex = 0; + // segment id between onIndex and offIndex are on. + // controll the swipe by setting/moving these indices around. + // onIndex must be less than or equal to offIndex + byte onIndex = 0; + byte offIndex = 0; - // The maximum number of configured segments. - // Dynamically updated based on user configuration. - byte maxSegmentId = 1; - byte mainSegmentId = 0; + // The maximum number of configured segments. + // Dynamically updated based on user configuration. + byte maxSegmentId = 1; + byte mainSegmentId = 0; - // These values are used by the API to read the - // last sensor state, or trigger a sensor - // through the API - bool topSensorRead = false; - bool topSensorWrite = false; - bool bottomSensorRead = false; - bool bottomSensorWrite = false; + // These values are used by the API to read the + // last sensor state, or trigger a sensor + // through the API + bool topSensorRead = false; + bool topSensorWrite = false; + bool bottomSensorRead = false; + bool bottomSensorWrite = false; + bool topSensorState = false; + bool bottomSensorState = false; - // strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _enabled[]; - static const char _segmentDelay[]; - static const char _onTime[]; - static const char _useTopUltrasoundSensor[]; - static const char _topPIRorTrigger_pin[]; - static const char _topEcho_pin[]; - static const char _useBottomUltrasoundSensor[]; - static const char _bottomPIRorTrigger_pin[]; - static const char _bottomEcho_pin[]; - static const char _topEchoTime[]; - static const char _bottomEchoTime[]; - static const char _[]; - - void updateSegments() { -// 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()) { - maxSegmentId = i - 1; - break; - } - - if (i >= onIndex && i < offIndex) { - segments->setOption(SEG_OPTION_ON, 1, 1); - - // We may need to copy mode and colors from segment 0 to make sure - // changes are propagated even when the config is changed during a wipe - // segments->mode = mainsegment.mode; - // segments->colors[0] = mainsegment.colors[0]; - } else { - segments->setOption(SEG_OPTION_ON, 0, 1); - } - // Always mark segments as "transitional", we are animating the staircase - segments->setOption(SEG_OPTION_TRANSITIONAL, 1, 1); - } - colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE); - } - - /* - * Detects if an object is within ultrasound range. - * signalPin: The pin where the pulse is sent - * echoPin: The pin where the echo is received - * maxTimeUs: Detection timeout in microseconds. If an echo is - * received within this time, an object is detected - * and the function will return true. - * - * The speed of sound is 343 meters per second at 20 degress Celcius. - * Since the sound has to travel back and forth, the detection - * distance for the sensor in cm is (0.0343 * maxTimeUs) / 2. - * - * For practical reasons, here are some useful distances: - * - * Distance = maxtime - * 5 cm = 292 uS - * 10 cm = 583 uS - * 20 cm = 1166 uS - * 30 cm = 1749 uS - * 50 cm = 2915 uS - * 100 cm = 5831 uS - */ - bool ultrasoundRead(uint8_t signalPin, - uint8_t echoPin, - unsigned int maxTimeUs) { - digitalWrite(signalPin, HIGH); - delayMicroseconds(10); - digitalWrite(signalPin, LOW); - return pulseIn(echoPin, HIGH, maxTimeUs) > 0; - } - - void checkSensors() { - if ((millis() - lastScanTime) > scanDelay) { - lastScanTime = millis(); - - if (!useUSSensorBottom) - bottomSensorRead = bottomSensorWrite || (digitalRead(bottomPIRorTriggerPin) == HIGH); - else - bottomSensorRead = bottomSensorWrite || ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxTimeUs); - - 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; - bottomSensorWrite = false; - - if (topSensorRead != bottomSensorRead) { - lastSwitchTime = millis(); - - if (on) { - lastSensor = topSensorRead; - } else { - // If the bottom sensor triggered, we need to swipe up, ON - swipe = bottomSensorRead; - - if (swipe) { - DEBUG_PRINTLN(F("ON -> Swipe up.")); - } else { - DEBUG_PRINTLN(F("ON -> Swipe down.")); - } - - if (onIndex == offIndex) { - // Position the indices for a correct on-swipe - if (swipe == SWIPE_UP) { - onIndex = mainSegmentId; - } else { - onIndex = maxSegmentId+1; - } - offIndex = onIndex; - } - on = true; - } + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _segmentDelay[]; + static const char _onTime[]; + static const char _useTopUltrasoundSensor[]; + static const char _topPIRorTrigger_pin[]; + static const char _topEcho_pin[]; + static const char _useBottomUltrasoundSensor[]; + static const char _bottomPIRorTrigger_pin[]; + static const char _bottomEcho_pin[]; + static const char _topEchoCm[]; + static const char _bottomEchoCm[]; + + void publishMqtt(bool bottom, const char* state) + { + //Check if MQTT Connected, otherwise it will crash the 8266 + if (WLED_MQTT_CONNECTED){ + char subuf[64]; + sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)bottom); + mqtt->publish(subuf, 0, false, state); } } - } - void autoPowerOff() { - // TODO: add logic to wait until PIR sensor deactivates - if (on && ((millis() - lastSwitchTime) > on_time_ms)) { - // Swipe OFF in the direction of the last sensor detection - swipe = lastSensor; - on = false; - - if (swipe) { - DEBUG_PRINTLN(F("OFF -> Swipe up.")); - } else { - DEBUG_PRINTLN(F("OFF -> Swipe down.")); - } - } - } - - void updateSwipe() { - if ((millis() - lastTime) > segment_delay_ms) { - lastTime = millis(); - -// byte oldOnIndex = onIndex; -// byte oldOffIndex = offIndex; - - if (on) { - // Turn on all segments - onIndex = MAX(mainSegmentId, onIndex - 1); - offIndex = MIN(maxSegmentId + 1, offIndex + 1); - } else { - if (swipe == SWIPE_UP) { - onIndex = MIN(offIndex, onIndex + 1); - } else { - offIndex = MAX(onIndex, offIndex - 1); - } - } - - updateSegments(); - } - } - - // send sesnor values to JSON API - void writeSensorsToJson(JsonObject& staircase) { - staircase[F("top-sensor")] = topSensorRead; - staircase[F("bottom-sensor")] = bottomSensorRead; - } - - // allow overrides from JSON API - void readSensorsFromJson(JsonObject& staircase) { - bottomSensorWrite = bottomSensorRead || (staircase[F("bottom-sensor")].as()); - topSensorWrite = topSensorRead || (staircase[F("top-sensor")].as()); - } - - void enable(bool enable) { - if (enable) { - 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.")); - - // TODO: attach interrupts - if (!useUSSensorBottom) - pinMode(bottomPIRorTriggerPin, INPUT); - else { - pinMode(bottomPIRorTriggerPin, OUTPUT); - pinMode(bottomEchoPin, INPUT); - } - - if (!useUSSensorTop) - pinMode(topPIRorTriggerPin, INPUT); - else { - pinMode(topPIRorTriggerPin, OUTPUT); - pinMode(topEchoPin, INPUT); - } - } else { - // Restore segment options -// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId); + void updateSegments() { + mainSegmentId = strip.getMainSegmentId(); WS2812FX::Segment* segments = strip.getSegments(); for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) { if (!segments->isActive()) { maxSegmentId = i - 1; break; } - segments->setOption(SEG_OPTION_ON, 1, 1); + + if (i >= onIndex && i < offIndex) { + segments->setOption(SEG_OPTION_ON, 1, 1); + + // We may need to copy mode and colors from segment 0 to make sure + // changes are propagated even when the config is changed during a wipe + // segments->mode = mainsegment.mode; + // segments->colors[0] = mainsegment.colors[0]; + } else { + segments->setOption(SEG_OPTION_ON, 0, 1); + } + // Always mark segments as "transitional", we are animating the staircase + segments->setOption(SEG_OPTION_TRANSITIONAL, 1, 1); } colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE); - DEBUG_PRINTLN(F("Animated Staircase disabled.")); } - enabled = enable; - } - public: - void setup() { - // allocate pins - if (topPIRorTriggerPin >= 0) { - if (!pinManager.allocatePin(topPIRorTriggerPin,useUSSensorTop)) - topPIRorTriggerPin = -1; + /* + * Detects if an object is within ultrasound range. + * signalPin: The pin where the pulse is sent + * echoPin: The pin where the echo is received + * maxTimeUs: Detection timeout in microseconds. If an echo is + * received within this time, an object is detected + * and the function will return true. + * + * The speed of sound is 343 meters per second at 20 degress Celcius. + * Since the sound has to travel back and forth, the detection + * distance for the sensor in cm is (0.0343 * maxTimeUs) / 2. + * + * For practical reasons, here are some useful distances: + * + * Distance = maxtime + * 5 cm = 292 uS + * 10 cm = 583 uS + * 20 cm = 1166 uS + * 30 cm = 1749 uS + * 50 cm = 2915 uS + * 100 cm = 5831 uS + */ + bool ultrasoundRead(int8_t signalPin, int8_t echoPin, unsigned int maxTimeUs) { + if (signalPin<0 || echoPin<0) return false; + digitalWrite(signalPin, LOW); + delayMicroseconds(2); + digitalWrite(signalPin, HIGH); + delayMicroseconds(10); + digitalWrite(signalPin, LOW); + return pulseIn(echoPin, HIGH, maxTimeUs) > 0; } - if (topEchoPin >= 0) { - if (!pinManager.allocatePin(topEchoPin,false)) - topEchoPin = -1; + + bool checkSensors() { + bool sensorChanged = false; + + if ((millis() - lastScanTime) > scanDelay) { + lastScanTime = millis(); + + bottomSensorRead = bottomSensorWrite || + (!useUSSensorBottom ? + (bottomPIRorTriggerPin<0 ? false : digitalRead(bottomPIRorTriggerPin)) : + ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxDist*59) // cm to us + ); + topSensorRead = topSensorWrite || + (!useUSSensorTop ? + (topPIRorTriggerPin<0 ? false : digitalRead(topPIRorTriggerPin)) : + ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxDist*59) // cm to us + ); + + if (bottomSensorRead != bottomSensorState) { + bottomSensorState = bottomSensorRead; // change previous state + sensorChanged = true; + publishMqtt(true, bottomSensorState ? "on" : "off"); + DEBUG_PRINTLN(F("Bottom sensor changed.")); + } + + if (topSensorRead != topSensorState) { + topSensorState = topSensorRead; // change previous state + sensorChanged = true; + publishMqtt(false, topSensorState ? "on" : "off"); + DEBUG_PRINTLN(F("Top sensor changed.")); + } + + // Values read, reset the flags for next API call + topSensorWrite = false; + bottomSensorWrite = false; + + if (topSensorRead != bottomSensorRead) { + lastSwitchTime = millis(); + + if (on) { + lastSensor = topSensorRead; + } else { + // If the bottom sensor triggered, we need to swipe up, ON + swipe = bottomSensorRead; + + DEBUG_PRINT(F("ON -> Swipe ")); + DEBUG_PRINTLN(swipe ? F("up.") : F("down.")); + + if (onIndex == offIndex) { + // Position the indices for a correct on-swipe + if (swipe == SWIPE_UP) { + onIndex = mainSegmentId; + } else { + onIndex = maxSegmentId+1; + } + offIndex = onIndex; + } + on = true; + } + } + } + return sensorChanged; } - if (bottomPIRorTriggerPin >= 0) { - if (!pinManager.allocatePin(bottomPIRorTriggerPin,useUSSensorBottom)) - bottomPIRorTriggerPin = -1; + + void autoPowerOff() { + if (on && ((millis() - lastSwitchTime) > on_time_ms)) { + // if sensors are still on, do nothing + if (bottomSensorState || topSensorState) return; + + // Swipe OFF in the direction of the last sensor detection + swipe = lastSensor; + on = false; + + DEBUG_PRINT(F("OFF -> Swipe ")); + DEBUG_PRINTLN(swipe ? F("up.") : F("down.")); + } } - if (bottomEchoPin >= 0) { - if (!pinManager.allocatePin(bottomPIRorTriggerPin,false)) - bottomEchoPin = -1; + + void updateSwipe() { + if ((millis() - lastTime) > segment_delay_ms) { + lastTime = millis(); + + if (on) { + // Turn on all segments + onIndex = MAX(mainSegmentId, onIndex - 1); + offIndex = MIN(maxSegmentId + 1, offIndex + 1); + } else { + if (swipe == SWIPE_UP) { + onIndex = MIN(offIndex, onIndex + 1); + } else { + offIndex = MAX(onIndex, offIndex - 1); + } + } + updateSegments(); + } } - // TODO: attach interrupts in enable() - // validate pins - if ( topPIRorTriggerPin < 0 || bottomPIRorTriggerPin < 0 || - (useUSSensorTop && topEchoPin < 0) || (useUSSensorBottom && bottomEchoPin < 0) ) - enabled = false; - - enable(enabled); - initDone = true; - } - - void loop() { - if (!enabled) return; - checkSensors(); - autoPowerOff(); - updateSwipe(); - } - - uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; } - - void addToJsonState(JsonObject& root) { - JsonObject staircase = root[FPSTR(_name)]; - if (staircase.isNull()) { - staircase = root.createNestedObject(FPSTR(_name)); + // send sesnor values to JSON API + void writeSensorsToJson(JsonObject& staircase) { + staircase[F("top-sensor")] = topSensorRead; + staircase[F("bottom-sensor")] = bottomSensorRead; } - writeSensorsToJson(staircase); - DEBUG_PRINTLN(F("Staircase sensor state exposed in API.")); - } - /* - * Reads configuration settings from the json API. - * See void addToJsonState(JsonObject& root) - */ - void readFromJsonState(JsonObject& root) { - if (!initDone) return; // prevent crash on boot applyPreset() - JsonObject staircase = root[FPSTR(_name)]; - if (!staircase.isNull()) { - if (staircase[FPSTR(_enabled)].is()) { - enabled = staircase[FPSTR(_enabled)].as(); + // allow overrides from JSON API + void readSensorsFromJson(JsonObject& staircase) { + bottomSensorWrite = bottomSensorState || (staircase[F("bottom-sensor")].as()); + topSensorWrite = topSensorState || (staircase[F("top-sensor")].as()); + } + + void enable(bool enable) { + if (enable) { + 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.")); + + if (!useUSSensorBottom) + pinMode(bottomPIRorTriggerPin, INPUT_PULLUP); + else { + pinMode(bottomPIRorTriggerPin, OUTPUT); + pinMode(bottomEchoPin, INPUT); + } + + if (!useUSSensorTop) + pinMode(topPIRorTriggerPin, INPUT_PULLUP); + else { + pinMode(topPIRorTriggerPin, OUTPUT); + pinMode(topEchoPin, INPUT); + } } else { - String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on - enabled = (bool)(str!="off"); // off is guaranteed to be present + // Restore segment options + WS2812FX::Segment* segments = strip.getSegments(); + for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) { + if (!segments->isActive()) { + maxSegmentId = i - 1; + break; + } + segments->setOption(SEG_OPTION_ON, 1, 1); + } + colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE); + DEBUG_PRINTLN(F("Animated Staircase disabled.")); } - readSensorsFromJson(root); - DEBUG_PRINTLN(F("Staircase sensor state read from API.")); + enabled = enable; } - } - /* - * Writes the configuration to internal flash memory. - */ - void addToConfig(JsonObject& root) { - JsonObject staircase = root[FPSTR(_name)]; - if (staircase.isNull()) { - staircase = root.createNestedObject(FPSTR(_name)); + public: + 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(bottomEchoPin,false)) + bottomEchoPin = -1; + } + enable(enabled); + initDone = true; } - staircase[FPSTR(_enabled)] = enabled; - staircase[FPSTR(_segmentDelay)] = segment_delay_ms; - staircase[FPSTR(_onTime)] = on_time_ms / 1000; - staircase[FPSTR(_useTopUltrasoundSensor)] = useUSSensorTop; - staircase[FPSTR(_topPIRorTrigger_pin)] = topPIRorTriggerPin; - staircase[FPSTR(_topEcho_pin)] = useUSSensorTop ? topEchoPin : -1; - staircase[FPSTR(_useBottomUltrasoundSensor)] = useUSSensorBottom; - staircase[FPSTR(_bottomPIRorTrigger_pin)] = bottomPIRorTriggerPin; - staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1; - staircase[FPSTR(_topEchoTime)] = topMaxTimeUs; - staircase[FPSTR(_bottomEchoTime)] = bottomMaxTimeUs; - DEBUG_PRINTLN(F("Staircase config saved.")); - } - /* - * Reads the configuration to internal flash memory before setup() is called. - */ - void readFromConfig(JsonObject& root) { - bool oldUseUSSensorTop = useUSSensorTop; - bool oldUseUSSensorBottom = useUSSensorBottom; - int8_t oldTopAPin = topPIRorTriggerPin; - int8_t oldTopBPin = topEchoPin; - int8_t oldBottomAPin = bottomPIRorTriggerPin; - int8_t oldBottomBPin = bottomEchoPin; + void loop() { + if (!enabled || strip.isUpdating()) return; + checkSensors(); + autoPowerOff(); + updateSwipe(); + } - JsonObject staircase = root[FPSTR(_name)]; - if (!staircase.isNull()) { - if (staircase[FPSTR(_enabled)].is()) { - enabled = staircase[FPSTR(_enabled)].as(); + uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; } + + /** + * handling of MQTT message + * topic only contains stripped topic (part after /wled/MAC) + * topic should look like: /swipe with amessage of [up|down] + */ + bool onMqttMessage(char* topic, char* payload) { + if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/swipe"), 6) == 0) { + String action = payload; + if (action == "up") { + bottomSensorWrite = true; + return true; + } else if (action == "down") { + topSensorWrite = true; + return true; + } + } + return false; + } + + /** + * subscribe to MQTT topic for controlling usermod + */ + void onMqttConnect(bool sessionPresent) { + //(re)subscribe to required topics + char subuf[64]; + if (mqttDeviceTopic[0] != 0) { + strcpy(subuf, mqttDeviceTopic); + strcat_P(subuf, PSTR("/swipe")); + mqtt->subscribe(subuf, 0); + } + } + + void addToJsonState(JsonObject& root) { + JsonObject staircase = root[FPSTR(_name)]; + if (staircase.isNull()) { + staircase = root.createNestedObject(FPSTR(_name)); + } + writeSensorsToJson(staircase); + DEBUG_PRINTLN(F("Staircase sensor state exposed in API.")); + } + + /* + * Reads configuration settings from the json API. + * See void addToJsonState(JsonObject& root) + */ + void readFromJsonState(JsonObject& root) { + if (!initDone) return; // prevent crash on boot applyPreset() + JsonObject staircase = root[FPSTR(_name)]; + if (!staircase.isNull()) { + if (staircase[FPSTR(_enabled)].is()) { + enabled = staircase[FPSTR(_enabled)].as(); + } else { + String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on + enabled = (bool)(str!="off"); // off is guaranteed to be present + } + readSensorsFromJson(staircase); + DEBUG_PRINTLN(F("Staircase sensor state read from API.")); + } + } + + /* + * Writes the configuration to internal flash memory. + */ + void addToConfig(JsonObject& root) { + JsonObject staircase = root[FPSTR(_name)]; + if (staircase.isNull()) { + staircase = root.createNestedObject(FPSTR(_name)); + } + staircase[FPSTR(_enabled)] = enabled; + staircase[FPSTR(_segmentDelay)] = segment_delay_ms; + staircase[FPSTR(_onTime)] = on_time_ms / 1000; + staircase[FPSTR(_useTopUltrasoundSensor)] = useUSSensorTop; + staircase[FPSTR(_topPIRorTrigger_pin)] = topPIRorTriggerPin; + staircase[FPSTR(_topEcho_pin)] = useUSSensorTop ? topEchoPin : -1; + staircase[FPSTR(_useBottomUltrasoundSensor)] = useUSSensorBottom; + staircase[FPSTR(_bottomPIRorTrigger_pin)] = bottomPIRorTriggerPin; + staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1; + staircase[FPSTR(_topEchoCm)] = topMaxDist; + staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist; + DEBUG_PRINTLN(F("Staircase config saved.")); + } + + /* + * Reads the configuration to internal flash memory before setup() is called. + */ + void readFromConfig(JsonObject& root) { + 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[FPSTR(_name)]; + if (!staircase.isNull()) { + if (staircase[FPSTR(_enabled)].is()) { + enabled = staircase[FPSTR(_enabled)].as(); + } 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())); // max delay 10s + on_time_ms = min(900,max(10,staircase[FPSTR(_onTime)].as())) * 1000; // min 10s, max 15min + + if (staircase[FPSTR(_useTopUltrasoundSensor)].is()) { + useUSSensorTop = staircase[FPSTR(_useTopUltrasoundSensor)].as(); + } 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())); + topEchoPin = min(39,max(-1,staircase[FPSTR(_topEcho_pin)].as())); + + if (staircase[FPSTR(_useBottomUltrasoundSensor)].is()) { + useUSSensorBottom = staircase[FPSTR(_useBottomUltrasoundSensor)].as(); + } else { + String str = staircase[FPSTR(_useBottomUltrasoundSensor)]; // checkbox -> off or on + useUSSensorBottom = (bool)(str!="off"); // off is guaranteed to be present + } + bottomPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_bottomPIRorTrigger_pin)].as())); + bottomEchoPin = min(39,max(-1,staircase[FPSTR(_bottomEcho_pin)].as())); + topMaxDist = min(150,max(30,staircase[FPSTR(_topEchoCm)].as())); // max distnace ~1.5m (a lag of 9ms may be expected) + bottomMaxDist = min(150,max(30,staircase[FPSTR(_bottomEchoCm)].as())); // max distance ~1.5m (a lag of 9ms may be expected) } else { - String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on - enabled = (bool)(str!="off"); // off is guaranteed to be present + DEBUG_PRINTLN(F("No config found. (Using defaults.)")); } - segment_delay_ms = min(10000,max(10,staircase[FPSTR(_segmentDelay)].as())); // max delay 10s - on_time_ms = min(900,max(10,staircase[FPSTR(_onTime)].as())) * 1000; // min 10s, max 15min - - if (staircase[FPSTR(_useTopUltrasoundSensor)].is()) { - useUSSensorTop = staircase[FPSTR(_useTopUltrasoundSensor)].as(); + if (!initDone) { + // first run: reading from cfg.json + DEBUG_PRINTLN(F("Staircase config loaded.")); } else { - String str = staircase[FPSTR(_useTopUltrasoundSensor)]; // checkbox -> off or on - useUSSensorTop = (bool)(str!="off"); // off is guaranteed to be present + // changing paramters from settings page + DEBUG_PRINTLN(F("Staircase config (re)loaded.")); + 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(); } - - topPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_topPIRorTrigger_pin)].as())); - topEchoPin = min(39,max(-1,staircase[FPSTR(_topEcho_pin)].as())); - - if (staircase[FPSTR(_useBottomUltrasoundSensor)].is()) { - useUSSensorBottom = staircase[FPSTR(_useBottomUltrasoundSensor)].as(); - } else { - String str = staircase[FPSTR(_useBottomUltrasoundSensor)]; // checkbox -> off or on - useUSSensorBottom = (bool)(str!="off"); // off is guaranteed to be present - } - bottomPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_bottomPIRorTrigger_pin)].as())); - bottomEchoPin = min(39,max(-1,staircase[FPSTR(_bottomEcho_pin)].as())); - topMaxTimeUs = min(18000,max(300,staircase[FPSTR(_topEchoTime)].as())); // max distnace ~3m (a noticable lag of 18ms may be expected) - bottomMaxTimeUs = min(18000,max(300,staircase[FPSTR(_bottomEchoTime)].as())); // max distance ~3m (a noticable lag of 18ms may be expected) - 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(); - } - } - - /* - * Shows the delay between steps and power-off time in the "info" - * tab of the web-UI. - */ - void addToJsonInfo(JsonObject& root) { - JsonObject staircase = root["u"]; - if (staircase.isNull()) { - staircase = root.createNestedObject("u"); } - if (enabled) { + /* + * Shows the delay between steps and power-off time in the "info" + * tab of the web-UI. + */ + void addToJsonInfo(JsonObject& root) { + JsonObject staircase = root["u"]; + if (staircase.isNull()) { + staircase = root.createNestedObject("u"); + } + JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name - usermodEnabled.add("yes"); // value + if (enabled) { + usermodEnabled.add("yes"); // value + /* + JsonArray segmentDelay = staircase.createNestedArray(F("Delay between stairs")); // name + segmentDelay.add(segment_delay_ms); // value + segmentDelay.add("ms"); // unit - JsonArray segmentDelay = staircase.createNestedArray(F("Delay between stairs")); // name - segmentDelay.add(segment_delay_ms); // value - segmentDelay.add("ms"); // unit - - JsonArray onTime = staircase.createNestedArray(F("Power-off stairs after")); // name - onTime.add(on_time_ms / 1000); // value - onTime.add("s"); // unit - } else { - JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name - usermodEnabled.add("no"); // value + JsonArray onTime = staircase.createNestedArray(F("Power-off stairs after")); // name + onTime.add(on_time_ms / 1000); // value + onTime.add("s"); // unit + */ + } else { + usermodEnabled.add("no"); // value + } } - } }; // strings to reduce flash memory usage (used more than twice) @@ -482,5 +525,5 @@ const char Animated_Staircase::_topEcho_pin[] PROGMEM = "topEcho_p const char Animated_Staircase::_useBottomUltrasoundSensor[] PROGMEM = "useBottomUltrasoundSensor"; const char Animated_Staircase::_bottomPIRorTrigger_pin[] PROGMEM = "bottomPIRorTrigger_pin"; const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin"; -const char Animated_Staircase::_topEchoTime[] PROGMEM = "top-echo-us"; -const char Animated_Staircase::_bottomEchoTime[] PROGMEM = "bottom-echo-us"; +const char Animated_Staircase::_topEchoCm[] PROGMEM = "top-dist-cm"; +const char Animated_Staircase::_bottomEchoCm[] PROGMEM = "bottom-dist-cm"; diff --git a/wled00/const.h b/wled00/const.h index 9f46a3f65..662b999c0 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -32,35 +32,36 @@ #endif //Usermod IDs -#define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present -#define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID -#define USERMOD_ID_EXAMPLE 2 //Usermod "usermod_v2_example.h" -#define USERMOD_ID_TEMPERATURE 3 //Usermod "usermod_temperature.h" -#define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h" -#define USERMOD_ID_PIRSWITCH 5 //Usermod "usermod_PIR_sensor_switch.h" -#define USERMOD_ID_IMU 6 //Usermod "usermod_mpu6050_imu.h" -#define USERMOD_ID_FOUR_LINE_DISP 7 //Usermod "usermod_v2_four_line_display.h -#define USERMOD_ID_ROTARY_ENC_UI 8 //Usermod "usermod_v2_rotary_encoder_ui.h" -#define USERMOD_ID_AUTO_SAVE 9 //Usermod "usermod_v2_auto_save.h" -#define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h" -#define USERMOD_ID_MODE_SORT 11 //Usermod "usermod_v2_mode_sort.h" -#define USERMOD_ID_VL53L0X 12 //Usermod "usermod_vl53l0x_gestures.h" -#define USERMOD_ID_MULTI_RELAY 101 //Usermod "usermod_multi_relay.h" +#define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present +#define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID +#define USERMOD_ID_EXAMPLE 2 //Usermod "usermod_v2_example.h" +#define USERMOD_ID_TEMPERATURE 3 //Usermod "usermod_temperature.h" +#define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h" +#define USERMOD_ID_PIRSWITCH 5 //Usermod "usermod_PIR_sensor_switch.h" +#define USERMOD_ID_IMU 6 //Usermod "usermod_mpu6050_imu.h" +#define USERMOD_ID_FOUR_LINE_DISP 7 //Usermod "usermod_v2_four_line_display.h +#define USERMOD_ID_ROTARY_ENC_UI 8 //Usermod "usermod_v2_rotary_encoder_ui.h" +#define USERMOD_ID_AUTO_SAVE 9 //Usermod "usermod_v2_auto_save.h" +#define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h" +#define USERMOD_ID_MODE_SORT 11 //Usermod "usermod_v2_mode_sort.h" +#define USERMOD_ID_VL53L0X 12 //Usermod "usermod_vl53l0x_gestures.h" +#define USERMOD_ID_MULTI_RELAY 13 //Usermod "usermod_multi_relay.h" +#define USERMOD_ID_ANIMATED_STAIRCASE 14 //Usermod "Animated_Staircase.h" //Access point behavior -#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot -#define AP_BEHAVIOR_NO_CONN 1 //Open when no connection (either after boot or if connection is lost) -#define AP_BEHAVIOR_ALWAYS 2 //Always open -#define AP_BEHAVIOR_BUTTON_ONLY 3 //Only when button pressed for 6 sec +#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot +#define AP_BEHAVIOR_NO_CONN 1 //Open when no connection (either after boot or if connection is lost) +#define AP_BEHAVIOR_ALWAYS 2 //Always open +#define AP_BEHAVIOR_BUTTON_ONLY 3 //Only when button pressed for 6 sec //Notifier callMode -#define NOTIFIER_CALL_MODE_INIT 0 //no updates on init, can be used to disable updates +#define NOTIFIER_CALL_MODE_INIT 0 //no updates on init, can be used to disable updates #define NOTIFIER_CALL_MODE_DIRECT_CHANGE 1 #define NOTIFIER_CALL_MODE_BUTTON 2 #define NOTIFIER_CALL_MODE_NOTIFICATION 3 #define NOTIFIER_CALL_MODE_NIGHTLIGHT 4 #define NOTIFIER_CALL_MODE_NO_NOTIFY 5 -#define NOTIFIER_CALL_MODE_FX_CHANGED 6 //no longer used +#define NOTIFIER_CALL_MODE_FX_CHANGED 6 //no longer used #define NOTIFIER_CALL_MODE_HUE 7 #define NOTIFIER_CALL_MODE_PRESET_CYCLE 8 #define NOTIFIER_CALL_MODE_BLYNK 9 diff --git a/wled00/wled.h b/wled00/wled.h index eae12215d..3aa6d2c4c 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2105131 +#define VERSION 2105151 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG