@ -8,10 +8,12 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
# if BUTTON_SUPPORT
# include <DebounceEvent.h >
# include <bitset >
# include <memory>
# include <vector>
# include "compat.h"
# include "gpio.h"
# include "system.h"
# include "relay.h"
# include "light.h"
@ -19,38 +21,197 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
# include "button.h"
# include "button_config.h"
# include "libs/DebounceEvent.h"
// TODO: if we are using such conversion helpers across the codebase, should convert() be in internal ns?
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 ;
}
}
template < >
debounce_event : : types : : DefaultState convert ( const String & value ) {
switch ( value . toInt ( ) ) {
case 0 :
return debounce_event : : types : : DefaultState : : Low ;
case 1 :
default :
return debounce_event : : types : : DefaultState : : High ;
}
}
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 ;
}
}
} // namespace settings::internal
} // namespace settings
// -----------------------------------------------------------------------------
constexpr const debounce_event : : types : : Config _buttonDecodeConfigBitmask ( const unsigned char bitmask ) {
return {
( ( bitmask & ButtonMask : : Pushbutton )
? debounce_event : : types : : Mode : : Pushbutton
: debounce_event : : types : : Mode : : Switch ) ,
( ( bitmask & ButtonMask : : DefaultHigh )
? debounce_event : : types : : DefaultState : : High
: debounce_event : : types : : DefaultState : : Low ) ,
( ( bitmask & ButtonMask : : SetPullup )
? debounce_event : : types : : PinMode : : InputPullup : ( bitmask & ButtonMask : : SetPulldown )
? debounce_event : : types : : PinMode : : InputPullup : debounce_event : : types : : PinMode : : Input )
} ;
}
constexpr const uint16_t _buttonDecodeEventAction ( const button_actions_t & actions , button_event_t event ) {
return (
( event = = button_event_t : : Pressed ) ? actions . pressed :
( 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 : 0U
) ;
}
constexpr const button_event_t _buttonMapReleased ( uint8_t count , unsigned long length , unsigned long lngclick_delay , unsigned long lnglngclick_delay ) {
return (
( 1 = = count ) ? (
( length > lnglngclick_delay ) ? button_event_t : : LongLongClick :
( length > lngclick_delay ) ? button_event_t : : LongClick : button_event_t : : Click
) :
( 2 = = count ) ? button_event_t : : DoubleClick :
( 3 = = count ) ? button_event_t : : TripleClick :
button_event_t : : None
) ;
}
button_actions_t _buttonConstructActions ( unsigned char index ) {
return {
_buttonPress ( index ) ,
_buttonClick ( index ) ,
_buttonDoubleClick ( index ) ,
_buttonLongClick ( index ) ,
_buttonLongLongClick ( index ) ,
_buttonTripleClick ( index )
} ;
}
debounce_event : : types : : Config _buttonConfig ( unsigned char index ) {
const auto config = _buttonDecodeConfigBitmask ( _buttonConfigBitmask ( index ) ) ;
return {
getSetting ( { " btnMode " , index } , config . mode ) ,
getSetting ( { " btnDefState " , index } , config . default_state ) ,
getSetting ( { " btnPinMode " , index } , config . pin_mode )
} ;
}
// -----------------------------------------------------------------------------
// TODO: dblclick and debounce delays - right now a global setting, independent of ID
unsigned long button_t : : DebounceDelay = BUTTON_DEBOUNCE_DELAY ;
unsigned long button_t : : DblclickDelay = BUTTON_DBLCLICK_DELAY ;
button_event_delays_t : : button_event_delays_t ( ) :
debounce ( _buttonDebounceDelay ( ) ) ,
repeat ( _buttonRepeatDelay ( ) ) ,
lngclick ( _buttonLongClickDelay ( ) ) ,
lnglngclick ( _buttonLongLongClickDelay ( ) )
{ }
button_event_delays_t : : button_event_delays_t ( unsigned long debounce , unsigned long repeat , unsigned long lngclick , unsigned long lnglngclick ) :
debounce ( debounce ) ,
repeat ( repeat ) ,
lngclick ( lngclick ) ,
lnglngclick ( lnglngclick )
{ }
button_t : : button_t ( unsigned char pin , unsigned char mode , unsigned long actions , unsigned char relayID ) :
event ( new DebounceEvent ( pin , mode , DebounceDelay , DblclickDelay ) ) ,
button_t : : button_t ( unsigned char relayID , const button_actions_t & actions , const button_event_delays_t & delays ) :
event_emitter ( nullptr ) ,
event_delays ( delays ) ,
actions ( actions ) ,
relayID ( relayID )
{ }
button_t : : button_t ( unsigned char index ) :
button_t ( _buttonPin ( index ) , _buttonMode ( index ) , _buttonConstructActions ( index ) , _buttonRelay ( index ) )
button_t : : button_t ( std : : shared_ptr < BasePin > pin , const debounce_event : : types : : Config & config , unsigned char relayID , const button_actions_t & actions , const button_event_delays_t & delays ) :
event_emitter ( std : : make_unique < debounce_event : : EventEmitter > ( pin , config , delays . debounce , delays . repeat ) ) ,
event_delays ( delays ) ,
actions ( actions ) ,
relayID ( relayID )
{ }
bool button_t : : state ( ) {
return event - > pressed ( ) ;
return event_emitter - > isPressed ( ) ;
}
button_event_t button_t : : loop ( ) {
if ( ! event_emitter ) {
return button_event_t : : None ;
}
auto event = event_emitter - > loop ( ) ;
if ( event = = debounce_event : : types : : EventNone ) {
return button_event_t : : None ;
}
switch ( event ) {
case debounce_event : : types : : EventPressed :
return button_event_t : : Pressed ;
case debounce_event : : types : : EventChanged :
return button_event_t : : Click ;
case debounce_event : : types : : EventReleased : {
return _buttonMapReleased (
event_emitter - > getEventCount ( ) ,
event_emitter - > getEventLength ( ) ,
event_delays . lngclick ,
event_delays . lnglngclick
) ;
}
case debounce_event : : types : : EventNone :
default :
break ;
}
return button_event_t : : None ;
}
std : : vector < button_t > _buttons ;
// -----------------------------------------------------------------------------
unsigned char buttonCount ( ) {
return _buttons . size ( ) ;
}
# if MQTT_SUPPORT
void buttonMQTT ( unsigned char id , uint8_t event ) {
std : : bitset < ButtonsMax > _buttons_mqtt_send_all (
( 1 = = BUTTON_MQTT_SEND_ALL_EVENTS ) ? 0xFFFFFFFFUL : 0UL
) ;
std : : bitset < ButtonsMax > _buttons_mqtt_retain (
( 1 = = BUTTON_MQTT_RETAIN ) ? 0xFFFFFFFFUL : 0UL
) ;
void buttonMQTT ( unsigned char id , button_event_t event ) {
char payload [ 4 ] = { 0 } ;
itoa ( event , payload , 10 ) ;
mqttSend ( MQTT_TOPIC_BUTTON , id , payload , false , false ) ; // 1st bool = force, 2nd = retain
itoa ( _buttonEventNumber ( event ) , payload , 10 ) ;
// mqttSend(topic, id, payload, force, retain)
mqttSend ( MQTT_TOPIC_BUTTON , id , payload , false , _buttons_mqtt_retain [ id ] ) ;
}
# endif
@ -63,6 +224,101 @@ void _buttonWebSocketOnVisible(JsonObject& root) {
}
}
# if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \
( BUTTON_EVENTS_SOURCE = = BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL )
void _buttonWebSocketOnConnected ( JsonObject & root ) {
root [ " btnRepDel " ] = getSetting ( " btnRepDel " , _buttonRepeatDelay ( ) ) ;
}
# else
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 event source
// 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?
JsonArray & schema = module . createNestedArray ( " _schema " ) ;
schema . add ( " GPIO " ) ;
schema . add ( " Mode " ) ;
schema . add ( " DefState " ) ;
schema . add ( " PinMode " ) ;
schema . add ( " Relay " ) ;
schema . add ( " Press " ) ;
schema . add ( " Click " ) ;
schema . add ( " Dclk " ) ;
schema . add ( " Lclk " ) ;
schema . add ( " LLclk " ) ;
schema . add ( " Tclk " ) ;
schema . add ( " DebDel " ) ;
schema . add ( " RepDel " ) ;
schema . add ( " LclkDel " ) ;
schema . add ( " LLclkDel " ) ;
# if MQTT_SUPPORT
schema . add ( " MqttSendAll " ) ;
schema . add ( " MqttRetain " ) ;
# endif
JsonArray & buttons = module . createNestedArray ( " list " ) ;
for ( unsigned char i = 0 ; i < buttonCount ( ) ; i + + ) {
JsonArray & button = buttons . createNestedArray ( ) ;
// TODO: configure PIN object instead of button specifically, link PIN<->BUTTON
if ( _buttons [ i ] . getPin ( ) ) {
button . add ( getSetting ( { " btnGPIO " , index } , _buttonPin ( index ) ) ) ;
const auto config = _buttonConfig ( index ) ;
button . add ( static_cast < int > ( config . mode ) ) ;
button . add ( static_cast < int > ( config . default_state ) ) ;
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 ] . relayID ) ;
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 ) ;
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 ) ;
// 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
}
# endif
}
# endif // BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC
bool _buttonWebSocketOnKeyCheck ( const char * key , JsonVariant & value ) {
return ( strncmp ( key , " btn " , 3 ) = = 0 ) ;
}
@ -74,122 +330,214 @@ bool buttonState(unsigned char id) {
return _buttons [ id ] . state ( ) ;
}
unsigned char buttonAction ( unsigned char id , unsigned char event ) {
if ( id > = _buttons . size ( ) ) return BUTTON_MODE_NONE ;
uint16_t buttonAction ( unsigned char id , const button_event_t event ) {
if ( id > = _buttons . size ( ) ) return 0 ;
return _buttonDecodeEventAction ( _buttons [ id ] . actions , event ) ;
}
void buttonEvent ( unsigned char id , unsigned char event ) {
int _buttonEventNumber ( button_event_t event ) {
return static_cast < int > ( event ) ;
}
// Approach based on https://github.com/esp8266/Arduino/pull/6950
// "PROGMEM footprint cleanup for responseCodeToString (#6950)"
// In this particular case, saves 76 bytes (120 vs 44)
# if 1
String _buttonEventString ( button_event_t event ) {
const __FlashStringHelper * ptr = nullptr ;
switch ( event ) {
case button_event_t : : Pressed :
ptr = F ( " Pressed " ) ;
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 :
default :
ptr = F ( " None " ) ;
break ;
}
return String ( ptr ) ;
}
# else
String _buttonEventString ( button_event_t event ) {
switch ( event ) {
case button_event_t : : Pressed :
return F ( " Pressed " ) ;
case button_event_t : : Click :
return F ( " Click " ) ;
case button_event_t : : DoubleClick :
return F ( " Double-click " ) ;
case button_event_t : : LongClick :
return F ( " Long-click " ) ;
case button_event_t : : LongLongClick :
return F ( " Looong-click " ) ;
case button_event_t : : TripleClick :
return F ( " Triple-click " ) ;
case button_event_t : : None :
default :
return F ( " None " ) ;
}
}
# endif
void buttonEvent ( unsigned char id , button_event_t event ) {
DEBUG_MSG_P ( PSTR ( " [BUTTON] Button #%u event %u \n " ) , id , event ) ;
if ( event = = 0 ) return ;
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 ;
auto & button = _buttons [ id ] ;
unsigned char action = _buttonDecodeEventAction ( button . actions , event ) ;
auto action = _buttonDecodeEventAction ( button . actions , event ) ;
# if MQTT_SUPPORT
if ( action ! = BUTTON_MODE_NONE | | BUTTON_MQTT_SEND_ALL_EVENTS ) {
buttonMQTT ( id , event ) ;
}
# endif
# if THERMOSTAT_DISPLAY_SUPPORT
if ( BUTTON_MODE_DISPLAY_ON = = action ) {
displayOn ( ) ;
if ( action | | _buttons_mqtt_send_all [ id ] ) {
buttonMQTT ( id , event ) ;
}
# endif
if ( BUTTON_MODE_TOGGLE = = action ) {
relayToggle ( button . relayID ) ;
}
switch ( action ) {
case BUTTON_ACTION_TOGGLE :
relayToggle ( button . relayID ) ;
break ;
if ( BUTTON_MODE_ON = = action ) {
relayStatus ( button . relayID , true ) ;
}
case BUTTON_ACTION_ON :
relayStatus ( button . relayID , true ) ;
break ;
if ( BUTTON_MODE_OFF = = action ) {
relayStatus ( button . relayID , false ) ;
}
if ( BUTTON_MODE_AP = = action ) {
if ( wifiState ( ) & WIFI_STATE_AP ) {
wifiStartSTA ( ) ;
} else {
wifiStartAP ( ) ;
}
}
if ( BUTTON_MODE_RESET = = action ) {
deferredReset ( 100 , CUSTOM_RESET_HARDWARE ) ;
}
case BUTTON_ACTION_OFF :
relayStatus ( button . relayID , false ) ;
break ;
if ( BUTTON_MODE_FACTORY = = action ) {
DEBUG_MSG_P ( PSTR ( " \n \n FACTORY RESET \n \n " ) ) ;
resetSettings ( ) ;
deferredReset ( 100 , CUSTOM_RESET_FACTORY ) ;
}
case BUTTON_ACTION_AP :
if ( wifiState ( ) & WIFI_STATE_AP ) {
wifiStartSTA ( ) ;
} else {
wifiStartAP ( ) ;
}
break ;
case BUTTON_ACTION_RESET :
deferredReset ( 100 , CUSTOM_RESET_HARDWARE ) ;
break ;
case BUTTON_ACTION_FACTORY :
DEBUG_MSG_P ( PSTR ( " \n \n FACTORY RESET \n \n " ) ) ;
resetSettings ( ) ;
deferredReset ( 100 , CUSTOM_RESET_FACTORY ) ;
break ;
# if defined(JUSTWIFI_ENABLE_WPS)
if ( BUTTON_MODE_WPS = = action ) {
case BUTTON_ACTION_WPS :
wifiStartWPS ( ) ;
}
break ;
# endif // defined(JUSTWIFI_ENABLE_WPS)
# if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if ( BUTTON_MODE_SMART_CONFIG = = action ) {
case BUTTON_ACTION_SMART_CONFIG :
wifiStartSmartConfig ( ) ;
}
break ;
# endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
# if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if ( BUTTON_MODE_DIM_UP = = action ) {
lightBrightnessStep ( 1 ) ;
lightUpdate ( true , true ) ;
}
if ( BUTTON_MODE_DIM_DOWN = = action ) {
lightBrightnessStep ( - 1 ) ;
lightUpdate ( true , true ) ;
}
case BUTTON_ACTION_DIM_UP :
lightBrightnessStep ( 1 ) ;
lightUpdate ( true , true ) ;
break ;
case BUTTON_ACTION_DIM_DOWN :
lightBrightnessStep ( - 1 ) ;
lightUpdate ( true , true ) ;
break ;
# endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
}
# if THERMOSTAT_DISPLAY_SUPPORT
case BUTTON_ACTION_DISPLAY_ON :
displayOn ( ) ;
break ;
# endif
unsigned char buttonAdd ( unsigned char pin , unsigned char mode , unsigned long actions , unsigned char relayID ) {
_buttons . emplace_back ( pin , mode , actions , relayID ) ;
return _buttons . size ( ) - 1 ;
}
}
void buttonSetup ( ) {
}
// Special hardware cases
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
}
# if defined(ITEAD_SONOFF_DUAL)
// 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 , getSetting ( { key , index } , default_value ) ) ;
}
_buttons . reserve ( 3 ) ;
void buttonSetup ( ) {
buttonAdd ( GPIO_NONE , BUTTON_PUSHBUTTON , 0 , _buttonRelay ( 0 ) ) ;
buttonAdd ( GPIO_NONE , BUTTON_PUSHBUTTON , 0 , _buttonRelay ( 1 ) ) ;
buttonAdd ( GPIO_NONE , BUTTON_PUSHBUTTON , 0 , _buttonRelay ( 2 ) ) ;
// Backwards compatibility
moveSetting ( " btnDelay " , " btnRepDel " ) ;
# elif defined(FOXEL_LIGHTFOX_DUAL)
// Special hardware cases
# if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \
( BUTTON_EVENTS_SOURCE = = BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL )
_buttons . reserve ( 4 ) ;
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
const auto actions = _buttonConstructActions (
BUTTON_MODE_NONE , BUTTON_MODE_TOGGLE , BUTTON_MODE_NONE ,
BUTTON_MODE_NONE , BUTTON_MODE_NONE , BUTTON_MODE_NONE
) ;
_buttons . reserve ( buttons ) ;
for ( unsigned char id = 0 ; id < 4 ; + + id ) {
buttonAdd (
GPIO_NONE , BUTTON_PUSHBUTTON ,
actions , getSetting ( { " btnRelay " , id } , _buttonRelay ( id ) )
// Ignore real button delays since we don't use them here
const auto delays = button_event_delays_t ( ) ;
for ( unsigned char index = 0 ; index < buttons ; + + index ) {
const button_actions_t actions {
BUTTON_ACTION_NONE ,
// The only generated event is ::Click
getSetting ( { " btnClick " , index } , _buttonClick ( index ) ) ,
BUTTON_ACTION_NONE ,
BUTTON_ACTION_NONE ,
BUTTON_ACTION_NONE ,
BUTTON_ACTION_NONE
} ;
_buttons . emplace_back (
getSetting ( { " btnRelay " , index } , _buttonRelay ( index ) ) ,
actions ,
delays
) ;
}
// Generic GPIO input handlers
# else
# elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC
size_t buttons = 0 ;
@ -220,108 +568,139 @@ void buttonSetup() {
_buttons . reserve ( buttons ) ;
// TODO: load based on index
button_t : : DebounceDelay = getSetting ( " btnDebounce " , BUTTON_DEBOUNCE_DELAY ) ;
button_t : : DblclickDelay = getSetting ( " btnDelay " , BUTTON_DBLCLICK_DELAY ) ;
for ( unsigned char id = 0 ; id < buttons ; + + id ) {
_buttons . emplace_back ( id ) ;
for ( unsigned char index = 0 ; index < buttons ; + + index ) {
const auto pin = getSetting ( { " btnGPIO " , index } , _buttonPin ( index ) ) ;
if ( ! gpioValid ( pin ) ) {
break ;
}
const auto relayID = getSetting ( { " btnRelay " , index } , _buttonRelay ( index ) ) ;
// TODO: compatibility proxy, fetch global key before indexed
const button_event_delays_t delays {
_buttonGetSetting ( " btnDebDel " , index , _buttonDebounceDelay ( index ) ) ,
_buttonGetSetting ( " btnRepDel " , index , _buttonRepeatDelay ( index ) ) ,
_buttonGetSetting ( " btnLclkDel " , index , _buttonLongClickDelay ( index ) ) ,
_buttonGetSetting ( " btnLLclkDel " , index , _buttonLongLongClickDelay ( index ) ) ,
} ;
const button_actions_t actions {
getSetting ( { " btnPress " , index } , _buttonPress ( 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 ) )
} ;
const auto config = _buttonConfig ( index ) ;
// TODO: allow to change GpioPin to something else based on config?
_buttons . emplace_back (
std : : make_shared < GpioPin > ( pin ) , config ,
relayID , actions , delays
) ;
}
# endif
_buttonConfigure ( ) ;
DEBUG_MSG_P ( PSTR ( " [BUTTON] Number of buttons: %u \n " ) , _buttons . size ( ) ) ;
// Websocket Callbacks
# if WEB_SUPPORT
wsRegister ( )
. onConnected ( _buttonWebSocketOnVisible )
. onVisible ( _buttonWebSocketOnVisible )
. onKeyCheck ( _buttonWebSocketOnKeyCheck ) ;
# endif
// Register loop
// Register system callbacks
espurnaRegisterLoop ( buttonLoop ) ;
espurnaRegisterReload ( _buttonConfigure ) ;
}
void buttonLoop ( ) {
// Sonoff Dual does not do real GPIO readings and we
// depend on the external MCU to send us relay / button events
// Lightfox uses the same protocol as Dual, but has slightly different actions
// TODO: move this to a separate 'hardware' setup file?
# 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 < relayCount ( ) ; i + + ) {
bool status = ( value & ( 1 < < 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 ;
}
# elif defined(FOXEL_LIGHTFOX_DUAL)
const unsigned char value [ [ gnu : : unused ] ] = bytes [ 2 ] ;
if ( Serial . available ( ) > = 4 ) {
if ( Serial . read ( ) = = 0xA0 ) {
if ( Serial . read ( ) = = 0x04 ) {
unsigned char value = Serial . read ( ) ;
if ( Serial . read ( ) = = 0xA1 ) {
# if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL
DEBUG_MSG_P ( PSTR ( " [BUTTON] [LIGHTFOX] Received buttons mask: %d \n " ) , value ) ;
// 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_t : : Click ) ;
return ;
}
for ( unsigned int i = 0 ; i < _buttons . size ( ) ; i + + ) {
// 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 < relayCount ( ) ; i + + ) {
bool clicked = ( value & ( 1 < < i ) ) > 0 ;
const bool status = ( value & ( 1 < < i ) ) > 0 ;
if ( clicked ) {
buttonEvent ( i , BUTTON_EVENT_CLICK ) ;
}
}
}
}
}
// Check if the status for that relay has changed
if ( relayStatus ( i ) ! = status ) {
buttonEvent ( i , button_event_t : : Click ) ;
break ;
}
# else
}
for ( size_t id = 0 ; id < _buttons . size ( ) ; + + id ) {
auto & button = _buttons [ id ] ;
if ( auto event = button . event - > loop ( ) ) {
buttonEvent ( id , _buttonMapEvent (
event ,
button . event - > getEventCount ( ) ,
button . event - > getEventLength ( )
) ) ;
}
}
# elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL
DEBUG_MSG_P ( PSTR ( " [BUTTON] [LIGHTFOX] Received buttons mask: %u \n " ) , value ) ;
for ( unsigned int i = 0 ; i < _buttons . size ( ) ; i + + ) {
if ( ( value & ( 1 < < i ) ) > 0 ) ;
buttonEvent ( i , button_event_t : : Click ) ;
}
}
# endif // BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL
}
void _buttonLoopGeneric ( ) {
for ( size_t id = 0 ; id < _buttons . size ( ) ; + + id ) {
auto event = _buttons [ id ] . loop ( ) ;
if ( event ! = button_event_t : : None ) {
buttonEvent ( id , event ) ;
}
}
}
void buttonLoop ( ) {
# if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC
_buttonLoopGeneric ( ) ;
# elif (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \
( BUTTON_EVENTS_SOURCE = = BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL )
_buttonLoopSonoffDual ( ) ;
# else
# warning "Unknown value for BUTTON_EVENTS_SOURCE"
# endif
}