Skip to content

Commit a93f05c

Browse files
Multirelay button support. (wled#2284)
* Multirelay button support. Added button hook for usermods. * Added MultiRelay relay states to JSON state object * Move button timings to constants No delay waiting for double press on button 0 if no macro set Co-authored-by: cschwinne <[email protected]>
1 parent 0023824 commit a93f05c

File tree

5 files changed

+247
-57
lines changed

5 files changed

+247
-57
lines changed

usermods/multi_relay/readme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ Examples
1616
1. 4 relays at all, relay 2 will be toggled: `http://[device-ip]/relays?toggle=0,1,0,0`
1717
2. 3 relays at all, relay 1&3 will be switched on: `http://[device-ip]/relays?switch=1,0,1`
1818

19+
## JSON API
20+
You can switch relay state using the following JSON object transmitted to: `http://[device-ip]/json`
21+
22+
Switch relay 0 on: `{"MultiRelay":{"relay":0,"on":true}}`
23+
Switch relay4 3 & 4 off: `{"MultiRelay":[{"relay":2,"on":false},{"relay":3,"on":false}]}`
24+
1925
## MQTT API
2026

2127
wled/deviceMAC/relay/0/command on|off|toggle

usermods/multi_relay/usermod_multi_relay.h

Lines changed: 159 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#define MULTI_RELAY_MAX_RELAYS 4
77
#endif
88

9+
#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
10+
911
#define ON true
1012
#define OFF false
1113

@@ -23,6 +25,7 @@ typedef struct relay_t {
2325
bool state;
2426
bool external;
2527
uint16_t delay;
28+
int8_t button;
2629
} Relay;
2730

2831

@@ -35,7 +38,7 @@ class MultiRelay : public Usermod {
3538
// switch timer start time
3639
uint32_t _switchTimerStart = 0;
3740
// old brightness
38-
bool _oldBrightness = 0;
41+
bool _oldMode;
3942

4043
// usermod enabled
4144
bool enabled = false; // needs to be configured (no default config)
@@ -49,6 +52,7 @@ class MultiRelay : public Usermod {
4952
static const char _delay_str[];
5053
static const char _activeHigh[];
5154
static const char _external[];
55+
static const char _button[];
5256

5357

5458
void publishMqtt(const char* state, int relay) {
@@ -170,6 +174,7 @@ class MultiRelay : public Usermod {
170174
_relay[i].active = false;
171175
_relay[i].state = false;
172176
_relay[i].external = false;
177+
_relay[i].button = -1;
173178
}
174179
}
175180
/**
@@ -261,11 +266,12 @@ class MultiRelay : public Usermod {
261266
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
262267
_relay[i].pin = -1; // allocation failed
263268
} else {
264-
switchRelay(i, _relay[i].state = (bool)bri);
269+
if (!_relay[i].external) _relay[i].state = offMode;
270+
switchRelay(i, _relay[i].state);
265271
_relay[i].active = false;
266272
}
267273
}
268-
_oldBrightness = (bool)bri;
274+
_oldMode = offMode;
269275
initDone = true;
270276
}
271277

@@ -281,24 +287,110 @@ class MultiRelay : public Usermod {
281287
* loop() is called continuously. Here you can check for events, read sensors, etc.
282288
*/
283289
void loop() {
290+
yield();
284291
if (!enabled || strip.isUpdating()) return;
285292

286293
static unsigned long lastUpdate = 0;
287-
if (millis() - lastUpdate < 200) return; // update only 5 times/s
294+
if (millis() - lastUpdate < 100) return; // update only 10 times/s
288295
lastUpdate = millis();
289296

290297
//set relay when LEDs turn on
291-
if (_oldBrightness != (bool)bri) {
292-
_oldBrightness = (bool)bri;
298+
if (_oldMode != offMode) {
299+
_oldMode = offMode;
293300
_switchTimerStart = millis();
294301
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
295-
if (_relay[i].pin>=0) _relay[i].active = true;
302+
if (_relay[i].pin>=0 && !_relay[i].external) _relay[i].active = true;
296303
}
297304
}
298305

299306
handleOffTimer();
300307
}
301308

309+
/**
310+
* handleButton() can be used to override default button behaviour. Returning true
311+
* will prevent button working in a default way.
312+
* Replicating button.cpp
313+
*/
314+
bool handleButton(uint8_t b) {
315+
yield();
316+
if (buttonType[b] == BTN_TYPE_NONE || buttonType[b] == BTN_TYPE_RESERVED || buttonType[b] == BTN_TYPE_PIR_SENSOR || buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
317+
return false;
318+
}
319+
320+
bool handled = false;
321+
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
322+
if (_relay[i].button == b) {
323+
handled = true;
324+
}
325+
}
326+
if (!handled) return false;
327+
328+
unsigned long now = millis();
329+
330+
//button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
331+
if (buttonType[b] == BTN_TYPE_SWITCH) {
332+
//handleSwitch(b);
333+
if (buttonPressedBefore[b] != isButtonPressed(b)) {
334+
buttonPressedTime[b] = now;
335+
buttonPressedBefore[b] = !buttonPressedBefore[b];
336+
}
337+
338+
if (buttonLongPressed[b] == buttonPressedBefore[b]) return handled;
339+
340+
if (now - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
341+
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
342+
if (_relay[i].pin>=0 && _relay[i].button == b) {
343+
switchRelay(i, buttonPressedBefore[b]);
344+
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
345+
}
346+
}
347+
}
348+
return handled;
349+
}
350+
351+
//momentary button logic
352+
if (isButtonPressed(b)) { //pressed
353+
354+
if (!buttonPressedBefore[b]) buttonPressedTime[b] = now;
355+
buttonPressedBefore[b] = true;
356+
357+
if (now - buttonPressedTime[b] > 600) { //long press
358+
buttonLongPressed[b] = true;
359+
}
360+
361+
} else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
362+
363+
long dur = now - buttonPressedTime[b];
364+
if (dur < WLED_DEBOUNCE_THRESHOLD) {
365+
buttonPressedBefore[b] = false;
366+
return handled;
367+
} //too short "press", debounce
368+
bool doublePress = buttonWaitTime[b]; //did we have short press before?
369+
buttonWaitTime[b] = 0;
370+
371+
if (!buttonLongPressed[b]) { //short press
372+
// if this is second release within 350ms it is a double press (buttonWaitTime!=0)
373+
if (doublePress) {
374+
//doublePressAction(b);
375+
} else {
376+
buttonWaitTime[b] = now;
377+
}
378+
}
379+
buttonPressedBefore[b] = false;
380+
buttonLongPressed[b] = false;
381+
}
382+
// if 450ms elapsed since last press/release it is a short press
383+
if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) {
384+
buttonWaitTime[b] = 0;
385+
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
386+
if (_relay[i].pin>=0 && _relay[i].button == b) {
387+
toggleRelay(i);
388+
}
389+
}
390+
}
391+
return handled;
392+
}
393+
302394
/**
303395
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
304396
*/
@@ -310,22 +402,73 @@ class MultiRelay : public Usermod {
310402

311403
JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name
312404
infoArr.add(String(getActiveRelayCount()));
405+
406+
String uiDomString;
407+
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
408+
if (_relay[i].pin<0 || !_relay[i].external) continue;
409+
uiDomString = F("<button class=\"btn\" onclick=\"requestJson({");
410+
uiDomString += FPSTR(_name);
411+
uiDomString += F(":{");
412+
uiDomString += FPSTR(_relay_str);
413+
uiDomString += F(":");
414+
uiDomString += i;
415+
uiDomString += F(",on:");
416+
uiDomString += _relay[i].state ? "false" : "true";
417+
uiDomString += F("}});\">");
418+
uiDomString += F("Relay ");
419+
uiDomString += i;
420+
uiDomString += F(" <i class=\"icons\">&#xe08f;</i></button>");
421+
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
422+
423+
infoArr.add(_relay[i].state ? "on" : "off");
424+
}
313425
}
314426
}
315427

316428
/**
317429
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
318430
* Values in the state object may be modified by connected clients
319431
*/
320-
//void addToJsonState(JsonObject &root) {
321-
//}
432+
void addToJsonState(JsonObject &root) {
433+
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
434+
JsonObject multiRelay = root[FPSTR(_name)];
435+
if (multiRelay.isNull()) {
436+
multiRelay = root.createNestedObject(FPSTR(_name));
437+
}
438+
#if MULTI_RELAY_MAX_RELAYS > 1
439+
JsonArray rel_arr = multiRelay.createNestedArray(F("relays"));
440+
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
441+
if (_relay[i].pin < 0) continue;
442+
JsonObject relay = rel_arr.createNestedObject();
443+
relay[FPSTR(_relay_str)] = i;
444+
relay[F("state")] = _relay[i].state;
445+
}
446+
#else
447+
multiRelay[FPSTR(_relay_str)] = 0;
448+
multiRelay[F("state")] = _relay[0].state;
449+
#endif
450+
}
322451

323452
/**
324453
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
325454
* Values in the state object may be modified by connected clients
326455
*/
327-
//void readFromJsonState(JsonObject &root) {
328-
//}
456+
void readFromJsonState(JsonObject &root) {
457+
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
458+
JsonObject usermod = root[FPSTR(_name)];
459+
if (!usermod.isNull()) {
460+
if (usermod["on"].is<bool>() && usermod[FPSTR(_relay_str)].is<int>() && usermod[FPSTR(_relay_str)].as<int>()>=0) {
461+
switchRelay(usermod[FPSTR(_relay_str)].as<int>(), usermod["on"].as<bool>());
462+
}
463+
} else if (root[FPSTR(_name)].is<JsonArray>()) {
464+
JsonArray relays = root[FPSTR(_name)].as<JsonArray>();
465+
for (JsonVariant r : relays) {
466+
if (r["on"].is<bool>() && r[FPSTR(_relay_str)].is<int>() && r[FPSTR(_relay_str)].as<int>()>=0) {
467+
switchRelay(r[FPSTR(_relay_str)].as<int>(), r["on"].as<bool>());
468+
}
469+
}
470+
}
471+
}
329472

330473
/**
331474
* provide the changeable values
@@ -341,6 +484,7 @@ class MultiRelay : public Usermod {
341484
relay[FPSTR(_activeHigh)] = _relay[i].mode;
342485
relay[FPSTR(_delay_str)] = _relay[i].delay;
343486
relay[FPSTR(_external)] = _relay[i].external;
487+
relay[FPSTR(_button)] = _relay[i].button;
344488
}
345489
DEBUG_PRINTLN(F("MultiRelay config saved."));
346490
}
@@ -370,6 +514,7 @@ class MultiRelay : public Usermod {
370514
_relay[i].mode = top[parName][FPSTR(_activeHigh)] | _relay[i].mode;
371515
_relay[i].external = top[parName][FPSTR(_external)] | _relay[i].external;
372516
_relay[i].delay = top[parName][FPSTR(_delay_str)] | _relay[i].delay;
517+
_relay[i].button = top[parName][FPSTR(_button)] | _relay[i].button;
373518
// begin backwards compatibility (beta) remove when 0.13 is released
374519
parName += '-';
375520
_relay[i].pin = top[parName+"pin"] | _relay[i].pin;
@@ -394,7 +539,7 @@ class MultiRelay : public Usermod {
394539
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
395540
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
396541
if (!_relay[i].external) {
397-
switchRelay(i, _relay[i].state = (bool)bri);
542+
switchRelay(i, offMode);
398543
}
399544
} else {
400545
_relay[i].pin = -1;
@@ -404,7 +549,7 @@ class MultiRelay : public Usermod {
404549
DEBUG_PRINTLN(F(" config (re)loaded."));
405550
}
406551
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
407-
return !top[F("relay-0")]["pin"].isNull();
552+
return !top[F("relay-0")][FPSTR(_button)].isNull();
408553
}
409554

410555
/**
@@ -424,3 +569,4 @@ const char MultiRelay::_relay_str[] PROGMEM = "relay";
424569
const char MultiRelay::_delay_str[] PROGMEM = "delay-s";
425570
const char MultiRelay::_activeHigh[] PROGMEM = "active-high";
426571
const char MultiRelay::_external[] PROGMEM = "external";
572+
const char MultiRelay::_button[] PROGMEM = "button";

0 commit comments

Comments
 (0)