Browse Source

Static configuration for LED and Buttons

- use similar to relay & wifi, configuration through helper methods that
  use indexed defines
- clock cycles for led delay polling
- button mode defaults
mcspr-patch-1
Maxim Prokhorov 4 years ago
parent
commit
2ff50169b9
12 changed files with 609 additions and 271 deletions
  1. +41
    -0
      code/espurna/button.h
  2. +85
    -122
      code/espurna/button.ino
  3. +172
    -0
      code/espurna/button_config.h
  4. +40
    -21
      code/espurna/config/defaults.h
  5. +6
    -0
      code/espurna/config/deprecated.h
  6. +2
    -0
      code/espurna/config/hardware.h
  7. +7
    -1
      code/espurna/config/types.h
  8. +2
    -0
      code/espurna/espurna.ino
  9. +45
    -0
      code/espurna/led.h
  10. +149
    -127
      code/espurna/led.ino
  11. +59
    -0
      code/espurna/led_config.h
  12. +1
    -0
      code/espurna/relay.ino

+ 41
- 0
code/espurna/button.h View File

@ -0,0 +1,41 @@
/*
BUTTON MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#pragma once
#include <DebounceEvent.h>
struct button_t {
// TODO: dblclick and debounce delays - right now a global setting, independent of ID
static unsigned long DebounceDelay;
static unsigned long DblclickDelay;
// Use built-in indexed definitions to configure DebounceEvent
button_t(unsigned char index);
// Provide custom DebounceEvent parameters instead
button_t(unsigned char pin, unsigned char mode, unsigned long actions, unsigned char relayID);
bool state();
std::unique_ptr<DebounceEvent> event;
unsigned long actions;
unsigned char relayID;
};
bool buttonState(unsigned char id);
unsigned char buttonAction(unsigned char id, unsigned char event);
void buttonMQTT(unsigned char id, uint8_t event);
void buttonEvent(unsigned char id, unsigned char event);
unsigned char buttonAdd(unsigned char pin, unsigned char mode, unsigned long actions, unsigned char relayID = RELAY_NONE);
unsigned char buttonCount();
void buttonSetup();

+ 85
- 122
code/espurna/button.ino View File

@ -6,32 +6,49 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
// -----------------------------------------------------------------------------
// BUTTON
// -----------------------------------------------------------------------------
#if BUTTON_SUPPORT
#include <DebounceEvent.h>
#include <memory>
#include <vector>
#include "system.h"
#include "relay.h"
#include "light.h"
typedef struct {
DebounceEvent * button;
unsigned long actions;
unsigned int relayID;
} button_t;
#include "button.h"
#include "button_config.h"
// -----------------------------------------------------------------------------
// TODO: dblclick and debounce delays - right now a global setting, independent of ID
unsigned long button_t::DebounceDelay = BUTTON_DEBOUNCE_DELAY;
unsigned long button_t::DblclickDelay = BUTTON_DBLCLICK_DELAY;
button_t::button_t(unsigned char pin, unsigned char mode, unsigned long actions, unsigned char relayID) :
event(new DebounceEvent(pin, mode, DebounceDelay, DblclickDelay)),
actions(actions),
relayID(relayID)
{}
button_t::button_t(unsigned char index) :
button_t(_buttonPin(index), _buttonMode(index), _buttonConstructActions(index), _buttonRelay(index))
{}
bool button_t::state() {
return event->pressed();
}
std::vector<button_t> _buttons;
unsigned char buttonCount() {
return _buttons.size();
}
#if MQTT_SUPPORT
void buttonMQTT(unsigned char id, uint8_t event) {
if (id >= _buttons.size()) return;
char payload[2];
char payload[4] = {0};
itoa(event, payload, 10);
mqttSend(MQTT_TOPIC_BUTTON, id, payload, false, false); // 1st bool = force, 2nd = retain
}
@ -40,12 +57,8 @@ void buttonMQTT(unsigned char id, uint8_t event) {
#if WEB_SUPPORT
unsigned char _buttonCount() {
return _buttons.size();
}
void _buttonWebSocketOnVisible(JsonObject& root) {
if (_buttonCount() > 0) {
if (buttonCount() > 0) {
root["btnVisible"] = 1;
}
}
@ -56,62 +69,23 @@ bool _buttonWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
#endif
int buttonFromRelay(unsigned int relayID) {
for (unsigned int i=0; i < _buttons.size(); i++) {
if (_buttons[i].relayID == relayID) return i;
}
return -1;
}
bool buttonState(unsigned char id) {
if (id >= _buttons.size()) return false;
return _buttons[id].button->pressed();
return _buttons[id].state();
}
unsigned char buttonAction(unsigned char id, unsigned char event) {
if (id >= _buttons.size()) return BUTTON_MODE_NONE;
unsigned long actions = _buttons[id].actions;
if (event == BUTTON_EVENT_PRESSED) return (actions) & 0x0F;
if (event == BUTTON_EVENT_CLICK) return (actions >> 4) & 0x0F;
if (event == BUTTON_EVENT_DBLCLICK) return (actions >> 8) & 0x0F;
if (event == BUTTON_EVENT_LNGCLICK) return (actions >> 12) & 0x0F;
if (event == BUTTON_EVENT_LNGLNGCLICK) return (actions >> 16) & 0x0F;
if (event == BUTTON_EVENT_TRIPLECLICK) return (actions >> 20) & 0x0F;
return BUTTON_MODE_NONE;
return _buttonDecodeEventAction(_buttons[id].actions, event);
}
unsigned long buttonStore(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick, unsigned long tripleclick) {
unsigned int value;
value = pressed;
value += click << 4;
value += dblclick << 8;
value += lngclick << 12;
value += lnglngclick << 16;
value += tripleclick << 20;
return value;
}
uint8_t mapEvent(uint8_t event, uint8_t count, uint16_t length) {
if (event == EVENT_PRESSED) return BUTTON_EVENT_PRESSED;
if (event == EVENT_CHANGED) return BUTTON_EVENT_CLICK;
if (event == EVENT_RELEASED) {
if (1 == count) {
if (length > BUTTON_LNGLNGCLICK_DELAY) return BUTTON_EVENT_LNGLNGCLICK;
if (length > BUTTON_LNGCLICK_DELAY) return BUTTON_EVENT_LNGCLICK;
return BUTTON_EVENT_CLICK;
}
if (2 == count) return BUTTON_EVENT_DBLCLICK;
if (3 == count) return BUTTON_EVENT_TRIPLECLICK;
}
return BUTTON_EVENT_NONE;
}
void buttonEvent(unsigned int id, unsigned char event) {
void buttonEvent(unsigned char id, unsigned char event) {
DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %u\n"), id, event);
if (event == 0) return;
unsigned char action = buttonAction(id, event);
auto& button = _buttons[id];
unsigned char action = _buttonDecodeEventAction(button.actions, event);
#if MQTT_SUPPORT
if (action != BUTTON_MODE_NONE || BUTTON_MQTT_SEND_ALL_EVENTS) {
@ -120,21 +94,15 @@ void buttonEvent(unsigned int id, unsigned char event) {
#endif
if (BUTTON_MODE_TOGGLE == action) {
if (_buttons[id].relayID > 0) {
relayToggle(_buttons[id].relayID - 1);
}
relayToggle(button.relayID);
}
if (BUTTON_MODE_ON == action) {
if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, true);
}
relayStatus(button.relayID, true);
}
if (BUTTON_MODE_OFF == action) {
if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, false);
}
relayStatus(button.relayID, false);
}
if (BUTTON_MODE_AP == action) {
@ -180,81 +148,74 @@ void buttonEvent(unsigned int id, unsigned char event) {
}
unsigned char buttonAdd(unsigned char pin, unsigned char mode, unsigned long actions, unsigned char relayID) {
_buttons.emplace_back(pin, mode, actions, relayID);
return _buttons.size() - 1;
}
void buttonSetup() {
// Special hardware cases
#if defined(ITEAD_SONOFF_DUAL)
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 1});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 2});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, BUTTON3_RELAY});
buttonAdd(GPIO_NONE, BUTTON_PUSHBUTTON, 0, _buttonRelay(0));
buttonAdd(GPIO_NONE, BUTTON_PUSHBUTTON, 0, _buttonRelay(1));
buttonAdd(GPIO_NONE, BUTTON_PUSHBUTTON, 0, _buttonRelay(2));
#elif defined(FOXEL_LIGHTFOX_DUAL)
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
unsigned int btn1Relay = getSetting<int>({"btnRelay", 0}, BUTTON1_RELAY - 1) + 1;
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, btn1Relay});
unsigned int btn2Relay = getSetting<int>({"btnRelay", 1}, BUTTON2_RELAY - 1) + 1;
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, btn2Relay});
unsigned int btn3Relay = getSetting<int>({"btnRelay", 2}, BUTTON3_RELAY - 1) + 1;
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, btn3Relay});
unsigned int btn4Relay = getSetting<int>({"btnRelay", 3}, BUTTON4_RELAY - 1) + 1;
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, btn4Relay});
const auto actions = _buttonConstructActions(
BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE,
BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE
);
for (unsigned char id = 0; id < 4; ++id) {
buttonAdd(
GPIO_NONE, BUTTON_PUSHBUTTON,
actions, getSetting({"btnRelay", id}, _buttonRelay(id))
);
}
// Generic GPIO input handlers
#else
unsigned long btnDelay = getSetting<int>("btnDelay", BUTTON_DBLCLICK_DELAY);
UNUSED(btnDelay);
size_t buttons = 0;
#if BUTTON1_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK, BUTTON1_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY});
}
++buttons;
#endif
#if BUTTON2_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK, BUTTON2_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON2_PIN, BUTTON2_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON2_RELAY});
}
++buttons;
#endif
#if BUTTON3_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK, BUTTON3_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON3_PIN, BUTTON3_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON3_RELAY});
}
++buttons;
#endif
#if BUTTON4_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK, BUTTON4_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON4_PIN, BUTTON4_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON4_RELAY});
}
++buttons;
#endif
#if BUTTON5_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK, BUTTON5_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON5_PIN, BUTTON5_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON5_RELAY});
}
++buttons;
#endif
#if BUTTON6_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK, BUTTON6_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON6_PIN, BUTTON6_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON6_RELAY});
}
++buttons;
#endif
#if BUTTON7_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK, BUTTON7_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON7_PIN, BUTTON7_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON7_RELAY});
}
++buttons;
#endif
#if BUTTON8_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK, BUTTON8_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON8_PIN, BUTTON8_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON8_RELAY});
}
++buttons;
#endif
// TODO: load based on index
button_t::DebounceDelay = getSetting("btnDebounce", BUTTON_DEBOUNCE_DELAY);
button_t::DblclickDelay = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY);
for (unsigned char id = 0; id < buttons; ++id) {
_buttons.emplace_back(id);
}
#endif
DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
@ -338,12 +299,14 @@ void buttonLoop() {
#else
for (unsigned int i=0; i < _buttons.size(); i++) {
if (unsigned char event = _buttons[i].button->loop()) {
unsigned char count = _buttons[i].button->getEventCount();
unsigned long length = _buttons[i].button->getEventLength();
unsigned char mapped = mapEvent(event, count, length);
buttonEvent(i, mapped);
for (size_t id = 0; id < _buttons.size(); ++id) {
auto& button = _buttons[id];
if (auto event = button.event->loop()) {
buttonEvent(id, _buttonMapEvent(
event,
button.event->getEventCount(),
button.event->getEventLength()
));
}
}


+ 172
- 0
code/espurna/button_config.h View File

@ -0,0 +1,172 @@
/*
BUTTON MODULE
*/
#pragma once
constexpr const unsigned char _buttonPin(unsigned char index) {
return (
(index == 0) ? BUTTON1_PIN :
(index == 1) ? BUTTON2_PIN :
(index == 2) ? BUTTON3_PIN :
(index == 3) ? BUTTON4_PIN :
(index == 4) ? BUTTON5_PIN :
(index == 5) ? BUTTON6_PIN :
(index == 6) ? BUTTON7_PIN :
(index == 7) ? BUTTON8_PIN : GPIO_NONE
);
}
constexpr const unsigned char _buttonMode(unsigned char index) {
return (
(index == 0) ? BUTTON1_MODE :
(index == 1) ? BUTTON2_MODE :
(index == 2) ? BUTTON3_MODE :
(index == 3) ? BUTTON4_MODE :
(index == 4) ? BUTTON5_MODE :
(index == 5) ? BUTTON6_MODE :
(index == 6) ? BUTTON7_MODE :
(index == 7) ? BUTTON8_MODE : (BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH)
);
}
constexpr const unsigned char _buttonPress(unsigned char index) {
return (
(index == 0) ? BUTTON1_PRESS :
(index == 1) ? BUTTON2_PRESS :
(index == 2) ? BUTTON3_PRESS :
(index == 3) ? BUTTON4_PRESS :
(index == 4) ? BUTTON5_PRESS :
(index == 5) ? BUTTON6_PRESS :
(index == 6) ? BUTTON7_PRESS :
(index == 7) ? BUTTON8_PRESS : BUTTON_MODE_NONE
);
}
constexpr const unsigned char _buttonClick(unsigned char index) {
return (
(index == 0) ? BUTTON1_CLICK :
(index == 1) ? BUTTON2_CLICK :
(index == 2) ? BUTTON3_CLICK :
(index == 3) ? BUTTON4_CLICK :
(index == 4) ? BUTTON5_CLICK :
(index == 5) ? BUTTON6_CLICK :
(index == 6) ? BUTTON7_CLICK :
(index == 7) ? BUTTON8_CLICK : BUTTON_MODE_NONE
);
}
constexpr const unsigned char _buttonDoubleClick(unsigned char index) {
return (
(index == 0) ? BUTTON1_DBLCLICK :
(index == 1) ? BUTTON2_DBLCLICK :
(index == 2) ? BUTTON3_DBLCLICK :
(index == 3) ? BUTTON4_DBLCLICK :
(index == 4) ? BUTTON5_DBLCLICK :
(index == 5) ? BUTTON6_DBLCLICK :
(index == 6) ? BUTTON7_DBLCLICK :
(index == 7) ? BUTTON8_DBLCLICK : BUTTON_MODE_NONE
);
}
constexpr const unsigned char _buttonTripleClick(unsigned char index) {
return (
(index == 0) ? BUTTON1_TRIPLECLICK :
(index == 1) ? BUTTON2_TRIPLECLICK :
(index == 2) ? BUTTON3_TRIPLECLICK :
(index == 3) ? BUTTON4_TRIPLECLICK :
(index == 4) ? BUTTON5_TRIPLECLICK :
(index == 5) ? BUTTON6_TRIPLECLICK :
(index == 6) ? BUTTON7_TRIPLECLICK :
(index == 7) ? BUTTON8_TRIPLECLICK : BUTTON_MODE_NONE
);
}
constexpr const unsigned char _buttonLongClick(unsigned char index) {
return (
(index == 0) ? BUTTON1_LNGCLICK :
(index == 1) ? BUTTON2_LNGCLICK :
(index == 2) ? BUTTON3_LNGCLICK :
(index == 3) ? BUTTON4_LNGCLICK :
(index == 4) ? BUTTON5_LNGCLICK :
(index == 5) ? BUTTON6_LNGCLICK :
(index == 6) ? BUTTON7_LNGCLICK :
(index == 7) ? BUTTON8_LNGCLICK : BUTTON_MODE_NONE
);
}
constexpr const unsigned char _buttonLongLongClick(unsigned char index) {
return (
(index == 0) ? BUTTON1_LNGLNGCLICK :
(index == 1) ? BUTTON2_LNGLNGCLICK :
(index == 2) ? BUTTON3_LNGLNGCLICK :
(index == 3) ? BUTTON4_LNGLNGCLICK :
(index == 4) ? BUTTON5_LNGLNGCLICK :
(index == 5) ? BUTTON6_LNGLNGCLICK :
(index == 6) ? BUTTON7_LNGLNGCLICK :
(index == 7) ? BUTTON8_LNGLNGCLICK : BUTTON_MODE_NONE
);
}
constexpr const unsigned char _buttonRelay(unsigned char index) {
return (
(index == 0) ? (BUTTON1_RELAY - 1) :
(index == 1) ? (BUTTON2_RELAY - 1) :
(index == 2) ? (BUTTON3_RELAY - 1) :
(index == 3) ? (BUTTON4_RELAY - 1) :
(index == 4) ? (BUTTON5_RELAY - 1) :
(index == 5) ? (BUTTON6_RELAY - 1) :
(index == 6) ? (BUTTON7_RELAY - 1) :
(index == 7) ? (BUTTON8_RELAY - 1) : RELAY_NONE
);
}
constexpr const unsigned char _buttonDecodeEventAction(unsigned long actions, unsigned char event) {
return (
(event == BUTTON_EVENT_PRESSED) ? ((actions) & 0x0F) :
(event == BUTTON_EVENT_CLICK) ? ((actions >> 4) & 0x0F) :
(event == BUTTON_EVENT_DBLCLICK) ? ((actions >> 8) & 0x0F) :
(event == BUTTON_EVENT_LNGCLICK) ? ((actions >> 12) & 0x0F) :
(event == BUTTON_EVENT_LNGLNGCLICK) ? ((actions >> 16) & 0x0F) :
(event == BUTTON_EVENT_TRIPLECLICK) ? ((actions >> 20) & 0x0F) : BUTTON_MODE_NONE
);
}
constexpr const uint8_t _buttonMapReleased(uint8_t count, uint8_t length) {
return (
(1 == count) ? (
(length > BUTTON_LNGLNGCLICK_DELAY) ? BUTTON_EVENT_LNGLNGCLICK :
(length > BUTTON_LNGCLICK_DELAY) ? BUTTON_EVENT_LNGCLICK : BUTTON_EVENT_CLICK
) :
(2 == count) ? BUTTON_EVENT_DBLCLICK :
(3 == count) ? BUTTON_EVENT_TRIPLECLICK :
BUTTON_EVENT_NONE
);
}
constexpr const uint8_t _buttonMapEvent(uint8_t event, uint8_t count, uint16_t length) {
return (
(event == EVENT_PRESSED) ? BUTTON_EVENT_PRESSED :
(event == EVENT_CHANGED) ? BUTTON_EVENT_CLICK :
(event == EVENT_RELEASED) ? _buttonMapReleased(count, length) :
BUTTON_EVENT_NONE
);
}
constexpr uint32_t _buttonConstructActions(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick, unsigned long tripleclick) {
return (
(tripleclick << 20) |
(lnglngclick << 16) |
(lngclick << 12) |
(dblclick << 8) |
(click << 4) |
pressed
);
}
constexpr uint32_t _buttonConstructActions(unsigned char id) {
return _buttonConstructActions(_buttonPress(id), _buttonClick(id), _buttonDoubleClick(id), _buttonLongClick(id), _buttonLongLongClick(id), _buttonTripleClick(id));
}

+ 40
- 21
code/espurna/config/defaults.h View File

@ -1,9 +1,3 @@
// -----------------------------------------------------------------------------
// Hardware default values
// -----------------------------------------------------------------------------
#define GPIO_NONE 0x99
// -----------------------------------------------------------------------------
// Buttons
// -----------------------------------------------------------------------------
@ -33,6 +27,31 @@
#define BUTTON8_PIN GPIO_NONE
#endif
#ifndef BUTTON1_MODE
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#endif
#ifndef BUTTON2_MODE
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#endif
#ifndef BUTTON3_MODE
#define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#endif
#ifndef BUTTON4_MODE
#define BUTTON4_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#endif
#ifndef BUTTON5_MODE
#define BUTTON5_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#endif
#ifndef BUTTON6_MODE
#define BUTTON6_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#endif
#ifndef BUTTON7_MODE
#define BUTTON7_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#endif
#ifndef BUTTON8_MODE
#define BUTTON8_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#endif
#ifndef BUTTON1_PRESS
#define BUTTON1_PRESS BUTTON_MODE_NONE
#endif
@ -184,28 +203,28 @@
#endif
#ifndef BUTTON1_RELAY
#define BUTTON1_RELAY 0
#define BUTTON1_RELAY RELAY_NONE
#endif
#ifndef BUTTON2_RELAY
#define BUTTON2_RELAY 0
#define BUTTON2_RELAY RELAY_NONE
#endif
#ifndef BUTTON3_RELAY
#define BUTTON3_RELAY 0
#define BUTTON3_RELAY RELAY_NONE
#endif
#ifndef BUTTON4_RELAY
#define BUTTON4_RELAY 0
#define BUTTON4_RELAY RELAY_NONE
#endif
#ifndef BUTTON5_RELAY
#define BUTTON5_RELAY 0
#define BUTTON5_RELAY RELAY_NONE
#endif
#ifndef BUTTON6_RELAY
#define BUTTON6_RELAY 0
#define BUTTON6_RELAY RELAY_NONE
#endif
#ifndef BUTTON7_RELAY
#define BUTTON7_RELAY 0
#define BUTTON7_RELAY RELAY_NONE
#endif
#ifndef BUTTON8_RELAY
#define BUTTON8_RELAY 0
#define BUTTON8_RELAY RELAY_NONE
#endif
// -----------------------------------------------------------------------------
@ -510,25 +529,25 @@
#define LED1_MODE LED_MODE_WIFI
#endif
#ifndef LED2_MODE
#define LED2_MODE LED_MODE_MQTT
#define LED2_MODE LED_MODE_MANUAL
#endif
#ifndef LED3_MODE
#define LED3_MODE LED_MODE_MQTT
#define LED3_MODE LED_MODE_MANUAL
#endif
#ifndef LED4_MODE
#define LED4_MODE LED_MODE_MQTT
#define LED4_MODE LED_MODE_MANUAL
#endif
#ifndef LED5_MODE
#define LED5_MODE LED_MODE_MQTT
#define LED5_MODE LED_MODE_MANUAL
#endif
#ifndef LED6_MODE
#define LED6_MODE LED_MODE_MQTT
#define LED6_MODE LED_MODE_MANUAL
#endif
#ifndef LED7_MODE
#define LED7_MODE LED_MODE_MQTT
#define LED7_MODE LED_MODE_MANUAL
#endif
#ifndef LED8_MODE
#define LED8_MODE LED_MODE_MQTT
#define LED8_MODE LED_MODE_MANUAL
#endif
#ifndef LED1_RELAY


+ 6
- 0
code/espurna/config/deprecated.h View File

@ -47,3 +47,9 @@
#if MQTT_SUPPORT && MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTT_CLIENT && ASYNC_TCP_SSL_ENABLED
#warning "Current implementation of AsyncMqttClient with axTLS is no longer supported. Consider switching to the SECURE_CLIENT configuration with MQTT_LIBRARY_ARDUINOMQTT or MQTT_LIBRARY_PUBSUBCLIENT. See: https://github.com/xoseperez/espurna/issues/1465"
#endif
// 1.14.2 renames MQTT to MANUAL
#undef LED_MODE_MQTT
#ifdef LED_MODE_MQTT
#warning LED_MODE_MQTT is deprecated! Please use LED_MODE_MANUAL instead
#endif

+ 2
- 0
code/espurna/config/hardware.h View File

@ -550,6 +550,8 @@
#define DEBUG_SERIAL_SUPPORT 0
// Buttons
#define BUTTON1_RELAY 1
#define BUTTON2_RELAY 2
#define BUTTON3_RELAY 1
// LEDs


+ 7
- 1
code/espurna/config/types.h View File

@ -153,7 +153,7 @@
// LED
//------------------------------------------------------------------------------
#define LED_MODE_MQTT 0 // LED will be managed from MQTT (OFF by default)
#define LED_MODE_MANUAL 0 // LED will be managed manually (OFF by default)
#define LED_MODE_WIFI 1 // LED will blink according to the WIFI status
#define LED_MODE_FOLLOW 2 // LED will follow state of linked relay (check RELAY#_LED)
#define LED_MODE_FOLLOW_INVERSE 3 // LED will follow the opposite state of linked relay (check RELAY#_LED)
@ -377,3 +377,9 @@
#define SECURE_CLIENT_CHECK_FINGERPRINT 1 // legacy fingerprint validation
#define SECURE_CLIENT_CHECK_CA 2 // set trust anchor from PROGMEM CA certificate
// -----------------------------------------------------------------------------
// Hardware default values
// -----------------------------------------------------------------------------
#define GPIO_NONE 0x99
#define RELAY_NONE 0x99

+ 2
- 0
code/espurna/espurna.ino View File

@ -23,7 +23,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "board.h"
#include "broker.h"
#include "button.h"
#include "debug.h"
#include "led.h"
#include "relay.h"
#include "settings.h"
#include "system.h"


+ 45
- 0
code/espurna/led.h View File

@ -0,0 +1,45 @@
/*
LED MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#pragma once
struct led_t {
led_t();
led_t(unsigned char id);
bool status();
bool status(bool new_status);
bool toggle();
unsigned char pin;
bool inverse;
unsigned char mode;
unsigned char relayID;
};
struct led_delay_t {
led_delay_t(unsigned long on_ms, unsigned long off_ms);
const unsigned long on;
const unsigned long off;
};
enum class LedMode {
NetworkAutoconfig,
NetworkConnected,
NetworkConnectedInverse,
NetworkConfig,
NetworkConfigInverse,
NetworkIdle,
None
};
const led_delay_t& _ledGetDelay(LedMode mode);
void ledUpdate(bool do_update);
void ledSetup();

+ 149
- 127
code/espurna/led.ino View File

@ -6,72 +6,98 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
// -----------------------------------------------------------------------------
// LED
// -----------------------------------------------------------------------------
#if LED_SUPPORT
#include "relay.h"
#include "broker.h"
#include "relay.h"
typedef struct {
unsigned char pin;
bool reverse;
unsigned char mode;
unsigned char relay;
} led_t;
std::vector<led_t> _leds;
bool _led_update = false; // For relay-based modes
// -----------------------------------------------------------------------------
bool _ledStatus(unsigned char id) {
if (id >= _ledCount()) return false;
bool status = digitalRead(_leds[id].pin);
return _leds[id].reverse ? !status : status;
#include "led.h"
#include "led_config.h"
// LED helper class
led_t::led_t() :
pin(GPIO_NONE),
inverse(false),
mode(LED_MODE_MANUAL),
relayID(0)
{}
led_t::led_t(unsigned char id) :
pin(_ledPin(id)),
inverse(_ledInverse(id)),
mode(_ledMode(id)),
relayID(_ledRelay(id))
{
if (pin != GPIO_NONE) {
pinMode(pin, OUTPUT);
}
}
bool _ledStatus(unsigned char id, bool status) {
if (id >=_ledCount()) return false;
digitalWrite(_leds[id].pin, _leds[id].reverse ? !status : status);
return status;
bool led_t::status() {
bool result = digitalRead(pin);
return inverse ? !result : result;
}
bool _ledToggle(unsigned char id) {
if (id >= _ledCount()) return false;
return _ledStatus(id, !_ledStatus(id));
bool led_t::status(bool new_status) {
digitalWrite(pin, inverse ? !new_status : new_status);
return new_status;
}
unsigned char _ledMode(unsigned char id) {
if (id >= _ledCount()) return false;
return _leds[id].mode;
bool led_t::toggle() {
return status(!status());
}
void _ledMode(unsigned char id, unsigned char mode) {
if (id >= _ledCount()) return;
_leds[id].mode = mode;
}
led_delay_t::led_delay_t(unsigned long on_ms, unsigned long off_ms) :
on(microsecondsToClockCycles(on_ms * 1000)),
off(microsecondsToClockCycles(off_ms * 1000))
{}
// For relay-based modes
bool _led_update = false;
// For network-based modes, cycle ON & OFF (time in milliseconds)
// XXX: internals convert these to clock cycles, delay cannot be longer than 25000 / 50000 ms
const led_delay_t _ledDelays[] {
{100, 100}, // Autoconfig
{100, 4900}, // Connected
{4900, 100}, // Connected (inverse)
{100, 900}, // Config / AP
{900, 100}, // Config / AP (inverse)
{500, 500} // Idle
};
std::vector<led_t> _leds;
unsigned char _ledRelay(unsigned char id) {
if (id >= _ledCount()) return false;
return _leds[id].relay;
// -----------------------------------------------------------------------------
unsigned char _ledCount() {
return _leds.size();
}
void _ledRelay(unsigned char id, unsigned char relay) {
if (id >= _ledCount()) return;
_leds[id].relay = relay;
const led_delay_t& _ledModeToDelay(LedMode mode) {
static_assert(
(sizeof(_ledDelays) / sizeof(_ledDelays[0])) <= static_cast<int>(LedMode::None),
"LedMode mapping out-of-bounds"
);
return _ledDelays[static_cast<int>(mode)];
}
void _ledBlink(unsigned char id, unsigned long delayOff, unsigned long delayOn) {
if (id >= _ledCount()) return;
static unsigned long next = millis();
if (next < millis()) {
next += (_ledToggle(id) ? delayOn : delayOff);
void _ledBlink(led_t& led, const led_delay_t& delays) {
static auto clock_last = ESP.getCycleCount();
static auto delay_for = delays.on;
const auto clock_current = ESP.getCycleCount();
if (clock_current - clock_last >= delay_for) {
delay_for = led.toggle() ? delays.on : delays.off;
clock_last = clock_current;
}
}
inline void _ledBlink(led_t& led, const LedMode mode) {
_ledBlink(led, _ledModeToDelay(mode));
}
#if WEB_SUPPORT
bool _ledWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
@ -85,12 +111,12 @@ void _ledWebSocketOnVisible(JsonObject& root) {
}
void _ledWebSocketOnConnected(JsonObject& root) {
if (_ledCount() == 0) return;
if (!_ledCount()) return;
JsonArray& leds = root.createNestedArray("ledConfig");
for (byte i=0; i<_ledCount(); i++) {
for (unsigned char id = 0; id < _ledCount(); ++id) {
JsonObject& led = leds.createNestedObject();
led["mode"] = getSetting({"ledMode", i}, _ledMode(i));
led["relay"] = getSetting({"ledRelay", i}, _ledRelay(i));
led["mode"] = getSetting({"ledMode", id}, _leds[id].mode);
led["relay"] = getSetting<unsigned char>({"ledRelay", id}, _leds[id].relayID);
}
}
@ -118,28 +144,28 @@ void _ledMQTTCallback(unsigned int type, const char * topic, const char * payloa
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
if (!t.startsWith(MQTT_TOPIC_LED)) return;
// Only want `led/+/<MQTT_SETTER>`
const String magnitude = mqttMagnitude((char *) topic);
if (!magnitude.startsWith(MQTT_TOPIC_LED)) return;
// Get led ID
unsigned int ledID = t.substring(strlen(MQTT_TOPIC_LED)+1).toInt();
// Get led ID from after the slash when t is `led/<LED_ID>`
unsigned int ledID = magnitude.substring(strlen(MQTT_TOPIC_LED) + 1).toInt();
if (ledID >= _ledCount()) {
DEBUG_MSG_P(PSTR("[LED] Wrong ledID (%d)\n"), ledID);
return;
}
// Check if LED is managed
if (_ledMode(ledID) != LED_MODE_MQTT) return;
if (_leds[ledID].mode != LED_MODE_MANUAL) return;
// get value
// Get value based on relays payload logic (0 / off, 1 / on, 2 / toggle)
const auto value = relayParsePayload(payload);
// Action to perform
// Action to perform is also based on relay constants ... TODO generic enum?
if (value == RelayStatus::TOGGLE) {
_ledToggle(ledID);
_leds[ledID].toggle();
} else {
_ledStatus(ledID, (value == RelayStatus::ON));
_leds[ledID].status(value == RelayStatus::ON);
}
}
@ -147,56 +173,53 @@ void _ledMQTTCallback(unsigned int type, const char * topic, const char * payloa
}
#endif
unsigned char _ledCount() {
return _leds.size();
}
void _ledConfigure() {
for (unsigned char i=0; i < _leds.size(); i++) {
_ledMode(i, getSetting({"ledMode", i}, _ledMode(i)));
_ledRelay(i, getSetting({"ledRelay", i}, _ledRelay(i)));
for (unsigned char id = 0; id < _leds.size(); ++id) {
_leds[id].mode = getSetting({"ledMode", id}, _ledMode(id));
_leds[id].relayID = getSetting<unsigned char>({"ledRelay", id}, _ledRelay(id));
}
_led_update = true;
}
// -----------------------------------------------------------------------------
void ledUpdate(bool value) {
_led_update = value;
void ledUpdate(bool do_update) {
_led_update = do_update;
}
void ledSetup() {
size_t leds = 0;
#if LED1_PIN != GPIO_NONE
_leds.push_back((led_t) { LED1_PIN, LED1_PIN_INVERSE, LED1_MODE, LED1_RELAY - 1 });
++leds;
#endif
#if LED2_PIN != GPIO_NONE
_leds.push_back((led_t) { LED2_PIN, LED2_PIN_INVERSE, LED2_MODE, LED2_RELAY - 1 });
++leds;
#endif
#if LED3_PIN != GPIO_NONE
_leds.push_back((led_t) { LED3_PIN, LED3_PIN_INVERSE, LED3_MODE, LED3_RELAY - 1 });
++leds;
#endif
#if LED4_PIN != GPIO_NONE
_leds.push_back((led_t) { LED4_PIN, LED4_PIN_INVERSE, LED4_MODE, LED4_RELAY - 1 });
++leds;
#endif
#if LED5_PIN != GPIO_NONE
_leds.push_back((led_t) { LED5_PIN, LED5_PIN_INVERSE, LED5_MODE, LED5_RELAY - 1 });
++leds;
#endif
#if LED6_PIN != GPIO_NONE
_leds.push_back((led_t) { LED6_PIN, LED6_PIN_INVERSE, LED6_MODE, LED6_RELAY - 1 });
++leds;
#endif
#if LED7_PIN != GPIO_NONE
_leds.push_back((led_t) { LED7_PIN, LED7_PIN_INVERSE, LED7_MODE, LED7_RELAY - 1 });
++leds;
#endif
#if LED8_PIN != GPIO_NONE
_leds.push_back((led_t) { LED8_PIN, LED8_PIN_INVERSE, LED8_MODE, LED8_RELAY - 1 });
++leds;
#endif
for (unsigned char i=0; i < _leds.size(); i++) {
if (!hasSetting({"ledMode", i})) setSetting({"ledMode", i}, _leds[i].mode);
if (!hasSetting({"ledRelay", i})) setSetting({"ledRelay", i}, _leds[i].relay);
pinMode(_leds[i].pin, OUTPUT);
_ledStatus(i, false);
_leds.reserve(leds);
for (unsigned char id=0; id < leds; ++id) {
_leds.emplace_back(id);
}
_ledConfigure();
@ -223,69 +246,68 @@ void ledSetup() {
espurnaRegisterLoop(ledLoop);
espurnaRegisterReload(_ledConfigure);
}
void ledLoop() {
uint8_t wifi_state = wifiState();
const auto wifi_state = wifiState();
for (unsigned char i=0; i<_leds.size(); i++) {
for (auto& led : _leds) {
if (_ledMode(i) == LED_MODE_WIFI) {
if (led.mode == LED_MODE_WIFI) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
_ledBlink(i, 4900, 100);
_ledBlink(led, LedMode::NetworkConnected);
} else if (wifi_state & WIFI_STATE_AP) {
_ledBlink(i, 900, 100);
_ledBlink(led, LedMode::NetworkConfig);
} else {
_ledBlink(i, 500, 500);
_ledBlink(led, LedMode::NetworkIdle);
}
}
if (_ledMode(i) == LED_MODE_FINDME_WIFI) {
if (led.mode == LED_MODE_FINDME_WIFI) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay)) {
_ledBlink(i, 4900, 100);
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConnected);
} else {
_ledBlink(i, 100, 4900);
_ledBlink(led, LedMode::NetworkConnectedInverse);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay)) {
_ledBlink(i, 900, 100);
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConfig);
} else {
_ledBlink(i, 100, 900);
_ledBlink(led, LedMode::NetworkConfigInverse);
}
} else {
_ledBlink(i, 500, 500);
_ledBlink(led, LedMode::NetworkIdle);
}
}
if (_ledMode(i) == LED_MODE_RELAY_WIFI) {
if (led.mode == LED_MODE_RELAY_WIFI) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay)) {
_ledBlink(i, 100, 4900);
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConnected);
} else {
_ledBlink(i, 4900, 100);
_ledBlink(led, LedMode::NetworkConnectedInverse);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay)) {
_ledBlink(i, 100, 900);
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConfig);
} else {
_ledBlink(i, 900, 100);
_ledBlink(led, LedMode::NetworkConfigInverse);
}
} else {
_ledBlink(i, 500, 500);
_ledBlink(led, LedMode::NetworkIdle);
}
}
@ -293,42 +315,42 @@ void ledLoop() {
// Relay-based modes, update only if relays have been updated
if (!_led_update) continue;
if (_ledMode(i) == LED_MODE_FOLLOW) {
_ledStatus(i, relayStatus(_leds[i].relay));
if (led.mode == LED_MODE_FOLLOW) {
led.status(relayStatus(led.relayID));
}
if (_ledMode(i) == LED_MODE_FOLLOW_INVERSE) {
_ledStatus(i, !relayStatus(_leds[i].relay));
if (led.mode == LED_MODE_FOLLOW_INVERSE) {
led.status(!relayStatus(led.relayID));
}
if (_ledMode(i) == LED_MODE_FINDME) {
if (led.mode == LED_MODE_FINDME) {
bool status = true;
for (unsigned char k=0; k<relayCount(); k++) {
if (relayStatus(k)) {
for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
if (relayStatus(relayID)) {
status = false;
break;
}
}
_ledStatus(i, status);
led.status(status);
}
if (_ledMode(i) == LED_MODE_RELAY) {
if (led.mode == LED_MODE_RELAY) {
bool status = false;
for (unsigned char k=0; k<relayCount(); k++) {
if (relayStatus(k)) {
for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
if (relayStatus(relayID)) {
status = true;
break;
}
}
_ledStatus(i, status);
led.status(status);
}
if (_ledMode(i) == LED_MODE_ON) {
_ledStatus(i, true);
if (led.mode == LED_MODE_ON) {
led.status(true);
}
if (_ledMode(i) == LED_MODE_OFF) {
_ledStatus(i, false);
if (led.mode == LED_MODE_OFF) {
led.status(false);
}
}


+ 59
- 0
code/espurna/led_config.h View File

@ -0,0 +1,59 @@
/*
LED MODULE
*/
#pragma once
constexpr const unsigned char _ledPin(unsigned char index) {
return (
(index == 0) ? LED1_PIN :
(index == 1) ? LED2_PIN :
(index == 2) ? LED3_PIN :
(index == 3) ? LED4_PIN :
(index == 4) ? LED5_PIN :
(index == 5) ? LED6_PIN :
(index == 6) ? LED7_PIN :
(index == 7) ? LED8_PIN : GPIO_NONE
);
}
constexpr const unsigned char _ledMode(unsigned char index) {
return (
(index == 0) ? LED1_MODE :
(index == 1) ? LED2_MODE :
(index == 2) ? LED3_MODE :
(index == 3) ? LED4_MODE :
(index == 4) ? LED5_MODE :
(index == 5) ? LED6_MODE :
(index == 6) ? LED7_MODE :
(index == 7) ? LED8_MODE : LED_MODE_WIFI
);
}
constexpr const unsigned char _ledRelay(unsigned char index) {
return (
(index == 0) ? (LED1_RELAY - 1) :
(index == 1) ? (LED2_RELAY - 1) :
(index == 2) ? (LED3_RELAY - 1) :
(index == 3) ? (LED4_RELAY - 1) :
(index == 4) ? (LED5_RELAY - 1) :
(index == 5) ? (LED6_RELAY - 1) :
(index == 6) ? (LED7_RELAY - 1) :
(index == 7) ? (LED8_RELAY - 1) : 0
);
}
constexpr const bool _ledInverse(unsigned char index) {
return (
(index == 0) ? (1 == LED1_PIN_INVERSE) :
(index == 1) ? (1 == LED2_PIN_INVERSE) :
(index == 2) ? (1 == LED3_PIN_INVERSE) :
(index == 3) ? (1 == LED4_PIN_INVERSE) :
(index == 4) ? (1 == LED5_PIN_INVERSE) :
(index == 5) ? (1 == LED6_PIN_INVERSE) :
(index == 6) ? (1 == LED7_PIN_INVERSE) :
(index == 7) ? (1 == LED8_PIN_INVERSE) : false
);
}

+ 1
- 0
code/espurna/relay.ino View File

@ -479,6 +479,7 @@ void relayPulse(unsigned char id) {
bool relayStatus(unsigned char id, bool status, bool report, bool group_report) {
if (id == RELAY_NONE) return false;
if (id >= _relays.size()) return false;
if (!_relayStatusLock(id, status)) {


Loading…
Cancel
Save