Browse Source

system: refactor build configurations

Namespace build configurations of modules, make more things into constexpr
(not fully finished though)

Unify code using ...Count() to parse IDs

Avoid using unsigned char aka uint8_t as index, prefer size_t
as most code already uses it anyway. Making sure we never accidentally
truncate the value or try to read it as 32bit-wide. Also, simplify
access to built in containers, since those use the wide type as well.

Renames led and button types, more consistent initialization and field access.
dev
Maxim Prokhorov 3 years ago
parent
commit
f92116341e
24 changed files with 1062 additions and 910 deletions
  1. +199
    -257
      code/espurna/button.cpp
  2. +15
    -18
      code/espurna/button.h
  3. +74
    -29
      code/espurna/button_config.h
  4. +10
    -10
      code/espurna/config/types.h
  5. +219
    -190
      code/espurna/led.cpp
  6. +112
    -34
      code/espurna/led.h
  7. +40
    -5
      code/espurna/led_config.h
  8. +4
    -5
      code/espurna/led_pattern.h
  9. +3
    -4
      code/espurna/led_pattern.h.in
  10. +68
    -72
      code/espurna/light.cpp
  11. +6
    -6
      code/espurna/light.h
  12. +6
    -6
      code/espurna/light_config.h
  13. +118
    -122
      code/espurna/relay.cpp
  14. +8
    -8
      code/espurna/relay.h
  15. +42
    -32
      code/espurna/relay_config.h
  16. +68
    -61
      code/espurna/rfbridge.cpp
  17. +5
    -5
      code/espurna/rfbridge.h
  18. +18
    -12
      code/espurna/settings.cpp
  19. +3
    -5
      code/espurna/settings_embedis.h
  20. +23
    -23
      code/espurna/thingspeak.cpp
  21. +1
    -1
      code/espurna/thingspeak.h
  22. +5
    -5
      code/espurna/tuya.cpp
  23. +12
    -0
      code/espurna/utils.cpp
  24. +3
    -0
      code/espurna/utils.h

+ 199
- 257
code/espurna/button.cpp View File

@ -3,6 +3,7 @@
BUTTON MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2019-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
@ -30,9 +31,7 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "button_config.h"
BrokerBind(ButtonBroker);
// TODO: if we are using such conversion helpers across the codebase, should convert() be in internal ns?
// -----------------------------------------------------------------------------
namespace settings {
namespace internal {
@ -169,93 +168,77 @@ ButtonAction convert(const String& value) {
// -----------------------------------------------------------------------------
constexpr debounce_event::types::Config _buttonDecodeConfigBitmask(int bitmask) {
return {
((bitmask & ButtonMask::Pushbutton)
? debounce_event::types::Mode::Pushbutton
: debounce_event::types::Mode::Switch),
((bitmask & ButtonMask::DefaultLow) ? debounce_event::types::PinValue::Low
: (bitmask & ButtonMask::DefaultHigh) ? debounce_event::types::PinValue::High
: (bitmask & ButtonMask::DefaultBoot) ? debounce_event::types::PinValue::Initial
: debounce_event::types::PinValue::Low),
((bitmask & ButtonMask::SetPullup) ? debounce_event::types::PinMode::InputPullup
: (bitmask & ButtonMask::SetPulldown) ? debounce_event::types::PinMode::InputPulldown
: debounce_event::types::PinMode::Input)
};
}
constexpr ButtonAction _buttonDecodeEventAction(const ButtonActions& actions, button_event_t event) {
constexpr ButtonAction _buttonDecodeEventAction(const ButtonActions& actions, ButtonEvent event) {
return (
(event == button_event_t::Pressed) ? actions.pressed :
(event == button_event_t::Released) ? actions.released :
(event == button_event_t::Click) ? actions.click :
(event == button_event_t::DoubleClick) ? actions.dblclick :
(event == button_event_t::LongClick) ? actions.lngclick :
(event == button_event_t::LongLongClick) ? actions.lnglngclick :
(event == button_event_t::TripleClick) ? actions.trplclick : ButtonAction::None
(event == ButtonEvent::Pressed) ? actions.pressed :
(event == ButtonEvent::Released) ? actions.released :
(event == ButtonEvent::Click) ? actions.click :
(event == ButtonEvent::DoubleClick) ? actions.dblclick :
(event == ButtonEvent::LongClick) ? actions.lngclick :
(event == ButtonEvent::LongLongClick) ? actions.lnglngclick :
(event == ButtonEvent::TripleClick) ? actions.trplclick : ButtonAction::None
);
}
constexpr button_event_t _buttonMapReleased(uint8_t count, unsigned long length, unsigned long lngclick_delay, unsigned long lnglngclick_delay) {
constexpr ButtonEvent _buttonMapReleased(uint8_t count, unsigned long length, unsigned long lngclick_delay, unsigned long lnglngclick_delay) {
return (
(0 == count) ? button_event_t::Released :
(0 == count) ? ButtonEvent::Released :
(1 == count) ? (
(length > lnglngclick_delay) ? button_event_t::LongLongClick :
(length > lngclick_delay) ? button_event_t::LongClick : button_event_t::Click
(length > lnglngclick_delay) ? ButtonEvent::LongLongClick :
(length > lngclick_delay) ? ButtonEvent::LongClick : ButtonEvent::Click
) :
(2 == count) ? button_event_t::DoubleClick :
(3 == count) ? button_event_t::TripleClick :
button_event_t::None
(2 == count) ? ButtonEvent::DoubleClick :
(3 == count) ? ButtonEvent::TripleClick :
ButtonEvent::None
);
}
ButtonActions _buttonConstructActions(unsigned char index) {
ButtonActions _buttonConstructActions(size_t index) {
return {
_buttonPress(index),
_buttonRelease(index),
_buttonClick(index),
_buttonDoubleClick(index),
_buttonLongClick(index),
_buttonLongLongClick(index),
_buttonTripleClick(index)
button::build::press(index),
button::build::release(index),
button::build::click(index),
button::build::doubleClick(index),
button::build::longClick(index),
button::build::longLongClick(index),
button::build::tripleClick(index)
};
}
debounce_event::types::Config _buttonRuntimeConfig(unsigned char index) {
const auto config = _buttonDecodeConfigBitmask(_buttonConfigBitmask(index));
debounce_event::types::Config _buttonRuntimeConfig(size_t index) {
return {
getSetting({"btnMode", index}, config.mode),
getSetting({"btnDefVal", index}, config.default_value),
getSetting({"btnPinMode", index}, config.pin_mode)
getSetting({"btnMode", index}, button::build::mode(index)),
getSetting({"btnDefVal", index}, button::build::defaultValue(index)),
getSetting({"btnPinMode", index}, button::build::pinMode(index))
};
}
int _buttonEventNumber(button_event_t event) {
int _buttonEventNumber(ButtonEvent event) {
return static_cast<int>(event);
}
// -----------------------------------------------------------------------------
button_event_delays_t::button_event_delays_t() :
debounce(_buttonDebounceDelay()),
repeat(_buttonRepeatDelay()),
lngclick(_buttonLongClickDelay()),
lnglngclick(_buttonLongLongClickDelay())
ButtonEventDelays::ButtonEventDelays() :
debounce(button::build::debounceDelay()),
repeat(button::build::repeatDelay()),
lngclick(button::build::longClickDelay()),
lnglngclick(button::build::longLongClickDelay())
{}
button_event_delays_t::button_event_delays_t(unsigned long debounce, unsigned long repeat, unsigned long lngclick, unsigned long lnglngclick) :
ButtonEventDelays::ButtonEventDelays(unsigned long debounce, unsigned long repeat, unsigned long lngclick, unsigned long lnglngclick) :
debounce(debounce),
repeat(repeat),
lngclick(lngclick),
lnglngclick(lnglngclick)
{}
button_t::button_t(ButtonActions&& actions_, button_event_delays_t&& delays_) :
button_t::button_t(ButtonActions&& actions_, ButtonEventDelays&& delays_) :
actions(std::move(actions_)),
event_delays(std::move(delays_))
{}
button_t::button_t(BasePinPtr&& pin, const debounce_event::types::Config& config, ButtonActions&& actions_, button_event_delays_t&& delays_) :
button_t::button_t(BasePinPtr&& pin, const debounce_event::types::Config& config, ButtonActions&& actions_, ButtonEventDelays&& delays_) :
event_emitter(std::make_unique<debounce_event::EventEmitter>(std::move(pin), config, delays_.debounce, delays_.repeat)),
actions(std::move(actions_)),
event_delays(std::move(delays_))
@ -265,11 +248,11 @@ bool button_t::state() {
return event_emitter->isPressed();
}
button_event_t button_t::loop() {
ButtonEvent button_t::loop() {
if (event_emitter) {
switch (event_emitter->loop()) {
case debounce_event::types::EventPressed:
return button_event_t::Pressed;
return ButtonEvent::Pressed;
case debounce_event::types::EventReleased: {
return _buttonMapReleased(
event_emitter->getEventCount(),
@ -283,146 +266,118 @@ button_event_t button_t::loop() {
}
}
return button_event_t::None;
return ButtonEvent::None;
}
std::vector<button_t> _buttons;
// -----------------------------------------------------------------------------
unsigned char buttonCount() {
size_t buttonCount() {
return _buttons.size();
}
#if MQTT_SUPPORT
std::bitset<ButtonsMax> _buttons_mqtt_send_all(
(1 == BUTTON_MQTT_SEND_ALL_EVENTS) ? 0xFFFFFFFFUL : 0UL
button::build::mqttSendAllEvents()
? std::numeric_limits<unsigned long>::max()
: std::numeric_limits<unsigned long>::min()
);
std::bitset<ButtonsMax> _buttons_mqtt_retain(
(1 == BUTTON_MQTT_RETAIN) ? 0xFFFFFFFFUL : 0UL
button::build::mqttRetain()
? std::numeric_limits<unsigned long>::max()
: std::numeric_limits<unsigned long>::min()
);
#endif
#if WEB_SUPPORT
void _buttonWebSocketOnVisible(JsonObject& root) {
if (buttonCount() > 0) {
root["btnVisible"] = 1;
}
}
void _buttonWebSocketOnConnected(JsonObject& root) {
root["btnRepDel"] = getSetting("btnRepDel", _buttonRepeatDelay());
// XXX: unused! pending webui changes
#if 0
if (buttonCount() < 1) return;
JsonObject& module = root.createNestedObject("btn");
// -----------------------------------------------------------------------------
// TODO: hardware can sometimes use a different providers
// e.g. Sonoff Dual does not need `Pin`, `Mode` or any of `Del`
// TODO: schema names are uppercase to easily match settings?
// TODO: schema name->type map to generate WebUI elements?
#if RELAY_SUPPORT
JsonArray& schema = module.createNestedArray("_schema");
std::vector<unsigned char> _button_relays;
schema.add("Prov");
size_t _buttonRelay(size_t id) {
return _button_relays[id];
}
schema.add("GPIO");
schema.add("Mode");
schema.add("DefVal");
schema.add("PinMode");
void _buttonRelayAction(size_t id, ButtonAction action) {
auto relayId = _buttonRelay(id);
schema.add("Press");
schema.add("Click");
schema.add("Dclk");
schema.add("Lclk");
schema.add("LLclk");
schema.add("Tclk");
switch (action) {
case ButtonAction::Toggle:
relayToggle(relayId);
break;
schema.add("DebDel");
schema.add("RepDel");
schema.add("LclkDel");
schema.add("LLclkDel");
case ButtonAction::On:
relayStatus(relayId, true);
break;
#if RELAY_SUPPORT
schema.add("Relay");
#endif
case ButtonAction::Off:
relayStatus(relayId, false);
break;
#if MQTT_SUPPORT
schema.add("MqttSendAll");
schema.add("MqttRetain");
#endif
case ButtonAction::Pulse:
// TODO
break;
JsonArray& buttons = module.createNestedArray("list");
default:
break;
}
}
for (unsigned char i=0; i<buttonCount(); i++) {
JsonArray& button = buttons.createNestedArray();
#endif // RELAY_SUPPORT
// TODO: configure PIN object instead of button specifically, link PIN<->BUTTON
button.add(getSetting({"btnProv", index}, _buttonProvider(index)));
if (_buttons[i].pin()) {
button.add(getSetting({"btnGPIO", index}, _buttonPin(index)));
const auto config = _buttonRuntimeConfig(index);
button.add(static_cast<int>(config.mode));
button.add(static_cast<int>(config.default_value));
button.add(static_cast<int>(config.pin_mode));
} else {
button.add(GPIO_NONE);
button.add(static_cast<int>(BUTTON_PUSHBUTTON));
button.add(0);
button.add(0);
button.add(0);
}
// -----------------------------------------------------------------------------
button.add(_buttons[i].actions.pressed);
button.add(_buttons[i].actions.click);
button.add(_buttons[i].actions.dblclick);
button.add(_buttons[i].actions.lngclick);
button.add(_buttons[i].actions.lnglngclick);
button.add(_buttons[i].actions.trplclick);
#if WEB_SUPPORT
button.add(_buttons[i].event_delays.debounce);
button.add(_buttons[i].event_delays.repeat);
button.add(_buttons[i].event_delays.lngclick);
button.add(_buttons[i].event_delays.lnglngclick);
namespace {
#if RELAY_SUPPORT
button.add(_buttons[i].relayID);
#endif
void _buttonWebSocketOnVisible(JsonObject& root) {
if (buttonCount()) {
root["btnVisible"] = 1;
}
}
// TODO: send bitmask as number?
#if MQTT_SUPPORT
button.add(_buttons_mqtt_send_all[i] ? 1 : 0);
button.add(_buttons_mqtt_retain[i] ? 1 : 0);
#endif
void _buttonWebSocketOnConnected(JsonObject& root) {
if (buttonCount()) {
root["btnRepDel"] = getSetting("btnRepDel", button::build::repeatDelay());
}
#endif
}
bool _buttonWebSocketOnKeyCheck(const char * key, JsonVariant&) {
return (strncmp(key, "btn", 3) == 0);
}
} // namespace
#endif // WEB_SUPPORT
ButtonCustomAction _button_custom_action { nullptr };
//------------------------------------------------------------------------------
ButtonEventHandler _button_custom_action { nullptr };
void buttonSetCustomAction(ButtonCustomAction action) {
_button_custom_action = action;
void buttonSetCustomAction(ButtonEventHandler handler) {
_button_custom_action = handler;
}
bool buttonState(unsigned char id) {
std::forward_list<ButtonEventHandler> _button_notify_event;
void buttonSetEventNotify(ButtonEventHandler handler) {
_button_notify_event.push_front(handler);
}
//------------------------------------------------------------------------------
bool buttonState(size_t id) {
return (id < _buttons.size())
? _buttons[id].state()
: false;
}
ButtonAction buttonAction(unsigned char id, const button_event_t event) {
ButtonAction buttonAction(size_t id, ButtonEvent event) {
return (id < _buttons.size())
? _buttonDecodeEventAction(_buttons[id].actions, event)
: ButtonAction::None;
@ -432,93 +387,53 @@ ButtonAction buttonAction(unsigned char id, const button_event_t event) {
// (ref. https://github.com/esp8266/Arduino/pull/6950 "PROGMEM footprint cleanup for responseCodeToString")
// In this particular case, saves 76 bytes (120 vs 44)
String _buttonEventString(button_event_t event) {
String _buttonEventString(ButtonEvent event) {
const __FlashStringHelper* ptr = nullptr;
switch (event) {
case button_event_t::Pressed:
ptr = F("pressed");
break;
case button_event_t::Released:
ptr = F("released");
break;
case button_event_t::Click:
ptr = F("click");
break;
case button_event_t::DoubleClick:
ptr = F("double-click");
break;
case button_event_t::LongClick:
ptr = F("long-click");
break;
case button_event_t::LongLongClick:
ptr = F("looong-click");
break;
case button_event_t::TripleClick:
ptr = F("triple-click");
break;
case button_event_t::None:
ptr = F("none");
break;
}
return String(ptr);
}
#if RELAY_SUPPORT
unsigned char _buttonRelaySetting(unsigned char id) {
static std::vector<uint8_t> relays;
if (!relays.size()) {
relays.reserve(_buttons.size());
for (unsigned char button = 0; button < _buttons.size(); ++button) {
relays.push_back(getSetting({"btnRelay", button}, _buttonRelay(button)));
}
}
return relays[id];
}
void _buttonRelayAction(unsigned char id, ButtonAction action) {
auto relayId = _buttonRelaySetting(id);
switch (action) {
case ButtonAction::Toggle:
relayToggle(relayId);
case ButtonEvent::Pressed:
ptr = F("pressed");
break;
case ButtonAction::On:
relayStatus(relayId, true);
case ButtonEvent::Released:
ptr = F("released");
break;
case ButtonAction::Off:
relayStatus(relayId, false);
case ButtonEvent::Click:
ptr = F("click");
break;
case ButtonAction::Pulse:
// TODO
case ButtonEvent::DoubleClick:
ptr = F("double-click");
break;
default:
case ButtonEvent::LongClick:
ptr = F("long-click");
break;
case ButtonEvent::LongLongClick:
ptr = F("looong-click");
break;
case ButtonEvent::TripleClick:
ptr = F("triple-click");
break;
case ButtonEvent::None:
ptr = F("none");
break;
}
return String(ptr);
}
#endif // RELAY_SUPPORT
void buttonEvent(unsigned char id, button_event_t event) {
void buttonEvent(size_t id, ButtonEvent event) {
DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %d (%s)\n"),
id, _buttonEventNumber(event), _buttonEventString(event).c_str()
);
if (event == button_event_t::None) return;
if (event == ButtonEvent::None) {
return;
}
auto& button = _buttons[id];
auto action = _buttonDecodeEventAction(button.actions, event);
#if BROKER_SUPPORT
ButtonBroker::Publish(id, event);
#endif
for (auto& notify : _button_notify_event) {
notify(id, event);
}
#if MQTT_SUPPORT
if ((action != ButtonAction::None) || _buttons_mqtt_send_all[id]) {
@ -617,24 +532,54 @@ void buttonEvent(unsigned char id, button_event_t event) {
}
void _buttonConfigure() {
#if MQTT_SUPPORT
for (unsigned char index = 0; index < _buttons.size(); ++index) {
_buttons_mqtt_send_all[index] = getSetting({"btnMqttSendAll", index}, _buttonMqttSendAllEvents(index));
_buttons_mqtt_retain[index] = getSetting({"btnMqttRetain", index}, _buttonMqttRetain(index));
}
#endif
auto buttons = _buttons.size();
#if RELAY_SUPPORT
_button_relays.clear();
#endif
for (decltype(buttons) id = 0; id < buttons; ++id) {
#if RELAY_SUPPORT
_button_relays.push_back(getSetting({"btnRelay", id}, button::build::relay(id)));
#endif
#if MQTT_SUPPORT
_buttons_mqtt_send_all[id] = getSetting({"btnMqttSendAll", id}, button::build::mqttSendAllEvents(id));
_buttons_mqtt_retain[id] = getSetting({"btnMqttRetain", id}, button::build::mqttRetain(id));
#endif
}
}
// TODO: compatibility proxy, fetch global key before indexed
template<typename T>
unsigned long _buttonGetSetting(const char* key, unsigned char index, T default_value) {
return getSetting({key, index}, getSetting(key, default_value));
unsigned long _buttonGetDelay(const char* key, size_t index, unsigned long default_value) {
unsigned long result { default_value };
bool found { false };
auto indexed = SettingsKey(key, index);
auto global = String(key);
settings::kv_store.foreach([&](settings::kvs_type::KeyValueResult&& kv) {
if (found) {
return;
}
if ((kv.key.length != indexed.length()) && (kv.key.length != global.length())) {
return;
}
auto other = kv.key.read();
found = indexed == other;
if (found || (global == other)) {
result = settings::internal::convert<unsigned long>(kv.value.read());
}
});
return result;
}
void buttonLoop() {
for (size_t id = 0; id < _buttons.size(); ++id) {
auto event = _buttons[id].loop();
if (event != button_event_t::None) {
if (event != ButtonEvent::None) {
buttonEvent(id, event);
}
}
@ -773,15 +718,15 @@ std::vector<AnalogPin*> AnalogPin::pins;
#endif // BUTTON_PROVIDER_ANALOG_SUPPORT
BasePinPtr _buttonGpioPin(unsigned char index, ButtonProvider provider) {
BasePinPtr _buttonGpioPin(size_t index, ButtonProvider provider) {
BasePinPtr result;
auto pin = getSetting({"btnGPIO", index}, _buttonPin(index));
auto pin = getSetting({"btnGpio", index}, button::build::pin(index));
switch (provider) {
case ButtonProvider::Gpio: {
#if BUTTON_PROVIDER_GPIO_SUPPORT
auto* base = gpioBase(getSetting({"btnGPIOType", index}, _buttonPinType(index)));
auto* base = gpioBase(getSetting({"btnGpioType", index}, button::build::pinType(index)));
if (!base) {
break;
}
@ -801,7 +746,7 @@ BasePinPtr _buttonGpioPin(unsigned char index, ButtonProvider provider) {
break;
}
auto level = getSetting({"btnLevel", index}, _buttonAnalogLevel(index));
auto level = getSetting({"btnLevel", index}, button::build::analogLevel(index));
if (!AnalogPin::checkExpectedLevel(level)) {
break;
}
@ -818,30 +763,30 @@ BasePinPtr _buttonGpioPin(unsigned char index, ButtonProvider provider) {
return result;
}
ButtonActions _buttonActions(unsigned char index) {
ButtonActions _buttonActions(size_t index) {
return {
getSetting({"btnPress", index}, _buttonPress(index)),
getSetting({"btnRlse", index}, _buttonRelease(index)),
getSetting({"btnClick", index}, _buttonClick(index)),
getSetting({"btnDclk", index}, _buttonDoubleClick(index)),
getSetting({"btnLclk", index}, _buttonLongClick(index)),
getSetting({"btnLLclk", index}, _buttonLongLongClick(index)),
getSetting({"btnTclk", index}, _buttonTripleClick(index))
getSetting({"btnPress", index}, button::build::press(index)),
getSetting({"btnRlse", index}, button::build::release(index)),
getSetting({"btnClick", index}, button::build::click(index)),
getSetting({"btnDclk", index}, button::build::doubleClick(index)),
getSetting({"btnLclk", index}, button::build::longClick(index)),
getSetting({"btnLLclk", index}, button::build::longLongClick(index)),
getSetting({"btnTclk", index}, button::build::tripleClick(index))
};
}
// Note that we use settings without indexes as default values to preserve backwards compatibility
button_event_delays_t _buttonDelays(unsigned char index) {
ButtonEventDelays _buttonDelays(size_t index) {
return {
_buttonGetSetting("btnDebDel", index, _buttonDebounceDelay(index)),
_buttonGetSetting("btnRepDel", index, _buttonRepeatDelay(index)),
_buttonGetSetting("btnLclkDel", index, _buttonLongClickDelay(index)),
_buttonGetSetting("btnLLclkDel", index, _buttonLongLongClickDelay(index)),
_buttonGetDelay("btnDebDel", index, button::build::debounceDelay(index)),
_buttonGetDelay("btnRepDel", index, button::build::repeatDelay(index)),
_buttonGetDelay("btnLclkDel", index, button::build::longClickDelay(index)),
_buttonGetDelay("btnLLclkDel", index, button::build::longLongClickDelay(index)),
};
}
bool _buttonSetupProvider(unsigned char index, ButtonProvider provider) {
bool _buttonSetupProvider(size_t index, ButtonProvider provider) {
bool result { false };
switch (provider) {
@ -883,8 +828,8 @@ void _buttonSettingsMigrate(int version) {
void buttonSetup() {
_buttonSettingsMigrate(migrateVersion());
for (unsigned char index = 0; index < ButtonsMax; ++index) {
auto provider = getSetting({"btnProv", index}, _buttonProvider(index));
for (size_t index = 0; index < ButtonsMax; ++index) {
auto provider = getSetting({"btnProv", index}, button::build::provider(index));
if (!_buttonSetupProvider(index, provider)) {
break;
}
@ -892,9 +837,6 @@ void buttonSetup() {
auto count = _buttons.size();
DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), count);
if (!count) {
return;
}
#if TERMINAL_SUPPORT
terminalRegisterCommand(F("BUTTON"), [](const terminal::CommandContext& ctx) {
@ -913,19 +855,19 @@ void buttonSetup() {
});
#endif
_buttonConfigure();
// Websocket Callbacks
#if WEB_SUPPORT
if (count) {
#if WEB_SUPPORT
wsRegister()
.onVisible(_buttonWebSocketOnVisible)
.onConnected(_buttonWebSocketOnConnected)
.onKeyCheck(_buttonWebSocketOnKeyCheck);
#endif
#endif
// Register system callbacks
espurnaRegisterLoop(buttonLoop);
espurnaRegisterReload(_buttonConfigure);
_buttonConfigure();
espurnaRegisterReload(_buttonConfigure);
espurnaRegisterLoop(buttonLoop);
}
}
#endif // BUTTON_SUPPORT

+ 15
- 18
code/espurna/button.h View File

@ -10,8 +10,6 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "espurna.h"
#include "broker.h"
#include "libs/BasePin.h"
#include "libs/DebounceEvent.h"
@ -28,7 +26,7 @@ enum class ButtonProvider : int {
Analog
};
enum class button_event_t {
enum class ButtonEvent {
None,
Pressed,
Released,
@ -71,9 +69,9 @@ struct ButtonActions {
ButtonAction trplclick;
};
struct button_event_delays_t {
button_event_delays_t();
button_event_delays_t(unsigned long debounce, unsigned long repeat, unsigned long lngclick, unsigned long lnglngclick);
struct ButtonEventDelays {
ButtonEventDelays();
ButtonEventDelays(unsigned long debounce, unsigned long repeat, unsigned long lngclick, unsigned long lnglngclick);
unsigned long debounce;
unsigned long repeat;
@ -82,28 +80,27 @@ struct button_event_delays_t {
};
struct button_t {
button_t(ButtonActions&& actions, button_event_delays_t&& delays);
button_t(ButtonActions&& actions, ButtonEventDelays&& delays);
button_t(BasePinPtr&& pin, const debounce_event::types::Config& config,
ButtonActions&& actions, button_event_delays_t&& delays);
ButtonActions&& actions, ButtonEventDelays&& delays);
bool state();
button_event_t loop();
ButtonEvent loop();
std::unique_ptr<debounce_event::EventEmitter> event_emitter;
ButtonActions actions;
button_event_delays_t event_delays;
ButtonEventDelays event_delays;
};
BrokerDeclare(ButtonBroker, void(unsigned char id, button_event_t event));
using ButtonCustomAction = void(*)(unsigned char id, button_event_t event);
void buttonSetCustomAction(ButtonCustomAction);
using ButtonEventHandler = void(*)(size_t id, ButtonEvent event);
void buttonSetCustomAction(ButtonEventHandler);
void buttonSetNotifyAction(ButtonEventHandler);
bool buttonState(unsigned char id);
ButtonAction buttonAction(unsigned char id, const button_event_t event);
bool buttonState(size_t id);
ButtonAction buttonAction(size_t id, const ButtonEvent event);
void buttonEvent(unsigned char id, button_event_t event);
void buttonEvent(size_t id, ButtonEvent event);
unsigned char buttonCount();
size_t buttonCount();
void buttonSetup();

+ 74
- 29
code/espurna/button_config.h View File

@ -20,7 +20,10 @@ constexpr int SetPulldown { 1 << 6 };
} // namespace ButtonMask
constexpr unsigned char _buttonPin(unsigned char index) {
namespace button {
namespace build {
constexpr size_t pin(size_t index) {
return (
(index == 0) ? BUTTON1_PIN :
(index == 1) ? BUTTON2_PIN :
@ -33,7 +36,7 @@ constexpr unsigned char _buttonPin(unsigned char index) {
);
}
constexpr GpioType _buttonPinType(unsigned char index) {
constexpr GpioType pinType(size_t index) {
return (
(index == 0) ? BUTTON1_PIN_TYPE :
(index == 1) ? BUTTON2_PIN_TYPE :
@ -46,7 +49,7 @@ constexpr GpioType _buttonPinType(unsigned char index) {
);
}
constexpr int _buttonConfigBitmask(unsigned char index) {
constexpr int configBitmask(size_t index) {
return (
(index == 0) ? (BUTTON1_CONFIG) :
(index == 1) ? (BUTTON2_CONFIG) :
@ -59,7 +62,38 @@ constexpr int _buttonConfigBitmask(unsigned char index) {
);
}
constexpr ButtonAction _buttonRelease(unsigned char index) {
namespace internal {
constexpr debounce_event::types::Config decode(int bitmask) {
return {
((bitmask & ButtonMask::Pushbutton)
? debounce_event::types::Mode::Pushbutton
: debounce_event::types::Mode::Switch),
((bitmask & ButtonMask::DefaultLow) ? debounce_event::types::PinValue::Low
: (bitmask & ButtonMask::DefaultHigh) ? debounce_event::types::PinValue::High
: (bitmask & ButtonMask::DefaultBoot) ? debounce_event::types::PinValue::Initial
: debounce_event::types::PinValue::Low),
((bitmask & ButtonMask::SetPullup) ? debounce_event::types::PinMode::InputPullup
: (bitmask & ButtonMask::SetPulldown) ? debounce_event::types::PinMode::InputPulldown
: debounce_event::types::PinMode::Input)
};
}
} // namespace internal
constexpr debounce_event::types::Mode mode(size_t index) {
return internal::decode(configBitmask(index)).mode;
}
constexpr debounce_event::types::PinValue defaultValue(size_t index) {
return internal::decode(configBitmask(index)).default_value;
}
constexpr debounce_event::types::PinMode pinMode(size_t index) {
return internal::decode(configBitmask(index)).pin_mode;
}
constexpr ButtonAction release(size_t index) {
return (
(index == 0) ? BUTTON1_RELEASE :
(index == 1) ? BUTTON2_RELEASE :
@ -72,7 +106,7 @@ constexpr ButtonAction _buttonRelease(unsigned char index) {
);
}
constexpr ButtonAction _buttonPress(unsigned char index) {
constexpr ButtonAction press(size_t index) {
return (
(index == 0) ? BUTTON1_PRESS :
(index == 1) ? BUTTON2_PRESS :
@ -85,7 +119,7 @@ constexpr ButtonAction _buttonPress(unsigned char index) {
);
}
constexpr ButtonAction _buttonClick(unsigned char index) {
constexpr ButtonAction click(size_t index) {
return (
(index == 0) ? BUTTON1_CLICK :
(index == 1) ? BUTTON2_CLICK :
@ -98,7 +132,7 @@ constexpr ButtonAction _buttonClick(unsigned char index) {
);
}
constexpr ButtonAction _buttonDoubleClick(unsigned char index) {
constexpr ButtonAction doubleClick(size_t index) {
return (
(index == 0) ? BUTTON1_DBLCLICK :
(index == 1) ? BUTTON2_DBLCLICK :
@ -111,7 +145,7 @@ constexpr ButtonAction _buttonDoubleClick(unsigned char index) {
);
}
constexpr ButtonAction _buttonTripleClick(unsigned char index) {
constexpr ButtonAction tripleClick(size_t index) {
return (
(index == 0) ? BUTTON1_TRIPLECLICK :
(index == 1) ? BUTTON2_TRIPLECLICK :
@ -124,7 +158,7 @@ constexpr ButtonAction _buttonTripleClick(unsigned char index) {
);
}
constexpr ButtonAction _buttonLongClick(unsigned char index) {
constexpr ButtonAction longClick(size_t index) {
return (
(index == 0) ? BUTTON1_LNGCLICK :
(index == 1) ? BUTTON2_LNGCLICK :
@ -137,7 +171,7 @@ constexpr ButtonAction _buttonLongClick(unsigned char index) {
);
}
constexpr ButtonAction _buttonLongLongClick(unsigned char index) {
constexpr ButtonAction longLongClick(size_t index) {
return (
(index == 0) ? BUTTON1_LNGLNGCLICK :
(index == 1) ? BUTTON2_LNGLNGCLICK :
@ -150,7 +184,7 @@ constexpr ButtonAction _buttonLongLongClick(unsigned char index) {
);
}
constexpr unsigned char _buttonRelay(unsigned char index) {
constexpr size_t relay(size_t index) {
return (
(index == 0) ? (BUTTON1_RELAY - 1) :
(index == 1) ? (BUTTON2_RELAY - 1) :
@ -163,11 +197,11 @@ constexpr unsigned char _buttonRelay(unsigned char index) {
);
}
constexpr unsigned long _buttonDebounceDelay() {
constexpr unsigned long debounceDelay() {
return BUTTON_DEBOUNCE_DELAY;
}
constexpr unsigned long _buttonDebounceDelay(unsigned char index) {
constexpr unsigned long debounceDelay(size_t index) {
return (
(index == 0) ? BUTTON1_DEBOUNCE_DELAY :
(index == 1) ? BUTTON2_DEBOUNCE_DELAY :
@ -176,15 +210,15 @@ constexpr unsigned long _buttonDebounceDelay(unsigned char index) {
(index == 4) ? BUTTON5_DEBOUNCE_DELAY :
(index == 5) ? BUTTON6_DEBOUNCE_DELAY :
(index == 6) ? BUTTON7_DEBOUNCE_DELAY :
(index == 7) ? BUTTON8_DEBOUNCE_DELAY : _buttonDebounceDelay()
(index == 7) ? BUTTON8_DEBOUNCE_DELAY : debounceDelay()
);
}
constexpr unsigned long _buttonRepeatDelay() {
constexpr unsigned long repeatDelay() {
return BUTTON_REPEAT_DELAY;
}
constexpr unsigned long _buttonRepeatDelay(unsigned char index) {
constexpr unsigned long repeatDelay(size_t index) {
return (
(index == 0) ? BUTTON1_REPEAT_DELAY :
(index == 1) ? BUTTON2_REPEAT_DELAY :
@ -193,15 +227,15 @@ constexpr unsigned long _buttonRepeatDelay(unsigned char index) {
(index == 4) ? BUTTON5_REPEAT_DELAY :
(index == 5) ? BUTTON6_REPEAT_DELAY :
(index == 6) ? BUTTON7_REPEAT_DELAY :
(index == 7) ? BUTTON8_REPEAT_DELAY : _buttonRepeatDelay()
(index == 7) ? BUTTON8_REPEAT_DELAY : repeatDelay()
);
}
constexpr unsigned long _buttonLongClickDelay() {
constexpr unsigned long longClickDelay() {
return BUTTON_LNGCLICK_DELAY;
}
constexpr unsigned long _buttonLongClickDelay(unsigned char index) {
constexpr unsigned long longClickDelay(size_t index) {
return (
(index == 0) ? BUTTON1_LNGCLICK_DELAY :
(index == 1) ? BUTTON2_LNGCLICK_DELAY :
@ -210,15 +244,15 @@ constexpr unsigned long _buttonLongClickDelay(unsigned char index) {
(index == 4) ? BUTTON5_LNGCLICK_DELAY :
(index == 5) ? BUTTON6_LNGCLICK_DELAY :
(index == 6) ? BUTTON7_LNGCLICK_DELAY :
(index == 7) ? BUTTON8_LNGCLICK_DELAY : _buttonLongClickDelay()
(index == 7) ? BUTTON8_LNGCLICK_DELAY : longClickDelay()
);
}
constexpr unsigned long _buttonLongLongClickDelay() {
constexpr unsigned long longLongClickDelay() {
return BUTTON_LNGLNGCLICK_DELAY;
}
constexpr unsigned long _buttonLongLongClickDelay(unsigned char index) {
constexpr unsigned long longLongClickDelay(size_t index) {
return (
(index == 0) ? BUTTON1_LNGLNGCLICK_DELAY :
(index == 1) ? BUTTON2_LNGLNGCLICK_DELAY :
@ -227,11 +261,15 @@ constexpr unsigned long _buttonLongLongClickDelay(unsigned char index) {
(index == 4) ? BUTTON5_LNGLNGCLICK_DELAY :
(index == 5) ? BUTTON6_LNGLNGCLICK_DELAY :
(index == 6) ? BUTTON7_LNGLNGCLICK_DELAY :
(index == 7) ? BUTTON8_LNGLNGCLICK_DELAY : _buttonLongLongClickDelay()
(index == 7) ? BUTTON8_LNGLNGCLICK_DELAY : longLongClickDelay()
);
}
constexpr bool _buttonMqttSendAllEvents(unsigned char index) {
constexpr bool mqttSendAllEvents() {
return (1 == BUTTON_MQTT_SEND_ALL_EVENTS);
}
constexpr bool mqttSendAllEvents(size_t index) {
return (
(index == 0) ? (1 == BUTTON1_MQTT_SEND_ALL_EVENTS) :
(index == 1) ? (1 == BUTTON2_MQTT_SEND_ALL_EVENTS) :
@ -240,11 +278,15 @@ constexpr bool _buttonMqttSendAllEvents(unsigned char index) {
(index == 4) ? (1 == BUTTON5_MQTT_SEND_ALL_EVENTS) :
(index == 5) ? (1 == BUTTON6_MQTT_SEND_ALL_EVENTS) :
(index == 6) ? (1 == BUTTON7_MQTT_SEND_ALL_EVENTS) :
(index == 7) ? (1 == BUTTON8_MQTT_SEND_ALL_EVENTS) : (1 == BUTTON_MQTT_SEND_ALL_EVENTS)
(index == 7) ? (1 == BUTTON8_MQTT_SEND_ALL_EVENTS) : mqttSendAllEvents()
);
}
constexpr bool _buttonMqttRetain(unsigned char index) {
constexpr bool mqttRetain() {
return (1 == BUTTON_MQTT_RETAIN);
}
constexpr bool mqttRetain(size_t index) {
return (
(index == 0) ? (1 == BUTTON1_MQTT_RETAIN) :
(index == 1) ? (1 == BUTTON2_MQTT_RETAIN) :
@ -253,11 +295,11 @@ constexpr bool _buttonMqttRetain(unsigned char index) {
(index == 4) ? (1 == BUTTON5_MQTT_RETAIN) :
(index == 5) ? (1 == BUTTON6_MQTT_RETAIN) :
(index == 6) ? (1 == BUTTON7_MQTT_RETAIN) :
(index == 7) ? (1 == BUTTON8_MQTT_RETAIN) : (1 == BUTTON_MQTT_RETAIN)
(index == 7) ? (1 == BUTTON8_MQTT_RETAIN) : mqttRetain()
);
}
constexpr ButtonProvider _buttonProvider(unsigned char index) {
constexpr ButtonProvider provider(size_t index) {
return (
(index == 0) ? (BUTTON1_PROVIDER) :
(index == 1) ? (BUTTON2_PROVIDER) :
@ -270,7 +312,7 @@ constexpr ButtonProvider _buttonProvider(unsigned char index) {
);
}
constexpr int _buttonAnalogLevel(unsigned char index) {
constexpr int analogLevel(size_t index) {
return (
(index == 0) ? (BUTTON1_ANALOG_LEVEL) :
(index == 1) ? (BUTTON2_ANALOG_LEVEL) :
@ -282,3 +324,6 @@ constexpr int _buttonAnalogLevel(unsigned char index) {
(index == 7) ? (BUTTON8_ANALOG_LEVEL) : 0
);
}
} // namespace build
} // namespace button

+ 10
- 10
code/espurna/config/types.h View File

@ -199,16 +199,16 @@
// LED
//------------------------------------------------------------------------------
#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 LED#_RELAY relay ID
#define LED_MODE_FOLLOW_INVERSE 3 // LED will follow the opposite state of linked LED#_RELAY relay ID
#define LED_MODE_FINDME 4 // LED will be ON if all relays are OFF
#define LED_MODE_FINDME_WIFI 5 // A mixture between WIFI and FINDME
#define LED_MODE_ON 6 // LED always ON
#define LED_MODE_OFF 7 // LED always OFF
#define LED_MODE_RELAY 8 // If any relay is ON, LED will be ON, otherwise OFF
#define LED_MODE_RELAY_WIFI 9 // A mixture between WIFI and RELAY, the reverse of MIXED
#define LED_MODE_MANUAL LedMode::Manual // LED will be managed manually (OFF by default)
#define LED_MODE_WIFI LedMode::WiFi // LED will blink according to the WIFI status
#define LED_MODE_FOLLOW LedMode::Follow // LED will follow state of linked LED#_RELAY relay ID
#define LED_MODE_FOLLOW_INVERSE LedMode::FollowInverse // LED will follow the opposite state of linked LED#_RELAY relay ID
#define LED_MODE_FINDME LedMode::FindMe // LED will be ON if all relays are OFF
#define LED_MODE_FINDME_WIFI LedMode::FindMeWiFi // A mixture between WIFI and FINDME
#define LED_MODE_ON LedMode::On // LED always ON
#define LED_MODE_OFF LedMode::Off // LED always OFF
#define LED_MODE_RELAY LedMode::Relay // If any relay is ON, LED will be ON, otherwise OFF
#define LED_MODE_RELAY_WIFI LedMode::RelayWiFi // A mixture between WIFI and RELAY, the reverse of MIXED
// -----------------------------------------------------------------------------
// UI


+ 219
- 190
code/espurna/led.cpp View File

@ -3,6 +3,7 @@
LED MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2019-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
@ -20,25 +21,18 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "led_pattern.h"
#include "led_config.h"
// LED helper class
led_t::led_t(unsigned char pin_, bool inverse_, unsigned char mode_, unsigned char relayID_) :
pin(pin_),
inverse(inverse_),
mode(mode_),
relayID(relayID_)
{
pinMode(pin, OUTPUT);
void led_t::init() {
pinMode(_pin, OUTPUT);
status(false);
}
bool led_t::status() {
bool result = digitalRead(pin);
return inverse ? !result : result;
bool result = digitalRead(_pin);
return _inverse ? !result : result;
}
bool led_t::status(bool new_status) {
digitalWrite(pin, inverse ? !new_status : new_status);
digitalWrite(_pin, _inverse ? !new_status : new_status);
return new_status;
}
@ -46,50 +40,34 @@ bool led_t::toggle() {
return status(!status());
}
led_delay_t::led_delay_t(unsigned long on_ms, unsigned long off_ms, unsigned char repeats) :
type(repeats ? led_delay_mode_t::Finite : led_delay_mode_t::Infinite),
on(microsecondsToClockCycles(on_ms * 1000)),
off(microsecondsToClockCycles(off_ms * 1000)),
repeats(repeats ? repeats : 0)
{}
led_delay_t::led_delay_t(unsigned long on_ms, unsigned long off_ms) :
led_delay_t(on_ms, off_ms, 0)
{}
led_pattern_t::led_pattern_t(const std::vector<led_delay_t>& delays) :
LedPattern::LedPattern(const LedPattern::Delays& delays) :
delays(delays),
queue(),
clock_last(ESP.getCycleCount()),
clock_delay(delays.size() ? delays.back().on : 0)
clock_delay(delays.size() ? delays.back().on() : 0)
{}
bool led_pattern_t::started() {
bool LedPattern::started() {
return queue.size() > 0;
}
bool led_pattern_t::ready() {
bool LedPattern::ready() {
return delays.size() > 0;
}
void led_pattern_t::start() {
void LedPattern::start() {
clock_last = ESP.getCycleCount();
clock_delay = 0;
queue = {
delays.rbegin(), delays.rend()
};
queue = { delays.rbegin(), delays.rend() };
}
void led_pattern_t::stop() {
void LedPattern::stop() {
queue.clear();
}
// 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[] {
static const LedDelay _ledDelays[] {
{100, 100}, // Autoconfig
{100, 4900}, // Connected
{4900, 100}, // Connected (inverse)
@ -98,30 +76,85 @@ const led_delay_t _ledDelays[] {
{500, 500} // Idle
};
enum class LedDelayName {
NetworkAutoconfig,
NetworkConnected,
NetworkConnectedInverse,
NetworkConfig,
NetworkConfigInverse,
NetworkIdle,
None
};
bool _led_update { false };
std::vector<led_t> _leds;
// -----------------------------------------------------------------------------
unsigned char ledCount() {
namespace settings {
namespace internal {
template <>
LedMode convert(const String& value) {
if (value.length() == 1) {
switch (*value.c_str()) {
case '0':
return LedMode::Manual;
case '1':
return LedMode::WiFi;
#if RELAY_SUPPORT
case '2':
return LedMode::Follow;
case '3':
return LedMode::FollowInverse;
case '4':
return LedMode::FindMe;
case '5':
return LedMode::FindMeWiFi;
#endif
case '6':
return LedMode::On;
case '7':
return LedMode::Off;
#if RELAY_SUPPORT
case '8':
return LedMode::Relay;
case '9':
return LedMode::RelayWiFi;
#endif
}
}
return LedMode::Manual;
}
} // namespace internal
} // namespace settings
// -----------------------------------------------------------------------------
size_t ledCount() {
return _leds.size();
}
bool _ledStatus(led_t& led) {
return led.pattern.started() || led.status();
return led.started() || led.status();
}
bool _ledStatus(led_t& led, bool status) {
bool result = false;
// when led has pattern, status depends on whether it's running
if (led.pattern.ready()) {
auto& pattern = led.pattern();
if (pattern.ready()) {
if (status) {
if (!led.pattern.started()) {
led.pattern.start();
if (!pattern.started()) {
pattern.start();
}
result = true;
} else {
led.pattern.stop();
pattern.stop();
led.status(false);
result = false;
}
@ -137,63 +170,71 @@ bool _ledToggle(led_t& led) {
return _ledStatus(led, !_ledStatus(led));
}
bool ledStatus(unsigned char id, bool status) {
if (id >= ledCount()) return false;
return _ledStatus(_leds[id], status);
bool ledStatus(size_t id, bool status) {
if (id < ledCount()) {
return _ledStatus(_leds[id], status);
}
return status;
}
bool ledStatus(unsigned char id) {
if (id >= ledCount()) return false;
return _ledStatus(_leds[id]);
bool ledStatus(size_t id) {
if (id < ledCount()) {
return _ledStatus(_leds[id]);
}
return false;
}
const led_delay_t& _ledModeToDelay(LedMode mode) {
const LedDelay& _ledDelayFromName(LedDelayName pattern) {
static_assert(
(sizeof(_ledDelays) / sizeof(_ledDelays[0])) <= static_cast<int>(LedMode::None),
"LedMode mapping out-of-bounds"
(sizeof(_ledDelays) / sizeof(_ledDelays[0])) <= static_cast<int>(LedDelayName::None),
"Out-of-bounds"
);
return _ledDelays[static_cast<int>(mode)];
return _ledDelays[static_cast<int>(pattern)];
}
void _ledPattern(led_t& led) {
const auto clock_current = ESP.getCycleCount();
if (clock_current - led.pattern.clock_last >= led.pattern.clock_delay) {
auto& pattern = led.pattern();
if (clock_current - pattern.clock_last >= pattern.clock_delay) {
const bool status = led.toggle();
auto& current = led.pattern.queue.back();
switch (current.type) {
case led_delay_mode_t::Finite:
if (status && !--current.repeats) {
led.pattern.queue.pop_back();
if (!led.pattern.queue.size()) {
led.status(false);
return;
}
auto& current = pattern.queue.back();
switch (current.mode()) {
case LedDelayMode::Finite:
if (status && current.repeat()) {
pattern.queue.pop_back();
if (!pattern.queue.size()) {
led.status(false);
return;
}
break;
case led_delay_mode_t::Infinite:
case led_delay_mode_t::None:
default:
break;
}
break;
case LedDelayMode::Infinite:
case LedDelayMode::None:
break;
}
led.pattern.clock_delay = status ? current.on : current.off;
led.pattern.clock_last = ESP.getCycleCount();
pattern.clock_delay = status ? current.on() : current.off();
pattern.clock_last = ESP.getCycleCount();
}
}
void _ledBlink(led_t& led, const led_delay_t& delays) {
void _ledBlink(led_t& led, const LedDelay& delays) {
static auto clock_last = ESP.getCycleCount();
static auto delay_for = delays.on;
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;
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));
inline void _ledBlink(led_t& led, LedDelayName name) {
_ledBlink(led, _ledDelayFromName(name));
}
#if WEB_SUPPORT
@ -216,73 +257,87 @@ void _ledWebSocketOnConnected(JsonObject& root) {
schema.add("GPIO");
schema.add("Inv");
schema.add("Mode");
#if RELAY_SUPPORT
schema.add("Relay");
#endif
JsonArray& leds = module.createNestedArray("list");
for (unsigned char index = 0; index < ledCount(); ++index) {
for (size_t index = 0; index < ledCount(); ++index) {
JsonArray& led = leds.createNestedArray();
led.add(getSetting({"ledGPIO", index}, _ledPin(index)));
led.add(static_cast<int>(getSetting({"ledInv", index}, _ledInverse(index))));
led.add(getSetting({"ledMode", index}, _ledMode(index)));
led.add(getSetting({"ledRelay", index}, _ledRelay(index)));
led.add(getSetting({"ledGpio", index}, led::build::pin(index)));
led.add(static_cast<int>(getSetting({"ledInv", index}, led::build::inverse(index))));
led.add(static_cast<int>(getSetting({"ledMode", index}, led::build::mode(index))));
#if RELAY_SUPPORT
led.add(getSetting({"ledRelay", index}, led::build::relay(index)));
#endif
}
}
#endif
#if MQTT_SUPPORT
void _ledMQTTCallback(unsigned int type, const char * topic, const char * payload) {
void _ledMQTTCallback(unsigned int type, const char* topic, const char* payload) {
if (type == MQTT_CONNECT_EVENT) {
char buffer[strlen(MQTT_TOPIC_LED) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_LED);
mqttSubscribe(buffer);
return;
}
// Only want `led/+/<MQTT_SETTER>`
// We get the led ID from the `+`
if (type == MQTT_MESSAGE_EVENT) {
// Only want `led/+/<MQTT_SETTER>`
const String magnitude = mqttMagnitude((char *) topic);
if (!magnitude.startsWith(MQTT_TOPIC_LED)) return;
if (!magnitude.startsWith(MQTT_TOPIC_LED)) {
return;
}
// 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);
size_t ledID;
if (!tryParseId(magnitude.substring(strlen(MQTT_TOPIC_LED) + 1).c_str(), ledCount, ledID)) {
return;
}
// Check if LED is managed
if (_leds[ledID].mode != LED_MODE_MANUAL) return;
auto& led = _leds[ledID];
if (led.mode() != LED_MODE_MANUAL) {
return;
}
// Get value based on rpc payload logic (see rpc.ino)
const auto value = rpcParsePayload(payload);
switch (value) {
case PayloadStatus::On:
case PayloadStatus::Off:
_ledStatus(_leds[ledID], (value == PayloadStatus::On));
break;
case PayloadStatus::Toggle:
_ledToggle(_leds[ledID]);
break;
case PayloadStatus::Unknown:
default:
_ledLoadPattern(_leds[ledID], payload);
_ledStatus(_leds[ledID], true);
break;
case PayloadStatus::On:
case PayloadStatus::Off:
_ledStatus(led, (value == PayloadStatus::On));
break;
case PayloadStatus::Toggle:
_ledToggle(led);
break;
case PayloadStatus::Unknown:
default:
_ledLoadPattern(led, payload);
_ledStatus(led, true);
break;
}
}
}
#endif
#if RELAY_SUPPORT
std::vector<size_t> _led_relays;
#endif
void _ledConfigure() {
for (unsigned char id = 0; id < _leds.size(); ++id) {
_leds[id].mode = getSetting({"ledMode", id}, _ledMode(id));
_leds[id].relayID = getSetting({"ledRelay", id}, _ledRelay(id));
_leds[id].pattern.stop();
#if RELAY_SUPPORT
_led_relays.resize(relayCount(), RelaysMax);
#endif
for (size_t id = 0; id < _leds.size(); ++id) {
#if RELAY_SUPPORT
_led_relays[id] = getSetting({"ledRelay", id}, led::build::relay(id));
#endif
_leds[id].mode(getSetting({"ledMode", id}, led::build::mode(id)));
_leds[id].stop();
_ledLoadPattern(_leds[id], getSetting({"ledPattern", id}).c_str());
}
_led_update = true;
@ -295,21 +350,25 @@ void ledUpdate(bool do_update) {
}
void ledLoop() {
const auto wifi_state = wifiState();
for (auto& led : _leds) {
for (size_t id = 0; id < _leds.size(); ++id) {
auto& led = _leds[id];
switch (led.mode()) {
case LED_MODE_MANUAL:
break;
switch (led.mode) {
case LED_MODE_WIFI:
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
_ledBlink(led, LedDelayName::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
_ledBlink(led, LedMode::NetworkConnected);
_ledBlink(led, LedDelayName::NetworkConnected);
} else if (wifi_state & WIFI_STATE_AP) {
_ledBlink(led, LedMode::NetworkConfig);
_ledBlink(led, LedDelayName::NetworkConfig);
} else {
_ledBlink(led, LedMode::NetworkIdle);
_ledBlink(led, LedDelayName::NetworkIdle);
}
break;
@ -317,60 +376,60 @@ void ledLoop() {
case LED_MODE_FINDME_WIFI:
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
_ledBlink(led, LedDelayName::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConnected);
if (relayStatus(_led_relays[id])) {
_ledBlink(led, LedDelayName::NetworkConnected);
} else {
_ledBlink(led, LedMode::NetworkConnectedInverse);
_ledBlink(led, LedDelayName::NetworkConnectedInverse);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConfig);
if (relayStatus(_led_relays[id])) {
_ledBlink(led, LedDelayName::NetworkConfig);
} else {
_ledBlink(led, LedMode::NetworkConfigInverse);
_ledBlink(led, LedDelayName::NetworkConfigInverse);
}
} else {
_ledBlink(led, LedMode::NetworkIdle);
_ledBlink(led, LedDelayName::NetworkIdle);
}
break;
case LED_MODE_RELAY_WIFI:
if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
_ledBlink(led, LedMode::NetworkAutoconfig);
_ledBlink(led, LedDelayName::NetworkAutoconfig);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConnected);
if (relayStatus(_led_relays[id])) {
_ledBlink(led, LedDelayName::NetworkConnected);
} else {
_ledBlink(led, LedMode::NetworkConnectedInverse);
_ledBlink(led, LedDelayName::NetworkConnectedInverse);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(led.relayID)) {
_ledBlink(led, LedMode::NetworkConfig);
if (relayStatus(_led_relays[id])) {
_ledBlink(led, LedDelayName::NetworkConfig);
} else {
_ledBlink(led, LedMode::NetworkConfigInverse);
_ledBlink(led, LedDelayName::NetworkConfigInverse);
}
} else {
_ledBlink(led, LedMode::NetworkIdle);
_ledBlink(led, LedDelayName::NetworkIdle);
}
break;
case LED_MODE_FOLLOW:
if (!_led_update) break;
_ledStatus(led, relayStatus(led.relayID));
_ledStatus(led, relayStatus(_led_relays[id]));
break;
case LED_MODE_FOLLOW_INVERSE:
if (!_led_update) break;
led.status(!relayStatus(led.relayID));
_ledStatus(led, !relayStatus(led.relayID));
led.status(!relayStatus(_led_relays[id]));
_ledStatus(led, !relayStatus(_led_relays[id]));
break;
case LED_MODE_FINDME: {
if (!_led_update) break;
bool status = true;
for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
if (relayStatus(relayID)) {
for (size_t relayId = 0; relayId < relayCount(); ++relayId) {
if (relayStatus(relayId)) {
status = false;
break;
}
@ -382,8 +441,8 @@ void ledLoop() {
case LED_MODE_RELAY: {
if (!_led_update) break;
bool status = false;
for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
if (relayStatus(relayID)) {
for (size_t relayId = 0; relayId < relayCount(); ++relayId) {
if (relayStatus(relayId)) {
status = true;
break;
}
@ -406,7 +465,7 @@ void ledLoop() {
}
if (led.pattern.started()) {
if (led.started()) {
_ledPattern(led);
continue;
}
@ -430,75 +489,45 @@ void _ledSettingsMigrate(int version) {
void ledSetup() {
_ledSettingsMigrate(migrateVersion());
_leds.reserve(led::build::preconfiguredLeds());
size_t leds = 0;
#if LED1_PIN != GPIO_NONE
++leds;
#endif
#if LED2_PIN != GPIO_NONE
++leds;
#endif
#if LED3_PIN != GPIO_NONE
++leds;
#endif
#if LED4_PIN != GPIO_NONE
++leds;
#endif
#if LED5_PIN != GPIO_NONE
++leds;
#endif
#if LED6_PIN != GPIO_NONE
++leds;
#endif
#if LED7_PIN != GPIO_NONE
++leds;
#endif
#if LED8_PIN != GPIO_NONE
++leds;
#endif
_leds.reserve(leds);
for (unsigned char index=0; index < LedsMax; ++index) {
const auto pin = getSetting({"ledGPIO", index}, _ledPin(index));
for (size_t index = 0; index < LedsMax; ++index) {
const auto pin = getSetting({"ledGpio", index}, led::build::pin(index));
if (!gpioLock(pin)) {
break;
}
_leds.emplace_back(
pin,
getSetting({"ledInv", index}, _ledInverse(index)),
getSetting({"ledMode", index}, _ledMode(index)),
getSetting({"ledRelay", index}, _ledRelay(index))
);
_leds.emplace_back(pin,
getSetting({"ledInv", index}, led::build::inverse(index)),
getSetting({"ledMode", index}, led::build::mode(index)));
}
_led_update = true;
auto leds = _leds.size();
DEBUG_MSG_P(PSTR("[LED] Number of leds: %u\n"), leds);
if (leds) {
_ledConfigure();
#if MQTT_SUPPORT
#if MQTT_SUPPORT
mqttRegister(_ledMQTTCallback);
#endif
#endif
#if WEB_SUPPORT
#if WEB_SUPPORT
wsRegister()
.onVisible(_ledWebSocketOnVisible)
.onConnected(_ledWebSocketOnConnected)
.onKeyCheck(_ledWebSocketOnKeyCheck);
#endif
#endif
#if RELAY_SUPPORT
#if RELAY_SUPPORT
relaySetStatusNotify([](size_t, bool) {
ledUpdate(true);
});
#endif
DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size());
// Main callbacks
espurnaRegisterLoop(ledLoop);
espurnaRegisterReload(_ledConfigure);
#endif
espurnaRegisterLoop(ledLoop);
espurnaRegisterReload(_ledConfigure);
}
}


+ 112
- 34
code/espurna/led.h View File

@ -12,38 +12,77 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include <vector>
constexpr size_t LedsMax = 8;
constexpr size_t LedsMax { 8ul };
enum class LedMode {
NetworkAutoconfig,
NetworkConnected,
NetworkConnectedInverse,
NetworkConfig,
NetworkConfigInverse,
NetworkIdle,
None
Manual,
WiFi,
Follow,
FollowInverse,
FindMe,
FindMeWiFi,
On,
Off,
Relay,
RelayWiFi
};
enum class led_delay_mode_t {
enum class LedDelayMode {
Finite,
Infinite,
None
};
struct led_delay_t {
led_delay_t() = delete;
led_delay_t(unsigned long on_ms, unsigned long off_ms);
led_delay_t(unsigned long on_ms, unsigned long off_ms, unsigned char repeats);
led_delay_mode_t type;
unsigned long on;
unsigned long off;
unsigned char repeats;
struct LedDelay {
LedDelay() = delete;
constexpr LedDelay(unsigned long on_ms, unsigned long off_ms, unsigned char repeats) :
_mode(repeats ? LedDelayMode::Finite : LedDelayMode::Infinite),
_on(microsecondsToClockCycles(on_ms * 1000)),
_off(microsecondsToClockCycles(off_ms * 1000)),
_repeats(repeats)
{}
constexpr LedDelay(unsigned long on_ms, unsigned long off_ms) :
LedDelay(on_ms, off_ms, 0)
{}
LedDelayMode mode() const {
return _mode;
}
unsigned long on() const {
return _on;
}
unsigned long off() const {
return _off;
}
unsigned char repeats() const {
return _repeats;
}
bool repeat() {
if (_repeats) {
--_repeats;
}
return _repeats;
}
private:
LedDelayMode _mode;
unsigned long _on;
unsigned long _off;
unsigned char _repeats;
};
struct led_pattern_t {
led_pattern_t() = default;
led_pattern_t(const std::vector<led_delay_t>& delays);
struct LedPattern {
using Delays = std::vector<LedDelay>;
LedPattern() = default;
LedPattern(const Delays& delays);
void start();
void stop();
@ -51,32 +90,71 @@ struct led_pattern_t {
bool started();
bool ready();
std::vector<led_delay_t> delays;
std::vector<led_delay_t> queue;
Delays delays;
Delays queue;
unsigned long clock_last;
unsigned long clock_delay;
};
struct led_t {
led_t() = delete;
led_t(unsigned char pin, bool inverse, unsigned char mode, unsigned char relayID);
led_t(unsigned char pin, bool inverse, LedMode mode) :
_pin(pin),
_inverse(inverse),
_mode(mode)
{
init();
}
unsigned char pin() const {
return _pin;
}
LedMode mode() const {
return _mode;
}
void mode(LedMode mode) {
_mode = mode;
}
bool inverse() const {
return _inverse;
}
LedPattern& pattern() {
return _pattern;
}
void init();
void start() {
_pattern.stop();
}
bool started() {
return _pattern.started();
}
void stop() {
_pattern.stop();
}
bool status();
bool status(bool new_status);
bool toggle();
unsigned char pin;
bool inverse;
unsigned char mode;
unsigned char relayID;
led_pattern_t pattern;
private:
unsigned char _pin;
bool _inverse;
LedMode _mode;
LedPattern _pattern;
};
size_t ledCount();
void ledUpdate(bool do_update);
unsigned char ledCount();
bool ledStatus(unsigned char id, bool status);
bool ledStatus(unsigned char id);
bool ledStatus(size_t id, bool status);
bool ledStatus(size_t id);
void ledSetup();

+ 40
- 5
code/espurna/led_config.h View File

@ -8,7 +8,39 @@ LED MODULE
#include "espurna.h"
constexpr const unsigned char _ledPin(unsigned char index) {
namespace led {
namespace build {
constexpr size_t preconfiguredLeds() {
return 0ul
#if LED1_PIN != GPIO_NONE
+ 1ul
#endif
#if LED2_PIN != GPIO_NONE
+ 1ul
#endif
#if LED3_PIN != GPIO_NONE
+ 1ul
#endif
#if LED4_PIN != GPIO_NONE
+ 1ul
#endif
#if LED5_PIN != GPIO_NONE
+ 1ul
#endif
#if LED6_PIN != GPIO_NONE
+ 1ul
#endif
#if LED7_PIN != GPIO_NONE
+ 1ul
#endif
#if LED8_PIN != GPIO_NONE
+ 1ul
#endif
;
}
constexpr unsigned char pin(size_t index) {
return (
(index == 0) ? LED1_PIN :
(index == 1) ? LED2_PIN :
@ -21,7 +53,7 @@ constexpr const unsigned char _ledPin(unsigned char index) {
);
}
constexpr const unsigned char _ledMode(unsigned char index) {
constexpr LedMode mode(size_t index) {
return (
(index == 0) ? LED1_MODE :
(index == 1) ? LED2_MODE :
@ -30,11 +62,11 @@ constexpr const unsigned char _ledMode(unsigned char index) {
(index == 4) ? LED5_MODE :
(index == 5) ? LED6_MODE :
(index == 6) ? LED7_MODE :
(index == 7) ? LED8_MODE : LED_MODE_WIFI
(index == 7) ? LED8_MODE : LedMode::Manual
);
}
constexpr const unsigned char _ledRelay(unsigned char index) {
constexpr unsigned char relay(size_t index) {
return (
(index == 0) ? (LED1_RELAY - 1) :
(index == 1) ? (LED2_RELAY - 1) :
@ -47,7 +79,7 @@ constexpr const unsigned char _ledRelay(unsigned char index) {
);
}
constexpr const bool _ledInverse(unsigned char index) {
constexpr bool inverse(size_t index) {
return (
(index == 0) ? (1 == LED1_PIN_INVERSE) :
(index == 1) ? (1 == LED2_PIN_INVERSE) :
@ -59,3 +91,6 @@ constexpr const bool _ledInverse(unsigned char index) {
(index == 7) ? (1 == LED8_PIN_INVERSE) : false
);
}
} // namespace build
} // namespace led

+ 4
- 5
code/espurna/led_pattern.h View File

@ -16,7 +16,7 @@ Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
// Scans input string with format
// '<on1>,<off1>,<repeats1> <on2>,<off2>,<repeats2> ...'
// Directly changing `led.pattern.delays` contents
// Directly changing `LedPattern::delays` contents
void _ledLoadPattern(led_t& led, const char* input) {
char buffer[16];
@ -28,7 +28,8 @@ void _ledLoadPattern(led_t& led, const char* input) {
const char* p = input;
const char* marker;
led.pattern.delays.clear();
auto& pattern = led.pattern();
pattern.delays.clear();
loop:
const char *yyt1;const char *yyt2;const char *yyt3;
@ -153,9 +154,7 @@ yy17:
buffer[int(p - d3)] = '\0';
repeats = strtoul(buffer, nullptr, 10);
led.pattern.delays.emplace_back(
on, off, repeats
);
pattern.delays.emplace_back(on, off, repeats);
goto loop;
}


+ 3
- 4
code/espurna/led_pattern.h.in View File

@ -26,7 +26,8 @@ void _ledLoadPattern(led_t& led, const char* input) {
const char* p = input;
const char* marker;
led.pattern.delays.clear();
auto& pattern = led.pattern;
pattern.delays.clear();
loop:
/*!stags:re2c format = 'const char *@@;'; */
@ -61,9 +62,7 @@ loop:
buffer[int(p - d3)] = '\0';
repeats = strtoul(buffer, nullptr, 10);
led.pattern.delays.emplace_back(
on, off, repeats
);
pattern.delays.emplace_back(on, off, repeats);
goto loop;
}


+ 68
- 72
code/espurna/light.cpp View File

@ -75,7 +75,7 @@ constexpr long Hsv::ValueMax;
class LightChannelProvider : public RelayProviderBase {
public:
LightChannelProvider() = delete;
explicit LightChannelProvider(unsigned char id) :
explicit LightChannelProvider(size_t id) :
_id(id)
{}
@ -90,7 +90,7 @@ public:
}
private:
unsigned char _id { RELAY_NONE };
size_t _id { RelaysMax };
};
class LightGlobalProvider : public RelayProviderBase {
@ -382,8 +382,8 @@ my92xx_model_t convert(const String& value) {
#endif
bool _setValue(unsigned char, unsigned int) __attribute__((warn_unused_result));
bool _setValue(unsigned char id, unsigned int value) {
bool _setValue(size_t, unsigned int) __attribute__((warn_unused_result));
bool _setValue(size_t id, unsigned int value) {
if (_light_channels[id].value != value) {
_light_channels[id].value = value;
return true;
@ -392,7 +392,7 @@ bool _setValue(unsigned char id, unsigned int value) {
return false;
}
void _setInputValue(unsigned char id, long value) {
void _setInputValue(size_t id, long value) {
_light_channels[id].inputValue = std::clamp(value, Light::ValueMin, Light::ValueMax);
}
@ -408,7 +408,7 @@ bool _lightApplyBrightnessChannels(size_t channels) {
channels = std::min(channels, lightChannels());
OnceFlag changed;
for (unsigned char channel = 0; channel < lightChannels(); ++channel) {
for (size_t channel = 0; channel < lightChannels(); ++channel) {
if (channel >= channels) {
scale = 1.0f;
}
@ -468,32 +468,33 @@ bool _lightApplyBrightnessColor() {
// Scale up to equal input values. So [250,150,50] -> [200,100,0,50] -> [250, 125, 0, 63]
unsigned char max_in = std::max({_light_channels[0].inputValue, _light_channels[1].inputValue, _light_channels[2].inputValue});
unsigned char max_out = std::max({_light_channels[0].value, _light_channels[1].value, _light_channels[2].value, _light_channels[3].value});
unsigned char channelSize = _light_use_cct ? 5 : 4;
size_t channelSize = _light_use_cct ? 5 : 4;
if (_light_use_cct) {
max_out = std::max(max_out, _light_channels[4].value);
}
double factor = (max_out > 0) ? (double) (max_in / max_out) : 0;
for (unsigned char i=0; i < channelSize; i++) {
for (size_t i = 0; i < channelSize; ++i) {
changed = _setValue(i, lround((double) _light_channels[i].value * factor * brightness));
}
// Scale white channel to match brightness
for (unsigned char i=3; i < channelSize; i++) {
for (size_t i = 3; i < channelSize; ++i) {
changed = _setValue(i, constrain(static_cast<unsigned int>(_light_channels[i].value * LIGHT_WHITE_FACTOR), Light::BrightnessMin, Light::BrightnessMax));
}
// For the rest of channels, don't apply brightness, it is already in the inputValue
// i should be 4 when RGBW and 5 when RGBWW
for (unsigned char i=channelSize; i < _light_channels.size(); i++) {
for (size_t i = channelSize; i < _light_channels.size(); ++i) {
changed = _setValue(i, _light_channels[i].inputValue);
}
return changed.get();
}
char _lightTag(size_t channels, unsigned char index) {
char _lightTag(size_t channels, size_t index) {
constexpr size_t Columns { 5ul };
constexpr size_t Rows { 5ul };
@ -513,12 +514,12 @@ char _lightTag(size_t channels, unsigned char index) {
return 0;
}
char _lightTag(unsigned char index) {
char _lightTag(size_t index) {
return _lightTag(_light_channels.size(), index);
}
// UI hint about channel distribution
const char* _lightDesc(size_t channels, unsigned char index) {
const char* _lightDesc(size_t channels, size_t index) {
const __FlashStringHelper* ptr { F("UNKNOWN") };
switch (_lightTag(channels, index)) {
case 'W':
@ -541,7 +542,7 @@ const char* _lightDesc(size_t channels, unsigned char index) {
return reinterpret_cast<const char*>(ptr);
}
const char* _lightDesc(unsigned char index) {
const char* _lightDesc(size_t index) {
return _lightDesc(_light_channels.size(), index);
}
@ -827,11 +828,11 @@ void _lightAdjustBrightness(const String& payload) {
_lightAdjustBrightness(payload.c_str());
}
void _lightAdjustChannel(unsigned char id, const char* payload) {
void _lightAdjustChannel(size_t id, const char* payload) {
lightChannel(id, _lightAdjustValue(lightChannel(id), payload));
}
void _lightAdjustChannel(unsigned char id, const String& payload) {
void _lightAdjustChannel(size_t id, const String& payload) {
_lightAdjustChannel(id, payload.c_str());
}
@ -967,7 +968,7 @@ public:
state(_state);
}
for (unsigned char index = 0; index < _transitions.size(); ++index) {
for (size_t index = 0; index < _transitions.size(); ++index) {
auto& transition = _transitions[index];
if (!transition.count) {
continue;
@ -1118,7 +1119,7 @@ inline bool _lightPwmMap(long value, long& result) {
}
// both require original values to be scaled into a PWM frequency
void _lightProviderHandleValue(unsigned char channel, float value) {
void _lightProviderHandleValue(size_t channel, float value) {
long pwm;
if (!_lightPwmMap(std::lround(value), pwm)) {
return;
@ -1145,7 +1146,7 @@ void _lightProviderHandleState(bool state) {
_light_provider->state(state);
}
void _lightProviderHandleValue(unsigned char channel, float value) {
void _lightProviderHandleValue(size_t channel, float value) {
_light_provider->channel(channel, value);
}
@ -1281,7 +1282,7 @@ void lightSave(bool save) {
void _lightSaveRtcmem() {
auto channels = LightRtcmem::defaultChannels();
for (unsigned char channel = 0; channel < lightChannels(); ++channel) {
for (size_t channel = 0; channel < lightChannels(); ++channel) {
channels[channel] = _light_channels[channel].inputValue;
}
@ -1294,7 +1295,7 @@ void _lightRestoreRtcmem() {
LightRtcmem light(value);
auto& channels = light.channels();
for (unsigned char channel = 0; channel < lightChannels(); ++channel) {
for (size_t channel = 0; channel < lightChannels(); ++channel) {
_light_channels[channel].inputValue = channels[channel];
}
@ -1307,7 +1308,7 @@ void _lightSaveSettings() {
return;
}
for (unsigned char channel = 0; channel < lightChannels(); ++channel) {
for (size_t channel = 0; channel < lightChannels(); ++channel) {
setSetting({"ch", channel}, _light_channels[channel].inputValue);
}
@ -1318,7 +1319,7 @@ void _lightSaveSettings() {
}
void _lightRestoreSettings() {
for (unsigned char channel = 0; channel < lightChannels(); ++channel) {
for (size_t channel = 0; channel < lightChannels(); ++channel) {
auto value = getSetting({"ch", channel}, (channel == 0) ? Light::ValueMax : Light::ValueMin);
_light_channels[channel].inputValue = value;
}
@ -1349,16 +1350,8 @@ bool _lightParsePayload(const String& payload) {
return _lightParsePayload(payload.c_str());
}
bool _lightTryParseChannel(const char* p, unsigned char& id) {
char* endp { nullptr };
const unsigned long result { strtoul(p, &endp, 10) };
if ((endp == p) || (*endp != '\0') || (result >= lightChannels())) {
DEBUG_MSG_P(PSTR("[LIGHT] Invalid channelID (%s)\n"), p);
return false;
}
id = result;
return true;
bool _lightTryParseChannel(const char* p, size_t& id) {
return tryParseId(p, lightChannels, id);
}
// -----------------------------------------------------------------------------
@ -1485,7 +1478,7 @@ void _lightMqttCallback(unsigned int type, const char * topic, const char * payl
// Channel
if (t.startsWith(MQTT_TOPIC_CHANNEL)) {
unsigned char id;
size_t id;
if (!_lightTryParseChannel(t.c_str() + strlen(MQTT_TOPIC_CHANNEL) + 1, id)) {
return;
}
@ -1561,7 +1554,7 @@ void lightMQTTGroup() {
template <typename T>
bool _lightApiTryHandle(ApiRequest& request, T&& callback) {
auto id_param = request.wildcard(0);
unsigned char id;
size_t id;
if (!_lightTryParseChannel(id_param.c_str(), id)) {
return false;
}
@ -1659,13 +1652,13 @@ void _lightApiSetup() {
apiRegister(F(MQTT_TOPIC_CHANNEL "/+"),
[](ApiRequest& request) {
return _lightApiTryHandle(request, [&](unsigned char id) {
return _lightApiTryHandle(request, [&](size_t id) {
request.send(String(static_cast<int>(_light_channels[id].target)));
return true;
});
},
[](ApiRequest& request) {
return _lightApiTryHandle(request, [&](unsigned char id) {
return _lightApiTryHandle(request, [&](size_t id) {
_lightAdjustChannel(id, request.param(F("value")));
lightUpdate();
return true;
@ -1716,7 +1709,7 @@ void _lightWebSocketStatus(JsonObject& root) {
root["useCCT"] = _light_use_cct;
}
JsonArray& channels = root.createNestedArray("channels");
for (unsigned char id=0; id < _light_channels.size(); id++) {
for (size_t id = 0; id < _light_channels.size(); ++id) {
channels.add(lightChannel(id));
}
root["brightness"] = lightBrightness();
@ -1813,29 +1806,26 @@ void _lightInitCommands() {
return;
}
auto id = -1;
if (ctx.argc > 1) {
id = ctx.argv[1].toInt();
}
auto description = [&](unsigned char channel) {
ctx.output.printf("#%hhu (%s): %hhu\n", channel, _lightDesc(channels, channel), _light_channels[channel].inputValue);
auto description = [&](size_t channel) {
ctx.output.printf("#%u (%s): %hhu\n", channel, _lightDesc(channels, channel), _light_channels[channel].inputValue);
};
if (id < 0 || id >= static_cast<decltype(id)>(channels)) {
for (unsigned char index = 0; index < channels; ++index) {
description(index);
if (ctx.argc > 2) {
size_t id;
if (!_lightTryParseChannel(ctx.argv[1].c_str(), id)) {
terminalError(ctx, F("Invalid channel ID"));
return;
}
terminalOK(ctx);
return;
}
if (ctx.argc > 2) {
_lightAdjustChannel(id, ctx.argv[2].c_str());
lightUpdate();
description(id);
} else {
for (size_t index = 0; index < channels; ++index) {
description(index);
}
}
description(id);
terminalOK(ctx);
});
@ -2116,17 +2106,19 @@ void lightUpdate() {
lightUpdate(lightTransition());
}
void lightState(unsigned char id, bool state) {
if (id >= _light_channels.size()) return;
if (_light_channels[id].state != state) {
void lightState(size_t id, bool state) {
if ((id < _light_channels.size()) && _light_channels[id].state != state) {
_light_channels[id].state = state;
_light_state_changed = true;
}
}
bool lightState(unsigned char id) {
if (id >= _light_channels.size()) return false;
return _light_channels[id].state;
bool lightState(size_t id) {
if (id < _light_channels.size()) {
return _light_channels[id].state;
}
return false;
}
void lightState(bool state) {
@ -2232,18 +2224,22 @@ Light::MiredsRange lightMiredsRange() {
return { _light_cold_mireds, _light_warm_mireds };
}
long lightChannel(unsigned char id) {
if (id >= _light_channels.size()) return 0;
return _light_channels[id].inputValue;
long lightChannel(size_t id) {
if (id < _light_channels.size()) {
return _light_channels[id].inputValue;
}
return 0l;
}
void lightChannel(unsigned char id, long value) {
if (id >= _light_channels.size()) return;
_setInputValue(id, value);
void lightChannel(size_t id, long value) {
if (id < _light_channels.size()) {
_setInputValue(id, value);
}
}
void lightChannelStep(unsigned char id, long steps, long multiplier) {
lightChannel(id, static_cast<int>(lightChannel(id)) + (steps * multiplier));
void lightChannelStep(size_t id, long steps, long multiplier) {
lightChannel(id, lightChannel(id) + (steps * multiplier));
}
long lightBrightness() {
@ -2316,7 +2312,7 @@ const unsigned long _light_iofunc[16] PROGMEM = {
namespace {
inline bool _lightUseGamma(size_t channels, unsigned char index) {
inline bool _lightUseGamma(size_t channels, size_t index) {
switch (_lightTag(channels, index)) {
case 'R':
case 'G':
@ -2327,7 +2323,7 @@ inline bool _lightUseGamma(size_t channels, unsigned char index) {
return false;
}
inline bool _lightUseGamma(unsigned char index) {
inline bool _lightUseGamma(size_t index) {
return _lightUseGamma(_light_channels.size(), index);
}
@ -2376,7 +2372,7 @@ void _lightConfigure() {
_light_transition_step = getSetting("ltStep", LIGHT_TRANSITION_STEP);
_light_use_gamma = getSetting("useGamma", 1 == LIGHT_USE_GAMMA);
for (unsigned char index = 0; index < lightChannels(); ++index) {
for (size_t index = 0; index < lightChannels(); ++index) {
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
_light_my92xx_channel_map[index] = getSetting({"ltMy92xxCh", index}, Light::build::my92xxChannel(index));
#endif
@ -2535,7 +2531,7 @@ void lightSetup() {
getSetting("ltMy92xxDiGPIO", Light::build::my92xxDiPin()),
getSetting("ltMy92xxDckiGPIO", Light::build::my92xxDckiPin()),
Light::build::my92xxCommand());
for (unsigned char index = 0; index < channels; ++index) {
for (size_t index = 0; index < channels; ++index) {
_light_channels.emplace_back(GPIO_NONE);
}
}
@ -2547,7 +2543,7 @@ void lightSetup() {
// 3-tuples of MUX_REGISTER, MUX_VALUE and GPIO number
uint32_t io_info[Light::ChannelsMax][3];
for (unsigned char index = 0; index < Light::ChannelsMax; ++index) {
for (size_t index = 0; index < Light::ChannelsMax; ++index) {
// Load up until first GPIO_NONE. Allow settings to override, but not remove values
const auto pin = getSetting({"ltDimmerGPIO", index}, Light::build::channelPin(index));


+ 6
- 6
code/espurna/light.h View File

@ -164,7 +164,7 @@ class LightProvider {
public:
virtual void update() = 0;
virtual void state(bool) = 0;
virtual void channel(unsigned char ch, float value) = 0;
virtual void channel(size_t ch, float value) = 0;
};
struct LightTransition {
@ -221,8 +221,8 @@ long lightColdWhite();
void lightWarmWhite(long value);
long lightWarmWhite();
void lightState(unsigned char i, bool state);
bool lightState(unsigned char i);
void lightState(size_t id, bool state);
bool lightState(size_t id);
void lightState(bool state);
bool lightState();
@ -230,11 +230,11 @@ bool lightState();
void lightBrightness(long brightness);
long lightBrightness();
long lightChannel(unsigned char id);
void lightChannel(unsigned char id, long value);
long lightChannel(size_t id);
void lightChannel(size_t id, long value);
void lightBrightnessStep(long steps, long multiplier = LIGHT_STEP);
void lightChannelStep(unsigned char id, long steps, long multiplier = LIGHT_STEP);
void lightChannelStep(size_t id, long steps, long multiplier = LIGHT_STEP);
void lightUpdate(bool save, LightTransition transition, Light::Report report);
void lightUpdate(bool save, LightTransition transition, int report);


+ 6
- 6
code/espurna/light_config.h View File

@ -15,7 +15,7 @@ constexpr unsigned char enablePin() {
return LIGHT_ENABLE_PIN;
}
constexpr unsigned char channelPin(unsigned char index) {
constexpr unsigned char channelPin(size_t index) {
return (
(index == 0) ? LIGHT_CH1_PIN :
(index == 1) ? LIGHT_CH2_PIN :
@ -25,7 +25,7 @@ constexpr unsigned char channelPin(unsigned char index) {
);
}
constexpr bool inverse(unsigned char index) {
constexpr bool inverse(size_t index) {
return (
(index == 0) ? (1 == LIGHT_CH1_INVERSE) :
(index == 1) ? (1 == LIGHT_CH2_INVERSE) :
@ -41,7 +41,7 @@ constexpr my92xx_cmd_t my92xxCommand() {
return MY92XX_COMMAND;
}
constexpr unsigned char my92xxChannels() {
constexpr size_t my92xxChannels() {
return MY92XX_CHANNELS;
}
@ -67,15 +67,15 @@ constexpr unsigned char _my92xx_mapping[MY92XX_CHANNELS] {
MY92XX_MAPPING
};
constexpr unsigned char my92xxChannel(unsigned char)
constexpr unsigned char my92xxChannel(size_t)
__attribute__((deprecated("MY92XX_CH# flags should be used instead of MY92XX_MAPPING")));
constexpr unsigned char my92xxChannel(unsigned char channel) {
constexpr unsigned char my92xxChannel(size_t channel) {
return _my92xx_mapping[channel];
}
#else
constexpr unsigned char my92xxChannel(unsigned char channel) {
constexpr unsigned char my92xxChannel(size_t channel) {
return (channel == 0) ? MY92XX_CH1 :
(channel == 1) ? MY92XX_CH2 :
(channel == 2) ? MY92XX_CH3 :


+ 118
- 122
code/espurna/relay.cpp View File

@ -62,7 +62,7 @@ struct RelayMaskHelper {
_mask.reset();
}
void set(unsigned char id, bool status) {
void set(size_t id, bool status) {
_mask.set(id, status);
}
@ -255,8 +255,8 @@ std::vector<relay_t> _relays;
bool _relayRecursive { false };
size_t _relayDummy { 0ul };
unsigned long _relay_flood_window { _relayFloodWindowMs() };
unsigned long _relay_flood_changes { _relayFloodChanges() };
unsigned long _relay_flood_window { relay::build::floodWindowMs() };
unsigned long _relay_flood_changes { relay::build::floodChanges() };
unsigned long _relay_delay_interlock;
int _relay_sync_mode { RELAY_SYNC_ANY };
@ -337,7 +337,7 @@ RelayProviderBase* _relayDummyProvider() {
// Real GPIO provider, using BasePin interface to implement writers
struct GpioProvider : public RelayProviderBase {
GpioProvider(unsigned char id, RelayType type, std::unique_ptr<BasePin>&& pin, std::unique_ptr<BasePin>&& reset_pin) :
GpioProvider(size_t id, RelayType type, std::unique_ptr<BasePin>&& pin, std::unique_ptr<BasePin>&& reset_pin) :
_id(id),
_type(type),
_pin(std::move(pin)),
@ -397,7 +397,7 @@ struct GpioProvider : public RelayProviderBase {
}
private:
unsigned char _id { RelaysMax };
size_t _id { RelaysMax };
RelayType _type { RelayType::Normal };
std::unique_ptr<BasePin> _pin;
std::unique_ptr<BasePin> _reset_pin;
@ -410,7 +410,7 @@ private:
class DualProvider : public RelayProviderBase {
public:
DualProvider() = delete;
explicit DualProvider(unsigned char id) : _id(id) {
explicit DualProvider(size_t id) : _id(id) {
_instances.push_back(this);
}
@ -444,7 +444,7 @@ public:
}
}
unsigned char relayId() const {
size_t relayId() const {
return _id;
}
@ -465,7 +465,7 @@ public:
static void flush() {
bool sync { true };
RelayMaskHelper mask;
for (unsigned char index = 0; index < _instances.size(); ++index) {
for (size_t index = 0; index < _instances.size(); ++index) {
bool status { relayStatus(_instances[index]->relayId()) };
sync = sync && status;
mask.set(index, status);
@ -505,13 +505,13 @@ public:
}
// Then, manage relays individually
for (unsigned char index = 0; index < _instances.size(); ++index) {
for (size_t index = 0; index < _instances.size(); ++index) {
relayStatus(_instances[index]->relayId(), mask[index]);
}
}
private:
unsigned char _id { 0 };
size_t _id;
static std::vector<DualProvider*> _instances;
};
@ -527,7 +527,7 @@ std::vector<DualProvider*> DualProvider::_instances;
class StmProvider : public RelayProviderBase {
public:
StmProvider() = delete;
explicit StmProvider(unsigned char id) :
explicit StmProvider(size_t id) :
_id(id)
{}
@ -564,7 +564,7 @@ public:
}
private:
unsigned char _id;
size_t _id;
};
#endif // RELAY_PROVIDER_STM_SUPPORT
@ -573,18 +573,11 @@ private:
// UTILITY
// -----------------------------------------------------------------------------
bool _relayTryParseId(const char* p, unsigned char& relayID) {
char* endp { nullptr };
const unsigned long result { strtoul(p, &endp, 10) };
if ((endp == p) || (*endp != '\0') || (result >= relayCount())) {
return false;
}
relayID = result;
return true;
bool _relayTryParseId(const char* p, size_t& id) {
return tryParseId(p, relayCount, id);
}
bool _relayTryParseIdFromPath(const String& endpoint, unsigned char& relayID) {
bool _relayTryParseIdFromPath(const String& endpoint, size_t& id) {
int next_slash { endpoint.lastIndexOf('/') };
if (next_slash < 0) {
return false;
@ -596,10 +589,10 @@ bool _relayTryParseIdFromPath(const String& endpoint, unsigned char& relayID) {
return false;
}
return _relayTryParseId(p, relayID);
return _relayTryParseId(p, id);
}
void _relayHandleStatus(unsigned char id, PayloadStatus status) {
void _relayHandleStatus(size_t id, PayloadStatus status) {
switch (status) {
case PayloadStatus::Off:
relayStatus(id, false);
@ -615,7 +608,7 @@ void _relayHandleStatus(unsigned char id, PayloadStatus status) {
}
}
bool _relayHandlePayload(unsigned char id, const char* payload) {
bool _relayHandlePayload(size_t id, const char* payload) {
auto status = relayParsePayload(payload);
if (status != PayloadStatus::Unknown) {
_relayHandleStatus(id, status);
@ -626,11 +619,11 @@ bool _relayHandlePayload(unsigned char id, const char* payload) {
return false;
}
bool _relayHandlePayload(unsigned char id, const String& payload) {
bool _relayHandlePayload(size_t id, const String& payload) {
return _relayHandlePayload(id, payload.c_str());
}
bool _relayHandlePulsePayload(unsigned char id, const char* payload) {
bool _relayHandlePulsePayload(size_t id, const char* payload) {
unsigned long pulse = 1000 * atof(payload);
if (!pulse) {
return false;
@ -647,7 +640,7 @@ bool _relayHandlePulsePayload(unsigned char id, const char* payload) {
return true;
}
bool _relayHandlePulsePayload(unsigned char id, const String& payload) {
bool _relayHandlePulsePayload(size_t id, const String& payload) {
return _relayHandlePulsePayload(id, payload.c_str());
}
@ -665,7 +658,7 @@ PayloadStatus _relayInvertStatus(PayloadStatus status) {
return PayloadStatus::Unknown;
}
PayloadStatus _relayPayloadStatus(unsigned char id) {
PayloadStatus _relayPayloadStatus(size_t id) {
if (id < _relays.size()) {
return _relays[id].current_status
? PayloadStatus::On
@ -689,7 +682,7 @@ void _relayUnlockAll() {
_relay_sync_locked = false;
}
bool _relayStatusLock(unsigned char id, bool status) {
bool _relayStatusLock(size_t id, bool status) {
if (_relays[id].lock != RelayLock::None) {
bool lock = _relays[id].lock == RelayLock::On;
if ((lock != status) || (lock != _relays[id].target_status)) {
@ -705,7 +698,7 @@ bool _relayStatusLock(unsigned char id, bool status) {
// https://github.com/xoseperez/espurna/issues/1510#issuecomment-461894516
// completely reset timing on the other relay to sync with this one
// to ensure that they change state sequentially
void _relaySyncRelaysDelay(unsigned char first, unsigned char second) {
void _relaySyncRelaysDelay(size_t first, size_t second) {
_relays[second].fw_start = _relays[first].change_start;
_relays[second].fw_count = 1;
_relays[second].change_delay = std::max({
@ -780,7 +773,7 @@ inline void _relayMaskSettings(const RelayMaskHelper& mask) {
// Pulse timers (timer after ON or OFF event)
// TODO: integrate with scheduled ON or OFF
void relayPulse(unsigned char id) {
void relayPulse(size_t id) {
auto& relay = _relays[id];
if (!relay.pulseTicker) {
@ -814,15 +807,15 @@ void relayPulse(unsigned char id) {
DEBUG_MSG_P(PSTR("[RELAY] Scheduling relay #%u back in %lums (pulse)\n"), id, ms);
relay.pulseTicker->once_ms(ms, relayToggle, id);
// Reconfigure after dynamic pulse
relay.pulse = getSetting({"relayPulse", id}, _relayPulseMode(id));
relay.pulse_ms = static_cast<unsigned long>(1000.0f * getSetting({"relayTime", id}, _relayPulseTime(id)));
relay.pulse = getSetting({"relayPulse", id},relay::build::pulseMode(id));
relay.pulse_ms = static_cast<unsigned long>(1000.0f * getSetting({"relayTime", id}, relay::build::pulseTime(id)));
}
}
// General relay status control
bool relayStatus(unsigned char id, bool status, bool report, bool group_report) {
bool relayStatus(size_t id, bool status, bool report, bool group_report) {
if ((id >= RelaysMax) || (id >= _relays.size())) {
return false;
@ -903,7 +896,7 @@ bool relayStatus(unsigned char id, bool status, bool report, bool group_report)
}
bool relayStatus(unsigned char id, bool status) {
bool relayStatus(size_t id, bool status) {
#if MQTT_SUPPORT
return relayStatus(id, status, mqttForward(), true);
#else
@ -911,7 +904,7 @@ bool relayStatus(unsigned char id, bool status) {
#endif
}
bool relayStatus(unsigned char id) {
bool relayStatus(size_t id) {
// Check that relay ID is valid
if (id >= _relays.size()) return false;
@ -921,35 +914,40 @@ bool relayStatus(unsigned char id) {
}
bool relayStatusTarget(unsigned char id) {
bool relayStatusTarget(size_t id) {
if (id >= _relays.size()) return false;
return _relays[id].target_status;
}
void relaySync(unsigned char id) {
// No sync if none or only one relay
if (_relays.size() < 2) return;
void relaySync(size_t target) {
// Do not go on if we are comming from a previous sync
if (_relayRecursive) return;
// Flag sync mode
if (_relayRecursive) {
return;
}
_relayRecursive = true;
bool status = _relays[id].target_status;
// No sync if none or only one relay
auto relays = _relays.size();
if (relays < 2) {
return;
}
bool status = _relays[target].target_status;
// If RELAY_SYNC_SAME all relays should have the same state
if (_relay_sync_mode == RELAY_SYNC_SAME) {
for (unsigned short i=0; i<_relays.size(); i++) {
if (i != id) relayStatus(i, status);
for (decltype(relays) id = 0; id < relays; ++id) {
if (id != target) {
relayStatus(id, status);
}
}
// If RELAY_SYNC_FIRST all relays should have the same state as first if first changes
} else if (_relay_sync_mode == RELAY_SYNC_FIRST) {
if (id == 0) {
for (unsigned short i=1; i<_relays.size(); i++) {
relayStatus(i, status);
if (target == 0) {
for (decltype(relays) id = 1; id < relays; ++id) {
relayStatus(id, status);
}
}
@ -957,11 +955,11 @@ void relaySync(unsigned char id) {
// If NONE_OR_ONE or ONE and setting ON we should set OFF all the others
if (status) {
if (_relay_sync_mode != RELAY_SYNC_ANY) {
for (unsigned short other_id=0; other_id<_relays.size(); other_id++) {
if (other_id != id) {
relayStatus(other_id, false);
if (relayStatus(other_id)) {
_relaySyncRelaysDelay(other_id, id);
for (decltype(relays) id = 0; id < relays; ++id) {
if (id != target) {
relayStatus(id, false);
if (relayStatus(id)) {
_relaySyncRelaysDelay(id, target);
}
}
}
@ -969,22 +967,21 @@ void relaySync(unsigned char id) {
// If ONLY_ONE and setting OFF we should set ON the other one
} else {
if (_relay_sync_mode == RELAY_SYNC_ONE) {
unsigned char other_id = (id + 1) % _relays.size();
_relaySyncRelaysDelay(id, other_id);
relayStatus(other_id, true);
auto id = (target + 1) % relays;
_relaySyncRelaysDelay(target, id);
relayStatus(id, true);
}
}
_relayLockAll();
}
// Unflag sync mode
_relayRecursive = false;
}
void relaySave(bool persist) {
RelayMaskHelper mask;
for (unsigned char id = 0; id < _relays.size(); ++id) {
for (size_t id = 0; id < _relays.size(); ++id) {
mask.set(id, _relays[id].current_status);
}
@ -1008,13 +1005,13 @@ void relaySave() {
relaySave(false);
}
void relayToggle(unsigned char id, bool report, bool group_report) {
void relayToggle(size_t id, bool report, bool group_report) {
if (id < _relays.size()) {
relayStatus(id, !relayStatus(id), report, group_report);
}
}
void relayToggle(unsigned char id) {
void relayToggle(size_t id) {
#if MQTT_SUPPORT
relayToggle(id, mqttForward(), true);
#else
@ -1050,7 +1047,7 @@ void _relaySettingsMigrate(int version) {
moveSetting("relayDelayInterlock", "relayIlkDelay");
// groups use a new set of keys
for (unsigned char index = 0; index < RelaysMax; ++index) {
for (size_t index = 0; index < RelaysMax; ++index) {
auto group = getSetting({"mqttGroup", index});
if (!group.length()) {
break;
@ -1072,7 +1069,7 @@ void _relaySettingsMigrate(int version) {
"mqttGroup", // migrated to relayTopic
"mqttGroupSync", // migrated to relayTopic
"relayOnDisc", // replaced with relayMqttDisc
"relayGPIO", // avoid depending on migrate.ino
"relayGpio", // avoid depending on migrate.ino
"relayProvider", // different type
"relayType", // different type
});
@ -1080,8 +1077,8 @@ void _relaySettingsMigrate(int version) {
}
}
void _relayBoot(unsigned char index, const RelayMaskHelper& mask) {
const auto boot_mode = getSetting({"relayBoot", index}, _relayBootMode(index));
void _relayBoot(size_t index, const RelayMaskHelper& mask) {
const auto boot_mode = getSetting({"relayBoot", index}, relay::build::bootMode(index));
auto status = false;
auto lock = RelayLock::None;
@ -1154,24 +1151,24 @@ void _relayBootAll() {
void _relayConfigure() {
auto relays = _relays.size();
for (decltype(relays) id = 0; id < relays; ++id) {
_relays[id].pulse = getSetting({"relayPulse", id}, _relayPulseMode(id));
_relays[id].pulse_ms = static_cast<unsigned long>(1000.0f * getSetting({"relayTime", i}, _relayPulseTime(i)));
_relays[id].pulse = getSetting({"relayPulse", id}, relay::build::pulseMode(id));
_relays[id].pulse_ms = static_cast<unsigned long>(1000.0f * getSetting({"relayTime", id}, relay::build::pulseTime(id)));
_relays[id].delay_on = getSetting({"relayDelayOn", id}, _relayDelayOn(id));
_relays[id].delay_off = getSetting({"relayDelayOff", id}, _relayDelayOff(id));
_relays[id].delay_on = getSetting({"relayDelayOn", id}, relay::build::delayOn(id));
_relays[id].delay_off = getSetting({"relayDelayOff", id}, relay::build::delayOff(id));
}
_relay_flood_window = (1000.0f * getSetting("relayFloodTime", _relayFloodWindow()));
_relay_flood_changes = getSetting("relayFloodChanges", _relayFloodChanges());
_relay_flood_window = (1000.0f * getSetting("relayFloodTime", relay::build::floodWindow()));
_relay_flood_changes = getSetting("relayFloodChanges", relay::build::floodChanges());
_relay_delay_interlock = getSetting("relayIlkDelay", _relayInterlockDelay());
_relay_sync_mode = getSetting("relaySync", _relaySyncMode());
_relay_delay_interlock = getSetting("relayIlkDelay", relay::build::interlockDelay());
_relay_sync_mode = getSetting("relaySync", relay::build::syncMode());
#if MQTT_SUPPORT || API_SUPPORT
settingsProcessConfig({
{_relay_rpc_payload_on, "relayPayloadOn", _relayMqttPayloadOn()},
{_relay_rpc_payload_off, "relayPayloadOff", _relayMqttPayloadOff()},
{_relay_rpc_payload_toggle, "relayPayloadToggle", _relayMqttPayloadToggle()},
{_relay_rpc_payload_on, "relayPayloadOn", relay::build::mqttPayloadOn()},
{_relay_rpc_payload_off, "relayPayloadOff", relay::build::mqttPayloadOff()},
{_relay_rpc_payload_toggle, "relayPayloadToggle", relay::build::mqttPayloadToggle()},
});
#endif // MQTT_SUPPORT
}
@ -1202,17 +1199,17 @@ void _relayWebSocketUpdate(JsonObject& root) {
}
void _relayWebSocketRelayConfig(JsonArray& relay, size_t id) {
relay.add(static_cast<uint8_t>(getSetting({"relayProv", id}, _relayProvider(id))));
relay.add(static_cast<uint8_t>(getSetting({"relayProv", id}, relay::build::provider(id))));
relay.add(getSetting({"relayName", id}));
relay.add(getSetting({"relayBoot", id}, _relayBootMode(id)));
relay.add(getSetting({"relayBoot", id}, relay::build::bootMode(id)));
#if MQTT_SUPPORT
relay.add(getSetting({"relayTopicSub", id}, _relayMqttTopicSub(id)));
relay.add(getSetting({"relayTopicPub", id}, _relayMqttTopicPub(id)));
relay.add(getSetting({"relayTopicSub", id}, relay::build::mqttTopicSub(id)));
relay.add(getSetting({"relayTopicPub", id}, relay::build::mqttTopicPub(id)));
relay.add(static_cast<uint8_t>(getSetting({"relayTopicMode", id},
_relayMqttTopicMode(id))));
relay::build::mqttTopicMode(id))));
relay.add(static_cast<uint8_t>(getSetting({"relayMqttDisc", id},
_relayMqttDisconnectionStatus(id))));
relay::build::mqttDisconnectionStatus(id))));
#endif
relay.add(static_cast<uint8_t>(_relays[id].pulse));
@ -1266,8 +1263,8 @@ void _relayWebSocketOnVisible(JsonObject& root) {
if (relayCount() > 1) {
root["multirelayVisible"] = 1;
root["relaySync"] = static_cast<uint8_t>(getSetting("relaySync", _relaySyncMode()));
root["relayIlkDelay"] = getSetting("relayIlkDelay", _relayInterlockDelay());
root["relaySync"] = static_cast<uint8_t>(getSetting("relaySync", relay::build::syncMode()));
root["relayIlkDelay"] = getSetting("relayIlkDelay", relay::build::interlockDelay());
}
root["relayVisible"] = 1;
@ -1314,7 +1311,7 @@ void relaySetupWS() {
template <typename T>
bool _relayApiTryHandle(ApiRequest& request, T&& callback) {
auto id_param = request.wildcard(0);
unsigned char id;
size_t id;
if (!_relayTryParseId(id_param.c_str(), id)) {
return false;
}
@ -1331,7 +1328,7 @@ void relaySetupAPI() {
apiRegister(F(MQTT_TOPIC_RELAY),
[](ApiRequest&, JsonObject& root) {
JsonArray& relays = root.createNestedArray("relayStatus");
for (unsigned char id = 0; id < relayCount(); ++id) {
for (size_t id = 0; id < relayCount(); ++id) {
relays.add(_relays[id].target_status ? 1 : 0);
}
return true;
@ -1341,13 +1338,13 @@ void relaySetupAPI() {
apiRegister(F(MQTT_TOPIC_RELAY "/+"),
[](ApiRequest& request) {
return _relayApiTryHandle(request, [&](unsigned char id) {
return _relayApiTryHandle(request, [&](size_t id) {
request.send(String(_relays[id].target_status ? 1 : 0));
return true;
});
},
[](ApiRequest& request) {
return _relayApiTryHandle(request, [&](unsigned char id) {
return _relayApiTryHandle(request, [&](size_t id) {
return _relayHandlePayload(id, request.param(F("value")));
});
}
@ -1355,13 +1352,13 @@ void relaySetupAPI() {
apiRegister(F(MQTT_TOPIC_PULSE "/+"),
[](ApiRequest& request) {
return _relayApiTryHandle(request, [&](unsigned char id) {
return _relayApiTryHandle(request, [&](size_t id) {
request.send(String(static_cast<double>(_relays[id].pulse_ms) / 1000));
return true;
});
},
[](ApiRequest& request) {
return _relayApiTryHandle(request, [&](unsigned char id) {
return _relayApiTryHandle(request, [&](size_t id) {
return _relayHandlePulsePayload(id, request.param(F("value")));
});
}
@ -1478,7 +1475,7 @@ struct RelayCustomTopic {
_mode(base.mode())
{}
unsigned char id() const {
size_t id() const {
return _id;
}
@ -1508,7 +1505,7 @@ struct RelayCustomTopic {
}
private:
unsigned char _id;
size_t _id;
String _topic;
PathParts _parts;
RelayMqttTopicMode _mode;
@ -1524,7 +1521,7 @@ void _relayMqttSubscribeCustomTopics() {
static std::vector<RelayCustomTopicBase> topics;
for (size_t id = 0; id < relays; ++id) {
topics.emplace_back(_relayMqttTopicSub(id), _relayMqttTopicMode(id));
topics.emplace_back(relay::build::mqttTopicSub(id), relay::build::mqttTopicMode(id));
}
settings::kv_store.foreach([&](settings::kvs_type::KeyValueResult&& kv) {
@ -1541,22 +1538,21 @@ void _relayMqttSubscribeCustomTopics() {
}
const auto key = kv.key.read();
unsigned char id;
size_t id;
if (key.startsWith(SubPrefix)) {
if (_relayTryParseId(key.c_str() + strlen(SubPrefix), id)) {
topics[id] = std::move(kv.value.read());
}
} else if (key.startsWith(ModePrefix)) {
using namespace settings::internal;
if (_relayTryParseId(key.c_str() + strlen(ModePrefix), id)) {
topics[id] = convert<RelayMqttTopicMode>(kv.value.read());
topics[id] = settings::internal::convert<RelayMqttTopicMode>(kv.value.read());
}
}
});
_relay_custom_topics.clear();
for (unsigned char id = 0; id < relays; ++id) {
for (size_t id = 0; id < relays; ++id) {
RelayCustomTopicBase& topic = topics[id];
auto& value = topic.value();
if (!value.length()) {
@ -1570,15 +1566,15 @@ void _relayMqttSubscribeCustomTopics() {
topics.clear();
}
void _relayMqttPublishCustomTopic(unsigned char id) {
const String topic = getSetting({"relayTopicPub", id}, _relayMqttTopicPub(id));
void _relayMqttPublishCustomTopic(size_t id) {
const String topic = getSetting({"relayTopicPub", id}, relay::build::mqttTopicPub(id));
if (!topic.length()) {
return;
}
auto status = _relayPayloadStatus(id);
auto mode = getSetting({"relayTopicMode", id}, _relayMqttTopicMode(id));
auto mode = getSetting({"relayTopicMode", id}, relay::build::mqttTopicMode(id));
if (mode == RelayMqttTopicMode::Inverse) {
status = _relayInvertStatus(status);
}
@ -1588,7 +1584,7 @@ void _relayMqttPublishCustomTopic(unsigned char id) {
} // namespace
void _relayMqttReport(unsigned char id) {
void _relayMqttReport(size_t id) {
if (id < _relays.size()) {
if (_relays[id].report) {
_relays[id].report = false;
@ -1608,7 +1604,7 @@ void _relayMqttReportAll() {
}
}
void relayStatusWrap(unsigned char id, PayloadStatus value, bool is_group_topic) {
void relayStatusWrap(size_t id, PayloadStatus value, bool is_group_topic) {
#if MQTT_SUPPORT
const auto forward = mqttForward();
#else
@ -1664,7 +1660,7 @@ void _relayMqttHandleDisconnect() {
const auto key = kv.key.read();
if (key.startsWith(prefix)) {
unsigned char id;
size_t id;
if (_relayTryParseId(key.c_str() + strlen(prefix), id)) {
const auto value = kv.value.read();
_relayHandleStatus(id, relayParsePayload(value.c_str()));
@ -1704,7 +1700,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
auto is_relay = t.startsWith(MQTT_TOPIC_RELAY);
auto is_pulse = t.startsWith(MQTT_TOPIC_PULSE);
if (is_relay || is_pulse) {
unsigned char id;
size_t id;
if (!_relayTryParseIdFromPath(t.c_str(), id)) {
return;
}
@ -1752,8 +1748,8 @@ void relaySetupMQTT() {
void _relayInitCommands() {
terminalRegisterCommand(F("RELAY"), [](const terminal::CommandContext& ctx) {
auto showRelays = [&](unsigned char start, unsigned char stop, bool full = true) {
for (unsigned char index = start; index < stop; ++index) {
auto showRelays = [&](size_t start, size_t stop, bool full = true) {
for (size_t index = start; index < stop; ++index) {
auto& relay = _relays[index];
char pulse_info[64] = "";
@ -1795,7 +1791,7 @@ void _relayInitCommands() {
return;
}
unsigned char id;
size_t id;
if (!_relayTryParseId(ctx.argv[1].c_str(), id)) {
terminalError(ctx, F("Invalid relayID"));
return;
@ -1821,7 +1817,7 @@ void _relayInitCommands() {
//------------------------------------------------------------------------------
void _relayReport(unsigned char id [[gnu::unused]], bool status [[gnu::unused]]) {
void _relayReport(size_t id [[gnu::unused]], bool status [[gnu::unused]]) {
for (auto& change : _relay_status_change) {
change(id, status);
}
@ -1873,7 +1869,7 @@ void _relayProcess(bool mode) {
// We will trigger a eeprom save only if
// we care about current relay status on boot
const auto boot_mode = getSetting({"relayBoot", id}, _relayBootMode(id));
const auto boot_mode = getSetting({"relayBoot", id}, relay::build::bootMode(id));
const bool save_eeprom = ((RELAY_BOOT_SAME == boot_mode) || (RELAY_BOOT_TOGGLE == boot_mode));
_relay_save_timer.once_ms(RELAY_SAVE_DELAY, relaySave, save_eeprom);
@ -1887,7 +1883,7 @@ void _relayProcess(bool mode) {
// Whenever we are using sync modes and any relay had changed the state, check if we can unlock
switch (_relay_sync_mode) {
case RELAY_SYNC_ONE:
case RELAY_SYNC_NONE_OR_ONE
case RELAY_SYNC_NONE_OR_ONE:
if (_relay_sync_locked && changed) {
_relaySyncUnlock();
}
@ -1969,14 +1965,14 @@ struct RelayGpioProviderCfg {
uint8_t reset;
};
RelayGpioProviderCfg _relayGpioProviderCfg(unsigned char index) {
RelayGpioProviderCfg _relayGpioProviderCfg(size_t index) {
return {
gpioBase(getSetting({"relayGPIOType", index}, _relayPinType(index))),
getSetting({"relayGPIO", index}, _relayPin(index)),
getSetting({"relayResetGPIO", index}, _relayResetPin(index))};
gpioBase(getSetting({"relayGpioType", index}, relay::build::pinType(index))),
getSetting({"relayGpio", index}, relay::build::pin(index)),
getSetting({"relayResetGpio", index}, relay::build::resetPin(index))};
}
std::unique_ptr<GpioProvider> _relayGpioProvider(unsigned char index, RelayType type) {
std::unique_ptr<GpioProvider> _relayGpioProvider(size_t index, RelayType type) {
auto cfg = _relayGpioProviderCfg(index);
if (!cfg.base) {
return nullptr;
@ -1992,9 +1988,9 @@ std::unique_ptr<GpioProvider> _relayGpioProvider(unsigned char index, RelayType
return nullptr;
}
RelayProviderBasePtr _relaySetupProvider(unsigned char index) {
auto provider = getSetting({"relayProv", index}, _relayProvider(index));
auto type = getSetting({"relayType", index}, _relayType(index));
RelayProviderBasePtr _relaySetupProvider(size_t index) {
auto provider = getSetting({"relayProv", index}, relay::build::provider(index));
auto type = getSetting({"relayType", index}, relay::build::type(index));
RelayProviderBasePtr result;
@ -2026,7 +2022,7 @@ void _relaySetup() {
auto relays = _relays.size();
_relays.reserve(relays + _relayAdhocPins());
for (unsigned char id = relays; id < RelaysMax; ++id) {
for (size_t id = relays; id < RelaysMax; ++id) {
auto impl = _relaySetupProvider(id);
if (!impl) {
break;
@ -2037,7 +2033,7 @@ void _relaySetup() {
_relays.emplace_back(std::move(impl));
}
relaySetupDummy(getSetting("relayDummy", DUMMY_RELAY_COUNT));
relaySetupDummy(getSetting("relayDummy", relay::build::dummyCount()));
}
void relaySetup() {


+ 8
- 8
code/espurna/relay.h View File

@ -70,17 +70,17 @@ public:
PayloadStatus relayParsePayload(const char * payload);
bool relayStatus(unsigned char id, bool status, bool report, bool group_report);
bool relayStatus(unsigned char id, bool status);
bool relayStatus(size_t id, bool status, bool report, bool group_report);
bool relayStatus(size_t id, bool status);
// gets either current or target status, where current is the status that we are
// actually in and target is the status we would be, eventually, unless
// relayStatus(id, relayStatus()) is called
bool relayStatus(unsigned char id);
bool relayStatusTarget(unsigned char id);
bool relayStatus(size_t id);
bool relayStatusTarget(size_t id);
void relayToggle(unsigned char id, bool report, bool group_report);
void relayToggle(unsigned char id);
void relayToggle(size_t id, bool report, bool group_report);
void relayToggle(size_t id);
size_t relayCount();
@ -90,8 +90,8 @@ const String& relayPayloadToggle();
const char* relayPayload(PayloadStatus status);
void relayPulse(unsigned char id);
void relaySync(unsigned char id);
void relayPulse(size_t id);
void relaySync(size_t id);
void relaySave(bool persist);
using RelayStatusCallback = void(*)(size_t id, bool status);


+ 42
- 32
code/espurna/relay_config.h View File

@ -8,29 +8,36 @@ RELAY MODULE
#include "espurna.h"
constexpr int _relaySyncMode() {
namespace relay {
namespace build {
constexpr size_t dummyCount() {
return DUMMY_RELAY_COUNT;
}
constexpr int syncMode() {
return RELAY_SYNC;
}
constexpr float _relayFloodWindow() {
constexpr float floodWindow() {
return RELAY_FLOOD_WINDOW;
}
static_assert(_relayFloodWindow() >= 0.0f, "");
static_assert(floodWindow() >= 0.0f, "");
constexpr unsigned long _relayFloodWindowMs() {
return static_cast<unsigned long>(_relayFloodWindow() * 1000.0f);
constexpr unsigned long floodWindowMs() {
return static_cast<unsigned long>(floodWindow() * 1000.0f);
}
constexpr unsigned long _relayFloodChanges() {
constexpr unsigned long floodChanges() {
return RELAY_FLOOD_CHANGES;
}
constexpr unsigned long _relayInterlockDelay() {
constexpr unsigned long interlockDelay() {
return RELAY_DELAY_INTERLOCK;
}
constexpr float _relayPulseTime(unsigned char index) {
constexpr float pulseTime(size_t index) {
return (
(index == 0) ? RELAY1_PULSE_TIME :
(index == 1) ? RELAY2_PULSE_TIME :
@ -43,16 +50,16 @@ constexpr float _relayPulseTime(unsigned char index) {
);
}
static_assert(_relayPulseTime(0) >= 0.0f, "");
static_assert(_relayPulseTime(1) >= 0.0f, "");
static_assert(_relayPulseTime(2) >= 0.0f, "");
static_assert(_relayPulseTime(3) >= 0.0f, "");
static_assert(_relayPulseTime(4) >= 0.0f, "");
static_assert(_relayPulseTime(5) >= 0.0f, "");
static_assert(_relayPulseTime(6) >= 0.0f, "");
static_assert(_relayPulseTime(7) >= 0.0f, "");
static_assert(pulseTime(0) >= 0.0f, "");
static_assert(pulseTime(1) >= 0.0f, "");
static_assert(pulseTime(2) >= 0.0f, "");
static_assert(pulseTime(3) >= 0.0f, "");
static_assert(pulseTime(4) >= 0.0f, "");
static_assert(pulseTime(5) >= 0.0f, "");
static_assert(pulseTime(6) >= 0.0f, "");
static_assert(pulseTime(7) >= 0.0f, "");
constexpr RelayPulse _relayPulseMode(unsigned char index) {
constexpr RelayPulse pulseMode(size_t index) {
return (
(index == 0) ? RELAY1_PULSE_MODE :
(index == 1) ? RELAY2_PULSE_MODE :
@ -65,7 +72,7 @@ constexpr RelayPulse _relayPulseMode(unsigned char index) {
);
}
constexpr unsigned long _relayDelayOn(unsigned char index) {
constexpr unsigned long delayOn(size_t index) {
return (
(index == 0) ? RELAY1_DELAY_ON :
(index == 1) ? RELAY2_DELAY_ON :
@ -78,7 +85,7 @@ constexpr unsigned long _relayDelayOn(unsigned char index) {
);
}
constexpr unsigned long _relayDelayOff(unsigned char index) {
constexpr unsigned long delayOff(size_t index) {
return (
(index == 0) ? RELAY1_DELAY_OFF :
(index == 1) ? RELAY2_DELAY_OFF :
@ -91,7 +98,7 @@ constexpr unsigned long _relayDelayOff(unsigned char index) {
);
}
constexpr unsigned char _relayPin(unsigned char index) {
constexpr unsigned char pin(size_t index) {
return (
(index == 0) ? RELAY1_PIN :
(index == 1) ? RELAY2_PIN :
@ -104,7 +111,7 @@ constexpr unsigned char _relayPin(unsigned char index) {
);
}
constexpr RelayType _relayType(unsigned char index) {
constexpr RelayType type(size_t index) {
return (
(index == 0) ? RELAY1_TYPE :
(index == 1) ? RELAY2_TYPE :
@ -117,7 +124,7 @@ constexpr RelayType _relayType(unsigned char index) {
);
}
constexpr GpioType _relayPinType(unsigned char index) {
constexpr GpioType pinType(size_t index) {
return (
(index == 0) ? RELAY1_PIN_TYPE :
(index == 1) ? RELAY2_PIN_TYPE :
@ -130,7 +137,7 @@ constexpr GpioType _relayPinType(unsigned char index) {
);
}
constexpr unsigned char _relayResetPin(unsigned char index) {
constexpr unsigned char resetPin(size_t index) {
return (
(index == 0) ? RELAY1_RESET_PIN :
(index == 1) ? RELAY2_RESET_PIN :
@ -143,7 +150,7 @@ constexpr unsigned char _relayResetPin(unsigned char index) {
);
}
constexpr int _relayBootMode(unsigned char index) {
constexpr int bootMode(size_t index) {
return (
(index == 0) ? RELAY1_BOOT_MODE :
(index == 1) ? RELAY2_BOOT_MODE :
@ -156,7 +163,7 @@ constexpr int _relayBootMode(unsigned char index) {
);
}
constexpr RelayProvider _relayProvider(unsigned char index) {
constexpr RelayProvider provider(size_t index) {
return (
(index == 0) ? (RELAY1_PROVIDER) :
(index == 1) ? (RELAY2_PROVIDER) :
@ -169,7 +176,7 @@ constexpr RelayProvider _relayProvider(unsigned char index) {
);
}
constexpr RelayMqttTopicMode _relayMqttTopicMode(unsigned char index) {
constexpr RelayMqttTopicMode mqttTopicMode(size_t index) {
return (
(index == 0) ? (RELAY1_MQTT_TOPIC_MODE) :
(index == 1) ? (RELAY2_MQTT_TOPIC_MODE) :
@ -182,19 +189,19 @@ constexpr RelayMqttTopicMode _relayMqttTopicMode(unsigned char index) {
);
}
constexpr const char* const _relayMqttPayloadOn() {
constexpr const char* const mqttPayloadOn() {
return RELAY_MQTT_ON;
}
constexpr const char* const _relayMqttPayloadOff() {
constexpr const char* const mqttPayloadOff() {
return RELAY_MQTT_OFF;
}
constexpr const char* const _relayMqttPayloadToggle() {
constexpr const char* const mqttPayloadToggle() {
return RELAY_MQTT_TOGGLE;
}
constexpr const char* const _relayMqttTopicSub(unsigned char index) {
constexpr const char* const mqttTopicSub(size_t index) {
return (
(index == 0) ? (RELAY1_MQTT_TOPIC_SUB) :
(index == 1) ? (RELAY2_MQTT_TOPIC_SUB) :
@ -207,7 +214,7 @@ constexpr const char* const _relayMqttTopicSub(unsigned char index) {
);
}
constexpr const char* const _relayMqttTopicPub(unsigned char index) {
constexpr const char* const mqttTopicPub(size_t index) {
return (
(index == 0) ? (RELAY1_MQTT_TOPIC_PUB) :
(index == 1) ? (RELAY2_MQTT_TOPIC_PUB) :
@ -220,7 +227,7 @@ constexpr const char* const _relayMqttTopicPub(unsigned char index) {
);
}
constexpr PayloadStatus _relayMqttDisconnectionStatus(unsigned char index) {
constexpr PayloadStatus mqttDisconnectionStatus(size_t index) {
return (
(index == 0) ? (RELAY1_MQTT_DISCONNECT_STATUS) :
(index == 1) ? (RELAY2_MQTT_DISCONNECT_STATUS) :
@ -232,3 +239,6 @@ constexpr PayloadStatus _relayMqttDisconnectionStatus(unsigned char index) {
(index == 7) ? (RELAY8_MQTT_DISCONNECT_STATUS) : RELAY_MQTT_DISCONNECT_NONE
);
}
} // namespace build
} // namespace relay

+ 68
- 61
code/espurna/rfbridge.cpp View File

@ -53,33 +53,39 @@ constexpr bool _rfb_transmit { true };
struct RfbRelayMatch {
RfbRelayMatch() = default;
RfbRelayMatch(unsigned char id_, PayloadStatus status_) :
id(id_),
status(status_),
RfbRelayMatch(size_t id, PayloadStatus status) :
_id(id),
_status(status),
_found(true)
{}
bool ok() {
return _found;
void reset(size_t id, PayloadStatus status) {
_id = id;
_status = status;
_found = true;
}
void reset(unsigned char id_, PayloadStatus status_) {
id = id_;
status = status_;
_found = true;
size_t id() const {
return _id;
}
unsigned char id { 0u };
PayloadStatus status { PayloadStatus::Unknown };
PayloadStatus status() const {
return _status;
}
private:
explicit operator bool() const {
return _found;
}
private:
size_t _id { RelaysMax };
PayloadStatus _status { PayloadStatus::Unknown };
bool _found { false };
};
struct RfbLearn {
unsigned long ts;
unsigned char id;
size_t id;
bool status;
};
@ -332,7 +338,7 @@ void _rfbWebSocketOnVisible(JsonObject& root) {
#if RELAY_SUPPORT
void _rfbWebSocketSendCodeArray(JsonObject& root, unsigned char start, unsigned char size) {
void _rfbWebSocketSendCodeArray(JsonObject& root, size_t start, size_t size) {
JsonObject& rfb = root.createNestedObject("rfb");
rfb["size"] = size;
rfb["start"] = start;
@ -340,14 +346,14 @@ void _rfbWebSocketSendCodeArray(JsonObject& root, unsigned char start, unsigned
JsonArray& on = rfb.createNestedArray("on");
JsonArray& off = rfb.createNestedArray("off");
for (uint8_t id=start; id<start+size; id++) {
for (auto id = start; id < (start + size); ++id) {
on.add(rfbRetrieve(id, true));
off.add(rfbRetrieve(id, false));
}
}
void _rfbWebSocketOnData(JsonObject& root) {
_rfbWebSocketSendCodeArray(root, 0, relayCount());
_rfbWebSocketSendCodeArray(root, 0ul, relayCount());
}
#endif // RELAY_SUPPORT
@ -447,23 +453,18 @@ RfbRelayMatch _rfbMatch(const char* code) {
return;
}
char *endptr = nullptr;
const auto id = strtoul(id_ptr, &endptr, 10);
if (endptr == id_ptr || endptr[0] != '\0' || id > std::numeric_limits<uint8_t>::max() || id >= relayCount()) {
size_t id;
if (!tryParseId(id_ptr, relayCount, id)) {
return;
}
// when we see the same id twice, we match the opposite statuses
if (matched.ok() && (id == matched.id)) {
matched.status = PayloadStatus::Toggle;
if (matched && (id == matched.id())) {
matched.reset(matched.id(), PayloadStatus::Toggle);
return;
}
matched.reset(matched.ok()
? std::min(static_cast<uint8_t>(id), matched.id)
: static_cast<uint8_t>(id),
status
);
matched.reset(matched ? std::min(id, matched.id()) : id, status);
});
@ -482,7 +483,7 @@ void _rfbLearnFromString(std::unique_ptr<RfbLearn>& learn, const char* buffer) {
#if WEB_SUPPORT
auto id = learn->id;
wsPost([id](JsonObject& root) {
_rfbWebSocketSendCodeArray(root, id, 1);
_rfbWebSocketSendCodeArray(root, id, 1ul);
});
#endif
@ -493,18 +494,18 @@ bool _rfbRelayHandler(const char* buffer, bool locked = false) {
bool result { false };
auto match = _rfbMatch(buffer);
if (match.ok()) {
DEBUG_MSG_P(PSTR("[RF] Matched with the relay ID %u\n"), match.id);
_rfb_relay_status_lock.set(match.id, locked);
if (match) {
DEBUG_MSG_P(PSTR("[RF] Matched with the relay ID %u\n"), match.id());
_rfb_relay_status_lock.set(match.id(), locked);
switch (match.status) {
switch (match.status()) {
case PayloadStatus::On:
case PayloadStatus::Off:
relayStatus(match.id, (PayloadStatus::On == match.status));
relayStatus(match.id(), (PayloadStatus::On == match.status()));
result = true;
break;
case PayloadStatus::Toggle:
relayToggle(match.id);
relayToggle(match.id());
result = true;
case PayloadStatus::Unknown:
break;
@ -529,13 +530,8 @@ void _rfbLearnStartFromPayload(const char* payload) {
std::copy(payload, sep, relay);
char *endptr = nullptr;
const auto id = strtoul(relay, &endptr, 10);
if (endptr == &relay[0] || endptr[0] != '\0') {
return;
}
if (id >= relayCount()) {
size_t id;
if (!tryParseId(relay, relayCount(), id)) {
DEBUG_MSG_P(PSTR("[RF] Invalid relay ID (%u)\n"), id);
return;
}
@ -580,7 +576,7 @@ bool _rfbEnqueue(const char* code, size_t length, unsigned char repeats = 1u) {
return false;
}
void _rfbSendRaw(const uint8_t* message, unsigned char size) {
void _rfbSendRaw(const uint8_t* message, size_t size) {
Serial.write(message, size);
}
@ -1075,6 +1071,20 @@ void _rfbApiSetup() {
#if TERMINAL_SUPPORT
void _rfbCommandStatusDispatch(const terminal::CommandContext& ctx, size_t id, const String& payload, RelayStatusCallback callback) {
auto parsed = rpcParsePayload(payload.c_str());
switch (parsed) {
case PayloadStatus::On:
case PayloadStatus::Off:
callback(id, (parsed == PayloadStatus::On));
return;
case PayloadStatus::Toggle:
case PayloadStatus::Unknown:
terminalError(ctx, F("Invalid status"));
break;
}
}
void _rfbInitCommands() {
terminalRegisterCommand(F("RFB.SEND"), [](const terminal::CommandContext& ctx) {
@ -1093,38 +1103,35 @@ void _rfbInitCommands() {
return;
}
int id = ctx.argv[1].toInt();
if (id >= relayCount()) {
size_t id;
if (!tryParseId(ctx.argv[1].c_str(), relayCount, id)) {
terminalError(ctx, F("Invalid relay ID"));
return;
}
rfbLearn(id, (ctx.argv[2].toInt()) == 1);
terminalOK(ctx);
_rfbCommandStatusDispatch(ctx, id, ctx.argv[2], rfbLearn);
});
terminalRegisterCommand(F("RFB.FORGET"), [](const terminal::CommandContext& ctx) {
if (ctx.argc < 2) {
terminalError(ctx, F("RFB.FORGET <ID> [<STATUS>]"));
return;
}
int id = ctx.argv[1].toInt();
if (id >= relayCount()) {
size_t id;
if (!tryParseId(ctx.argv[1].c_str(), relayCount, id)) {
terminalError(ctx, F("Invalid relay ID"));
return;
}
if (ctx.argc == 3) {
rfbForget(id, (ctx.argv[2].toInt()) == 1);
} else {
rfbForget(id, true);
rfbForget(id, false);
_rfbCommandStatusDispatch(ctx, id, ctx.argv[2], rfbForget);
return;
}
rfbForget(id, true);
rfbForget(id, false);
terminalOK(ctx);
});
#endif // if RELAY_SUPPORT
@ -1148,19 +1155,19 @@ void _rfbInitCommands() {
// PUBLIC
// -----------------------------------------------------------------------------
void rfbStore(unsigned char id, bool status, const char * code) {
void rfbStore(size_t id, bool status, const char * code) {
SettingsKey key { status ? F("rfbON") : F("rfbOFF"), id };
setSetting(key, code);
DEBUG_MSG_P(PSTR("[RF] Saved %s => \"%s\"\n"), key.toString().c_str(), code);
DEBUG_MSG_P(PSTR("[RF] Saved %s => \"%s\"\n"), key.c_str(), code);
}
String rfbRetrieve(unsigned char id, bool status) {
String rfbRetrieve(size_t id, bool status) {
return getSetting({ status ? F("rfbON") : F("rfbOFF"), id });
}
#if RELAY_SUPPORT
void rfbStatus(unsigned char id, bool status) {
void rfbStatus(size_t id, bool status) {
// TODO: This is a left-over from the old implementation. Right now we set this lock when relay handler
// is called within the receiver, while this is called from either relayStatus or relay loop calling
// this via provider callback. This prevents us from re-sending the code we just received.
@ -1173,12 +1180,12 @@ void rfbStatus(unsigned char id, bool status) {
_rfb_relay_status_lock[id] = false;
}
void rfbLearn(unsigned char id, bool status) {
void rfbLearn(size_t id, bool status) {
_rfb_learn.reset(new RfbLearn { millis(), id, status });
_rfbLearnImpl();
}
void rfbForget(unsigned char id, bool status) {
void rfbForget(size_t id, bool status) {
delSetting({status ? F("rfbON") : F("rfbOFF"), id});
@ -1186,7 +1193,7 @@ void rfbForget(unsigned char id, bool status) {
// we send these in bulk is at the very start of the connection
#if WEB_SUPPORT
wsPost([id](JsonObject& root) {
_rfbWebSocketSendCodeArray(root, id, 1);
_rfbWebSocketSendCodeArray(root, id, 1ul);
});
#endif


+ 5
- 5
code/espurna/rfbridge.h View File

@ -19,13 +19,13 @@ BrokerDeclare(RfbridgeBroker, void(unsigned char protocol, const char* code));
void rfbSend(const char* code);
void rfbSend(const String& code);
void rfbStatus(unsigned char id, bool status);
void rfbLearn(unsigned char id, bool status);
void rfbStatus(size_t id, bool status);
void rfbLearn(size_t id, bool status);
String rfbRetrieve(unsigned char id, bool status);
void rfbStore(unsigned char id, bool status, const char * code);
String rfbRetrieve(size_t id, bool status);
void rfbStore(size_t id, bool status, const char* code);
void rfbForget(unsigned char id, bool status);
void rfbForget(size_t id, bool status);
void rfbSetup();
#endif // RFB_SUPPORT == 1

+ 18
- 12
code/espurna/settings.cpp View File

@ -211,33 +211,39 @@ String settingsQueryDefaults(const String& key) {
return String();
}
settings_move_key_t _moveKeys(const String& from, const String& to, unsigned char index) {
settings_move_key_t _moveKeys(const String& from, const String& to, size_t index) {
return settings_move_key_t {{from, index}, {to, index}};
}
void moveSetting(const String& from, const String& to) {
const auto value = getSetting(from);
if (value.length() > 0) setSetting(to, value);
auto result = settings::kv_store.get(from);
if (result) {
setSetting(to, result.ref());
}
delSetting(from);
}
void moveSetting(const String& from, const String& to, unsigned char index) {
const auto keys = _moveKeys(from, to, index);
const auto value = getSetting(keys.first);
if (value.length() > 0) setSetting(keys.second, value);
auto result = settings::kv_store.get(keys.first.value());
if (result) {
setSetting(keys.second, result.ref());
}
delSetting(keys.first);
}
void moveSettings(const String& from, const String& to) {
unsigned char index = 0;
while (index < 100) {
for (size_t index = 0; index < 100; ++index) {
const auto keys = _moveKeys(from, to, index);
const auto value = getSetting(keys.first);
if (value.length() == 0) break;
setSetting(keys.second, value);
auto result = settings::kv_store.get(keys.first.value());
if (!result) {
break;
}
setSetting(keys.second, result.ref());
delSetting(keys.first);
++index;
}
}
@ -516,7 +522,7 @@ void _settingsInitCommands() {
continue;
}
ctx.output.printf("> %s => \"%s\"\n", key.c_str(), result.value.c_str());
ctx.output.printf("> %s => \"%s\"\n", key.c_str(), result.c_str());
}
terminalOK(ctx);


+ 3
- 5
code/espurna/settings_embedis.h View File

@ -489,11 +489,9 @@ class KeyValueStore {
auto key_result = kv.key.read();
if (key_result == key) {
if (read_value) {
out = std::move(kv.value.read();
} else {
out = String();
}
out = read_value
? std::move(kv.value.read())
: String();
break;
}
} while (_state != State::End);


+ 23
- 23
code/espurna/thingspeak.cpp View File

@ -50,7 +50,7 @@ const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM =
bool _tspk_enabled = false;
bool _tspk_clear = false;
char * _tspk_queue[THINGSPEAK_FIELDS] = {NULL};
String _tspk_fields[THINGSPEAK_FIELDS];
String _tspk_data;
bool _tspk_flush = false;
@ -136,14 +136,11 @@ void _tspkConfigure() {
#endif
}
void _tspkClearQueue() {
void _tspkClearFields() {
_tspk_tries = THINGSPEAK_TRIES;
if (_tspk_clear) {
for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
if (_tspk_queue[id] != NULL) {
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
}
for (size_t id = 0; id < THINGSPEAK_FIELDS; ++id) {
_tspk_fields[id] = "";
}
}
}
@ -153,7 +150,7 @@ void _tspkRetry(int code) {
_tspk_flush = true;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Re-enqueuing %u more time(s)\n"), _tspk_tries);
} else {
_tspkClearQueue();
_tspkClearFields();
}
}
@ -369,11 +366,11 @@ void _tspkPost(const String& address) {
#endif // THINGSPEAK_USE_ASYNC
void _tspkEnqueue(unsigned char index, const char * payload) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Enqueuing field #%u with value %s\n"), index, payload);
--index;
if (_tspk_queue[index] != NULL) free(_tspk_queue[index]);
_tspk_queue[index] = strdup(payload);
void _tspkEnqueue(size_t index, const char* payload) {
if (index > 0) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Enqueuing field #%hhu with value %s\n"), index, payload);
_tspk_fields[--index] = payload;
}
}
void _tspkFlush() {
@ -390,11 +387,11 @@ void _tspkFlush() {
_tspk_data.reserve(tspkDataBufferSize);
// Walk the fields, numbered 1...THINGSPEAK_FIELDS
for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
if (_tspk_queue[id] != NULL) {
for (size_t id = 0; id<THINGSPEAK_FIELDS; id++) {
if (_tspk_fields[id].length()) {
if (_tspk_data.length() > 0) _tspk_data.concat("&");
char buf[32] = {0};
snprintf_P(buf, sizeof(buf), PSTR("field%u=%s"), (id + 1), _tspk_queue[id]);
snprintf_P(buf, sizeof(buf), PSTR("field%u=%s"), (id + 1), _tspk_fields[id].c_str());
_tspk_data.concat(buf);
}
}
@ -411,14 +408,17 @@ void _tspkFlush() {
// -----------------------------------------------------------------------------
bool tspkEnqueueRelay(unsigned char index, bool status) {
if (!_tspk_enabled) return true;
unsigned char id = getSetting({"tspkRelay", index}, 0);
if (id > 0) {
_tspkEnqueue(id, status ? "1" : "0");
return true;
bool tspkEnqueueRelay(size_t index, bool status) {
if (_tspk_enabled) {
unsigned char id = getSetting({"tspkRelay", index}, 0);
if (id > 0) {
_tspkEnqueue(id, status ? "1" : "0");
return true;
}
return false;
}
return false;
return true;
}
bool tspkEnqueueMeasurement(unsigned char index, const char * payload) {


+ 1
- 1
code/espurna/thingspeak.h View File

@ -12,7 +12,7 @@ Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
constexpr size_t tspkDataBufferSize { 256ul };
bool tspkEnqueueRelay(unsigned char index, bool status);
bool tspkEnqueueRelay(size_t index, bool status);
bool tspkEnqueueMeasurement(unsigned char index, const char * payload);
void tspkFlush();


+ 5
- 5
code/espurna/tuya.cpp View File

@ -139,7 +139,7 @@ namespace tuya {
}
}
void channel(unsigned char channel, float value) override {
void channel(size_t channel, float value) override {
// XXX: can't handle channel values when OFF, and will turn the lights ON
if (!_last_state) {
return;
@ -520,7 +520,7 @@ error:
namespace build {
constexpr unsigned char channelDpId(unsigned char index) {
constexpr unsigned char channelDpId(size_t index) {
return (index == 0) ? TUYA_CH1_DPID :
(index == 1) ? TUYA_CH2_DPID :
(index == 2) ? TUYA_CH3_DPID :
@ -528,7 +528,7 @@ error:
(index == 4) ? TUYA_CH5_DPID : 0u;
}
constexpr unsigned char switchDpId(unsigned char index) {
constexpr unsigned char switchDpId(size_t index) {
return (index == 0) ? TUYA_SW1_DPID :
(index == 1) ? TUYA_SW2_DPID :
(index == 2) ? TUYA_SW3_DPID :
@ -551,7 +551,7 @@ error:
void setupSwitches() {
bool done { false };
for (unsigned char id = 0; id < RelaysMax; ++id) {
for (size_t id = 0; id < RelaysMax; ++id) {
auto dp = getSetting({"tuyaSwitch", id}, build::switchDpId(id));
if (!dp) {
break;
@ -577,7 +577,7 @@ error:
void setupChannels() {
bool done { false };
for (unsigned char id = 0; id < Light::ChannelsMax; ++id) {
for (size_t id = 0; id < Light::ChannelsMax; ++id) {
auto dp = getSetting({"tuyaChannel", id}, build::channelDpId(id));
if (!dp) {
break;


+ 12
- 0
code/espurna/utils.cpp View File

@ -11,6 +11,18 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "board.h"
#include "ntp.h"
bool tryParseId(const char* p, TryParseIdFunc limit, size_t& out) {
static_assert(std::numeric_limits<size_t>::max() >= std::numeric_limits<unsigned long>::max(), "");
char* endp { nullptr };
out = strtoul(p, &endp, 10);
if ((endp == p) || (*endp != '\0') || (out >= limit())) {
return false;
}
return true;
}
void setDefaultHostname() {
if (strlen(HOSTNAME) > 0) {
setSetting("hostname", F(HOSTNAME));


+ 3
- 0
code/espurna/utils.h View File

@ -52,3 +52,6 @@ double roundTo(double num, unsigned char positions);
size_t hexEncode(const uint8_t* in, size_t in_size, char* out, size_t out_size);
size_t hexDecode(const char* in, size_t in_size, uint8_t* out, size_t out_size);
using TryParseIdFunc = size_t(*)();
bool tryParseId(const char* ptr, TryParseIdFunc limit, size_t& out);

Loading…
Cancel
Save