2023-06-22 08:06:19 +00:00
# include "wled.h"
2023-09-10 16:52:14 +00:00
# ifndef WLED_DISABLE_ESPNOW
2023-06-22 08:06:19 +00:00
2024-12-19 16:41:44 +00:00
# define ESPNOW_BUSWAIT_TIMEOUT 24 // one frame timeout to wait for bus to finish updating
2024-12-13 06:40:04 +00:00
2023-06-22 08:06:19 +00:00
# define NIGHT_MODE_DEACTIVATED -1
# define NIGHT_MODE_BRIGHTNESS 5
# define WIZMOTE_BUTTON_ON 1
# define WIZMOTE_BUTTON_OFF 2
# define WIZMOTE_BUTTON_NIGHT 3
# define WIZMOTE_BUTTON_ONE 16
# define WIZMOTE_BUTTON_TWO 17
# define WIZMOTE_BUTTON_THREE 18
# define WIZMOTE_BUTTON_FOUR 19
# define WIZMOTE_BUTTON_BRIGHT_UP 9
# define WIZMOTE_BUTTON_BRIGHT_DOWN 8
2023-11-25 15:42:39 +00:00
# define WIZ_SMART_BUTTON_ON 100
# define WIZ_SMART_BUTTON_OFF 101
# define WIZ_SMART_BUTTON_BRIGHT_UP 102
# define WIZ_SMART_BUTTON_BRIGHT_DOWN 103
2023-06-22 08:06:19 +00:00
// This is kind of an esoteric strucure because it's pulled from the "Wizmote"
// product spec. That remote is used as the baseline for behavior and availability
// since it's broadly commercially available and works out of the box as a drop-in
2023-09-10 16:52:14 +00:00
typedef struct WizMoteMessageStructure {
2023-09-27 18:39:26 +00:00
uint8_t program ; // 0x91 for ON button, 0x81 for all others
uint8_t seq [ 4 ] ; // Incremetal sequence number 32 bit unsigned integer LSB first
2024-08-23 03:24:03 +00:00
uint8_t dt1 ; // Button Data Type (0x32)
2023-09-27 18:39:26 +00:00
uint8_t button ; // Identifies which button is being pressed
2024-08-23 03:24:03 +00:00
uint8_t dt2 ; // Battery Level Data Type (0x01)
uint8_t batLevel ; // Battery Level 0-100
2023-09-27 18:39:26 +00:00
uint8_t byte10 ; // Unknown, maybe checksum
uint8_t byte11 ; // Unknown, maybe checksum
uint8_t byte12 ; // Unknown, maybe checksum
uint8_t byte13 ; // Unknown, maybe checksum
2023-09-10 16:52:14 +00:00
} message_structure_t ;
2023-06-22 08:06:19 +00:00
2023-08-01 09:53:32 +00:00
static uint32_t last_seq = UINT32_MAX ;
2023-06-22 08:06:19 +00:00
static int brightnessBeforeNightMode = NIGHT_MODE_DEACTIVATED ;
2024-12-19 16:46:39 +00:00
static int16_t ESPNowButton = - 1 ; // set in callback if new button value is received
2023-06-22 08:06:19 +00:00
// Pulled from the IR Remote logic but reduced to 10 steps with a constant of 3
2023-07-30 19:42:05 +00:00
static const byte brightnessSteps [ ] = {
2023-06-22 08:06:19 +00:00
6 , 9 , 14 , 22 , 33 , 50 , 75 , 113 , 170 , 255
} ;
2023-09-27 18:39:26 +00:00
static const size_t numBrightnessSteps = sizeof ( brightnessSteps ) / sizeof ( byte ) ;
2023-06-22 08:06:19 +00:00
2023-09-27 18:39:26 +00:00
inline bool nightModeActive ( ) {
2023-06-22 08:06:19 +00:00
return brightnessBeforeNightMode ! = NIGHT_MODE_DEACTIVATED ;
}
2023-07-30 19:42:05 +00:00
static void activateNightMode ( ) {
2023-09-27 18:39:26 +00:00
if ( nightModeActive ( ) ) return ;
2023-06-22 08:06:19 +00:00
brightnessBeforeNightMode = bri ;
bri = NIGHT_MODE_BRIGHTNESS ;
2023-09-27 18:39:26 +00:00
stateUpdated ( CALL_MODE_BUTTON ) ;
2023-06-22 08:06:19 +00:00
}
2023-07-30 19:42:05 +00:00
static bool resetNightMode ( ) {
2023-09-27 18:39:26 +00:00
if ( ! nightModeActive ( ) ) return false ;
2023-06-22 08:06:19 +00:00
bri = brightnessBeforeNightMode ;
brightnessBeforeNightMode = NIGHT_MODE_DEACTIVATED ;
2023-09-27 18:39:26 +00:00
stateUpdated ( CALL_MODE_BUTTON ) ;
2023-06-22 08:06:19 +00:00
return true ;
}
// increment `bri` to the next `brightnessSteps` value
2023-07-30 19:42:05 +00:00
static void brightnessUp ( ) {
2023-09-27 18:39:26 +00:00
if ( nightModeActive ( ) ) return ;
2023-06-22 08:06:19 +00:00
// dumb incremental search is efficient enough for so few items
2024-07-11 19:22:58 +00:00
for ( unsigned index = 0 ; index < numBrightnessSteps ; + + index ) {
2023-06-22 08:06:19 +00:00
if ( brightnessSteps [ index ] > bri ) {
bri = brightnessSteps [ index ] ;
break ;
}
}
2023-09-27 18:39:26 +00:00
stateUpdated ( CALL_MODE_BUTTON ) ;
2023-06-22 08:06:19 +00:00
}
// decrement `bri` to the next `brightnessSteps` value
2023-07-30 19:42:05 +00:00
static void brightnessDown ( ) {
2023-09-27 18:39:26 +00:00
if ( nightModeActive ( ) ) return ;
2023-06-22 08:06:19 +00:00
// dumb incremental search is efficient enough for so few items
for ( int index = numBrightnessSteps - 1 ; index > = 0 ; - - index ) {
if ( brightnessSteps [ index ] < bri ) {
bri = brightnessSteps [ index ] ;
break ;
}
}
2023-09-27 18:39:26 +00:00
stateUpdated ( CALL_MODE_BUTTON ) ;
2023-06-22 08:06:19 +00:00
}
2023-07-30 19:42:05 +00:00
static void setOn ( ) {
2023-09-27 18:39:26 +00:00
resetNightMode ( ) ;
2023-06-22 08:06:19 +00:00
if ( ! bri ) {
2023-09-27 18:39:26 +00:00
toggleOnOff ( ) ;
stateUpdated ( CALL_MODE_BUTTON ) ;
2023-06-22 08:06:19 +00:00
}
}
2023-07-30 19:42:05 +00:00
static void setOff ( ) {
2023-09-27 18:39:26 +00:00
resetNightMode ( ) ;
2023-06-22 08:06:19 +00:00
if ( bri ) {
2023-09-27 18:39:26 +00:00
toggleOnOff ( ) ;
stateUpdated ( CALL_MODE_BUTTON ) ;
2023-06-22 08:06:19 +00:00
}
}
2023-11-12 11:41:14 +00:00
void presetWithFallback ( uint8_t presetID , uint8_t effectID , uint8_t paletteID ) {
2023-09-27 18:39:26 +00:00
resetNightMode ( ) ;
2023-06-22 08:06:19 +00:00
applyPresetWithFallback ( presetID , CALL_MODE_BUTTON_PRESET , effectID , paletteID ) ;
}
2023-09-27 18:39:26 +00:00
// this function follows the same principle as decodeIRJson()
static bool remoteJson ( int button )
{
char objKey [ 10 ] ;
bool parsed = false ;
if ( ! requestJSONBufferLock ( 22 ) ) return false ;
sprintf_P ( objKey , PSTR ( " \" %d \" : " ) , button ) ;
2024-12-13 06:40:04 +00:00
unsigned long start = millis ( ) ;
while ( strip . isUpdating ( ) & & millis ( ) - start < ESPNOW_BUSWAIT_TIMEOUT ) yield ( ) ; // wait for strip to finish updating, accessing FS during sendout causes glitches
2023-09-27 18:39:26 +00:00
// attempt to read command from remote.json
2024-03-01 13:36:07 +00:00
readObjectFromFile ( PSTR ( " /remote.json " ) , objKey , pDoc ) ;
2023-12-21 20:30:17 +00:00
JsonObject fdo = pDoc - > as < JsonObject > ( ) ;
2023-09-27 18:39:26 +00:00
if ( fdo . isNull ( ) ) {
// the received button does not exist
2024-03-07 19:21:56 +00:00
//if (!WLED_FS.exists(F("/remote.json"))) errorFlag = ERR_FS_RMLOAD; //warn if file itself doesn't exist
2023-09-27 18:39:26 +00:00
releaseJSONBufferLock ( ) ;
return parsed ;
}
String cmdStr = fdo [ " cmd " ] . as < String > ( ) ;
JsonObject jsonCmdObj = fdo [ " cmd " ] ; //object
if ( jsonCmdObj . isNull ( ) ) // we could also use: fdo["cmd"].is<String>()
{
if ( cmdStr . startsWith ( " ! " ) ) {
// call limited set of C functions
if ( cmdStr . startsWith ( F ( " !incBri " ) ) ) {
brightnessUp ( ) ;
parsed = true ;
} else if ( cmdStr . startsWith ( F ( " !decBri " ) ) ) {
brightnessDown ( ) ;
parsed = true ;
} else if ( cmdStr . startsWith ( F ( " !presetF " ) ) ) { //!presetFallback
uint8_t p1 = fdo [ " PL " ] | 1 ;
2024-12-20 18:12:29 +00:00
uint8_t p2 = fdo [ " FX " ] | hw_random8 ( strip . getModeCount ( ) - 1 ) ;
2023-09-27 18:39:26 +00:00
uint8_t p3 = fdo [ " FP " ] | 0 ;
presetWithFallback ( p1 , p2 , p3 ) ;
parsed = true ;
}
} else {
// HTTP API command
String apireq = " win " ; apireq + = ' & ' ; // reduce flash string usage
//if (cmdStr.indexOf("~") || fdo["rpt"]) lastValidCode = code; // repeatable action
if ( ! cmdStr . startsWith ( apireq ) ) cmdStr = apireq + cmdStr ; // if no "win&" prefix
if ( ! irApplyToAllSelected & & cmdStr . indexOf ( F ( " SS= " ) ) < 0 ) {
char tmp [ 10 ] ;
sprintf_P ( tmp , PSTR ( " &SS=%d " ) , strip . getMainSegmentId ( ) ) ;
cmdStr + = tmp ;
}
fdo . clear ( ) ; // clear JSON buffer (it is no longer needed)
handleSet ( nullptr , cmdStr , false ) ; // no stateUpdated() call here
stateUpdated ( CALL_MODE_BUTTON ) ;
parsed = true ;
}
} else {
// command is JSON object (TODO: currently will not handle irApplyToAllSelected correctly)
deserializeState ( jsonCmdObj , CALL_MODE_BUTTON ) ;
parsed = true ;
}
releaseJSONBufferLock ( ) ;
return parsed ;
}
2025-05-19 18:34:27 +00:00
// Callback function that will be executed when data is received from a linked remote
2024-12-19 16:41:44 +00:00
void handleWiZdata ( uint8_t * incomingData , size_t len ) {
2023-09-10 16:52:14 +00:00
message_structure_t * incoming = reinterpret_cast < message_structure_t * > ( incomingData ) ;
2023-06-22 08:06:19 +00:00
2023-09-10 16:52:14 +00:00
if ( len ! = sizeof ( message_structure_t ) ) {
2024-09-10 13:20:34 +00:00
DEBUG_PRINTF_P ( PSTR ( " Unknown incoming ESP Now message received of length %u \n " ) , len ) ;
2023-06-22 08:06:19 +00:00
return ;
}
2023-09-10 16:52:14 +00:00
uint32_t cur_seq = incoming - > seq [ 0 ] | ( incoming - > seq [ 1 ] < < 8 ) | ( incoming - > seq [ 2 ] < < 16 ) | ( incoming - > seq [ 3 ] < < 24 ) ;
2023-06-22 08:06:19 +00:00
if ( cur_seq = = last_seq ) {
return ;
}
2023-09-27 18:39:26 +00:00
DEBUG_PRINT ( F ( " Incoming ESP Now Packet [ " ) ) ;
2023-06-22 08:06:19 +00:00
DEBUG_PRINT ( cur_seq ) ;
2023-09-27 18:39:26 +00:00
DEBUG_PRINT ( F ( " ] from sender [ " ) ) ;
2023-06-22 08:06:19 +00:00
DEBUG_PRINT ( last_signal_src ) ;
DEBUG_PRINT ( F ( " ] button: " ) ) ;
2023-09-10 16:52:14 +00:00
DEBUG_PRINTLN ( incoming - > button ) ;
2023-09-27 18:39:26 +00:00
2024-12-13 06:40:04 +00:00
ESPNowButton = incoming - > button ; // save state, do not process in callback (can cause glitches)
last_seq = cur_seq ;
}
2024-12-19 16:41:44 +00:00
// process ESPNow button data (acesses FS, should not be called while update to avoid glitches)
void handleRemote ( ) {
2024-12-19 16:46:39 +00:00
if ( ESPNowButton > = 0 ) {
2024-12-13 06:40:04 +00:00
if ( ! remoteJson ( ESPNowButton ) )
switch ( ESPNowButton ) {
2023-09-27 18:39:26 +00:00
case WIZMOTE_BUTTON_ON : setOn ( ) ; break ;
case WIZMOTE_BUTTON_OFF : setOff ( ) ; break ;
case WIZMOTE_BUTTON_ONE : presetWithFallback ( 1 , FX_MODE_STATIC , 0 ) ; break ;
case WIZMOTE_BUTTON_TWO : presetWithFallback ( 2 , FX_MODE_BREATH , 0 ) ; break ;
case WIZMOTE_BUTTON_THREE : presetWithFallback ( 3 , FX_MODE_FIRE_FLICKER , 0 ) ; break ;
case WIZMOTE_BUTTON_FOUR : presetWithFallback ( 4 , FX_MODE_RAINBOW , 0 ) ; break ;
case WIZMOTE_BUTTON_NIGHT : activateNightMode ( ) ; break ;
case WIZMOTE_BUTTON_BRIGHT_UP : brightnessUp ( ) ; break ;
case WIZMOTE_BUTTON_BRIGHT_DOWN : brightnessDown ( ) ; break ;
2023-11-25 15:42:39 +00:00
case WIZ_SMART_BUTTON_ON : setOn ( ) ; break ;
case WIZ_SMART_BUTTON_OFF : setOff ( ) ; break ;
case WIZ_SMART_BUTTON_BRIGHT_UP : brightnessUp ( ) ; break ;
case WIZ_SMART_BUTTON_BRIGHT_DOWN : brightnessDown ( ) ; break ;
2023-09-27 18:39:26 +00:00
default : break ;
}
2024-12-13 06:40:04 +00:00
}
2024-12-19 16:46:39 +00:00
ESPNowButton = - 1 ;
2023-06-22 08:06:19 +00:00
}
2023-09-10 16:52:14 +00:00
# else
2024-12-19 16:41:44 +00:00
void handleRemote ( ) { }
2024-08-23 03:24:03 +00:00
# endif