Skip to content

Add initialization logic to switch type inputs for correct boot-up behaviour#5374

Closed
KuglicsL wants to merge 1 commit intowled:mainfrom
KuglicsL:KuglicsL-switch-fix
Closed

Add initialization logic to switch type inputs for correct boot-up behaviour#5374
KuglicsL wants to merge 1 commit intowled:mainfrom
KuglicsL:KuglicsL-switch-fix

Conversation

@KuglicsL
Copy link

@KuglicsL KuglicsL commented Feb 16, 2026

Added boot-up state initialization logic to switch type inputs.
This ensures switch type inputs can be in any state at boot-up without erroneously calling Off->On state change macros.

The initialization logic debounces the input state, initializes button state based on the filtered value and prints debug information.

The changes were tested on an ESP32.

Summary by CodeRabbit

  • Bug Fixes
    • Improved button input debouncing and stability through enhanced state handling logic.

Added boot-up state initialization logic to switch type inputs.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 16, 2026

Walkthrough

Refactors the handleSwitch function in button.cpp to introduce caching of the current switch state and restructure the switch-state machine logic, using waitTime as an explicit initialization and debounce state controller with three defined states before processing edge detection.

Changes

Cohort / File(s) Summary
Switch State Initialization and Debouncing
wled00/button.cpp
Introduces cached currentSwitchState at the start of handleSwitch. Reworks the state machine by repurposing waitTime into three explicit states: Case 0 captures initial state and starts debounce timer; Case 1 performs debounce and marks initialization complete if stable; Case 2 skips further processing. Replaces final edge-detection comparison to use cached state instead of calling isButtonPressed again.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The title 'Update button.cpp' is generic and vague, using a non-descriptive term that doesn't convey meaningful information about the changeset's purpose or main objective. Revise the title to be more specific, such as 'Add boot-up state initialization for switch-type inputs' or 'Fix erroneous Off→On state-change macros at startup', to clearly communicate the primary change.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

@KuglicsL KuglicsL changed the title Update button.cpp Add initialization logic to switch type inputs for correct boot-up behaviour Feb 16, 2026
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.

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@wled00/button.cpp`:
- Line 136: Remove the unreachable "break;" statements that follow a "return" in
button.cpp (the redundant break; lines shown in the diff); locate the
switch/case or control blocks where a return immediately precedes a break and
delete those break statements to avoid unreachable-code warnings and cleanup the
code.
🧹 Nitpick comments (1)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@wled00/button.cpp`:
- Line 136: Remove the unreachable "break;" statements that follow a "return" in
button.cpp (the redundant break; lines shown in the diff); locate the
switch/case or control blocks where a return immediately precedes a break and
delete those break statements to avoid unreachable-code warnings and cleanup the
code.
wled00/button.cpp (1)

136-136: Dead code: break after return is unreachable.

The comments say "redundant, left as guard," but these statements are truly unreachable — no compiler or runtime path can execute them. They may trigger compiler warnings (e.g., -Wunreachable-code). Consider removing them.

Proposed fix
       ++buttons[b].waitTime;                              // change initialization stage to debouncing
       return;                                             // will have to debounce later, might as well return now
-      break;                                              // redundant, left as guard 
     case 1: // debouncing
       return;
-      break;    // redundant, left as guard 
     case 2:   // the switch is initialized

Also applies to: 150-150

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/button.cpp` at line 136, Remove the unreachable "break;" statements
that follow a "return" in button.cpp (the redundant break; lines shown in the
diff); locate the switch/case or control blocks where a return immediately
precedes a break and delete those break statements to avoid unreachable-code
warnings and cleanup the code.

@DedeHai
Copy link
Collaborator

DedeHai commented Feb 17, 2026

thanks for contributing.
IMHO this is overcomplicating it, this would do the same:

if (buttons[b].waitTime == 0) {
  buttons[b].waitTime++;
  buttons[b].pressedBefore = isButtonPressed(b);;
}

@KuglicsL
Copy link
Author

KuglicsL commented Feb 17, 2026

thanks for contributing. IMHO this is overcomplicating it, this would do the same:

if (buttons[b].waitTime == 0) {
  buttons[b].waitTime++;
  buttons[b].pressedBefore = isButtonPressed(b);;
}

This would skip the debouncing.
I think it is necessary, as my solution elimimates any uncertainity about system voltage dV/dt at startup.

My example. The board is supplied from 12V -> SMPS -> MCU 3.3V. It also has optocoupler inputs triggered by >=10V, driven from the 12V supply. If the 12V ramp up is slow, the MCU can start up, read switch input and save it as bootup state before 12V reaches the trigger level for the optocoupler stage.

Having the initial debounce also helps if your input has a large external filter cap, only pulled up by internal pullup - it gives the cap time to be charged by the large MCU pullup resistance.

Also debounce in general is used against noise spikes from bad wiring and unstable supply practices. E.g. WLED hardware installed in cars could suffer the most from the lack of it.

Overall I think the slightly more complicated solution is more foolproof and has minimal impact on the handleSwitch() function's speed and size.

@DedeHai
Copy link
Collaborator

DedeHai commented Feb 17, 2026

debounce as the name sais is to "debounce" a switch, it should not be used to compensate for all sorts of bad hardware design. At boot a switch will be stable and can not bounce. if power is unstable at boot, there is a compile flag for such setups.

@KuglicsL
Copy link
Author

I agree about the bad HW design part, but 99% of WLED users have no knowledge what a complier even is. If they encounter an issue like this, they will just find a workaround or downgrade their project. Increasing stability is always welcome especially in unpredictable end user environments.

Debouncing buttons in all states (even initial) keeps industry best practices in sight and gives button handling the most stability. It has low added size (based on amap: button.cpp 256873 -> 267622) and very low added runtime cycles after initialization is done (maybe two extra comparsions in switch - case).

I'd like if others chipped in with their ideas, other than that I rest my case.

@DedeHai
Copy link
Collaborator

DedeHai commented Feb 17, 2026

if you want to have debounce for each state change that is fine with me but it then should be done properly without code duplication.

@DedeHai
Copy link
Collaborator

DedeHai commented Feb 17, 2026

startup delay logic aside:
won't this change break users setups that rely on current behaviour? like if I have a switch where on is leds on and off is leds off: if the switch is on and a reboot happens it turns back on, after this PR change it wont. correct?

@KuglicsL
Copy link
Author

On the code duplication note: I think the whole button handling could use a makeover to move debouncing from individual handleButton/handleSwitch debouncing to a combined debouncer. The short/long press differentiation then could be handled in their own separate state machine. Right now, the debounce threshold is referenced in three different places, and it is not possible to implement initial state debouncing without duplication.

won't this change break users setups that rely on current behaviour?

That is correct - I presumed the suggested behaviour should be the default based on the comments in the source code.
I believe the best option would be to introduce a new input type: a switch, but it only activates on edge transitions; the actual logic level does not matter. This would maximalize user options for interfacing with the controller.

We would have two types:

  1. Switch (level) - old
  2. Switch (edge) - new

If this sounds like a useful feature, I might sift through the source and try implementing it. In the meantime, this PR is a dead end.

@KuglicsL KuglicsL closed this Feb 17, 2026
@DedeHai
Copy link
Collaborator

DedeHai commented Feb 17, 2026

Right now, the debounce threshold is referenced in three different places, and it is not possible to implement initial state debouncing without duplication.

yea, that is what I was hinting at. piling fixes upon a bad base makes it worse over time (and is probably how it got the way it is). We try to improve things in baby steps.

  • Switch (level) - old
  • Switch (edge) - new

I am not sure this is the best way but it might be, I am not using toggle switches except for tests, all I can say that to me the current implementation is a bit confusing on when it actually switches a preset. Also PIR sensor uses the same code if I read that right, current implementation is already edge driven(?). You are right that the whole code could use a better, unified approach.

@KuglicsL
Copy link
Author

KuglicsL commented Feb 17, 2026

Current implementation based on my understanding:

  1. Initializes switches to default false value
  2. If the actual switch state on boot-up is true, it will call the assigned Off->On preset (macroButton) immediately after WLED_DEBOUNCE_THRESHOLD time, which might interrupt the boot-up playlist if one is configured
  3. After that, the switch works normally.

After looking at the source some more, there's a few more weird behaviours in there. If a long strip is currently updating, both analog and digital button refresh time is tied to ANALOG_BTN_READ_CYCLE (250ms). Why not have a dedicated define for that frequency, and why is it this large?

The only difference between a PIR switch and a normal SWITCH is inverted reading and a unique MQTT topic (/motion) for the PIR sensor.

I believe the overall cleanest solution would be to rework the button handling to individually configurable entities, rather than pre-configured input types and a global pullup toggle checkbox.
Example:

[COMMON GLOBAL OPTIONS]
Global debounce time.
Global touch threshold.

[COMMON INDIVIDUAL OPTIONS]
Inputs can be either normal or inverted.
Inputs can have their own MQTT topic configured for state change reporting. (with limited length topic names)
Inputs can have internal pulls configured. Possible options are pullup/pulldown/none.
Inputs have a short press/positive edge and a long press/negative edge macro slot.
An input can be a momentary switch, a touch switch, or a toggle switch.

[MOMENTARY SWITCH GLOBAL OPTIONS]
Global button long press threshold.
Global button double press interval.

[TOGGLE SWITCH ONLY OPTIONS]
Activation mode can be either level sensitive (current behaviour) or edge sensitive (boot-up state does not matter).

Obviously this approach introduces a lot of new possibilities for interfacing on the user side. It is totally feasible to make this change backwards compatible, and selectable presets (BUTTON, SWITCH, PIR) would allow users to easily select old button types in the new system.

If you think this is not over the top, I could start working on a large, backwards compatible overhaul in my free time.
The possible downsides would be a more cluttered setup page (perhaps move the buttons to a new "Button preferences" page?), more memory usage, and larger config files.

Let me know what you think.

@DedeHai
Copy link
Collaborator

DedeHai commented Feb 17, 2026

without checking code: the list you have there should be pretty much how it is implemented right now. or which are the differences (apart from the code being messy)

@KuglicsL
Copy link
Author

KuglicsL commented Feb 17, 2026

[COMMON GLOBAL OPTIONS]
Global debounce time. - Currently not changeable from UI, set at compile time.
Global touch threshold. - Same as current operation, but could also be on a button by button basis

[COMMON INDIVIDUAL OPTIONS]
Inputs can be either normal or inverted. - Inputs can't be selected as inverted, inversion is coded into the input type itself.
Inputs can have their own MQTT topic configured for state change reporting. (with limited length topic names). - Right now all events on inputs go to the same MQTT channel (/button), except PIR
Inputs can have internal pulls configured. Possible options are pullup/pulldown/none. - Currently a global option, and it only support pullups, except for the inverted pushbutton which is pulldown
Inputs have a short press/positive edge and a long press/negative edge macro slot. - Same as current operation
An input can be a momentary switch, a touch switch, or a toggle switch. - Same as current, but choosing the input type does not lock you into the default values assigned to that input in code

[MOMENTARY SWITCH GLOBAL OPTIONS]
Global button long press threshold. - Currently not changeable from UI, set at compile time.
Global button double press interval. - Currently not changeable from UI, set at compile time.

[TOGGLE SWITCH INDIVIDUAL OPTIONS]
Activation mode can be either level sensitive (current behaviour) or edge sensitive (boot-up state does not matter). - No option for this mode right now

Example structure:

//shared variables between all configurable buttons
class ConfigurableInput {
  int8_t pin;                   // -1 if uninitialized, positive otherwise
  uint8_t type;                 // cfginput type
  bool inverted;                // if true, input logic level is inverted
  String MQTTEventTopic;        // individual configurable MQTT topic for each input
  uint8_t pullDirection;        // none/up/down
  
  uint8_t prevState;            // previous value for debouncing
  unsigned long lastTransition; // last transition time for debouncing
  unsigned long debounceTime;   // debouncing time threshold

  uint8_t presetEventPrimary;   // preset to call on primary event (short press for button, positive edge for simple switch, any edge for toggle switch)
  uint8_t presetEventSecondary; // preset to call on secondary event (long press for button, negative edge for simple switch)
};

class MomentarySwitch: public ConfigurableInput {
  unsigned long longPressThreshold;   // minimum duration for long press
  unsigned long doublePressInterval;  // maximum interval for double press
};

class ToggleSwitch: public ConfigurableInput  {
  bool edgeMode;                // if true, boot-up state does not matter
};

class TouchSwitch: public ConfigurableInput  {
  uint8_t touchThreshold;       // touch button threshold level
};

class AnalogSwitch: public ConfigurableInput  {
  uint16_t turnOffThreshold;    // global brightness mode turnoff value
  uint16_t filteredValue;       // IIR filtered analog value  
  uint8_t filterCoeff;          // 0 - 100 IIR filter coefficient
};

So basically this would move lots of compile time options into the runtime configurable territory.
By allowing the user to select every property of each individual input, while also offering "quick setup input presets" (AKA the legacy input types), input configuration flexibility increases by a huge margin, and backwards compatibility is maximized.

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