Skip to content

Usermod Animated Staircase: added sentinel option and fixed off swipe direction#5076

Draft
SentiQ wants to merge 9 commits into
wled:mainfrom
SentiQ:added_sentinel_option
Draft

Usermod Animated Staircase: added sentinel option and fixed off swipe direction#5076
SentiQ wants to merge 9 commits into
wled:mainfrom
SentiQ:added_sentinel_option

Conversation

@SentiQ

@SentiQ SentiQ commented Nov 11, 2025

Copy link
Copy Markdown
  1. Added a new option to the UI to activate the first and last segment as a sentinel segment - if activated both segments will stay active with the configured opacity

  2. Fixed the swipe off direction so that the swipe always goes off to the direction of the last triggered sensor.

Summary by CodeRabbit

  • New Features

    • Per-stair sentinel support: enable/disable sentinel segments and set dim opacity for the first/last steps.
  • Improvements

    • Sentinel segments remain fully visible during swipe, and when sentinel mode is enabled they appear dim outside the swipe while other non-swipe segments stay off.
    • Enhanced swipe direction/sensor handling for more reliable on/off behavior.
    • Sentinel settings are saved to configuration, loaded with clamping, and applied immediately.
  • Bug Fixes

    • Disabling the usermod no longer restores all active segments—only the first/last segments are forced before refreshing.

@coderabbitai

coderabbitai Bot commented Nov 11, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Ignore keyword(s) in the title.

⛔ Ignored keywords (2)
  • WIP
  • DO NOT MERGE

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ab49e1c1-b02e-41cd-bef9-84522ff301a3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Adds per-stair sentinel support to Animated_Staircase: two new config fields (enabledSentinel, sentinelDimOpacity), sentinel-aware opacity rules and swipe handling, adjusted sensor logic, enable/disable behavior changes, and persistence/readback for the new settings.

Changes

Cohort / File(s) Change Summary
Animated Staircase core
usermods/Animated_Staircase/Animated_Staircase.cpp
Introduces enabledSentinel (bool) and sentinelDimOpacity (unsigned int) plus PROGMEM config key strings (_enabledSentinel, _sentinelDimOpacity); computes firstSegId/lastSegId; enforces full opacity on sentinel segments during swipes and optionally dims them to sentinelDimOpacity when idle; adjusts swipe logic (lastSensor derived from bottomSensorRead, inverted for OFF swipes); on disable forces first/last segments to 255 then triggers full refresh/state propagation; persists and reads new settings via addToConfig/readFromConfig/readFromJsonState; minor guards for inactive first/last segments and hygiene tweaks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review updateSegments sentinel detection and in-swipe vs idle opacity branching.
  • Verify lastSensor derivation from bottomSensorRead and inversion for OFF swipes.
  • Check enable/disable flow that forces sentinel opacity and triggers refresh/state propagation.
  • Validate config/JSON keys (_enabledSentinel, _sentinelDimOpacity) handling and that updateSegments is invoked on config changes.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 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 two main changes: adding a sentinel option and fixing the off swipe direction.

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd933ff and a56985a.

📒 Files selected for processing (1)
  • usermods/Animated_Staircase/Animated_Staircase.cpp (11 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.
📚 Learning: 2025-06-07T15:58:42.579Z
Learnt from: willmmiles
Repo: wled/WLED PR: 4712
File: wled00/FX_fcn.cpp:992-993
Timestamp: 2025-06-07T15:58:42.579Z
Learning: In WLED segments, the backing pixel buffer is always allocated to the full physical segment size, but effect rendering only uses a subset when mirror or grouping are enabled. For 2D segments, this subset is vWidth() * vHeight(), and for 1D segments it's vLength(). Color processing operations like fade_out should iterate over this rendering subset, not the full allocated buffer.

Applied to files:

  • usermods/Animated_Staircase/Animated_Staircase.cpp
📚 Learning: 2025-04-24T09:31:06.879Z
Learnt from: blazoncek
Repo: wled/WLED PR: 4658
File: wled00/led.cpp:90-91
Timestamp: 2025-04-24T09:31:06.879Z
Learning: In the WLED codebase, the `getSegment()` function guards against out-of-bounds segment IDs, and `getFirstSelectedSegId()` falls back to `getMainSegmentId()` if no segments are selected, ensuring no crashes when used through the `setValuesFromFirstSelectedSeg()` macro.

Applied to files:

  • usermods/Animated_Staircase/Animated_Staircase.cpp
📚 Learning: 2025-04-24T09:31:06.879Z
Learnt from: blazoncek
Repo: wled/WLED PR: 4658
File: wled00/led.cpp:90-91
Timestamp: 2025-04-24T09:31:06.879Z
Learning: In the WLED codebase, the `getSegment()` function guards against out-of-bounds segment IDs by falling back to the main segment ID, and `getFirstSelectedSegId()` falls back to `getMainSegmentId()` if no segments are selected, ensuring no crashes when used through the `setValuesFromFirstSelectedSeg()` macro.

Applied to files:

  • usermods/Animated_Staircase/Animated_Staircase.cpp
📚 Learning: 2025-10-20T09:41:50.374Z
Learnt from: blazoncek
Repo: wled/WLED PR: 4995
File: wled00/FX.cpp:5209-5212
Timestamp: 2025-10-20T09:41:50.374Z
Learning: In WLED 2D effects, SEGMENT.length() returns the physical pixel count (upper bound), while SEG_W * SEG_H can be smaller depending on mapping (mirroring/grouping). For per-cell state buffers (e.g., mode_2Dgameoflife in wled00/FX.cpp), allocate using SEGMENT.length() to ensure sufficient capacity and avoid reallocations, while bounding indexing by cols*rows.

Applied to files:

  • usermods/Animated_Staircase/Animated_Staircase.cpp
📚 Learning: 2025-09-12T17:29:43.826Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.

Applied to files:

  • usermods/Animated_Staircase/Animated_Staircase.cpp
📚 Learning: 2025-09-02T19:42:57.475Z
Learnt from: freakintoddles2
Repo: wled/WLED PR: 4904
File: wled00/FX.cpp:3915-3930
Timestamp: 2025-09-02T19:42:57.475Z
Learning: WLED wled00/FX.cpp::mode_percent — For time-based options (% of Minute/Hour/Day), behavior is clock-like: at rollover (elapsed==0) intentionally show SEGLEN lit for one tick to represent completion, then move to 1 LED at the next second. This is by design (not a progress bar) and should not be “fixed”.

Applied to files:

  • usermods/Animated_Staircase/Animated_Staircase.cpp

Comment thread usermods/Animated_Staircase/Animated_Staircase.cpp
@SentiQ SentiQ closed this Nov 11, 2025
@SentiQ SentiQ reopened this Nov 11, 2025
@SentiQ SentiQ closed this Nov 11, 2025
@SentiQ SentiQ reopened this Nov 12, 2025
coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

@DedeHai DedeHai requested a review from blazoncek November 16, 2025 19:21
@DedeHai

DedeHai commented Nov 16, 2025

Copy link
Copy Markdown
Collaborator

@realrolfje any comments on this?

@blazoncek blazoncek left a comment

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.

I do not use Animated Staircase usermod so cannot comment on usability enhancements.

@DedeHai

DedeHai commented Mar 17, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai are the changes fully backwards compatible or are there any breaking changes for existing setups?

@coderabbitai

coderabbitai Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the Animated_Staircase.cpp for default values, config keys, and logic changes
fd "Animated_Staircase.cpp" --exec cat {}

# Also check the header file if present
fd "Animated_Staircase.h" --exec cat {}

Length of output: 24082


@DedeHai: Here's a detailed backwards compatibility analysis based on the actual code:


✅ Fully backwards compatible

Area Details
New sentinel feature enabledSentinel defaults to false — zero impact on existing setups that don't configure it
Config persistence All new keys use the `
Existing config keys None of the original config keys (segment-delay-ms, on-time-s, pin assignments, etc.) are renamed or removed

⚠️ Behavioral change (existing setups affected)

Swipe-off direction — This is the most notable change. In checkSensors():

lastSensor = bottomSensorRead;

And in autoPowerOff():

swipe = !lastSensor;

Previously, the logic for determining which direction the lights swipe off was reportedly buggy. The PR fixes this so the off-swipe goes in the direction of the last triggered sensor. Existing users will observe a different swipe-off direction after upgrading. While it's framed as a bug fix, it's a runtime behavioral change.


🔶 Minor behavioral difference on disable

The enable(false) path now explicitly resets the opacity of the first and last segments to 255:

if (firstSeg.isActive()) firstSeg.setOpacity(255);
if (lastSeg.isActive()) lastSeg.setOpacity(255);

Users who had non-default opacity values on those two segments may see them reset to full when the usermod is disabled.


🔶 readFromConfig return value change

The return statement changed to:

return !top[FPSTR(_sentinelDimOpacity)].isNull();

For existing configs that don't yet have sentinel-dim-opacity, this returns false on the first boot after upgrade, triggering a re-run of setup(). This is the standard WLED pattern for adding new config parameters and stabilizes after the first save — not truly breaking, but worth noting.


Summary: The sentinel feature itself is fully additive and safe. The main concern for existing users is the swipe-off direction change, which is behaviorally breaking even if it corrects a bug.


🧠 Learnings used
Learnt from: KrX3D
Repo: wled/WLED PR: 4585
File: usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.cpp:3-5
Timestamp: 2026-02-18T00:11:02.403Z
Learning: In PR `#4585`, the seven_segment_display_reloaded_v2 usermod intentionally uses `#error` to require MQTT (`#ifdef WLED_DISABLE_MQTT `#error` "This user mod requires MQTT to be enabled." `#endif``) because the integration flow has not been tested or validated without MQTT, and failing fast at compile time is preferred over allowing potentially broken builds until a full MQTT-optional refactor is completed.

@SentiQ

SentiQ commented May 4, 2026

Copy link
Copy Markdown
Author

FYI: Animated Staircase usermod actually not working anymore with current main branch.

@SentiQ SentiQ marked this pull request as draft May 21, 2026 05:53

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
usermods/Animated_Staircase/Animated_Staircase.cpp (2)

334-342: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don’t reset opacity when sentinel mode is disabled.

enabledSentinel defaults to false, but disabling the usermod now still forces first/last segment opacity to 255, overriding custom opacity for existing setups. Gate this restore behind sentinel mode at minimum.

Proposed fix
-        if (strip.getSegmentsNum() > 0) {
+        if (enabledSentinel && strip.getSegmentsNum() > 0) {
           byte firstSegId = strip.getMainSegmentId();
🤖 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/Animated_Staircase/Animated_Staircase.cpp` around lines 334 - 342,
The opacity reset in Animated_Staircase::loop (the block using getMainSegmentId,
getLastActiveSegmentId, and setOpacity(255)) is now running even when sentinel
mode is disabled. Gate this first/last segment restore behind enabledSentinel so
the existing custom opacity is preserved unless sentinel mode is actually
active.

125-130: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor sentinelDimOpacity = 0 as a valid configured opacity.

Line 128 turns sentinels off when the configured opacity is 0, even though the value is clamped and persisted as part of the valid [0,255] range. Keep the segment active and apply opacity 0.

Proposed fix
-        } else if (enabledSentinel && isSentinel && sentinelDimOpacity > 0) {
+        } else if (enabledSentinel && isSentinel) {
           seg.setOption(SEG_OPTION_ON, true);
           seg.setOpacity(sentinelDimOpacity);
🤖 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/Animated_Staircase/Animated_Staircase.cpp` around lines 125 - 130,
The sentinel rendering branch in Animated_Staircase.cpp is treating
sentinelDimOpacity as “disabled” when it equals 0, but 0 is a valid persisted
opacity. Update the conditional around the sentinel path in the segment update
logic so enabledSentinel and isSentinel still keep the segment on even when
sentinelDimOpacity is 0, and always apply seg.setOpacity(sentinelDimOpacity) for
that branch.
🧹 Nitpick comments (3)
usermods/Animated_Staircase/Animated_Staircase.cpp (3)

218-221: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the lastSensor comment with the actual stored value.

The new comment says true means bottom and false means top, which conflicts with the nearby LOWER/UPPER constants. Reword this as “mirrors bottomSensorRead” to avoid future swipe-direction mistakes. As per path instructions, verify comments match code behavior.

🤖 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/Animated_Staircase/Animated_Staircase.cpp` around lines 218 - 221,
The comment around lastSensor assignment conflicts with the LOWER/UPPER sensor
naming and can mislead future changes. Update the comment near the lastSensor =
bottomSensorRead assignment in Animated_Staircase so it simply says lastSensor
mirrors bottomSensorRead, matching the actual stored value and avoiding any
implied swipe-direction semantics.

Source: Path instructions


505-506: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unused invert snapshots.

oldTopAPinInvert and oldBottomAPinInvert are assigned but never read in readFromConfig(). Delete them unless they are needed for a change check. As per coding guidelines, remove dead/unused code or justify it with comments.

🤖 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/Animated_Staircase/Animated_Staircase.cpp` around lines 505 - 506,
Remove the unused invert snapshot variables in readFromConfig(), since
oldTopAPinInvert and oldBottomAPinInvert are assigned but never read. Update the
Animated_Staircase::readFromConfig() logic to delete these dead declarations
unless you actually need them for a change-detection comparison, and keep the
function focused on the values it uses.

Source: Coding guidelines


174-177: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fix the extra indentation in readPIRPin().

Line 176 is indented one level too deep. As per coding guidelines, C++ files should use 2-space indentation with no tabs.

Proposed fix
-        bool v = digitalRead(pin);
+      bool v = digitalRead(pin);
🤖 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/Animated_Staircase/Animated_Staircase.cpp` around lines 174 - 177,
Fix the indentation in readPIRPin() so it matches the project’s 2-space C++
style: the digitalRead(pin) assignment is indented one level too deep relative
to the surrounding if and return statements. Update the readPIRPin function in
Animated_Staircase.cpp to use consistent two-space indentation with no tabs,
keeping the logic unchanged.

Sources: Coding guidelines, Path instructions

🤖 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.

Outside diff comments:
In `@usermods/Animated_Staircase/Animated_Staircase.cpp`:
- Around line 334-342: The opacity reset in Animated_Staircase::loop (the block
using getMainSegmentId, getLastActiveSegmentId, and setOpacity(255)) is now
running even when sentinel mode is disabled. Gate this first/last segment
restore behind enabledSentinel so the existing custom opacity is preserved
unless sentinel mode is actually active.
- Around line 125-130: The sentinel rendering branch in Animated_Staircase.cpp
is treating sentinelDimOpacity as “disabled” when it equals 0, but 0 is a valid
persisted opacity. Update the conditional around the sentinel path in the
segment update logic so enabledSentinel and isSentinel still keep the segment on
even when sentinelDimOpacity is 0, and always apply
seg.setOpacity(sentinelDimOpacity) for that branch.

---

Nitpick comments:
In `@usermods/Animated_Staircase/Animated_Staircase.cpp`:
- Around line 218-221: The comment around lastSensor assignment conflicts with
the LOWER/UPPER sensor naming and can mislead future changes. Update the comment
near the lastSensor = bottomSensorRead assignment in Animated_Staircase so it
simply says lastSensor mirrors bottomSensorRead, matching the actual stored
value and avoiding any implied swipe-direction semantics.
- Around line 505-506: Remove the unused invert snapshot variables in
readFromConfig(), since oldTopAPinInvert and oldBottomAPinInvert are assigned
but never read. Update the Animated_Staircase::readFromConfig() logic to delete
these dead declarations unless you actually need them for a change-detection
comparison, and keep the function focused on the values it uses.
- Around line 174-177: Fix the indentation in readPIRPin() so it matches the
project’s 2-space C++ style: the digitalRead(pin) assignment is indented one
level too deep relative to the surrounding if and return statements. Update the
readPIRPin function in Animated_Staircase.cpp to use consistent two-space
indentation with no tabs, keeping the logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 81f30a77-61af-4362-88d8-63b077a30608

📥 Commits

Reviewing files that changed from the base of the PR and between 23e53ad and c71858a.

📒 Files selected for processing (1)
  • usermods/Animated_Staircase/Animated_Staircase.cpp

// 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
// seg.setMode(mainsegment.mode);
// seg.setColor(0, mainsegment.colors[0]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what's the reason for deleting this comment? Is it wrong or maybe outdated?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It's outdated. Obviously used in an old version of the usermod.

@softhack007 softhack007 added this to the 17.0 milestone Jul 3, 2026
@softhack007 softhack007 added the usermod usermod related label Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

usermod usermod related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants