/* BUTTON MODULE Copyright (C) 2016-2019 by Xose Pérez Copyright (C) 2019-2021 by Maxim Prokhorov */ #include "espurna.h" #if BUTTON_SUPPORT #include "button.h" #include "compat.h" #include "fan.h" #include "gpio.h" #include "light.h" #include "mqtt.h" #include "relay.h" #include "system.h" #include "ws.h" #include "gpio_pin.h" #include "mcp23s08_pin.h" #include #include #include // ----------------------------------------------------------------------------- namespace settings { namespace internal { template<> debounce_event::types::Mode convert(const String& value) { switch (value.toInt()) { case 1: return debounce_event::types::Mode::Switch; case 0: default: return debounce_event::types::Mode::Pushbutton; } } String serialize(debounce_event::types::Mode value) { String result; switch (value) { case debounce_event::types::Mode::Switch: result = "1"; break; case debounce_event::types::Mode::Pushbutton: default: result = "0"; break; } return result; } template<> debounce_event::types::PinValue convert(const String& value) { switch (value.toInt()) { case 1: return debounce_event::types::PinValue::High; case 2: return debounce_event::types::PinValue::Initial; default: case 0: return debounce_event::types::PinValue::Low; } } String serialize(debounce_event::types::PinValue value) { String result; switch (value) { case debounce_event::types::PinValue::Low: result = "0"; break; case debounce_event::types::PinValue::High: result = "1"; break; case debounce_event::types::PinValue::Initial: result = "2"; break; } return result; } template<> debounce_event::types::PinMode convert(const String& value) { switch (value.toInt()) { case 1: return debounce_event::types::PinMode::InputPullup; case 2: return debounce_event::types::PinMode::InputPulldown; case 0: default: return debounce_event::types::PinMode::Input; } } String serialize(debounce_event::types::PinMode mode) { String result; switch (mode) { case debounce_event::types::PinMode::InputPullup: result = "1"; break; case debounce_event::types::PinMode::InputPulldown: result = "2"; break; case debounce_event::types::PinMode::Input: default: result = "0"; break; } return result; } template <> ButtonProvider convert(const String& value) { auto type = static_cast(value.toInt()); switch (type) { case ButtonProvider::None: case ButtonProvider::Gpio: case ButtonProvider::Analog: return type; } return ButtonProvider::None; } template<> ButtonAction convert(const String& value) { auto num = strtoul(value.c_str(), nullptr, 10); if (num < ButtonsActionMax) { auto action = static_cast(num); switch (action) { case ButtonAction::None: case ButtonAction::Toggle: case ButtonAction::On: case ButtonAction::Off: case ButtonAction::AccessPoint: case ButtonAction::Reset: case ButtonAction::Pulse: case ButtonAction::FactoryReset: case ButtonAction::Wps: case ButtonAction::SmartConfig: case ButtonAction::BrightnessIncrease: case ButtonAction::BrightnessDecrease: case ButtonAction::DisplayOn: case ButtonAction::Custom: case ButtonAction::FanLow: case ButtonAction::FanMedium: case ButtonAction::FanHigh: return action; } } return ButtonAction::None; } } // namespace internal } // namespace settings // ----------------------------------------------------------------------------- namespace button { namespace { namespace build { constexpr size_t pin(size_t index) { return ( (index == 0) ? BUTTON1_PIN : (index == 1) ? BUTTON2_PIN : (index == 2) ? BUTTON3_PIN : (index == 3) ? BUTTON4_PIN : (index == 4) ? BUTTON5_PIN : (index == 5) ? BUTTON6_PIN : (index == 6) ? BUTTON7_PIN : (index == 7) ? BUTTON8_PIN : GPIO_NONE ); } constexpr GpioType pinType(size_t index) { return ( (index == 0) ? BUTTON1_PIN_TYPE : (index == 1) ? BUTTON2_PIN_TYPE : (index == 2) ? BUTTON3_PIN_TYPE : (index == 3) ? BUTTON4_PIN_TYPE : (index == 4) ? BUTTON5_PIN_TYPE : (index == 5) ? BUTTON6_PIN_TYPE : (index == 6) ? BUTTON7_PIN_TYPE : (index == 7) ? BUTTON8_PIN_TYPE : GPIO_TYPE_NONE ); } namespace internal { namespace ButtonMask { constexpr int Pushbutton { 1 << 0 }; constexpr int Switch { 1 << 1 }; constexpr int DefaultLow { 1 << 2 }; constexpr int DefaultHigh { 1 << 3 }; constexpr int DefaultBoot { 1 << 4 }; constexpr int SetPullup { 1 << 5 }; constexpr int SetPulldown { 1 << 6 }; } // namespace ButtonMask constexpr int configBitmask(size_t index) { return ( (index == 0) ? (BUTTON1_CONFIG) : (index == 1) ? (BUTTON2_CONFIG) : (index == 2) ? (BUTTON3_CONFIG) : (index == 3) ? (BUTTON4_CONFIG) : (index == 4) ? (BUTTON5_CONFIG) : (index == 5) ? (BUTTON6_CONFIG) : (index == 6) ? (BUTTON7_CONFIG) : (index == 7) ? (BUTTON8_CONFIG) : (BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH) ); } 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) }; } constexpr debounce_event::types::Mode mode(size_t index) { return decode(configBitmask(index)).mode; } constexpr debounce_event::types::PinValue defaultValue(size_t index) { return decode(configBitmask(index)).default_value; } constexpr debounce_event::types::PinMode pinMode(size_t index) { return decode(configBitmask(index)).pin_mode; } } // namespace internal constexpr debounce_event::types::Mode mode(size_t index) { return internal::mode(index); } constexpr debounce_event::types::PinValue defaultValue(size_t index) { return internal::defaultValue(index); } constexpr debounce_event::types::PinMode pinMode(size_t index) { return internal::pinMode(index); } constexpr ButtonAction release(size_t index) { return ( (index == 0) ? BUTTON1_RELEASE : (index == 1) ? BUTTON2_RELEASE : (index == 2) ? BUTTON3_RELEASE : (index == 3) ? BUTTON4_RELEASE : (index == 4) ? BUTTON5_RELEASE : (index == 5) ? BUTTON6_RELEASE : (index == 6) ? BUTTON7_RELEASE : (index == 7) ? BUTTON8_RELEASE : BUTTON_ACTION_NONE ); } constexpr ButtonAction press(size_t index) { return ( (index == 0) ? BUTTON1_PRESS : (index == 1) ? BUTTON2_PRESS : (index == 2) ? BUTTON3_PRESS : (index == 3) ? BUTTON4_PRESS : (index == 4) ? BUTTON5_PRESS : (index == 5) ? BUTTON6_PRESS : (index == 6) ? BUTTON7_PRESS : (index == 7) ? BUTTON8_PRESS : BUTTON_ACTION_NONE ); } constexpr ButtonAction click(size_t index) { return ( (index == 0) ? BUTTON1_CLICK : (index == 1) ? BUTTON2_CLICK : (index == 2) ? BUTTON3_CLICK : (index == 3) ? BUTTON4_CLICK : (index == 4) ? BUTTON5_CLICK : (index == 5) ? BUTTON6_CLICK : (index == 6) ? BUTTON7_CLICK : (index == 7) ? BUTTON8_CLICK : BUTTON_ACTION_NONE ); } constexpr ButtonAction doubleClick(size_t index) { return ( (index == 0) ? BUTTON1_DBLCLICK : (index == 1) ? BUTTON2_DBLCLICK : (index == 2) ? BUTTON3_DBLCLICK : (index == 3) ? BUTTON4_DBLCLICK : (index == 4) ? BUTTON5_DBLCLICK : (index == 5) ? BUTTON6_DBLCLICK : (index == 6) ? BUTTON7_DBLCLICK : (index == 7) ? BUTTON8_DBLCLICK : BUTTON_ACTION_NONE ); } constexpr ButtonAction tripleClick(size_t index) { return ( (index == 0) ? BUTTON1_TRIPLECLICK : (index == 1) ? BUTTON2_TRIPLECLICK : (index == 2) ? BUTTON3_TRIPLECLICK : (index == 3) ? BUTTON4_TRIPLECLICK : (index == 4) ? BUTTON5_TRIPLECLICK : (index == 5) ? BUTTON6_TRIPLECLICK : (index == 6) ? BUTTON7_TRIPLECLICK : (index == 7) ? BUTTON8_TRIPLECLICK : BUTTON_ACTION_NONE ); } constexpr ButtonAction longClick(size_t index) { return ( (index == 0) ? BUTTON1_LNGCLICK : (index == 1) ? BUTTON2_LNGCLICK : (index == 2) ? BUTTON3_LNGCLICK : (index == 3) ? BUTTON4_LNGCLICK : (index == 4) ? BUTTON5_LNGCLICK : (index == 5) ? BUTTON6_LNGCLICK : (index == 6) ? BUTTON7_LNGCLICK : (index == 7) ? BUTTON8_LNGCLICK : BUTTON_ACTION_NONE ); } constexpr ButtonAction longLongClick(size_t index) { return ( (index == 0) ? BUTTON1_LNGLNGCLICK : (index == 1) ? BUTTON2_LNGLNGCLICK : (index == 2) ? BUTTON3_LNGLNGCLICK : (index == 3) ? BUTTON4_LNGLNGCLICK : (index == 4) ? BUTTON5_LNGLNGCLICK : (index == 5) ? BUTTON6_LNGLNGCLICK : (index == 6) ? BUTTON7_LNGLNGCLICK : (index == 7) ? BUTTON8_LNGLNGCLICK : BUTTON_ACTION_NONE ); } constexpr size_t relay(size_t index) { return ( (index == 0) ? (BUTTON1_RELAY - 1) : (index == 1) ? (BUTTON2_RELAY - 1) : (index == 2) ? (BUTTON3_RELAY - 1) : (index == 3) ? (BUTTON4_RELAY - 1) : (index == 4) ? (BUTTON5_RELAY - 1) : (index == 5) ? (BUTTON6_RELAY - 1) : (index == 6) ? (BUTTON7_RELAY - 1) : (index == 7) ? (BUTTON8_RELAY - 1) : RELAY_NONE ); } constexpr unsigned long debounceDelay() { return BUTTON_DEBOUNCE_DELAY; } constexpr unsigned long debounceDelay(size_t index) { return ( (index == 0) ? BUTTON1_DEBOUNCE_DELAY : (index == 1) ? BUTTON2_DEBOUNCE_DELAY : (index == 2) ? BUTTON3_DEBOUNCE_DELAY : (index == 3) ? BUTTON4_DEBOUNCE_DELAY : (index == 4) ? BUTTON5_DEBOUNCE_DELAY : (index == 5) ? BUTTON6_DEBOUNCE_DELAY : (index == 6) ? BUTTON7_DEBOUNCE_DELAY : (index == 7) ? BUTTON8_DEBOUNCE_DELAY : debounceDelay() ); } constexpr unsigned long repeatDelay() { return BUTTON_REPEAT_DELAY; } constexpr unsigned long repeatDelay(size_t index) { return ( (index == 0) ? BUTTON1_REPEAT_DELAY : (index == 1) ? BUTTON2_REPEAT_DELAY : (index == 2) ? BUTTON3_REPEAT_DELAY : (index == 3) ? BUTTON4_REPEAT_DELAY : (index == 4) ? BUTTON5_REPEAT_DELAY : (index == 5) ? BUTTON6_REPEAT_DELAY : (index == 6) ? BUTTON7_REPEAT_DELAY : (index == 7) ? BUTTON8_REPEAT_DELAY : repeatDelay() ); } constexpr unsigned long longClickDelay() { return BUTTON_LNGCLICK_DELAY; } constexpr unsigned long longClickDelay(size_t index) { return ( (index == 0) ? BUTTON1_LNGCLICK_DELAY : (index == 1) ? BUTTON2_LNGCLICK_DELAY : (index == 2) ? BUTTON3_LNGCLICK_DELAY : (index == 3) ? BUTTON4_LNGCLICK_DELAY : (index == 4) ? BUTTON5_LNGCLICK_DELAY : (index == 5) ? BUTTON6_LNGCLICK_DELAY : (index == 6) ? BUTTON7_LNGCLICK_DELAY : (index == 7) ? BUTTON8_LNGCLICK_DELAY : longClickDelay() ); } constexpr unsigned long longLongClickDelay() { return BUTTON_LNGLNGCLICK_DELAY; } constexpr unsigned long longLongClickDelay(size_t index) { return ( (index == 0) ? BUTTON1_LNGLNGCLICK_DELAY : (index == 1) ? BUTTON2_LNGLNGCLICK_DELAY : (index == 2) ? BUTTON3_LNGLNGCLICK_DELAY : (index == 3) ? BUTTON4_LNGLNGCLICK_DELAY : (index == 4) ? BUTTON5_LNGLNGCLICK_DELAY : (index == 5) ? BUTTON6_LNGLNGCLICK_DELAY : (index == 6) ? BUTTON7_LNGLNGCLICK_DELAY : (index == 7) ? BUTTON8_LNGLNGCLICK_DELAY : longLongClickDelay() ); } 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) : (index == 2) ? (1 == BUTTON3_MQTT_SEND_ALL_EVENTS) : (index == 3) ? (1 == BUTTON4_MQTT_SEND_ALL_EVENTS) : (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) : mqttSendAllEvents() ); } 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) : (index == 2) ? (1 == BUTTON3_MQTT_RETAIN) : (index == 3) ? (1 == BUTTON4_MQTT_RETAIN) : (index == 4) ? (1 == BUTTON5_MQTT_RETAIN) : (index == 5) ? (1 == BUTTON6_MQTT_RETAIN) : (index == 6) ? (1 == BUTTON7_MQTT_RETAIN) : (index == 7) ? (1 == BUTTON8_MQTT_RETAIN) : mqttRetain() ); } constexpr ButtonProvider provider(size_t index) { return ( (index == 0) ? (BUTTON1_PROVIDER) : (index == 1) ? (BUTTON2_PROVIDER) : (index == 2) ? (BUTTON3_PROVIDER) : (index == 3) ? (BUTTON4_PROVIDER) : (index == 4) ? (BUTTON5_PROVIDER) : (index == 5) ? (BUTTON6_PROVIDER) : (index == 6) ? (BUTTON7_PROVIDER) : (index == 7) ? (BUTTON8_PROVIDER) : BUTTON_PROVIDER_NONE ); } constexpr int analogLevel(size_t index) { return ( (index == 0) ? (BUTTON1_ANALOG_LEVEL) : (index == 1) ? (BUTTON2_ANALOG_LEVEL) : (index == 2) ? (BUTTON3_ANALOG_LEVEL) : (index == 3) ? (BUTTON4_ANALOG_LEVEL) : (index == 4) ? (BUTTON5_ANALOG_LEVEL) : (index == 5) ? (BUTTON6_ANALOG_LEVEL) : (index == 6) ? (BUTTON7_ANALOG_LEVEL) : (index == 7) ? (BUTTON8_ANALOG_LEVEL) : 0 ); } } // namespace build namespace settings { namespace internal { template T indexedThenGlobal(const String& prefix, size_t index, T defaultValue) { SettingsKey key{prefix, index}; auto indexed = ::settings::internal::get(key.value()); if (indexed) { return ::settings::internal::convert(indexed.ref()); } auto global = ::settings::internal::get(prefix); if (global) { return ::settings::internal::convert(indexed.ref()); } return defaultValue; } } // namespace internal unsigned char pin(size_t index) { return getSetting({"btnGpio", index}, build::pin(index)); } GpioType pinType(size_t index) { return getSetting({"btnGpioType", index}, build::pinType(index)); } ButtonProvider provider(size_t index) { return getSetting({"btnProv", index}, build::provider(index)); } debounce_event::types::Mode mode(size_t index) { return getSetting({"btnMode", index}, build::mode(index)); } debounce_event::types::PinValue defaultValue(size_t index) { return getSetting({"btnDefVal", index}, build::defaultValue(index)); } debounce_event::types::PinMode pinMode(size_t index) { return getSetting({"btnPinMode", index}, build::pinMode(index)); } ButtonAction release(size_t index) { return getSetting({"btnRlse", index}, build::release(index)); } ButtonAction press(size_t index) { return getSetting({"btnPress", index}, build::press(index)); } ButtonAction click(size_t index) { return getSetting({"btnClick", index}, build::click(index)); } ButtonAction doubleClick(size_t index) { return getSetting({"btnDclk", index}, build::doubleClick(index)); } ButtonAction tripleClick(size_t index) { return getSetting({"btnTclk", index}, build::tripleClick(index)); } ButtonAction longClick(size_t index) { return getSetting({"btnLclk", index}, build::longClick(index)); } ButtonAction longLongClick(size_t index) { return getSetting({"btnLLclk", index}, build::longLongClick(index)); } unsigned long debounceDelay(size_t index) { return internal::indexedThenGlobal("btnDebDel", index, build::debounceDelay(index)); } unsigned long longClickDelay(size_t index) { return internal::indexedThenGlobal("btnLclkDel", index, build::longClickDelay(index)); } unsigned long longLongClickDelay(size_t index) { return internal::indexedThenGlobal("btnLLclkDel", index, build::longLongClickDelay(index)); } [[gnu::unused]] unsigned long repeatDelay() { return getSetting("btnRepDel", build::repeatDelay()); } unsigned long repeatDelay(size_t index) { return internal::indexedThenGlobal("btnRepDel", index, build::repeatDelay(index)); } size_t relay(size_t index) { return getSetting({"btnRelay", index}, build::relay(index)); } [[gnu::unused]] bool mqttSendAllEvents(size_t index) { return getSetting({"btnMqttSendAll", index}, build::mqttSendAllEvents(index)); } [[gnu::unused]] bool mqttRetain(size_t index) { return getSetting({"btnMqttRetain", index}, build::mqttRetain(index)); } #if BUTTON_PROVIDER_ANALOG_SUPPORT int analogLevel(size_t index) { return getSetting({"btnLevel", index}, build::analogLevel(index)); } #endif } // namespace settings } // namespace } // namespace button namespace { constexpr ButtonAction _buttonDecodeEventAction(const ButtonActions& actions, ButtonEvent event) { return ( (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 ButtonEvent _buttonMapReleased(uint8_t count, unsigned long length, unsigned long lngclick_delay, unsigned long lnglngclick_delay) { return ( (0 == count) ? ButtonEvent::Released : (1 == count) ? ( (length > lnglngclick_delay) ? ButtonEvent::LongLongClick : (length > lngclick_delay) ? ButtonEvent::LongClick : ButtonEvent::Click ) : (2 == count) ? ButtonEvent::DoubleClick : (3 == count) ? ButtonEvent::TripleClick : ButtonEvent::None ); } debounce_event::types::Config _buttonRuntimeConfig(size_t index) { return { button::settings::mode(index), button::settings::defaultValue(index), button::settings::pinMode(index)}; } } // namespace // ----------------------------------------------------------------------------- ButtonEventDelays::ButtonEventDelays() : debounce(button::build::debounceDelay()), repeat(button::build::repeatDelay()), lngclick(button::build::longClickDelay()), lnglngclick(button::build::longLongClickDelay()) {} ButtonEventDelays::ButtonEventDelays(unsigned long debounce, unsigned long repeat, unsigned long lngclick, unsigned long lnglngclick) : debounce(debounce), repeat(repeat), lngclick(lngclick), lnglngclick(lnglngclick) {} Button::Button(ButtonActions&& actions_, ButtonEventDelays&& delays_) : actions(std::move(actions_)), event_delays(std::move(delays_)) {} Button::Button(BasePinPtr&& pin, const debounce_event::types::Config& config, ButtonActions&& actions_, ButtonEventDelays&& delays_) : event_emitter(std::make_unique(std::move(pin), config, delays_.debounce, delays_.repeat)), actions(std::move(actions_)), event_delays(std::move(delays_)) {} ButtonEvent Button::loop() { if (event_emitter) { switch (event_emitter->loop()) { case debounce_event::types::EventPressed: return ButtonEvent::Pressed; case debounce_event::types::EventReleased: { return _buttonMapReleased( event_emitter->getEventCount(), event_emitter->getEventLength(), event_delays.lngclick, event_delays.lnglngclick ); } case debounce_event::types::EventNone: break; } } return ButtonEvent::None; } static std::vector