muwerk mupplet Core Library
muwerk applets; mupplets: functional units that support specific hardware or reusable applications
Loading...
Searching...
No Matches
mup_switch.h
1// mup_switch.h - muwerk switch applet
2#pragma once
3
4#include "scheduler.h"
5
6namespace ustd {
7
8#if defined(__ESP32__) || defined(__ESP__)
9#define G_INT_ATTR IRAM_ATTR
10#else
11#define G_INT_ATTR
12#endif
13
14#define USTD_SW_MAX_IRQS (10)
15
16volatile unsigned long pSwIrqCounter[USTD_SW_MAX_IRQS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
17volatile unsigned long pSwLastIrq[USTD_SW_MAX_IRQS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
18volatile unsigned long pSwDebounceMs[USTD_SW_MAX_IRQS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
19
20void G_INT_ATTR ustd_sw_irq_master(uint8_t irqno) {
21 unsigned long curr = millis();
22 noInterrupts();
23 if (pSwDebounceMs[irqno]) {
24 if (timeDiff(pSwLastIrq[irqno], curr) < pSwDebounceMs[irqno]) {
25 interrupts();
26 return;
27 }
28 }
29 ++pSwIrqCounter[irqno];
30 pSwLastIrq[irqno] = curr;
31 interrupts();
32}
33
34void G_INT_ATTR ustd_sw_irq0() {
35 ustd_sw_irq_master(0);
36}
37void G_INT_ATTR ustd_sw_irq1() {
38 ustd_sw_irq_master(1);
39}
40void G_INT_ATTR ustd_sw_irq2() {
41 ustd_sw_irq_master(2);
42}
43void G_INT_ATTR ustd_sw_irq3() {
44 ustd_sw_irq_master(3);
45}
46void G_INT_ATTR ustd_sw_irq4() {
47 ustd_sw_irq_master(4);
48}
49void G_INT_ATTR ustd_sw_irq5() {
50 ustd_sw_irq_master(5);
51}
52void G_INT_ATTR ustd_sw_irq6() {
53 ustd_sw_irq_master(6);
54}
55void G_INT_ATTR ustd_sw_irq7() {
56 ustd_sw_irq_master(7);
57}
58void G_INT_ATTR ustd_sw_irq8() {
59 ustd_sw_irq_master(8);
60}
61void G_INT_ATTR ustd_sw_irq9() {
62 ustd_sw_irq_master(9);
63}
64
65void (*ustd_sw_irq_table[USTD_SW_MAX_IRQS])() = {ustd_sw_irq0, ustd_sw_irq1, ustd_sw_irq2, ustd_sw_irq3, ustd_sw_irq4,
66 ustd_sw_irq5, ustd_sw_irq6, ustd_sw_irq7, ustd_sw_irq8, ustd_sw_irq9};
67
68unsigned long getSwResetIrqCount(uint8_t irqno) {
69 unsigned long count = (unsigned long)-1;
70 noInterrupts();
71 if (irqno < USTD_SW_MAX_IRQS) {
72 count = pSwIrqCounter[irqno];
73 pSwIrqCounter[irqno] = 0;
74 }
75 interrupts();
76 return count;
77}
78
79// clang-format off
135// clang-format on
136class Switch {
137 public:
138 static const char *version; // = "0.1.0";
140 enum Mode {
141 Default,
143 Rising,
145 Falling,
149 Duration,
152 };
153
154 private:
155 Scheduler *pSched;
156 int tID;
157
158 String name;
159 uint8_t port;
160 Mode mode;
161 bool activeLogic;
162 String customTopic;
163 int8_t interruptIndex;
164 unsigned long debounceTimeMs;
165
166 bool useInterrupt = false;
167 uint8_t ipin = 255;
168 unsigned long lastChangeMs = 0;
169 int physicalState = -1;
170 int logicalState = -1;
171 bool overriddenPhysicalState = false;
172 bool overridePhysicalActive = false;
173
174 bool bCounter = false;
175 unsigned long counter = 0;
176
177 bool flipflop = true; // This starts with 'off', since state is initially changed once.
178 unsigned long activeTimer = 0;
179 unsigned long timerDuration = 1000; // ms
180 unsigned long startEvent = 0; // ms
181 unsigned long durations[2] = {3000, 30000};
182
183 unsigned long lastStatePublish = 0;
184 unsigned int stateRefresh = 0;
185 bool initialStatePublish = false;
186 bool initialStateIsPublished = false;
187
188 public:
189 Switch(String name, uint8_t port, Mode mode = Mode::Default, bool activeLogic = false,
190 String customTopic = "", int8_t interruptIndex = -1, unsigned long debounceTimeMs = 0)
191 : name(name), port(port), mode(mode), activeLogic(activeLogic), customTopic(customTopic),
192 interruptIndex(interruptIndex), debounceTimeMs(debounceTimeMs) {
207 }
208
209 virtual ~Switch() {
210 if (useInterrupt)
211 detachInterrupt(ipin);
212 }
213
214 void setDebounce(long ms) {
219 if (ms < 0)
220 ms = 0;
221 if (ms > 1000)
222 ms = 1000;
223 debounceTimeMs = (unsigned long)ms;
224 }
225
226 void activateCounter(bool bActive) {
231 bCounter = bActive;
232 if (bCounter) {
233 counter = 0;
234 publishCounter();
235 }
236 }
237
238 void setTimerDuration(unsigned long ms) {
245 timerDuration = ms;
246 }
247
248 void setMode(Mode newmode, unsigned long duration = 0) {
255 if (useInterrupt)
256 flipflop = false; // This starts with 'off', since state is
257 // initially changed once.
258 else
259 flipflop = true; // This starts with 'off', since state is
260 // initially changed once.
261 activeTimer = 0;
262 timerDuration = duration;
263 physicalState = -1;
264 logicalState = -1;
265 overriddenPhysicalState = false;
266 overridePhysicalActive = false;
267 lastChangeMs = 0;
268 mode = newmode;
269 if (mode == Mode::BinarySensor) {
270 initialStateIsPublished = false;
271 initialStatePublish = true;
272 stateRefresh = 600;
273 }
274 startEvent = (unsigned long)-1;
275 }
276
277 void begin(Scheduler *_pSched) {
280 pSched = _pSched;
281
282 pinMode(port, INPUT_PULLUP);
283 setMode(mode);
284
285 if (interruptIndex >= 0 && interruptIndex < USTD_SW_MAX_IRQS) {
286 ipin = digitalPinToInterrupt(port);
287 switch (mode) {
288 case Mode::Falling:
289 attachInterrupt(ipin, ustd_sw_irq_table[interruptIndex], FALLING);
290 break;
291 case Mode::Rising:
292 attachInterrupt(ipin, ustd_sw_irq_table[interruptIndex], RISING);
293 break;
294 default:
295 attachInterrupt(ipin, ustd_sw_irq_table[interruptIndex], CHANGE);
296 break;
297 }
298 pSwDebounceMs[interruptIndex] = debounceTimeMs;
299 useInterrupt = true;
300 }
301
302 readState();
303
304 auto ft = [=]() { this->loop(); };
305 tID = pSched->add(ft, name, 50000);
306
307 auto fnall = [=](String topic, String msg, String originator) {
308 this->subsMsg(topic, msg, originator);
309 };
310 pSched->subscribe(tID, name + "/#", fnall);
311 pSched->subscribe(tID, "mqtt/state", fnall);
312 }
313
314 void setLogicalState(bool newLogicalState) {
322 if (logicalState != newLogicalState) {
323 logicalState = newLogicalState;
324 publishLogicalState(logicalState);
325 if (bCounter) {
326 if (logicalState) {
327 counter += 1;
328 publishCounter();
329 }
330 }
331 }
332 }
333
334 void setStateRefresh(int logicalStateRefreshEverySecs) {
338 stateRefresh = logicalStateRefreshEverySecs;
339 }
340
341 void setToggle() {
347 setPhysicalState(!physicalState, true);
348 }
349
350 void setPulse() {
356 setPhysicalState(true, true);
357 setPhysicalState(false, true);
358 }
359
360 private:
361 unsigned long getTimestamp() {
363#if defined(__ARDUINO__) || defined(__ARM__) || defined(__RISC_V__)
364 return pSched->getUptime();
365#else
366 return (unsigned long)time(nullptr);
367#endif
368 }
369
370 void publishCounter() {
371 char buf[32];
372 sprintf(buf, "%ld", counter);
373 if (bCounter) {
374 pSched->publish(name + "/switch/counter", buf);
375 pSched->publish(name + "/sensor/counter", buf);
376 } else {
377 pSched->publish(name + "/switch/counter", "NaN");
378 pSched->publish(name + "/sensor/counter", "NaN");
379 }
380 }
381
382 void publishLogicalState(bool lState) {
383 String textState;
384 String binaryState;
385 lastStatePublish = getTimestamp();
386 if (lState == true) {
387 textState = "on";
388 binaryState = "ON";
389 } else {
390 textState = "off";
391 binaryState = "OFF";
392 }
393 switch (mode) {
394 case Mode::Default:
395 case Mode::Flipflop:
396 case Mode::Timer:
397 pSched->publish(name + "/switch/state", textState);
398 if (customTopic != "")
399 pSched->publish(customTopic, textState);
400 break;
401 case Mode::Rising:
402 if (lState == true) {
403 pSched->publish(name + "/switch/state", "trigger");
404 if (customTopic != "")
405 pSched->publish(customTopic, "trigger");
406 }
407 break;
408 case Mode::Falling:
409 if (lState == false) {
410 pSched->publish(name + "/switch/state", "trigger");
411 if (customTopic != "")
412 pSched->publish(customTopic, "trigger");
413 }
414 break;
415 case Mode::Duration:
416 if (lState == true) {
417 startEvent = millis();
418 } else {
419 if (startEvent != (unsigned long)-1) {
420 unsigned long dt = timeDiff(startEvent, millis());
421 char msg[32];
422 sprintf(msg, "%ld", dt);
423 pSched->publish(name + "/switch/duration", msg);
424 if (dt < durations[0]) {
425 pSched->publish(name + "/switch/shortpress", "trigger");
426 } else if (dt < durations[1]) {
427 pSched->publish(name + "/switch/longpress", "trigger");
428 } else {
429 pSched->publish(name + "/switch/verylongpress", "trigger");
430 }
431 }
432 }
433 break;
435 pSched->publish(name + "/binary_sensor/state", binaryState);
436 if (customTopic != "")
437 pSched->publish(customTopic, binaryState);
438 break;
439 }
440 }
441
442 void decodeLogicalState(bool physicalState) {
443 switch (mode) {
444 case Mode::Default:
445 case Mode::Rising:
446 case Mode::Falling:
447 case Mode::Duration:
449 setLogicalState(physicalState);
450 break;
451 case Mode::Flipflop:
452 if (physicalState == false) {
453 flipflop = !flipflop;
454 setLogicalState(flipflop);
455 }
456 break;
457 case Mode::Timer:
458 if (physicalState == false) {
459 activeTimer = millis();
460 } else {
461 setLogicalState(true);
462 }
463 break;
464 }
465 }
466
467 void setPhysicalState(bool newState, bool override) {
468 if (mode != Mode::Timer) {
469 activeTimer = 0;
470 }
471 if (override) {
472 overriddenPhysicalState = physicalState;
473 overridePhysicalActive = true;
474 if (newState != physicalState) {
475 physicalState = newState;
476 decodeLogicalState(newState);
477 }
478 } else {
479 if (overridePhysicalActive && newState != overriddenPhysicalState) {
480 overridePhysicalActive = false;
481 }
482 if (overridePhysicalActive)
483 return;
484 if (newState != physicalState || mode == Mode::Falling || mode == Mode::Rising) {
485 if (timeDiff(lastChangeMs, millis()) > debounceTimeMs || useInterrupt) {
486 lastChangeMs = millis();
487 physicalState = newState;
488 decodeLogicalState(physicalState);
489 }
490 }
491 }
492 }
493
494 void readState() {
495 if (useInterrupt) {
496 unsigned long count = getSwResetIrqCount(interruptIndex);
497 int curstate = digitalRead(port);
498 char msg[32];
499 if (count) {
500 sprintf(msg, "%ld", count);
501 pSched->publish(name + "/switch/irqcount/0", msg);
502 if (curstate == HIGH)
503 curstate = true;
504 else
505 curstate = false;
506 switch (mode) {
507 case Mode::Rising:
508 for (unsigned long i = 0; i < count; i++) {
509 if (activeLogic) {
510 setPhysicalState(false, false);
511 setPhysicalState(true, false);
512 } else {
513 setPhysicalState(true, false);
514 setPhysicalState(false, false);
515 }
516 }
517 break;
518 case Mode::Falling:
519 for (unsigned long i = 0; i < count; i++) {
520 if (activeLogic) {
521 setPhysicalState(true, false);
522 setPhysicalState(false, false);
523 } else {
524 setPhysicalState(false, false);
525 setPhysicalState(true, false);
526 }
527 }
528 break;
529 default:
530 bool iState = ((count % 2) == 0);
531 if (curstate)
532 iState = !iState;
533 for (unsigned long i = 0; i < count; i++) {
534 if (activeLogic) {
535 setPhysicalState(iState, false);
536 } else {
537 setPhysicalState(!iState, false);
538 }
539 iState = !iState;
540 }
541 break;
542 }
543 }
544 } else {
545 int newstate = digitalRead(port);
546 if (newstate == HIGH)
547 newstate = true;
548 else
549 newstate = false;
550 if (!activeLogic)
551 newstate = !newstate;
552 setPhysicalState(newstate, false);
553 }
554 }
555
556 void loop() {
557 readState();
558 if (mode == Mode::Timer && activeTimer) {
559 if (timeDiff(activeTimer, millis()) > timerDuration) {
560 activeTimer = 0;
561 setLogicalState(false);
562 }
563 }
564 if (stateRefresh != 0 || (initialStateIsPublished = false && initialStatePublish == true)) {
565 if (mode == Mode::BinarySensor) {
566 if (getTimestamp() - lastStatePublish > stateRefresh || (initialStateIsPublished = false && initialStatePublish == true)) {
567 publishLogicalState(logicalState);
568 if (bCounter) publishCounter();
569 initialStateIsPublished = true;
570 }
571 }
572 }
573 }
574
575 void subsMsg(String topic, String msg, String originator) {
576 if (topic == name + "/switch/state/get" || topic == name + "/binary_sensor/state/get") {
577 publishLogicalState(logicalState);
578 } else if (topic == name + "/switch/counter/get" || topic == name + "/sensor/counter/get") {
579 publishCounter();
580 } else if (topic == name + "/switch/physicalstate/get") {
581 char buf[32];
582 if (physicalState)
583 sprintf(buf, "on");
584 else
585 sprintf(buf, "off");
586 pSched->publish(name + "/switch/physicalstate", buf);
587 } else if (topic == name + "/switch/mode/set") {
588 char buf[32];
589 memset(buf, 0, 32);
590 strncpy(buf, msg.c_str(), 31);
591 char *p = strchr(buf, ' ');
592 char *p2 = nullptr;
593 if (p) {
594 *p = 0;
595 ++p;
596 p2 = strchr(p, ',');
597 if (p2) {
598 *p2 = 0;
599 ++p2;
600 }
601 }
602 if (!strcmp(buf, "default")) {
604 } else if (!strcmp(buf, "rising")) {
606 } else if (!strcmp(buf, "falling")) {
608 } else if (!strcmp(buf, "flipflop")) {
610 } else if (!strcmp(buf, "binary_sensor")) {
612 } else if (!strcmp(buf, "timer")) {
613 unsigned long dur = 1000;
614 if (p)
615 dur = atol(p);
616 setMode(Mode::Timer, dur);
617 } else if (!strcmp(buf, "duration")) {
618 durations[0] = 3000;
619 durations[1] = 30000;
620 if (p) {
621 durations[0] = atol(p);
622 if (p2)
623 durations[1] = atol(p2);
624 }
625 if (durations[0] > durations[1]) {
626 durations[1] = (unsigned long)-1;
627 }
629 }
630 } else if (topic == name + "/switch/set") {
631 char buf[32];
632 memset(buf, 0, 32);
633 strncpy(buf, msg.c_str(), 31);
634 if (!strcmp(buf, "on") || !strcmp(buf, "true")) {
635 // setPhysicalState(true, true);
636 setLogicalState(true);
637 }
638 if (!strcmp(buf, "off") || !strcmp(buf, "false")) {
639 // setPhysicalState(false, true);
640 setLogicalState(false);
641 }
642 if (!strcmp(buf, "toggle")) {
643 setToggle();
644 }
645 if (!strcmp(buf, "pulse")) {
646 setPulse();
647 }
648 } else if (topic == name + "/switch/debounce/get") {
649 char buf[32];
650 sprintf(buf, "%ld", debounceTimeMs);
651 pSched->publish(name + "/debounce", buf);
652 } else if (topic == name + "/switch/debounce/set") {
653 long dbt = atol(msg.c_str());
654 setDebounce(dbt);
655 } else if (topic == "mqtt/state") {
656 if (mode == Mode::Default || mode == Mode::Flipflop || mode == Mode::BinarySensor) {
657 if (msg == "connected") {
658 publishLogicalState(logicalState);
659 if (bCounter) {
660 publishCounter();
661 }
662 }
663 }
664 } else if (topic == name + "/switch/counter/start") {
665 activateCounter(true);
666 } else if (topic == name + "/switch/counter/stop") {
667 activateCounter(false);
668 }
669 };
670}; // Switch
671
672const char *Switch::version = "0.1.0";
673
674} // namespace ustd
mupplet-core GPIO Switch class
Definition: mup_switch.h:136
Mode
Definition: mup_switch.h:140
@ Falling
Definition: mup_switch.h:145
@ Duration
Definition: mup_switch.h:149
@ Flipflop
Definition: mup_switch.h:147
@ Default
Definition: mup_switch.h:141
@ Rising
Definition: mup_switch.h:143
@ BinarySensor
Definition: mup_switch.h:151
@ Timer
Definition: mup_switch.h:148
void setToggle()
Definition: mup_switch.h:341
void setTimerDuration(unsigned long ms)
Definition: mup_switch.h:238
void setLogicalState(bool newLogicalState)
Definition: mup_switch.h:314
void setPulse()
Definition: mup_switch.h:350
void activateCounter(bool bActive)
Definition: mup_switch.h:226
void setDebounce(long ms)
Definition: mup_switch.h:214
void setMode(Mode newmode, unsigned long duration=0)
Definition: mup_switch.h:248
void begin(Scheduler *_pSched)
Definition: mup_switch.h:277
Switch(String name, uint8_t port, Mode mode=Mode::Default, bool activeLogic=false, String customTopic="", int8_t interruptIndex=-1, unsigned long debounceTimeMs=0)
Definition: mup_switch.h:189
void setStateRefresh(int logicalStateRefreshEverySecs)
Definition: mup_switch.h:334
The muwerk namespace.
Definition: home_assistant.h:10