From f1339e97a33714266480a1d41fd32e2019b67797 Mon Sep 17 00:00:00 2001
From: Adam Matthews <52178922+adamsthws@users.noreply.github.com>
Date: Fri, 11 Sep 2026 13:59:17 +0100
Subject: [PATCH 1/2] Make button timing constants configurable via UI
WLED_DEBOUNCE_THRESHOLD, WLED_LONG_PRESS and WLED_DOUBLE_PRESS were
plain #defines with no way to change them without recompiling.
Per discussion on wled/WLED#5844, expose them as runtime settings on
the LED Preferences > Buttons page.
Limits (Debounce 0-250ms, Long press 100-4000ms, Double press
0-1000ms) follow similar timing conventions used by Tasmota, OneButton
and ESPHome for the same settings. Long press is capped
below WLED_LONG_AP (5000ms) so a user-configured value can't collide
with button 0's existing AP-mode/factory-reset hold thresholds.
---
wled00/button.cpp | 17 +++++++----------
wled00/cfg.cpp | 6 ++++++
wled00/const.h | 4 ++++
wled00/data/settings_leds.htm | 8 +++++++-
wled00/set.cpp | 6 ++++++
wled00/wled.h | 3 +++
wled00/xml.cpp | 3 +++
7 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/wled00/button.cpp b/wled00/button.cpp
index ef56cea7c6..148f3ce15e 100644
--- a/wled00/button.cpp
+++ b/wled00/button.cpp
@@ -4,9 +4,6 @@
* Physical IO
*/
-#define WLED_DEBOUNCE_THRESHOLD 50 // only consider button input of at least 50ms as valid (debouncing)
-#define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms
-#define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press
#define WLED_LONG_REPEATED_ACTION 400 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0
#define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP
#define WLED_LONG_FACTORY_RESET 10000 // how long button 0 needs to be held to trigger a factory reset
@@ -136,7 +133,7 @@ void handleSwitch(uint8_t b)
if (buttons[b].longPressed == buttons[b].pressedBefore) return;
- if (millis() - buttons[b].pressedTime > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
+ if (millis() - buttons[b].pressedTime > buttonDebounceMs) { //fire edge event only after debounce threshold without change
DEBUG_PRINTF_P(PSTR("Switch: Activating %u\n"), b);
if (!buttons[b].pressedBefore) { // on -> off
DEBUG_PRINTF_P(PSTR("Switch: On -> Off (%u)\n"), b);
@@ -304,7 +301,7 @@ void handleButton()
if (!buttons[b].pressedBefore) buttons[b].pressedTime = now;
buttons[b].pressedBefore = true;
- if (now - buttons[b].pressedTime > WLED_LONG_PRESS) { //long press
+ if (now - buttons[b].pressedTime > buttonLongPressMs) { //long press
if (!buttons[b].longPressed) {
buttonBriDirection = !buttonBriDirection; //toggle brightness direction on long press
longPressAction(b);
@@ -320,11 +317,11 @@ void handleButton()
// released after rising-edge short press action
if (buttons[b].macroButton && buttons[b].macroButton == buttons[b].macroLongPress && buttons[b].macroButton == buttons[b].macroDoublePress) {
- if (dur > WLED_DEBOUNCE_THRESHOLD) buttons[b].pressedBefore = false; // debounce, blocks button for 50 ms once it has been released
+ if (dur > buttonDebounceMs) buttons[b].pressedBefore = false; // debounce, blocks button once it has been released
continue;
}
- if (dur < WLED_DEBOUNCE_THRESHOLD) {buttons[b].pressedBefore = false; continue;} // too short "press", debounce
+ if (dur < buttonDebounceMs) {buttons[b].pressedBefore = false; continue;} // too short "press", debounce
bool doublePress = buttons[b].waitTime; //did we have a short press before?
buttons[b].waitTime = 0;
@@ -339,7 +336,7 @@ void handleButton()
//NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling
if (b != 1 && !buttons[b].macroDoublePress) { //don't wait for double press on buttons without a default action if no double press macro set
shortPressAction(b);
- } else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0)
+ } else { //double press if within double press window of current press and previous short press release (buttonWaitTime!=0)
if (doublePress) {
doublePressAction(b);
} else {
@@ -351,8 +348,8 @@ void handleButton()
buttons[b].longPressed = false;
}
- //if 350ms elapsed since last short press release it is a short press
- if (buttons[b].waitTime && now - buttons[b].waitTime > WLED_DOUBLE_PRESS && !buttons[b].pressedBefore) {
+ //if double press window elapsed since last short press release it is a short press
+ if (buttons[b].waitTime && now - buttons[b].waitTime > buttonDoublePressMs && !buttons[b].pressedBefore) {
buttons[b].waitTime = 0;
shortPressAction(b);
}
diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp
index bfb6242883..f4c0ac28fc 100644
--- a/wled00/cfg.cpp
+++ b/wled00/cfg.cpp
@@ -451,6 +451,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
CJSON(buttonPublishMqtt, btn_obj["mqtt"]);
+ CJSON(buttonDebounceMs, btn_obj[F("dbnc")]);
+ CJSON(buttonLongPressMs, btn_obj[F("lp")]);
+ CJSON(buttonDoublePressMs, btn_obj[F("dp")]);
#ifndef WLED_DISABLE_INFRARED
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
@@ -1048,6 +1051,9 @@ void serializeConfig(JsonObject root) {
hw_btn[F("tt")] = touchThreshold;
hw_btn["mqtt"] = buttonPublishMqtt;
+ hw_btn[F("dbnc")] = buttonDebounceMs;
+ hw_btn[F("lp")] = buttonLongPressMs;
+ hw_btn[F("dp")] = buttonDoublePressMs;
JsonObject hw_ir = hw.createNestedObject("ir");
#ifndef WLED_DISABLE_INFRARED
diff --git a/wled00/const.h b/wled00/const.h
index 00a6b4d226..5f1133a8b7 100644
--- a/wled00/const.h
+++ b/wled00/const.h
@@ -651,6 +651,10 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define TOUCH_THRESHOLD 32 // limit to recognize a touch, higher value means more sensitive
+#define WLED_DEBOUNCE_THRESHOLD 50 // default: only consider button input of at least 50ms as valid (debouncing)
+#define WLED_LONG_PRESS 600 // default: long press if button is released after held for at least 600ms
+#define WLED_DOUBLE_PRESS 350 // default: double press if another press within 350ms after a short press
+
// Size of buffer for API JSON object (increase for more segments)
#ifdef ESP8266
#define JSON_BUFFER_SIZE 10240
diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm
index 03fae259c4..ad98dee546 100644
--- a/wled00/data/settings_leds.htm
+++ b/wled00/data/settings_leds.htm
@@ -1093,7 +1093,13 @@
Buttons
Disable internal pull-up/down:
- Touch threshold:
+ Touch threshold:
+ Debounce time: ms (0-250)
+ Ignores presses shorter than this (filters switch/contact noise)
+ Long press time: ms (100-4000)
+ How long to hold before a long press triggers
+ Double press time: ms (0-1000)
+ Max gap between two clicks to count as a double press
IR Remote
diff --git a/wled00/set.cpp b/wled00/set.cpp
index 5c459e5570..4ee0dfa273 100644
--- a/wled00/set.cpp
+++ b/wled00/set.cpp
@@ -332,6 +332,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
disablePullUp = (bool)request->hasArg(F("IP"));
touchThreshold = request->arg(F("TT")).toInt();
+ t = request->arg(F("DB")).toInt();
+ if (t >= 0 && t <= 250) buttonDebounceMs = t;
+ t = request->arg(F("LP")).toInt();
+ if (t >= 100 && t <= 4000) buttonLongPressMs = t; // stay below WLED_LONG_AP so button 0's AP-mode/factory-reset thresholds are unaffected
+ t = request->arg(F("DP")).toInt();
+ if (t >= 0 && t <= 1000) buttonDoublePressMs = t;
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
int offset = i < 10 ? '0' : 'A' - 10;
char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
diff --git a/wled00/wled.h b/wled00/wled.h
index 5d2a6e75b1..a89b7709fc 100644
--- a/wled00/wled.h
+++ b/wled00/wled.h
@@ -647,6 +647,9 @@ WLED_GLOBAL std::vector
Disable internal pull-up/down:
Touch threshold:
- Debounce time: ms (0-250)
+ Debounce time: ms (0-100)
Ignores presses shorter than this (filters switch/contact noise)
- Long press time: ms (100-4000)
+ Long press time: ms (200-4000)
How long to hold before a long press triggers
- Double press time: ms (0-1000)
+ Double press time: ms (100-1000)
Max gap between two clicks to count as a double press
diff --git a/wled00/set.cpp b/wled00/set.cpp
index 4ee0dfa273..ae7b2f6d73 100644
--- a/wled00/set.cpp
+++ b/wled00/set.cpp
@@ -333,11 +333,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
disablePullUp = (bool)request->hasArg(F("IP"));
touchThreshold = request->arg(F("TT")).toInt();
t = request->arg(F("DB")).toInt();
- if (t >= 0 && t <= 250) buttonDebounceMs = t;
+ if (t >= 0 && t <= 100) buttonDebounceMs = t;
t = request->arg(F("LP")).toInt();
- if (t >= 100 && t <= 4000) buttonLongPressMs = t; // stay below WLED_LONG_AP so button 0's AP-mode/factory-reset thresholds are unaffected
+ if (t >= 200 && t <= 4000) buttonLongPressMs = t; // stay below WLED_LONG_AP so button 0's AP-mode/factory-reset thresholds are unaffected; floor kept above the debounce max (100) so long press can never be shorter than debounce
t = request->arg(F("DP")).toInt();
- if (t >= 0 && t <= 1000) buttonDoublePressMs = t;
+ if (t >= 100 && t <= 1000) buttonDoublePressMs = t;
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
int offset = i < 10 ? '0' : 'A' - 10;
char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)