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\" ></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";
424569const char MultiRelay::_delay_str[] PROGMEM = " delay-s" ;
425570const char MultiRelay::_activeHigh[] PROGMEM = " active-high" ;
426571const char MultiRelay::_external[] PROGMEM = " external" ;
572+ const char MultiRelay::_button[] PROGMEM = " button" ;
0 commit comments