Skip to content

Migrated AnimatedStaircase usermod to VL53L0X TOF sensors#5656

Open
Hoverman1977 wants to merge 2 commits into
wled:mainfrom
Hoverman1977:Hoverman1977/usermod_AnimatedStaircase_VL53L0X
Open

Migrated AnimatedStaircase usermod to VL53L0X TOF sensors#5656
Hoverman1977 wants to merge 2 commits into
wled:mainfrom
Hoverman1977:Hoverman1977/usermod_AnimatedStaircase_VL53L0X

Conversation

@Hoverman1977
Copy link
Copy Markdown

@Hoverman1977 Hoverman1977 commented May 30, 2026

Summary by CodeRabbit

  • New Features

    • AnimatedStaircase_VL53L0X usermod: configurable staircase lighting using dual VL53L0X distance sensors, swipe detection, MQTT integration, and optional auto power-off
    • Default build target updated to esp32dev for usermod builds
  • Documentation

    • Added README with wiring, setup, UI/API examples, MQTT trigger format, sample config, and changelog

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4006e8bb-f09e-409b-83fe-8f6f2ea73f55

📥 Commits

Reviewing files that changed from the base of the PR and between a018eec and 4ca9f24.

📒 Files selected for processing (2)
  • usermods/AnimatedStaircase_VL53L0X/README.md
  • usermods/AnimatedStaircase_VL53L0X/platformio_override_example.ini
✅ Files skipped from review due to trivial changes (1)
  • usermods/AnimatedStaircase_VL53L0X/README.md

Walkthrough

Adds a new WLED usermod, AnimatedStaircase_VL53L0X, that uses two VL53L0X time-of-flight sensors (with optional XSHUT pins) to drive segmented staircase animations via a FreeRTOS polling task; includes config/state JSON IO, MQTT state publish, README, library manifest, and PlatformIO override/example config while restricting default build envs to esp32dev.

Changes

AnimatedStaircase_VL53L0X Usermod Implementation

Layer / File(s) Summary
Sensor initialization and configuration storage
usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp (range_3ed74a5f162d)
Declares sensor/state fields, timing/threshold and XSHUT pin configs, MQTT helper; implements initialiseSensors() to allocate XSHUT pins, initialize two VL53L0X sensors with distinct addresses, and start continuous ranging.
Background task and segment animation
usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp (range_c05f8a5dc2cc)
Creates FreeRTOS background task (ensureTask) that periodically polls sensors and runs updateSegments() to apply computed on/off segment options and trigger WLED updates.
Sensor reading and swipe detection
usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp (range_2547ed02952e)
Implements checkSensors() with read rate-limiting, continuous-distance reads with timeouts, threshold-based top/bottom activation, MQTT publishes on state transitions, swipe boundary updates, autoPowerOff(), and updateSwipe() index advancement.
Lifecycle management and enable/disable
usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp (range_775401b88459)
Implements enable(bool) to start/stop the usermod (first-time sensor init), setup()/cleanup() for task teardown and XSHUT deallocation, and getId() implementation.
Configuration persistence and state serialization
usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp (range_58cef89ec087)
Implements addToConfig()/readFromConfig() for persisted params (enabled, timings, on-time, toggle behavior, XSHUT pins, thresholds) and addToJsonState()/readFromJsonState() for sensor flags and enabled state.
UI metadata and module registration
usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp (range_b535e5f4af98)
Adds addToJsonInfo() HTML button, PROGMEM JSON key strings, global instance and REGISTER_USERMOD() registration.

Documentation, Manifest, and Build Overrides

Layer / File(s) Summary
README and API/usage examples
usermods/AnimatedStaircase_VL53L0X/README.md (range_d4d952ffc02c, range_6bbfaa5e72a1, range_12f1fb98cc33, range_341d43f7cd2d)
Adds README describing behavior, wiring (I2C/XSHUT), WLED UI steps, curl/MQTT examples, JSON config sample, credits, and changelog.
PlatformIO override and library manifest
usermods/AnimatedStaircase_VL53L0X/platformio_override_example.ini, usermods/AnimatedStaircase_VL53L0X/library.json, platformio.ini (range_9c010c2fb392, range_a2041b91d68a, range_b970b185cdd7, range_75ffff9429f8)
Adds a platformio_override_example.ini env for esp32dev usermod with pololu/VL53L0X dependency and USERMOD define; adds library.json declaring the usermod library and VL53L0X dependency; updates top-level platformio.ini default_envs to leave only esp32dev enabled.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • wled/WLED#5107: Related changes to platformio.ini default_envs and ESP32-related environment selection.
  • wled/WLED#5644: Related PlatformIO usermod environment patterns and custom_usermods usage.

Suggested labels

documentation

Suggested reviewers

  • softhack007
  • DedeHai
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: migrating the AnimatedStaircase usermod from PIR/ultrasonic sensors to VL53L0X time-of-flight sensors.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added enhancement usermod usermod related labels May 30, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp (2)

64-69: ⚡ Quick win

Verify the fixed MQTT buffers are large enough.

Both paths build on char subuf[64] with unbounded sprintf_P / strcpy + strcat_P. If mqttDeviceTopic ever grows past the expected limit, this becomes a stack overwrite. Please confirm the upstream max length leaves room for "/motion/%d" and "/swipe", or switch to bounded APIs so the safety invariant lives in this file.

Also applies to: 103-109

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp` around
lines 64 - 69, The publishMqtt routine uses a fixed stack buffer char subuf[64]
and unbounded sprintf_P/strcpy/strcat_P which can overflow if mqttDeviceTopic
grows; change publishMqtt (and the similar code around where subuf is used
again) to build the topic with bounded APIs or a length check: compute required
length (strlen(mqttDeviceTopic) + room for "/motion/%d" or "/swipe" + null), and
either allocate dynamically or use snprintf/strncpy/strncat (or ensure the
buffer constant is increased safely) so you never write past subuf; update both
occurrences that reference subuf and mqttDeviceTopic to follow this bounded
pattern.

28-34: ⚡ Quick win

Replace these #define booleans with scoped constants.

SWIPE_UP, SWIPE_DOWN, LOWER, and UPPER are still file-global after preprocessing, so they leak outside the class and lose type checking. static constexpr bool or a small enum keeps them scoped and matches the repo rule here.

As per coding guidelines, "Prefer constexpr over #define for compile-time constants in C++."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp` around
lines 28 - 34, Replace the four preprocessor booleans (SWIPE_UP, SWIPE_DOWN,
LOWER, UPPER) with scoped compile-time constants to avoid global leakage and
preserve type safety: remove the `#define` lines and declare either static
constexpr bool SWIPE_UP/ SWIPE_DOWN and static constexpr bool LOWER/ UPPER in
the relevant class/namespace or define small scoped enums (e.g., enum class
Swipe { Up, Down } and enum class SensorPos { Lower, Upper }) and update the
variables swipe and lastSensor to use the new scoped constants/types (variables
named swipe and lastSensor in AnimatedStaircase_VL53L0X.cpp). Ensure all
references are updated to the new names/types.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@platformio.ini`:
- Around line 13-35: The change narrows the PlatformIO default build matrix by
editing the default_envs list (e.g., removing many envs and leaving esp32dev),
which affects CI; restore or document the intended CI behavior and obtain
explicit maintainer approval before merging. Revert the default_envs to the
previous matrix or add a clear comment in platformio.ini explaining the
intentional change and its CI impact, then get an explicit approval from a WLED
maintainer/org member (record the approval in the PR description) referencing
the modified default_envs and the retained envs like esp32dev so reviewers can
verify the intended scope.

In `@usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp`:
- Around line 474-475: The assignments to topThresholdMM and bottomThresholdMM
are taking client JSON values directly (via top[FPSTR(_topThreshold)] /
top[FPSTR(_bottomThreshold)]) and must be range-clamped before storing so
malformed/negative/oversized inputs can't break checkSensors(); extract the
numeric value, validate it is numeric, clamp it to the sensor's valid range
(e.g., 0..SENSOR_MAX_DISTANCE_MM or define a constant like
VL53L0X_MAX_DISTANCE_MM) and then assign the clamped value to topThresholdMM and
bottomThresholdMM (replace the current bitwise-or pattern), ensuring the new
logic is used wherever these thresholds are read (e.g., checkSensors()).
- Around line 178-193: The init failure paths in initialiseSensors() leave
partial hardware state (bottom/top sensors running, XSHUT pins held) before
returning; modify the two failure branches after bottomVL53.init() and
topVL53.init() to explicitly stop and reset any sensor started so far (e.g.,
call bottomVL53.stopContinuous()/reset or power-cycle via
digitalWrite(xshutBottomPin, LOW) and digitalWrite(xshutTopPin, LOW) as
appropriate), clear any I2C address assignments (if applicable), and
release/deallocate pins (the same cleanup resetSensorState() does plus explicit
pin power-down) before returning false so subsequent retries and
allocateMultiplePins() see a clean state. Ensure references to bottomVL53,
topVL53, xshutTopPin, xshutBottomPin, setAddress, startContinuous, and
resetSensorState() are used to locate and implement the unwind logic.
- Around line 221-243: The background task started by ensureTask() accesses and
mutates shared usermod state (fields like enabled, configLoaded,
sensorsInitialised, on/off indices, timers, on, swipe) and calls into
strip/segment APIs (strip.get*, strip.getSegment, setOption, setTransition,
trigger, toggleOnOff) without synchronization, causing races with main-context
methods (readFromConfig(), readFromJsonState(), enable(), cleanup(), MQTT
handlers). Fix by adding a single FreeRTOS mutex (e.g., usermodMutex) or a
task-level serialization mechanism: take the mutex before any reads/writes or
strip/segment calls in the task loop (the while(true) block that calls
initialiseSensors(), checkSensors(), autoPowerOff(), updateSwipe(), and when
computing minSegmentId/maxSegmentId), and also take the same mutex in
readFromConfig(), readFromJsonState(), enable(), cleanup(), and any MQTT
handlers or functions that call initialiseSensors() or strip.* so those
operations are serialized; alternatively suspend the task
(vTaskSuspend/vTaskResume using the task handle created in ensureTask()) around
large critical sections in enable()/cleanup() if fine-grained locking is
infeasible. Ensure the mutex is created once during initialization and always
released on every code path, and prefer using the mutex rather than critical
sections to avoid blocking other WLED internals.

In `@usermods/AnimatedStaircase_VL53L0X/README.md`:
- Around line 33-36: The README's install steps omit activating the
usermod—update the "Before compiling" section to instruct users to add
AnimatedStaircase_VL53L0X to the custom_usermods list in their PlatformIO build
configuration (e.g., in platformio_override.ini or platformio.ini) so the
usermod is actually enabled; reference the exact name
"AnimatedStaircase_VL53L0X" and the key "custom_usermods" and show the placement
(build environment section) where it should be added.
- Line 17: Update the README to stop calling XSHUT pins optional: explicitly
state that both XSHUT pins are mandatory and must be assigned to different GPIOs
(they cannot be the same), because initialiseSensors() in
AnimatedStaircase_VL53L0X.cpp requires both pins to be set for I²C address
assignment and will disable the usermod if either is unset; revise the relevant
lines (the earlier pin description and the examples/usage sections) to reflect
“required” and “must be different” wording so users configure two distinct XSHUT
pins before running initialiseSensors().

---

Nitpick comments:
In `@usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp`:
- Around line 64-69: The publishMqtt routine uses a fixed stack buffer char
subuf[64] and unbounded sprintf_P/strcpy/strcat_P which can overflow if
mqttDeviceTopic grows; change publishMqtt (and the similar code around where
subuf is used again) to build the topic with bounded APIs or a length check:
compute required length (strlen(mqttDeviceTopic) + room for "/motion/%d" or
"/swipe" + null), and either allocate dynamically or use
snprintf/strncpy/strncat (or ensure the buffer constant is increased safely) so
you never write past subuf; update both occurrences that reference subuf and
mqttDeviceTopic to follow this bounded pattern.
- Around line 28-34: Replace the four preprocessor booleans (SWIPE_UP,
SWIPE_DOWN, LOWER, UPPER) with scoped compile-time constants to avoid global
leakage and preserve type safety: remove the `#define` lines and declare either
static constexpr bool SWIPE_UP/ SWIPE_DOWN and static constexpr bool LOWER/
UPPER in the relevant class/namespace or define small scoped enums (e.g., enum
class Swipe { Up, Down } and enum class SensorPos { Lower, Upper }) and update
the variables swipe and lastSensor to use the new scoped constants/types
(variables named swipe and lastSensor in AnimatedStaircase_VL53L0X.cpp). Ensure
all references are updated to the new names/types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1adbefd6-8bd4-49b5-b998-a46cec06297b

📥 Commits

Reviewing files that changed from the base of the PR and between 1825ce7 and a018eec.

📒 Files selected for processing (4)
  • platformio.ini
  • usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp
  • usermods/AnimatedStaircase_VL53L0X/README.md
  • usermods/AnimatedStaircase_VL53L0X/library.json

Comment thread platformio.ini
Comment on lines +13 to +35
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

PlatformIO default build matrix was narrowed; this must get explicit maintainer approval.

Changing [platformio].default_envs here directly changes CI build coverage (the workflow reads this list and builds exactly those envs), and platformio.ini edits are policy-gated. Please get explicit approval from a maintainer/WLED org member before merge, and confirm the reduced default matrix is intentional for CI/release behavior.

As per coding guidelines: “platformio.ini modifications MUST be approved explicitly by a maintainer or WLED organisation Member; always flag such modifications.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platformio.ini` around lines 13 - 35, The change narrows the PlatformIO
default build matrix by editing the default_envs list (e.g., removing many envs
and leaving esp32dev), which affects CI; restore or document the intended CI
behavior and obtain explicit maintainer approval before merging. Revert the
default_envs to the previous matrix or add a clear comment in platformio.ini
explaining the intentional change and its CI impact, then get an explicit
approval from a WLED maintainer/org member (record the approval in the PR
description) referencing the modified default_envs and the retained envs like
esp32dev so reviewers can verify the intended scope.

Comment on lines +178 to +193
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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unwind the partial sensor init before returning.

Both failure paths call resetSensorState() and return, but they leave the XSHUT pins allocated and, on the top-sensor failure path, can leave the bottom sensor powered and running at 0x30. The worker keeps retrying initialiseSensors(), so recovery now depends on allocateMultiplePins() tolerating already-owned pins. Please explicitly power down and deallocate the partial setup here.

Suggested unwind pattern
     if (!bottomVL53.init()) {
       DEBUG_PRINTLN(F("[StaircaseVL53] Bottom VL53L0X init failed"));
+      digitalWrite(xshutTopPin, LOW);
+      digitalWrite(xshutBottomPin, LOW);
+      PinManager::deallocatePin(xshutTopPin, PinOwner::UM_AnimatedStaircase);
+      PinManager::deallocatePin(xshutBottomPin, PinOwner::UM_AnimatedStaircase);
       resetSensorState();
       return false;
     }
@@
     if (!topVL53.init()) {
       DEBUG_PRINTLN(F("[StaircaseVL53] Top VL53L0X init failed"));
+      digitalWrite(xshutTopPin, LOW);
+      digitalWrite(xshutBottomPin, LOW);
+      PinManager::deallocatePin(xshutTopPin, PinOwner::UM_AnimatedStaircase);
+      PinManager::deallocatePin(xshutBottomPin, PinOwner::UM_AnimatedStaircase);
       resetSensorState();
       return false;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp` around
lines 178 - 193, The init failure paths in initialiseSensors() leave partial
hardware state (bottom/top sensors running, XSHUT pins held) before returning;
modify the two failure branches after bottomVL53.init() and topVL53.init() to
explicitly stop and reset any sensor started so far (e.g., call
bottomVL53.stopContinuous()/reset or power-cycle via
digitalWrite(xshutBottomPin, LOW) and digitalWrite(xshutTopPin, LOW) as
appropriate), clear any I2C address assignments (if applicable), and
release/deallocate pins (the same cleanup resetSensorState() does plus explicit
pin power-down) before returning false so subsequent retries and
allocateMultiplePins() see a clean state. Ensure references to bottomVL53,
topVL53, xshutTopPin, xshutBottomPin, setAddress, startContinuous, and
resetSensorState() are used to locate and implement the unwind logic.

Comment on lines +221 to +243
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();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp"

echo "== File existence =="
ls -l "$FILE"

echo "== Line range around 221-243 =="
nl -ba "$FILE" | sed -n '180,280p'

echo "== Find task creation / handle(s) =="
rg -n "xTaskCreate|xTaskCreatePinnedToCore|vTaskDelayUntil|xLastWakeTime|task|TaskHandle_t" "$FILE"

echo "== Inspect shared state fields & methods called in loop =="
rg -n "checkSensors\\(|initialiseSensors\\(|updateSwipe\\(|autoPowerOff\\(|toggleOnOff\\(|readFromConfig\\(|readFromJsonState\\(|enable\\(|cleanup\\(" "$FILE"

echo "== Inspect implementations of those methods (selected ranges) =="
for sym in initialiseSensors checkSensors updateSwipe autoPowerOff toggleOnOff readFromConfig readFromJsonState enable cleanup; do
  echo "--- $sym ---"
  # show the first ~120 lines around match
  nl -ba "$FILE" | rg -n "^\s*[0-9]+\s+.*$sym\\(" | head -n 5 || true
done

echo "== Show broader context for the ingress/mutation methods =="
for section in "readFromConfig" "readFromJsonState" "enable" "cleanup"; do
  echo "### $section context"
  line=$(nl -ba "$FILE" | rg -n "^\s*[0-9]+\s+.*$section\\(" | head -n1 | awk '{print $1}' || true)
  if [[ -n "${line:-}" ]]; then
    start=$((line-60))
    end=$((line+120))
    nl -ba "$FILE" | sed -n "${start},${end}p"
  fi
done

echo "== Search for any mutex/semaphore/queue usage =="
rg -n "SemaphoreHandle_t|xSemaphore|mutex|Mutex|xQueue|QueueHandle_t|xQueueCreate|xSemaphoreCreate|portENTER_CRITICAL|taskENTER_CRITICAL" "$FILE"

Repository: wled/WLED

Length of output: 283


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp"

echo "== Line range 180-280 (with numbers via python) =="
python3 - <<'PY'
import itertools
path = "usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp"
start, end = 180, 280
with open(path, "r", encoding="utf-8", errors="replace") as f:
    for i, line in enumerate(f, 1):
        if start <= i <= end:
            print(f"{i:5d} {line.rstrip()}")
        if i > end:
            break
PY

echo "== Find task creation / loop primitives in file =="
rg -n "xTaskCreate|xTaskCreatePinnedToCore|vTaskDelayUntil|xLastWakeTime|TaskHandle_t|while \\(true\\)" "$FILE" || true

echo "== Find loop methods/entrypoints that likely touch shared state =="
rg -n "checkSensors\\(|initialiseSensors\\(|updateSwipe\\(|autoPowerOff\\(|toggleOnOff\\(|readFromConfig\\(|readFromJsonState\\(|enable\\(|cleanup\\(" "$FILE" || true

echo "== Show mutex/semaphore/queue usage in file =="
rg -n "SemaphoreHandle_t|xSemaphore|mutex|Mutex|xQueue|QueueHandle_t|xQueueCreate|xSemaphoreCreate|portENTER_CRITICAL|taskENTER_CRITICAL" "$FILE" || true

Repository: wled/WLED

Length of output: 4247


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp"

python3 - <<'PY'
path = "usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp"
ranges = [(1,120),(120,190),(190,270),(270,420),(420,520),(520,620)]
with open(path,"r",encoding="utf-8",errors="replace") as f:
    lines = f.readlines()
for a,b in ranges:
    print(f"\n===== {a}-{b} =====")
    for i in range(a,b+1):
        if i-1 < 0 or i-1 >= len(lines): break
        print(f"{i:5d} {lines[i-1].rstrip()}")
PY

Repository: wled/WLED

Length of output: 18124


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp"

rg -n "SemaphoreHandle_t|xSemaphore|xSemaphoreCreate|Mutex|mutex|xQueue|QueueHandle_t|xQueueCreate|portENTER_CRITICAL|taskENTER_CRITICAL|vTaskSuspend|vTaskResume|uxTaskPriority|ulTaskNotify|xTaskNotify" "$FILE" || true

echo "== Check if cleanup/enable pause the task ==" 
rg -n "cleanup\\(|enable\\(|vTaskDelete|taskHandle|ensureTask\\(" "$FILE" || true

Repository: wled/WLED

Length of output: 510


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp"

echo "== FreeRTOS sync primitives used in file =="
rg -n "SemaphoreHandle_t|xSemaphore|xSemaphoreCreate|QueueHandle_t|xQueueCreate|portENTER_CRITICAL|taskENTER_CRITICAL|vTaskSuspend|vTaskResume|xTaskNotify|xTaskNotifyFromISR|xTaskCreate(.*)Notify" "$FILE" || true

echo "== initialiseSensors() call sites =="
rg -n "initialiseSensors\\(" "$FILE"

Repository: wled/WLED

Length of output: 272


The background task needs a synchronization boundary.

In usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp, the FreeRTOS task created in ensureTask() reads/writes usermod fields (enabled, configLoaded, sensor read/write/state, onIndex/offIndex, timers, on, swipe, etc.) and calls into WLED/strip/global logic (strip.get*/setTransition/trigger, segment setOption(), stateChanged/colorUpdated, and toggleOnOff()), all without any synchronization. At the same time, main-context entrypoints (readFromConfig(), readFromJsonState(), enable(), cleanup(), and MQTT handler via enable()) mutate the same shared fields and also call initialiseSensors() and strip.* directly, creating real race conditions (including concurrent strip/segment updates and pin/sensor re-init). Add a mutex/queue (or otherwise serialize/suspend the task during config/enable/cleanup changes) so shared state and all strip/WLED calls are not accessed concurrently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp` around
lines 221 - 243, The background task started by ensureTask() accesses and
mutates shared usermod state (fields like enabled, configLoaded,
sensorsInitialised, on/off indices, timers, on, swipe) and calls into
strip/segment APIs (strip.get*, strip.getSegment, setOption, setTransition,
trigger, toggleOnOff) without synchronization, causing races with main-context
methods (readFromConfig(), readFromJsonState(), enable(), cleanup(), MQTT
handlers). Fix by adding a single FreeRTOS mutex (e.g., usermodMutex) or a
task-level serialization mechanism: take the mutex before any reads/writes or
strip/segment calls in the task loop (the while(true) block that calls
initialiseSensors(), checkSensors(), autoPowerOff(), updateSwipe(), and when
computing minSegmentId/maxSegmentId), and also take the same mutex in
readFromConfig(), readFromJsonState(), enable(), cleanup(), and any MQTT
handlers or functions that call initialiseSensors() or strip.* so those
operations are serialized; alternatively suspend the task
(vTaskSuspend/vTaskResume using the task handle created in ensureTask()) around
large critical sections in enable()/cleanup() if fine-grained locking is
infeasible. Ensure the mutex is created once during initialization and always
released on every code path, and prefer using the mutex rather than critical
sections to avoid blocking other WLED internals.

Comment on lines +474 to +475
topThresholdMM = top[FPSTR(_topThreshold)] | topThresholdMM;
bottomThresholdMM = top[FPSTR(_bottomThreshold)] | bottomThresholdMM;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp the config thresholds before storing them.

These values come from client-supplied JSON and are consumed unguarded in checkSensors(). A negative or oversized value coerced into uint16_t can leave a sensor effectively stuck on or off. Please range-clamp them here instead of trusting the incoming config.

As per coding guidelines, "Values retrieved at these ingress points are considered trusted only after the usermod itself has validated and range-clamped them."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp` around
lines 474 - 475, The assignments to topThresholdMM and bottomThresholdMM are
taking client JSON values directly (via top[FPSTR(_topThreshold)] /
top[FPSTR(_bottomThreshold)]) and must be range-clamped before storing so
malformed/negative/oversized inputs can't break checkSensors(); extract the
numeric value, validate it is numeric, clamp it to the sensor's valid range
(e.g., 0..SENSOR_MAX_DISTANCE_MM or define a constant like
VL53L0X_MAX_DISTANCE_MM) and then assign the clamped value to topThresholdMM and
bottomThresholdMM (replace the current bitwise-or pattern), ensuring the new
logic is used wherever these thresholds are read (e.g., checkSensors()).

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

XSHUT pins are documented as optional, but code requires them.

The README says XSHUT is optional, but initialiseSensors() fails when either XSHUT pin is unset and disables the usermod (usermods/AnimatedStaircase_VL53L0X/AnimatedStaircase_VL53L0X.cpp, Lines 127-209). Please update wording to make both pins mandatory and explicitly state they must be different.

Also applies to: 40-40, 55-56

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/AnimatedStaircase_VL53L0X/README.md` at line 17, Update the README
to stop calling XSHUT pins optional: explicitly state that both XSHUT pins are
mandatory and must be assigned to different GPIOs (they cannot be the same),
because initialiseSensors() in AnimatedStaircase_VL53L0X.cpp requires both pins
to be set for I²C address assignment and will disable the usermod if either is
unset; revise the relevant lines (the earlier pin description and the
examples/usage sections) to reflect “required” and “must be different” wording
so users configure two distinct XSHUT pins before running initialiseSensors().

Comment on lines +33 to +36
Before compiling, you have to make the following modifications:

Copy platformio_override.ini to the same folder as platformio.ini

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Setup steps miss custom_usermods activation, so install instructions are incomplete.

The install section should explicitly include adding AnimatedStaircase_VL53L0X to custom_usermods in the build config; otherwise users can compile without actually enabling the usermod.

As per coding guidelines: "platformio*.ini: To activate a usermod, add the usermod name to custom_usermods in the build configuration".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/AnimatedStaircase_VL53L0X/README.md` around lines 33 - 36, The
README's install steps omit activating the usermod—update the "Before compiling"
section to instruct users to add AnimatedStaircase_VL53L0X to the
custom_usermods list in their PlatformIO build configuration (e.g., in
platformio_override.ini or platformio.ini) so the usermod is actually enabled;
reference the exact name "AnimatedStaircase_VL53L0X" and the key
"custom_usermods" and show the placement (build environment section) where it
should be added.

@coderabbitai coderabbitai Bot added the documentation documentation should be updated to explain behaviour label May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation documentation should be updated to explain behaviour enhancement usermod usermod related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant