2020-03-25 09:00:55 +01:00
# include "wled.h"
2020-03-25 10:14:23 +01:00
2016-12-31 00:38:51 +01:00
/*
* Physical IO
*/
2022-03-15 09:55:23 +01:00
# define WLED_DEBOUNCE_THRESHOLD 50 // only consider button input of at least 50ms as valid (debouncing)
# define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms
# define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press
# define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0
# define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP
# define WLED_LONG_FACTORY_RESET 10000 // how long button 0 needs to be held to trigger a factory reset
2021-04-12 00:45:33 +02:00
2021-06-03 12:18:11 +02:00
static const char _mqtt_topic_button [ ] PROGMEM = " %s/button/%d " ; // optimize flash usage
2021-05-20 19:54:07 +02:00
void shortPressAction ( uint8_t b )
2019-03-13 11:13:03 +01:00
{
2021-10-31 11:57:03 +01:00
if ( ! macroButton [ b ] ) {
switch ( b ) {
2022-02-20 22:24:11 +01:00
case 0 : toggleOnOff ( ) ; stateUpdated ( CALL_MODE_BUTTON ) ; break ;
2022-02-22 10:42:00 +01:00
case 1 : + + effectCurrent % = strip . getModeCount ( ) ; stateChanged = true ; colorUpdated ( CALL_MODE_BUTTON ) ; break ;
2021-10-31 11:57:03 +01:00
}
2019-03-13 11:13:03 +01:00
} else {
2021-12-11 23:44:21 +01:00
applyPreset ( macroButton [ b ] , CALL_MODE_BUTTON_PRESET ) ;
2019-03-13 11:13:03 +01:00
}
2021-06-03 12:18:11 +02:00
// publish MQTT message
2021-07-01 20:51:52 +02:00
if ( buttonPublishMqtt & & WLED_MQTT_CONNECTED ) {
2021-06-03 12:18:11 +02:00
char subuf [ 64 ] ;
sprintf_P ( subuf , _mqtt_topic_button , mqttDeviceTopic , ( int ) b ) ;
mqtt - > publish ( subuf , 0 , false , " short " ) ;
}
2019-03-13 11:13:03 +01:00
}
2021-10-31 11:57:03 +01:00
void longPressAction ( uint8_t b )
{
if ( ! macroLongPress [ b ] ) {
switch ( b ) {
2022-02-20 22:24:11 +01:00
case 0 : setRandomColor ( col ) ; colorUpdated ( CALL_MODE_BUTTON ) ; break ;
case 1 : bri + = 8 ; stateUpdated ( CALL_MODE_BUTTON ) ; buttonPressedTime [ b ] = millis ( ) ; break ; // repeatable action
2021-10-31 11:57:03 +01:00
}
} else {
2021-12-11 23:44:21 +01:00
applyPreset ( macroLongPress [ b ] , CALL_MODE_BUTTON_PRESET ) ;
2021-10-31 11:57:03 +01:00
}
// publish MQTT message
if ( buttonPublishMqtt & & WLED_MQTT_CONNECTED ) {
char subuf [ 64 ] ;
sprintf_P ( subuf , _mqtt_topic_button , mqttDeviceTopic , ( int ) b ) ;
mqtt - > publish ( subuf , 0 , false , " long " ) ;
}
}
void doublePressAction ( uint8_t b )
{
if ( ! macroDoublePress [ b ] ) {
switch ( b ) {
//case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set
2022-02-20 22:24:11 +01:00
case 1 : + + effectPalette % = strip . getPaletteCount ( ) ; colorUpdated ( CALL_MODE_BUTTON ) ; break ;
2021-10-31 11:57:03 +01:00
}
} else {
2021-12-11 23:44:21 +01:00
applyPreset ( macroDoublePress [ b ] , CALL_MODE_BUTTON_PRESET ) ;
2021-10-31 11:57:03 +01:00
}
// publish MQTT message
if ( buttonPublishMqtt & & WLED_MQTT_CONNECTED ) {
char subuf [ 64 ] ;
sprintf_P ( subuf , _mqtt_topic_button , mqttDeviceTopic , ( int ) b ) ;
mqtt - > publish ( subuf , 0 , false , " double " ) ;
}
}
2021-05-20 19:54:07 +02:00
bool isButtonPressed ( uint8_t i )
2020-09-20 16:12:46 +02:00
{
2021-05-20 19:54:07 +02:00
if ( btnPin [ i ] < 0 ) return false ;
2022-02-09 19:59:17 +01:00
uint8_t pin = btnPin [ i ] ;
2021-05-20 19:54:07 +02:00
switch ( buttonType [ i ] ) {
case BTN_TYPE_NONE :
case BTN_TYPE_RESERVED :
break ;
case BTN_TYPE_PUSH :
case BTN_TYPE_SWITCH :
2022-02-09 19:59:17 +01:00
if ( digitalRead ( pin ) = = LOW ) return true ;
2021-05-20 19:54:07 +02:00
break ;
case BTN_TYPE_PUSH_ACT_HIGH :
2021-06-03 12:18:11 +02:00
case BTN_TYPE_PIR_SENSOR :
2022-02-09 19:59:17 +01:00
if ( digitalRead ( pin ) = = HIGH ) return true ;
2021-05-20 19:54:07 +02:00
break ;
case BTN_TYPE_TOUCH :
2022-01-24 11:34:02 +01:00
# if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
2022-02-09 19:59:17 +01:00
if ( touchRead ( pin ) < = touchThreshold ) return true ;
2021-05-20 19:54:07 +02:00
# endif
break ;
}
2020-09-20 16:12:46 +02:00
return false ;
}
2021-05-20 19:54:07 +02:00
void handleSwitch ( uint8_t b )
2021-04-12 00:45:33 +02:00
{
2021-06-03 12:18:11 +02:00
// isButtonPressed() handles inverted/noninverted logic
2021-05-20 19:54:07 +02:00
if ( buttonPressedBefore [ b ] ! = isButtonPressed ( b ) ) {
buttonPressedTime [ b ] = millis ( ) ;
buttonPressedBefore [ b ] = ! buttonPressedBefore [ b ] ;
2021-04-12 00:45:33 +02:00
}
2021-05-20 19:54:07 +02:00
if ( buttonLongPressed [ b ] = = buttonPressedBefore [ b ] ) return ;
2021-04-12 00:45:33 +02:00
2021-05-20 19:54:07 +02:00
if ( millis ( ) - buttonPressedTime [ b ] > WLED_DEBOUNCE_THRESHOLD ) { //fire edge event only after 50ms without change (debounce)
2021-06-03 12:18:11 +02:00
if ( ! buttonPressedBefore [ b ] ) { // on -> off
2021-12-11 23:44:21 +01:00
if ( macroButton [ b ] ) applyPreset ( macroButton [ b ] , CALL_MODE_BUTTON_PRESET ) ;
2021-04-12 00:45:33 +02:00
else { //turn on
2022-02-20 22:24:11 +01:00
if ( ! bri ) { toggleOnOff ( ) ; stateUpdated ( CALL_MODE_BUTTON ) ; }
2021-04-12 00:45:33 +02:00
}
2021-06-03 12:18:11 +02:00
} else { // off -> on
2021-12-11 23:44:21 +01:00
if ( macroLongPress [ b ] ) applyPreset ( macroLongPress [ b ] , CALL_MODE_BUTTON_PRESET ) ;
2021-04-12 00:45:33 +02:00
else { //turn off
2022-02-20 22:24:11 +01:00
if ( bri ) { toggleOnOff ( ) ; stateUpdated ( CALL_MODE_BUTTON ) ; }
2021-04-12 00:45:33 +02:00
}
}
2021-06-03 12:18:11 +02:00
// publish MQTT message
2021-07-01 20:51:52 +02:00
if ( buttonPublishMqtt & & WLED_MQTT_CONNECTED ) {
2021-06-03 12:18:11 +02:00
char subuf [ 64 ] ;
if ( buttonType [ b ] = = BTN_TYPE_PIR_SENSOR ) sprintf_P ( subuf , PSTR ( " %s/motion/%d " ) , mqttDeviceTopic , ( int ) b ) ;
else sprintf_P ( subuf , _mqtt_topic_button , mqttDeviceTopic , ( int ) b ) ;
mqtt - > publish ( subuf , 0 , false , ! buttonPressedBefore [ b ] ? " off " : " on " ) ;
}
2021-05-20 19:54:07 +02:00
buttonLongPressed [ b ] = buttonPressedBefore [ b ] ; //save the last "long term" switch state
2021-04-12 00:45:33 +02:00
}
}
2022-06-20 16:04:43 +02:00
# define ANALOG_BTN_READ_CYCLE 250 // min time between two analog reading cycles
# define STRIP_WAIT_TIME 6 // max wait time in case of strip.isUpdating()
2022-06-20 22:00:23 +02:00
# define POT_SMOOTHING 0.25f // smoothing factor for raw potentiometer readings
2022-06-20 16:04:43 +02:00
# define POT_SENSITIVITY 4 // changes below this amount are noise (POT scratching, or ADC noise)
2021-05-21 13:33:22 +02:00
void handleAnalog ( uint8_t b )
{
2022-06-22 09:58:21 +02:00
static uint8_t oldRead [ WLED_MAX_BUTTONS ] = { 0 } ;
2022-06-20 21:56:16 +02:00
static float filteredReading [ WLED_MAX_BUTTONS ] = { 0.0f } ;
2022-06-20 16:04:43 +02:00
uint16_t rawReading ; // raw value from analogRead, scaled to 12bit
2021-05-21 13:33:22 +02:00
# ifdef ESP8266
2022-06-20 16:04:43 +02:00
rawReading = analogRead ( A0 ) < < 2 ; // convert 10bit read to 12bit
2021-05-21 13:33:22 +02:00
# else
2022-06-20 16:04:43 +02:00
rawReading = analogRead ( btnPin [ b ] ) ; // collect at full 12bit resolution
2021-05-21 13:33:22 +02:00
# endif
2022-06-20 16:04:43 +02:00
yield ( ) ; // keep WiFi task running - analog read may take several millis on ESP8266
2022-06-22 09:58:21 +02:00
filteredReading [ b ] + = POT_SMOOTHING * ( ( float ( rawReading ) / 16.0f ) - filteredReading [ b ] ) ; // filter raw input, and scale to [0..255]
2022-06-20 16:04:43 +02:00
uint16_t aRead = max ( min ( int ( filteredReading [ b ] ) , 255 ) , 0 ) ; // squash into 8bit
if ( aRead < = POT_SENSITIVITY ) aRead = 0 ; // make sure that 0 and 255 are used
if ( aRead > = 255 - POT_SENSITIVITY ) aRead = 255 ;
2021-06-03 12:18:11 +02:00
if ( buttonType [ b ] = = BTN_TYPE_ANALOG_INVERTED ) aRead = 255 - aRead ;
2021-05-28 14:14:50 +02:00
// remove noise & reduce frequency of UI updates
2022-06-20 16:04:43 +02:00
if ( abs ( int ( aRead ) - int ( oldRead [ b ] ) ) < = POT_SENSITIVITY ) return ; // no significant change in reading
2022-06-22 12:36:47 +02:00
// Unomment the next lines if you still see flickering related to potentiometer
// This waits until strip finishes updating (why: strip was not updating at the start of handleButton() but may have started during analogRead()?)
//unsigned long wait_started = millis();
//while(strip.isUpdating() && (millis() - wait_started < STRIP_WAIT_TIME)) {
// delay(1);
//}
//if (strip.isUpdating()) return; // give up
2021-05-21 13:33:22 +02:00
2021-05-25 23:59:43 +02:00
oldRead [ b ] = aRead ;
2021-05-21 13:33:22 +02:00
// if no macro for "short press" and "long press" is defined use brightness control
if ( ! macroButton [ b ] & & ! macroLongPress [ b ] ) {
2021-05-28 08:47:15 +02:00
// if "double press" macro defines which option to change
2021-05-25 23:59:43 +02:00
if ( macroDoublePress [ b ] > = 250 ) {
2021-05-28 08:47:15 +02:00
// global brightness
2021-05-25 23:59:43 +02:00
if ( aRead = = 0 ) {
briLast = bri ;
bri = 0 ;
} else {
2021-05-28 14:14:50 +02:00
bri = aRead ;
2021-05-28 08:47:15 +02:00
}
} else if ( macroDoublePress [ b ] = = 249 ) {
// effect speed
2021-05-28 14:14:50 +02:00
effectSpeed = aRead ;
2021-05-28 08:47:15 +02:00
} else if ( macroDoublePress [ b ] = = 248 ) {
// effect intensity
2021-05-28 14:14:50 +02:00
effectIntensity = aRead ;
2021-05-28 08:47:15 +02:00
} else if ( macroDoublePress [ b ] = = 247 ) {
// selected palette
2021-05-28 14:14:50 +02:00
effectPalette = map ( aRead , 0 , 252 , 0 , strip . getPaletteCount ( ) - 1 ) ;
2022-06-20 16:04:43 +02:00
effectPalette = constrain ( effectPalette , 0 , strip . getPaletteCount ( ) - 1 ) ; // map is allowed to "overshoot", so we need to contrain the result
2021-05-30 02:03:32 +02:00
} else if ( macroDoublePress [ b ] = = 200 ) {
// primary color, hue, full saturation
colorHStoRGB ( aRead * 256 , 255 , col ) ;
2021-05-21 13:33:22 +02:00
} else {
// otherwise use "double press" for segment selection
2022-07-06 13:13:54 +02:00
Segment & seg = strip . getSegment ( macroDoublePress [ b ] ) ;
2021-05-21 13:33:22 +02:00
if ( aRead = = 0 ) {
2021-05-25 23:59:43 +02:00
seg . setOption ( SEG_OPTION_ON , 0 ) ; // off
2021-05-21 13:33:22 +02:00
} else {
2021-05-28 14:14:50 +02:00
seg . setOpacity ( aRead , macroDoublePress [ b ] ) ;
2021-05-25 23:59:43 +02:00
seg . setOption ( SEG_OPTION_ON , 1 ) ;
2021-05-21 13:33:22 +02:00
}
2021-05-25 23:59:43 +02:00
// this will notify clients of update (websockets,mqtt,etc)
2021-07-09 18:54:28 +02:00
updateInterfaces ( CALL_MODE_BUTTON ) ;
2021-05-21 13:33:22 +02:00
}
} else {
//TODO:
// we can either trigger a preset depending on the level (between short and long entries)
// or use it for RGBW direct control
}
2021-07-09 18:54:28 +02:00
colorUpdated ( CALL_MODE_BUTTON ) ;
2021-05-21 13:33:22 +02:00
}
2016-11-19 19:39:17 +01:00
void handleButton ( )
{
2021-05-25 23:59:43 +02:00
static unsigned long lastRead = 0UL ;
2022-01-09 15:13:33 +01:00
bool analog = false ;
unsigned long now = millis ( ) ;
2021-05-25 23:59:43 +02:00
2022-06-20 16:04:43 +02:00
if ( strip . isUpdating ( ) ) return ; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle)
2021-05-20 19:54:07 +02:00
for ( uint8_t b = 0 ; b < WLED_MAX_BUTTONS ; b + + ) {
2021-05-28 17:45:14 +02:00
# ifdef ESP8266
2021-06-03 12:18:11 +02:00
if ( ( btnPin [ b ] < 0 & & ! ( buttonType [ b ] = = BTN_TYPE_ANALOG | | buttonType [ b ] = = BTN_TYPE_ANALOG_INVERTED ) ) | | buttonType [ b ] = = BTN_TYPE_NONE ) continue ;
2021-05-28 17:45:14 +02:00
# else
2021-05-25 23:59:43 +02:00
if ( btnPin [ b ] < 0 | | buttonType [ b ] = = BTN_TYPE_NONE ) continue ;
2021-05-28 17:45:14 +02:00
# endif
2021-04-12 00:45:33 +02:00
2021-10-31 11:57:03 +01:00
if ( usermods . handleButton ( b ) ) continue ; // did usermod handle buttons
2022-06-22 16:11:09 +02:00
if ( ( buttonType [ b ] = = BTN_TYPE_ANALOG | | buttonType [ b ] = = BTN_TYPE_ANALOG_INVERTED ) & & now - lastRead > ANALOG_BTN_READ_CYCLE ) { // button is not a button but a potentiometer
2022-01-09 15:13:33 +01:00
analog = true ;
2021-05-21 13:33:22 +02:00
handleAnalog ( b ) ; continue ;
}
2021-04-12 00:45:33 +02:00
2021-06-03 12:18:11 +02:00
//button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
if ( buttonType [ b ] = = BTN_TYPE_SWITCH | | buttonType [ b ] = = BTN_TYPE_PIR_SENSOR ) {
2021-05-20 19:54:07 +02:00
handleSwitch ( b ) ; continue ;
}
2019-10-18 12:19:52 +02:00
2021-05-20 19:54:07 +02:00
//momentary button logic
2021-10-31 11:57:03 +01:00
if ( isButtonPressed ( b ) ) { //pressed
2022-01-09 15:13:33 +01:00
if ( ! buttonPressedBefore [ b ] ) buttonPressedTime [ b ] = now ;
2021-05-20 19:54:07 +02:00
buttonPressedBefore [ b ] = true ;
2022-01-09 15:13:33 +01:00
if ( now - buttonPressedTime [ b ] > WLED_LONG_PRESS ) { //long press
2021-10-31 11:57:03 +01:00
if ( ! buttonLongPressed [ b ] ) longPressAction ( b ) ;
else if ( b ) { //repeatable action (~3 times per s) on button > 0
longPressAction ( b ) ;
2022-01-24 07:41:35 +01:00
buttonPressedTime [ b ] = now - WLED_LONG_REPEATED_ACTION ; //333ms
2021-05-20 19:54:07 +02:00
}
2021-10-31 11:57:03 +01:00
buttonLongPressed [ b ] = true ;
2019-10-18 12:19:52 +02:00
}
2021-10-31 11:57:03 +01:00
} else if ( ! isButtonPressed ( b ) & & buttonPressedBefore [ b ] ) { //released
2022-01-09 15:13:33 +01:00
long dur = now - buttonPressedTime [ b ] ;
2021-05-20 19:54:07 +02:00
if ( dur < WLED_DEBOUNCE_THRESHOLD ) { buttonPressedBefore [ b ] = false ; continue ; } //too short "press", debounce
2021-10-31 11:57:03 +01:00
bool doublePress = buttonWaitTime [ b ] ; //did we have a short press before?
2021-05-20 19:54:07 +02:00
buttonWaitTime [ b ] = 0 ;
2022-03-15 09:55:23 +01:00
if ( b = = 0 & & dur > WLED_LONG_AP ) { // long press on button 0 (when released)
if ( dur > WLED_LONG_FACTORY_RESET ) { // factory reset if pressed > 10 seconds
WLED_FS . format ( ) ;
clearEEPROM ( ) ;
doReboot = true ;
} else {
WLED : : instance ( ) . initAP ( true ) ;
}
2021-10-31 11:57:03 +01:00
} else if ( ! buttonLongPressed [ b ] ) { //short press
2022-02-20 22:24:11 +01:00
//NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling
if ( b ! = 1 & & ! macroDoublePress [ b ] ) { //don't wait for double press on buttons without a default action if no double press macro set
2022-01-21 21:24:49 +01:00
shortPressAction ( b ) ;
} else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0)
2021-06-03 12:18:11 +02:00
if ( doublePress ) {
2021-10-31 11:57:03 +01:00
doublePressAction ( b ) ;
} else {
2022-01-09 15:13:33 +01:00
buttonWaitTime [ b ] = now ;
2021-10-31 11:57:03 +01:00
}
2022-01-21 21:24:49 +01:00
}
2021-05-20 19:54:07 +02:00
}
buttonPressedBefore [ b ] = false ;
buttonLongPressed [ b ] = false ;
2016-11-19 19:39:17 +01:00
}
2019-03-13 11:13:03 +01:00
2021-10-31 11:57:03 +01:00
//if 350ms elapsed since last short press release it is a short press
2022-01-09 15:13:33 +01:00
if ( buttonWaitTime [ b ] & & now - buttonWaitTime [ b ] > WLED_DOUBLE_PRESS & & ! buttonPressedBefore [ b ] ) {
2021-05-20 19:54:07 +02:00
buttonWaitTime [ b ] = 0 ;
shortPressAction ( b ) ;
}
2019-03-13 11:13:03 +01:00
}
2022-01-09 15:13:33 +01:00
if ( analog ) lastRead = now ;
2019-03-13 11:13:03 +01:00
}
void handleIO ( )
{
handleButton ( ) ;
//set relay when LEDs turn on
if ( strip . getBrightness ( ) )
{
lastOnTime = millis ( ) ;
if ( offMode )
2020-10-12 20:13:13 +02:00
{
2021-01-17 00:20:31 +01:00
if ( rlyPin > = 0 ) {
pinMode ( rlyPin , OUTPUT ) ;
digitalWrite ( rlyPin , rlyMde ) ;
}
2019-03-13 11:13:03 +01:00
offMode = false ;
}
} else if ( millis ( ) - lastOnTime > 600 )
{
2021-01-17 00:20:31 +01:00
if ( ! offMode ) {
# ifdef ESP8266
2021-05-20 19:54:07 +02:00
// turn off built-in LED if strip is turned off
// this will break digital bus so will need to be reinitialised on On
2021-11-17 11:13:07 +01:00
PinOwner ledPinOwner = pinManager . getPinOwner ( LED_BUILTIN ) ;
2022-02-04 13:28:00 +01:00
if ( ! strip . isOffRefreshRequired ( ) & & ( ledPinOwner = = PinOwner : : None | | ledPinOwner = = PinOwner : : BusDigital ) ) {
2021-11-17 11:13:07 +01:00
pinMode ( LED_BUILTIN , OUTPUT ) ;
digitalWrite ( LED_BUILTIN , HIGH ) ;
}
2020-10-12 20:13:13 +02:00
# endif
2021-01-17 00:20:31 +01:00
if ( rlyPin > = 0 ) {
pinMode ( rlyPin , OUTPUT ) ;
digitalWrite ( rlyPin , ! rlyMde ) ;
}
}
2019-03-13 11:13:03 +01:00
offMode = true ;
2016-11-19 19:39:17 +01:00
}
2022-02-09 19:59:17 +01:00
}