From a018eec8ec28ef84d3b0f1606e57c9a722b04b33 Mon Sep 17 00:00:00 2001 From: Josh Dixon Date: Sat, 30 May 2026 13:02:18 +1000 Subject: [PATCH 01/10] Migrated AnimatedStaircase usermod to VL53L0X TOF sensors --- platformio.ini | 44 +- .../AnimatedStaircase_VL53L0X.cpp | 562 ++++++++++++++++++ usermods/AnimatedStaircase_VL53L0X/README.md | 115 ++++ .../AnimatedStaircase_VL53L0X/library.json | 15 + 4 files changed, 714 insertions(+), 22 deletions(-) create mode 100644 usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp create mode 100644 usermods/AnimatedStaircase_VL53L0X/README.md create mode 100644 usermods/AnimatedStaircase_VL53L0X/library.json diff --git a/platformio.ini b/platformio.ini index f08e7c151f..2ad70134f9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,29 +10,29 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2 - esp8266_2m - esp01_1m_full - nodemcuv2_160 - esp8266_2m_160 - esp01_1m_full_160 - nodemcuv2_compat - esp8266_2m_compat - esp01_1m_full_compat +default_envs = #nodemcuv2 + #esp8266_2m + #esp01_1m_full + #nodemcuv2_160 + #esp8266_2m_160 + #esp01_1m_full_160 + #nodemcuv2_compat + #esp8266_2m_compat + #esp01_1m_full_compat esp32dev - esp32dev_debug - esp32_eth - esp32_wrover - lolin_s2_mini - esp32c3dev - esp32c3dev_qio - esp32S3_wroom2 - esp32s3dev_16MB_opi - esp32s3dev_8MB_opi - esp32s3dev_8MB_qspi - esp32s3dev_8MB_none - esp32s3_4M_qspi - usermods + #esp32dev_debug + #esp32_eth + #esp32_wrover + #lolin_s2_mini + #esp32c3dev + #esp32c3dev_qio + #esp32S3_wroom2 + #esp32s3dev_16MB_opi + #esp32s3dev_8MB_opi + #esp32s3dev_8MB_qspi + #esp32s3dev_8MB_none + #esp32s3_4M_qspi + #usermods src_dir = ./wled00 data_dir = ./wled00/data diff --git a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp new file mode 100644 index 0000000000..49042a5b3c --- /dev/null +++ b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp @@ -0,0 +1,562 @@ +#include "wled.h" +#include + +class AnimatedStaircase_VL53L0X : public Usermod { + TaskHandle_t taskHandle = nullptr; + +private: + bool enabled = false; + bool configLoaded = false; + bool sensorsInitialised = false; + + unsigned long segment_delay_ms = 150; + unsigned long on_time_ms = 30000; + + uint16_t bottomThresholdMM = 900; + uint16_t topThresholdMM = 900; + + int8_t xshutTopPin = -1; + int8_t xshutBottomPin = -1; + + VL53L0X topVL53; + VL53L0X bottomVL53; + + bool initDone = false; + const unsigned int scanDelay = 100; + bool on = false; + +#define SWIPE_UP true +#define SWIPE_DOWN false + bool swipe = SWIPE_UP; + +#define LOWER false +#define UPPER true + bool lastSensor = LOWER; + + unsigned long lastTime = 0; + unsigned long lastScanTime = 0; + unsigned long lastSwitchTime = 0; + + byte onIndex = 0; + byte offIndex = 0; + byte maxSegmentId = 1; + byte minSegmentId = 0; + + bool topSensorRead = false; + bool topSensorWrite = false; + bool bottomSensorRead = false; + bool bottomSensorWrite = false; + bool topSensorState = false; + bool bottomSensorState = false; + + bool togglePower = false; + + static const char _name[]; + static const char _enabled[]; + static const char _segmentDelay[]; + static const char _onTime[]; + static const char _togglePower[]; + static const char _xshutTopPin[]; + static const char _xshutBottomPin[]; + static const char _topThreshold[]; + static const char _bottomThreshold[]; + + void publishMqtt(bool bottom, const char* state) { +#ifndef WLED_DISABLE_MQTT + if (WLED_MQTT_CONNECTED) { + char subuf[64]; + sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)bottom); + mqtt->publish(subuf, 0, false, state); + } +#endif + } + +#ifndef WLED_DISABLE_MQTT + 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; + } + + if (action == "down") { + topSensorWrite = true; + return true; + } + + if (action == "on") { + enable(true); + return true; + } + + if (action == "off") { + enable(false); + return true; + } + } + + return false; + } + + void onMqttConnect(bool sessionPresent) { + char subuf[64]; + + if (mqttDeviceTopic[0] != 0) { + strcpy(subuf, mqttDeviceTopic); + strcat_P(subuf, PSTR("/swipe")); + mqtt->subscribe(subuf, 0); + } + } +#endif + + void resetSensorState() { + sensorsInitialised = false; + initDone = false; + + topSensorRead = false; + topSensorWrite = false; + topSensorState = false; + + bottomSensorRead = false; + bottomSensorWrite = false; + bottomSensorState = false; + } + + bool initialiseSensors() { + if (!configLoaded) { + DEBUG_PRINTLN(F("[StaircaseVL53] Config not loaded yet")); + return false; + } + + if (xshutTopPin < 0 || xshutBottomPin < 0) { + DEBUG_PRINTLN(F("[StaircaseVL53] XSHUT pins not configured")); + return false; + } + + if (xshutTopPin == xshutBottomPin) { + DEBUG_PRINTLN(F("[StaircaseVL53] XSHUT pins must be different")); + return false; + } + + PinManagerPinType pins[] = { + { xshutTopPin, true }, + { xshutBottomPin, true }, + }; + + if (!PinManager::allocateMultiplePins(pins, 2, PinOwner::UM_AnimatedStaircase)) { + DEBUG_PRINTLN(F("[StaircaseVL53] Failed to allocate XSHUT pins")); + + PinManager::deallocatePin(xshutTopPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(xshutBottomPin, PinOwner::UM_AnimatedStaircase); + + resetSensorState(); + enabled = false; + return false; + } + + static bool wireStarted = false; + if (!wireStarted) { + Wire.begin(); + wireStarted = true; + } + + pinMode(xshutTopPin, OUTPUT); + pinMode(xshutBottomPin, OUTPUT); + + digitalWrite(xshutTopPin, LOW); + digitalWrite(xshutBottomPin, LOW); + delay(20); + + bottomVL53.setTimeout(50); + topVL53.setTimeout(50); + + digitalWrite(xshutBottomPin, HIGH); + delay(20); + + if (!bottomVL53.init()) { + DEBUG_PRINTLN(F("[StaircaseVL53] Bottom VL53L0X init failed")); + resetSensorState(); + return false; + } + + bottomVL53.setAddress(0x30); + bottomVL53.startContinuous(); + + digitalWrite(xshutTopPin, HIGH); + delay(20); + + if (!topVL53.init()) { + DEBUG_PRINTLN(F("[StaircaseVL53] Top VL53L0X init failed")); + resetSensorState(); + return false; + } + + topVL53.setAddress(0x31); + topVL53.startContinuous(); + + sensorsInitialised = true; + initDone = true; + + DEBUG_PRINTF( + "[StaircaseVL53] Sensors initialised top=%d bottom=%d\n", + xshutTopPin, + xshutBottomPin + ); + + return true; + } + + void ensureTask() { + if (taskHandle != nullptr) return; + + xTaskCreatePinnedToCore( + [](void* param) { + const TickType_t xFrequency = 10 / portTICK_PERIOD_MS; + TickType_t xLastWakeTime = xTaskGetTickCount(); + + auto* self = static_cast(param); + + while (true) { + vTaskDelayUntil(&xLastWakeTime, xFrequency); + + if (!self || !self->enabled) continue; + if (!self->configLoaded) continue; + + if (!self->sensorsInitialised) { + self->initialiseSensors(); + continue; + } + + if (!self->initDone || strip.getMaxSegments() == 0) continue; + if (strip.getSegment(0).stop == 0) continue; + + self->minSegmentId = strip.getMainSegmentId(); + self->maxSegmentId = strip.getLastActiveSegmentId() + 1; + + self->checkSensors(); + + if (self->on) self->autoPowerOff(); + + self->updateSwipe(); + } + }, + "StairVL53Task", + 4096, + this, + 1, + &taskHandle, + 1 + ); + } + + void updateSegments() { + uint8_t lastSeg = strip.getLastActiveSegmentId(); + + for (int i = minSegmentId; i <= lastSeg && i < maxSegmentId; i++) { + Segment& seg = strip.getSegment(i); + if (!seg.isActive()) continue; + + seg.setOption(SEG_OPTION_ON, i >= onIndex && i < offIndex); + } + + strip.trigger(); + stateChanged = true; + colorUpdated(CALL_MODE_DIRECT_CHANGE); + } + + bool checkSensors() { + bool sensorChanged = false; + + if ((millis() - lastScanTime) < scanDelay) return false; + lastScanTime = millis(); + + uint16_t bottomReading = bottomVL53.readRangeContinuousMillimeters(); + uint16_t topReading = topVL53.readRangeContinuousMillimeters(); + + if (bottomVL53.timeoutOccurred()) return false; + if (topVL53.timeoutOccurred()) return false; + + bottomSensorRead = bottomSensorWrite || (bottomReading < bottomThresholdMM); + topSensorRead = topSensorWrite || (topReading < topThresholdMM); + + if (bottomSensorRead != bottomSensorState) { + bottomSensorState = bottomSensorRead; + sensorChanged = true; + publishMqtt(true, bottomSensorState ? "on" : "off"); + } + + if (topSensorRead != topSensorState) { + topSensorState = topSensorRead; + sensorChanged = true; + publishMqtt(false, topSensorState ? "on" : "off"); + } + + topSensorWrite = false; + bottomSensorWrite = false; + + if (topSensorRead != bottomSensorRead) { + lastSwitchTime = millis(); + + if (on) { + lastSensor = topSensorRead; + } else { + if (togglePower && onIndex == offIndex && offMode) toggleOnOff(); + + swipe = bottomSensorRead; + + if (onIndex == offIndex) { + if (swipe == SWIPE_UP) { + onIndex = minSegmentId; + offIndex = minSegmentId; + } else { + onIndex = maxSegmentId; + offIndex = maxSegmentId; + } + } + + on = true; + } + } + + return sensorChanged; + } + + void autoPowerOff() { + if ((millis() - lastSwitchTime) > on_time_ms) { + if (bottomSensorState || topSensorState) return; + + swipe = lastSensor; + on = false; + } + } + + void updateSwipe() { + if ((millis() - lastTime) > segment_delay_ms) { + lastTime = millis(); + + byte oldOn = onIndex; + byte oldOff = offIndex; + + if (on) { + if (swipe == SWIPE_UP) { + offIndex = MIN(maxSegmentId, offIndex + 1); + } else { + onIndex = MAX(minSegmentId, onIndex - 1); + } + } else { + if (swipe == SWIPE_UP) { + onIndex = MIN(offIndex, onIndex + 1); + } else { + offIndex = MAX(onIndex, offIndex - 1); + } + } + + if (oldOn != onIndex || oldOff != offIndex) { + updateSegments(); + + if (togglePower && onIndex == offIndex && !offMode && !on) { + toggleOnOff(); + } + } + } + } + + void enable(bool enable) { + enabled = enable; + + if (!configLoaded) return; + + if (enable && !sensorsInitialised) { + initialiseSensors(); + } + + if (strip.getMaxSegments() == 0 || strip.getSegment(0).stop == 0) { + DEBUG_PRINTLN(F("[StaircaseVL53] Segments not initialized yet")); + return; + } + + if (enable) { + onIndex = minSegmentId = strip.getMainSegmentId(); + offIndex = maxSegmentId = strip.getLastActiveSegmentId() + 1; + + strip.setTransition(segment_delay_ms); + strip.trigger(); + } else { + for (int i = 0; i <= strip.getLastActiveSegmentId(); i++) { + Segment& seg = strip.getSegment(i); + if (seg.stop == 0) continue; + + seg.setOption(SEG_OPTION_ON, true); + } + + strip.trigger(); + stateChanged = true; + colorUpdated(CALL_MODE_DIRECT_CHANGE); + } + } + +public: + void setup() { + DEBUG_PRINTF( + "[StaircaseVL53] Setup called configLoaded=%d enabled=%d top=%d bottom=%d\n", + configLoaded, + enabled, + xshutTopPin, + xshutBottomPin + ); + + if (!configLoaded) return; + + if (enabled && !sensorsInitialised) { + initialiseSensors(); + } + + ensureTask(); + } + + void loop() { + } + + void cleanup() { + if (taskHandle != nullptr) { + vTaskDelete(taskHandle); + taskHandle = nullptr; + } + + if (xshutTopPin >= 0) { + PinManager::deallocatePin(xshutTopPin, PinOwner::UM_AnimatedStaircase); + } + + if (xshutBottomPin >= 0) { + PinManager::deallocatePin(xshutBottomPin, PinOwner::UM_AnimatedStaircase); + } + + resetSensorState(); + } + + uint16_t getId() { + return USERMOD_ID_ANIMATED_STAIRCASE; + } + + void addToConfig(JsonObject& root) { + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) top = root.createNestedObject(FPSTR(_name)); + + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_segmentDelay)] = segment_delay_ms; + top[FPSTR(_onTime)] = on_time_ms / 1000; + top[FPSTR(_togglePower)] = togglePower; + top[FPSTR(_xshutTopPin)] = xshutTopPin; + top[FPSTR(_xshutBottomPin)] = xshutBottomPin; + top[FPSTR(_topThreshold)] = topThresholdMM; + top[FPSTR(_bottomThreshold)] = bottomThresholdMM; + } + + bool readFromConfig(JsonObject& root) { + JsonObject top = root[FPSTR(_name)]; + + if (top.isNull()) { + configLoaded = true; + return false; + } + + int8_t oldXshutTop = xshutTopPin; + int8_t oldXshutBottom = xshutBottomPin; + + enabled = top[FPSTR(_enabled)] | enabled; + segment_delay_ms = constrain(top[FPSTR(_segmentDelay)] | segment_delay_ms, 10UL, 10000UL); + on_time_ms = constrain(top[FPSTR(_onTime)] | (on_time_ms / 1000), 10, 900) * 1000; + togglePower = top[FPSTR(_togglePower)] | togglePower; + xshutTopPin = top[FPSTR(_xshutTopPin)] | xshutTopPin; + xshutBottomPin = top[FPSTR(_xshutBottomPin)] | xshutBottomPin; + topThresholdMM = top[FPSTR(_topThreshold)] | topThresholdMM; + bottomThresholdMM = top[FPSTR(_bottomThreshold)] | bottomThresholdMM; + + configLoaded = true; + + DEBUG_PRINTF( + "[StaircaseVL53] Loaded config enabled=%d top=%d bottom=%d\n", + enabled, + xshutTopPin, + xshutBottomPin + ); + + bool pinsChanged = + oldXshutTop != xshutTopPin || + oldXshutBottom != xshutBottomPin; + + if (pinsChanged) { + if (oldXshutTop >= 0) { + PinManager::deallocatePin(oldXshutTop, PinOwner::UM_AnimatedStaircase); + } + + if (oldXshutBottom >= 0) { + PinManager::deallocatePin(oldXshutBottom, PinOwner::UM_AnimatedStaircase); + } + + resetSensorState(); + } + + setup(); + + return true; + } + + void addToJsonState(JsonObject& root) { + JsonObject staircase = root[FPSTR(_name)]; + if (staircase.isNull()) staircase = root.createNestedObject(FPSTR(_name)); + + staircase[F("top-sensor")] = topSensorRead; + staircase[F("bottom-sensor")] = bottomSensorRead; + } + + void readFromJsonState(JsonObject& root) { + JsonObject staircase = root[FPSTR(_name)]; + + if (!staircase.isNull()) { + if (staircase[FPSTR(_enabled)].is()) { + enable(staircase[FPSTR(_enabled)]); + } + + if (staircase[F("top-sensor")]) topSensorWrite = true; + if (staircase[F("bottom-sensor")]) bottomSensorWrite = true; + } + } + + void appendConfigData() { + } + + void addToJsonInfo(JsonObject& root) { + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + + String uiDomString = F(""); + + infoArr.add(uiDomString); + } +}; + +const char AnimatedStaircase_VL53L0X::_name[] PROGMEM = "staircase-vl53"; +const char AnimatedStaircase_VL53L0X::_enabled[] PROGMEM = "enabled"; +const char AnimatedStaircase_VL53L0X::_segmentDelay[] PROGMEM = "segment-delay-ms"; +const char AnimatedStaircase_VL53L0X::_onTime[] PROGMEM = "on-time-s"; +const char AnimatedStaircase_VL53L0X::_togglePower[] PROGMEM = "toggle-on-off"; +const char AnimatedStaircase_VL53L0X::_xshutTopPin[] PROGMEM = "xshut-top-pin"; +const char AnimatedStaircase_VL53L0X::_xshutBottomPin[] PROGMEM = "xshut-bottom-pin"; +const char AnimatedStaircase_VL53L0X::_topThreshold[] PROGMEM = "top-threshold-mm"; +const char AnimatedStaircase_VL53L0X::_bottomThreshold[] PROGMEM = "bottom-threshold-mm"; + +static AnimatedStaircase_VL53L0X animated_staircase_vl53; + +REGISTER_USERMOD(animated_staircase_vl53); \ No newline at end of file diff --git a/usermods/AnimatedStaircase_VL53L0X/README.md b/usermods/AnimatedStaircase_VL53L0X/README.md new file mode 100644 index 0000000000..91f50e2774 --- /dev/null +++ b/usermods/AnimatedStaircase_VL53L0X/README.md @@ -0,0 +1,115 @@ +# Usermod: Animated Staircase for VL53L0X Sensors + +This usermod is based on the excellent work done in the original [Animated_Staircase](https://github.com/Aircoookie/WLED/tree/main/usermods/Animated_Staircase) WLED usermod by [@rolfje](https://github.com/rolfje) and [@blazoncek](https://github.com/blazoncek). It retains the same beautiful functionality of animating your staircase lighting based on user movement — now enhanced with support for **VL53L0X time-of-flight distance sensors**. + +## šŸš¶ā€ā™‚ļø What It Does + +- Lights up the steps in the direction you're walking (up or down). +- Animates off behind you after a configurable timeout. +- Responds to multiple people using the stairs in either direction. +- Automatically handles bidirectional triggers with two VL53L0X sensors. + +## 🧠 What's New in This Version + +This mod replaces the original PIR or ultrasonic sensors with two **VL53L0X** I²C time-of-flight sensors. These are more compact, and immune to acoustic interference. Changes include: + +- VL53L0X support for both top and bottom sensors +- Optional `xshut` pins for I²C address assignment +- Configurable sensor threshold distance in millimeters +- Preserves original animation behavior and API +- New config options: + - `xshut-top-pin` + - `xshut-bottom-pin` + - `trigger-threshold-mm` + +## šŸ”§ Installation & Setup + +### 1. Prerequisites + +You must [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/) to use this usermod. + +To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/). + +Before compiling, you have to make the following modifications: + +Copy platformio_override.ini to the same folder as platformio.ini + +## šŸ”Œ Wiring + +- **VL53L0X** sensors share the I²C bus (SDA, SCL). +- Use optional `XSHUT` GPIOs to assign unique I²C addresses (0x30 and 0x31). +- Wire your LEDs and segments the same as the original mod. +- Example VL53L0X wiring: + - SDA → GPIO 21 (or 4) + - SCL → GPIO 22 (or 5) + - XSHUT_TOP → GPIO 17 + - XSHUT_BOTTOM → GPIO 16 + +## āš™ļø WLED Configuration + +1. Create one segment per step. +2. Save your segment layout as a preset. +3. Set that preset to apply at boot. +4. Open the **Usermod Settings** page in WLED UI: + - Enable the mod + - Set `xshut-top-pin`, `xshut-bottom-pin`, and optionally adjust animation timings + - Set `trigger-threshold-mm` (900 is a good start) + +## šŸ“” API & MQTT + +### Enable / Disable + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase-vl53":{"enabled":true}}' \ + http:///json/state +``` + +### Trigger Sensors via API + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase-vl53":{"bottom-sensor":true}}' \ + http:///json/state +``` + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase-vl53":{"top-sensor":true}}' \ + http:///json/state +``` + +### MQTT + +Publish to `/swipe`: +- `"up"` or `"down"` triggers animation +- `"on"` or `"off"` enables/disables the usermod + +## šŸ“ JSON Configuration Example + +```json +{ + "staircase-vl53": { + "enabled": true, + "segment-delay-ms": 150, + "on-time-s": 30, + "toggle-on-off": false, + "xshut-top-pin": 17, + "xshut-bottom-pin": 16, + "trigger-threshold-mm": 900 + } +} +``` + +## šŸ™ Credits + +- Original concept: [@rolfje](https://github.com/rolfje) +- Runtime configuration and MQTT: [@blazoncek](https://github.com/blazoncek) +- VL53L0X integration and enhancements: [@Hoverman1977](https://github.com/Hoverman1977) + +## šŸ“œ Changelog + +**2026-05** +- Initial release of VL53L0X-compatible version +- Migrated from ultrasonic to I²C time-of-flight sensors +- Added XSHUT pin config and threshold control diff --git a/usermods/AnimatedStaircase_VL53L0X/library.json b/usermods/AnimatedStaircase_VL53L0X/library.json new file mode 100644 index 0000000000..abc8ec1cb5 --- /dev/null +++ b/usermods/AnimatedStaircase_VL53L0X/library.json @@ -0,0 +1,15 @@ +{ + "name": "AnimatedStaircase_VL53L0X", + "version": "1.0.0", + "build": { + "libArchive": false + }, + "dependencies": [ + { + "name": "VL53L0X", + "build": { + "libArchive": false + } + } + ] +} \ No newline at end of file From 4ca9f245f3b353ce9d7ea3525aa7bea804b26ae5 Mon Sep 17 00:00:00 2001 From: Josh Dixon Date: Sat, 30 May 2026 13:06:42 +1000 Subject: [PATCH 02/10] Renamed platform_override.ini to platform_override.example.ini and updated README.md --- usermods/AnimatedStaircase_VL53L0X/README.md | 2 +- .../platformio_override_example.ini | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 usermods/AnimatedStaircase_VL53L0X/platformio_override_example.ini diff --git a/usermods/AnimatedStaircase_VL53L0X/README.md b/usermods/AnimatedStaircase_VL53L0X/README.md index 91f50e2774..1f86731c7d 100644 --- a/usermods/AnimatedStaircase_VL53L0X/README.md +++ b/usermods/AnimatedStaircase_VL53L0X/README.md @@ -32,7 +32,7 @@ To include this usermod in your WLED setup, you have to be able to [compile WLED Before compiling, you have to make the following modifications: -Copy platformio_override.ini to the same folder as platformio.ini +Copy platformio_override_example.ini to the same folder as platformio.ini ## šŸ”Œ Wiring diff --git a/usermods/AnimatedStaircase_VL53L0X/platformio_override_example.ini b/usermods/AnimatedStaircase_VL53L0X/platformio_override_example.ini new file mode 100644 index 0000000000..b98b9de538 --- /dev/null +++ b/usermods/AnimatedStaircase_VL53L0X/platformio_override_example.ini @@ -0,0 +1,14 @@ +[platformio] +default_envs = esp32dev_usermod_AnimatedStaircase_VL53L0X + +# Animated Staircase VL53L0X sensors +[env:esp32dev_usermod_AnimatedStaircase_VL53L0X] +extends = env:esp32dev +custom_usermods = + AnimatedStaircase_VL53L0X +lib_deps = + ${env:esp32dev.lib_deps} + pololu/VL53L0X @ ^1.3.0 +build_flags = + ${env:esp32dev.build_flags} + -D USERMOD_ANIMATED_STAIRCASE_VL53L0X \ No newline at end of file From 4ed24d04c5293529d04ba5112008a5587df4cfbc Mon Sep 17 00:00:00 2001 From: Josh Dixon Date: Sat, 30 May 2026 13:02:18 +1000 Subject: [PATCH 03/10] Remove platformio.ini changes, modified README.md to include reference to custom_usermods. Renamed platformio_override_example.ini as suggested to platformio_override.ini.sample --- platformio.ini | 44 +++---- usermods/AnimatedStaircase_VL53L0X/README.md | 118 +++++++++++++++++++ 2 files changed, 140 insertions(+), 22 deletions(-) diff --git a/platformio.ini b/platformio.ini index 2ad70134f9..f08e7c151f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,29 +10,29 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = #nodemcuv2 - #esp8266_2m - #esp01_1m_full - #nodemcuv2_160 - #esp8266_2m_160 - #esp01_1m_full_160 - #nodemcuv2_compat - #esp8266_2m_compat - #esp01_1m_full_compat +default_envs = nodemcuv2 + esp8266_2m + esp01_1m_full + nodemcuv2_160 + esp8266_2m_160 + esp01_1m_full_160 + nodemcuv2_compat + esp8266_2m_compat + esp01_1m_full_compat esp32dev - #esp32dev_debug - #esp32_eth - #esp32_wrover - #lolin_s2_mini - #esp32c3dev - #esp32c3dev_qio - #esp32S3_wroom2 - #esp32s3dev_16MB_opi - #esp32s3dev_8MB_opi - #esp32s3dev_8MB_qspi - #esp32s3dev_8MB_none - #esp32s3_4M_qspi - #usermods + esp32dev_debug + esp32_eth + esp32_wrover + lolin_s2_mini + esp32c3dev + esp32c3dev_qio + esp32S3_wroom2 + esp32s3dev_16MB_opi + esp32s3dev_8MB_opi + esp32s3dev_8MB_qspi + esp32s3dev_8MB_none + esp32s3_4M_qspi + usermods src_dir = ./wled00 data_dir = ./wled00/data diff --git a/usermods/AnimatedStaircase_VL53L0X/README.md b/usermods/AnimatedStaircase_VL53L0X/README.md index 1f86731c7d..9086bea843 100644 --- a/usermods/AnimatedStaircase_VL53L0X/README.md +++ b/usermods/AnimatedStaircase_VL53L0X/README.md @@ -113,3 +113,121 @@ Publish to `/swipe`: - Initial release of VL53L0X-compatible version - Migrated from ultrasonic to I²C time-of-flight sensors - Added XSHUT pin config and threshold control + +# Usermod: Animated Staircase for VL53L0X Sensors + +This usermod is based on the excellent work done in the original [Animated_Staircase](https://github.com/Aircoookie/WLED/tree/main/usermods/Animated_Staircase) WLED usermod by [@rolfje](https://github.com/rolfje) and [@blazoncek](https://github.com/blazoncek). It retains the same beautiful functionality of animating your staircase lighting based on user movement — now enhanced with support for **VL53L0X time-of-flight distance sensors**. + +## šŸš¶ā€ā™‚ļø What It Does + +- Lights up the steps in the direction you're walking (up or down). +- Animates off behind you after a configurable timeout. +- Responds to multiple people using the stairs in either direction. +- Automatically handles bidirectional triggers with two VL53L0X sensors. + +## 🧠 What's New in This Version + +This mod replaces the original PIR or ultrasonic sensors with two **VL53L0X** I²C time-of-flight sensors. These are more compact, and immune to acoustic interference. Changes include: + +- VL53L0X support for both top and bottom sensors +- Optional `xshut` pins for I²C address assignment +- Configurable sensor threshold distance in millimeters +- Preserves original animation behavior and API +- New config options: + - `xshut-top-pin` + - `xshut-bottom-pin` + - `trigger-threshold-mm` + +## šŸ”§ Installation & Setup + +### 1. Prerequisites + +You must [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/) to use this usermod. + +To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/). + +Before compiling, you have to make the following modifications: + +In order to add the usermod to WLED the custom_usermods section in platformio_override.ini needs to include this usermod. + +- Copy platformio_override.ini.sample to the same folder as platformio.ini and rename to platformio_override.ini + +## šŸ”Œ Wiring + +- **VL53L0X** sensors share the I²C bus (SDA, SCL). +- Use optional `XSHUT` GPIOs to assign unique I²C addresses (0x30 and 0x31). +- Wire your LEDs and segments the same as the original mod. +- Example VL53L0X wiring: + - SDA → GPIO 21 (or 4) + - SCL → GPIO 22 (or 5) + - XSHUT_TOP → GPIO 17 + - XSHUT_BOTTOM → GPIO 16 + +## āš™ļø WLED Configuration + +1. Create one segment per step. +2. Save your segment layout as a preset. +3. Set that preset to apply at boot. +4. Open the **Usermod Settings** page in WLED UI: + - Enable the mod + - Set `xshut-top-pin`, `xshut-bottom-pin`, and optionally adjust animation timings + - Set `trigger-threshold-mm` (900 is a good start) + +## šŸ“” API & MQTT + +### Enable / Disable + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase-vl53":{"enabled":true}}' \ + http:///json/state +``` + +### Trigger Sensors via API + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase-vl53":{"bottom-sensor":true}}' \ + http:///json/state +``` + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase-vl53":{"top-sensor":true}}' \ + http:///json/state +``` + +### MQTT + +Publish to `/swipe`: +- `"up"` or `"down"` triggers animation +- `"on"` or `"off"` enables/disables the usermod + +## šŸ“ JSON Configuration Example + +```json +{ + "staircase-vl53": { + "enabled": true, + "segment-delay-ms": 150, + "on-time-s": 30, + "toggle-on-off": false, + "xshut-top-pin": 17, + "xshut-bottom-pin": 16, + "trigger-threshold-mm": 900 + } +} +``` + +## šŸ™ Credits + +- Original concept: [@rolfje](https://github.com/rolfje) +- Runtime configuration and MQTT: [@blazoncek](https://github.com/blazoncek) +- VL53L0X integration and enhancements: [@Hoverman1977](https://github.com/Hoverman1977) + +## šŸ“œ Changelog + +**2026-05** +- Initial release of VL53L0X-compatible version +- Migrated from ultrasonic to I²C time-of-flight sensors +- Added XSHUT pin config and threshold control From dd5bfd968271dcfdecd251474c076fd73e045de8 Mon Sep 17 00:00:00 2001 From: Josh Dixon Date: Sun, 31 May 2026 09:22:41 +1000 Subject: [PATCH 04/10] Saved new platformio_override.ini.sample --- .../AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp | 5 ++--- ..._override_example.ini => platformio_override.ini.sample} | 6 ------ 2 files changed, 2 insertions(+), 9 deletions(-) rename usermods/AnimatedStaircase_VL53L0X/{platformio_override_example.ini => platformio_override.ini.sample} (60%) diff --git a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp index 49042a5b3c..6f6e55b1e9 100644 --- a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp +++ b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp @@ -211,7 +211,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { void ensureTask() { if (taskHandle != nullptr) return; - xTaskCreatePinnedToCore( + xTaskCreate( [](void* param) { const TickType_t xFrequency = 10 / portTICK_PERIOD_MS; TickType_t xLastWakeTime = xTaskGetTickCount(); @@ -246,8 +246,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { 4096, this, 1, - &taskHandle, - 1 + &taskHandle ); } diff --git a/usermods/AnimatedStaircase_VL53L0X/platformio_override_example.ini b/usermods/AnimatedStaircase_VL53L0X/platformio_override.ini.sample similarity index 60% rename from usermods/AnimatedStaircase_VL53L0X/platformio_override_example.ini rename to usermods/AnimatedStaircase_VL53L0X/platformio_override.ini.sample index b98b9de538..15ce2c381f 100644 --- a/usermods/AnimatedStaircase_VL53L0X/platformio_override_example.ini +++ b/usermods/AnimatedStaircase_VL53L0X/platformio_override.ini.sample @@ -6,9 +6,3 @@ default_envs = esp32dev_usermod_AnimatedStaircase_VL53L0X extends = env:esp32dev custom_usermods = AnimatedStaircase_VL53L0X -lib_deps = - ${env:esp32dev.lib_deps} - pololu/VL53L0X @ ^1.3.0 -build_flags = - ${env:esp32dev.build_flags} - -D USERMOD_ANIMATED_STAIRCASE_VL53L0X \ No newline at end of file From 48b580a9388634b5480237ca507c5341ef021d89 Mon Sep 17 00:00:00 2001 From: Hoverman1977 Date: Sun, 31 May 2026 09:32:06 +1000 Subject: [PATCH 05/10] Fix VL53 staircase task synchronization --- .../AnimatedStaircase_VL53L0X.cpp | 99 ++++++++++++++----- 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp index 49042a5b3c..ed3266bdfd 100644 --- a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp +++ b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp @@ -3,6 +3,8 @@ class AnimatedStaircase_VL53L0X : public Usermod { TaskHandle_t taskHandle = nullptr; + volatile bool loopRequested = true; + volatile bool pendingColorUpdated = false; private: bool enabled = false; @@ -22,7 +24,8 @@ class AnimatedStaircase_VL53L0X : public Usermod { VL53L0X bottomVL53; bool initDone = false; - const unsigned int scanDelay = 100; + static constexpr uint16_t VL53L0X_MAX_DISTANCE_MM = 2000; + const unsigned int scanDelay = 250; bool on = false; #define SWIPE_UP true @@ -111,6 +114,19 @@ class AnimatedStaircase_VL53L0X : public Usermod { } #endif + void resetRuntimeState() { + on = false; + swipe = SWIPE_UP; + lastSensor = LOWER; + lastTime = 0; + lastScanTime = 0; + lastSwitchTime = 0; + onIndex = 0; + offIndex = 0; + minSegmentId = 0; + maxSegmentId = 1; + } + void resetSensorState() { sensorsInitialised = false; initDone = false; @@ -124,6 +140,16 @@ class AnimatedStaircase_VL53L0X : public Usermod { bottomSensorState = false; } + uint16_t readThreshold(JsonObject& top, const __FlashStringHelper* key, uint16_t currentValue) { + JsonVariant value = top[key]; + if (value.isNull()) return currentValue; + + if (!value.is()) return currentValue; + + long threshold = value.as(); + return constrain(threshold, 0L, (long)VL53L0X_MAX_DISTANCE_MM); + } + bool initialiseSensors() { if (!configLoaded) { DEBUG_PRINTLN(F("[StaircaseVL53] Config not loaded yet")); @@ -211,35 +237,16 @@ class AnimatedStaircase_VL53L0X : public Usermod { void ensureTask() { if (taskHandle != nullptr) return; - xTaskCreatePinnedToCore( + BaseType_t taskCreated = xTaskCreatePinnedToCore( [](void* param) { - const TickType_t xFrequency = 10 / portTICK_PERIOD_MS; + const TickType_t xFrequency = 250 / portTICK_PERIOD_MS; TickType_t xLastWakeTime = xTaskGetTickCount(); auto* self = static_cast(param); while (true) { vTaskDelayUntil(&xLastWakeTime, xFrequency); - - if (!self || !self->enabled) continue; - if (!self->configLoaded) continue; - - if (!self->sensorsInitialised) { - self->initialiseSensors(); - continue; - } - - if (!self->initDone || strip.getMaxSegments() == 0) continue; - if (strip.getSegment(0).stop == 0) continue; - - self->minSegmentId = strip.getMainSegmentId(); - self->maxSegmentId = strip.getLastActiveSegmentId() + 1; - - self->checkSensors(); - - if (self->on) self->autoPowerOff(); - - self->updateSwipe(); + if (self) self->loopRequested = true; } }, "StairVL53Task", @@ -249,6 +256,12 @@ class AnimatedStaircase_VL53L0X : public Usermod { &taskHandle, 1 ); + + if (taskCreated != pdPASS) { + DEBUG_PRINTLN(F("[StaircaseVL53] Failed to create background task")); + taskHandle = nullptr; + enabled = false; + } } void updateSegments() { @@ -263,7 +276,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { strip.trigger(); stateChanged = true; - colorUpdated(CALL_MODE_DIRECT_CHANGE); + pendingColorUpdated = true; } bool checkSensors() { @@ -365,6 +378,8 @@ class AnimatedStaircase_VL53L0X : public Usermod { void enable(bool enable) { enabled = enable; + resetRuntimeState(); + resetSensorState(); if (!configLoaded) return; @@ -393,7 +408,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { strip.trigger(); stateChanged = true; - colorUpdated(CALL_MODE_DIRECT_CHANGE); + pendingColorUpdated = true; } } @@ -417,6 +432,32 @@ class AnimatedStaircase_VL53L0X : public Usermod { } void loop() { + if (pendingColorUpdated) { + pendingColorUpdated = false; + colorUpdated(CALL_MODE_DIRECT_CHANGE); + } + + if (!loopRequested) return; + loopRequested = false; + + if (!enabled || !configLoaded) return; + + if (!sensorsInitialised) { + initialiseSensors(); + return; + } + + if (!initDone || strip.getMaxSegments() == 0) return; + if (strip.getSegment(0).stop == 0) return; + + minSegmentId = strip.getMainSegmentId(); + maxSegmentId = strip.getLastActiveSegmentId() + 1; + + checkSensors(); + + if (on) autoPowerOff(); + + updateSwipe(); } void cleanup() { @@ -426,10 +467,12 @@ class AnimatedStaircase_VL53L0X : public Usermod { } if (xshutTopPin >= 0) { + digitalWrite(xshutTopPin, LOW); PinManager::deallocatePin(xshutTopPin, PinOwner::UM_AnimatedStaircase); } if (xshutBottomPin >= 0) { + digitalWrite(xshutBottomPin, LOW); PinManager::deallocatePin(xshutBottomPin, PinOwner::UM_AnimatedStaircase); } @@ -471,8 +514,8 @@ class AnimatedStaircase_VL53L0X : public Usermod { togglePower = top[FPSTR(_togglePower)] | togglePower; xshutTopPin = top[FPSTR(_xshutTopPin)] | xshutTopPin; xshutBottomPin = top[FPSTR(_xshutBottomPin)] | xshutBottomPin; - topThresholdMM = top[FPSTR(_topThreshold)] | topThresholdMM; - bottomThresholdMM = top[FPSTR(_bottomThreshold)] | bottomThresholdMM; + topThresholdMM = readThreshold(top, FPSTR(_topThreshold), topThresholdMM); + bottomThresholdMM = readThreshold(top, FPSTR(_bottomThreshold), bottomThresholdMM); configLoaded = true; @@ -489,10 +532,12 @@ class AnimatedStaircase_VL53L0X : public Usermod { if (pinsChanged) { if (oldXshutTop >= 0) { + digitalWrite(oldXshutTop, LOW); PinManager::deallocatePin(oldXshutTop, PinOwner::UM_AnimatedStaircase); } if (oldXshutBottom >= 0) { + digitalWrite(oldXshutBottom, LOW); PinManager::deallocatePin(oldXshutBottom, PinOwner::UM_AnimatedStaircase); } From ec59f6d1b89161f1995ae21037f6c9de7a45d630 Mon Sep 17 00:00:00 2001 From: Josh Dixon Date: Sun, 31 May 2026 09:41:07 +1000 Subject: [PATCH 06/10] Use xTaskCreate instead of pinning to a core --- .../AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp index 68bfa960ed..bc75e2755d 100644 --- a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp +++ b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp @@ -237,11 +237,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { void ensureTask() { if (taskHandle != nullptr) return; -<<<<<<< HEAD - xTaskCreate( -======= - BaseType_t taskCreated = xTaskCreatePinnedToCore( ->>>>>>> origin/codex/fix-thread-safety-in-animatedstaircase-usermod + BaseType_t taskCreated = xTaskCreate( [](void* param) { const TickType_t xFrequency = 250 / portTICK_PERIOD_MS; TickType_t xLastWakeTime = xTaskGetTickCount(); From db660947081535c6e1325106ea45dc4a4ea7f870 Mon Sep 17 00:00:00 2001 From: Josh Dixon Date: Sun, 31 May 2026 10:28:27 +1000 Subject: [PATCH 07/10] Reverted ensureTask back to 10 ms after testing. 10ms ensures a smooth effect when turning on segments - with a large staircase (60 segments) it takes 600ms to turn on at 10ms which is acceptable. --- .../AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp index bc75e2755d..265942b421 100644 --- a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp +++ b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp @@ -239,7 +239,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { BaseType_t taskCreated = xTaskCreate( [](void* param) { - const TickType_t xFrequency = 250 / portTICK_PERIOD_MS; + const TickType_t xFrequency = 10 / portTICK_PERIOD_MS; TickType_t xLastWakeTime = xTaskGetTickCount(); auto* self = static_cast(param); From 9b16b1c544c3a0eb3ec421e45cfa85cb81a8318a Mon Sep 17 00:00:00 2001 From: Josh Dixon Date: Sun, 31 May 2026 10:56:26 +1000 Subject: [PATCH 08/10] Updated README.md to include custom_usermods reference --- usermods/AnimatedStaircase_VL53L0X/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/AnimatedStaircase_VL53L0X/README.md b/usermods/AnimatedStaircase_VL53L0X/README.md index 9086bea843..50de19d310 100644 --- a/usermods/AnimatedStaircase_VL53L0X/README.md +++ b/usermods/AnimatedStaircase_VL53L0X/README.md @@ -32,7 +32,7 @@ To include this usermod in your WLED setup, you have to be able to [compile WLED Before compiling, you have to make the following modifications: -Copy platformio_override_example.ini to the same folder as platformio.ini +Add the AnimatedStaircase_VL53L03X usermod to custom_usermods in the build config or just platformio_override.ini.sample to the same folder as platformio.ini ## šŸ”Œ Wiring From e66cee558cb512e163f4c132d367e9c4578e02c0 Mon Sep 17 00:00:00 2001 From: Josh Dixon Date: Sun, 31 May 2026 11:19:34 +1000 Subject: [PATCH 09/10] Compilation instruction on line 35 of README.md updated.. Also implemented sensor shutdown function that gracefully shuts down sensors and deallocates pins. --- .../AnimatedStaircase_VL53L0X.cpp | 46 ++++--- usermods/AnimatedStaircase_VL53L0X/README.md | 120 +----------------- 2 files changed, 23 insertions(+), 143 deletions(-) diff --git a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp index 265942b421..00c1229eef 100644 --- a/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp +++ b/usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp @@ -203,7 +203,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { if (!bottomVL53.init()) { DEBUG_PRINTLN(F("[StaircaseVL53] Bottom VL53L0X init failed")); - resetSensorState(); + shutdownSensors(); return false; } @@ -215,7 +215,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { if (!topVL53.init()) { DEBUG_PRINTLN(F("[StaircaseVL53] Top VL53L0X init failed")); - resetSensorState(); + shutdownSensors(); return false; } @@ -234,6 +234,24 @@ class AnimatedStaircase_VL53L0X : public Usermod { return true; } + void shutdownSensorPins(int8_t topPin, int8_t bottomPin) { + if (topPin >= 0) { + digitalWrite(topPin, LOW); + PinManager::deallocatePin(topPin, PinOwner::UM_AnimatedStaircase); + } + + if (bottomPin >= 0) { + digitalWrite(bottomPin, LOW); + PinManager::deallocatePin(bottomPin, PinOwner::UM_AnimatedStaircase); + } + + resetSensorState(); + } + + void shutdownSensors() { + shutdownSensorPins(xshutTopPin, xshutBottomPin); + } + void ensureTask() { if (taskHandle != nullptr) return; @@ -465,17 +483,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { taskHandle = nullptr; } - if (xshutTopPin >= 0) { - digitalWrite(xshutTopPin, LOW); - PinManager::deallocatePin(xshutTopPin, PinOwner::UM_AnimatedStaircase); - } - - if (xshutBottomPin >= 0) { - digitalWrite(xshutBottomPin, LOW); - PinManager::deallocatePin(xshutBottomPin, PinOwner::UM_AnimatedStaircase); - } - - resetSensorState(); + shutdownSensors(); } uint16_t getId() { @@ -530,17 +538,7 @@ class AnimatedStaircase_VL53L0X : public Usermod { oldXshutBottom != xshutBottomPin; if (pinsChanged) { - if (oldXshutTop >= 0) { - digitalWrite(oldXshutTop, LOW); - PinManager::deallocatePin(oldXshutTop, PinOwner::UM_AnimatedStaircase); - } - - if (oldXshutBottom >= 0) { - digitalWrite(oldXshutBottom, LOW); - PinManager::deallocatePin(oldXshutBottom, PinOwner::UM_AnimatedStaircase); - } - - resetSensorState(); + shutdownSensorPins(oldXshutTop, oldXshutBottom); } setup(); diff --git a/usermods/AnimatedStaircase_VL53L0X/README.md b/usermods/AnimatedStaircase_VL53L0X/README.md index 50de19d310..991cb0a578 100644 --- a/usermods/AnimatedStaircase_VL53L0X/README.md +++ b/usermods/AnimatedStaircase_VL53L0X/README.md @@ -32,125 +32,7 @@ To include this usermod in your WLED setup, you have to be able to [compile WLED Before compiling, you have to make the following modifications: -Add the AnimatedStaircase_VL53L03X usermod to custom_usermods in the build config or just platformio_override.ini.sample to the same folder as platformio.ini - -## šŸ”Œ Wiring - -- **VL53L0X** sensors share the I²C bus (SDA, SCL). -- Use optional `XSHUT` GPIOs to assign unique I²C addresses (0x30 and 0x31). -- Wire your LEDs and segments the same as the original mod. -- Example VL53L0X wiring: - - SDA → GPIO 21 (or 4) - - SCL → GPIO 22 (or 5) - - XSHUT_TOP → GPIO 17 - - XSHUT_BOTTOM → GPIO 16 - -## āš™ļø WLED Configuration - -1. Create one segment per step. -2. Save your segment layout as a preset. -3. Set that preset to apply at boot. -4. Open the **Usermod Settings** page in WLED UI: - - Enable the mod - - Set `xshut-top-pin`, `xshut-bottom-pin`, and optionally adjust animation timings - - Set `trigger-threshold-mm` (900 is a good start) - -## šŸ“” API & MQTT - -### Enable / Disable - -```bash -curl -X POST -H "Content-Type: application/json" \ - -d '{"staircase-vl53":{"enabled":true}}' \ - http:///json/state -``` - -### Trigger Sensors via API - -```bash -curl -X POST -H "Content-Type: application/json" \ - -d '{"staircase-vl53":{"bottom-sensor":true}}' \ - http:///json/state -``` - -```bash -curl -X POST -H "Content-Type: application/json" \ - -d '{"staircase-vl53":{"top-sensor":true}}' \ - http:///json/state -``` - -### MQTT - -Publish to `/swipe`: -- `"up"` or `"down"` triggers animation -- `"on"` or `"off"` enables/disables the usermod - -## šŸ“ JSON Configuration Example - -```json -{ - "staircase-vl53": { - "enabled": true, - "segment-delay-ms": 150, - "on-time-s": 30, - "toggle-on-off": false, - "xshut-top-pin": 17, - "xshut-bottom-pin": 16, - "trigger-threshold-mm": 900 - } -} -``` - -## šŸ™ Credits - -- Original concept: [@rolfje](https://github.com/rolfje) -- Runtime configuration and MQTT: [@blazoncek](https://github.com/blazoncek) -- VL53L0X integration and enhancements: [@Hoverman1977](https://github.com/Hoverman1977) - -## šŸ“œ Changelog - -**2026-05** -- Initial release of VL53L0X-compatible version -- Migrated from ultrasonic to I²C time-of-flight sensors -- Added XSHUT pin config and threshold control - -# Usermod: Animated Staircase for VL53L0X Sensors - -This usermod is based on the excellent work done in the original [Animated_Staircase](https://github.com/Aircoookie/WLED/tree/main/usermods/Animated_Staircase) WLED usermod by [@rolfje](https://github.com/rolfje) and [@blazoncek](https://github.com/blazoncek). It retains the same beautiful functionality of animating your staircase lighting based on user movement — now enhanced with support for **VL53L0X time-of-flight distance sensors**. - -## šŸš¶ā€ā™‚ļø What It Does - -- Lights up the steps in the direction you're walking (up or down). -- Animates off behind you after a configurable timeout. -- Responds to multiple people using the stairs in either direction. -- Automatically handles bidirectional triggers with two VL53L0X sensors. - -## 🧠 What's New in This Version - -This mod replaces the original PIR or ultrasonic sensors with two **VL53L0X** I²C time-of-flight sensors. These are more compact, and immune to acoustic interference. Changes include: - -- VL53L0X support for both top and bottom sensors -- Optional `xshut` pins for I²C address assignment -- Configurable sensor threshold distance in millimeters -- Preserves original animation behavior and API -- New config options: - - `xshut-top-pin` - - `xshut-bottom-pin` - - `trigger-threshold-mm` - -## šŸ”§ Installation & Setup - -### 1. Prerequisites - -You must [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/) to use this usermod. - -To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/). - -Before compiling, you have to make the following modifications: - -In order to add the usermod to WLED the custom_usermods section in platformio_override.ini needs to include this usermod. - -- Copy platformio_override.ini.sample to the same folder as platformio.ini and rename to platformio_override.ini +Add the AnimatedStaircase_VL53L03X usermod to custom_usermods in the build config or just copy platformio_override.ini.sample to the same folder as platformio.ini ## šŸ”Œ Wiring From 1bc751c63c48c422ee0565ddf08a05b283584625 Mon Sep 17 00:00:00 2001 From: Josh Dixon Date: Sun, 31 May 2026 11:35:36 +1000 Subject: [PATCH 10/10] README.md updated to clarify that the XSHUT pins are required not optional --- usermods/AnimatedStaircase_VL53L0X/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/AnimatedStaircase_VL53L0X/README.md b/usermods/AnimatedStaircase_VL53L0X/README.md index 991cb0a578..4e59973b88 100644 --- a/usermods/AnimatedStaircase_VL53L0X/README.md +++ b/usermods/AnimatedStaircase_VL53L0X/README.md @@ -37,7 +37,7 @@ Add the AnimatedStaircase_VL53L03X usermod to custom_usermods in the build confi ## šŸ”Œ Wiring - **VL53L0X** sensors share the I²C bus (SDA, SCL). -- Use optional `XSHUT` GPIOs to assign unique I²C addresses (0x30 and 0x31). +- Use required `XSHUT` GPIOs to assign unique I²C addresses (0x30 and 0x31). - Wire your LEDs and segments the same as the original mod. - Example VL53L0X wiring: - SDA → GPIO 21 (or 4)