Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions usermods/multi_relay/multi_relay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
#define MULTI_RELAY_INVERTS false
#endif

#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)

#define ON true
#define OFF false

Expand Down Expand Up @@ -590,7 +588,7 @@ bool MultiRelay::handleButton(uint8_t b) {

if (buttons[b].longPressed == buttons[b].pressedBefore) return handled;

if (now - buttons[b].pressedTime > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
if (now - buttons[b].pressedTime > buttonDebounceMs) { //fire edge event only after debounce time without change
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].button == b) {
switchRelay(i, buttons[b].pressedBefore);
Expand All @@ -607,7 +605,7 @@ bool MultiRelay::handleButton(uint8_t b) {
if (!buttons[b].pressedBefore) buttons[b].pressedTime = now;
buttons[b].pressedBefore = true;

if (now - buttons[b].pressedTime > 600) { //long press
if (now - buttons[b].pressedTime > buttonLongPressMs) { //long press
//longPressAction(b); //not exposed
//handled = false; //use if you want to pass to default behaviour
buttons[b].longPressed = true;
Expand All @@ -616,15 +614,15 @@ bool MultiRelay::handleButton(uint8_t b) {
} else if (!isButtonPressed(b) && buttons[b].pressedBefore) { //released

long dur = now - buttons[b].pressedTime;
if (dur < WLED_DEBOUNCE_THRESHOLD) {
if (dur < buttonDebounceMs) {
buttons[b].pressedBefore = false;
return handled;
} //too short "press", debounce
bool doublePress = buttons[b].waitTime; //did we have short press before?
buttons[b].waitTime = 0;

if (!buttons[b].longPressed) { //short press
// if this is second release within 350ms it is a double press (buttonWaitTime!=0)
// if this is second release within double press time it is a double press (buttonWaitTime!=0)
if (doublePress) {
//doublePressAction(b); //not exposed
//handled = false; //use if you want to pass to default behaviour
Expand All @@ -635,8 +633,8 @@ bool MultiRelay::handleButton(uint8_t b) {
buttons[b].pressedBefore = false;
buttons[b].longPressed = false;
}
// if 350ms elapsed since last press/release it is a short press
if (buttons[b].waitTime && now - buttons[b].waitTime > 350 && !buttons[b].pressedBefore) {
// if double press time elapsed since last 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); //not exposed
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
Expand Down
17 changes: 5 additions & 12 deletions usermods/pixels_dice_tray/pixels_dice_tray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,6 @@
#define BLE_TIME_BETWEEN_SCANS_SEC 5
#endif

#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

class PixelsDiceTrayUsermod : public Usermod {
private:
bool enabled = true;
Expand Down Expand Up @@ -482,15 +475,15 @@ class PixelsDiceTrayUsermod : public Usermod {
}
buttons[b].pressedBefore = true;

if (now - buttons[b].pressedTime > WLED_LONG_PRESS) { //long press
if (now - buttons[b].pressedTime > buttonLongPressMs) { //long press
menu_ctrl.HandleButton(ButtonType::LONG, b);
buttons[b].longPressed = true;
return true;
}
} else if (!isButtonPressed(b) && buttons[b].pressedBefore) { //released

long dur = now - buttons[b].pressedTime;
if (dur < WLED_DEBOUNCE_THRESHOLD) {
if (dur < buttonDebounceMs) {
buttons[b].pressedBefore = false;
return true;
} //too short "press", debounce
Expand All @@ -499,7 +492,7 @@ class PixelsDiceTrayUsermod : public Usermod {
buttons[b].waitTime = 0;

if (!buttons[b].longPressed) { //short press
// if this is second release within 350ms it is a double press (buttonWaitTime!=0)
// if this is second release within double press time it is a double press (buttonWaitTime!=0)
if (doublePress) {
menu_ctrl.HandleButton(ButtonType::DOUBLE, b);
} else {
Expand All @@ -509,8 +502,8 @@ class PixelsDiceTrayUsermod : public Usermod {
buttons[b].pressedBefore = false;
buttons[b].longPressed = false;
}
// if 350ms elapsed since last press/release it is a short press
if (buttons[b].waitTime && now - buttons[b].waitTime > WLED_DOUBLE_PRESS &&
// if double press time elapsed since last press/release it is a short press
if (buttons[b].waitTime && now - buttons[b].waitTime > buttonDoublePressMs &&
!buttons[b].pressedBefore) {
buttons[b].waitTime = 0;
menu_ctrl.HandleButton(ButtonType::SINGLE, b);
Expand Down
33 changes: 17 additions & 16 deletions wled00/button.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
* 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
#define WLED_LONG_BRI_STEPS 16 // how much to increase/decrease the brightness with each long press repetition

static_assert(WLED_LONG_PRESS_MAX < WLED_LONG_AP, "long press must not reach the AP-mode/factory-reset hold time of button 0");
static_assert(WLED_LONG_AP < WLED_LONG_FACTORY_RESET, "AP mode hold must be shorter than factory reset hold");

static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage

// Runtime state private to this file - previously WLED_GLOBAL, a leftover from
Expand Down Expand Up @@ -136,7 +135,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);
Expand Down Expand Up @@ -304,27 +303,29 @@ 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 (!buttons[b].longPressed) {
// first long press fires after buttonLongPressMs, repeats are timed from the last action
if (!buttons[b].longPressed) {
if (now - buttons[b].pressedTime > buttonLongPressMs) { //long press
buttonBriDirection = !buttonBriDirection; //toggle brightness direction on long press
longPressAction(b);
} else if (b) { //repeatable action (~5 times per s) on button > 0
longPressAction(b);
buttons[b].pressedTime = now - WLED_LONG_REPEATED_ACTION; //200ms
if (b) buttons[b].pressedTime = now; // pause before first repeat; button 0 keeps press start for AP timing
buttons[b].longPressed = true;
}
buttons[b].longPressed = true;
} else if (b && now - buttons[b].pressedTime > buttonRepeatDelayMs) { //repeatable action on button > 0
longPressAction(b);
buttons[b].pressedTime = now - (buttonRepeatDelayMs - buttonRepeatIntervalMs); // next repeat after buttonRepeatIntervalMs (delay >= interval is enforced where settings are read)
}

} else if (buttons[b].pressedBefore) { //released
long dur = now - buttons[b].pressedTime;

// 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].longPressed) {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;

Expand All @@ -339,7 +340,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 {
Expand All @@ -351,8 +352,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);
}
Expand Down
17 changes: 17 additions & 0 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,18 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}

CJSON(buttonPublishMqtt, btn_obj["mqtt"]);
// CJSON keeps the previous value if the JSON value doesn't fit uint16_t (e.g. negative), so no wraparound before the clamps
CJSON(buttonDebounceMs, btn_obj[F("dbnc")]);
CJSON(buttonLongPressMs, btn_obj[F("lp")]);
CJSON(buttonDoublePressMs, btn_obj[F("dp")]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
CJSON(buttonRepeatDelayMs, btn_obj[F("brpd")]);
CJSON(buttonRepeatIntervalMs, btn_obj[F("brpi")]);
buttonDebounceMs = min(buttonDebounceMs, (uint16_t)WLED_DEBOUNCE_MAX);
buttonLongPressMs = constrain(buttonLongPressMs, WLED_LONG_PRESS_MIN, WLED_LONG_PRESS_MAX);
buttonDoublePressMs = constrain(buttonDoublePressMs, WLED_DOUBLE_PRESS_MIN, WLED_DOUBLE_PRESS_MAX);
buttonRepeatIntervalMs = constrain(buttonRepeatIntervalMs, WLED_LONG_REPEAT_INTERVAL_MIN, WLED_LONG_REPEAT_INTERVAL_MAX);
buttonRepeatDelayMs = constrain(buttonRepeatDelayMs, WLED_LONG_REPEAT_DELAY_MIN, WLED_LONG_REPEAT_DELAY_MAX);
buttonRepeatDelayMs = max(buttonRepeatDelayMs, buttonRepeatIntervalMs); // repeat delay must be at least the repeat interval

#ifndef WLED_DISABLE_INFRARED
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
Expand Down Expand Up @@ -1048,6 +1060,11 @@ 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;
hw_btn[F("brpd")] = buttonRepeatDelayMs;
hw_btn[F("brpi")] = buttonRepeatIntervalMs;

JsonObject hw_ir = hw.createNestedObject("ir");
#ifndef WLED_DISABLE_INFRARED
Expand Down
16 changes: 16 additions & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,22 @@ 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_MAX 100 // settings limits for button timings (buttonDebounceMs, buttonLongPressMs, buttonDoublePressMs, buttonRepeatDelayMs, buttonRepeatIntervalMs)
#define WLED_LONG_PRESS_MIN 200
#define WLED_LONG_PRESS_MAX 4000 // must stay below WLED_LONG_AP (button.cpp)
#define WLED_DOUBLE_PRESS_MIN 100
#define WLED_DOUBLE_PRESS_MAX 1000
static_assert(WLED_DEBOUNCE_MAX > 0, "debounce max must be positive");
static_assert(WLED_LONG_PRESS_MIN < WLED_LONG_PRESS_MAX, "long press min must be below max");
static_assert(WLED_DOUBLE_PRESS_MIN < WLED_DOUBLE_PRESS_MAX, "double press min must be below max");
static_assert(WLED_LONG_PRESS_MIN > WLED_DEBOUNCE_MAX, "long press must be longer than the debounce time");
Comment thread
willmmiles marked this conversation as resolved.
#define WLED_LONG_REPEAT_DELAY_MIN 100 // buttonRepeatDelayMs must also be >= buttonRepeatIntervalMs (enforced where settings are read)
#define WLED_LONG_REPEAT_DELAY_MAX 4000
#define WLED_LONG_REPEAT_INTERVAL_MIN 100
#define WLED_LONG_REPEAT_INTERVAL_MAX 4000
static_assert(WLED_LONG_REPEAT_DELAY_MIN < WLED_LONG_REPEAT_DELAY_MAX, "repeat delay min must be below max");
static_assert(WLED_LONG_REPEAT_INTERVAL_MIN < WLED_LONG_REPEAT_INTERVAL_MAX, "repeat interval min must be below max");
static_assert(WLED_LONG_REPEAT_INTERVAL_MAX <= WLED_LONG_REPEAT_DELAY_MAX, "raising the repeat delay to the repeat interval must stay within the delay range");
// Size of buffer for API JSON object (increase for more segments)
#ifdef ESP8266
#define JSON_BUFFER_SIZE 10240
Expand Down
25 changes: 22 additions & 3 deletions wled00/data/settings_leds.htm
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
function S() {
getLoc();
if(localStorage.getItem('ASc')==='true') d.Sf.AS.checked=true;
if(localStorage.getItem('btnASc')==='true') gId('btnAS').checked=true;
fetch(getURL('/json/info'))
.then(r => r.ok ? r.json() : {})
.then(info => { hasPSRAM = Number(info.psrSz ?? 0) > 0 || Number(info.psram ?? 0) > 0; })
Expand Down Expand Up @@ -509,6 +510,11 @@
gId('psu').innerHTML = s;
gId('psu2').innerHTML = s2;
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
// advanced button settings only when enabled, or if invalid (a hidden invalid field would silently block saving)
// long-press repeat delay must be at least the repeat interval (blocks saving an invalid pair)
d.Sf.BRPD.setCustomValidity(parseInt(d.Sf.BRPD.value) < parseInt(d.Sf.BRPI.value) ? "Must not be shorter than the long-press repeat interval" : "");
let btInv = [...gId("btnAdv").querySelectorAll("input")].some(e => !e.checkValidity());
gId("btnAdv").style.display = (gId("btnAS").checked || btInv) ? "" : "none";

// show/hide FPS warning messages
gId('fpsNone').style.display = (d.Sf.FR.value == 0) ? 'block':'none';
Expand Down Expand Up @@ -800,6 +806,11 @@
addBtn(i,v.pin[0],v.type);
});
d.getElementsByName("TT")[0].value = b.tt;
if (b.dbnc !== undefined) d.getElementsByName("DB")[0].value = b.dbnc;
if (b.lp !== undefined) d.getElementsByName("LP")[0].value = b.lp;
if (b.dp !== undefined) d.getElementsByName("DP")[0].value = b.dp;
Comment thread
adamsthws marked this conversation as resolved.
if (b.brpd !== undefined) d.getElementsByName("BRPD")[0].value = b.brpd;
if (b.brpi !== undefined) d.getElementsByName("BRPI")[0].value = b.brpi;
}
let ir = c.hw.ir;
if (ir) {
Expand Down Expand Up @@ -1088,12 +1099,20 @@ <h2>Hardware setup</h2>
<h3>Buttons</h3>
<div id="btn_wrap">
<div id="btns"></div>
<hr class="sml">
<button type="button" id="btn_add" onclick="addBtn(gId('btns').children.length,-1,0)">+</button>
<button type="button" id="btn_rem" onclick="remBtn()">-</button>
</div>
Disable internal pull-up/down: <input type="checkbox" name="IP"><br>
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br><br>
<hr class="sml">
Show Advanced Button Settings <input type="checkbox" id="btnAS" onchange="localStorage.setItem('btnASc',this.checked);UI()"><br>
Comment thread
adamsthws marked this conversation as resolved.
<div id="btnAdv" style="display:none;margin-top:20px">
Disable internal pull-up/down: <input type="checkbox" name="IP"><br>
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
Debounce time: <input type="number" class="s" min="0" max="100" name="DB" required> ms<br>
Double-press time: <input type="number" class="s" min="100" max="1000" name="DP" required> ms<br>
Long-press time: <input type="number" class="s" min="200" max="4000" name="LP" required> ms<br>
Long-press repeat delay: <input type="number" class="s" min="100" max="4000" name="BRPD" oninput="UI()" required> ms<br>
Long-press repeat interval: <input type="number" class="s" min="100" max="4000" name="BRPI" oninput="UI()" required> ms<br>
</div><br>
</div>
<div class="sec">
<h3>IR Remote</h3>
Expand Down
6 changes: 6 additions & 0 deletions wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)

disablePullUp = (bool)request->hasArg(F("IP"));
touchThreshold = request->arg(F("TT")).toInt();
if (request->hasArg(F("DB"))) buttonDebounceMs = constrain(request->arg(F("DB")).toInt(), 0, WLED_DEBOUNCE_MAX);
if (request->hasArg(F("LP"))) buttonLongPressMs = constrain(request->arg(F("LP")).toInt(), WLED_LONG_PRESS_MIN, WLED_LONG_PRESS_MAX);
if (request->hasArg(F("DP"))) buttonDoublePressMs = constrain(request->arg(F("DP")).toInt(), WLED_DOUBLE_PRESS_MIN, WLED_DOUBLE_PRESS_MAX);
if (request->hasArg(F("BRPI"))) buttonRepeatIntervalMs = constrain(request->arg(F("BRPI")).toInt(), WLED_LONG_REPEAT_INTERVAL_MIN, WLED_LONG_REPEAT_INTERVAL_MAX);
if (request->hasArg(F("BRPD"))) buttonRepeatDelayMs = constrain(request->arg(F("BRPD")).toInt(), WLED_LONG_REPEAT_DELAY_MIN, WLED_LONG_REPEAT_DELAY_MAX);
buttonRepeatDelayMs = max(buttonRepeatDelayMs, buttonRepeatIntervalMs); // repeat delay must be at least the repeat interval
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)
Expand Down
5 changes: 5 additions & 0 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,11 @@ WLED_GLOBAL std::vector<Button> buttons; // vector of button structs
WLED_GLOBAL bool buttonPublishMqtt _INIT(false);
WLED_GLOBAL bool disablePullUp _INIT(false);
WLED_GLOBAL byte touchThreshold _INIT(TOUCH_THRESHOLD);
WLED_GLOBAL uint16_t buttonDebounceMs _INIT(50); // only consider button input of at least this long as valid (debouncing)
WLED_GLOBAL uint16_t buttonLongPressMs _INIT(600); // long press if button is held for at least this long
WLED_GLOBAL uint16_t buttonDoublePressMs _INIT(350); // double press if another press follows a short press within this time
WLED_GLOBAL uint16_t buttonRepeatDelayMs _INIT(600); // pause after the first long-press action before it repeats (buttons >0)
WLED_GLOBAL uint16_t buttonRepeatIntervalMs _INIT(200); // interval between repeated long-press actions (buttons >0)

// notifications
WLED_GLOBAL bool sendNotifications _INIT(false); // master notification switch
Expand Down
5 changes: 5 additions & 0 deletions wled00/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,11 @@ void getSettingsJS(byte subPage, Print& settingsScript)
}
printSetFormCheckbox(settingsScript,PSTR("IP"),disablePullUp);
printSetFormValue(settingsScript,PSTR("TT"),touchThreshold);
printSetFormValue(settingsScript,PSTR("DB"),buttonDebounceMs);
printSetFormValue(settingsScript,PSTR("LP"),buttonLongPressMs);
printSetFormValue(settingsScript,PSTR("DP"),buttonDoublePressMs);
printSetFormValue(settingsScript,PSTR("BRPD"),buttonRepeatDelayMs);
printSetFormValue(settingsScript,PSTR("BRPI"),buttonRepeatIntervalMs);
#ifndef WLED_DISABLE_INFRARED
printSetFormValue(settingsScript,PSTR("IR"),irPin);
printSetFormValue(settingsScript,PSTR("IT"),irEnabled);
Expand Down
Loading