diff --git a/code/espurna/board.ino b/code/espurna/board.ino index a0ba05cf..1b8e0203 100644 --- a/code/espurna/board.ino +++ b/code/espurna/board.ino @@ -19,7 +19,11 @@ PROGMEM const char espurna_modules[] = "BROKER " #endif #if BUTTON_SUPPORT + #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC "BUTTON " + #else + "BUTTON_DUAL " + #endif #endif #if DEBUG_SERIAL_SUPPORT "DEBUG_SERIAL " @@ -81,6 +85,9 @@ PROGMEM const char espurna_modules[] = #if RF_SUPPORT "RF " #endif + #if RPN_RULES_SUPPORT + "RPN_RULES " + #endif #if SCHEDULER_SUPPORT "SCHEDULER " #endif diff --git a/code/espurna/button.ino b/code/espurna/button.ino index d32f9bb3..3eafc517 100644 --- a/code/espurna/button.ino +++ b/code/espurna/button.ino @@ -147,7 +147,7 @@ void buttonEvent(unsigned char id, unsigned char event) { if (BUTTON_MODE_OFF == action) { relayStatus(button.relayID, false); } - + if (BUTTON_MODE_AP == action) { if (wifiState() & WIFI_STATE_AP) { wifiStartSTA(); @@ -155,7 +155,7 @@ void buttonEvent(unsigned char id, unsigned char event) { wifiStartAP(); } } - + if (BUTTON_MODE_RESET == action) { deferredReset(100, CUSTOM_RESET_HARDWARE); } @@ -171,13 +171,13 @@ void buttonEvent(unsigned char id, unsigned char event) { wifiStartWPS(); } #endif // defined(JUSTWIFI_ENABLE_WPS) - + #if defined(JUSTWIFI_ENABLE_SMARTCONFIG) if (BUTTON_MODE_SMART_CONFIG == action) { wifiStartSmartConfig(); } #endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG) - + #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE if (BUTTON_MODE_DIM_UP == action) { lightBrightnessStep(1); @@ -214,24 +214,32 @@ void buttonSetup() { // Special hardware cases - #if defined(ITEAD_SONOFF_DUAL) + #if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \ + (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL) - _buttons.reserve(3); - - buttonAdd(GPIO_NONE, BUTTON_PUSHBUTTON, 0, _buttonRelay(0)); - buttonAdd(GPIO_NONE, BUTTON_PUSHBUTTON, 0, _buttonRelay(1)); - buttonAdd(GPIO_NONE, BUTTON_PUSHBUTTON, 0, _buttonRelay(2)); - - #elif defined(FOXEL_LIGHTFOX_DUAL) + size_t buttons = 0; + #if BUTTON1_RELAY != RELAY_NONE + ++buttons; + #endif + #if BUTTON2_RELAY != RELAY_NONE + ++buttons; + #endif + #if BUTTON3_RELAY != RELAY_NONE + ++buttons; + #endif + #if BUTTON4_RELAY != RELAY_NONE + ++buttons; + #endif - _buttons.reserve(4); + _buttons.reserve(buttons); + // Ignore default button modes const auto actions = _buttonConstructActions( BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE ); - for (unsigned char id = 0; id < 4; ++id) { + for (unsigned char id = 0; id < buttons; ++id) { buttonAdd( GPIO_NONE, BUTTON_PUSHBUTTON, actions, getSetting({"btnRelay", id}, _buttonRelay(id)) @@ -244,6 +252,9 @@ void buttonSetup() { size_t buttons = 0; + // TODO: no real point of doing this when running with dynamic settings + // if there is limit like RELAYS_MAX - use that + // if not, try to allocate some reasonable amount #if BUTTON1_PIN != GPIO_NONE ++buttons; #endif @@ -284,6 +295,7 @@ void buttonSetup() { getSetting({"btnLngLngCDelay", index}, _buttonLongLongClickDelay(index)) }; + // TODO: allow to change DebounceEvent::DigitalPin to something else based on config _buttons.emplace_back( std::make_shared(pin), getSetting({"btnMode", index}, _buttonMode(index)), @@ -309,80 +321,109 @@ void buttonSetup() { } -void buttonLoop() { +// Sonoff Dual does not do real GPIO readings and we +// depend on the external MCU to send us relay / button events +// TODO: move this to a separate 'hardware' setup file? +#if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL - #if defined(ITEAD_SONOFF_DUAL) - - if (Serial.available() >= 4) { - if (Serial.read() == 0xA0) { - if (Serial.read() == 0x04) { - unsigned char value = Serial.read(); - if (Serial.read() == 0xA1) { - - // RELAYs and BUTTONs are synchonized in the SIL F330 - // The on-board BUTTON2 should toggle RELAY0 value - // Since we are not passing back RELAY2 value - // (in the relayStatus method) it will only be present - // here if it has actually been pressed - if ((value & 4) == 4) { - buttonEvent(2, BUTTON_EVENT_CLICK); - return; - } - - // Otherwise check if any of the other two BUTTONs - // (in the header) has been pressed, but we should - // ensure that we only toggle one of them to avoid - // the synchronization going mad - // This loop is generic for any PSB-04 module - for (unsigned int i=0; i 0; - - // Check if the status for that relay has changed - if (relayStatus(i) != status) { - buttonEvent(i, BUTTON_EVENT_CLICK); - break; - } - - } - - } - } - } +void _buttonLoopSonoffDual() { + + if (Serial.available() < 4) { + return; + } + + unsigned char bytes[4] = {0}; + Serial.readBytes(bytes, 4); + if ((bytes[0] != 0xA0) || (bytes[1] != 0x04) && (bytes[3] != 0xA1)) { + return; + } + + const unsigned char value = bytes[2]; + + // RELAYs and BUTTONs are synchonized in the SIL F330 + // The on-board BUTTON2 should toggle RELAY0 value + // Since we are not passing back RELAY2 value + // (in the relayStatus method) it will only be present + // here if it has actually been pressed + if ((value & 4) == 4) { + buttonEvent(2, BUTTON_EVENT_CLICK); + return; + } + + // Otherwise check if any of the other two BUTTONs + // (in the header) has been pressed, but we should + // ensure that we only toggle one of them to avoid + // the synchronization going mad + // This loop is generic for any PSB-04 module + for (unsigned int i=0; i 0; + + // Check if the status for that relay has changed + if (relayStatus(i) != status) { + buttonEvent(i, BUTTON_EVENT_CLICK); + break; } - #elif defined(FOXEL_LIGHTFOX_DUAL) + } - if (Serial.available() >= 4) { - if (Serial.read() == 0xA0) { - if (Serial.read() == 0x04) { - unsigned char value = Serial.read(); - if (Serial.read() == 0xA1) { +} - DEBUG_MSG_P(PSTR("[BUTTON] [LIGHTFOX] Received buttons mask: %d\n"), value); +#endif // BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL == 1 - for (unsigned int i=0; i<_buttons.size(); i++) { +// Lightfox uses the same protocol as Dual, but has slightly different actions +// TODO: same as above, move from here someplace else +#if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL - bool clicked = (value & (1 << i)) > 0; +void _buttonLoopFoxelLightfox() { - if (clicked) { - buttonEvent(i, BUTTON_EVENT_CLICK); - } - } - } - } - } + if (Serial.available() < 4) { + return; + } + + unsigned char bytes[4] = {0}; + Serial.readBytes(bytes, 4); + if ((bytes[0] != 0xA0) || (bytes[1] != 0x04) && (bytes[3] != 0xA1)) { + return; + } + + const unsigned char value = bytes[2]; + + DEBUG_MSG_P(PSTR("[BUTTON] [LIGHTFOX] Received buttons mask: %u\n"), value); + + for (unsigned int i=0; i<_buttons.size(); i++) { + + bool clicked = (value & (1 << i)) > 0; + + if (clicked) { + buttonEvent(i, BUTTON_EVENT_CLICK); } + } +} - #else +#endif // BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL == 1 - for (size_t id = 0; id < _buttons.size(); ++id) { - auto& button = _buttons[id]; - if (auto event = button.event_handler->loop()) { - buttonEvent(id, _buttonMapEvent(button, event)); - } - } +void _buttonLoopGeneric() { + for (size_t id = 0; id < _buttons.size(); ++id) { + auto& button = _buttons[id]; + auto event = button.event_handler->loop(); + + if (event != DebounceEvent::Types::EventNone) { + buttonEvent(id, _buttonMapEvent(button, event)); + } + } +} +void buttonLoop() { + + #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC + _buttonLoopGeneric(); + #elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL + _buttonLoopSonoffDual(); + #elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL + _buttonLoopFoxelLightfox(); + #else + #warning "Unknown value for BUTTON_EVENTS_SOURCE" #endif } diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index b657877d..ac04f29a 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -377,8 +377,15 @@ #endif #ifndef BUTTON_MQTT_SEND_ALL_EVENTS -#define BUTTON_MQTT_SEND_ALL_EVENTS 0 // 0 - to send only events the are bound to actions - // 1 - to send all button events to MQTT +#define BUTTON_MQTT_SEND_ALL_EVENTS 0 // 0 - to send only events the are bound to actions + // 1 - to send all button events to MQTT +#endif + +#ifndef BUTTON_EVENTS_SOURCE +#define BUTTON_EVENTS_SOURCE BUTTON_EVENTS_SOURCE_GENERIC // Type of button event source. One of: + // BUTTON_EVENTS_SOURCE_GENERIC - GPIOs (virtual or real) + // BUTTON_EVENTS_SOURCE_SONOFF_DUAL - hardware specific, drive buttons through serial connection + // BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL - similar to Itead Sonoff Dual, hardware specific #endif //------------------------------------------------------------------------------ diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 797caf86..58547aed 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -556,6 +556,8 @@ #define BUTTON2_RELAY 2 #define BUTTON3_RELAY 1 + #define BUTTON_EVENTS_SOURCE BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL + // LEDs #define LED1_PIN 13 #define LED1_PIN_INVERSE 1 @@ -4125,6 +4127,8 @@ #define BUTTON3_RELAY 2 #define BUTTON4_RELAY 1 + #define BUTTON_EVENTS_SOURCE BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL + // ----------------------------------------------------------------------------- // Teckin SP20 // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/types.h b/code/espurna/config/types.h index dc634490..c3292718 100644 --- a/code/espurna/config/types.h +++ b/code/espurna/config/types.h @@ -44,6 +44,10 @@ #define BUTTON_MODE_DIM_UP 10 #define BUTTON_MODE_DIM_DOWN 11 +#define BUTTON_EVENTS_SOURCE_GENERIC 0 +#define BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL 1 +#define BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL 2 + //------------------------------------------------------------------------------ // ENCODER //------------------------------------------------------------------------------