2018-11-24 02:29:32 +00:00
/*
2019-08-17 13:50:12 +01:00
xdrv_19_ps16dz . dimmer . ino - PS_16_DZ dimmer and Sonoff L1 support for Sonoff - Tasmota
2018-11-24 02:29:32 +00:00
2019-07-02 11:26:04 +01:00
Copyright ( C ) 2019 Joel Stein and Theo Arends
2018-11-24 02:29:32 +00:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
2019-06-16 15:43:23 +01:00
# ifdef USE_LIGHT
2018-11-24 02:29:32 +00:00
# ifdef USE_PS_16_DZ
2019-07-02 11:26:04 +01:00
/*********************************************************************************************\
* PS 16 DZ Serial Dimmer and Sonoff L1
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2018-11-24 02:29:32 +00:00
# define XDRV_19 19
2019-04-10 13:54:50 +01:00
# define PS16DZ_BUFFER_SIZE 140
2018-11-24 02:29:32 +00:00
2019-04-10 13:54:50 +01:00
# define PS16DZ_SONOFF_L1_MODE_COLORFUL 1 // Colorful (static color)
# define PS16DZ_SONOFF_L1_MODE_COLORFUL_GRADIENT 2 // Colorful Gradient
# define PS16DZ_SONOFF_L1_MODE_COLORFUL_BREATH 3 // Colorful Breath
# define PS16DZ_SONOFF_L1_MODE_DIY_GRADIENT 4 // DIY Gradient (fade in and out) [Speed 1- 100, color]
# define PS16DZ_SONOFF_L1_MODE_DIY_PULSE 5 // DIY Pulse (faster fade in and out) [Speed 1- 100, color]
# define PS16DZ_SONOFF_L1_MODE_DIY_BREATH 6 // DIY Breath (toggle on/off) [Speed 1- 100, color]
# define PS16DZ_SONOFF_L1_MODE_DIY_STROBE 7 // DIY Strobe (faster toggle on/off) [Speed 1- 100, color]
# define PS16DZ_SONOFF_L1_MODE_RGB_GRADIENT 8 // RGB Gradient
# define PS16DZ_SONOFF_L1_MODE_RGB_PULSE 9 // RGB Pulse
# define PS16DZ_SONOFF_L1_MODE_RGB_BREATH 10 // RGB Breath
# define PS16DZ_SONOFF_L1_MODE_RGB_STROBE 11 // RGB strobe
# define PS16DZ_SONOFF_L1_MODE_SYNC_TO_MUSIC 12 // Sync to music [Speed 1- 100, sensitivity 1 - 10]
2018-11-24 02:29:32 +00:00
# include <TasmotaSerial.h>
TasmotaSerial * PS16DZSerial = nullptr ;
2019-08-17 13:48:42 +01:00
struct PS16DZ {
char * tx_buffer = nullptr ; // Serial transmit buffer
char * rx_buffer = nullptr ; // Serial receive buffer
int byte_counter = 0 ;
uint8_t color [ 3 ] ; // Most recent serial sent/received values
uint8_t dimmer = 0 ;
bool supports_color = false ;
bool switch_state = false ;
} Ps16dz ;
2018-11-24 02:29:32 +00:00
/*********************************************************************************************\
* Internal Functions
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-07-02 11:26:04 +01:00
void PS16DZSerialSendTxBuffer ( void )
2019-04-10 13:54:50 +01:00
{
2019-08-17 13:48:42 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " PSZ: Send %s " ) , Ps16dz . tx_buffer ) ;
2019-04-10 13:54:50 +01:00
2019-08-17 13:48:42 +01:00
PS16DZSerial - > print ( Ps16dz . tx_buffer ) ;
2019-07-02 11:26:04 +01:00
PS16DZSerial - > write ( 0x1B ) ;
PS16DZSerial - > flush ( ) ;
2019-04-10 13:54:50 +01:00
}
2019-07-02 11:26:04 +01:00
void PS16DZSerialSendOkCommand ( void )
2018-11-24 02:29:32 +00:00
{
2019-08-17 13:48:42 +01:00
snprintf_P ( Ps16dz . tx_buffer , PS16DZ_BUFFER_SIZE , PSTR ( " AT+SEND=ok " ) ) ;
2019-07-02 11:26:04 +01:00
PS16DZSerialSendTxBuffer ( ) ;
2018-11-24 02:29:32 +00:00
}
2019-06-30 21:04:24 +01:00
// Send a serial update command to the LED controller
// For dimmer types:
2019-07-02 14:49:08 +01:00
// AT+UPDATE="sequence":"1554682835320","switch":"on","bright":100
2019-06-30 21:04:24 +01:00
// For color types:
2019-07-02 14:49:08 +01:00
// AT+UPDATE="sequence":"1554682835320","switch":"on","bright":100,"mode":1,"colorR":255,"colorG":46,"colorB":101,"light_types":1
2019-07-02 11:26:04 +01:00
void PS16DZSerialSendUpdateCommand ( void )
2019-04-10 13:54:50 +01:00
{
2019-07-02 11:26:04 +01:00
uint8_t light_state_dimmer = light_state . getDimmer ( ) ;
2019-06-29 03:25:39 +01:00
// Dimming acts odd below 10% - this mirrors the threshold set on the faceplate itself
2019-10-10 15:53:01 +01:00
light_state_dimmer = ( light_state_dimmer < Settings . dimmer_hw_min ) ? Settings . dimmer_hw_min : light_state_dimmer ;
light_state_dimmer = ( light_state_dimmer > Settings . dimmer_hw_max ) ? Settings . dimmer_hw_max : light_state_dimmer ;
2019-04-10 13:54:50 +01:00
2019-08-17 13:48:42 +01:00
snprintf_P ( Ps16dz . tx_buffer , PS16DZ_BUFFER_SIZE , PSTR ( " AT+UPDATE= \" sequence \" : \" %d%03d \" , \" switch \" : \" %s \" , \" bright \" :%d " ) ,
2019-07-02 11:26:04 +01:00
LocalTime ( ) , millis ( ) % 1000 , power ? " on " : " off " , light_state_dimmer ) ;
2019-06-29 03:25:39 +01:00
2019-08-17 13:48:42 +01:00
if ( Ps16dz . supports_color ) {
2019-06-29 03:25:39 +01:00
uint8_t light_state_rgb [ 3 ] ;
light_state . getRGB ( & light_state_rgb [ 0 ] , & light_state_rgb [ 1 ] , & light_state_rgb [ 2 ] ) ;
2019-08-17 13:48:42 +01:00
snprintf_P ( Ps16dz . tx_buffer , PS16DZ_BUFFER_SIZE , PSTR ( " %s, \" mode \" :%d, \" colorR \" :%d, \" colorG \" :%d, \" colorB \" :%d, \" light_types \" :1 " ) ,
Ps16dz . tx_buffer , PS16DZ_SONOFF_L1_MODE_COLORFUL , light_state_rgb [ 0 ] , light_state_rgb [ 1 ] , light_state_rgb [ 2 ] ) ;
2019-06-29 03:25:39 +01:00
}
2019-06-30 21:04:24 +01:00
PS16DZSerialSendTxBuffer ( ) ;
}
2019-04-10 13:54:50 +01:00
2019-07-02 11:26:04 +01:00
/*********************************************************************************************\
* API Functions
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-04-10 13:54:50 +01:00
2019-07-02 11:26:04 +01:00
bool PS16DZSerialSendUpdateCommandIfRequired ( void )
2019-06-30 21:04:24 +01:00
{
2019-07-02 11:26:04 +01:00
if ( ! PS16DZSerial ) { return true ; }
2019-04-10 13:54:50 +01:00
2019-07-02 11:26:04 +01:00
bool is_switch_change = ( XdrvMailbox . payload ! = SRC_SWITCH ) ;
2019-08-17 13:48:42 +01:00
bool is_brightness_change = ( light_state . getDimmer ( ) ! = Ps16dz . dimmer ) ;
2019-07-02 11:26:04 +01:00
uint8_t light_state_rgb [ 3 ] ;
light_state . getRGB ( & light_state_rgb [ 0 ] , & light_state_rgb [ 1 ] , & light_state_rgb [ 2 ] ) ;
2019-08-17 13:48:42 +01:00
bool is_color_change = ( Ps16dz . supports_color & & ( memcmp ( light_state_rgb , Ps16dz . color , 3 ) ! = 0 ) ) ;
2019-07-02 11:26:04 +01:00
if ( is_switch_change | | is_brightness_change | | is_color_change ) {
PS16DZSerialSendUpdateCommand ( ) ;
2018-11-24 02:29:32 +00:00
}
2019-07-02 11:26:04 +01:00
return true ;
}
2018-11-24 02:29:32 +00:00
2019-01-28 13:08:33 +00:00
bool PS16DZModuleSelected ( void )
2018-11-24 02:29:32 +00:00
{
2019-04-10 13:54:50 +01:00
switch ( my_module_type )
{
case PS_16_DZ :
2019-10-09 15:55:15 +01:00
devices_present + + ;
2019-04-10 13:54:50 +01:00
light_type = LT_SERIAL1 ;
break ;
case SONOFF_L1 :
2019-10-09 15:55:15 +01:00
devices_present + + ;
2019-04-10 13:54:50 +01:00
light_type = LT_PWM3 ;
break ;
}
2018-11-24 02:29:32 +00:00
return true ;
}
void PS16DZInit ( void )
{
2019-08-17 13:48:42 +01:00
Ps16dz . supports_color = ( light_state . getColorMode ( ) = = LCM_RGB ) ;
2019-06-30 21:04:24 +01:00
2019-08-17 13:48:42 +01:00
Ps16dz . tx_buffer = ( char * ) ( malloc ( PS16DZ_BUFFER_SIZE ) ) ;
if ( Ps16dz . tx_buffer ! = nullptr ) {
Ps16dz . rx_buffer = ( char * ) ( malloc ( PS16DZ_BUFFER_SIZE ) ) ;
if ( Ps16dz . rx_buffer ! = nullptr ) {
2018-11-27 10:55:54 +00:00
PS16DZSerial = new TasmotaSerial ( pin [ GPIO_RXD ] , pin [ GPIO_TXD ] , 2 ) ;
if ( PS16DZSerial - > begin ( 19200 ) ) {
if ( PS16DZSerial - > hardwareSerial ( ) ) { ClaimSerial ( ) ; }
}
}
2018-11-24 02:29:32 +00:00
}
}
void PS16DZSerialInput ( void )
{
char scmnd [ 20 ] ;
while ( PS16DZSerial - > available ( ) ) {
yield ( ) ;
2019-01-28 13:08:33 +00:00
uint8_t serial_in_byte = PS16DZSerial - > read ( ) ;
2019-07-02 11:26:04 +01:00
if ( serial_in_byte ! = 0x1B ) {
2019-08-17 13:48:42 +01:00
if ( Ps16dz . byte_counter > = PS16DZ_BUFFER_SIZE - 1 ) {
memset ( Ps16dz . rx_buffer , 0 , PS16DZ_BUFFER_SIZE ) ;
Ps16dz . byte_counter = 0 ;
2018-11-27 15:52:09 +00:00
}
2019-08-17 13:48:42 +01:00
if ( Ps16dz . byte_counter | | ( ! Ps16dz . byte_counter & & ( ' A ' = = serial_in_byte ) ) ) {
Ps16dz . rx_buffer [ Ps16dz . byte_counter + + ] = serial_in_byte ;
2018-12-23 20:06:10 +00:00
}
2019-07-02 11:26:04 +01:00
} else {
2019-08-17 13:48:42 +01:00
Ps16dz . rx_buffer [ Ps16dz . byte_counter + + ] = 0x00 ;
2019-07-02 11:26:04 +01:00
2019-07-22 17:21:25 +01:00
// AT+RESULT="sequence":"1554682835320"
2019-08-17 13:48:42 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " PSZ: Received %s " ) , Ps16dz . rx_buffer ) ;
2019-07-02 11:26:04 +01:00
2019-08-17 13:48:42 +01:00
if ( ! strncmp ( Ps16dz . rx_buffer + 3 , " UPDATE " , 6 ) ) {
2019-07-23 13:40:33 +01:00
// AT+UPDATE="switch":"on","light_type":1,"colorR":255,"colorG":255,"colorB":255,"bright":100,"mode":19,"speed":100,"sensitive":100
2018-11-25 21:43:28 +00:00
char * end_str ;
2019-08-17 13:48:42 +01:00
char * string = Ps16dz . rx_buffer + 10 ;
2019-07-02 11:26:04 +01:00
char * token = strtok_r ( string , " , " , & end_str ) ;
2019-04-10 13:54:50 +01:00
2019-06-30 21:04:24 +01:00
bool color_updated [ 3 ] = { false , false , false } ;
2019-08-17 13:48:42 +01:00
memcpy ( Ps16dz . color , Settings . light_color , 3 ) ;
2019-06-30 19:32:34 +01:00
bool is_switch_change = false ;
2019-04-10 13:54:50 +01:00
bool is_color_change = false ;
2019-06-29 03:25:39 +01:00
bool is_brightness_change = false ;
2019-03-26 17:26:50 +00:00
while ( token ! = nullptr ) {
2018-11-25 21:43:28 +00:00
char * end_token ;
char * token2 = strtok_r ( token , " : " , & end_token ) ;
2019-03-26 17:26:50 +00:00
char * token3 = strtok_r ( nullptr , " : " , & end_token ) ;
2019-04-10 13:54:50 +01:00
2019-07-02 11:26:04 +01:00
if ( ! strncmp ( token2 , " \" switch \" " , 8 ) ) {
2019-08-17 13:48:42 +01:00
Ps16dz . switch_state = ! strncmp ( token3 , " \" on \" " , 4 ) ? true : false ;
2019-06-30 19:32:34 +01:00
2019-08-17 13:48:42 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " PSZ: Switch %d " ) , Ps16dz . switch_state ) ;
2019-06-30 19:32:34 +01:00
2019-08-17 13:48:42 +01:00
is_switch_change = ( Ps16dz . switch_state ! = power ) ;
2019-07-02 11:26:04 +01:00
if ( is_switch_change ) {
2019-08-17 13:48:42 +01:00
ExecuteCommandPower ( 1 , Ps16dz . switch_state , SRC_SWITCH ) ; // send SRC_SWITCH? to use as flag to prevent loop from inbound states from faceplate interaction
2019-06-30 19:32:34 +01:00
}
2019-04-10 13:54:50 +01:00
}
2019-07-02 11:26:04 +01:00
else if ( ! strncmp ( token2 , " \" color " , 6 ) ) {
2019-04-10 13:54:50 +01:00
2019-07-01 22:50:59 +01:00
char color_channel_name = token2 [ 6 ] ;
2019-06-30 21:04:24 +01:00
int color_index ;
2019-04-10 13:54:50 +01:00
switch ( color_channel_name )
{
2019-06-30 21:04:24 +01:00
case ' R ' : color_index = 0 ;
2019-04-10 13:54:50 +01:00
break ;
2019-06-30 21:04:24 +01:00
case ' G ' : color_index = 1 ;
2019-04-10 13:54:50 +01:00
break ;
2019-06-30 21:04:24 +01:00
case ' B ' : color_index = 2 ;
2019-04-10 13:54:50 +01:00
break ;
}
2019-06-30 21:04:24 +01:00
int color_value = atoi ( token3 ) ;
2019-08-17 13:48:42 +01:00
Ps16dz . color [ color_index ] = color_value ;
2019-06-30 21:04:24 +01:00
color_updated [ color_index ] = true ;
2019-04-10 13:54:50 +01:00
2019-06-30 21:04:24 +01:00
bool all_color_channels_updated = color_updated [ 0 ] & & color_updated [ 1 ] & & color_updated [ 2 ] ;
2019-07-02 11:26:04 +01:00
if ( all_color_channels_updated ) {
2019-07-02 14:49:08 +01:00
2019-08-17 13:48:42 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " PSZ: Color R:%d, G:%d, B:%d " ) , Ps16dz . color [ 0 ] , Ps16dz . color [ 1 ] , Ps16dz . color [ 2 ] ) ;
2019-04-10 13:54:50 +01:00
2019-08-17 13:48:42 +01:00
is_color_change = ( memcmp ( Ps16dz . color , Settings . light_color , 3 ) ! = 0 ) ;
2019-06-30 21:04:24 +01:00
}
2019-04-10 13:54:50 +01:00
2019-07-02 11:26:04 +01:00
if ( power & & is_color_change ) {
2019-08-17 13:48:42 +01:00
snprintf_P ( scmnd , sizeof ( scmnd ) , PSTR ( D_CMND_COLOR " 2 %02x%02x%02x " ) , Ps16dz . color [ 0 ] , Ps16dz . color [ 1 ] , Ps16dz . color [ 2 ] ) ;
2019-04-10 13:54:50 +01:00
ExecuteCommand ( scmnd , SRC_SWITCH ) ;
2018-11-25 21:43:28 +00:00
}
2018-11-24 02:29:32 +00:00
}
2019-07-02 11:26:04 +01:00
else if ( ! strncmp ( token2 , " \" bright \" " , 8 ) ) {
2019-08-17 13:48:42 +01:00
Ps16dz . dimmer = atoi ( token3 ) ;
2019-07-02 11:26:04 +01:00
2019-08-17 13:48:42 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " PSZ: Brightness %d " ) , Ps16dz . dimmer ) ;
2018-11-24 02:29:32 +00:00
2019-08-17 13:48:42 +01:00
is_brightness_change = Ps16dz . dimmer ! = Settings . light_dimmer ;
if ( power & & ( Ps16dz . dimmer > 0 ) & & is_brightness_change ) {
snprintf_P ( scmnd , sizeof ( scmnd ) , PSTR ( D_CMND_DIMMER " %d " ) , Ps16dz . dimmer ) ;
2019-06-29 03:25:39 +01:00
ExecuteCommand ( scmnd , SRC_SWITCH ) ;
}
2018-11-24 02:29:32 +00:00
}
2019-07-02 11:26:04 +01:00
else if ( ! strncmp ( token2 , " \" sequence \" " , 10 ) ) {
2019-07-02 14:49:08 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " PSZ: Sequence %s " ) , token3 ) ;
2018-11-27 10:55:54 +00:00
}
2019-03-26 17:26:50 +00:00
token = strtok_r ( nullptr , " , " , & end_str ) ;
2018-11-24 02:29:32 +00:00
}
2019-04-10 13:54:50 +01:00
2019-07-02 11:26:04 +01:00
if ( ! is_color_change & & ! is_brightness_change ) {
2019-07-02 14:49:08 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " PSZ: Update " ) ) ;
2019-07-02 11:26:04 +01:00
2019-06-30 21:04:24 +01:00
PS16DZSerialSendOkCommand ( ) ;
2019-04-10 13:54:50 +01:00
}
2018-11-24 02:29:32 +00:00
}
2019-08-17 13:48:42 +01:00
else if ( ! strncmp ( Ps16dz . rx_buffer + 3 , " SETTING " , 7 ) ) {
2019-07-23 13:40:33 +01:00
// AT+SETTING=enterESPTOUCH - When ON button is held for over 5 seconds
// AT+SETTING=exitESPTOUCH - When ON button is pressed
2019-07-02 11:26:04 +01:00
if ( ! Settings . flag . button_restrict ) {
2019-07-23 13:40:33 +01:00
int state = WIFI_MANAGER ;
2019-08-17 13:48:42 +01:00
if ( ! strncmp ( Ps16dz . rx_buffer + 10 , " =exit " , 5 ) ) { state = WIFI_RETRY ; }
2019-07-23 13:40:33 +01:00
if ( state ! = Settings . sta_config ) {
snprintf_P ( scmnd , sizeof ( scmnd ) , PSTR ( D_CMND_WIFICONFIG " %d " ) , state ) ;
ExecuteCommand ( scmnd , SRC_BUTTON ) ;
}
2019-07-02 11:26:04 +01:00
}
2018-11-25 21:43:28 +00:00
}
2019-08-17 13:48:42 +01:00
memset ( Ps16dz . rx_buffer , 0 , PS16DZ_BUFFER_SIZE ) ;
Ps16dz . byte_counter = 0 ;
2018-11-24 02:29:32 +00:00
}
}
}
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-01-28 13:08:33 +00:00
bool Xdrv19 ( uint8_t function )
2018-11-24 02:29:32 +00:00
{
2019-01-28 13:08:33 +00:00
bool result = false ;
2018-11-24 02:29:32 +00:00
2019-07-02 11:26:04 +01:00
if ( ( PS_16_DZ = = my_module_type ) | | ( SONOFF_L1 = = my_module_type ) ) {
2018-11-24 02:29:32 +00:00
switch ( function ) {
2019-03-30 12:03:45 +00:00
case FUNC_LOOP :
if ( PS16DZSerial ) { PS16DZSerialInput ( ) ; }
break ;
2018-11-24 02:29:32 +00:00
case FUNC_MODULE_INIT :
result = PS16DZModuleSelected ( ) ;
break ;
case FUNC_INIT :
PS16DZInit ( ) ;
break ;
case FUNC_SET_DEVICE_POWER :
2018-12-17 16:34:55 +00:00
case FUNC_SET_CHANNELS :
2019-07-02 11:26:04 +01:00
result = PS16DZSerialSendUpdateCommandIfRequired ( ) ;
2018-12-17 16:34:55 +00:00
break ;
2018-11-24 02:29:32 +00:00
}
}
return result ;
}
# endif // USE_PS_16_DZ
2019-06-16 15:43:23 +01:00
# endif // USE_LIGHT