Skip to content

Conversation

@rawframe
Copy link
Contributor

@rawframe rawframe commented Dec 10, 2025

What It Does:

Multiple PIR sensors triggering WLED presets with individual on/off delays.

  • Multiple PIR sensors - Connect as many motion sensors as you need
  • Flexible linking - Each PIR can trigger one or more actions
  • On/Off presets - Each action runs an "on" preset when motion is detected
  • Automatic off-delay - After motion stops, actions wait for your configured delay then run an "off" preset
  • Smart coordination - When multiple PIRs link to the same action, it only turns off when ALL PIRs go idle
  • Web interface - Configure and monitor everything through the WLED UI

Screenshots (default config of 3x PIRs & 3x Actions) :

Usermod Info Page (3x PIRs, 3x Actions example)
Usermod Settings Page (3x PIRs, 3x Actions example)
ugh the WLED UI

Summary by CodeRabbit

  • New Features

    • Multi-PIR motion detection with configurable action profiles and many-to-many sensor→action linking
    • Configurable on/off presets, per-action off-delay, runtime toggling and flood-protected preset queuing
    • Standby vs. highlight brightness modes with preservation across transitions and automatic off-timer
    • Real-time web UI controls, status indicators, control buttons and per-action countdowns
  • Documentation

    • Added detailed user guide and legacy notes explaining setup, configuration, UI and API usage
  • Chores

    • Added library manifest for the new usermod

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Walkthrough

Adds a new PIR sensor switch package: a simple PIRsensorSwitch (standby/highlight, interrupt/debounce, brightness preservation, off‑timer) and a full MotionDetectionUsermod (multi‑PIR, per‑action presets/delays, contributor linking, FIFO). Also adds a library manifest and two documentation files.

Changes

Cohort / File(s) Change Summary
Standby/Highlight usermod
usermods/PIR_sensor_switch_v2/PIR_Highlight_Standby
New PIRsensorSwitch class implementing PIR interrupt handling, debounce/notify, standby vs highlight brightness tracking, off‑timer, lifecycle hooks (setup, loop, connected), and JSON endpoints (addToJsonInfo, addToJsonState, readFromJsonState) with runtime enable/disable and UI control.
Multi‑PIR motion usermod
usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp
New MotionDetectionUsermod class implementing multi‑PIR support, per‑PIR pin/config, action profiles (on/off presets, off‑delay), per‑PIR contributor bitmasks, FIFO preset queue with spacing/flood protection, periodic PIR polling and edge detection, per‑action off‑timers, config read/write (readFromConfig, addToConfig, appendConfigData), JSON UI/state endpoints, and registered usermod instance.
Library manifest
usermods/PIR_sensor_switch_v2/library.json
Added library manifest declaring PIR_sensor_switch_v2 with build configuration (libArchive: false).
Documentation
usermods/PIR_sensor_switch_v2/readme.md, usermods/PIR_sensor_switch_v2/readme_old.md
Added/updated documentation describing features, configuration, wiring, API controls, usage scenarios, and implementation notes for both usermods.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas requiring extra attention:
    • Multi‑PIR polling, edge detection, contributor counting and action linking in usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp.
    • FIFO preset queue behavior (ordering, spacing, flood protection) and processPresetFifo correctness.
    • Per‑action off‑delay timer logic and interactions with motion events and enqueue/dequeue.
    • Config read/write and JSON UI/state consistency (enable/disable flows, persistence) across both usermods.
    • Interrupt attach/detach, debounce handling, and brightness preservation/transition logic in usermods/PIR_sensor_switch_v2/PIR_Highlight_Standby.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.33% 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 clearly and specifically summarizes the main feature added: multiple PIR sensors triggering WLED presets with individual on/off delays, which matches the core functionality introduced in this PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
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: 7

🧹 Nitpick comments (4)
usermods/PIR_sensor_switch_v2/PIR_Highlight_Standby (2)

190-199: Inconsistent brace alignment reduces readability.

The opening brace on line 192 is not aligned with its closing counterpart, making the control flow harder to follow.

     bool handleOffTimer() {
       if (m_offTimerStart > 0) {
-        if ((millis() - m_offTimerStart > m_switchOffDelay) || bri == 0 ) { // now also checking for manual power off during highlight mode
-        switchStrip(false);
-        m_offTimerStart = 0;        
-        return true;
+        if ((millis() - m_offTimerStart > m_switchOffDelay) || bri == 0) {
+          switchStrip(false);
+          m_offTimerStart = 0;
+          return true;
         }
       }
       return false;
     }

60-82: Consider making the PIR pin configurable.

The PIRsensorPin is hardcoded as const uint8_t PIRsensorPin = 13. Unlike the newer MotionDetectionUsermod which supports runtime pin configuration, this class requires recompilation to change the pin. Consider aligning with the approach in the main implementation if this class is intended to be used alongside it.

usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp (2)

239-245: Remove redundant comments about removed code.

These comments explain code that was removed, which adds noise without value.

           // if action just became active, enqueue onPreset; always clear offStartMs
           if (actions[a].activeCount == 1) {
-            // OPTIMIZATION: Removed redundant offStartMs = 0
             if (actions[a].onPreset) enqueuePreset(actions[a].onPreset);
-          } else {
-            // OPTIMIZATION: Removed redundant offStartMs = 0
           }

515-613: Large inline JavaScript block for UI customization.

The appendConfigData() method contains substantial embedded JavaScript (~100 lines). While functional, this approach makes the code harder to maintain and debug. Consider documenting the expected DOM structure or extracting common patterns if this grows further.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc5b504 and 09da13a.

⛔ Files ignored due to path filters (2)
  • usermods/PIR_sensor_switch_v2/Usermod Info Page (3x PIRs, 3x Actions example).jpg is excluded by !**/*.jpg
  • usermods/PIR_sensor_switch_v2/Usermod Settings Page (3x PIRs, 3x Actions example).jpg is excluded by !**/*.jpg
📒 Files selected for processing (5)
  • usermods/PIR_sensor_switch_v2/PIR_Highlight_Standby (1 hunks)
  • usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp (1 hunks)
  • usermods/PIR_sensor_switch_v2/library.json (1 hunks)
  • usermods/PIR_sensor_switch_v2/readme.md (1 hunks)
  • usermods/PIR_sensor_switch_v2/readme_old.md (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: willmmiles
Repo: wled/WLED PR: 4623
File: usermods/word-clock-matrix/word-clock-matrix.cpp:332-332
Timestamp: 2025-03-29T01:22:54.617Z
Learning: In the WLED project, hardcoded usermod IDs (like 500 in the WordClockMatrix's getId() method) are intentionally used as part of a strategy to avoid modifying core code. There are plans to remove numeric usermod IDs entirely in the future.
📚 Learning: 2025-03-29T01:22:54.617Z
Learnt from: willmmiles
Repo: wled/WLED PR: 4623
File: usermods/word-clock-matrix/word-clock-matrix.cpp:332-332
Timestamp: 2025-03-29T01:22:54.617Z
Learning: In the WLED project, hardcoded usermod IDs (like 500 in the WordClockMatrix's getId() method) are intentionally used as part of a strategy to avoid modifying core code. There are plans to remove numeric usermod IDs entirely in the future.

Applied to files:

  • usermods/PIR_sensor_switch_v2/PIR_Highlight_Standby
  • usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp
📚 Learning: 2025-11-30T15:29:00.726Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4456
File: usermods/deep_sleep/deep_sleep.cpp:224-230
Timestamp: 2025-11-30T15:29:00.726Z
Learning: In WLED, `touchThreshold` is a global variable defined in wled00/wled.h and is accessible to all usermods. It stores the touch sensitivity threshold for touch-capable GPIO pins on ESP32 variants and should not be flagged as undefined when used in usermods.

Applied to files:

  • usermods/PIR_sensor_switch_v2/PIR_Highlight_Standby
🪛 Cppcheck (2.18.0)
usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp

[error] 42-42: There is an unknown macro here somewhere. Configuration is required. If PROGMEM is a macro then please configure it.

(unknownMacro)

🪛 LanguageTool
usermods/PIR_sensor_switch_v2/readme_old.md

[grammar] ~12-~12: Ensure spelling is correct
Context: ... timer. Usermod can also be temporarily disbled/enabled from the info page by clicking ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~24-~24: Ensure spelling is correct
Context: ... which lacks such potentiometer).

Usermod installation

NOTE: Usermod has b...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~28-~28: Ensure spelling is correct
Context: ... from outside. For example from another usermod

To query or change the PIR sensor state ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~98-~98: Use a hyphen to join words.
Context: ...fo dialog)

2022-11

  • Added compile time option for off timer.
  • Added Home...

(QB_NEW_EN_HYPHEN)


[grammar] ~109-~109: Use a hyphen to join words.
Context: ...oticz/in`)

2024-02

  • Added compile time option to expand number of PIR sens...

(QB_NEW_EN_HYPHEN)

usermods/PIR_sensor_switch_v2/readme.md

[style] ~9-~9: Using “real” as an adverb is considered informal. Consider using “really” or “very”.
Context: ...ors and actions, macro chains get messy real quick, and moreover we still cannot eas...

(REAL_REALLY)


[grammar] ~62-~62: Ensure spelling is correct
Context: ... more than 8 PIRs or Actions.

  • PIR to Acion contributor tracking ensures Actions be...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~70-~70: Ensure spelling is correct
Context: ...do so bit remember to adjust the custom webapge formatting also.

Hope its useful :)
...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~70-~70: The adverb ‘also’ is commonly used to connect clauses and isn’t usually used at the end of a phrase or before a conjunction. Consider replacing it with a more formal alternative.
Context: ...to adjust the custom webapge formatting also.

Hope its useful :)

(ALSO_AS_WELL)

🔇 Additional comments (6)
usermods/PIR_sensor_switch_v2/library.json (1)

1-6: LGTM!

Standard PlatformIO library manifest for the usermod.

usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp (5)

50-52: Array initialization only sets the first element explicitly.

Using = { true } or = { PIR_SENSOR_PIN } only initializes the first element; remaining elements are zero-initialized. This appears intentional (default: first PIR enabled at default pin, others disabled at pin -1/0), but worth confirming this matches the expected behavior.


87-98: LGTM: FIFO implementation is correct.

The circular buffer implementation with duplicate-detection and overflow handling (drop oldest) is well-designed for preventing preset flooding.


175-207: LGTM: Setup handles pin allocation and failure gracefully.

The setup correctly allocates pins via PinManager, handles platform differences (ESP8266 vs ESP32), and disables PIRs when allocation fails.


420-430: Index mapping logic accepts both 0-based and 1-based keys inconsistently.

The logic attempts to handle both action0 (0-based) and action1 (1-based) key styles, but the conditions overlap. For example, if ACTION_MAX=2, then action1 matches both the 0-based check (idx >= 0 && idx < 2) mapping to index 1, and the 1-based check (idx >= 1 && idx <= 2) mapping to index 0. The first matching condition wins, so action1 maps to index 1 (0-based interpretation), which may not match user expectations if they intended 1-based indexing.

Consider documenting the expected key format or simplifying to support only one style consistently.


618-623: LGTM: Usermod registration follows WLED conventions.

The getId() returns the standard USERMOD_ID_PIRSWITCH and uses REGISTER_USERMOD macro correctly. Based on learnings, hardcoded usermod IDs are intentional in WLED.

Comment on lines +273 to +291
if (m_offTimerStart > 0) {
uiDomString = "";
unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
if (offSeconds >= 3600) {
uiDomString += (offSeconds / 3600);
uiDomString += " hours ";
offSeconds %= 3600;
}
if (offSeconds >= 60) {
uiDomString += (offSeconds / 60);
offSeconds %= 60;
} else if (uiDomString.length() > 0){
uiDomString += 0;
}
if (uiDomString.length() > 0){
uiDomString += " min ";
}
uiDomString += (offSeconds);
infoArr.add(uiDomString + " sec");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential unsigned underflow in timer calculation.

If the timer has already elapsed (elapsed time > m_switchOffDelay), the subtraction m_switchOffDelay - (millis() - m_offTimerStart) will underflow, producing a large value instead of 0.

       if (m_offTimerStart > 0) {
         uiDomString = "";
-        unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
+        unsigned long elapsed = millis() - m_offTimerStart;
+        unsigned int offSeconds = (elapsed >= m_switchOffDelay) ? 0 : (m_switchOffDelay - elapsed) / 1000;
         if (offSeconds >= 3600) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (m_offTimerStart > 0) {
uiDomString = "";
unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
if (offSeconds >= 3600) {
uiDomString += (offSeconds / 3600);
uiDomString += " hours ";
offSeconds %= 3600;
}
if (offSeconds >= 60) {
uiDomString += (offSeconds / 60);
offSeconds %= 60;
} else if (uiDomString.length() > 0){
uiDomString += 0;
}
if (uiDomString.length() > 0){
uiDomString += " min ";
}
uiDomString += (offSeconds);
infoArr.add(uiDomString + " sec");
if (m_offTimerStart > 0) {
uiDomString = "";
unsigned long elapsed = millis() - m_offTimerStart;
unsigned int offSeconds = (elapsed >= m_switchOffDelay) ? 0 : (m_switchOffDelay - elapsed) / 1000;
if (offSeconds >= 3600) {
uiDomString += (offSeconds / 3600);
uiDomString += " hours ";
offSeconds %= 3600;
}
if (offSeconds >= 60) {
uiDomString += (offSeconds / 60);
offSeconds %= 60;
} else if (uiDomString.length() > 0){
uiDomString += 0;
}
if (uiDomString.length() > 0){
uiDomString += " min ";
}
uiDomString += (offSeconds);
infoArr.add(uiDomString + " sec");
🤖 Prompt for AI Agents
In usermods/PIR_sensor_switch_v2/PIR_Highlight_Standby around lines 273 to 291,
the subtraction m_switchOffDelay - (millis() - m_offTimerStart) can underflow if
the elapsed time exceeds the delay; compute elapsed first as an unsigned long,
check if elapsed >= m_switchOffDelay and set remainingSeconds to 0 in that case,
otherwise compute remaining = m_switchOffDelay - elapsed and convert to seconds
for the UI; replace the current direct subtraction with this safe elapsed-check
pattern to prevent underflow and ensure correct remaining time is displayed.

Comment on lines +323 to +332
if (root["PIRenabled"] != nullptr) {
if (root["PIRenabled"] && !m_PIRenabled) {
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
newPIRsensorState(true, true);
}
else if(m_PIRenabled) {
detachInterrupt(PIRsensorPin);
}
m_PIRenabled = root["PIRenabled"];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Logic error: Interrupt detached incorrectly when PIR is already enabled.

The else if(m_PIRenabled) branch executes whenever m_PIRenabled is currently true, regardless of the new JSON value. This means if the user sends {"PIRenabled": true} while the sensor is already enabled, the interrupt gets incorrectly detached.

     if (root["PIRenabled"] != nullptr) {
-      if (root["PIRenabled"] && !m_PIRenabled) {
+      bool newState = root["PIRenabled"].as<bool>();
+      if (newState && !m_PIRenabled) {
         attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
         newPIRsensorState(true, true);
       } 
-      else if(m_PIRenabled) {
+      else if(!newState && m_PIRenabled) {
         detachInterrupt(PIRsensorPin);
       }
-      m_PIRenabled = root["PIRenabled"];          
+      m_PIRenabled = newState;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (root["PIRenabled"] != nullptr) {
if (root["PIRenabled"] && !m_PIRenabled) {
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
newPIRsensorState(true, true);
}
else if(m_PIRenabled) {
detachInterrupt(PIRsensorPin);
}
m_PIRenabled = root["PIRenabled"];
}
if (root["PIRenabled"] != nullptr) {
bool newState = root["PIRenabled"].as<bool>();
if (newState && !m_PIRenabled) {
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
newPIRsensorState(true, true);
}
else if(!newState && m_PIRenabled) {
detachInterrupt(PIRsensorPin);
}
m_PIRenabled = newState;
}
🤖 Prompt for AI Agents
In usermods/PIR_sensor_switch_v2/PIR_Highlight_Standby around lines 323-332, the
branch detaches the interrupt whenever m_PIRenabled is true regardless of the
new JSON value; change the logic to compare the incoming root["PIRenabled"]
value to the current m_PIRenabled and only attach the ISR when new==true &&
current==false and only detach when new==false && current==true, then update
m_PIRenabled to the new value; also use digitalPinToInterrupt(PIRsensorPin)
consistently when attaching and detaching the interrupt.


## Web interface

The info page in the web interface shows the remaining time of the off timer. Usermod can also be temporarily disbled/enabled from the info page by clicking PIR button.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo: "disbled" → "disabled"

-The info page in the web interface shows the remaining time of the off timer. Usermod can also be temporarily disbled/enabled from the info page by clicking PIR button.
+The info page in the web interface shows the remaining time of the off timer. Usermod can also be temporarily disabled/enabled from the info page by clicking PIR button.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The info page in the web interface shows the remaining time of the off timer. Usermod can also be temporarily disbled/enabled from the info page by clicking PIR button.
The info page in the web interface shows the remaining time of the off timer. Usermod can also be temporarily disabled/enabled from the info page by clicking PIR button.
🧰 Tools
🪛 LanguageTool

[grammar] ~12-~12: Ensure spelling is correct
Context: ... timer. Usermod can also be temporarily disbled/enabled from the info page by clicking ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
In usermods/PIR_sensor_switch_v2/readme_old.md around line 12, there's a typo:
"disbled" should be "disabled"; update the sentence to read "...Usermod can also
be temporarily disabled/enabled from the info page by clicking PIR button." and
save the file.


void togglePIRSensor() {
#ifdef USERMOD_PIR_SENSOR_SWITCH
PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) UsermodManager::lookup(USERMOD_ID_PIRSWITCH);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix invalid C++ cast syntax in example code.

The ::* syntax is for pointer-to-member declarations, not for casting. This should be a regular pointer cast.

-    PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) UsermodManager::lookup(USERMOD_ID_PIRSWITCH);
+    PIRsensorSwitch *PIRsensor = (PIRsensorSwitch*) UsermodManager::lookup(USERMOD_ID_PIRSWITCH);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) UsermodManager::lookup(USERMOD_ID_PIRSWITCH);
PIRsensorSwitch *PIRsensor = (PIRsensorSwitch*) UsermodManager::lookup(USERMOD_ID_PIRSWITCH);
🤖 Prompt for AI Agents
In usermods/PIR_sensor_switch_v2/readme_old.md around line 56, the example uses
an invalid C++ cast "PIRsensorSwitch::*" (pointer-to-member syntax) when casting
the lookup result; replace it with a proper pointer cast such as using C-style
cast "(PIRsensorSwitch*) UsermodManager::lookup(USERMOD_ID_PIRSWITCH)" or,
preferably, a C++ cast
"static_cast<PIRsensorSwitch*>(UsermodManager::lookup(USERMOD_ID_PIRSWITCH))" so
the code compiles and the intent is clear.

@DedeHai
Copy link
Collaborator

DedeHai commented Dec 10, 2025

thanks for contributing. the code looks unfinished, is it a draft still?

Copy link
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: 1

♻️ Duplicate comments (1)
usermods/PIR_sensor_switch_v2/readme.md (1)

66-66: Capitalize "PIRs" for consistency.

Use "PIRs" instead of "pirs" to match the document's capitalization convention used elsewhere.

-- To add more pirs or actions, just change the defines in the cpp file (defaults) or the platformio.ini / override
+- To add more PIRs or actions, just change the defines in the cpp file (defaults) or the platformio.ini / override
🧹 Nitpick comments (1)
usermods/PIR_sensor_switch_v2/readme.md (1)

70-70: Rephrase to remove trailing "also".

The phrase ending with "also" reads awkwardly. Consider rephrasing for better flow.

-- If anyone wants to add the other options from the @gegu & @blazoncek usermod then feel free to do so but remember to adjust the custom webpage formatting also.
+- If anyone wants to add the other options from the @gegu & @blazoncek usermod then feel free to do so, but remember to adjust the custom webpage formatting as well.

Alternatively:

-- If anyone wants to add the other options from the @gegu & @blazoncek usermod then feel free to do so but remember to adjust the custom webpage formatting also.
+- If anyone wants to add the other options from the @gegu & @blazoncek usermod then feel free to do so but remember to adjust the custom webpage formatting accordingly.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09da13a and 42cc9d6.

📒 Files selected for processing (1)
  • usermods/PIR_sensor_switch_v2/readme.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
usermods/PIR_sensor_switch_v2/readme.md

[grammar] ~62-~62: Use a hyphen to join words.
Context: ...need more than 8 PIRs or Actions.

  • PIR to Action contributor tracking ensures A...

(QB_NEW_EN_HYPHEN)


[grammar] ~62-~62: Use a hyphen to join words.
Context: ...d more than 8 PIRs or Actions.

  • PIR to Action contributor tracking ensures Acti...

(QB_NEW_EN_HYPHEN)


[style] ~70-~70: The adverb ‘also’ is commonly used to connect clauses and isn’t usually used at the end of a phrase or before a conjunction. Consider replacing it with a more formal alternative.
Context: ...to adjust the custom webpage formatting also.

Hope it's useful :)

(ALSO_AS_WELL)

🪛 markdownlint-cli2 (0.18.1)
usermods/PIR_sensor_switch_v2/readme.md

3-3: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

Updated the readme to improve formatting and clarity.
Copy link
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: 0

♻️ Duplicate comments (1)
usermods/PIR_sensor_switch_v2/readme.md (1)

3-3: Use heading syntax instead of emphasis.

Line 3 should use a level-2 heading (##) rather than bold emphasis for the author attribution, following markdown conventions and fixing the MD036 linting violation.

-**Rawframe**
+## Rawframe
🧹 Nitpick comments (1)
usermods/PIR_sensor_switch_v2/readme.md (1)

62-62: Hyphenate compound modifier for clarity.

"PIR to Action" should be hyphenated as "PIR-to-Action" when used as a compound modifier before "contributor tracking."

-- PIR to Action contributor tracking ensures Actions behave correctly with multiple PIRs
+- PIR-to-Action contributor tracking ensures Actions behave correctly with multiple PIRs
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42cc9d6 and 975d15d.

📒 Files selected for processing (1)
  • usermods/PIR_sensor_switch_v2/readme.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
usermods/PIR_sensor_switch_v2/readme.md

[grammar] ~62-~62: Use a hyphen to join words.
Context: ...need more than 8 PIRs or Actions.

  • PIR to Action contributor tracking ensures A...

(QB_NEW_EN_HYPHEN)


[grammar] ~62-~62: Use a hyphen to join words.
Context: ...d more than 8 PIRs or Actions.

  • PIR to Action contributor tracking ensures Acti...

(QB_NEW_EN_HYPHEN)

🪛 markdownlint-cli2 (0.18.1)
usermods/PIR_sensor_switch_v2/readme.md

3-3: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

Copy link
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: 0

🧹 Nitpick comments (1)
usermods/PIR_sensor_switch_v2/readme.md (1)

62-62: Hyphenate compound adjective "PIR-to-Action" for consistency.

The term "PIR to Action" should use hyphens as "PIR-to-Action" to align with the hyphenation pattern used for "many-to-many" on line 33 and follow standard compound modifier conventions.

-- PIR to Action contributor tracking ensures Actions behave correctly with multiple PIRs
+- PIR-to-Action contributor tracking ensures Actions behave correctly with multiple PIRs
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 975d15d and fbb5790.

📒 Files selected for processing (1)
  • usermods/PIR_sensor_switch_v2/readme.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
usermods/PIR_sensor_switch_v2/readme.md

[grammar] ~62-~62: Use a hyphen to join words.
Context: ...need more than 8 PIRs or Actions.

  • PIR to Action contributor tracking ensures A...

(QB_NEW_EN_HYPHEN)


[grammar] ~62-~62: Use a hyphen to join words.
Context: ...d more than 8 PIRs or Actions.

  • PIR to Action contributor tracking ensures Acti...

(QB_NEW_EN_HYPHEN)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@rawframe
Copy link
Contributor Author

Copy link
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: 1

🧹 Nitpick comments (1)
usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp (1)

51-58: Make pirActions initialization explicit for robustness

pirActions is never explicitly initialized; it currently relies on the fact that MotionDetectionUsermod is a static instance and thus gets zero-initialized before readFromConfig() runs. If this class is ever instantiated with automatic storage (e.g., tests or future refactors), the bitmasks would start with indeterminate values and the OR/AND updates in readFromConfig() could yield garbage link state.

This is not a bug today, but a brittle assumption. Consider explicitly clearing the array in the constructor or setup():

 class MotionDetectionUsermod : public Usermod {
 private:
@@
   uint8_t pirActions[PIR_SENSOR_MAX];
@@
 public:
-  MotionDetectionUsermod() {}
+  MotionDetectionUsermod() {
+    for (int i = 0; i < PIR_SENSOR_MAX; i++) {
+      pirActions[i] = 0;
+    }
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fbb5790 and 8930ad8.

📒 Files selected for processing (1)
  • usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-03-29T01:22:54.617Z
Learnt from: willmmiles
Repo: wled/WLED PR: 4623
File: usermods/word-clock-matrix/word-clock-matrix.cpp:332-332
Timestamp: 2025-03-29T01:22:54.617Z
Learning: In the WLED project, hardcoded usermod IDs (like 500 in the WordClockMatrix's getId() method) are intentionally used as part of a strategy to avoid modifying core code. There are plans to remove numeric usermod IDs entirely in the future.

Applied to files:

  • usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp
🪛 Cppcheck (2.18.0)
usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp

[error] 43-43: There is an unknown macro here somewhere. Configuration is required. If PROGMEM is a macro then please configure it.

(unknownMacro)

🔇 Additional comments (1)
usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp (1)

611-615: Verify USERMOD_ID_PIRSWITCH uniqueness across PIR usermods

getId() returns USERMOD_ID_PIRSWITCH. Given that this package also introduces a legacy/simple PIR usermod in another file (per PR description/summary), there is a risk that both usermods might share the same ID if both are compiled in the same build configuration.

If two different usermods return the same ID, usermod management and any ID‑based logic (including config migration or API usage keyed by ID) can become ambiguous.

Please verify in const.h and the other PIR usermod implementation that:

  • Either only one of these usermods is ever compiled at a time, or
  • Each usermod has a distinct ID constant.

If they currently collide, assign a new constant in const.h for this MotionDetection usermod and update getId() accordingly.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Checkout the specific commit and identify which file corresponds to the diff hash
cd /tmp
git clone --depth 1 --branch main https://github.com/wled/WLED.git wled_pr5179
cd wled_pr5179

# Fetch the PR
git fetch origin pull/5179/head:pr5179
git checkout 09da13aeaf45586a44a05c8d91c662c51141e87a

# List all files in the usermod directory
echo "=== Files in PIR_sensor_switch_v2 ==="
find usermods/PIR_sensor_switch_v2 -type f

# Show the git diff to see which files were changed
echo -e "\n=== Changed files in this commit ==="
git diff --name-only HEAD~1 HEAD 2>/dev/null || git show --name-only --pretty=format: HEAD

# The diff hash likely corresponds to PIR_sensor_switch_v2.cpp based on it being the main file
# Let's read the main implementation file
echo -e "\n=== Content of PIR_sensor_switch_v2.cpp ==="
cat usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp

Length of output: 29224


@rawframe, I'll provide a comprehensive review of the PIR_sensor_switch_v2.cpp file:

Critical Issues

1. Off-Timer Not Cleared on Rising Edge ⚠️

Lines 206-224, 228-230

There's a critical logic bug in the rising edge handling. When a PIR triggers while an action's off-timer is running, the offStartMs is never cleared. The comments say "OPTIMIZATION: Removed redundant offStartMs = 0" but this removal is actually a bug.

Problem scenario:

  1. Action has activeCount = 0 and offStartMs is running (e.g., 30 seconds remaining)
  2. A PIR triggers → activeCount becomes 1
  3. offStartMs is NOT cleared
  4. In lines 254-262, the timer check will still execute and fire the off preset even though the action is active

Fix needed:

if (pin == HIGH) {
  for (int a = 0; a < ACTION_MAX; a++) {
    if (!actionEnabled[a]) continue;
    if (!((pirActions[i] >> a) & 0x01)) continue;

    if (!actions[a].contrib[i]) {
      actions[a].contrib[i] = true;
      actions[a].activeCount++;
    }

    if (actions[a].activeCount == 1) {
      actions[a].offStartMs = 0;  // MUST reset timer when becoming active
      if (actions[a].onPreset) enqueuePreset(actions[a].onPreset);
    } else {
      actions[a].offStartMs = 0;  // MUST reset timer even if already active
    }
  }
}

The offStartMs = 0 assignments are NOT redundant - they prevent off-timers from firing while PIRs are still active.

Code Quality Issues

2. Magic Numbers

Lines 198, 88-91

Per WLED code review guidelines, hardcoded numeric literals should be replaced with named constants:

// Add these near the top with other defines
#ifndef PIR_POLL_INTERVAL_MS
  #define PIR_POLL_INTERVAL_MS 200
#endif

// Then in loop():
if (millis() - lastLoop < PIR_POLL_INTERVAL_MS) return;

Also consider making PRESET_SPACING_MS configurable if users might need different values.

3. No Preset Number Validation

Line 128

The enqueuePreset() function checks for zero but doesn't validate upper bounds:

inline void enqueuePreset(uint8_t preset) {
  if (preset == 0 || preset > 250) return;  // Add upper bound check
  // ... rest of function

4. Complex JavaScript Generation

Lines 410-478

The appendConfigData() method contains a very long JavaScript string that's difficult to maintain and debug. Consider:

  • Adding more inline comments explaining the UI structure
  • Breaking into logical sections with better formatting
  • The FIX comment at line ~430 suggests this area has been problematic

5. No Rate Limiting on State Changes

Lines 331-401

The readFromJsonState() method has no protection against rapid state toggling. A malicious or buggy client could rapidly enable/disable PIRs or actions, potentially causing issues.

Consider adding a timestamp check:

unsigned long lastStateChange = 0;
// In readFromJsonState():
if (millis() - lastStateChange < 100) return;  // Debounce 100ms

Positive Aspects ✅

Well-Implemented Features:

  1. Bitmask Optimization (lines 51, 185, 209, 230, 286, 362): Smart use of bitmasks instead of 2D bool arrays saves significant RAM
  2. Buffer Safety (lines 159, 165, 275, 352): Consistent use of snprintf with buffer size checking
  3. Proper Pin Management (lines 197-211): Correct use of PinManager::allocatePin with fallback handling
  4. FIFO Flood Protection (line 129): Prevents duplicate sequential presets
  5. Explicit Type Casting (lines 165, 175, 180, 365): Proper use of .as<bool>() for JSON parsing
  6. Platform-Specific Pin Modes (lines 204-208): Handles ESP8266 vs ESP32 differences correctly
  7. Unified Off-Timer Design: Single timer per action coordinating multiple PIRs is elegant
  8. Configuration Persistence: Proper implementation of config load/save cycle

Minor Improvements

6. Potential Overflow in Delay Calculation

Line 172

When reading offSec from config:

uint32_t s = ap["offSec"] | (actions[a].offDelayMs / 1000UL);
actions[a].offDelayMs = s * 1000UL;

Consider adding bounds checking:

uint32_t s = ap["offSec"] | (actions[a].offDelayMs / 1000UL);
if (s > 4294967) s = 4294967;  // Prevent uint32_t overflow when multiplied by 1000
actions[a].offDelayMs = s * 1000UL;

7. Comment Accuracy

Lines 218-220, 228-230, 263-265

Comments about "Removed redundant offStartMs = 0" should be removed or corrected once the critical bug is fixed, as these statements are necessary.

8. ActionProfile Constructor

Lines 54-63

Well-structured initialization, but consider initializing contrib array with a loop or memset for clarity:

ActionProfile() : onPreset(0), offPreset(0), offDelayMs(600000UL), 
                  activeCount(0), offStartMs(0) {
  memset(contrib, 0, sizeof(contrib));
}

Documentation

9. Missing Documentation

  • The bitmask optimization for pirActions should be documented in comments
  • The FIFO queue behavior could use more explanation
  • The unified off-timer logic deserves clearer documentation

Testing Recommendations

  1. Test the critical bug scenario: PIR triggers while off-timer is counting down
  2. Verify rapid enable/disable toggling doesn't cause issues
  3. Test with maximum PIR_SENSOR_MAX and ACTION_MAX values
  4. Verify UI rendering with all 8 PIRs and 8 Actions
  5. Test edge case: all PIRs disabled, then re-enabled
  6. Test edge case: preset numbers at boundaries (0, 1, 250, 255)

Summary: This is a well-architected usermod with smart optimizations (bitmasks, FIFO), but has one critical timing bug that must be fixed before merging. The off-timer clearing issue could cause unexpected behavior where lights turn off even when motion is detected.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, verify that file operations (especially file.open()) respect LittleFS filename limitations. Assume default WLED configuration with LittleFS default filename limit of 255 bytes. Do not assume extreme configuration values like WLED_MAX_SEGNAME_LEN = 512 which would not be standard configurations.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, file operations (especially file.open()) should be checked to ensure they respect LittleFS filename limitations. The default LittleFS filename limit is 255 bytes (LFS_NAME_MAX). Reviews should assume default WLED configuration defines and not extreme edge-case values (e.g., WLED_MAX_SEGNAME_LEN = 512 would not be standard). File paths should be validated to stay within the 255-byte limit.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase. For example, suggest replacing hardcoded "32" with WLED_MAX_SEGNAME_LEN if the context relates to segment name length limits.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1176-1187
Timestamp: 2025-09-16T18:08:42.848Z
Learning: In WLED finalizeInit() bus creation (wled00/FX_fcn.cpp), intentionally allowing memory overruns when bus configurations exceed MAX_LED_MEMORY is a deliberate design choice. The trade-off prioritizes creating buses with reduced LED counts over completely failing to create buses, which would cause no LED output and UI failures. This approach forces users to update configurations after migrating to version 0.16 while maintaining basic functionality.

Learnt from: willmmiles
Repo: wled/WLED PR: 4859
File: usermods/udp_name_sync/udp_name_sync.cpp:62-70
Timestamp: 2025-08-21T00:00:17.196Z
Learning: In WLED codebase, UDP packet processing code must never rely on null termination being present on the wire and must always validate packet size first. The codebase already has some vulnerabilities where malformed data can crash the software, so avoiding introduction of new ones is a priority.

Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR #5134, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.
✅ Actions performed

Full review triggered.

Copy link
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: 2

🧹 Nitpick comments (3)
usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp (3)

212-213: Consider extracting poll interval to named constant.

The 200ms polling interval is hardcoded. For maintainability, consider defining it as a constant.

+#ifndef PIR_POLL_INTERVAL_MS
+  #define PIR_POLL_INTERVAL_MS 200
+#endif
+
 // In loop():
-    if (millis() - lastLoop < 200) return;
+    if (millis() - lastLoop < PIR_POLL_INTERVAL_MS) return;

373-475: LGTM: State management with proper cleanup.

The JSON state parsing correctly handles enable/disable toggling for both PIRs and actions, with appropriate cleanup of contributor state and timers. The index mapping logic supports both 0-based and 1-based action keys.

Optional: Consider adding rate limiting or debouncing to prevent rapid enable/disable toggling from external API calls, which could cause timing inconsistencies.


512-610: Consider refactoring JavaScript generation for maintainability.

The JavaScript injection spans 100 lines of concatenated strings within oappend(F(...)) calls. While functional, this approach makes the code difficult to read, debug, and maintain.

Options:

  1. Split into multiple smaller oappend(F(...)) calls with logical grouping (CSS, helpers, main logic, cleanup)
  2. Move JavaScript to a separate resource file served by the web server
  3. Add inline comments to mark major sections within the JavaScript string

Given the "Chill" review mode, this is flagged as optional since the code works correctly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8930ad8 and 0826e96.

📒 Files selected for processing (1)
  • usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-03-29T01:22:54.617Z
Learnt from: willmmiles
Repo: wled/WLED PR: 4623
File: usermods/word-clock-matrix/word-clock-matrix.cpp:332-332
Timestamp: 2025-03-29T01:22:54.617Z
Learning: In the WLED project, hardcoded usermod IDs (like 500 in the WordClockMatrix's getId() method) are intentionally used as part of a strategy to avoid modifying core code. There are plans to remove numeric usermod IDs entirely in the future.

Applied to files:

  • usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp
🪛 Cppcheck (2.18.0)
usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp

[error] 43-43: There is an unknown macro here somewhere. Configuration is required. If PROGMEM is a macro then please configure it.

(unknownMacro)

🔇 Additional comments (5)
usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp (5)

119-163: LGTM: Config parsing is robust.

The configuration parsing handles buffer safety with snprintf, uses explicit type casts for JSON boolean parsing, and includes overflow prevention for the off-delay calculation.


169-201: LGTM: Proper pin management and initialization.

The setup correctly handles pin allocation with fallback on failure, uses platform-specific pinMode configuration for ESP8266 vs ESP32, and initializes runtime state without overwriting persisted configuration.


279-367: LGTM: UI generation is functional.

The JSON info generation correctly builds UI elements with buffer safety (snprintf) and includes underflow protection for timer calculations. The HTML string concatenation is verbose but clear and functional.


481-506: LGTM: Configuration persistence is correct.

The configuration serialization properly saves all PIR and action settings with buffer safety. The enabled state persistence ensures configuration survives reboots.


615-620: LGTM: Standard usermod registration.

The usermod ID and registration follow WLED conventions. Based on learnings, hardcoded usermod IDs are intentional and part of the project's strategy.

Comment on lines +27 to +33
#ifndef PIR_SENSOR_MAX
#define PIR_SENSOR_MAX 2
#endif

#ifndef ACTION_MAX
#define ACTION_MAX 2
#endif
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add compile-time validation for bitmask size constraint.

The bitmask pirActions uses uint8_t (8 bits), limiting support to 8 actions maximum. However, ACTION_MAX is configurable without validation. If ACTION_MAX > 8, bitmask operations at lines 136, 226, 245, 310, and 493 will fail silently.

Add a compile-time assertion after the definitions:

+#ifndef ACTION_MAX
+  #define ACTION_MAX 2
+#endif
+
+#if ACTION_MAX > 8
+  #error "ACTION_MAX must be <= 8 due to uint8_t bitmask size constraint in pirActions"
+#endif
+
 #ifndef PRESET_FIFO_SIZE
   #define PRESET_FIFO_SIZE 16
 #endif

Alternatively, if support for more than 8 actions is desired, change pirActions to uint16_t or uint32_t and update the constraint accordingly.

Also applies to: 57-57

🤖 Prompt for AI Agents
usermods/PIR_sensor_switch_v2/PIR_sensor_switch_v2.cpp lines 27-33 (and also at
line 57): ACTION_MAX can exceed the 8 bits of the uint8_t bitmask used for
pirActions, causing silent failures; add a compile-time validation and/or widen
the bitmask type. Either add a static_assert immediately after these #defines to
enforce ACTION_MAX <= 8, or change the pirActions declaration to
uint16_t/uint32_t and update the assertion (and any masking/shift logic) to use
the new width; ensure all bitwise operations and masks at lines 136, 226, 245,
310, and 493 are adjusted to the chosen integer size.

Comment on lines +234 to +239
if (actions[a].activeCount == 1) {
// OPTIMIZATION: Removed redundant offStartMs = 0
if (actions[a].onPreset) enqueuePreset(actions[a].onPreset);
} else {
// OPTIMIZATION: Removed redundant offStartMs = 0
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Off-timer not cleared when action becomes active.

When a PIR triggers while an off-timer is counting (activeCount transitions from 0→1), offStartMs is not reset. This causes the off-preset to fire incorrectly despite active motion.

Scenario:

  1. PIR1 goes LOW → activeCount = 0, offStartMs = millis() (timer starts at line 251)
  2. PIR2 goes HIGH before timer expires → activeCount = 1 (line 230)
  3. Line 234 condition is true, but offStartMs is not cleared (line 235 comment claims "redundant")
  4. Timer check at line 263 can still fire and run the off-preset despite activeCount > 0

Apply this diff to clear offStartMs when the action becomes or remains active:

           // if action just became active, enqueue onPreset; always clear offStartMs
           if (actions[a].activeCount == 1) {
-            // OPTIMIZATION: Removed redundant offStartMs = 0
+            actions[a].offStartMs = 0;
             if (actions[a].onPreset) enqueuePreset(actions[a].onPreset);
           } else {
-            // OPTIMIZATION: Removed redundant offStartMs = 0
+            actions[a].offStartMs = 0;
           }

Also apply this diff to the unified timer check:

         if (actions[a].offPreset) enqueuePreset(actions[a].offPreset);
         }
       } else {
-        // OPTIMIZATION: Removed redundant offStartMs = 0
+        actions[a].offStartMs = 0;
       }

Also applies to: 267-269

@rawframe
Copy link
Contributor Author

thanks for contributing. the code looks unfinished, is it a draft still?
@DedeHai
yes this is finished for now. (made a couple of minor changes). thanks

*/

#include "wled.h"

Copy link
Collaborator

Choose a reason for hiding this comment

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

missing an include here, also missing the .h in you header

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it compiles ok. my coding and github useage isnt great so im not sure what else is needed here? it doesn't seem much different the existing usermod of pir_sensor_switch thats already in wled repo.

@@ -0,0 +1,110 @@
# PIR sensor switch
Copy link
Collaborator

Choose a reason for hiding this comment

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

why have two readme files?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is the older readme from the existing usermod of pir_sensor_switch thats already in wled repo. it was kept in the same way that usermod kept my old highlight standby pir from years ago

## Notes

- To add more PIRs or Actions, just change the defines in the cpp file (defaults) or the platformio.ini / override
- PIR_SENSOR_MAX
Copy link
Collaborator

Choose a reason for hiding this comment

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

limit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it says a limit of 8 in the readme

*
* Using a usermod:
* 1. Copy the usermod into the sketch folder (same folder as wled00.ino)
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
Copy link
Collaborator

Choose a reason for hiding this comment

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

not how its done.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not edited in by me but is in the existing usermod of pir_sensor_switch thats already in wled repo.

@DedeHai
Copy link
Collaborator

DedeHai commented Dec 11, 2025

I see what you did now, but why mix in the old code and the old readme? if this is a new, independent usermod keeping the old stuff just makes it really convoluted and confusing.

edit:
if this extends the functionality of the original UM and keeps all its functions, it should replace the original

@rawframe
Copy link
Contributor Author

I see what you did now, but why mix in the old code and the old readme? if this is a new, independent usermod keeping the old stuff just makes it really convoluted and confusing.

edit: if this extends the functionality of the original UM and keeps all its functions, it should replace the original

Sure, there no need for the standbyhighlight file or the older readme. I only kept it there in the same way the other pir usermod kept my old codes readme with more added (highlightstandby) and I figured a similar trail was the convention here.
In essence, my v2 doesn't replace the original and has evolved at the same time - so not a replacement but may be in time if i add more features down the line though i have no plans to do so for now

@DedeHai
Copy link
Collaborator

DedeHai commented Dec 11, 2025

without knowing this nor the other PIR usermod (so please excuse my ignorance): why do we need two PIR usermods? is it possible to unify them?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants