2017-02-19 16:49:17 +00:00
/*
2019-10-27 10:13:24 +00:00
xdrv_04_light . ino - PWM , WS2812 and sonoff led support for Tasmota
2017-02-19 16:49:17 +00:00
2021-01-01 12:44:04 +00:00
Copyright ( C ) 2021 Theo Arends
2017-02-19 16:49:17 +00:00
2017-05-13 12:02:10 +01: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 .
2017-02-19 16:49:17 +00:00
2017-05-13 12:02:10 +01:00
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/>.
2017-02-19 16:49:17 +00:00
*/
2019-06-16 15:43:23 +01:00
# ifdef USE_LIGHT
2017-02-19 16:49:17 +00:00
/*********************************************************************************************\
2017-10-27 11:12:07 +01:00
* PWM , WS2812 , Sonoff B1 , AiLight , Sonoff Led and BN - SZ01 , H801 , MagicHome and Arilux
2017-09-02 13:37:02 +01:00
*
2017-10-27 11:12:07 +01:00
* light_type Module Color ColorTemp Modules
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2019-05-02 21:50:19 +01:00
* 0 - no ( Sonoff Basic )
2017-10-27 11:12:07 +01:00
* 1 PWM1 W no ( Sonoff BN - SZ )
* 2 PWM2 CW yes ( Sonoff Led )
2017-11-15 22:07:45 +00:00
* 3 PWM3 RGB no ( H801 , MagicHome and Arilux LC01 )
2017-10-27 11:12:07 +01:00
* 4 PWM4 RGBW no ( H801 , MagicHome and Arilux )
2017-11-15 22:07:45 +00:00
* 5 PWM5 RGBCW yes ( H801 , Arilux LC11 )
2024-10-10 16:49:11 +01:00
* 6 PWM6
* 7 PWM7
* 8 reserved
* 9 SERIAL1 no
* 10 SERIAL2 yes
2019-10-08 16:46:03 +01:00
* 11 + WS2812 RGB no ( One WS2812 RGB or RGBW ledstrip )
2017-10-27 11:12:07 +01:00
* 12 AiLight RGBW no
* 13 Sonoff B1 RGBCW yes
2024-10-10 16:49:11 +01:00
* 14 reserved
* 15 reserved
2017-09-16 16:34:03 +01:00
*
2017-10-27 11:12:07 +01:00
* light_scheme WS2812 3 + Colors 1 + 2 Colors Effect
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* 0 yes yes yes Color On / Off
* 1 yes yes yes Wakeup light
* 2 yes yes no Color cycle RGB
* 3 yes yes no Color cycle RBG
* 4 yes yes no Random RGB colors
* 5 yes no no Clock
* 6 yes no no Incandescent
* 7 yes no no RGB
* 8 yes no no Christmas
* 9 yes no no Hanukkah
* 10 yes no no Kwanzaa
* 11 yes no no Rainbow
* 12 yes no no Fire
2023-05-26 16:47:57 +01:00
* 13 yes no no Stairs
* 14 yes no no Clear ( = Berry )
2017-02-19 16:49:17 +00:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-04-25 12:06:35 +01:00
/*********************************************************************************************\
*
* Light management has been refactored to provide a cleaner class - based interface .
* Also , now all values are stored as integer , no more floats that could generate
* rounding errors .
*
* Two singletons are now used to control the state of the light .
* - light_state ( LightStateClass ) stores the color / white temperature and
* brightness . Use this object to READ only .
* - light_controller ( LightControllerClass ) is used to change light state
* and adjust all Settings and levels accordingly .
* Always use this object to change light status .
2019-05-04 08:40:26 +01:00
*
* As there have been lots of changes in light control , here is a summary out
* the whole flow from setting colors to drving the PMW pins .
*
* 1. To change colors , always use ' light_controller ' object .
* ' light_state ' is only to be used to read current state .
* . a For color bulbs , set color via changeRGB ( ) or changeHS ( ) for Hue / Sat .
* Set the overall brightness changeBri ( 0. .255 ) or changeDimmer ( 0. .100 % )
* RGB and Hue / Sat are always kept in sync . Internally , RGB are stored at
* full range ( max brightness ) so that when you reduce brightness and
* raise it back again , colors don ' t change due to rounding errors .
* . b For white bulbs with Cold / Warm colortone , use changeCW ( ) or changeCT ( )
* to change color - tone . Set overall brightness separately .
* Color - tone temperature can range from 153 ( Cold ) to 500 ( Warm ) .
2020-01-04 10:01:44 +00:00
* SetOption82 can expand the rendering from 200 - 380 due to Alexa reduced range .
2019-05-04 08:40:26 +01:00
* CW channels are stored at full brightness to avoid rounding errors .
* . c Alternatively , you can set all 5 channels at once with changeChannels ( ) ,
* in this case it will also set the corresponding brightness .
*
* 2. a After any change , the Settings object is updated so that changes
* survive a reboot and can be stored in flash - in saveSettings ( )
* . b Actual channel values are computed from RGB or CT combined with brightness .
* Range is still 0. .255 ( 8 bits ) - in getActualRGBCW ( )
2019-10-12 12:27:09 +01:00
* . c The 5 internal channels RGBWC are mapped to the actual channels supported
2019-05-04 08:40:26 +01:00
* by the light_type : in calcLevels ( )
* 1 channel - 0 : Brightness
2019-05-08 11:06:22 +01:00
* 2 channels - 0 : Coldwhite 1 : Warmwhite
2019-05-04 08:40:26 +01:00
* 3 channels - 0 : Red 1 : Green 2 : Blue
2020-11-24 22:56:24 +00:00
* 4 channels - 0 : Red 1 : Green 2 : Blue 3 : White
* 5 channels - 0 : Red 1 : Green 2 : Blue 3 : ColdWhite 4 : Warmwhite
2019-05-04 08:40:26 +01:00
*
* 3. In LightAnimate ( ) , final PWM values are computed at next tick .
* . a If color did not change since last tick - ignore .
2019-07-07 09:15:50 +01:00
* . b Extend resolution from 8 bits to 10 bits , which makes a significant
2019-05-04 08:40:26 +01:00
* difference when applying gamma correction at low brightness .
2019-07-07 09:15:50 +01:00
* . c Apply Gamma Correction if LedTable = = 1 ( by default ) .
2019-05-04 08:40:26 +01:00
* Gamma Correction uses an adaptative resolution table from 11 to 8 bits .
2019-07-07 09:15:50 +01:00
* . d For Warm / Cold - white channels , Gamma correction is calculated in combined mode .
2019-05-04 08:40:26 +01:00
* Ie . total white brightness ( C + W ) is used for Gamma correction and gives
* the overall light power required . Then this light power is split among
* Wamr / Cold channels .
2019-07-07 09:15:50 +01:00
* . e Gamma correction is still applied to 8 bits channels for compatibility
2019-05-04 08:40:26 +01:00
* with other non - PMW modules .
2019-07-07 09:15:50 +01:00
* . f Apply color balance correction from rgbwwTable [ ] .
* Note : correction is done after Gamma correction , it is meant
* to adjust leds with different power
* . g If rgbwwTable [ 4 ] is zero , blend RGB with White and adjust the level of
* White channel according to rgbwwTable [ 3 ]
2019-12-27 20:02:23 +00:00
* . h Scale ranges from 10 bits to 0. . PWMRange ( by default 1023 ) so no change
2019-05-04 08:40:26 +01:00
* by default .
2019-12-27 20:02:23 +00:00
* . i Apply port remapping from Option37
* . j Invert PWM value if port is of type PMWxi instead of PMWx
* . k Apply PWM value with analogWrite ( ) - if pin is configured
2019-05-04 08:40:26 +01:00
*
2019-04-25 12:06:35 +01:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2018-11-07 09:30:03 +00:00
# define XDRV_04 4
2019-08-06 09:57:50 +01:00
// #define DEBUG_LIGHT
2018-11-07 09:30:03 +00:00
2021-12-12 17:23:49 +00:00
# ifdef USE_NETWORK_LIGHT_SCHEMES
enum LightSchemes { LS_POWER , LS_WAKEUP , LS_CYCLEUP , LS_CYCLEDN , LS_RANDOM , LS_DDP , LS_MAX } ;
# else
2019-10-08 16:46:03 +01:00
enum LightSchemes { LS_POWER , LS_WAKEUP , LS_CYCLEUP , LS_CYCLEDN , LS_RANDOM , LS_MAX } ;
2021-12-12 17:23:49 +00:00
# endif
2019-10-08 16:46:03 +01:00
2019-08-01 14:47:00 +01:00
const uint8_t LIGHT_COLOR_SIZE = 25 ; // Char array scolor size
2017-12-29 13:55:39 +00:00
2019-08-11 17:12:18 +01:00
const char kLightCommands [ ] PROGMEM = " | " // No prefix
Add light synonyms
Add commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively
2021-01-26 13:56:58 +00:00
// SetOptions synonyms
D_SO_CHANNELREMAP " | " D_SO_MULTIPWM " | " D_SO_ALEXACTRANGE " | " D_SO_POWERONFADE " | " D_SO_PWMCT " | "
2022-11-13 17:22:39 +00:00
D_SO_WHITEBLEND " | " D_SO_ARTNET_AUTORUN " | "
Add light synonyms
Add commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively
2021-01-26 13:56:58 +00:00
// Other commands
2020-10-30 13:49:36 +00:00
D_CMND_COLOR " | " D_CMND_COLORTEMPERATURE " | " D_CMND_DIMMER " | " D_CMND_DIMMER_RANGE " | " D_CMND_DIMMER_STEP " | " D_CMND_LEDTABLE " | " D_CMND_FADE " | "
2019-08-01 14:47:00 +01:00
D_CMND_RGBWWTABLE " | " D_CMND_SCHEME " | " D_CMND_SPEED " | " D_CMND_WAKEUP " | " D_CMND_WAKEUPDURATION " | "
2020-04-13 05:17:25 +01:00
D_CMND_WHITE " | " D_CMND_CHANNEL " | " D_CMND_HSBCOLOR
2020-12-29 18:31:27 +00:00
" | " D_CMND_CTRANGE
# ifdef USE_LIGHT_VIRTUAL_CT
2021-01-01 12:44:04 +00:00
" | " D_CMND_VIRTUALCT
2020-12-29 18:31:27 +00:00
# endif // USE_LIGHT_VIRTUAL_CT
2020-04-13 05:17:25 +01:00
# ifdef USE_LIGHT_PALETTE
" | " D_CMND_PALETTE
# endif // USE_LIGHT_PALETTE
2020-04-25 23:49:34 +01:00
# ifdef USE_DGR_LIGHT_SEQUENCE
" | " D_CMND_SEQUENCE_OFFSET
# endif // USE_DGR_LIGHT_SEQUENCE
2022-11-13 17:22:39 +00:00
# ifdef USE_LIGHT_ARTNET
2022-11-16 15:26:12 +00:00
" | " D_CMND_ARTNET " | " D_CMND_ARTNET_CONFIG
2022-11-13 17:22:39 +00:00
# endif
2020-04-13 05:17:25 +01:00
" |UNDOCA " ;
2019-08-01 14:47:00 +01:00
2021-01-28 08:04:42 +00:00
SO_SYNONYMS ( kLightSynonyms ,
Add light synonyms
Add commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively
2021-01-26 13:56:58 +00:00
37 , 68 , 82 , 91 , 92 ,
2022-11-13 17:22:39 +00:00
105 , 148 ,
2021-01-28 08:04:42 +00:00
) ;
Add light synonyms
Add commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively
2021-01-26 13:56:58 +00:00
2019-08-01 14:47:00 +01:00
void ( * const LightCommand [ ] ) ( void ) PROGMEM = {
2020-10-30 13:49:36 +00:00
& CmndColor , & CmndColorTemperature , & CmndDimmer , & CmndDimmerRange , & CmndDimmerStep , & CmndLedTable , & CmndFade ,
2019-08-01 14:47:00 +01:00
& CmndRgbwwTable , & CmndScheme , & CmndSpeed , & CmndWakeup , & CmndWakeupDuration ,
2020-04-13 05:17:25 +01:00
& CmndWhite , & CmndChannel , & CmndHsbColor ,
2020-12-29 18:31:27 +00:00
& CmndCTRange ,
# ifdef USE_LIGHT_VIRTUAL_CT
& CmndVirtualCT ,
# endif // USE_LIGHT_VIRTUAL_CT
2020-04-13 05:17:25 +01:00
# ifdef USE_LIGHT_PALETTE
& CmndPalette ,
# endif // USE_LIGHT_PALETTE
2020-04-25 23:49:34 +01:00
# ifdef USE_DGR_LIGHT_SEQUENCE
& CmndSequenceOffset ,
# endif // USE_DGR_LIGHT_SEQUENCE
2022-11-13 17:22:39 +00:00
# ifdef USE_LIGHT_ARTNET
2022-11-16 15:26:12 +00:00
& CmndArtNet , & CmndArtNetConfig ,
2022-11-13 17:22:39 +00:00
# endif
2020-04-13 05:17:25 +01:00
& CmndUndocA } ;
2017-10-29 17:18:46 +00:00
2019-05-04 22:04:53 +01:00
// Light color mode, either RGB alone, or white-CT alone, or both only available if ct_rgb_linked is false
enum LightColorModes {
2021-05-10 19:36:22 +01:00
LCM_RGB = 1 , LCM_CT = 2 , LCM_BOTH = 3
} ;
2019-05-04 22:04:53 +01:00
2017-11-15 22:07:45 +00:00
struct LRgbColor {
uint8_t R , G , B ;
} ;
2019-03-31 10:59:04 +01:00
const uint8_t MAX_FIXED_COLOR = 12 ;
2017-11-15 22:07:45 +00:00
const LRgbColor kFixedColor [ MAX_FIXED_COLOR ] PROGMEM =
{ 255 , 0 , 0 , 0 , 255 , 0 , 0 , 0 , 255 , 228 , 32 , 0 , 0 , 228 , 32 , 0 , 32 , 228 , 188 , 64 , 0 , 0 , 160 , 96 , 160 , 32 , 240 , 255 , 255 , 0 , 255 , 0 , 170 , 255 , 255 , 255 } ;
2018-09-04 21:43:27 +01:00
struct LWColor {
uint8_t W ;
} ;
2019-03-31 10:59:04 +01:00
const uint8_t MAX_FIXED_WHITE = 4 ;
2018-09-04 22:03:19 +01:00
const LWColor kFixedWhite [ MAX_FIXED_WHITE ] PROGMEM = { 0 , 255 , 128 , 32 } ;
2018-09-04 21:43:27 +01:00
2017-11-15 22:07:45 +00:00
struct LCwColor {
uint8_t C , W ;
} ;
2019-03-31 10:59:04 +01:00
const uint8_t MAX_FIXED_COLD_WARM = 4 ;
2017-11-15 22:07:45 +00:00
const LCwColor kFixedColdWarm [ MAX_FIXED_COLD_WARM ] PROGMEM = { 0 , 0 , 255 , 0 , 0 , 255 , 128 , 128 } ;
2020-01-04 10:01:44 +00:00
// CT min and max
const uint16_t CT_MIN = 153 ; // 6500K
const uint16_t CT_MAX = 500 ; // 2000K
// Ranges used for Alexa
const uint16_t CT_MIN_ALEXA = 200 ; // also 5000K
const uint16_t CT_MAX_ALEXA = 380 ; // also 2600K
2020-12-29 18:31:27 +00:00
// Virtual CT default values
typedef uint8_t vct_pivot_t [ LST_MAX ] ;
const size_t CT_PIVOTS = LIGHT_VIRTUAL_CT_POINTS ;
const vct_pivot_t CT_PIVOTS_RGB PROGMEM = { 255 , 255 , 255 , 0 , 0 } ;
const vct_pivot_t CT_PIVOTS_CWW PROGMEM = { 0 , 0 , 0 , 255 , 0 } ;
const vct_pivot_t CT_PIVOTS_WWW PROGMEM = { 0 , 0 , 0 , 0 , 255 } ;
2020-01-04 10:01:44 +00:00
2019-08-17 12:17:30 +01:00
struct LIGHT {
2019-11-23 18:18:09 +00:00
uint32_t strip_timer_counter = 0 ; // Bars and Gradient
2019-08-17 12:17:30 +01:00
power_t power = 0 ; // Power<x> for each channel if SetOption68, or boolean if single light
uint8_t entry_color [ LST_MAX ] ;
uint8_t current_color [ LST_MAX ] ;
uint8_t new_color [ LST_MAX ] ;
uint8_t last_color [ LST_MAX ] ;
uint8_t color_remap [ LST_MAX ] ;
2019-12-02 13:51:28 +00:00
uint8_t wheel = 0 ;
uint8_t random = 0 ;
2019-08-17 12:17:30 +01:00
uint8_t subtype = 0 ; // LST_ subtype
uint8_t device = 0 ;
uint8_t old_power = 1 ;
2020-09-27 10:03:29 +01:00
uint8_t wakeup_active = 0 ; // 0=inctive, 1=on-going, 2=about to start, 3=will be triggered next cycle
2019-08-17 12:17:30 +01:00
uint8_t fixed_color_index = 1 ;
2022-01-27 20:30:05 +00:00
uint8_t pwm_offset = 0 ; // Offset in color buffer, used by sm16716 to drive itself RGB, and PWM for CCT (value is 0 or 3)
2019-10-08 16:46:03 +01:00
uint8_t max_scheme = LS_MAX - 1 ;
2023-05-27 11:33:50 +01:00
uint8_t last_scheme ;
2020-10-05 14:16:43 +01:00
2020-09-27 10:03:29 +01:00
uint32_t wakeup_start_time = 0 ;
2019-08-17 12:17:30 +01:00
bool update = true ;
bool pwm_multi_channels = false ; // SetOption68, treat each PWM channel as an independant dimmer
2020-08-15 18:24:57 +01:00
bool virtual_ct = false ; // SetOption106, add a 5th virtual channel, only if SO106 = 1, SO68 = 0, Light is RGBW (4 channels), SO37 < 128
2019-11-23 18:18:09 +00:00
2020-01-01 15:11:36 +00:00
bool fade_initialized = false ; // dont't fade at startup
2019-11-23 18:18:09 +00:00
bool fade_running = false ;
2020-04-04 00:24:48 +01:00
# ifdef USE_DEVICE_GROUPS
2023-05-27 11:33:50 +01:00
uint8_t last_dgr_scheme = 0 ;
2020-04-04 00:24:48 +01:00
bool devgrp_no_channels_out = false ; // don't share channels with device group (e.g. if scheme set by other device)
2020-04-25 23:49:34 +01:00
# ifdef USE_DGR_LIGHT_SEQUENCE
uint8_t sequence_offset = 0 ; // number of channel changes this light is behind the master
uint8_t * channels_fifo ;
# endif // USE_DGR_LIGHT_SEQUENCE
2020-04-04 00:24:48 +01:00
# endif // USE_DEVICE_GROUPS
2020-04-13 05:17:25 +01:00
# ifdef USE_LIGHT_PALETTE
uint8_t palette_count = 0 ; // palette entry count
uint8_t * palette ; // dynamically allocated palette color array
# endif // USE_LIGHT_PALETTE
2019-11-23 18:18:09 +00:00
uint16_t fade_start_10 [ LST_MAX ] = { 0 , 0 , 0 , 0 , 0 } ;
uint16_t fade_cur_10 [ LST_MAX ] ;
uint16_t fade_end_10 [ LST_MAX ] ; // 10 bits resolution target channel values
2019-12-21 17:26:36 +00:00
uint16_t fade_duration = 0 ; // duration of fade in milliseconds
uint32_t fade_start = 0 ; // fade start time in milliseconds, compared to millis()
2021-01-28 15:41:59 +00:00
bool fade_once_enabled = false ; // override fade a single time
bool fade_once_value = false ; // override fade a single time
bool speed_once_enabled = false ; // override speed a single time
uint8_t speed_once_value = 0 ; // override speed a single time
2020-04-13 20:00:52 +01:00
uint16_t pwm_min = 0 ; // minimum value for PWM, from DimmerRange, 0..1023
uint16_t pwm_max = 1023 ; // maxumum value for PWM, from DimmerRange, 0..1023
2020-12-29 18:31:27 +00:00
// Virtual CT
uint16_t vct_ct [ CT_PIVOTS ] ; // CT value for each segment
# ifdef USE_LIGHT_VIRTUAL_CT
vct_pivot_t vct_color [ CT_PIVOTS ] ; // array of 3 colors each with 5 values
# endif
2019-08-17 12:17:30 +01:00
} Light ;
power_t LightPower ( void )
{
return Light . power ; // Make external
}
2017-02-19 16:49:17 +00:00
2019-08-17 12:17:30 +01:00
uint8_t LightDevice ( void )
{
return Light . device ; // Make external
}
2017-09-16 16:34:03 +01:00
2024-11-27 12:48:30 +00:00
uint32_t LightDevices ( void ) {
if ( 0 = = Light . device ) {
return 0 ;
}
return TasmotaGlobal . devices_present - Light . device + 1 ; // Make external
}
2019-07-07 09:15:50 +01:00
static uint32_t min3 ( uint32_t a , uint32_t b , uint32_t c ) {
return ( a < b & & a < c ) ? a : ( b < c ) ? b : c ;
}
2021-12-12 17:23:49 +00:00
# ifdef USE_NETWORK_LIGHT_SCHEMES
WiFiUDP ddp_udp ;
uint8_t ddp_udp_up = 0 ;
# endif
2019-04-25 12:06:35 +01:00
//
// LightStateClass
// This class is an abstraction of the current light state.
// It allows for b/w, full colors, or white colortone
//
2019-05-05 08:18:57 +01:00
// This class has 2 independant slots
2019-04-25 12:06:35 +01:00
// 1/ Brightness 0.255, dimmer controls both RGB and WC (warm-cold)
2019-05-05 08:18:57 +01:00
// 1/ RGB and Hue/Sat - always kept in sync and stored at full brightness,
// i.e. R G or B are 255
// briRGB specifies the brightness for the RGB slot.
2019-04-25 12:06:35 +01:00
// If Brightness is 0, it is equivalent to Off (for compatibility)
// Dimmer is Brightness converted to range 0..100
2019-05-08 11:06:22 +01:00
// 2/ White with colortone - or CW (Cold / Warm)
2019-05-05 08:18:57 +01:00
// ct is 153..500 temperature (153=cold, 500=warm)
// briCT specifies the brightness for white channel
2019-04-25 12:06:35 +01:00
//
2019-05-05 08:18:57 +01:00
// Dimmer (0.100) is automatically derived from brightness
2019-04-25 12:06:35 +01:00
//
2019-05-05 08:18:57 +01:00
// INVARIANTS:
// 1. RGB components are always stored at full brightness and modulated with briRGB
// ((R == 255) || (G == 255) || (B == 255))
// 2. RGB and Hue/Sat are always kept in sync whether you use setRGB() or setHS()
// 3. Warm/Cold white channels are always stored at full brightness
// ((WW == 255) || (WC == 255))
// 4. WC/WW and CT are always kept in sync.
// Note: if you use setCT() then WC+WW == 255 (both channels are linked)
2019-05-08 11:06:22 +01:00
// but if you use setCW() both channels can be set independantly
2019-05-05 08:18:57 +01:00
// 5. If RGB or CT channels are deactivated, then corresponding brightness is zero
// if (colot_tone == LCM_RGB) then briCT = 0
// if (color_tone == LCM_CT) then briRGB = 0
// if (colot_tone == LCM_BOTH) then briRGB and briCT can have any values
2019-04-25 12:06:35 +01:00
//
2019-05-05 08:18:57 +01:00
// Note: If you want the actual RGB, you need to multiply with Bri, or use getActualRGBCW()
2019-04-25 12:06:35 +01:00
// Note: all values are stored as unsigned integer, no floats.
2020-11-24 22:56:24 +00:00
// Note: you can query values from this singleton. But to change values,
2019-04-25 12:06:35 +01:00
// use the LightController - changing this object will have no effect on actual light.
//
class LightStateClass {
private :
uint16_t _hue = 0 ; // 0..359
2022-02-13 21:59:43 +00:00
uint16_t _hue16 = 0 ; // 0..65535 - high resolution hue necessary for Alexa/Hue integration
2019-04-25 12:06:35 +01:00
uint8_t _sat = 255 ; // 0..255
2019-05-04 22:04:53 +01:00
uint8_t _briRGB = 255 ; // 0..255
2021-05-10 19:36:22 +01:00
uint8_t _briRGB_orig = 255 ; // 0..255
2019-04-25 12:06:35 +01:00
// dimmer is same as _bri but with a range of 0%-100%
uint8_t _r = 255 ; // 0..255
uint8_t _g = 255 ; // 0..255
uint8_t _b = 255 ; // 0..255
2019-05-04 22:04:53 +01:00
2019-08-17 12:17:30 +01:00
uint8_t _subtype = 0 ; // local copy of Light.subtype, if we need multiple lights
2020-01-04 10:01:44 +00:00
uint16_t _ct = CT_MIN ; // 153..500, default to 153 (cold white)
2019-05-04 22:04:53 +01:00
uint8_t _wc = 255 ; // white cold channel
uint8_t _ww = 0 ; // white warm channel
uint8_t _briCT = 255 ;
2021-05-10 19:36:22 +01:00
uint8_t _briCT_orig = 255 ;
2019-05-04 22:04:53 +01:00
uint8_t _color_mode = LCM_RGB ; // RGB by default
2022-02-02 21:03:58 +00:00
bool _power = false ; // power indicator, used only in virtual lights, Tasmota tracks power separately
2022-02-13 21:59:43 +00:00
bool _reachable = true ; // reachable indicator, used only in vritual lights. Not used in internal light
2019-04-25 12:06:35 +01:00
public :
LightStateClass ( ) {
2021-01-23 15:26:23 +00:00
//AddLog(LOG_LEVEL_DEBUG_MORE, "LightStateClass::Constructor RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _bri);
2019-04-25 12:06:35 +01:00
}
2019-05-04 22:04:53 +01:00
void setSubType ( uint8_t sub_type ) {
_subtype = sub_type ; // set sub_type at initialization, shoudln't be changed afterwards
2019-04-25 12:06:35 +01:00
}
2022-02-02 21:03:58 +00:00
uint8_t getSubType ( void ) const {
return _subtype ;
}
2019-04-25 12:06:35 +01:00
2021-05-10 19:36:22 +01:00
// This function is a bit hairy, it will try to match the required
2019-05-04 22:04:53 +01:00
// colormode with the features of the device:
// LST_NONE: LCM_RGB
// LST_SINGLE: LCM_RGB
// LST_COLDWARM: LCM_CT
// LST_RGB: LCM_RGB
// LST_RGBW: LCM_RGB, LCM_CT or LCM_BOTH
2020-01-04 10:01:44 +00:00
// LST_RGBCW: LCM_RGB, LCM_CT or LCM_BOTH
2019-05-04 22:04:53 +01:00
uint8_t setColorMode ( uint8_t cm ) {
uint8_t prev_cm = _color_mode ;
if ( cm < LCM_RGB ) { cm = LCM_RGB ; }
if ( cm > LCM_BOTH ) { cm = LCM_BOTH ; }
uint8_t maxbri = ( _briRGB > = _briCT ) ? _briRGB : _briCT ;
switch ( _subtype ) {
case LST_COLDWARM :
_color_mode = LCM_CT ;
break ;
case LST_NONE :
case LST_SINGLE :
case LST_RGB :
default :
_color_mode = LCM_RGB ;
break ;
case LST_RGBW :
2020-01-04 10:01:44 +00:00
case LST_RGBCW :
2019-05-04 22:04:53 +01:00
_color_mode = cm ;
break ;
}
if ( LCM_RGB = = _color_mode ) {
_briCT = 0 ;
if ( 0 = = _briRGB ) { _briRGB = maxbri ; }
}
if ( LCM_CT = = _color_mode ) {
_briRGB = 0 ;
if ( 0 = = _briCT ) { _briCT = maxbri ; }
}
# ifdef DEBUG_LIGHT
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setColorMode prev_cm (%d) req_cm (%d) new_cm (%d) " , prev_cm , cm , _color_mode ) ;
2019-05-04 22:04:53 +01:00
# endif
return prev_cm ;
2019-04-25 12:06:35 +01:00
}
2022-02-02 21:03:58 +00:00
inline uint8_t getColorMode ( ) const {
2019-05-04 22:04:53 +01:00
return _color_mode ;
}
2022-02-02 21:03:58 +00:00
void addRGBMode ( void ) {
2019-05-04 22:04:53 +01:00
setColorMode ( _color_mode | LCM_RGB ) ;
}
2022-02-02 21:03:58 +00:00
void addCTMode ( void ) {
2019-05-04 22:04:53 +01:00
setColorMode ( _color_mode | LCM_CT ) ;
2019-04-25 12:06:35 +01:00
}
2022-02-02 21:03:58 +00:00
// power accessors, for virtual lights only (has no effect on Tasmota lights)
bool getPower ( void ) const {
return _power ;
}
void setPower ( bool pow ) {
_power = pow ;
}
2022-02-13 21:59:43 +00:00
// reachable accessors, for virtual lights only (has no effect on Tasmota lights)
bool getReachable ( void ) const {
return _reachable ;
}
void setReachable ( bool reachable ) {
_reachable = reachable ;
}
2019-04-25 12:06:35 +01:00
// Get RGB color, always at full brightness (ie. one of the components is 255)
2022-02-02 21:03:58 +00:00
void getRGB ( uint8_t * r , uint8_t * g , uint8_t * b ) const {
2019-05-04 22:04:53 +01:00
if ( r ) { * r = _r ; }
if ( g ) { * g = _g ; }
if ( b ) { * b = _b ; }
2019-04-25 12:06:35 +01:00
}
2019-10-06 19:34:35 +01:00
// get full brightness values for warm and cold channels.
2019-07-02 21:10:57 +01:00
// either w=c=0 (off) or w+c >= 255
2022-02-02 21:03:58 +00:00
void getCW ( uint8_t * rc , uint8_t * rw ) const {
2019-05-04 22:04:53 +01:00
if ( rc ) { * rc = _wc ; }
if ( rw ) { * rw = _ww ; }
}
// Get the actual values for each channel, ie multiply with brightness
2022-02-02 21:03:58 +00:00
void getActualRGBCW ( uint8_t * r , uint8_t * g , uint8_t * b , uint8_t * c , uint8_t * w ) const {
2019-05-04 22:04:53 +01:00
bool rgb_channels_on = _color_mode & LCM_RGB ;
bool ct_channels_on = _color_mode & LCM_CT ;
if ( r ) { * r = rgb_channels_on ? changeUIntScale ( _r , 0 , 255 , 0 , _briRGB ) : 0 ; }
if ( g ) { * g = rgb_channels_on ? changeUIntScale ( _g , 0 , 255 , 0 , _briRGB ) : 0 ; }
if ( b ) { * b = rgb_channels_on ? changeUIntScale ( _b , 0 , 255 , 0 , _briRGB ) : 0 ; }
if ( c ) { * c = ct_channels_on ? changeUIntScale ( _wc , 0 , 255 , 0 , _briCT ) : 0 ; }
if ( w ) { * w = ct_channels_on ? changeUIntScale ( _ww , 0 , 255 , 0 , _briCT ) : 0 ; }
2019-04-25 12:06:35 +01:00
}
2022-02-02 21:03:58 +00:00
void getChannels ( uint8_t * channels ) const {
2019-05-08 11:06:22 +01:00
getActualRGBCW ( & channels [ 0 ] , & channels [ 1 ] , & channels [ 2 ] , & channels [ 3 ] , & channels [ 4 ] ) ;
2019-04-25 12:06:35 +01:00
}
2022-02-02 21:03:58 +00:00
void getChannelsRaw ( uint8_t * channels ) const {
2019-10-15 15:41:43 +01:00
channels [ 0 ] = _r ;
channels [ 1 ] = _g ;
channels [ 2 ] = _b ;
channels [ 3 ] = _wc ;
channels [ 4 ] = _ww ;
}
2022-02-02 21:03:58 +00:00
void getHSB ( uint16_t * hue , uint8_t * sat , uint8_t * bri ) const {
2019-05-04 22:04:53 +01:00
if ( hue ) { * hue = _hue ; }
if ( sat ) { * sat = _sat ; }
if ( bri ) { * bri = _briRGB ; }
2019-04-25 12:06:35 +01:00
}
// getBri() is guaranteed to give the same result as setBri() - no rounding errors.
2022-02-02 21:03:58 +00:00
uint8_t getBri ( void ) const {
2019-05-04 22:04:53 +01:00
// return the max of _briCT and _briRGB
return ( _briRGB > = _briCT ) ? _briRGB : _briCT ;
2019-04-25 12:06:35 +01:00
}
2019-05-04 22:04:53 +01:00
// get the white Brightness
2022-02-02 21:03:58 +00:00
inline uint8_t getBriCT ( void ) const {
2019-05-04 22:04:53 +01:00
return _briCT ;
2019-04-25 12:06:35 +01:00
}
2022-02-02 21:03:58 +00:00
inline uint8_t getBriCTOrig ( void ) const {
2021-05-10 19:36:22 +01:00
return _briCT_orig ;
}
2019-07-02 21:10:57 +01:00
static inline uint8_t DimmerToBri ( uint8_t dimmer ) {
return changeUIntScale ( dimmer , 0 , 100 , 0 , 255 ) ; // 0..255
}
static uint8_t BriToDimmer ( uint8_t bri ) {
uint8_t dimmer = changeUIntScale ( bri , 0 , 255 , 0 , 100 ) ;
2019-04-25 12:06:35 +01:00
// if brightness is non zero, force dimmer to be non-zero too
2019-05-04 22:04:53 +01:00
if ( ( dimmer = = 0 ) & & ( bri > 0 ) ) { dimmer = 1 ; }
2019-04-25 12:06:35 +01:00
return dimmer ;
}
2022-02-02 21:03:58 +00:00
uint8_t getDimmer ( uint32_t mode = 0 ) const {
2019-10-31 10:12:00 +00:00
uint8_t bri ;
switch ( mode ) {
case 1 :
bri = getBriRGB ( ) ;
break ;
case 2 :
bri = getBriCT ( ) ;
break ;
default :
bri = getBri ( ) ;
break ;
}
return BriToDimmer ( bri ) ;
2019-07-02 21:10:57 +01:00
}
2022-02-02 21:03:58 +00:00
inline uint16_t getCT ( void ) const {
2020-01-04 10:01:44 +00:00
return _ct ; // 153..500, or CT_MIN..CT_MAX
2020-12-29 18:31:27 +00:00
}
2019-04-25 12:06:35 +01:00
// get current color in XY format
2022-02-02 21:03:58 +00:00
void getXY ( float * x , float * y ) const {
2019-04-25 12:06:35 +01:00
RgbToXy ( _r , _g , _b , x , y ) ;
}
// setters -- do not use directly, use the light_controller instead
// sets both master Bri and whiteBri
2019-05-04 22:04:53 +01:00
void setBri ( uint8_t bri ) {
setBriRGB ( _color_mode & LCM_RGB ? bri : 0 ) ;
setBriCT ( _color_mode & LCM_CT ? bri : 0 ) ;
2019-04-25 12:06:35 +01:00
# ifdef DEBUG_LIGHT
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setBri RGB raw (%d %d %d) HS (%d %d) bri (%d) " , _r , _g , _b , _hue , _sat , _briRGB ) ;
2019-04-25 12:06:35 +01:00
# endif
}
2019-05-04 22:04:53 +01:00
// changes the RGB brightness alone
uint8_t setBriRGB ( uint8_t bri_rgb ) {
uint8_t prev_bri = _briRGB ;
_briRGB = bri_rgb ;
if ( bri_rgb > 0 ) { addRGBMode ( ) ; }
return prev_bri ;
}
// changes the white brightness alone
uint8_t setBriCT ( uint8_t bri_ct ) {
uint8_t prev_bri = _briCT ;
_briCT = bri_ct ;
if ( bri_ct > 0 ) { addCTMode ( ) ; }
return prev_bri ;
}
2022-02-02 21:03:58 +00:00
inline uint8_t getBriRGB ( void ) const {
2019-05-04 22:04:53 +01:00
return _briRGB ;
2019-04-25 12:06:35 +01:00
}
2022-02-02 21:03:58 +00:00
inline uint8_t getBriRGBOrig ( void ) const {
2021-05-10 19:36:22 +01:00
return _briRGB_orig ;
}
2019-04-25 12:06:35 +01:00
void setDimmer ( uint8_t dimmer ) {
2019-07-02 21:10:57 +01:00
setBri ( DimmerToBri ( dimmer ) ) ;
2019-04-25 12:06:35 +01:00
}
void setCT ( uint16_t ct ) {
if ( 0 = = ct ) {
// disable ct mode
2019-05-04 22:04:53 +01:00
setColorMode ( LCM_RGB ) ; // try deactivating CT mode, setColorMode() will check which is legal
2019-04-25 12:06:35 +01:00
} else {
2020-01-04 10:01:44 +00:00
ct = ( ct < CT_MIN ? CT_MIN : ( ct > CT_MAX ? CT_MAX : ct ) ) ;
2020-12-29 18:31:27 +00:00
_ww = changeUIntScale ( ct , Light . vct_ct [ 0 ] , Light . vct_ct [ CT_PIVOTS - 1 ] , 0 , 255 ) ;
2019-05-04 22:04:53 +01:00
_wc = 255 - _ww ;
_ct = ct ;
addCTMode ( ) ;
2019-04-25 12:06:35 +01:00
}
# ifdef DEBUG_LIGHT
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setCT RGB raw (%d %d %d) HS (%d %d) briRGB (%d) briCT (%d) CT (%d) " , _r , _g , _b , _hue , _sat , _briRGB , _briCT , _ct ) ;
2019-04-25 12:06:35 +01:00
# endif
}
2019-05-08 11:06:22 +01:00
// Manually set Cold/Warm channels.
2019-05-04 22:04:53 +01:00
// There are two modes:
// 1. (free_range == false, default)
// In this mode there is only one virtual white channel with color temperature
// As a side effect, WC+WW = 255. It means also that the sum of light power
// from white LEDs is always equal to briCT. It is not possible here
// to set both white LEDs at full power, hence protecting power supplies
// from overlaoding.
// 2. (free_range == true)
// In this mode, values of WC and WW are free -- both channels can be set
// at full power.
// In this mode, we always scale both channels so that one at least is 255.
//
// We automatically adjust briCT to have the right values of channels
2019-05-08 11:06:22 +01:00
void setCW ( uint8_t c , uint8_t w , bool free_range = false ) {
2019-05-04 22:04:53 +01:00
uint16_t max = ( w > c ) ? w : c ; // 0..255
uint16_t sum = c + w ;
2020-04-22 11:13:53 +01:00
if ( sum < = 257 ) { free_range = false ; } // if we don't allow free range or if sum is below 255 (with tolerance of 2)
2019-05-04 22:04:53 +01:00
if ( 0 = = max ) {
_briCT = 0 ; // brightness set to null
setColorMode ( LCM_RGB ) ; // try deactivating CT mode, setColorMode() will check which is legal
2019-04-25 12:06:35 +01:00
} else {
2019-05-04 22:04:53 +01:00
if ( ! free_range ) {
// we need to normalize to sum = 255
_ww = changeUIntScale ( w , 0 , sum , 0 , 255 ) ;
_wc = 255 - _ww ;
} else { // we normalize to max = 255
_ww = changeUIntScale ( w , 0 , max , 0 , 255 ) ;
_wc = changeUIntScale ( c , 0 , max , 0 , 255 ) ;
}
2020-06-24 20:41:04 +01:00
_ct = changeUIntScale ( w , 0 , sum , CT_MIN , CT_MAX ) ;
2019-05-04 22:04:53 +01:00
addCTMode ( ) ; // activate CT mode if needed
if ( _color_mode & LCM_CT ) { _briCT = free_range ? max : ( sum > 255 ? 255 : sum ) ; }
2019-04-25 12:06:35 +01:00
}
# ifdef DEBUG_LIGHT
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setCW CW (%d %d) CT (%d) briCT (%d) " , c , w , _ct , _briCT ) ;
2019-04-25 12:06:35 +01:00
# endif
}
2019-05-04 22:04:53 +01:00
// sets RGB and returns the Brightness. Bri is updated unless keep_bri is true
uint8_t setRGB ( uint8_t r , uint8_t g , uint8_t b , bool keep_bri = false ) {
2019-04-25 12:06:35 +01:00
uint16_t hue ;
uint8_t sat ;
2019-05-04 22:04:53 +01:00
# ifdef DEBUG_LIGHT
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setRGB RGB input (%d %d %d) " , r , g , b ) ;
2019-05-04 22:04:53 +01:00
# endif
2019-04-25 12:06:35 +01:00
uint32_t max = ( r > g & & r > b ) ? r : ( g > b ) ? g : b ; // 0..255
if ( 0 = = max ) {
r = g = b = 255 ;
2019-05-04 22:04:53 +01:00
setColorMode ( LCM_CT ) ; // try deactivating RGB, setColorMode() will check if this is legal
} else {
if ( 255 > max ) {
// we need to normalize rgb
r = changeUIntScale ( r , 0 , max , 0 , 255 ) ;
g = changeUIntScale ( g , 0 , max , 0 , 255 ) ;
b = changeUIntScale ( b , 0 , max , 0 , 255 ) ;
}
addRGBMode ( ) ;
}
if ( ! keep_bri ) {
_briRGB = ( _color_mode & LCM_RGB ) ? max : 0 ;
2019-04-25 12:06:35 +01:00
}
RgbToHsb ( r , g , b , & hue , & sat , nullptr ) ;
_r = r ;
_g = g ;
_b = b ;
_hue = hue ;
2022-02-13 21:59:43 +00:00
_hue16 = changeUIntScale ( _hue , 0 , 360 , 0 , 65535 ) ;
2019-04-25 12:06:35 +01:00
_sat = sat ;
# ifdef DEBUG_LIGHT
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setRGB RGB raw (%d %d %d) HS (%d %d) bri (%d) " , _r , _g , _b , _hue , _sat , _briRGB ) ;
2019-04-25 12:06:35 +01:00
# endif
return max ;
}
2022-02-13 21:59:43 +00:00
void setHS ( uint16_t hue , uint8_t sat ) {
uint8_t r , g , b ;
HsToRgb ( hue , sat , & r , & g , & b ) ;
_r = r ;
_g = g ;
_b = b ;
_hue = hue ;
_hue16 = changeUIntScale ( _hue , 0 , 360 , 0 , 65535 ) ;
_sat = sat ;
addRGBMode ( ) ;
2019-04-25 12:06:35 +01:00
# ifdef DEBUG_LIGHT
2022-02-13 21:59:43 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setHS HS (%d %d) rgb (%d %d %d) " , hue , sat , r , g , b ) ;
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setHS RGB raw (%d %d %d) HS (%d %d) bri (%d) " , _r , _g , _b , _hue , _sat , _briRGB ) ;
2019-04-25 12:06:35 +01:00
# endif
}
2022-02-13 21:59:43 +00:00
// version taking Hue over 0..65535 as sent by Zigbee or Hue
void setH16S ( uint16_t hue16 , uint8_t sat ) {
uint16_t hue360 = changeUIntScale ( hue16 , 0 , 65535 , 0 , 360 ) ;
setHS ( hue360 , sat ) ;
_hue16 = hue16 ; // keep a higher resolution version in memory
}
uint16_t getHue16 ( void ) const {
return _hue16 ;
}
2019-10-15 15:41:43 +01:00
// set all 5 channels at once, don't modify the values in ANY way
// Channels are: R G B CW WW
void setChannelsRaw ( uint8_t * channels ) {
_r = channels [ 0 ] ;
_g = channels [ 1 ] ;
_b = channels [ 2 ] ;
_wc = channels [ 3 ] ;
_ww = channels [ 4 ] ;
2022-02-13 21:59:43 +00:00
}
2019-10-15 15:41:43 +01:00
2019-04-25 12:06:35 +01:00
// set all 5 channels at once.
// Channels are: R G B CW WW
// Brightness is automatically recalculated to adjust channels to the desired values
void setChannels ( uint8_t * channels ) {
2019-05-04 22:04:53 +01:00
setRGB ( channels [ 0 ] , channels [ 1 ] , channels [ 2 ] ) ;
2019-05-08 11:06:22 +01:00
setCW ( channels [ 3 ] , channels [ 4 ] , true ) ; // free range for WC and WW
2021-05-10 19:36:22 +01:00
uint8_t r = channels [ 0 ] ;
uint8_t g = channels [ 1 ] ;
uint8_t b = channels [ 2 ] ;
uint8_t cw = channels [ 3 ] ;
uint8_t ww = channels [ 4 ] ;
_briRGB_orig = ( r > g & & r > b ) ? r : ( g > b ) ? g : b ;
_briCT_orig = ( cw > ww ) ? cw : ww ;
2019-04-25 12:06:35 +01:00
# ifdef DEBUG_LIGHT
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setChannels (%d %d %d %d %d) " ,
2019-04-25 12:06:35 +01:00
channels [ 0 ] , channels [ 1 ] , channels [ 2 ] , channels [ 3 ] , channels [ 4 ] ) ;
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setChannels CT (%d) briRGB (%d) briCT (%d) " , _ct , _briRGB , _briCT ) ;
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightStateClass::setChannels Actuals (%d %d %d %d %d) " ,
2020-04-22 11:13:53 +01:00
_r , _g , _b , _wc , _ww ) ;
2019-04-25 12:06:35 +01:00
# endif
}
} ;
/*********************************************************************************************\
* LightStateClass implementation
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
class LightControllerClass {
2019-05-04 22:04:53 +01:00
private :
2019-04-25 12:06:35 +01:00
LightStateClass * _state ;
2019-05-04 22:04:53 +01:00
// are RGB and CT linked, i.e. if we set CT then RGB channels are off
bool _ct_rgb_linked = true ;
2019-08-06 09:57:50 +01:00
bool _pwm_multi_channels = false ; // treat each channel as independant dimmer
2019-05-04 22:04:53 +01:00
2019-04-25 12:06:35 +01:00
public :
LightControllerClass ( LightStateClass & state ) {
_state = & state ;
}
2019-05-04 22:04:53 +01:00
void setSubType ( uint8_t sub_type ) {
_state - > setSubType ( sub_type ) ;
}
inline bool setCTRGBLinked ( bool ct_rgb_linked ) {
bool prev = _ct_rgb_linked ;
2019-08-06 09:57:50 +01:00
if ( _pwm_multi_channels ) {
_ct_rgb_linked = false ; // force to false if _pwm_multi_channels is set
} else {
_ct_rgb_linked = ct_rgb_linked ;
}
2019-05-04 22:04:53 +01:00
return prev ;
}
inline bool isCTRGBLinked ( ) {
return _ct_rgb_linked ;
}
2019-08-06 09:57:50 +01:00
inline bool setPWMMultiChannel ( bool pwm_multi_channels ) {
bool prev = _pwm_multi_channels ;
_pwm_multi_channels = pwm_multi_channels ;
if ( pwm_multi_channels ) setCTRGBLinked ( false ) ; // if pwm multi channel, then unlink RGB and CT
return prev ;
}
inline bool isPWMMultiChannel ( void ) {
return _pwm_multi_channels ;
}
2019-04-25 12:06:35 +01:00
# ifdef DEBUG_LIGHT
void debugLogs ( ) {
uint8_t r , g , b , c , w ;
2019-05-08 11:06:22 +01:00
_state - > getActualRGBCW ( & r , & g , & b , & c , & w ) ;
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightControllerClass::debugLogs rgb (%d %d %d) cw (%d %d) " ,
2019-04-25 12:06:35 +01:00
r , g , b , c , w ) ;
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightControllerClass::debugLogs lightCurrent (%d %d %d %d %d) " ,
2019-08-17 12:17:30 +01:00
Light . current_color [ 0 ] , Light . current_color [ 1 ] , Light . current_color [ 2 ] ,
Light . current_color [ 3 ] , Light . current_color [ 4 ] ) ;
2019-04-25 12:06:35 +01:00
}
# endif
void loadSettings ( ) {
# ifdef DEBUG_LIGHT
2021-06-11 17:14:12 +01:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightControllerClass::loadSettings Settings->light_color (%d %d %d %d %d - %d) " ,
Settings - > light_color [ 0 ] , Settings - > light_color [ 1 ] , Settings - > light_color [ 2 ] ,
Settings - > light_color [ 3 ] , Settings - > light_color [ 4 ] , Settings - > light_dimmer ) ;
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightControllerClass::loadSettings light_type/sub (%d %d) " ,
2020-10-30 11:29:48 +00:00
TasmotaGlobal . light_type , Light . subtype ) ;
2019-04-25 12:06:35 +01:00
# endif
2019-10-15 15:41:43 +01:00
if ( _pwm_multi_channels ) {
2021-06-11 17:14:12 +01:00
_state - > setChannelsRaw ( Settings - > light_color ) ;
2019-10-15 15:41:43 +01:00
} else {
// first try setting CW, if zero, it select RGB mode
2021-06-11 17:14:12 +01:00
_state - > setCW ( Settings - > light_color [ 3 ] , Settings - > light_color [ 4 ] , true ) ;
_state - > setRGB ( Settings - > light_color [ 0 ] , Settings - > light_color [ 1 ] , Settings - > light_color [ 2 ] ) ;
2019-10-15 15:41:43 +01:00
2019-08-06 09:57:50 +01:00
// only if non-multi channel
// We apply dimmer in priority to RGB
2021-06-11 17:14:12 +01:00
uint8_t bri = _state - > DimmerToBri ( Settings - > light_dimmer ) ;
2020-04-10 21:55:21 +01:00
// The default values are #FFFFFFFFFF, in this case we avoid setting all channels
// at the same time, see #6534 and #8120
2021-06-11 17:14:12 +01:00
if ( ( DEFAULT_LIGHT_COMPONENT = = Settings - > light_color [ 0 ] ) & &
( DEFAULT_LIGHT_COMPONENT = = Settings - > light_color [ 1 ] ) & &
( DEFAULT_LIGHT_COMPONENT = = Settings - > light_color [ 2 ] ) & &
( DEFAULT_LIGHT_COMPONENT = = Settings - > light_color [ 3 ] ) & &
( DEFAULT_LIGHT_COMPONENT = = Settings - > light_color [ 4 ] ) & &
( DEFAULT_LIGHT_DIMMER = = Settings - > light_dimmer ) ) {
2020-04-22 11:13:53 +01:00
if ( ( LST_COLDWARM = = Light . subtype ) | | ( LST_RGBCW = = Light . subtype ) ) {
_state - > setCW ( 255 , 0 ) ; // avoid having both white channels at 100%, zero second channel (#see 8120)
}
2020-04-10 21:55:21 +01:00
_state - > setBriCT ( bri ) ;
_state - > setBriRGB ( bri ) ;
_state - > setColorMode ( LCM_RGB ) ;
}
2021-06-11 17:14:12 +01:00
if ( Settings - > light_color [ 0 ] + Settings - > light_color [ 1 ] + Settings - > light_color [ 2 ] > 0 ) {
2019-08-06 09:57:50 +01:00
_state - > setBriRGB ( bri ) ;
} else {
_state - > setBriCT ( bri ) ;
}
2019-04-25 12:06:35 +01:00
}
}
2019-05-04 22:04:53 +01:00
void changeCTB ( uint16_t new_ct , uint8_t briCT ) {
2019-04-25 12:06:35 +01:00
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
*
2020-01-04 10:01:44 +00:00
* ct = 153 = 6500 K = Cold = CCWW = FF00
* ct = 500 = 2000 K = Warm = CCWW = 00FF
2019-04-25 12:06:35 +01:00
*/
// don't set CT if not supported
2019-08-17 12:17:30 +01:00
if ( ( LST_COLDWARM ! = Light . subtype ) & & ( LST_RGBW > Light . subtype ) ) {
2019-04-25 12:06:35 +01:00
return ;
}
_state - > setCT ( new_ct ) ;
2019-05-04 22:04:53 +01:00
_state - > setBriCT ( briCT ) ;
if ( _ct_rgb_linked ) { _state - > setColorMode ( LCM_CT ) ; } // try to force CT
2019-04-25 12:06:35 +01:00
saveSettings ( ) ;
calcLevels ( ) ;
//debugLogs();
}
2019-10-31 10:12:00 +00:00
void changeDimmer ( uint8_t dimmer , uint32_t mode = 0 ) {
2019-04-25 12:06:35 +01:00
uint8_t bri = changeUIntScale ( dimmer , 0 , 100 , 0 , 255 ) ;
2019-10-31 10:12:00 +00:00
switch ( mode ) {
case 1 :
changeBriRGB ( bri ) ;
2019-12-28 21:32:08 +00:00
if ( _ct_rgb_linked ) { _state - > setColorMode ( LCM_RGB ) ; } // try to force CT
2019-10-31 10:12:00 +00:00
break ;
case 2 :
changeBriCT ( bri ) ;
2019-12-28 21:32:08 +00:00
if ( _ct_rgb_linked ) { _state - > setColorMode ( LCM_CT ) ; } // try to force CT
2019-10-31 10:12:00 +00:00
break ;
default :
changeBri ( bri ) ;
break ;
}
2019-04-25 12:06:35 +01:00
}
void changeBri ( uint8_t bri ) {
_state - > setBri ( bri ) ;
saveSettings ( ) ;
calcLevels ( ) ;
}
2019-10-31 10:12:00 +00:00
void changeBriRGB ( uint8_t bri ) {
_state - > setBriRGB ( bri ) ;
saveSettings ( ) ;
2019-11-03 11:33:36 +00:00
calcLevels ( ) ;
2019-10-31 10:12:00 +00:00
}
void changeBriCT ( uint8_t bri ) {
_state - > setBriCT ( bri ) ;
saveSettings ( ) ;
2019-11-03 11:33:36 +00:00
calcLevels ( ) ;
2019-10-31 10:12:00 +00:00
}
2019-05-04 22:04:53 +01:00
void changeRGB ( uint8_t r , uint8_t g , uint8_t b , bool keep_bri = false ) {
_state - > setRGB ( r , g , b , keep_bri ) ;
if ( _ct_rgb_linked ) { _state - > setColorMode ( LCM_RGB ) ; } // try to force RGB
2019-04-25 12:06:35 +01:00
saveSettings ( ) ;
calcLevels ( ) ;
}
// calculate the levels for each channel
2019-11-23 18:18:09 +00:00
// if no parameter, results are stored in Light.current_color
void calcLevels ( uint8_t * current_color = nullptr ) {
2019-05-08 11:06:22 +01:00
uint8_t r , g , b , c , w , briRGB , briCT ;
2019-11-23 18:18:09 +00:00
if ( current_color = = nullptr ) { current_color = Light . current_color ; }
2019-08-06 09:57:50 +01:00
if ( _pwm_multi_channels ) { // if PWM multi channel, no more transformation required
2019-11-23 18:18:09 +00:00
_state - > getChannelsRaw ( current_color ) ;
2019-08-06 09:57:50 +01:00
return ;
}
2019-10-15 15:41:43 +01:00
_state - > getActualRGBCW ( & r , & g , & b , & c , & w ) ;
2019-05-04 22:04:53 +01:00
briRGB = _state - > getBriRGB ( ) ;
briCT = _state - > getBriCT ( ) ;
2019-04-25 12:06:35 +01:00
2019-11-23 18:18:09 +00:00
current_color [ 0 ] = current_color [ 1 ] = current_color [ 2 ] = 0 ;
current_color [ 3 ] = current_color [ 4 ] = 0 ;
2019-08-17 12:17:30 +01:00
switch ( Light . subtype ) {
2019-05-05 17:18:20 +01:00
case LST_NONE :
2019-11-23 18:18:09 +00:00
current_color [ 0 ] = 255 ;
2019-05-05 17:18:20 +01:00
break ;
case LST_SINGLE :
2019-11-23 18:18:09 +00:00
current_color [ 0 ] = briRGB ;
2019-05-05 17:18:20 +01:00
break ;
case LST_COLDWARM :
2019-11-23 18:18:09 +00:00
current_color [ 0 ] = c ;
current_color [ 1 ] = w ;
2019-05-05 17:18:20 +01:00
break ;
case LST_RGBW :
2020-01-04 10:01:44 +00:00
case LST_RGBCW :
if ( LST_RGBCW = = Light . subtype ) {
2019-11-23 18:18:09 +00:00
current_color [ 3 ] = c ;
current_color [ 4 ] = w ;
2019-05-05 17:18:20 +01:00
} else {
2019-11-23 18:18:09 +00:00
current_color [ 3 ] = briCT ;
2019-05-05 17:18:20 +01:00
}
// continue
case LST_RGB :
2019-11-23 18:18:09 +00:00
current_color [ 0 ] = r ;
current_color [ 1 ] = g ;
current_color [ 2 ] = b ;
2019-05-05 17:18:20 +01:00
break ;
2019-04-25 12:06:35 +01:00
}
}
2019-05-04 22:04:53 +01:00
void changeHSB ( uint16_t hue , uint8_t sat , uint8_t briRGB ) {
2019-04-25 12:06:35 +01:00
_state - > setHS ( hue , sat ) ;
2019-05-04 22:04:53 +01:00
_state - > setBriRGB ( briRGB ) ;
if ( _ct_rgb_linked ) { _state - > setColorMode ( LCM_RGB ) ; } // try to force RGB
2019-04-25 12:06:35 +01:00
saveSettings ( ) ;
2019-05-04 22:04:53 +01:00
calcLevels ( ) ;
2019-04-25 12:06:35 +01:00
}
2022-03-05 17:42:17 +00:00
/* special version for Alexa Hue maintaining a 16 bits Hue value */
void changeH16SB ( uint16_t hue , uint8_t sat , uint8_t briRGB ) {
_state - > setH16S ( hue , sat ) ;
_state - > setBriRGB ( briRGB ) ;
if ( _ct_rgb_linked ) { _state - > setColorMode ( LCM_RGB ) ; } // try to force RGB
saveSettings ( ) ;
calcLevels ( ) ;
}
2021-06-13 10:10:52 +01:00
// save the current light state to Settings
2019-04-25 12:06:35 +01:00
void saveSettings ( ) {
2019-08-17 12:17:30 +01:00
if ( Light . pwm_multi_channels ) {
2019-08-06 09:57:50 +01:00
// simply save each channel
2021-06-11 17:14:12 +01:00
_state - > getChannelsRaw ( Settings - > light_color ) ;
Settings - > light_dimmer = 100 ; // arbitrary value, unused in this mode
2019-08-06 09:57:50 +01:00
} else {
uint8_t cm = _state - > getColorMode ( ) ;
2021-06-11 17:14:12 +01:00
memset ( & Settings - > light_color [ 0 ] , 0 , sizeof ( Settings - > light_color ) ) ; // clear all channels
2019-08-06 09:57:50 +01:00
if ( LCM_RGB & cm ) { // can be either LCM_RGB or LCM_BOTH
2021-06-11 17:14:12 +01:00
_state - > getRGB ( & Settings - > light_color [ 0 ] , & Settings - > light_color [ 1 ] , & Settings - > light_color [ 2 ] ) ;
Settings - > light_dimmer = _state - > BriToDimmer ( _state - > getBriRGB ( ) ) ;
2019-08-06 09:57:50 +01:00
// anyways we always store RGB with BrightnessRGB
if ( LCM_BOTH = = cm ) {
// then store at actual brightness CW/WW if dual mode
2021-06-11 17:14:12 +01:00
_state - > getActualRGBCW ( nullptr , nullptr , nullptr , & Settings - > light_color [ 3 ] , & Settings - > light_color [ 4 ] ) ;
2019-08-06 09:57:50 +01:00
}
} else if ( LCM_CT = = cm ) { // cm can only be LCM_CT
2021-06-11 17:14:12 +01:00
_state - > getCW ( & Settings - > light_color [ 3 ] , & Settings - > light_color [ 4 ] ) ;
Settings - > light_dimmer = _state - > BriToDimmer ( _state - > getBriCT ( ) ) ;
2019-07-02 21:10:57 +01:00
}
2019-05-04 22:04:53 +01:00
}
2019-04-25 12:06:35 +01:00
# ifdef DEBUG_LIGHT
2021-06-11 17:14:12 +01:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightControllerClass::saveSettings Settings->light_color (%d %d %d %d %d - %d) " ,
Settings - > light_color [ 0 ] , Settings - > light_color [ 1 ] , Settings - > light_color [ 2 ] ,
Settings - > light_color [ 3 ] , Settings - > light_color [ 4 ] , Settings - > light_dimmer ) ;
2019-04-25 12:06:35 +01:00
# endif
}
// set all 5 channels at once.
// Channels are: R G B CW WW
// Brightness is automatically recalculated to adjust channels to the desired values
2021-05-08 05:01:57 +01:00
// if channelsmapped is true, CCT/RGB mapping has already been done
void changeChannels ( uint8_t * channels , bool channelsmapped = false ) {
2019-10-15 15:41:43 +01:00
if ( Light . pwm_multi_channels ) {
_state - > setChannelsRaw ( channels ) ;
2021-05-08 05:01:57 +01:00
} else if ( LST_COLDWARM = = Light . subtype & & ! channelsmapped ) {
2019-06-12 21:49:22 +01:00
// remap channels 0-1 to 3-4 if cold/warm
uint8_t remapped_channels [ 5 ] = { 0 , 0 , 0 , channels [ 0 ] , channels [ 1 ] } ;
_state - > setChannels ( remapped_channels ) ;
} else {
_state - > setChannels ( channels ) ;
}
2019-10-15 15:41:43 +01:00
2019-04-25 12:06:35 +01:00
saveSettings ( ) ;
calcLevels ( ) ;
}
} ;
// the singletons for light state and Light Controller
LightStateClass light_state = LightStateClass ( ) ;
LightControllerClass light_controller = LightControllerClass ( light_state ) ;
2020-12-29 18:31:27 +00:00
/*********************************************************************************************\
* CT ( White Color Temperature )
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
//
// Ensure that invariants for Virtual CT are good:
// - CT_MIN <= ct[0] <= ct[1] <= ct[2] <= CT_MAX
# ifdef USE_LIGHT_VIRTUAL_CT
void checkVirtualCT ( void ) {
if ( Light . vct_ct [ 0 ] < CT_MIN ) { Light . vct_ct [ 0 ] = CT_MIN ; }
if ( Light . vct_ct [ CT_PIVOTS - 1 ] > CT_MAX ) { Light . vct_ct [ CT_PIVOTS - 1 ] = CT_MAX ; }
for ( uint32_t i = 0 ; i < CT_PIVOTS - 1 ; i + + ) {
if ( Light . vct_ct [ i + 1 ] < Light . vct_ct [ i ] ) { Light . vct_ct [ i + 1 ] = Light . vct_ct [ i ] ; }
}
}
# endif // USE_LIGHT_VIRTUAL_CT
# ifdef USE_LIGHT_VIRTUAL_CT
// Init default values for virtual CT, depending on the number of channels
void initCTRange ( uint32_t channels ) {
if ( channels = = 4 ) {
2021-06-11 17:14:12 +01:00
if ( Settings - > flag4 . virtual_ct_cw ) { // Hardware White is Cold White
2020-12-29 18:31:27 +00:00
memcpy_P ( Light . vct_color [ 0 ] , CT_PIVOTS_CWW , sizeof ( Light . vct_color [ 0 ] ) ) ; // Cold white
memcpy_P ( Light . vct_color [ 1 ] , CT_PIVOTS_RGB , sizeof ( Light . vct_color [ 1 ] ) ) ; // Warm white
} else { // Hardware White is Warm White
memcpy_P ( Light . vct_color [ 0 ] , CT_PIVOTS_RGB , sizeof ( Light . vct_color [ 0 ] ) ) ; // Cold white
memcpy_P ( Light . vct_color [ 1 ] , CT_PIVOTS_CWW , sizeof ( Light . vct_color [ 1 ] ) ) ; // Warm white
}
} else if ( channels = = 5 ) {
memcpy_P ( Light . vct_color [ 0 ] , CT_PIVOTS_CWW , sizeof ( Light . vct_color [ 0 ] ) ) ; // Cold white
memcpy_P ( Light . vct_color [ 1 ] , CT_PIVOTS_WWW , sizeof ( Light . vct_color [ 1 ] ) ) ; // Warm white
} else {
memcpy_P ( Light . vct_color [ 0 ] , CT_PIVOTS_RGB , sizeof ( Light . vct_color [ 0 ] ) ) ; // Cold white
memcpy_P ( Light . vct_color [ 1 ] , CT_PIVOTS_RGB , sizeof ( Light . vct_color [ 1 ] ) ) ; // Warm white
}
for ( uint32_t i = 1 ; i < CT_PIVOTS - 1 ; i + + ) {
memcpy_P ( Light . vct_color [ i + 1 ] , Light . vct_color [ i ] , sizeof ( Light . vct_color [ 0 ] ) ) ; // Copy slot 1 into slot 2 (slot 2 in unused)
}
checkVirtualCT ( ) ;
}
# endif // USE_LIGHT_VIRTUAL_CT
2020-12-29 18:58:38 +00:00
void getCTRange ( uint16_t * min_ct , uint16_t * max_ct ) {
if ( min_ct ! = nullptr ) { * min_ct = Light . vct_ct [ 0 ] ; }
if ( max_ct ! = nullptr ) { * max_ct = Light . vct_ct [ CT_PIVOTS - 1 ] ; }
}
2020-12-29 18:31:27 +00:00
void setCTRange ( uint16_t ct_min , uint16_t ct_max ) {
Light . vct_ct [ 0 ] = ct_min ;
for ( uint32_t i = 1 ; i < CT_PIVOTS ; i + + ) {
Light . vct_ct [ i ] = ct_max ; // all slots above [1] are not used
}
}
void setAlexaCTRange ( void ) { // depending on SetOption82, full or limited CT range
2021-06-11 17:14:12 +01:00
if ( Settings - > flag4 . alexa_ct_range ) {
2020-12-29 18:31:27 +00:00
setCTRange ( CT_MIN_ALEXA , CT_MAX_ALEXA ) ;
} else {
setCTRange ( CT_MIN , CT_MAX ) ;
}
}
2019-10-06 16:19:05 +01:00
/********************************************************************************************/
2019-01-30 22:19:40 +00:00
2019-10-06 16:19:05 +01:00
void LightPwmOffset ( uint32_t offset )
2019-01-30 22:19:40 +00:00
{
2019-10-06 16:19:05 +01:00
Light . pwm_offset = offset ;
2019-01-30 22:19:40 +00:00
}
2019-10-06 16:19:05 +01:00
bool LightModuleInit ( void )
2019-01-30 22:19:40 +00:00
{
2020-11-29 14:26:42 +00:00
TasmotaGlobal . light_type = LT_BASIC ; // Use basic PWM control if SetOption15 = 0
2019-01-30 22:19:40 +00:00
2021-06-11 17:14:12 +01:00
if ( Settings - > flag . pwm_control ) { // SetOption15 - Switch between commands PWM or COLOR/DIMMER/CT/CHANNEL
2022-01-27 20:30:05 +00:00
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
2020-10-30 11:29:48 +00:00
if ( PinUsed ( GPIO_PWM1 , i ) ) { TasmotaGlobal . light_type + + ; } // Use Dimmer/Color control for all PWM as SetOption15 = 1
2019-10-06 16:19:05 +01:00
}
2019-01-30 22:19:40 +00:00
}
2020-10-30 11:29:48 +00:00
TasmotaGlobal . light_driver = 0 ;
2019-10-06 16:19:05 +01:00
if ( XlgtCall ( FUNC_MODULE_INIT ) ) {
// serviced
}
2020-04-10 17:24:08 +01:00
# ifdef ESP8266
2020-10-30 11:29:48 +00:00
else if ( SONOFF_BN = = TasmotaGlobal . module_type ) { // PWM Single color led (White)
TasmotaGlobal . light_type = LT_PWM1 ;
2019-10-06 16:19:05 +01:00
}
2020-10-30 11:29:48 +00:00
else if ( SONOFF_LED = = TasmotaGlobal . module_type ) { // PWM Dual color led (White warm and cold)
2020-11-29 14:26:42 +00:00
if ( ! TasmotaGlobal . my_module . io [ 4 ] ) { // Fix Sonoff Led instabilities
pinMode ( 4 , OUTPUT ) ; // Stop floating outputs
2019-10-06 16:19:05 +01:00
digitalWrite ( 4 , LOW ) ;
}
2020-10-30 11:29:48 +00:00
if ( ! TasmotaGlobal . my_module . io [ 5 ] ) {
2020-11-29 14:26:42 +00:00
pinMode ( 5 , OUTPUT ) ; // Stop floating outputs
2019-10-06 16:19:05 +01:00
digitalWrite ( 5 , LOW ) ;
}
2020-10-30 11:29:48 +00:00
if ( ! TasmotaGlobal . my_module . io [ 14 ] ) {
2020-11-29 14:26:42 +00:00
pinMode ( 14 , OUTPUT ) ; // Stop floating outputs
2019-10-06 16:19:05 +01:00
digitalWrite ( 14 , LOW ) ;
}
2020-10-30 11:29:48 +00:00
TasmotaGlobal . light_type = LT_PWM2 ;
2019-10-06 16:19:05 +01:00
}
2020-04-10 17:24:08 +01:00
# endif // ESP8266
2020-10-02 18:40:45 +01:00
# ifdef USE_PWM_DIMMER
2020-10-30 11:29:48 +00:00
else if ( PWM_DIMMER = = TasmotaGlobal . module_type ) {
2021-06-11 17:14:12 +01:00
TasmotaGlobal . light_type = Settings - > pwm_dimmer_cfg . pwm_count + 1 ;
2020-10-02 18:40:45 +01:00
}
# endif // USE_PWM_DIMMER
2019-10-08 16:46:03 +01:00
2020-10-30 11:29:48 +00:00
if ( TasmotaGlobal . light_type > LT_BASIC ) {
2023-02-25 15:44:04 +00:00
UpdateDevicesPresent ( 1 ) ;
2019-10-09 15:55:15 +01:00
}
2019-10-06 16:19:05 +01:00
// post-process for lights
2020-10-30 11:29:48 +00:00
uint32_t pwm_channels = ( TasmotaGlobal . light_type & 7 ) > LST_MAX ? LST_MAX : ( TasmotaGlobal . light_type & 7 ) ;
2021-06-11 17:14:12 +01:00
if ( Settings - > flag3 . pwm_multi_channels ) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
2019-10-06 16:19:05 +01:00
if ( 0 = = pwm_channels ) { pwm_channels = 1 ; }
2023-02-25 15:44:04 +00:00
UpdateDevicesPresent ( pwm_channels - 1 ) ; // add the pwm channels controls at the end
2021-06-11 17:14:12 +01:00
} else if ( ( Settings - > param [ P_RGB_REMAP ] & 128 ) & & ( LST_RGBW < = pwm_channels ) ) { // SetOption37
2019-10-31 10:12:00 +00:00
// if RGBW or RGBCW, and SetOption37 >= 128, we manage RGB and W separately, hence adding a device
2023-02-25 15:44:04 +00:00
UpdateDevicesPresent ( 1 ) ;
2020-12-29 18:31:27 +00:00
} else {
# ifdef USE_LIGHT_VIRTUAL_CT
initCTRange ( pwm_channels ) ;
2021-06-11 17:14:12 +01:00
if ( ( Settings - > flag4 . virtual_ct ) & & ( LST_RGB < = pwm_channels ) ) {
2020-12-29 18:31:27 +00:00
Light . virtual_ct = true ; // enabled
TasmotaGlobal . light_type + = 5 - pwm_channels ; // pretend it is a 5 channels bulb
}
# endif // USE_LIGHT_VIRTUAL_CT
2019-01-30 22:19:40 +00:00
}
2019-01-30 22:32:17 +00:00
2020-10-30 11:29:48 +00:00
return ( TasmotaGlobal . light_type > LT_BASIC ) ;
2019-10-06 16:19:05 +01:00
}
2017-08-12 16:55:20 +01:00
2020-04-13 20:00:52 +01:00
// compute actual PWM min/max values from DimmerRange
// must be called when DimmerRange is changed or LedTable
void LightCalcPWMRange ( void ) {
uint16_t pwm_min , pwm_max ;
2021-06-11 17:14:12 +01:00
pwm_min = change8to10 ( LightStateClass : : DimmerToBri ( Settings - > dimmer_hw_min ) ) ; // default 0
pwm_max = change8to10 ( LightStateClass : : DimmerToBri ( Settings - > dimmer_hw_max ) ) ; // default 100
if ( Settings - > light_correction ) {
2020-04-13 20:00:52 +01:00
pwm_min = ledGamma10_10 ( pwm_min ) ; // apply gamma correction
pwm_max = ledGamma10_10 ( pwm_max ) ; // 0..1023
}
2021-06-11 17:14:12 +01:00
pwm_min = pwm_min > 0 ? changeUIntScale ( pwm_min , 1 , 1023 , 1 , Settings - > pwm_range ) : 0 ; // adapt range but keep zero and non-zero values
pwm_max = changeUIntScale ( pwm_max , 1 , 1023 , 1 , Settings - > pwm_range ) ; // pwm_max cannot be zero
2020-04-13 20:00:52 +01:00
Light . pwm_min = pwm_min ;
Light . pwm_max = pwm_max ;
2021-06-11 17:14:12 +01:00
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LightCalcPWMRange %d %d - %d %d"), Settings->dimmer_hw_min, Settings->dimmer_hw_max, Light.pwm_min, Light.pwm_max);
2020-04-13 20:00:52 +01:00
}
2021-02-28 14:11:29 +00:00
void LightSetScheme ( uint32_t scheme ) {
2021-06-11 17:14:12 +01:00
if ( ! scheme & & Settings - > light_scheme ) {
2021-02-28 14:27:12 +00:00
Light . update = true ;
2021-02-28 14:11:29 +00:00
}
2021-06-11 17:14:12 +01:00
Settings - > light_scheme = scheme ;
2021-02-28 14:11:29 +00:00
}
2018-11-14 13:32:09 +00:00
void LightInit ( void )
2017-02-19 16:49:17 +00:00
{
2020-08-15 18:24:57 +01:00
// move white blend mode from deprecated `RGBWWTable` to `SetOption105`
2021-06-11 17:14:12 +01:00
if ( 0 = = Settings - > rgbwwTable [ 4 ] ) {
Settings - > flag4 . white_blend_mode = true ;
Settings - > rgbwwTable [ 4 ] = 255 ; // set RGBWWTable value to its default
2020-08-15 18:24:57 +01:00
}
2020-10-30 11:29:48 +00:00
Light . device = TasmotaGlobal . devices_present ;
Light . subtype = ( TasmotaGlobal . light_type & 7 ) > LST_MAX ? LST_MAX : ( TasmotaGlobal . light_type & 7 ) ; // Always 0 - LST_MAX (5)
2021-06-11 17:14:12 +01:00
Light . pwm_multi_channels = Settings - > flag3 . pwm_multi_channels ; // SetOption68 - Enable multi-channels PWM instead of Color PWM
2017-11-11 15:02:18 +00:00
2019-10-31 10:12:00 +00:00
if ( LST_RGBW < = Light . subtype ) {
// only change if RGBW or RGBCW
// do not allow independant RGB and WC colors
2021-06-11 17:14:12 +01:00
bool ct_rgb_linked = ! ( Settings - > param [ P_RGB_REMAP ] & 128 ) ; // SetOption37
2019-10-31 10:12:00 +00:00
light_controller . setCTRGBLinked ( ct_rgb_linked ) ;
}
2019-11-24 21:19:41 +00:00
if ( ( LST_SINGLE < = Light . subtype ) & & Light . pwm_multi_channels ) {
2019-08-06 09:57:50 +01:00
// we treat each PWM channel as an independant one, hence we switch to
light_controller . setPWMMultiChannel ( true ) ;
2020-10-30 11:29:48 +00:00
Light . device = TasmotaGlobal . devices_present - Light . subtype + 1 ; // adjust if we also have relays
2019-10-31 10:12:00 +00:00
} else if ( ! light_controller . isCTRGBLinked ( ) ) {
// if RGBW or RGBCW, and SetOption37 >= 128, we manage RGB and W separately
Light . device - - ; // we take the last two devices as lights
2019-08-06 09:57:50 +01:00
}
2020-04-13 20:00:52 +01:00
LightCalcPWMRange ( ) ;
2019-08-06 09:57:50 +01:00
# ifdef DEBUG_LIGHT
2021-06-05 10:47:09 +01:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightInit Light.pwm_multi_channels=%d Light.subtype=%d Light.device=%d TasmotaGlobal.devices_present=%d " ,
2020-10-30 11:29:48 +00:00
Light . pwm_multi_channels , Light . subtype , Light . device , TasmotaGlobal . devices_present ) ;
2019-08-06 09:57:50 +01:00
# endif
2019-08-17 12:17:30 +01:00
light_controller . setSubType ( Light . subtype ) ;
2019-04-25 12:06:35 +01:00
light_controller . loadSettings ( ) ;
2020-12-29 18:31:27 +00:00
setAlexaCTRange ( ) ;
2020-04-13 15:47:27 +01:00
light_controller . calcLevels ( ) ; // calculate the initial values (#8058)
2019-04-25 12:06:35 +01:00
2019-08-17 12:17:30 +01:00
if ( LST_SINGLE = = Light . subtype ) {
2021-06-11 17:14:12 +01:00
Settings - > light_color [ 0 ] = 255 ; // One channel only supports Dimmer but needs max color
2018-11-16 11:22:15 +00:00
}
2020-10-30 11:29:48 +00:00
if ( TasmotaGlobal . light_type < LT_PWM6 ) { // PWM
for ( uint32_t i = 0 ; i < TasmotaGlobal . light_type ; i + + ) {
2021-06-11 17:14:12 +01:00
Settings - > pwm_value [ i ] = 0 ; // Disable direct PWM control
2020-04-27 11:54:07 +01:00
if ( PinUsed ( GPIO_PWM1 , i ) ) {
2020-08-02 11:40:15 +01:00
# ifdef ESP8266
2020-04-26 16:33:27 +01:00
pinMode ( Pin ( GPIO_PWM1 , i ) , OUTPUT ) ;
2020-11-28 15:39:15 +00:00
# endif // ESP8266
2022-01-27 20:30:05 +00:00
// For ESP32, the PWM are already attached by GpioInit() - GpioInitPwm()
2017-12-16 19:11:12 +00:00
}
2017-10-05 12:28:31 +01:00
}
2020-04-27 11:54:07 +01:00
if ( PinUsed ( GPIO_ARIRFRCV ) ) {
if ( PinUsed ( GPIO_ARIRFSEL ) ) {
2020-04-26 16:33:27 +01:00
pinMode ( Pin ( GPIO_ARIRFSEL ) , OUTPUT ) ;
digitalWrite ( Pin ( GPIO_ARIRFSEL ) , 1 ) ; // Turn off RF
2017-11-15 22:07:45 +00:00
}
}
2017-09-16 16:34:03 +01:00
}
2017-09-02 13:37:02 +01:00
2019-10-08 16:46:03 +01:00
uint32_t max_scheme = Light . max_scheme ;
2019-08-17 12:17:30 +01:00
if ( Light . subtype < LST_RGB ) {
2017-10-26 15:33:33 +01:00
max_scheme = LS_POWER ;
}
2021-06-11 17:14:12 +01:00
if ( ( LS_WAKEUP = = Settings - > light_scheme ) | | ( Settings - > light_scheme > max_scheme ) ) {
2021-02-28 14:11:29 +00:00
LightSetScheme ( LS_POWER ) ;
2017-10-26 15:33:33 +01:00
}
2019-08-17 12:17:30 +01:00
Light . power = 0 ;
Light . update = true ;
Light . wakeup_active = 0 ;
2021-06-11 17:14:12 +01:00
if ( 0 = = Settings - > light_wakeup ) {
Settings - > light_wakeup = 60 ; // Fix divide by zero exception 0 in Animate
2020-10-05 14:16:43 +01:00
}
2021-06-11 17:14:12 +01:00
if ( Settings - > flag4 . fade_at_startup ) {
2020-04-06 09:46:17 +01:00
Light . fade_initialized = true ; // consider fade intialized starting from black
}
2019-02-24 20:03:33 +00:00
2019-02-24 23:48:03 +00:00
LightUpdateColorMapping ( ) ;
}
void LightUpdateColorMapping ( void )
{
2021-06-11 17:14:12 +01:00
uint8_t param = Settings - > param [ P_RGB_REMAP ] & 127 ; // SetOption37
2019-04-25 12:06:35 +01:00
if ( param > 119 ) { param = 0 ; }
2019-02-24 20:56:44 +00:00
uint8_t tmp [ ] = { 0 , 1 , 2 , 3 , 4 } ;
2019-08-17 12:17:30 +01:00
Light . color_remap [ 0 ] = tmp [ param / 24 ] ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = param / 24 ; i < 4 ; + + i ) {
2019-02-24 20:56:44 +00:00
tmp [ i ] = tmp [ i + 1 ] ;
}
2019-02-24 20:03:33 +00:00
param = param % 24 ;
2019-08-17 12:17:30 +01:00
Light . color_remap [ 1 ] = tmp [ ( param / 6 ) ] ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = param / 6 ; i < 3 ; + + i ) {
2019-02-24 20:56:44 +00:00
tmp [ i ] = tmp [ i + 1 ] ;
}
2019-02-24 20:03:33 +00:00
param = param % 6 ;
2019-08-17 12:17:30 +01:00
Light . color_remap [ 2 ] = tmp [ ( param / 2 ) ] ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = param / 2 ; i < 2 ; + + i ) {
2019-02-24 20:56:44 +00:00
tmp [ i ] = tmp [ i + 1 ] ;
}
2019-02-24 20:03:33 +00:00
param = param % 2 ;
2019-08-17 12:17:30 +01:00
Light . color_remap [ 3 ] = tmp [ param ] ;
Light . color_remap [ 4 ] = tmp [ 1 - param ] ;
2019-02-24 20:03:33 +00:00
2019-08-17 12:17:30 +01:00
Light . update = true ;
2021-06-11 17:14:12 +01:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("%d colors: %d %d %d %d %d") ,Settings->param[P_RGB_REMAP], Light.color_remap[0],Light.color_remap[1],Light.color_remap[2],Light.color_remap[3],Light.color_remap[4]);
2017-02-19 16:49:17 +00:00
}
2020-01-16 13:22:39 +00:00
uint8_t LightGetDimmer ( uint8_t dimmer ) {
return light_state . getDimmer ( dimmer ) ;
}
2019-05-13 10:27:46 +01:00
void LightSetDimmer ( uint8_t dimmer ) {
light_controller . changeDimmer ( dimmer ) ;
}
2019-11-21 18:17:57 +00:00
2020-01-16 13:22:39 +00:00
void LightGetHSB ( uint16_t * hue , uint8_t * sat , uint8_t * bri ) {
2019-11-19 20:22:45 +00:00
light_state . getHSB ( hue , sat , bri ) ;
}
2020-06-03 21:39:04 +01:00
void LightGetXY ( float * X , float * Y ) {
light_state . getXY ( X , Y ) ;
}
2019-08-06 09:57:50 +01:00
// If SetOption68 is set, get the brightness for a specific device
uint8_t LightGetBri ( uint8_t device ) {
uint8_t bri = 254 ; // default value if relay
2019-08-17 12:17:30 +01:00
if ( Light . pwm_multi_channels ) {
2020-10-30 11:29:48 +00:00
if ( ( device > = Light . device ) & & ( device < Light . device + LST_MAX ) & & ( device < = TasmotaGlobal . devices_present ) ) {
2019-08-17 12:17:30 +01:00
bri = Light . current_color [ device - Light . device ] ;
2019-08-06 09:57:50 +01:00
}
2019-10-31 10:12:00 +00:00
} else if ( light_controller . isCTRGBLinked ( ) ) { // standard behavior
if ( device = = Light . device ) {
bri = light_state . getBri ( ) ;
}
} else { // unlinked
if ( device = = Light . device ) {
bri = light_state . getBriRGB ( ) ;
} else if ( device = = Light . device + 1 ) {
bri = light_state . getBriCT ( ) ;
}
2019-08-06 09:57:50 +01:00
}
return bri ;
}
2019-10-31 10:12:00 +00:00
// If SetOption68 is set, set the brightness for a specific device
2019-08-06 09:57:50 +01:00
void LightSetBri ( uint8_t device , uint8_t bri ) {
2019-08-17 12:17:30 +01:00
if ( Light . pwm_multi_channels ) {
2020-10-30 11:29:48 +00:00
if ( ( device > = Light . device ) & & ( device < Light . device + LST_MAX ) & & ( device < = TasmotaGlobal . devices_present ) ) {
2019-08-17 12:17:30 +01:00
Light . current_color [ device - Light . device ] = bri ;
light_controller . changeChannels ( Light . current_color ) ;
2019-08-06 09:57:50 +01:00
}
2019-10-31 10:12:00 +00:00
} else if ( light_controller . isCTRGBLinked ( ) ) { // standard
if ( device = = Light . device ) {
light_controller . changeBri ( bri ) ;
}
} else { // unlinked
if ( device = = Light . device ) {
light_controller . changeBriRGB ( bri ) ;
} else if ( device = = Light . device + 1 ) {
light_controller . changeBriCT ( bri ) ;
}
2019-08-06 09:57:50 +01:00
}
}
2021-05-10 14:01:41 +01:00
void LightSetBriScaled ( uint8_t bri ) {
// change both dimmers, retain ratio between white and color channels
2021-05-10 19:36:22 +01:00
uint32_t bri_rgb = light_state . getBriRGBOrig ( ) ;
uint32_t bri_ct = light_state . getBriCTOrig ( ) ;
2021-05-10 14:01:41 +01:00
# ifdef DEBUG_LIGHT
AddLog ( LOG_LEVEL_DEBUG , " LightSetBri bri:%d, bri_rgb:%d, bri_ct: %d " , bri , bri_rgb , bri_ct ) ;
# endif
2021-05-10 19:36:22 +01:00
uint32_t max_bri = bri_rgb > bri_ct ? bri_rgb : bri_ct ;
2021-05-10 14:01:41 +01:00
if ( max_bri = = 0 ) {
bri_rgb = bri ;
bri_ct = bri ;
} else {
2021-05-10 19:35:04 +01:00
bri_rgb = changeUIntScale ( bri_rgb , 0 , max_bri , 0 , bri ) ;
bri_ct = changeUIntScale ( bri_ct , 0 , max_bri , 0 , bri ) ;
2021-05-10 14:01:41 +01:00
}
# ifdef DEBUG_LIGHT
AddLog ( LOG_LEVEL_DEBUG , " LightSetBri new bri_rgb:%d, new bri_ct: %d " , bri_rgb , bri_ct ) ;
# endif
light_controller . changeBriRGB ( bri_rgb ) ;
light_controller . changeBriCT ( bri_ct ) ;
}
2020-06-30 16:48:38 +01:00
void LightColorOffset ( int32_t offset ) {
uint16_t hue ;
uint8_t sat ;
light_state . getHSB ( & hue , & sat , nullptr ) ; // Allow user control over Saturation
2023-08-25 16:06:27 +01:00
int16_t hue_new = hue + offset ;
if ( hue_new < 0 ) { hue_new + = 359 ; }
if ( hue_new > 359 ) { hue_new - = 359 ; }
hue = hue_new ;
2020-06-30 16:48:38 +01:00
if ( ! Light . pwm_multi_channels ) {
light_state . setHS ( hue , sat ) ;
} else {
light_state . setHS ( hue , 255 ) ;
light_state . setBri ( 255 ) ; // If multi-channel, force bri to max, it will be later dimmed to correct value
}
light_controller . calcLevels ( Light . new_color ) ;
}
bool LightColorTempOffset ( int32_t offset ) {
2020-06-30 15:58:36 +01:00
int32_t ct = LightGetColorTemp ( ) ;
2020-06-30 16:48:38 +01:00
if ( 0 = = ct ) { return false ; } // CT not supported
2020-06-30 15:58:36 +01:00
ct + = offset ;
if ( ct < CT_MIN ) { ct = CT_MIN ; }
else if ( ct > CT_MAX ) { ct = CT_MAX ; }
LightSetColorTemp ( ct ) ;
2020-06-30 16:48:38 +01:00
return true ;
2020-06-30 15:58:36 +01:00
}
2017-10-18 17:22:34 +01:00
void LightSetColorTemp ( uint16_t ct )
2017-08-16 16:05:36 +01:00
{
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
2017-09-02 13:37:02 +01:00
*
2020-06-30 15:58:36 +01:00
* ct = 153 mirek = 6500 K = Cold = CCWW = FF00
* ct = 500 mirek = 2000 K = Warm = CCWW = 00FF
2017-08-16 16:05:36 +01:00
*/
2019-04-17 20:21:56 +01:00
// don't set CT if not supported
2020-01-04 10:01:44 +00:00
if ( ( LST_COLDWARM ! = Light . subtype ) & & ( LST_RGBCW ! = Light . subtype ) ) {
2019-04-17 20:21:56 +01:00
return ;
}
2019-05-04 22:04:53 +01:00
light_controller . changeCTB ( ct , light_state . getBriCT ( ) ) ;
2017-08-16 16:05:36 +01:00
}
2018-11-14 13:32:09 +00:00
uint16_t LightGetColorTemp ( void )
2017-08-16 16:05:36 +01:00
{
2019-04-17 20:21:56 +01:00
// don't calculate CT for unsupported devices
2020-01-04 10:01:44 +00:00
if ( ( LST_COLDWARM ! = Light . subtype ) & & ( LST_RGBCW ! = Light . subtype ) ) {
2019-04-17 20:21:56 +01:00
return 0 ;
}
2019-05-04 22:04:53 +01:00
return ( light_state . getColorMode ( ) & LCM_CT ) ? light_state . getCT ( ) : 0 ;
2017-08-12 16:55:20 +01:00
}
2017-12-27 13:10:55 +00:00
void LightSetSignal ( uint16_t lo , uint16_t hi , uint16_t value )
{
/* lo - below lo is green
hi - above hi is red
*/
2021-06-11 17:14:12 +01:00
if ( Settings - > flag . light_signal ) { // SetOption18 - Pair light signal with CO2 sensor
2019-04-25 12:06:35 +01:00
uint16_t signal = changeUIntScale ( value , lo , hi , 0 , 255 ) ; // 0..255
2021-01-23 15:26:23 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Light signal %d"), signal);
2019-05-04 22:04:53 +01:00
light_controller . changeRGB ( signal , 255 - signal , 0 , true ) ; // keep bri
2021-02-28 14:11:29 +00:00
LightSetScheme ( LS_POWER ) ;
2019-04-25 12:06:35 +01:00
if ( 0 = = light_state . getBri ( ) ) {
light_controller . changeBri ( 50 ) ;
2017-12-27 13:10:55 +00:00
}
}
}
2019-04-25 12:06:35 +01:00
// convert channels to string, use Option 17 to foce decimal, unless force_hex
char * LightGetColor ( char * scolor , boolean force_hex = false )
2017-08-12 16:55:20 +01:00
{
2021-06-11 17:14:12 +01:00
if ( ( 0 = = Settings - > light_scheme ) | | ( ! Light . pwm_multi_channels ) ) {
2020-04-13 15:47:27 +01:00
light_controller . calcLevels ( ) ; // recalculate levels only if Scheme 0, otherwise we mess up levels
}
2017-08-12 16:55:20 +01:00
scolor [ 0 ] = ' \0 ' ;
2019-08-17 12:17:30 +01:00
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
2021-06-11 17:14:12 +01:00
if ( ! force_hex & & Settings - > flag . decimal_text ) { // SetOption17 - Switch between decimal or hexadecimal output
2019-08-17 12:17:30 +01:00
snprintf_P ( scolor , LIGHT_COLOR_SIZE , PSTR ( " %s%s%d " ) , scolor , ( i > 0 ) ? " , " : " " , Light . current_color [ i ] ) ;
2017-10-23 11:18:15 +01:00
} else {
2019-08-17 12:17:30 +01:00
snprintf_P ( scolor , LIGHT_COLOR_SIZE , PSTR ( " %s%02X " ) , scolor , Light . current_color [ i ] ) ;
2017-10-23 11:18:15 +01:00
}
2017-08-12 16:55:20 +01:00
}
return scolor ;
2017-07-15 14:07:30 +01:00
}
2018-11-14 13:32:09 +00:00
void LightPowerOn ( void )
2017-10-25 13:27:30 +01:00
{
2019-08-17 12:17:30 +01:00
if ( light_state . getBri ( ) & & ! ( Light . power ) ) {
ExecuteCommandPower ( Light . device , POWER_ON , SRC_LIGHT ) ;
2017-10-25 13:27:30 +01:00
}
}
2020-07-13 14:10:23 +01:00
void ResponseLightState ( uint8_t append )
2017-07-15 14:07:30 +01:00
{
2019-08-01 14:47:00 +01:00
char scolor [ LIGHT_COLOR_SIZE ] ;
2018-01-13 14:53:02 +00:00
char scommand [ 33 ] ;
2019-11-24 21:19:41 +00:00
bool unlinked = ! light_controller . isCTRGBLinked ( ) & & ( Light . subtype > = LST_RGBW ) ; // there are 2 power and dimmers for RGB and White
2017-09-02 13:37:02 +01:00
2018-01-20 11:12:39 +00:00
if ( append ) {
2019-03-23 16:00:59 +00:00
ResponseAppend_P ( PSTR ( " , " ) ) ;
2018-01-20 11:12:39 +00:00
} else {
2019-03-23 16:00:59 +00:00
Response_P ( PSTR ( " { " ) ) ;
2017-07-15 14:07:30 +01:00
}
2019-08-17 12:17:30 +01:00
if ( ! Light . pwm_multi_channels ) {
2019-11-24 21:19:41 +00:00
if ( unlinked ) {
// RGB and W are unlinked, we display the second Power/Dimmer
2020-06-11 20:14:30 +01:00
ResponseAppend_P ( PSTR ( " \" " D_RSLT_POWER " %d \" : \" %s \" , \" " D_CMND_DIMMER " 1 \" :%d "
" , \" " D_RSLT_POWER " %d \" : \" %s \" , \" " D_CMND_DIMMER " 2 \" :%d " ) ,
Light . device , GetStateText ( Light . power & 1 ) , light_state . getDimmer ( 1 ) ,
Light . device + 1 , GetStateText ( Light . power & 2 ? 1 : 0 ) , light_state . getDimmer ( 2 ) ) ;
2019-11-24 21:19:41 +00:00
} else {
2021-06-11 17:14:12 +01:00
GetPowerDevice ( scommand , Light . device , sizeof ( scommand ) , Settings - > flag . device_index_enable ) ; // SetOption26 - Switch between POWER or POWER1
2019-11-24 21:19:41 +00:00
ResponseAppend_P ( PSTR ( " \" %s \" : \" %s \" , \" " D_CMND_DIMMER " \" :%d " ) , scommand , GetStateText ( Light . power & 1 ) ,
light_state . getDimmer ( ) ) ;
}
2019-08-16 17:33:41 +01:00
2019-08-17 12:17:30 +01:00
if ( Light . subtype > LST_SINGLE ) {
2019-08-16 17:33:41 +01:00
ResponseAppend_P ( PSTR ( " , \" " D_CMND_COLOR " \" : \" %s \" " ) , LightGetColor ( scolor ) ) ;
2019-12-28 21:59:20 +00:00
if ( LST_RGB < = Light . subtype ) {
uint16_t hue ;
uint8_t sat , bri ;
light_state . getHSB ( & hue , & sat , & bri ) ;
sat = changeUIntScale ( sat , 0 , 255 , 0 , 100 ) ;
bri = changeUIntScale ( bri , 0 , 255 , 0 , 100 ) ;
ResponseAppend_P ( PSTR ( " , \" " D_CMND_HSBCOLOR " \" : \" %d,%d,%d \" " ) , hue , sat , bri ) ;
}
2019-12-28 21:32:08 +00:00
// Add White level
2019-12-28 21:59:20 +00:00
if ( ( LST_COLDWARM = = Light . subtype ) | | ( LST_RGBW < = Light . subtype ) ) {
2019-12-28 21:32:08 +00:00
ResponseAppend_P ( PSTR ( " , \" " D_CMND_WHITE " \" :%d " ) , light_state . getDimmer ( 2 ) ) ;
}
// Add CT
2020-01-04 10:01:44 +00:00
if ( ( LST_COLDWARM = = Light . subtype ) | | ( LST_RGBCW = = Light . subtype ) ) {
2019-12-28 21:32:08 +00:00
ResponseAppend_P ( PSTR ( " , \" " D_CMND_COLORTEMPERATURE " \" :%d " ) , light_state . getCT ( ) ) ;
}
2019-08-16 17:33:41 +01:00
// Add status for each channel
ResponseAppend_P ( PSTR ( " , \" " D_CMND_CHANNEL " \" :[ " ) ) ;
2019-08-17 12:17:30 +01:00
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
uint8_t channel_raw = Light . current_color [ i ] ;
2019-08-16 17:33:41 +01:00
uint8_t channel = changeUIntScale ( channel_raw , 0 , 255 , 0 , 100 ) ;
// if non null, force to be at least 1
if ( ( 0 = = channel ) & & ( channel_raw > 0 ) ) { channel = 1 ; }
ResponseAppend_P ( PSTR ( " %s%d " ) , ( i > 0 ? " , " : " " ) , channel ) ;
}
ResponseAppend_P ( PSTR ( " ] " ) ) ;
2018-03-21 08:56:39 +00:00
}
2019-08-16 17:33:41 +01:00
if ( append ) {
2019-08-17 12:17:30 +01:00
if ( Light . subtype > = LST_RGB ) {
2021-06-11 17:14:12 +01:00
ResponseAppend_P ( PSTR ( " , \" " D_CMND_SCHEME " \" :%d " ) , Settings - > light_scheme ) ;
2019-08-16 17:33:41 +01:00
}
2019-10-08 16:46:03 +01:00
if ( Light . max_scheme > LS_MAX ) {
2021-06-11 17:14:12 +01:00
ResponseAppend_P ( PSTR ( " , \" " D_CMND_WIDTH " \" :%d " ) , Settings - > light_width ) ;
2019-08-16 17:33:41 +01:00
}
ResponseAppend_P ( PSTR ( " , \" " D_CMND_FADE " \" : \" %s \" , \" " D_CMND_SPEED " \" :%d, \" " D_CMND_LEDTABLE " \" : \" %s \" " ) ,
2021-06-11 17:14:12 +01:00
GetStateText ( Settings - > light_fade ) , Settings - > light_speed , GetStateText ( Settings - > light_correction ) ) ;
2018-01-20 11:12:39 +00:00
}
2019-08-17 12:17:30 +01:00
} else { // Light.pwm_multi_channels
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
GetPowerDevice ( scommand , Light . device + i , sizeof ( scommand ) , 1 ) ;
uint32_t light_power_masked = Light . power & ( 1 < < i ) ; // the Light.power value for this device
2019-08-16 17:33:41 +01:00
light_power_masked = light_power_masked ? 1 : 0 ; // convert to on/off
2019-08-17 12:17:30 +01:00
ResponseAppend_P ( PSTR ( " \" %s \" : \" %s \" , \" " D_CMND_CHANNEL " %d \" :%d, " ) , scommand , GetStateText ( light_power_masked ) , Light . device + i ,
changeUIntScale ( Light . current_color [ i ] , 0 , 255 , 0 , 100 ) ) ;
2019-08-16 17:33:41 +01:00
}
2019-08-17 10:06:49 +01:00
ResponseAppend_P ( PSTR ( " \" " D_CMND_COLOR " \" : \" %s \" " ) , LightGetColor ( scolor ) ) ;
2019-08-17 12:17:30 +01:00
} // Light.pwm_multi_channels
2019-08-16 17:33:41 +01:00
if ( ! append ) {
Add support for Shelly 1PM Template
Add support for Shelly 1PM Template {"NAME":"Shelly 1PM","GPIO":[56,0,0,0,82,134,0,0,0,0,0,21,0],"FLAG":2,"BASE":18} (#5716)
2019-05-13 17:26:07 +01:00
ResponseJsonEnd ( ) ;
2018-01-20 11:12:39 +00:00
}
}
2019-11-24 21:19:41 +00:00
void LightPreparePower ( power_t channels = 0xFFFFFFFF ) { // 1 = only RGB, 2 = only CT, 3 = both RGB and CT
2019-08-06 09:57:50 +01:00
# ifdef DEBUG_LIGHT
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG , " LightPreparePower power=%d Light.power=%d " , TasmotaGlobal . power , Light . power ) ;
2019-08-06 09:57:50 +01:00
# endif
// If multi-channels, then we only switch off channels with a value of zero
2019-08-17 12:17:30 +01:00
if ( Light . pwm_multi_channels ) {
2019-11-28 20:25:11 +00:00
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
if ( bitRead ( channels , i ) ) {
// if channel is non-null, channel is supposed to be on, but it is off, do Power On
if ( ( Light . current_color [ i ] ) & & ( ! bitRead ( Light . power , i ) ) ) {
2021-06-11 17:14:12 +01:00
if ( ! Settings - > flag . not_power_linked ) { // SetOption20 - Control power in relation to Dimmer/Color/Ct changes
2019-11-28 20:25:11 +00:00
ExecuteCommandPower ( Light . device + i , POWER_ON_NO_STATE , SRC_LIGHT ) ;
}
} else {
// if channel is zero and channel is on, set it off
if ( ( 0 = = Light . current_color [ i ] ) & & bitRead ( Light . power , i ) ) {
ExecuteCommandPower ( Light . device + i , POWER_OFF_NO_STATE , SRC_LIGHT ) ;
}
}
# ifdef USE_DOMOTICZ
DomoticzUpdatePowerState ( Light . device + i ) ;
# endif // USE_DOMOTICZ
2024-05-18 21:15:46 +01:00
# ifdef USE_KNX
KnxUpdateLight ( ) ;
# endif
2019-11-28 20:25:11 +00:00
}
}
2019-08-06 09:57:50 +01:00
} else {
2019-10-31 10:12:00 +00:00
if ( light_controller . isCTRGBLinked ( ) ) { // linked, standard
if ( light_state . getBri ( ) & & ! ( Light . power ) ) {
2021-06-11 17:14:12 +01:00
if ( ! Settings - > flag . not_power_linked ) { // SetOption20 - Control power in relation to Dimmer/Color/Ct changes
2019-10-31 10:12:00 +00:00
ExecuteCommandPower ( Light . device , POWER_ON_NO_STATE , SRC_LIGHT ) ;
}
} else if ( ! light_state . getBri ( ) & & Light . power ) {
ExecuteCommandPower ( Light . device , POWER_OFF_NO_STATE , SRC_LIGHT ) ;
}
} else {
// RGB
2019-11-24 21:19:41 +00:00
if ( channels & 1 ) {
if ( light_state . getBriRGB ( ) & & ! ( Light . power & 1 ) ) {
2021-06-11 17:14:12 +01:00
if ( ! Settings - > flag . not_power_linked ) { // SetOption20 - Control power in relation to Dimmer/Color/Ct changes
2019-11-24 21:19:41 +00:00
ExecuteCommandPower ( Light . device , POWER_ON_NO_STATE , SRC_LIGHT ) ;
}
} else if ( ! light_state . getBriRGB ( ) & & ( Light . power & 1 ) ) {
ExecuteCommandPower ( Light . device , POWER_OFF_NO_STATE , SRC_LIGHT ) ;
2019-10-31 10:12:00 +00:00
}
}
// White CT
2019-11-24 21:19:41 +00:00
if ( channels & 2 ) {
if ( light_state . getBriCT ( ) & & ! ( Light . power & 2 ) ) {
2021-06-11 17:14:12 +01:00
if ( ! Settings - > flag . not_power_linked ) { // SetOption20 - Control power in relation to Dimmer/Color/Ct changes
2019-11-24 21:19:41 +00:00
ExecuteCommandPower ( Light . device + 1 , POWER_ON_NO_STATE , SRC_LIGHT ) ;
}
} else if ( ! light_state . getBriCT ( ) & & ( Light . power & 2 ) ) {
ExecuteCommandPower ( Light . device + 1 , POWER_OFF_NO_STATE , SRC_LIGHT ) ;
2019-10-31 10:12:00 +00:00
}
2019-08-06 09:57:50 +01:00
}
2018-01-27 16:52:48 +00:00
}
2018-01-20 11:12:39 +00:00
# ifdef USE_DOMOTICZ
2019-08-17 12:17:30 +01:00
DomoticzUpdatePowerState ( Light . device ) ;
2018-01-20 11:12:39 +00:00
# endif // USE_DOMOTICZ
2024-05-18 21:15:46 +01:00
# ifdef USE_KNX
KnxUpdateLight ( ) ;
# endif
2019-08-06 09:57:50 +01:00
}
2021-06-11 17:14:12 +01:00
if ( Settings - > flag3 . hass_tele_on_power ) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
2019-11-03 12:51:22 +00:00
MqttPublishTeleState ( ) ;
}
2018-01-20 11:12:39 +00:00
2019-08-06 09:57:50 +01:00
# ifdef DEBUG_LIGHT
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG , " LightPreparePower End power=%d Light.power=%d " , TasmotaGlobal . power , Light . power ) ;
2019-08-06 09:57:50 +01:00
# endif
2020-10-28 18:03:39 +00:00
Light . power = TasmotaGlobal . power > > ( Light . device - 1 ) ; // reset next state, works also with unlinked RGB/CT
2020-07-13 14:10:23 +01:00
ResponseLightState ( 0 ) ;
2017-07-15 14:07:30 +01:00
}
2020-04-13 05:17:25 +01:00
# ifdef USE_LIGHT_PALETTE
void LightSetPaletteEntry ( void )
{
uint8_t bri = light_state . getBri ( ) ;
2020-11-08 03:40:17 +00:00
uint8_t * palette_entry = & Light . palette [ Light . wheel * Light . subtype ] ;
for ( int i = 0 ; i < Light . subtype ; i + + ) {
2020-04-13 05:17:25 +01:00
Light . new_color [ i ] = changeUIntScale ( palette_entry [ i ] , 0 , 255 , 0 , bri ) ;
}
light_state . setChannelsRaw ( Light . new_color ) ;
if ( ! Light . pwm_multi_channels ) {
light_state . setCW ( Light . new_color [ 3 ] , Light . new_color [ 4 ] , true ) ;
if ( Light . new_color [ 0 ] | | Light . new_color [ 1 ] | | Light . new_color [ 2 ] ) light_state . addRGBMode ( ) ;
}
}
# endif // USE_LIGHT_PALETTE
2017-10-25 13:27:30 +01:00
void LightCycleColor ( int8_t direction )
{
2021-06-11 17:14:12 +01:00
// if (Light.strip_timer_counter % (Settings->light_speed * 2)) { return; } // Speed 1: 24sec, 2: 48sec, 3: 72sec, etc
if ( Settings - > light_speed > 3 ) {
if ( Light . strip_timer_counter % ( Settings - > light_speed - 2 ) ) { return ; } // Speed 4: 24sec, 5: 36sec, 6: 48sec, etc
2017-10-25 13:27:30 +01:00
}
2020-04-13 05:17:25 +01:00
# ifdef USE_LIGHT_PALETTE
if ( Light . palette_count ) {
2020-04-28 04:26:32 +01:00
if ( ! Light . fade_running ) {
if ( 0 = = direction ) {
Light . wheel = random ( Light . palette_count ) ;
2020-04-13 05:17:25 +01:00
}
2020-04-28 04:26:32 +01:00
else {
Light . wheel + = direction ;
if ( Light . wheel > = Light . palette_count ) {
Light . wheel = 0 ;
if ( direction < 0 ) Light . wheel = Light . palette_count - 1 ;
}
}
LightSetPaletteEntry ( ) ;
2020-04-13 05:17:25 +01:00
}
return ;
}
# endif // USE_LIGHT_PALETTE
2019-11-30 16:53:49 +00:00
if ( 0 = = direction ) {
if ( Light . random = = Light . wheel ) {
2019-12-02 13:51:28 +00:00
Light . random = random ( 255 ) ;
2020-01-08 15:45:43 +00:00
2020-01-08 16:45:48 +00:00
uint8_t my_dir = ( Light . random < Light . wheel - 128 ) ? 1 :
( Light . random < Light . wheel ) ? 0 :
( Light . random > Light . wheel + 128 ) ? 0 : 1 ; // Increment or Decrement and roll-over
Light . random = ( Light . random & 0xFE ) | my_dir ;
2020-01-08 15:45:43 +00:00
2021-01-23 15:26:23 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("LGT: random %d"), Light.random);
2017-10-25 13:27:30 +01:00
}
2020-01-08 15:45:43 +00:00
// direction = (Light.random < Light.wheel) ? -1 : 1;
2020-01-08 16:45:48 +00:00
direction = ( Light . random & 0x01 ) ? 1 : - 1 ;
2017-10-25 13:27:30 +01:00
}
Change light scheme 2,3,4 cycle time
Change light scheme 2,3,4 cycle time speed from 24,48,72,... seconds to 4,6,12,24,36,48,... seconds (#8034)
2020-03-31 16:27:33 +01:00
2021-06-11 17:14:12 +01:00
// if (Settings->light_speed < 3) { direction <<= (3 - Settings->light_speed); } // Speed 1: 12/4=3sec, 2: 12/2=6sec, 3: 12sec
if ( Settings - > light_speed < 3 ) { direction * = ( 4 - Settings - > light_speed ) ; } // Speed 1: 12/3=4sec, 2: 12/2=6sec, 3: 12sec
2019-12-02 13:51:28 +00:00
Light . wheel + = direction ;
uint16_t hue = changeUIntScale ( Light . wheel , 0 , 255 , 0 , 359 ) ; // Scale to hue to keep amount of steps low (max 255 instead of 359)
2021-01-23 15:26:23 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("LGT: random %d, wheel %d, hue %d"), Light.random, Light.wheel, hue);
2019-12-01 17:19:17 +00:00
2020-04-13 15:47:27 +01:00
if ( ! Light . pwm_multi_channels ) {
2020-06-30 16:48:38 +01:00
uint8_t sat ;
light_state . getHSB ( nullptr , & sat , nullptr ) ; // Allow user control over Saturation
light_state . setHS ( hue , sat ) ;
2020-04-13 15:47:27 +01:00
} else {
light_state . setHS ( hue , 255 ) ;
light_state . setBri ( 255 ) ; // If multi-channel, force bri to max, it will be later dimmed to correct value
}
2019-12-01 17:19:17 +00:00
light_controller . calcLevels ( Light . new_color ) ;
2017-10-25 13:27:30 +01:00
}
2021-12-12 17:23:49 +00:00
# ifdef USE_NETWORK_LIGHT_SCHEMES
void LightListenDDP ( )
{
// Light channels gets completely controlled over DDP. So, we don't really check other settings.
// To enter this scheme, we are already assured the light is at least RGB
static uint8_t ddp_color [ 5 ] = { 0 , 0 , 0 , 0 , 0 } ;
// Can't be trying to initialize UDP too early.
if ( TasmotaGlobal . restart_flag | | TasmotaGlobal . global_state . network_down ) {
light_state . setChannels ( ddp_color ) ;
light_controller . calcLevels ( Light . new_color ) ;
return ;
}
// Start DDP listener, if fail, just set last ddp_color
if ( ! ddp_udp_up ) {
if ( ! ddp_udp . begin ( 4048 ) ) {
light_state . setChannels ( ddp_color ) ;
light_controller . calcLevels ( Light . new_color ) ;
return ;
}
ddp_udp_up = 1 ;
AddLog ( LOG_LEVEL_DEBUG_MORE , " DDP: UDP Listener Started: Normal Scheme " ) ;
}
// Get the DDP payload over UDP
std : : vector < uint8_t > payload ;
while ( uint16_t packet_size = ddp_udp . parsePacket ( ) ) {
payload . resize ( packet_size ) ;
if ( ! ddp_udp . read ( & payload [ 0 ] , payload . size ( ) ) ) {
continue ;
}
}
// No verification checks performed against packet besides length
if ( payload . size ( ) > 12 ) {
ddp_color [ 0 ] = payload [ 10 ] ;
ddp_color [ 1 ] = payload [ 11 ] ;
ddp_color [ 2 ] = payload [ 12 ] ;
}
light_state . setChannels ( ddp_color ) ;
light_controller . calcLevels ( Light . new_color ) ;
}
# endif
2018-11-14 13:32:09 +00:00
void LightSetPower ( void )
2017-02-19 16:49:17 +00:00
{
2019-08-17 12:17:30 +01:00
// Light.power = XdrvMailbox.index;
Light . old_power = Light . power ;
//Light.power = bitRead(XdrvMailbox.index, Light.device -1);
2019-08-06 09:57:50 +01:00
uint32_t mask = 1 ; // default mask
2019-08-17 12:17:30 +01:00
if ( Light . pwm_multi_channels ) {
mask = ( 1 < < Light . subtype ) - 1 ; // wider mask
2019-10-31 10:12:00 +00:00
} else if ( ! light_controller . isCTRGBLinked ( ) ) {
mask = 3 ; // we got 2 devices, for RGB and White
2019-08-06 09:57:50 +01:00
}
2019-08-17 12:17:30 +01:00
uint32_t shift = Light . device - 1 ;
2019-08-06 09:57:50 +01:00
// If PWM multi_channels
2020-10-30 11:29:48 +00:00
// Ex: 3 Relays and 4 PWM - TasmotaGlobal.devices_present = 7, Light.device = 4, Light.subtype = 4
2019-08-06 09:57:50 +01:00
// Result: mask = 0b00001111 = 0x0F, shift = 3.
// Power bits we consider are: 0b01111000 = 0x78
2020-10-30 11:29:48 +00:00
// If regular situation: TasmotaGlobal.devices_present == Light.subtype
2019-08-17 12:17:30 +01:00
Light . power = ( XdrvMailbox . index & ( mask < < shift ) ) > > shift ;
if ( Light . wakeup_active ) {
Light . wakeup_active - - ;
2017-07-03 10:45:15 +01:00
}
2019-08-06 09:57:50 +01:00
# ifdef DEBUG_LIGHT
2021-06-05 10:47:09 +01:00
AddLog ( LOG_LEVEL_DEBUG_MORE , " LightSetPower XdrvMailbox.index=%d Light.old_power=%d Light.power=%d mask=%d shift=%d " ,
2019-08-17 12:17:30 +01:00
XdrvMailbox . index , Light . old_power , Light . power , mask , shift ) ;
2019-08-06 09:57:50 +01:00
# endif
2019-08-17 12:17:30 +01:00
if ( Light . power ! = Light . old_power ) {
Light . update = true ;
2017-09-18 17:06:46 +01:00
}
2017-10-18 17:22:34 +01:00
LightAnimate ( ) ;
2017-02-19 16:49:17 +00:00
}
2021-01-28 15:41:59 +00:00
bool LightGetFadeSetting ( void ) {
if ( Light . fade_once_enabled ) return Light . fade_once_value ;
2021-06-11 17:14:12 +01:00
return Settings - > light_fade ;
2021-01-28 15:41:59 +00:00
}
uint8_t LightGetSpeedSetting ( void ) {
if ( Light . speed_once_enabled ) return Light . speed_once_value ;
2021-06-11 17:14:12 +01:00
return Settings - > light_speed ;
2021-01-28 15:41:59 +00:00
}
2021-09-15 18:54:55 +01:00
// Force to reapply color, for example when PWM Frequency changed
void LightReapplyColor ( void ) {
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
Light . last_color [ i ] = 0 ;
}
}
2019-11-23 18:18:09 +00:00
// On entry Light.new_color[5] contains the color to be displayed
// and Light.last_color[5] the color currently displayed
// Light.power tells which lights or channels (SetOption68) are on/off
2018-11-14 13:32:09 +00:00
void LightAnimate ( void )
2017-02-19 16:49:17 +00:00
{
2019-12-02 17:54:08 +00:00
bool power_off = false ;
2022-11-02 21:37:53 +00:00
static int32_t sleep_previous = - 1 ; // previous value of sleep before changing it to PWM_MAX_SLEEP, -1 means unchanged
2017-09-02 13:37:02 +01:00
2020-01-04 10:01:44 +00:00
// make sure we update CT range in case SetOption82 was changed
2019-08-17 12:17:30 +01:00
Light . strip_timer_counter + + ;
2019-12-21 17:26:36 +00:00
2023-12-26 11:41:55 +00:00
// Set a maximum sleep of PWM_MAX_SLEEP if Fade is running, or if light is on and
// a frequently updating light scheme is in use. This is to allow smooth transitions
// between light levels and colors.
if ( ( Settings - > light_scheme > LS_POWER & & Light . power ) | | Light . fade_running ) {
2022-11-02 21:37:53 +00:00
if ( TasmotaGlobal . sleep > PWM_MAX_SLEEP ) {
sleep_previous = TasmotaGlobal . sleep ; // save previous value of sleep
2020-11-24 22:56:24 +00:00
TasmotaGlobal . sleep = PWM_MAX_SLEEP ; // set a maximum value (in milliseconds) to sleep to ensure that animations are smooth
2019-12-21 17:26:36 +00:00
}
} else {
2022-11-02 21:37:53 +00:00
if ( sleep_previous > 0 ) {
TasmotaGlobal . sleep = sleep_previous ;
sleep_previous = - 1 ; // rearm
}
2019-12-21 17:26:36 +00:00
}
2019-11-23 18:18:09 +00:00
if ( ! Light . power ) { // All channels powered off
2019-08-17 12:17:30 +01:00
Light . strip_timer_counter = 0 ;
2021-06-11 17:14:12 +01:00
if ( Settings - > light_scheme > = LS_MAX ) {
2019-12-02 17:54:08 +00:00
power_off = true ;
}
2021-12-12 17:23:49 +00:00
# ifdef USE_NETWORK_LIGHT_SCHEMES
if ( ddp_udp_up ) {
ddp_udp . stop ( ) ;
ddp_udp_up = 0 ;
AddLog ( LOG_LEVEL_DEBUG_MORE , " DDP: UDP Stopped: Power Off " ) ;
}
# endif
2019-11-25 14:20:44 +00:00
} else {
2021-12-12 17:23:49 +00:00
# ifdef USE_NETWORK_LIGHT_SCHEMES
if ( ( Settings - > light_scheme < LS_MAX ) & & ( Settings - > light_scheme ! = LS_DDP ) & & ( ddp_udp_up ) ) {
ddp_udp . stop ( ) ;
ddp_udp_up = 0 ;
AddLog ( LOG_LEVEL_DEBUG_MORE , " DDP: UDP Stopped: Normal Scheme not DDP " ) ;
}
# endif
2021-06-11 17:14:12 +01:00
switch ( Settings - > light_scheme ) {
2017-10-25 13:27:30 +01:00
case LS_POWER :
2019-11-23 18:18:09 +00:00
light_controller . calcLevels ( Light . new_color ) ;
2017-09-16 16:34:03 +01:00
break ;
2017-10-25 13:27:30 +01:00
case LS_WAKEUP :
2020-09-27 10:03:29 +01:00
{
if ( 2 = = Light . wakeup_active ) {
Light . wakeup_active = 1 ;
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
Light . new_color [ i ] = 0 ;
}
Light . wakeup_start_time = millis ( ) ;
2017-08-12 16:55:20 +01:00
}
2020-09-27 10:03:29 +01:00
// which step are we in a range 0..1023
2021-06-11 17:14:12 +01:00
uint32_t step_10 = ( ( millis ( ) - Light . wakeup_start_time ) * 1023 ) / ( Settings - > light_wakeup * 1000 ) ;
2020-09-27 10:03:29 +01:00
if ( step_10 > 1023 ) { step_10 = 1023 ; } // sanity check
2021-06-11 17:14:12 +01:00
uint8_t wakeup_bri = changeUIntScale ( step_10 , 0 , 1023 , 0 , LightStateClass : : DimmerToBri ( Settings - > light_dimmer ) ) ;
2020-09-27 10:03:29 +01:00
if ( wakeup_bri ! = light_state . getBri ( ) ) {
light_state . setBri ( wakeup_bri ) ;
2019-04-25 12:06:35 +01:00
light_controller . calcLevels ( ) ;
2019-08-17 12:17:30 +01:00
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
Light . new_color [ i ] = Light . current_color [ i ] ;
2017-09-16 16:34:03 +01:00
}
2020-09-27 10:03:29 +01:00
}
if ( 1023 = = step_10 ) {
2020-01-24 11:48:50 +00:00
Response_P ( PSTR ( " { \" " D_CMND_WAKEUP " \" : \" " D_JSON_DONE " \" " ) ) ;
2020-07-13 14:10:23 +01:00
ResponseLightState ( 1 ) ;
2020-01-24 11:48:50 +00:00
ResponseJsonEnd ( ) ;
2020-07-20 16:24:51 +01:00
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_STAT , PSTR ( D_CMND_WAKEUP ) ) ;
2020-01-24 11:48:50 +00:00
2019-08-17 12:17:30 +01:00
Light . wakeup_active = 0 ;
2021-02-28 14:11:29 +00:00
LightSetScheme ( LS_POWER ) ;
2017-09-16 16:34:03 +01:00
}
}
break ;
2017-10-25 13:27:30 +01:00
case LS_CYCLEUP :
case LS_CYCLEDN :
case LS_RANDOM :
2021-06-11 17:14:12 +01:00
if ( LS_CYCLEUP = = Settings - > light_scheme ) {
2020-04-13 15:49:09 +01:00
LightCycleColor ( 1 ) ;
2021-06-11 17:14:12 +01:00
} else if ( LS_CYCLEDN = = Settings - > light_scheme ) {
2020-04-13 15:49:09 +01:00
LightCycleColor ( - 1 ) ;
2020-04-13 15:47:27 +01:00
} else {
2020-04-13 15:49:09 +01:00
LightCycleColor ( 0 ) ;
2020-04-13 15:47:27 +01:00
}
if ( Light . pwm_multi_channels ) { // See #8058
2021-06-11 17:14:12 +01:00
Light . new_color [ 0 ] = changeUIntScale ( Light . new_color [ 0 ] , 0 , 255 , 0 , Settings - > light_color [ 0 ] ) ;
Light . new_color [ 1 ] = changeUIntScale ( Light . new_color [ 1 ] , 0 , 255 , 0 , Settings - > light_color [ 1 ] ) ;
Light . new_color [ 2 ] = changeUIntScale ( Light . new_color [ 2 ] , 0 , 255 , 0 , Settings - > light_color [ 2 ] ) ;
2020-04-13 15:47:27 +01:00
}
2017-10-25 13:27:30 +01:00
break ;
2021-12-12 17:23:49 +00:00
# ifdef USE_NETWORK_LIGHT_SCHEMES
case LS_DDP :
LightListenDDP ( ) ;
break ;
# endif
2017-09-16 16:34:03 +01:00
default :
2023-05-26 16:47:57 +01:00
XlgtCall ( FUNC_SET_SCHEME ) ;
2017-08-12 16:55:20 +01:00
}
2020-04-21 22:33:07 +01:00
# ifdef USE_DEVICE_GROUPS
2023-05-27 11:33:50 +01:00
if ( Settings - > light_scheme ! = Light . last_dgr_scheme ) {
Light . last_dgr_scheme = Settings - > light_scheme ;
2021-06-11 17:14:12 +01:00
SendDeviceGroupMessage ( Light . device , DGR_MSGTYP_UPDATE , DGR_ITEM_LIGHT_SCHEME , Settings - > light_scheme ) ;
2020-04-21 22:33:07 +01:00
Light . devgrp_no_channels_out = false ;
}
# endif // USE_DEVICE_GROUPS
2023-05-27 11:33:50 +01:00
Light . last_scheme = Settings - > light_scheme ;
2017-08-12 16:55:20 +01:00
}
2017-09-16 16:34:03 +01:00
2021-06-11 17:14:12 +01:00
if ( ( Settings - > light_scheme < LS_MAX ) | | power_off ) { // exclude WS281X Neopixel schemes
2019-08-06 09:57:50 +01:00
2019-11-23 18:18:09 +00:00
// Apply power modifiers to Light.new_color
LightApplyPower ( Light . new_color , Light . power ) ;
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_INFO, PSTR("last_color (%02X%02X%02X%02X%02X) new_color (%02X%02X%02X%02X%02X) power %d"),
2019-11-25 14:20:44 +00:00
// Light.last_color[0], Light.last_color[1], Light.last_color[2], Light.last_color[3], Light.last_color[4],
// Light.new_color[0], Light.new_color[1], Light.new_color[2], Light.new_color[3], Light.new_color[4],
2019-11-23 18:18:09 +00:00
// Light.power
// );
2019-10-31 10:12:00 +00:00
2019-08-17 12:17:30 +01:00
if ( memcmp ( Light . last_color , Light . new_color , Light . subtype ) ) {
Light . update = true ;
2017-02-19 16:49:17 +00:00
}
2019-08-17 12:17:30 +01:00
if ( Light . update ) {
2020-02-21 15:09:21 +00:00
# ifdef USE_DEVICE_GROUPS
2021-02-09 03:28:59 +00:00
if ( Light . power & & ! Light . devgrp_no_channels_out ) LightSendDeviceGroupStatus ( ) ;
2020-02-21 15:09:21 +00:00
# endif // USE_DEVICE_GROUPS
2019-12-27 20:02:23 +00:00
uint16_t cur_col_10 [ LST_MAX ] ; // 10 bits resolution
2019-08-17 12:17:30 +01:00
Light . update = false ;
2019-05-02 21:50:19 +01:00
2019-07-07 09:15:50 +01:00
// first set 8 and 10 bits channels
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
2019-12-27 20:02:23 +00:00
Light . last_color [ i ] = Light . new_color [ i ] ;
2019-05-03 19:10:13 +01:00
// Extend from 8 to 10 bits if no correction (in case no gamma correction is required)
2019-12-27 20:02:23 +00:00
cur_col_10 [ i ] = change8to10 ( Light . new_color [ i ] ) ;
2019-02-24 12:07:15 +00:00
}
2019-10-06 19:34:35 +01:00
if ( Light . pwm_multi_channels ) {
2019-12-27 20:02:23 +00:00
calcGammaMultiChannels ( cur_col_10 ) ;
2019-10-06 19:34:35 +01:00
} else {
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_INFO, PSTR(">>> calcGammaBulbs In %03X,%03X,%03X,%03X,%03X"), cur_col_10[0], cur_col_10[1], cur_col_10[2], cur_col_10[3], cur_col_10[4]);
2023-06-01 21:12:57 +01:00
calcGammaBulbs ( cur_col_10 ) ;
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_INFO, PSTR(">>> calcGammaBulbs Out %03X,%03X,%03X,%03X,%03X"), cur_col_10[0], cur_col_10[1], cur_col_10[2], cur_col_10[3], cur_col_10[4]);
2020-01-22 20:37:23 +00:00
}
2020-08-15 18:24:57 +01:00
// final adjusments for PMW ranges, post-gamma correction
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
2019-05-03 19:10:13 +01:00
// scale from 0..1023 to 0..pwm_range, but keep any non-zero value to at least 1
2021-06-11 17:14:12 +01:00
cur_col_10 [ i ] = ( cur_col_10 [ i ] > 0 ) ? changeUIntScale ( cur_col_10 [ i ] , 1 , 1023 , 1 , Settings - > pwm_range ) : 0 ;
2019-02-24 12:07:15 +00:00
}
2019-05-03 19:10:13 +01:00
// apply port remapping on both 8 bits and 10 bits versions
uint16_t orig_col_10bits [ LST_MAX ] ;
2019-12-27 20:02:23 +00:00
memcpy ( orig_col_10bits , cur_col_10 , sizeof ( orig_col_10bits ) ) ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
2019-12-27 20:02:23 +00:00
cur_col_10 [ i ] = orig_col_10bits [ Light . color_remap [ i ] ] ;
2019-02-24 12:07:15 +00:00
}
2021-01-28 15:41:59 +00:00
if ( ! LightGetFadeSetting ( ) | | TasmotaGlobal . skip_light_fade | | power_off | | ( ! Light . fade_initialized ) ) { // no fade
2019-11-23 18:18:09 +00:00
// record the current value for a future Fade
2019-12-27 20:02:23 +00:00
memcpy ( Light . fade_start_10 , cur_col_10 , sizeof ( Light . fade_start_10 ) ) ;
2019-11-23 18:18:09 +00:00
// push the final values at 8 and 10 bits resolution to the PWMs
2019-12-27 20:02:23 +00:00
LightSetOutputs ( cur_col_10 ) ;
2021-10-09 16:42:32 +01:00
LightStopFade ( ) ;
2020-01-01 15:11:36 +00:00
Light . fade_initialized = true ; // it is now ok to fade
2021-01-28 15:41:59 +00:00
Light . fade_once_enabled = false ; // light has been set, reset fade_once_enabled
Light . speed_once_enabled = false ; // light has been set, reset speed_once_enabled
2019-11-23 18:18:09 +00:00
} else { // fade on
if ( Light . fade_running ) {
// if fade is running, we take the curring value as the start for the next fade
memcpy ( Light . fade_start_10 , Light . fade_cur_10 , sizeof ( Light . fade_start_10 ) ) ;
2017-09-16 16:34:03 +01:00
}
2019-12-27 20:02:23 +00:00
memcpy ( Light . fade_end_10 , cur_col_10 , sizeof ( Light . fade_start_10 ) ) ;
2022-09-08 21:04:08 +01:00
// check if PWM CT is enabled, we need a special handling of CT #16454
int32_t channel_ct = ChannelCT ( ) ;
int32_t channel_white = ChannelWhite_when_PWMCT ( ) ;
if ( channel_ct > = 0 & & channel_white > = 0 ) {
if ( Light . fade_start_10 [ channel_white ] = = 0 ) {
// if fading from black, change the start CT to the target, otherwise we will have a wrong fade
Light . fade_start_10 [ channel_ct ] = Light . fade_end_10 [ channel_ct ] ;
}
}
2022-11-16 15:15:31 +00:00
2019-11-23 18:18:09 +00:00
Light . fade_running = true ;
Light . fade_duration = 0 ; // set the value to zero to force a recompute
2019-12-21 17:26:36 +00:00
Light . fade_start = 0 ;
2021-01-28 15:41:59 +00:00
Light . fade_once_enabled = false ; // fade will be applied, reset fade_once_enabled
2019-11-23 18:18:09 +00:00
// Fade will applied immediately below
2017-09-16 16:34:03 +01:00
}
2019-11-23 18:18:09 +00:00
}
if ( Light . fade_running ) {
2019-12-21 17:26:36 +00:00
if ( LightApplyFade ( ) ) {
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_INFO, PSTR("LightApplyFade %d %d %d %d %d"),
2019-12-21 17:26:36 +00:00
// Light.fade_cur_10[0], Light.fade_cur_10[1], Light.fade_cur_10[2], Light.fade_cur_10[3], Light.fade_cur_10[4]);
2019-01-13 04:33:54 +00:00
2019-12-27 20:02:23 +00:00
LightSetOutputs ( Light . fade_cur_10 ) ;
2019-12-21 17:26:36 +00:00
}
2019-11-23 18:18:09 +00:00
}
2020-12-20 14:25:13 +00:00
// For WYZE bulbs we must set the CT pin (PWM2) to INPUT to fully turn it off
2020-12-20 12:22:01 +00:00
if ( TasmotaGlobal . gpio_optiona . pwm1_input & & ! Light . power & & ! Light . fade_running ) { // GPIO Option_A1
2020-12-20 14:25:13 +00:00
if ( PinUsed ( GPIO_PWM1 , 1 ) ) { pinMode ( Pin ( GPIO_PWM1 , 1 ) , INPUT ) ; }
2020-12-20 12:22:01 +00:00
}
2019-11-23 18:18:09 +00:00
}
}
2020-01-01 15:11:36 +00:00
bool isChannelGammaCorrected ( uint32_t channel ) {
2021-06-11 17:14:12 +01:00
if ( ! Settings - > light_correction ) { return false ; } // Gamma correction not activated
2020-01-01 15:11:36 +00:00
if ( channel > = Light . subtype ) { return false ; } // Out of range
2020-04-10 17:24:08 +01:00
# ifdef ESP8266
2021-06-11 17:14:12 +01:00
if ( ( PHILIPS = = TasmotaGlobal . module_type ) | | ( Settings - > flag4 . pwm_ct_mode ) ) {
2021-12-08 18:09:54 +00:00
# else
if ( Settings - > flag4 . pwm_ct_mode ) {
# endif // ESP8266
2020-01-01 15:11:36 +00:00
if ( ( LST_COLDWARM = = Light . subtype ) & & ( 1 = = channel ) ) { return false ; } // PMW reserved for CT
2020-01-04 10:01:44 +00:00
if ( ( LST_RGBCW = = Light . subtype ) & & ( 4 = = channel ) ) { return false ; } // PMW reserved for CT
2020-01-01 15:11:36 +00:00
}
return true ;
}
2022-09-08 21:04:08 +01:00
// Returns the channel number for PWM CT if any, or -1 if none
int32_t ChannelCT ( void ) {
# ifdef ESP8266
if ( ( PHILIPS = = TasmotaGlobal . module_type ) | | ( Settings - > flag4 . pwm_ct_mode ) ) {
# else
if ( Settings - > flag4 . pwm_ct_mode ) {
# endif // ESP8266
if ( LST_COLDWARM = = Light . subtype ) { return 1 ; } // PMW reserved for CT
if ( LST_RGBCW = = Light . subtype ) { return 4 ; } // PMW reserved for CT
}
return - 1 ;
}
// Returns the white channel when PWM CT is enabled -- needed to check for brightness #16454
int32_t ChannelWhite_when_PWMCT ( void ) {
2020-04-16 10:00:56 +01:00
# ifdef ESP8266
2021-06-11 17:14:12 +01:00
if ( ( PHILIPS = = TasmotaGlobal . module_type ) | | ( Settings - > flag4 . pwm_ct_mode ) ) {
2021-12-08 18:09:54 +00:00
# else
if ( Settings - > flag4 . pwm_ct_mode ) {
# endif // ESP8266
2022-09-08 21:04:08 +01:00
if ( LST_COLDWARM = = Light . subtype ) { return 0 ; }
if ( LST_RGBCW = = Light . subtype ) { return 3 ; }
2020-04-15 18:42:50 +01:00
}
2022-09-08 21:04:08 +01:00
return - 1 ;
2020-04-15 18:42:50 +01:00
}
2020-01-01 15:11:36 +00:00
// Calculate the Gamma correction, if any, for fading, using the fast Gamma curve (10 bits in+out)
uint16_t fadeGamma ( uint32_t channel , uint16_t v ) {
if ( isChannelGammaCorrected ( channel ) ) {
2021-01-02 18:26:24 +00:00
return ledGammaFast ( v ) ;
2020-01-01 15:11:36 +00:00
} else {
return v ;
}
}
uint16_t fadeGammaReverse ( uint32_t channel , uint16_t vg ) {
if ( isChannelGammaCorrected ( channel ) ) {
2021-01-02 18:26:24 +00:00
return leddGammaReverseFast ( vg ) ;
2020-01-01 15:11:36 +00:00
} else {
return vg ;
}
}
2021-03-09 06:35:38 +00:00
uint8_t LightGetCurFadeBri ( void ) {
uint8_t max_bri = 0 ;
uint8_t bri_i = 0 ;
for ( uint8_t i = 0 ; i < LST_MAX ; i + + ) {
bri_i = changeUIntScale ( fadeGammaReverse ( i , Light . fade_cur_10 [ i ] ) , 4 , 1023 , 1 , 100 ) ;
if ( bri_i > max_bri ) max_bri = bri_i ;
}
return max_bri ;
}
2021-10-09 16:42:32 +01:00
void LightStopFade ( void ) {
Light . fade_running = false ;
# ifdef USE_PWM_DIMMER
// If the power is off and the fade is done, turn the relay off.
if ( PWM_DIMMER = = TasmotaGlobal . module_type & & ! Light . power ) PWMDimmerSetPower ( ) ;
# endif // USE_PWM_DIMMER
}
2019-12-21 17:26:36 +00:00
bool LightApplyFade ( void ) { // did the value chanegd and needs to be applied
static uint32_t last_millis = 0 ;
uint32_t now = millis ( ) ;
if ( ( now - last_millis ) < = 5 ) {
return false ; // the value was not changed in the last 5 milliseconds, ignore
}
last_millis = now ;
2019-11-23 18:18:09 +00:00
// Check if we need to calculate the duration
if ( 0 = = Light . fade_duration ) {
2019-12-21 17:26:36 +00:00
Light . fade_start = now ;
2019-11-23 18:18:09 +00:00
// compute the distance between start and and color (max of distance for each channel)
uint32_t distance = 0 ;
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
2020-01-01 15:11:36 +00:00
int32_t channel_distance = fadeGammaReverse ( i , Light . fade_end_10 [ i ] ) - fadeGammaReverse ( i , Light . fade_start_10 [ i ] ) ;
2019-11-23 18:18:09 +00:00
if ( channel_distance < 0 ) { channel_distance = - channel_distance ; }
if ( channel_distance > distance ) { distance = channel_distance ; }
}
if ( distance > 0 ) {
// compute the duration of the animation
2021-06-11 17:14:12 +01:00
// Note: Settings->light_speed is the number of half-seconds for a 100% fade,
2019-12-21 17:26:36 +00:00
// i.e. light_speed=1 means 1024 steps in 500ms
2021-01-28 15:41:59 +00:00
Light . fade_duration = LightGetSpeedSetting ( ) * 500 ;
Light . speed_once_enabled = false ; // The once off speed value has been read, reset it
2021-06-11 17:14:12 +01:00
if ( ! Settings - > flag5 . fade_fixed_duration ) {
2023-08-02 16:48:12 +01:00
Light . fade_duration = ( distance * Light . fade_duration ) / 1023 + 1 /* make sure value is not zero */ ; // time is proportional to distance, except with SO117
2020-12-14 18:47:10 +00:00
}
2021-06-11 17:14:12 +01:00
if ( Settings - > save_data ) {
2019-12-20 15:01:24 +00:00
// Also postpone the save_data for the duration of the Fade (in seconds)
2019-12-21 17:26:36 +00:00
uint32_t delay_seconds = 1 + ( Light . fade_duration + 999 ) / 1000 ; // add one more second
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_INFO, PSTR("delay_seconds %d, save_data_counter %d"), delay_seconds, TasmotaGlobal.save_data_counter);
2020-10-29 13:41:12 +00:00
if ( TasmotaGlobal . save_data_counter < delay_seconds ) {
TasmotaGlobal . save_data_counter = delay_seconds ; // pospone
2019-12-20 15:01:24 +00:00
}
2019-11-28 20:07:59 +00:00
}
2019-11-23 18:18:09 +00:00
} else {
// no fade needed, we keep the duration at zero, it will fallback directly to end of fade
2021-10-09 16:42:32 +01:00
LightStopFade ( ) ;
2019-11-23 18:18:09 +00:00
}
}
2019-12-21 17:26:36 +00:00
uint16_t fade_current = now - Light . fade_start ; // number of milliseconds since start of fade
if ( fade_current < = Light . fade_duration ) { // fade not finished
//Serial.printf("Fade: %d / %d - ", fade_current, Light.fade_duration);
2019-11-23 18:18:09 +00:00
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
2020-01-01 15:11:36 +00:00
Light . fade_cur_10 [ i ] = fadeGamma ( i ,
changeUIntScale ( fadeGammaReverse ( i , fade_current ) ,
0 , Light . fade_duration ,
fadeGammaReverse ( i , Light . fade_start_10 [ i ] ) ,
fadeGammaReverse ( i , Light . fade_end_10 [ i ] ) ) ) ;
// Light.fade_cur_10[i] = changeUIntScale(fade_current,
// 0, Light.fade_duration,
// Light.fade_start_10[i], Light.fade_end_10[i]);
2019-11-23 18:18:09 +00:00
}
} else {
// stop fade
//AddLop_P2(LOG_LEVEL_DEBUG, PSTR("Stop fade"));
2021-10-09 16:42:32 +01:00
LightStopFade ( ) ;
2019-12-21 17:26:36 +00:00
Light . fade_start = 0 ;
2019-11-23 18:18:09 +00:00
Light . fade_duration = 0 ;
// set light to target value
memcpy ( Light . fade_cur_10 , Light . fade_end_10 , sizeof ( Light . fade_end_10 ) ) ;
// record the last value for next start
memcpy ( Light . fade_start_10 , Light . fade_end_10 , sizeof ( Light . fade_start_10 ) ) ;
}
2019-12-21 17:26:36 +00:00
return true ;
2019-11-23 18:18:09 +00:00
}
// On entry we take the 5 channels 8 bits entry, and we apply Power modifiers
// I.e. shut down channels that are powered down
void LightApplyPower ( uint8_t new_color [ LST_MAX ] , power_t power ) {
// If SetOption68, multi_channels
if ( Light . pwm_multi_channels ) {
// if multi-channels, specifically apply the Light.power bits
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
if ( 0 = = bitRead ( power , i ) ) { // if power down bit is zero
new_color [ i ] = 0 ; // shut down this channel
}
}
// #ifdef DEBUG_LIGHT
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_DEBUG_MORE, "Animate>> Light.power=%d Light.new_color=[%d,%d,%d,%d,%d]",
2019-11-23 18:18:09 +00:00
// Light.power, Light.new_color[0], Light.new_color[1], Light.new_color[2],
// Light.new_color[3], Light.new_color[4]);
// #endif
} else {
if ( ! light_controller . isCTRGBLinked ( ) ) {
// we have 2 power bits for RGB and White
if ( 0 = = ( power & 1 ) ) {
new_color [ 0 ] = new_color [ 1 ] = new_color [ 2 ] = 0 ;
}
if ( 0 = = ( power & 2 ) ) {
new_color [ 3 ] = new_color [ 4 ] = 0 ;
2019-10-17 09:45:51 +01:00
}
2019-11-23 18:18:09 +00:00
} else if ( ! power ) {
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
new_color [ i ] = 0 ;
2019-10-06 16:19:05 +01:00
}
2019-11-23 18:18:09 +00:00
}
}
}
2019-12-27 20:02:23 +00:00
void LightSetOutputs ( const uint16_t * cur_col_10 ) {
2019-11-23 18:18:09 +00:00
// now apply the actual PWM values, adjusted and remapped 10-bits range
2020-10-30 11:29:48 +00:00
if ( TasmotaGlobal . light_type < LT_PWM6 ) { // only for direct PWM lights, not for Tuya, Armtronix...
2022-01-24 22:13:41 +00:00
2022-09-08 21:22:31 +01:00
int32_t channel_ct = ChannelCT ( ) ; // Channel for PWM CT or -1 if no CT or regular CT
2021-08-11 13:32:53 +01:00
# ifdef USE_PWM_DIMMER
uint16_t max_col = 0 ;
2022-01-07 15:17:53 +00:00
# ifdef USE_I2C
if ( TasmotaGlobal . gpio_optiona . linkind_support ) { // Option_A6
uint8_t val = change10to8 ( cur_col_10 [ Light . pwm_offset ] > 0 ? changeUIntScale ( cur_col_10 [ Light . pwm_offset ] , 0 , Settings - > pwm_range , Light . pwm_min , Light . pwm_max ) : 0 ) ;
max_col = val ;
uint16_t chk = 65403 - val ;
uint8_t buf [ ] = { 0x09 , 0x50 , 0x01 , val , 0x00 , 0x00 , ( uint8_t ) ( chk > > 8 ) , ( uint8_t ) ( chk & 0xff ) } ;
I2cWriteBuffer ( 0x50 , 0x2A , buf , sizeof ( buf ) ) ;
} else
# endif // USE_I2C
2021-08-11 13:32:53 +01:00
# endif // USE_PWM_DIMMER
2019-11-23 18:18:09 +00:00
for ( uint32_t i = 0 ; i < ( Light . subtype - Light . pwm_offset ) ; i + + ) {
2021-08-11 13:32:53 +01:00
uint16_t cur_col = cur_col_10 [ i + Light . pwm_offset ] ;
# ifdef USE_PWM_DIMMER
if ( cur_col > max_col ) max_col = cur_col ;
# endif // USE_PWM_DIMMER
2020-04-27 11:54:07 +01:00
if ( PinUsed ( GPIO_PWM1 , i ) ) {
2021-01-23 16:24:54 +00:00
//AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "Cur_Col%d 10 bits %d"), i, cur_col_10[i]);
2020-04-13 20:00:52 +01:00
uint16_t cur_col = cur_col_10 [ i + Light . pwm_offset ] ;
2022-09-08 21:04:08 +01:00
if ( i ! = channel_ct ) { // if CT don't use pwm_min and pwm_max
2021-06-11 17:14:12 +01:00
cur_col = cur_col > 0 ? changeUIntScale ( cur_col , 0 , Settings - > pwm_range , Light . pwm_min , Light . pwm_max ) : 0 ; // shrink to the range of pwm_min..pwm_max
2020-04-15 18:42:50 +01:00
}
2022-01-24 21:48:57 +00:00
# ifdef ESP32
2023-05-17 07:54:22 +01:00
TasmotaGlobal . pwm_value [ i ] = cur_col ; // mark the new expected value
// AddLog(LOG_LEVEL_DEBUG_MORE, "analogWrite-%i 0x%03X", i, cur_col);
2022-01-24 21:48:57 +00:00
# else // ESP32
2023-07-14 08:10:20 +01:00
if ( ! Settings - > flag4 . zerocross_dimmer ) {
2024-01-07 14:10:19 +00:00
AnalogWrite ( Pin ( GPIO_PWM1 , i ) , bitRead ( TasmotaGlobal . pwm_inverted , i ) ? Settings - > pwm_range - cur_col : cur_col ) ;
2023-07-14 08:10:20 +01:00
// AddLog(LOG_LEVEL_DEBUG_MORE, "analogWrite-%i 0x%03X", bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - cur_col : cur_col);
}
2023-05-15 21:51:14 +01:00
# endif // ESP32
2018-12-17 16:34:55 +00:00
}
2017-08-12 16:55:20 +01:00
}
2022-01-27 20:30:05 +00:00
# ifdef ESP32
2022-03-08 21:49:12 +00:00
PwmApplyGPIO ( false ) ;
2022-01-27 20:30:05 +00:00
# endif // ESP32
2021-08-11 13:32:53 +01:00
# ifdef USE_PWM_DIMMER
// Animate brightness LEDs to follow PWM dimmer brightness
2022-05-17 14:11:56 +01:00
if ( PWM_DIMMER = = TasmotaGlobal . module_type ) {
TasmotaGlobal . pwm_dimmer_led_bri = change10to8 ( max_col ) ;
PWMDimmerSetBrightnessLeds ( - 1 ) ;
}
2021-08-11 13:32:53 +01:00
# endif // USE_PWM_DIMMER
2020-11-09 12:52:42 +00:00
}
// char msg[24];
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("LGT: Channels %s"), ToHex_P((const unsigned char *)cur_col_10, 10, msg, sizeof(msg)));
2019-12-31 13:23:34 +00:00
2020-11-09 12:52:42 +00:00
// Some devices need scaled RGB like Sonoff L1
uint32_t max = ( cur_col_10 [ 0 ] > cur_col_10 [ 1 ] & & cur_col_10 [ 0 ] > cur_col_10 [ 2 ] ) ? cur_col_10 [ 0 ] : ( cur_col_10 [ 1 ] > cur_col_10 [ 2 ] ) ? cur_col_10 [ 1 ] : cur_col_10 [ 2 ] ; // 0..1023
uint8_t scale_col [ 3 ] ;
for ( uint32_t i = 0 ; i < 3 ; i + + ) {
scale_col [ i ] = ( 0 = = max ) ? 255 : changeUIntScale ( cur_col_10 [ i ] , 0 , max , 0 , 255 ) ;
}
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("LGT: CurCol %03X %03X %03X, ScaleCol %02X %02X %02X, Max %02X"),
2020-11-09 12:52:42 +00:00
// cur_col_10[0], cur_col_10[1], cur_col_10[2], scale_col[0], scale_col[1], scale_col[2], max);
2019-11-23 18:18:09 +00:00
2020-11-09 12:52:42 +00:00
uint8_t cur_col [ LST_MAX ] ;
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
cur_col [ i ] = change10to8 ( cur_col_10 [ i ] ) ;
2020-11-08 16:49:13 +00:00
}
2020-11-09 12:52:42 +00:00
char * tmp_data = XdrvMailbox . data ;
char * tmp_topic = XdrvMailbox . topic ;
2022-05-29 15:34:58 +01:00
char * tmp_command = XdrvMailbox . command ;
2020-11-09 12:52:42 +00:00
XdrvMailbox . data = ( char * ) cur_col ;
XdrvMailbox . topic = ( char * ) scale_col ;
2022-05-29 15:34:58 +01:00
XdrvMailbox . command = ( char * ) cur_col_10 ;
2022-11-13 17:22:39 +00:00
# ifdef USE_LIGHT_ARTNET
if ( ArtNetSetChannels ( ) ) { /* Serviced */ }
else
# endif
2020-11-09 12:52:42 +00:00
if ( XlgtCall ( FUNC_SET_CHANNELS ) ) { /* Serviced */ }
else if ( XdrvCall ( FUNC_SET_CHANNELS ) ) { /* Serviced */ }
XdrvMailbox . data = tmp_data ;
XdrvMailbox . topic = tmp_topic ;
2022-05-29 15:34:58 +01:00
XdrvMailbox . command = tmp_command ;
2017-02-19 16:49:17 +00:00
}
2019-08-06 09:57:50 +01:00
// Just apply basic Gamma to each channel
2019-12-27 20:02:23 +00:00
void calcGammaMultiChannels ( uint16_t cur_col_10 [ 5 ] ) {
2019-08-06 09:57:50 +01:00
// Apply gamma correction for 8 and 10 bits resolutions, if needed
2021-06-11 17:14:12 +01:00
if ( Settings - > light_correction ) {
2019-08-06 09:57:50 +01:00
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
2019-12-27 20:02:23 +00:00
cur_col_10 [ i ] = ledGamma10_10 ( cur_col_10 [ i ] ) ;
2019-08-06 09:57:50 +01:00
}
}
}
2020-12-29 18:31:27 +00:00
//
// Compute the Gamma correction for CW/WW
// Can be used for 2-channels (channels 0,1) or 5 channels (channels 3,4)
//
// It is implicitly called by calcGammaBulb5Channels()
//
// In:
// - 2 channels CW/WW in 10 bits format (0..1023)
// Out:
// - 2 channels CW/WW in 10 bits format, with Gamma corretion (if enabled), replaced in place
// - white_bri10: global brightness of white channel, split over CW/WW (basically the sum of CW+WW, but it's easier to compute on this basis)
// - white_free_cw: signals that CW/WW are free mode, and not linked via CT. This is used when channels are manually set on a channel per channel basis. CT is ignored
//
void calcGammaBulbCW ( uint16_t cw10 [ 2 ] , uint16_t * white_bri10_out , bool * white_free_cw_out ) {
uint16_t white_bri10 = cw10 [ 0 ] + cw10 [ 1 ] ; // cumulated brightness
bool white_free_cw = ( white_bri10 > 1031 ) ; // take a margin of 8 above 1023 to account for rounding errors
white_bri10 = ( white_bri10 > 1023 ) ? 1023 : white_bri10 ; // max 1023
2020-02-01 13:23:13 +00:00
2021-06-11 17:14:12 +01:00
if ( Settings - > light_correction ) {
2020-12-29 18:31:27 +00:00
if ( white_free_cw ) {
cw10 [ 0 ] = ledGamma10_10 ( cw10 [ 0 ] ) ;
cw10 [ 1 ] = ledGamma10_10 ( cw10 [ 1 ] ) ;
} else {
uint16_t white_bri10_gamma = ledGamma10_10 ( white_bri10 ) ; // gamma corrected white
// now distributed among both channels
cw10 [ 0 ] = changeUIntScale ( cw10 [ 0 ] , 0 , white_bri10 , 0 , white_bri10_gamma ) ;
cw10 [ 1 ] = changeUIntScale ( cw10 [ 1 ] , 0 , white_bri10 , 0 , white_bri10_gamma ) ;
// now use white_bri10_gamma as a reference
white_bri10 = white_bri10_gamma ;
}
}
if ( white_bri10_out ! = nullptr ) { * white_bri10_out = white_bri10 ; }
if ( white_free_cw_out ! = nullptr ) { * white_free_cw_out = white_free_cw ; }
}
//
// Calculate the gamma correction for all 5 channels RGBCW
// Computation is valid for 1,3,4,5 channels
// 2-channels bulbs must be handled separately
//
// In:
// - 5 channels RGBCW in 10 bits format (0..1023)
// Out:
// - 5 channels RGBCW in 10 bits format, with Gamma corretion (if enabled), replaced in place
// - white_bri10: global brightness of white channel, split over CW/WW (basically the sum of CW+WW, but it's easier to compute on this basis)
// - white_free_cw: signals that CW/WW are free mode, and not linked via CT. This is used when channels are manually set on a channel per channel basis. CT is ignored
//
void calcGammaBulb5Channels ( uint16_t col10 [ LST_MAX ] , uint16_t * white_bri10_out , bool * white_free_cw ) {
for ( uint32_t i = 0 ; i < 3 ; i + + ) {
2021-06-11 17:14:12 +01:00
if ( Settings - > light_correction ) {
2020-12-29 18:31:27 +00:00
col10 [ i ] = ledGamma10_10 ( col10 [ i ] ) ;
}
}
calcGammaBulbCW ( & col10 [ 3 ] , white_bri10_out , white_free_cw ) ;
}
// sale but converts from 8 bits to 10 bits first
void calcGammaBulb5Channels_8 ( uint8_t in8 [ LST_MAX ] , uint16_t col10 [ LST_MAX ] ) {
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
col10 [ i ] = change8to10 ( in8 [ i ] ) ;
}
calcGammaBulb5Channels ( col10 , nullptr , nullptr ) ;
}
2023-06-01 21:12:57 +01:00
void calcGammaBulbs ( uint16_t cur_col_10 [ 5 ] ) {
2020-12-29 18:31:27 +00:00
bool rgbwwtable_applied_white = false ;
bool white_free_cw = false ; // true if White channels are uncorrelated. Happens when CW+WW>255, i.e. manually setting white channels to exceed to total power of a single channel (may harm the power supply)
// Various values needed for accurate White calculation
// CT value streteched to 0..1023 (from within CT range, so not necessarily from 153 to 500). 0=Cold, 1023=Warm
uint16_t ct = light_state . getCT ( ) ;
uint16_t ct_10 = changeUIntScale ( ct , Light . vct_ct [ 0 ] , Light . vct_ct [ CT_PIVOTS - 1 ] , 0 , 1023 ) ;
uint16_t white_bri10 = 0 ; // White total brightness normalized to 0..1023
// uint32_t cw1 = Light.subtype - 1; // address for the ColorTone PWM
uint32_t cw0 = Light . subtype - 2 ; // address for the White Brightness PWM
// calc basic gamma correction for all types
if ( ( LST_SINGLE = = Light . subtype ) | | ( LST_RGB < = Light . subtype ) ) {
calcGammaBulb5Channels ( cur_col_10 , & white_bri10 , & white_free_cw ) ;
} else if ( LST_COLDWARM = = Light . subtype ) {
calcGammaBulbCW ( cur_col_10 , & white_bri10 , & white_free_cw ) ;
}
2023-06-01 21:12:57 +01:00
// Now see if we need to mix RGB and White
2020-12-29 18:31:27 +00:00
// Valid only for LST_RGBW, LST_RGBCW, SetOption105 1, and white is zero (see doc)
2021-06-11 17:14:12 +01:00
if ( ( LST_RGBW < = Light . subtype ) & & ( Settings - > flag4 . white_blend_mode ) & & ( 0 = = cur_col_10 [ 3 ] + cur_col_10 [ 4 ] ) ) {
2020-12-29 18:31:27 +00:00
uint32_t min_rgb_10 = min3 ( cur_col_10 [ 0 ] , cur_col_10 [ 1 ] , cur_col_10 [ 2 ] ) ;
cur_col_10 [ 0 ] - = min_rgb_10 ;
cur_col_10 [ 1 ] - = min_rgb_10 ;
cur_col_10 [ 2 ] - = min_rgb_10 ;
// Add to white level
2021-06-11 17:14:12 +01:00
uint32_t adjust_w_10 = change8to10 ( Settings - > rgbwwTable [ 3 ] ) ; // take the correction factor, bought back to 10 bits
2020-12-29 18:31:27 +00:00
white_bri10 + = changeUIntScale ( min_rgb_10 , 0 , 1023 , 0 , adjust_w_10 ) ; // set white power down corrected with rgbwwTable[3]
white_bri10 = ( white_bri10 > 1023 ) ? 1023 : white_bri10 ; // max 1023
rgbwwtable_applied_white = true ;
}
2021-01-01 12:44:04 +00:00
2020-12-29 18:31:27 +00:00
# ifdef USE_LIGHT_VIRTUAL_CT
// compute virtual CT, which is suppsed to be compatible with white_blend_mode
if ( Light . virtual_ct & & ( ! white_free_cw ) & & ( LST_RGBW < = Light . subtype ) ) { // any light with a white channel
vct_pivot_t * pivot = & Light . vct_color [ 0 ] ;
uint16_t * from_ct = & Light . vct_ct [ 0 ] ;
for ( uint32_t i = 1 ; i < CT_PIVOTS - 1 ; i + + ) {
if ( ct > Light . vct_ct [ i ] ) { // if above mid-point, take range [1]..[2] instead of [0]..[1]
pivot + + ;
from_ct + + ;
}
}
uint16_t from10 [ LST_MAX ] ;
uint16_t to10 [ LST_MAX ] ;
calcGammaBulb5Channels_8 ( * pivot , from10 ) ;
calcGammaBulb5Channels_8 ( * ( pivot + 1 ) , to10 ) ;
2022-06-03 21:57:05 +01:00
// vct_pivot_t *pivot1 = pivot + 1;
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_INFO, PSTR("+++ from_ct %d, to_ct %d [%03X,%03X,%03X,%03X,%03X] - [%03X,%03X,%03X,%03X,%03X]"),
2020-12-29 18:31:27 +00:00
// *from_ct, *(from_ct+1), (*pivot)[0], (*pivot)[1], (*pivot)[2], (*pivot)[3], (*pivot)[4],
// (*pivot1)[0], (*pivot1)[1], (*pivot1)[2], (*pivot1)[3], (*pivot1)[4]);
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_INFO, PSTR("+++ from10 [%03X,%03X,%03X,%03X,%03X] - to 10 [%03X,%03X,%03X,%03X,%03X]"),
2020-12-29 18:31:27 +00:00
// from10[0],from10[0],from10[0],from10[0],from10[4],
// to10[0],to10[0],to10[0],to10[0],to10[4]);
// set both CW/WW to zero since their previous value don't count anymore
cur_col_10 [ 3 ] = 0 ;
cur_col_10 [ 4 ] = 0 ;
// Add the interpolated point to each component
for ( uint32_t i = 0 ; i < LST_MAX ; i + + ) {
cur_col_10 [ i ] + = changeUIntScale ( changeUIntScale ( ct , * from_ct , * ( from_ct + 1 ) , from10 [ i ] , to10 [ i ] ) , 0 , 1023 , 0 , white_bri10 ) ;
if ( cur_col_10 [ i ] > 1023 ) { cur_col_10 [ i ] = 1023 ; }
2019-08-06 09:57:50 +01:00
}
2020-12-29 18:31:27 +00:00
} else
# endif // USE_LIGHT_VIRTUAL_CT
// compute the actual levels for CW/WW
// We know ct_10 and white_bri_10 (which may be Gamma corrected)
// cur_col_10[cw0] and cur_col_10[cw1] were unmodified up to now
if ( LST_RGBW = = Light . subtype ) {
cur_col_10 [ 3 ] = white_bri10 ; // simple case, we set the White level to the required brightness
} else if ( ( LST_COLDWARM = = Light . subtype ) | | ( LST_RGBCW = = Light . subtype ) ) {
// if sum of both channels is > 255, then channels are probably uncorrelated
2021-01-01 12:44:04 +00:00
if ( ! white_free_cw ) {
2020-12-29 18:31:27 +00:00
// then we split the total energy among the cold and warm leds
cur_col_10 [ cw0 + 1 ] = changeUIntScale ( ct_10 , 0 , 1023 , 0 , white_bri10 ) ;
cur_col_10 [ cw0 ] = white_bri10 - cur_col_10 [ cw0 + 1 ] ;
2019-08-06 09:57:50 +01:00
}
}
2023-06-01 21:12:57 +01:00
// Apply RGBWWTable (RGB: always, CW: only if white_blend_mode is not engaged)
for ( uint32_t i = 0 ; i < ( rgbwwtable_applied_white ? 3 : Light . subtype ) ; i + + ) {
uint32_t adjust = change8to10 ( Settings - > rgbwwTable [ i ] ) ;
cur_col_10 [ i ] = changeUIntScale ( cur_col_10 [ i ] , 0 , 1023 , 0 , adjust ) ;
}
// Implement SO92: Some lights like Xiaomi Philips bulbs follow the scheme
// cw0=intensity, cw0+1=temperature
if ( ChannelCT ( ) > = 0 ) {
// Need to compute white_bri10 and ct_10 from cur_col_10[] for compatibility with VirtualCT
white_bri10 = cur_col_10 [ cw0 ] + cur_col_10 [ cw0 + 1 ] ;
if ( white_bri10 > 1023 ) {
// In white_free_cw mode, the combined brightness of cw and ww may be larger than 1023.
// This cannot be represented in pwm_ct_mode, so we set the maximum brightness instead.
white_bri10 = 1023 ;
}
cur_col_10 [ cw0 ] = white_bri10 ;
cur_col_10 [ cw0 + 1 ] = ct_10 ;
}
2019-08-06 09:57:50 +01:00
}
2020-02-21 15:09:21 +00:00
# ifdef USE_DEVICE_GROUPS
2021-02-09 03:28:59 +00:00
void LightSendDeviceGroupStatus ( )
2020-02-21 15:09:21 +00:00
{
2020-04-04 00:24:48 +01:00
static uint8_t last_bri ;
uint8_t bri = light_state . getBri ( ) ;
2021-02-09 03:28:59 +00:00
bool send_bri_update = ( building_status_message | | bri ! = last_bri ) ;
2020-11-07 23:37:06 +00:00
if ( Light . subtype > LST_SINGLE ) {
2021-02-09 03:28:59 +00:00
static uint8_t last_channels [ LST_MAX + 1 ] = { 0 , 0 , 0 , 0 , 0 , 0 } ;
uint8_t channels [ LST_MAX ] ;
light_state . getChannelsRaw ( channels ) ;
uint8_t color_mode = light_state . getColorMode ( ) ;
if ( ! ( color_mode & LCM_RGB ) ) channels [ 0 ] = channels [ 1 ] = channels [ 2 ] = 0 ;
if ( ! ( color_mode & LCM_CT ) ) channels [ 3 ] = channels [ 4 ] = 0 ;
if ( building_status_message | | memcmp ( channels , last_channels , LST_MAX ) ) {
memcpy ( last_channels , channels , LST_MAX ) ;
last_channels [ LST_MAX ] + + ;
2021-02-09 21:10:32 +00:00
SendDeviceGroupMessage ( Light . device , ( send_bri_update ? DGR_MSGTYP_PARTIAL_UPDATE : DGR_MSGTYP_UPDATE ) , DGR_ITEM_LIGHT_CHANNELS , last_channels ) ;
2020-04-04 00:24:48 +01:00
}
}
if ( send_bri_update ) {
last_bri = bri ;
2021-02-09 21:10:32 +00:00
SendDeviceGroupMessage ( Light . device , DGR_MSGTYP_UPDATE , DGR_ITEM_LIGHT_BRI , light_state . getBri ( ) ) ;
2020-03-13 17:08:44 +00:00
}
2020-02-21 15:09:21 +00:00
}
2020-04-06 18:29:50 +01:00
void LightHandleDevGroupItem ( void )
2020-02-21 15:09:21 +00:00
{
static bool send_state = false ;
2020-03-13 17:08:44 +00:00
static bool restore_power = false ;
2020-10-20 02:12:41 +01:00
2021-06-11 17:14:12 +01:00
if ( Settings - > flag4 . multiple_device_groups ? Settings - > device_group_tie [ * XdrvMailbox . topic ] ! = Light . device : ! ( XdrvMailbox . index & DGR_FLAG_LOCAL ) ) return ;
2020-10-20 02:12:41 +01:00
bool more_to_come ;
uint32_t value = XdrvMailbox . payload ;
2020-02-21 15:09:21 +00:00
switch ( XdrvMailbox . command_code ) {
case DGR_ITEM_EOL :
2020-03-13 17:08:44 +00:00
more_to_come = ( XdrvMailbox . index & DGR_FLAG_MORE_TO_COME ) ;
2020-11-07 23:37:06 +00:00
if ( more_to_come ) {
TasmotaGlobal . skip_light_fade = true ;
}
else if ( restore_power ) {
2020-03-13 17:08:44 +00:00
restore_power = false ;
Light . power = Light . old_power ;
}
2020-02-21 15:09:21 +00:00
LightAnimate ( ) ;
2020-03-13 17:08:44 +00:00
2021-02-09 03:28:59 +00:00
TasmotaGlobal . skip_light_fade = false ;
2020-03-13 17:08:44 +00:00
if ( send_state & & ! more_to_come ) {
2020-02-21 15:09:21 +00:00
light_controller . saveSettings ( ) ;
2021-06-11 17:14:12 +01:00
if ( Settings - > flag3 . hass_tele_on_power ) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
2020-02-21 15:09:21 +00:00
MqttPublishTeleState ( ) ;
}
send_state = false ;
}
break ;
case DGR_ITEM_LIGHT_BRI :
if ( light_state . getBri ( ) ! = value ) {
2020-03-13 17:08:44 +00:00
light_state . setBri ( value ) ;
2021-06-11 17:14:12 +01:00
Settings - > light_dimmer = light_state . BriToDimmer ( value ) ;
2020-02-21 15:09:21 +00:00
send_state = true ;
}
break ;
case DGR_ITEM_LIGHT_SCHEME :
2021-06-11 17:14:12 +01:00
if ( Settings - > light_scheme ! = value ) {
2023-05-27 11:33:50 +01:00
Light . last_dgr_scheme = Settings - > light_scheme = value ;
2020-04-04 00:24:48 +01:00
Light . devgrp_no_channels_out = ( value ! = 0 ) ;
2020-02-21 15:09:21 +00:00
send_state = true ;
}
break ;
case DGR_ITEM_LIGHT_CHANNELS :
2020-04-25 23:49:34 +01:00
{
2021-02-09 03:28:59 +00:00
uint8_t bri = light_state . getBri ( ) ;
# ifdef USE_DGR_LIGHT_SEQUENCE
2020-04-25 23:49:34 +01:00
static uint8_t last_sequence = 0 ;
// If a sequence offset is set, set the channels to the ones we received <SequenceOffset>
// changes ago.
if ( Light . sequence_offset ) {
2021-05-08 05:01:57 +01:00
light_controller . changeChannels ( Light . channels_fifo , true ) ;
2020-04-25 23:49:34 +01:00
// Shift the fifo down and load the newly received channels at the end for this update and
// any updates we missed.
int last_entry = ( Light . sequence_offset - 1 ) * LST_MAX ;
for ( uint8_t sequence = ( uint8_t ) XdrvMailbox . data [ LST_MAX ] ; ( uint8_t ) ( sequence - last_sequence ) > 0 ; last_sequence + + ) {
memmove ( Light . channels_fifo , & Light . channels_fifo [ LST_MAX ] , last_entry ) ;
memcpy ( & Light . channels_fifo [ last_entry ] , XdrvMailbox . data , LST_MAX ) ;
}
}
2021-02-09 03:28:59 +00:00
else
2020-04-25 23:49:34 +01:00
# endif // USE_DGR_LIGHT_SEQUENCE
2021-05-08 05:01:57 +01:00
light_controller . changeChannels ( ( uint8_t * ) XdrvMailbox . data , true ) ;
2021-02-09 03:28:59 +00:00
light_controller . changeBri ( bri ) ;
2020-04-25 23:49:34 +01:00
}
2020-02-21 15:09:21 +00:00
send_state = true ;
break ;
case DGR_ITEM_LIGHT_FIXED_COLOR :
2020-10-07 21:06:50 +01:00
if ( Light . subtype > = LST_COLDWARM ) {
2020-04-13 23:55:12 +01:00
send_state = true ;
2020-04-13 05:17:25 +01:00
# ifdef USE_LIGHT_PALETTE
2020-04-13 23:55:12 +01:00
if ( Light . palette_count ) {
Light . wheel = value % Light . palette_count ;
LightSetPaletteEntry ( ) ;
break ;
}
# endif // !USE_LIGHT_PALETTE
2020-10-07 21:06:50 +01:00
if ( Light . subtype < = LST_COLDWARM ) {
value = value % ( MAX_FIXED_COLD_WARM - 1 ) + 201 ;
2020-02-21 15:09:21 +00:00
}
else {
2020-10-07 21:06:50 +01:00
uint32_t max = MAX_FIXED_COLOR ;
if ( Light . subtype > = LST_RGB ) {
max + + ;
if ( Light . subtype > = LST_RGBCW ) max + = ( MAX_FIXED_COLD_WARM - 2 ) ;
}
value = value % max + 1 ;
if ( value > MAX_FIXED_COLOR ) value + = 200 - MAX_FIXED_COLOR ;
2020-02-21 15:09:21 +00:00
}
2020-10-07 21:06:50 +01:00
Light . fixed_color_index = value ;
2021-06-11 17:14:12 +01:00
bool save_decimal_text = Settings - > flag . decimal_text ;
2020-10-07 21:06:50 +01:00
char str [ 16 ] ;
LightColorEntry ( str , sprintf_P ( str , PSTR ( " %u " ) , value ) ) ;
2021-06-11 17:14:12 +01:00
Settings - > flag . decimal_text = save_decimal_text ;
2020-10-07 21:06:50 +01:00
uint32_t old_bri = light_state . getBri ( ) ;
light_controller . changeChannels ( Light . entry_color ) ;
light_controller . changeBri ( old_bri ) ;
2021-02-28 14:11:29 +00:00
LightSetScheme ( LS_POWER ) ;
2020-03-13 17:08:44 +00:00
if ( ! restore_power & & ! Light . power ) {
Light . old_power = Light . power ;
Light . power = 0xff ;
restore_power = true ;
2020-02-21 15:09:21 +00:00
}
}
break ;
2020-03-05 23:51:22 +00:00
case DGR_ITEM_LIGHT_FADE :
2021-06-11 17:14:12 +01:00
if ( Settings - > light_fade ! = value ) {
Settings - > light_fade = value ;
2020-03-05 23:51:22 +00:00
send_state = true ;
}
break ;
2020-02-21 15:09:21 +00:00
case DGR_ITEM_LIGHT_SPEED :
2021-06-11 17:14:12 +01:00
if ( Settings - > light_speed ! = value & & value > 0 & & value < = 40 ) {
Settings - > light_speed = value ;
2020-02-21 15:09:21 +00:00
send_state = true ;
}
break ;
case DGR_ITEM_STATUS :
2021-06-11 17:14:12 +01:00
SendDeviceGroupMessage ( Light . device , DGR_MSGTYP_PARTIAL_UPDATE , DGR_ITEM_LIGHT_FADE , Settings - > light_fade ,
DGR_ITEM_LIGHT_SPEED , Settings - > light_speed , DGR_ITEM_LIGHT_SCHEME , Settings - > light_scheme ) ;
2021-02-09 03:28:59 +00:00
LightSendDeviceGroupStatus ( ) ;
2020-02-21 15:09:21 +00:00
break ;
}
}
# endif // USE_DEVICE_GROUPS
2017-02-19 16:49:17 +00:00
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-08-01 14:47:00 +01:00
bool LightColorEntry ( char * buffer , uint32_t buffer_length )
2017-10-23 11:18:15 +01:00
{
char scolor [ 10 ] ;
char * p ;
char * str ;
2019-11-03 11:33:36 +00:00
uint32_t entry_type = 0 ; // Invalid
2019-08-17 12:17:30 +01:00
uint8_t value = Light . fixed_color_index ;
2020-04-13 23:55:12 +01:00
# ifdef USE_LIGHT_PALETTE
if ( Light . palette_count ) value = Light . wheel ;
# endif // USE_LIGHT_PALETTE
2017-10-23 11:18:15 +01:00
2017-10-24 13:57:10 +01:00
if ( buffer [ 0 ] = = ' # ' ) { // Optional hexadecimal entry
2017-10-23 11:18:15 +01:00
buffer + + ;
buffer_length - - ;
}
2017-11-17 16:52:31 +00:00
2019-08-17 12:17:30 +01:00
if ( Light . subtype > = LST_RGB ) {
2017-11-17 16:52:31 +00:00
char option = ( 1 = = buffer_length ) ? buffer [ 0 ] : ' \0 ' ;
2020-04-13 23:55:12 +01:00
if ( ' + ' = = option ) {
# ifdef USE_LIGHT_PALETTE
if ( Light . palette_count | | Light . fixed_color_index < MAX_FIXED_COLOR ) {
# else // USE_LIGHT_PALETTE
if ( Light . fixed_color_index < MAX_FIXED_COLOR ) {
# endif // !USE_LIGHT_PALETTE
value + + ;
}
2017-11-17 16:52:31 +00:00
}
2020-04-13 23:55:12 +01:00
else if ( ' - ' = = option ) {
# ifdef USE_LIGHT_PALETTE
if ( Light . palette_count | | Light . fixed_color_index > 1 ) {
# else // USE_LIGHT_PALETTE
if ( Light . fixed_color_index > 1 ) {
# endif // !USE_LIGHT_PALETTE
value - - ;
}
2017-11-17 16:52:31 +00:00
} else {
value = atoi ( buffer ) ;
}
2020-04-13 23:55:12 +01:00
# ifdef USE_LIGHT_PALETTE
if ( Light . palette_count ) value = value % Light . palette_count ;
# endif // USE_LIGHT_PALETTE
2017-11-17 16:52:31 +00:00
}
2019-08-17 12:17:30 +01:00
memset ( & Light . entry_color , 0x00 , sizeof ( Light . entry_color ) ) ;
2019-11-08 19:45:15 +00:00
// erase all channels except if the last character is '=', #6799
while ( ( buffer_length > 0 ) & & ( ' = ' = = buffer [ buffer_length - 1 ] ) ) {
2019-11-25 14:20:44 +00:00
buffer_length - - ; // remove all trailing '='
2019-11-08 19:45:15 +00:00
memcpy ( & Light . entry_color , & Light . current_color , sizeof ( Light . entry_color ) ) ;
}
2020-11-04 10:20:17 +00:00
if ( strchr ( buffer , ' , ' ) ! = nullptr ) { // Decimal entry
2017-10-23 11:18:15 +01:00
int8_t i = 0 ;
2019-03-26 17:26:50 +00:00
for ( str = strtok_r ( buffer , " , " , & p ) ; str & & i < 6 ; str = strtok_r ( nullptr , " , " , & p ) ) {
2019-05-02 21:50:19 +01:00
if ( i < LST_MAX ) {
2019-08-17 12:17:30 +01:00
Light . entry_color [ i + + ] = atoi ( str ) ;
2017-10-24 13:57:10 +01:00
}
2017-10-23 11:18:15 +01:00
}
2018-01-18 15:19:28 +00:00
entry_type = 2 ; // Decimal
2017-10-23 11:18:15 +01:00
}
2019-08-17 12:17:30 +01:00
else if ( ( ( 2 * Light . subtype ) = = buffer_length ) | | ( buffer_length > 3 ) ) { // Hexadecimal entry
for ( uint32_t i = 0 ; i < tmin ( ( uint ) ( buffer_length / 2 ) , sizeof ( Light . entry_color ) ) ; i + + ) {
2017-10-23 11:18:15 +01:00
strlcpy ( scolor , buffer + ( i * 2 ) , 3 ) ;
2019-08-17 12:17:30 +01:00
Light . entry_color [ i ] = ( uint8_t ) strtol ( scolor , & p , 16 ) ;
2017-10-23 11:18:15 +01:00
}
2017-10-24 13:57:10 +01:00
entry_type = 1 ; // Hexadecimal
2017-10-23 11:18:15 +01:00
}
2020-04-13 23:55:12 +01:00
# ifdef USE_LIGHT_PALETTE
else if ( Light . palette_count ) {
Light . wheel = value ;
2020-11-08 03:40:17 +00:00
memcpy_P ( & Light . entry_color , & Light . palette [ value * Light . subtype ] , Light . subtype ) ;
2020-04-13 23:55:12 +01:00
entry_type = 1 ; // Hexadecimal
}
# endif // USE_LIGHT_PALETTE
2019-08-17 12:17:30 +01:00
else if ( ( Light . subtype > = LST_RGB ) & & ( value > 0 ) & & ( value < = MAX_FIXED_COLOR ) ) {
Light . fixed_color_index = value ;
memcpy_P ( & Light . entry_color , & kFixedColor [ value - 1 ] , 3 ) ;
2017-11-15 22:07:45 +00:00
entry_type = 1 ; // Hexadecimal
}
else if ( ( value > 199 ) & & ( value < = 199 + MAX_FIXED_COLD_WARM ) ) {
2019-08-17 12:17:30 +01:00
if ( LST_RGBW = = Light . subtype ) {
memcpy_P ( & Light . entry_color [ 3 ] , & kFixedWhite [ value - 200 ] , 1 ) ;
2019-11-03 11:33:36 +00:00
entry_type = 1 ; // Hexadecimal
2018-09-04 21:43:27 +01:00
}
2019-08-17 12:17:30 +01:00
else if ( LST_COLDWARM = = Light . subtype ) {
memcpy_P ( & Light . entry_color , & kFixedColdWarm [ value - 200 ] , 2 ) ;
2019-11-03 11:33:36 +00:00
entry_type = 1 ; // Hexadecimal
2017-11-17 16:52:31 +00:00
}
2020-01-04 10:01:44 +00:00
else if ( LST_RGBCW = = Light . subtype ) {
2019-08-17 12:17:30 +01:00
memcpy_P ( & Light . entry_color [ 3 ] , & kFixedColdWarm [ value - 200 ] , 2 ) ;
2019-11-03 11:33:36 +00:00
entry_type = 1 ; // Hexadecimal
2017-11-17 16:52:31 +00:00
}
2017-11-15 22:07:45 +00:00
}
2020-11-04 16:42:30 +00:00
// Too much magic so removed since 9.0.0.3
// if (entry_type) {
2021-06-11 17:14:12 +01:00
// Settings->flag.decimal_text = entry_type -1; // SetOption17 - Switch between decimal or hexadecimal output
2020-11-04 16:42:30 +00:00
// }
2017-10-23 11:18:15 +01:00
return ( entry_type ) ;
}
2017-10-26 15:33:33 +01:00
/********************************************************************************************/
2019-08-01 14:47:00 +01:00
void CmndSupportColor ( void )
2017-02-19 16:49:17 +00:00
{
2019-01-28 13:08:33 +00:00
bool valid_entry = false ;
2019-08-01 14:47:00 +01:00
bool coldim = false ;
2019-04-25 12:06:35 +01:00
2019-08-01 14:47:00 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2020-04-13 23:55:12 +01:00
valid_entry = LightColorEntry ( XdrvMailbox . data , XdrvMailbox . data_len ) ;
2019-08-01 14:47:00 +01:00
if ( valid_entry ) {
if ( XdrvMailbox . index < = 2 ) { // Color(1), 2
2020-04-13 05:17:25 +01:00
# ifdef USE_LIGHT_PALETTE
2020-04-13 23:55:12 +01:00
if ( Light . palette_count & & XdrvMailbox . index = = 2 ) {
2020-04-13 05:17:25 +01:00
LightSetPaletteEntry ( ) ;
2019-08-01 14:47:00 +01:00
}
2020-04-13 05:17:25 +01:00
else {
# endif // USE_LIGHT_PALETTE
uint32_t old_bri = light_state . getBri ( ) ;
2021-06-16 12:37:33 +01:00
uint32_t old_bri_rgb = light_state . getBriRGB ( ) ;
2020-04-13 05:17:25 +01:00
// change all channels to specified values
light_controller . changeChannels ( Light . entry_color ) ;
if ( 2 = = XdrvMailbox . index ) {
// If Color2, set back old brightness
2021-06-16 12:37:33 +01:00
if ( light_controller . isCTRGBLinked ( ) ) {
// RGB and white are linked, adjust brightness of all channels
LightSetBriScaled ( old_bri ) ;
} else {
// RGB and white are unlinked, adjust brightness only of RGB channels
LightSetBri ( Light . device , old_bri_rgb ) ;
}
2020-04-13 05:17:25 +01:00
}
# ifdef USE_LIGHT_PALETTE
}
# endif // USE_LIGHT_PALETTE
2021-02-28 14:11:29 +00:00
LightSetScheme ( LS_POWER ) ;
2019-08-01 14:47:00 +01:00
coldim = true ;
} else { // Color3, 4, 5 and 6
for ( uint32_t i = 0 ; i < LST_RGB ; i + + ) {
2021-06-11 17:14:12 +01:00
Settings - > ws_color [ XdrvMailbox . index - 3 ] [ i ] = Light . entry_color [ i ] ;
2017-10-23 11:18:15 +01:00
}
}
2017-09-08 11:57:08 +01:00
}
2019-08-01 14:47:00 +01:00
}
char scolor [ LIGHT_COLOR_SIZE ] ;
if ( ! valid_entry & & ( XdrvMailbox . index < = 2 ) ) {
2019-08-03 12:01:34 +01:00
ResponseCmndChar ( LightGetColor ( scolor ) ) ;
2019-08-01 14:47:00 +01:00
}
if ( XdrvMailbox . index > = 3 ) {
scolor [ 0 ] = ' \0 ' ;
for ( uint32_t i = 0 ; i < LST_RGB ; i + + ) {
2021-06-11 17:14:12 +01:00
if ( Settings - > flag . decimal_text ) { // SetOption17 - Switch between decimal or hexadecimal output
snprintf_P ( scolor , sizeof ( scolor ) , PSTR ( " %s%s%d " ) , scolor , ( i > 0 ) ? " , " : " " , Settings - > ws_color [ XdrvMailbox . index - 3 ] [ i ] ) ;
2019-08-01 14:47:00 +01:00
} else {
2021-06-11 17:14:12 +01:00
snprintf_P ( scolor , sizeof ( scolor ) , PSTR ( " %s%02X " ) , scolor , Settings - > ws_color [ XdrvMailbox . index - 3 ] [ i ] ) ;
2017-08-12 16:55:20 +01:00
}
2017-02-19 16:49:17 +00:00
}
2019-08-03 12:01:34 +01:00
ResponseCmndIdxChar ( scolor ) ;
2017-02-19 16:49:17 +00:00
}
2019-08-01 14:47:00 +01:00
if ( coldim ) {
2019-11-24 21:19:41 +00:00
LightPreparePower ( ) ; // no parameter, recalculate Power for all channels
2019-08-01 14:47:00 +01:00
}
}
void CmndColor ( void )
{
2020-01-22 15:14:03 +00:00
// Color - Show current RGBWW color state
// Color1 - Change color to RGBWW
// Color2 - Change color to RGBWW but retain brightness (=dimmer)
// Color3 - Change color to RGB of WS2812 Clock Second
// Color4 - Change color to RGB of WS2812 Clock Minute
// Color5 - Change color to RGB of WS2812 Clock Hour
// Color6 - Change color to RGB of WS2812 Clock Marker
2019-08-17 12:17:30 +01:00
if ( ( Light . subtype > LST_SINGLE ) & & ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = 6 ) ) {
2019-08-01 14:47:00 +01:00
CmndSupportColor ( ) ;
}
}
void CmndWhite ( void )
{
2020-01-22 15:14:03 +00:00
// White - Show current White (=Dimmer2) state
// White 0..100 - Set White colors dimmer state
2019-12-28 21:32:08 +00:00
if ( Light . pwm_multi_channels ) { return ; }
2019-12-28 21:59:20 +00:00
if ( ( ( Light . subtype > = LST_RGBW ) | | ( LST_COLDWARM = = Light . subtype ) ) & & ( XdrvMailbox . index = = 1 ) ) {
2019-08-01 14:47:00 +01:00
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 100 ) ) {
2019-12-28 21:32:08 +00:00
light_controller . changeDimmer ( XdrvMailbox . payload , 2 ) ;
LightPreparePower ( 2 ) ;
2019-12-28 21:59:20 +00:00
} else {
ResponseCmndNumber ( light_state . getDimmer ( 2 ) ) ;
2019-08-01 14:47:00 +01:00
}
}
}
void CmndChannel ( void )
{
2020-01-22 15:14:03 +00:00
// Channel<x> - Show current Channel state
// Channel<x> 0..100 - Set Channel dimmer state
// Channel<x> + - Incerement Channel in steps of 10
// Channel<x> - - Decrement Channel in steps of 10
2019-08-17 12:17:30 +01:00
if ( ( XdrvMailbox . index > = Light . device ) & & ( XdrvMailbox . index < Light . device + Light . subtype ) ) {
2019-11-24 21:19:41 +00:00
uint32_t light_index = XdrvMailbox . index - Light . device ;
power_t coldim = 0 ; // bit flag to update
2019-10-31 10:12:00 +00:00
// Handle +/- special command
if ( 1 = = XdrvMailbox . data_len ) {
2019-11-24 21:19:41 +00:00
uint8_t channel = changeUIntScale ( Light . current_color [ light_index ] , 0 , 255 , 0 , 100 ) ;
2019-10-31 10:12:00 +00:00
if ( ' + ' = = XdrvMailbox . data [ 0 ] ) {
XdrvMailbox . payload = ( channel > 89 ) ? 100 : channel + 10 ;
} else if ( ' - ' = = XdrvMailbox . data [ 0 ] ) {
XdrvMailbox . payload = ( channel < 11 ) ? 1 : channel - 10 ;
}
}
2018-03-20 15:28:18 +00:00
// Set "Channel" directly - this allows Color and Direct PWM control to coexist
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 100 ) ) {
2019-11-24 21:19:41 +00:00
Light . current_color [ light_index ] = changeUIntScale ( XdrvMailbox . payload , 0 , 100 , 0 , 255 ) ;
2019-08-17 12:17:30 +01:00
if ( Light . pwm_multi_channels ) {
2019-11-24 21:19:41 +00:00
coldim = 1 < < light_index ; // change the specified channel
2019-08-06 09:57:50 +01:00
} else {
2019-11-24 21:19:41 +00:00
if ( light_controller . isCTRGBLinked ( ) ) {
// if we change channels 1,2,3 then turn off CT mode (unless non-linked)
if ( ( light_index < 3 ) & & ( light_controller . isCTRGBLinked ( ) ) ) {
Light . current_color [ 3 ] = Light . current_color [ 4 ] = 0 ;
} else {
Light . current_color [ 0 ] = Light . current_color [ 1 ] = Light . current_color [ 2 ] = 0 ;
}
coldim = 1 ;
} else {
if ( light_index < 3 ) { coldim = 1 ; } // RGB
else { coldim = 2 ; } // CT
2019-08-06 09:57:50 +01:00
}
2019-04-25 12:06:35 +01:00
}
2019-08-17 12:17:30 +01:00
light_controller . changeChannels ( Light . current_color ) ;
2018-03-20 15:28:18 +00:00
}
2019-11-24 21:19:41 +00:00
ResponseCmndIdxNumber ( changeUIntScale ( Light . current_color [ light_index ] , 0 , 255 , 0 , 100 ) ) ;
2019-08-01 14:47:00 +01:00
if ( coldim ) {
2019-11-24 21:19:41 +00:00
LightPreparePower ( coldim ) ;
2019-08-01 14:47:00 +01:00
}
2018-03-20 15:28:18 +00:00
}
2019-08-01 14:47:00 +01:00
}
void CmndHsbColor ( void )
{
2020-01-22 15:14:03 +00:00
// HsbColor - Show current HSB
// HsbColor 360,100,100 - Set Hue, Saturation and Brighthness
// HsbColor 360,100 - Set Hue and Saturation
// HsbColor 360 - Set Hue
// HsbColor1 360 - Set Hue
// HsbColor2 100 - Set Saturation
// HsbColor3 100 - Set Brightness
2019-08-17 12:17:30 +01:00
if ( Light . subtype > = LST_RGB ) {
2020-01-22 15:14:03 +00:00
if ( XdrvMailbox . data_len > 0 ) {
2020-01-15 11:14:47 +00:00
uint16_t c_hue ;
uint8_t c_sat ;
light_state . getHSB ( & c_hue , & c_sat , nullptr ) ;
2020-01-22 15:14:03 +00:00
uint32_t HSB [ 3 ] ;
2020-01-15 11:14:47 +00:00
HSB [ 0 ] = c_hue ;
HSB [ 1 ] = c_sat ;
HSB [ 2 ] = light_state . getBriRGB ( ) ;
2020-01-22 15:14:03 +00:00
if ( ( 2 = = XdrvMailbox . index ) | | ( 3 = = XdrvMailbox . index ) ) {
if ( ( uint32_t ) XdrvMailbox . payload > 100 ) { XdrvMailbox . payload = 100 ; }
HSB [ XdrvMailbox . index - 1 ] = changeUIntScale ( XdrvMailbox . payload , 0 , 100 , 0 , 255 ) ;
} else {
uint32_t paramcount = ParseParameters ( 3 , HSB ) ;
if ( HSB [ 0 ] > 360 ) { HSB [ 0 ] = 360 ; }
for ( uint32_t i = 1 ; i < paramcount ; i + + ) {
2020-11-12 18:38:21 +00:00
if ( HSB [ i ] > 100 ) { HSB [ i ] = 100 ; }
2020-01-15 11:14:47 +00:00
HSB [ i ] = changeUIntScale ( HSB [ i ] , 0 , 100 , 0 , 255 ) ; // change sat and bri to 0..255
2018-08-28 02:46:04 +01:00
}
2018-03-20 15:28:18 +00:00
}
2020-01-22 15:14:03 +00:00
light_controller . changeHSB ( HSB [ 0 ] , HSB [ 1 ] , HSB [ 2 ] ) ;
LightPreparePower ( 1 ) ;
2018-03-20 15:28:18 +00:00
} else {
2020-07-13 14:10:23 +01:00
ResponseLightState ( 0 ) ;
2018-03-20 15:28:18 +00:00
}
}
2019-08-01 14:47:00 +01:00
}
void CmndScheme ( void )
{
2023-05-26 16:47:57 +01:00
// Scheme 0..14 - Select one of schemes 0 to 14
2020-01-22 15:14:03 +00:00
// Scheme 2 - Select scheme 2
// Scheme 2,0 - Select scheme 2 with color wheel set to 0 (HSB Red)
// Scheme + - Select next scheme
// Scheme - - Select previous scheme
2019-08-17 12:17:30 +01:00
if ( Light . subtype > = LST_RGB ) {
2019-10-08 16:46:03 +01:00
uint32_t max_scheme = Light . max_scheme ;
2019-08-01 14:47:00 +01:00
if ( 1 = = XdrvMailbox . data_len ) {
2021-06-11 17:14:12 +01:00
if ( ( ' + ' = = XdrvMailbox . data [ 0 ] ) & & ( Settings - > light_scheme < max_scheme ) ) {
XdrvMailbox . payload = Settings - > light_scheme + ( ( 0 = = Settings - > light_scheme ) ? 2 : 1 ) ; // Skip wakeup
2019-08-01 14:47:00 +01:00
}
2021-06-11 17:14:12 +01:00
else if ( ( ' - ' = = XdrvMailbox . data [ 0 ] ) & & ( Settings - > light_scheme > 0 ) ) {
XdrvMailbox . payload = Settings - > light_scheme - ( ( 2 = = Settings - > light_scheme ) ? 2 : 1 ) ; // Skip wakeup
2019-08-01 14:47:00 +01:00
}
2017-11-15 22:07:45 +00:00
}
2018-01-05 11:26:19 +00:00
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = max_scheme ) ) {
2020-01-22 10:55:48 +00:00
uint32_t parm [ 2 ] ;
if ( ParseParameters ( 2 , parm ) > 1 ) {
Light . wheel = parm [ 1 ] ;
2020-04-13 23:55:12 +01:00
# ifdef USE_LIGHT_PALETTE
Light . wheel - - ;
# endif // USE_LIGHT_PALETTE
2020-01-22 10:55:48 +00:00
}
2021-02-28 14:11:29 +00:00
LightSetScheme ( XdrvMailbox . payload ) ;
2021-06-11 17:14:12 +01:00
if ( LS_WAKEUP = = Settings - > light_scheme ) {
2019-08-17 12:17:30 +01:00
Light . wakeup_active = 3 ;
2017-09-16 16:34:03 +01:00
}
2017-10-25 13:27:30 +01:00
LightPowerOn ( ) ;
2019-08-17 12:17:30 +01:00
Light . strip_timer_counter = 0 ;
2018-12-12 19:32:10 +00:00
// Publish state message for Hass
2021-06-11 17:14:12 +01:00
if ( Settings - > flag3 . hass_tele_on_power ) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
2019-11-03 12:51:22 +00:00
MqttPublishTeleState ( ) ;
}
2017-09-16 16:34:03 +01:00
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber ( Settings - > light_scheme ) ;
2017-09-16 16:34:03 +01:00
}
2019-08-01 14:47:00 +01:00
}
void CmndWakeup ( void )
{
2020-01-22 15:14:03 +00:00
// Wakeup - Start wakeup light
// Wakeup 0..100 - Start wakeup light to dimmer value 0..100
2019-08-01 14:47:00 +01:00
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 100 ) ) {
2020-01-10 19:54:13 +00:00
light_controller . changeDimmer ( XdrvMailbox . payload ) ;
2017-09-18 17:06:46 +01:00
}
2019-08-17 12:17:30 +01:00
Light . wakeup_active = 3 ;
2021-02-28 14:11:29 +00:00
LightSetScheme ( LS_WAKEUP ) ;
2019-08-01 14:47:00 +01:00
LightPowerOn ( ) ;
2021-01-18 20:48:04 +00:00
ResponseCmndChar ( PSTR ( D_JSON_STARTED ) ) ;
2019-08-01 14:47:00 +01:00
}
void CmndColorTemperature ( void )
{
2020-01-22 15:14:03 +00:00
// CT - Show current color temperature
// CT 153..500 - Set color temperature
// CT + - Incerement color temperature in steps of 34
// CT - - Decrement color temperature in steps of 34
2019-12-28 21:32:08 +00:00
if ( Light . pwm_multi_channels ) { return ; }
2020-01-04 10:01:44 +00:00
if ( ( LST_COLDWARM = = Light . subtype ) | | ( LST_RGBCW = = Light . subtype ) ) { // ColorTemp
2019-08-01 14:47:00 +01:00
uint32_t ct = light_state . getCT ( ) ;
if ( 1 = = XdrvMailbox . data_len ) {
if ( ' + ' = = XdrvMailbox . data [ 0 ] ) {
2020-01-04 10:01:44 +00:00
XdrvMailbox . payload = ( ct > ( CT_MAX - 34 ) ) ? CT_MAX : ct + 34 ;
2017-11-17 16:52:31 +00:00
}
2019-08-01 14:47:00 +01:00
else if ( ' - ' = = XdrvMailbox . data [ 0 ] ) {
2020-01-04 10:01:44 +00:00
XdrvMailbox . payload = ( ct < ( CT_MIN + 34 ) ) ? CT_MIN : ct - 34 ;
2017-11-17 16:52:31 +00:00
}
}
2020-01-04 10:01:44 +00:00
if ( ( XdrvMailbox . payload > = CT_MIN ) & & ( XdrvMailbox . payload < = CT_MAX ) ) { // https://developers.meethue.com/documentation/core-concepts
2020-01-16 13:37:10 +00:00
light_controller . changeCTB ( XdrvMailbox . payload , light_state . getBriCT ( ) ) ;
2019-11-24 21:19:41 +00:00
LightPreparePower ( 2 ) ;
2017-08-16 16:05:36 +01:00
} else {
2019-08-03 12:01:34 +01:00
ResponseCmndNumber ( ct ) ;
2017-08-16 16:05:36 +01:00
}
}
2019-08-01 14:47:00 +01:00
}
2020-07-13 14:10:23 +01:00
void LightDimmerOffset ( uint32_t index , int32_t offset ) {
int32_t dimmer = light_state . getDimmer ( index ) + offset ;
2021-06-11 17:14:12 +01:00
if ( dimmer < 1 ) { dimmer = Settings - > flag3 . slider_dimmer_stay_on ; } // SetOption77 - Do not power off if slider moved to far left
2020-06-30 15:58:36 +01:00
if ( dimmer > 100 ) { dimmer = 100 ; }
2020-07-13 14:10:23 +01:00
XdrvMailbox . index = index ;
2020-06-30 15:58:36 +01:00
XdrvMailbox . payload = dimmer ;
CmndDimmer ( ) ;
}
2022-04-15 05:51:30 +01:00
static int DimmerStep ( int dimmer )
{
int step ;
int plus = 1 ;
int payload ;
if ( 0 = = XdrvMailbox . data_len ) {
return - 1 ;
}
switch ( XdrvMailbox . data [ 0 ] ) {
case ' - ' :
plus = 0 ;
case ' + ' :
break ;
default :
return - 1 ;
}
if ( 1 = = XdrvMailbox . data_len ) {
step = Settings - > dimmer_step ;
} else {
char * ep ;
step = strtoul ( XdrvMailbox . data + 1 , & ep , 10 ) ;
if ( ' \0 ' ! = * ep | | 1 > step ) {
return - 1 ;
}
}
if ( plus ) {
payload = min ( 100 , dimmer + step ) ;
} else {
payload = max ( 1 , dimmer - step ) ;
}
return payload ;
}
2019-08-01 14:47:00 +01:00
void CmndDimmer ( void )
{
2020-01-22 15:14:03 +00:00
// Dimmer - Show current Dimmer state
// Dimmer0 0..100 - Change both RGB and W(W) Dimmers
// Dimmer1 0..100 - Change RGB Dimmer
// Dimmer2 0..100 - Change W(W) Dimmer
2020-03-05 23:51:22 +00:00
// Dimmer3 0..100 - Change both RGB and W(W) Dimmers with no fading
2020-10-30 13:49:36 +00:00
// Dimmer<x> + - Incerement Dimmer in steps of DimmerStep
// Dimmer<x> - - Decrement Dimmer in steps of DimmerStep
2019-10-31 10:12:00 +00:00
uint32_t dimmer ;
2020-03-05 23:51:22 +00:00
if ( XdrvMailbox . index = = 3 ) {
2020-10-30 11:29:48 +00:00
TasmotaGlobal . skip_light_fade = true ;
2020-03-05 23:51:22 +00:00
XdrvMailbox . index = 0 ;
}
2021-05-10 14:01:41 +01:00
else if ( XdrvMailbox . index > 4 ) {
2020-03-05 23:51:22 +00:00
XdrvMailbox . index = 1 ;
}
2019-11-03 11:33:36 +00:00
2019-11-02 17:32:46 +00:00
if ( ( light_controller . isCTRGBLinked ( ) ) | | ( 0 = = XdrvMailbox . index ) ) {
2019-10-31 10:12:00 +00:00
dimmer = light_state . getDimmer ( ) ;
2019-11-02 17:32:46 +00:00
} else {
dimmer = light_state . getDimmer ( XdrvMailbox . index ) ;
2019-10-31 10:12:00 +00:00
}
2021-03-09 06:35:38 +00:00
// Handle +/-/!/</> special commands
2022-04-15 05:51:30 +01:00
int payload = DimmerStep ( dimmer ) ;
if ( - 1 ! = payload ) {
XdrvMailbox . payload = payload ;
} else if ( 1 = = XdrvMailbox . data_len ) {
if ( ' ! ' = = XdrvMailbox . data [ 0 ] & & Light . fade_running ) {
2021-03-09 06:35:38 +00:00
XdrvMailbox . payload = LightGetCurFadeBri ( ) ;
} else if ( ' < ' = = XdrvMailbox . data [ 0 ] ) {
XdrvMailbox . payload = 1 ;
} else if ( ' > ' = = XdrvMailbox . data [ 0 ] ) {
XdrvMailbox . payload = 100 ;
2017-11-15 22:07:45 +00:00
}
2017-02-19 16:49:17 +00:00
}
2019-10-31 10:12:00 +00:00
// If value is ok, change it, otherwise report old value
2019-08-01 14:47:00 +01:00
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 100 ) ) {
2019-11-02 17:32:46 +00:00
if ( light_controller . isCTRGBLinked ( ) ) {
// normal state, linked RGB and CW
2021-05-10 14:01:41 +01:00
if ( 4 = = XdrvMailbox . index ) {
// change both dimmers, retain ratio between white and color channels
uint32_t new_bri = changeUIntScale ( XdrvMailbox . payload , 0 , 100 , 0 , 255 ) ;
LightSetBriScaled ( new_bri ) ;
} else {
// change both dimmers
light_controller . changeDimmer ( XdrvMailbox . payload ) ;
}
2019-11-24 21:19:41 +00:00
LightPreparePower ( ) ;
2019-11-02 17:32:46 +00:00
} else {
if ( 0 ! = XdrvMailbox . index ) {
light_controller . changeDimmer ( XdrvMailbox . payload , XdrvMailbox . index ) ;
2019-11-24 21:19:41 +00:00
LightPreparePower ( 1 < < ( XdrvMailbox . index - 1 ) ) ; // recalculate only the target dimmer
2019-11-02 17:32:46 +00:00
} else {
// change both dimmers
light_controller . changeDimmer ( XdrvMailbox . payload , 1 ) ;
light_controller . changeDimmer ( XdrvMailbox . payload , 2 ) ;
2019-11-24 21:19:41 +00:00
LightPreparePower ( ) ;
2019-11-02 17:32:46 +00:00
}
2019-10-31 10:12:00 +00:00
}
2020-03-13 20:15:38 +00:00
# if defined(USE_PWM_DIMMER) && defined(USE_DEVICE_GROUPS)
2020-04-21 22:33:07 +01:00
uint8_t bri = light_state . getBri ( ) ;
2021-06-11 17:14:12 +01:00
if ( bri ! = Settings - > bri_power_on ) {
Settings - > bri_power_on = bri ;
SendDeviceGroupMessage ( Light . device , DGR_MSGTYP_PARTIAL_UPDATE , DGR_ITEM_BRI_POWER_ON , Settings - > bri_power_on ) ;
2020-04-21 22:33:07 +01:00
}
2020-03-13 20:15:38 +00:00
# endif // USE_PWM_DIMMER && USE_DEVICE_GROUPS
2019-08-17 12:17:30 +01:00
Light . update = true ;
2020-10-30 11:29:48 +00:00
if ( TasmotaGlobal . skip_light_fade ) LightAnimate ( ) ;
2019-08-01 14:47:00 +01:00
} else {
2019-10-31 10:12:00 +00:00
ResponseCmndNumber ( dimmer ) ;
2017-02-19 16:49:17 +00:00
}
2020-10-30 11:29:48 +00:00
TasmotaGlobal . skip_light_fade = false ;
2019-08-01 14:47:00 +01:00
}
2018-09-29 15:55:53 +01:00
2019-10-10 15:53:01 +01:00
void CmndDimmerRange ( void )
{
2020-01-22 15:14:03 +00:00
// DimmerRange - Show current dimmer range as used by Tuya and PS16DZ Dimmers
// DimmerRange 0,100 - Set dimmer hardware range from 0 to 100 and restart
2019-10-10 15:53:01 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2020-01-22 10:55:48 +00:00
uint32_t parm [ 2 ] ;
2021-06-11 17:14:12 +01:00
parm [ 0 ] = Settings - > dimmer_hw_min ;
parm [ 1 ] = Settings - > dimmer_hw_max ;
2020-01-22 10:55:48 +00:00
ParseParameters ( 2 , parm ) ;
2019-10-10 15:53:01 +01:00
if ( parm [ 0 ] < parm [ 1 ] ) {
2021-06-11 17:14:12 +01:00
Settings - > dimmer_hw_min = parm [ 0 ] ;
Settings - > dimmer_hw_max = parm [ 1 ] ;
2019-10-10 15:53:01 +01:00
} else {
2021-06-11 17:14:12 +01:00
Settings - > dimmer_hw_min = parm [ 1 ] ;
Settings - > dimmer_hw_max = parm [ 0 ] ;
2019-10-10 15:53:01 +01:00
}
2020-04-13 20:00:52 +01:00
LightCalcPWMRange ( ) ;
Light . update = true ;
2019-10-10 15:53:01 +01:00
}
2021-06-11 17:14:12 +01:00
Response_P ( PSTR ( " { \" " D_CMND_DIMMER_RANGE " \" :{ \" Min \" :%d, \" Max \" :%d}} " ) , Settings - > dimmer_hw_min , Settings - > dimmer_hw_max ) ;
2019-10-10 15:53:01 +01:00
}
2020-10-30 13:49:36 +00:00
void CmndDimmerStep ( void )
{
// DimmerStep - Show current dimmer step as used by Dimmer +/-
2020-11-04 10:02:34 +00:00
// DimmerStep 1..50 - Set dimmer step
2020-10-30 13:49:36 +00:00
if ( XdrvMailbox . data_len > 0 ) {
2020-11-04 18:05:19 +00:00
if ( XdrvMailbox . payload < 1 ) {
2021-06-11 17:14:12 +01:00
Settings - > dimmer_step = 1 ;
2020-11-04 18:05:19 +00:00
} else if ( XdrvMailbox . payload > 50 ) {
2021-06-11 17:14:12 +01:00
Settings - > dimmer_step = 50 ;
2020-10-30 13:49:36 +00:00
} else {
2021-06-11 17:14:12 +01:00
Settings - > dimmer_step = XdrvMailbox . payload ;
2020-10-30 13:49:36 +00:00
}
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber ( Settings - > dimmer_step ) ;
2020-11-04 10:02:34 +00:00
}
2020-10-30 13:49:36 +00:00
2019-08-01 14:47:00 +01:00
void CmndLedTable ( void )
{
2020-01-22 15:14:03 +00:00
// LedTable - Show current LedTable state
// LedTable 0 - Turn LedTable Off
// LedTable On - Turn LedTable On
// LedTable Toggle - Toggle LedTable state
2019-08-01 14:47:00 +01:00
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 2 ) ) {
2018-01-05 11:26:19 +00:00
switch ( XdrvMailbox . payload ) {
2017-06-06 22:23:23 +01:00
case 0 : // Off
case 1 : // On
2021-06-11 17:14:12 +01:00
Settings - > light_correction = XdrvMailbox . payload ;
2017-06-06 22:23:23 +01:00
break ;
case 2 : // Toggle
2021-06-11 17:14:12 +01:00
Settings - > light_correction ^ = 1 ;
2017-06-06 22:23:23 +01:00
break ;
2017-02-19 16:49:17 +00:00
}
2020-04-13 20:00:52 +01:00
LightCalcPWMRange ( ) ;
2019-08-17 12:17:30 +01:00
Light . update = true ;
2017-02-19 16:49:17 +00:00
}
2021-06-11 17:14:12 +01:00
ResponseCmndStateText ( Settings - > light_correction ) ;
2019-08-01 14:47:00 +01:00
}
void CmndRgbwwTable ( void )
{
2020-01-22 15:14:03 +00:00
// RgbWwTable - Show current RGBWW State
// RgbWwTable 255,255,255,255,255 - Set RGBWW state to maximum
2019-08-01 14:47:00 +01:00
if ( ( XdrvMailbox . data_len > 0 ) ) {
2020-01-22 15:14:03 +00:00
uint32_t parm [ LST_RGBCW - 1 ] ;
uint32_t parmcount = ParseParameters ( LST_RGBCW , parm ) ;
for ( uint32_t i = 0 ; i < parmcount ; i + + ) {
2021-06-11 17:14:12 +01:00
Settings - > rgbwwTable [ i ] = parm [ i ] ;
2019-08-01 14:47:00 +01:00
}
2019-08-17 12:17:30 +01:00
Light . update = true ;
2019-08-01 14:47:00 +01:00
}
char scolor [ LIGHT_COLOR_SIZE ] ;
scolor [ 0 ] = ' \0 ' ;
2020-01-04 10:01:44 +00:00
for ( uint32_t i = 0 ; i < LST_RGBCW ; i + + ) {
2021-06-11 17:14:12 +01:00
snprintf_P ( scolor , sizeof ( scolor ) , PSTR ( " %s%s%d " ) , scolor , ( i > 0 ) ? " , " : " " , Settings - > rgbwwTable [ i ] ) ;
2019-08-01 14:47:00 +01:00
}
2020-01-22 15:14:03 +00:00
ResponseCmndChar ( scolor ) ;
2019-08-01 14:47:00 +01:00
}
void CmndFade ( void )
{
2021-02-11 15:03:04 +00:00
if ( 2 = = XdrvMailbox . index ) {
// Home Assistant backwards compatibility, can be removed mid 2021
} else {
// Fade - Show current Fade state
// Fade 0 - Turn Fade Off
// Fade On - Turn Fade On
// Fade Toggle - Toggle Fade state
switch ( XdrvMailbox . payload ) {
case 0 : // Off
case 1 : // On
2021-06-11 17:14:12 +01:00
Settings - > light_fade = XdrvMailbox . payload ;
2021-02-11 15:03:04 +00:00
break ;
case 2 : // Toggle
2021-06-11 17:14:12 +01:00
Settings - > light_fade ^ = 1 ;
2021-02-11 15:03:04 +00:00
break ;
}
# ifdef USE_DEVICE_GROUPS
2021-06-11 17:14:12 +01:00
if ( XdrvMailbox . payload > = 0 & & XdrvMailbox . payload < = 2 ) SendDeviceGroupMessage ( Light . device , DGR_MSGTYP_UPDATE , DGR_ITEM_LIGHT_FADE , Settings - > light_fade ) ;
2021-02-11 15:03:04 +00:00
# endif // USE_DEVICE_GROUPS
2021-10-09 16:42:32 +01:00
if ( ! Settings - > light_fade ) { LightStopFade ( ) ; }
2019-08-01 14:47:00 +01:00
}
2021-06-11 17:14:12 +01:00
ResponseCmndStateText ( Settings - > light_fade ) ;
2019-08-01 14:47:00 +01:00
}
void CmndSpeed ( void )
2020-01-22 15:14:03 +00:00
{
2021-02-11 15:03:04 +00:00
if ( 2 = = XdrvMailbox . index ) {
2022-07-09 11:57:06 +01:00
// Speed2 ! cancels use of Speed2 in the future
if ( ( 1 = = XdrvMailbox . data_len ) & & ( ' ! ' = = XdrvMailbox . data [ 0 ] ) ) {
Light . fade_once_enabled = false ;
Light . speed_once_enabled = false ;
ResponseCmndDone ( ) ;
return ;
}
2021-02-15 08:06:52 +00:00
// Speed2 setting will be used only once, then revert to fade/speed
2021-01-28 20:16:27 +00:00
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 40 ) ) {
Light . fade_once_enabled = true ;
2021-02-11 15:03:04 +00:00
Light . fade_once_value = ( XdrvMailbox . payload > 0 ) ;
2021-01-28 15:41:59 +00:00
Light . speed_once_enabled = true ;
Light . speed_once_value = XdrvMailbox . payload ;
2021-10-09 16:42:32 +01:00
if ( ! Light . fade_once_value ) { LightStopFade ( ) ; }
2021-01-28 15:41:59 +00:00
}
2021-02-15 08:06:52 +00:00
ResponseCmndIdxNumber ( Light . speed_once_value ) ;
2021-02-11 15:03:04 +00:00
} else {
// Speed 1 - Fast
// Speed 40 - Very slow
// Speed + - Increment Speed
// Speed - - Decrement Speed
if ( 1 = = XdrvMailbox . data_len ) {
2021-06-11 17:14:12 +01:00
if ( ( ' + ' = = XdrvMailbox . data [ 0 ] ) & & ( Settings - > light_speed > 1 ) ) {
XdrvMailbox . payload = Settings - > light_speed - 1 ;
2021-02-11 15:03:04 +00:00
}
2021-06-11 17:14:12 +01:00
else if ( ( ' - ' = = XdrvMailbox . data [ 0 ] ) & & ( Settings - > light_speed < 40 ) ) {
XdrvMailbox . payload = Settings - > light_speed + 1 ;
2021-02-11 15:03:04 +00:00
}
2017-11-15 22:07:45 +00:00
}
2021-02-11 15:03:04 +00:00
if ( ( XdrvMailbox . payload > 0 ) & & ( XdrvMailbox . payload < = 40 ) ) {
2021-06-11 17:14:12 +01:00
Settings - > light_speed = XdrvMailbox . payload ;
2020-02-21 15:09:21 +00:00
# ifdef USE_DEVICE_GROUPS
2021-06-11 17:14:12 +01:00
SendDeviceGroupMessage ( Light . device , DGR_MSGTYP_UPDATE , DGR_ITEM_LIGHT_SPEED , Settings - > light_speed ) ;
2020-02-21 15:09:21 +00:00
# endif // USE_DEVICE_GROUPS
2021-02-11 15:03:04 +00:00
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber ( Settings - > light_speed ) ;
2017-02-19 16:49:17 +00:00
}
2019-08-01 14:47:00 +01:00
}
2018-05-09 09:49:43 +01:00
2019-08-01 14:47:00 +01:00
void CmndWakeupDuration ( void )
{
2020-01-22 15:14:03 +00:00
// WakeUpDuration - Show current Wake Up duration in seconds
// WakeUpDuration 60 - Set Wake Up duration to 60 seconds
2019-08-01 14:47:00 +01:00
if ( ( XdrvMailbox . payload > 0 ) & & ( XdrvMailbox . payload < 3001 ) ) {
2021-06-11 17:14:12 +01:00
Settings - > light_wakeup = XdrvMailbox . payload ;
2019-08-17 12:17:30 +01:00
Light . wakeup_active = 0 ;
2017-03-12 17:36:33 +00:00
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber ( Settings - > light_wakeup ) ;
2019-08-01 14:47:00 +01:00
}
2018-05-28 10:35:23 +01:00
2020-12-29 18:31:27 +00:00
void CmndCTRange ( void )
{
// Format is "CTRange ctmin,ctmax"
// Ex:
// CTRange 153,500
// CTRange
// CTRange 200,350
char * p ;
strtok_r ( XdrvMailbox . data , " , " , & p ) ;
if ( p ! = nullptr ) {
int32_t ct_min = strtol ( XdrvMailbox . data , nullptr , 0 ) ;
int32_t ct_max = strtol ( p , nullptr , 0 ) ;
if ( ( ct_min > = CT_MIN ) & & ( ct_min < = CT_MAX ) & &
( ct_max > = CT_MIN ) & & ( ct_max < = CT_MAX ) & &
( ct_min < = ct_max )
) {
setCTRange ( ct_min , ct_max ) ;
} else {
return ; // error
}
}
Response_P ( PSTR ( " { \" %s \" : \" %d,%d \" } " ) , XdrvMailbox . command , Light . vct_ct [ 0 ] , Light . vct_ct [ CT_PIVOTS - 1 ] ) ;
}
# ifdef USE_LIGHT_VIRTUAL_CT
void CmndVirtualCT ( void )
{
2021-06-11 17:14:12 +01:00
if ( ! Settings - > flag4 . virtual_ct ) {
2020-12-29 18:31:27 +00:00
ResponseCmndChar_P ( PSTR ( " You need to enable `SetOption106 1` " ) ) ;
return ;
}
if ( XdrvMailbox . data [ 0 ] = = ( ' { ' ) ) {
// parse JSON
JsonParser parser ( XdrvMailbox . data ) ;
JsonParserObject root = parser . getRootObject ( ) ;
if ( ! root ) { return ; }
uint32_t idx = 0 ;
for ( auto key : root ) {
if ( idx > = CT_PIVOTS ) { ResponseCmndChar_P ( PSTR ( " Too many points " ) ) ; return ; }
int32_t ct_val = strtol ( key . getStr ( ) , nullptr , 0 ) ;
if ( ( ct_val < CT_MIN ) | | ( ct_val > CT_MAX ) ) { ResponseCmndChar_P ( PSTR ( " CT out of range " ) ) ; return ; }
char * color = ( char * ) key . getValue ( ) . getStr ( ) ;
// call color parser
Light . vct_ct [ idx ] = ct_val ;
if ( LightColorEntry ( color , strlen ( color ) ) ) {
memcpy ( & Light . vct_color [ idx ] , Light . entry_color , sizeof ( Light . vct_color [ idx ] ) ) ;
}
idx + + ;
}
for ( uint32_t i = idx - 1 ; i < CT_PIVOTS - 1 ; i + + ) {
Light . vct_ct [ i + 1 ] = Light . vct_ct [ i ] ;
memcpy ( & Light . vct_color [ i + 1 ] , & Light . vct_color [ i ] , sizeof ( Light . vct_color [ 0 ] ) ) ;
}
}
checkVirtualCT ( ) ;
2023-06-27 13:23:44 +01:00
Light . update = true ;
2020-12-29 18:31:27 +00:00
Response_P ( PSTR ( " { \" %s \" :{ " ) , XdrvMailbox . command ) ;
uint32_t pivot_len = CT_PIVOTS ;
vct_pivot_t * pivot = & Light . vct_color [ 0 ] ;
if ( Light . vct_ct [ 1 ] > = Light . vct_ct [ 2 ] ) { pivot_len = 2 ; } // only 2 points are valid
bool end = false ;
for ( uint32_t i = 0 ; ( i < CT_PIVOTS ) & & ! end ; i + + ) {
if ( ( i > = CT_PIVOTS - 1 ) | | ( Light . vct_ct [ i ] > = Light . vct_ct [ i + 1 ] ) ) {
end = true ;
}
ResponseAppend_P ( PSTR ( " \" %d \" : \" %02X%02X%02X%02X%02X \" %c " ) , Light . vct_ct [ i ] ,
( * pivot ) [ 0 ] , ( * pivot ) [ 1 ] , ( * pivot ) [ 2 ] , ( * pivot ) [ 3 ] , ( * pivot ) [ 4 ] ,
end ? ' } ' : ' , ' ) ;
pivot + + ;
}
ResponseJsonEnd ( ) ;
}
# endif // USE_LIGHT_VIRTUAL_CT
2020-04-13 05:17:25 +01:00
# ifdef USE_LIGHT_PALETTE
void CmndPalette ( void )
{
uint8_t * palette_entry ;
char * p ;
// Palette Color[ ...]
if ( XdrvMailbox . data_len ) {
Light . wheel = 0 ;
2020-04-13 23:55:12 +01:00
Light . palette_count = 0 ;
2020-04-13 05:17:25 +01:00
if ( Light . palette ) {
free ( Light . palette ) ;
Light . palette = nullptr ;
}
if ( XdrvMailbox . data_len > 1 | | XdrvMailbox . data [ 0 ] ! = ' 0 ' ) {
2020-04-13 23:55:12 +01:00
uint8_t palette_count = 0 ;
char * color = XdrvMailbox . data ;
if ( ! ( Light . palette = ( uint8_t * ) malloc ( 255 * Light . subtype ) ) ) return ;
palette_entry = Light . palette ;
for ( ; ; ) {
p = strchr ( color , ' ' ) ;
if ( p ) * p = 0 ;
color = Trim ( color ) ;
if ( * color & & LightColorEntry ( color , strlen ( color ) ) ) {
memcpy ( palette_entry , Light . entry_color , Light . subtype ) ;
palette_entry + = Light . subtype ;
palette_count + + ;
2020-04-13 05:17:25 +01:00
}
2020-04-13 23:55:12 +01:00
if ( ! p ) break ;
color = p + 1 ;
2020-04-13 05:17:25 +01:00
}
2020-04-13 23:55:12 +01:00
if ( ! ( Light . palette = ( uint8_t * ) realloc ( Light . palette , palette_count * Light . subtype ) ) ) return ;
Light . palette_count = palette_count ;
2020-04-13 05:17:25 +01:00
}
}
char palette_str [ 5 * Light . subtype * Light . palette_count + 3 ] ;
p = palette_str ;
* p + + = ' [ ' ;
if ( Light . palette_count ) {
palette_entry = Light . palette ;
2020-04-13 23:55:12 +01:00
for ( int entry = 0 ; entry < Light . palette_count ; entry + + ) {
2021-06-11 17:14:12 +01:00
if ( Settings - > flag . decimal_text ) { // SetOption17 - Switch between decimal or hexadecimal output
2020-04-13 05:17:25 +01:00
* p + + = ' " ' ;
2020-04-25 23:49:34 +01:00
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
p + = sprintf_P ( p , PSTR ( " %d, " ) , * palette_entry + + ) ;
}
* ( p - 1 ) = ' " ' ;
2020-04-13 05:17:25 +01:00
}
2020-04-25 23:49:34 +01:00
else {
for ( uint32_t i = 0 ; i < Light . subtype ; i + + ) {
p + = sprintf_P ( p , PSTR ( " %02X " ) , * palette_entry + + ) ;
}
2020-04-13 05:17:25 +01:00
}
* p + + = ' , ' ;
}
p - - ;
}
* p + + = ' ] ' ;
* p = 0 ;
ResponseCmndChar ( palette_str ) ;
}
# endif // USE_LIGHT_PALETTE
2020-04-25 23:49:34 +01:00
# ifdef USE_DGR_LIGHT_SEQUENCE
void CmndSequenceOffset ( void )
{
2020-11-08 14:10:11 +00:00
// SequenceOffset<x> <offset>, x: 0=offset, 1=Friendly name 1 ending digits + offset [-1]
// 2=MQTT topic ending digits + offset [-1]
int32_t offset = XdrvMailbox . payload ;
if ( XdrvMailbox . usridx & & XdrvMailbox . index > 0 ) {
uint32_t index = SET_FRIENDLYNAME1 ;
if ( XdrvMailbox . index = = 2 ) index = SET_MQTT_TOPIC ;
char * name = SettingsText ( index ) ;
char * ptr = name + strlen ( name ) ;
while ( - - ptr > = name & & isdigit ( * ptr ) ) ;
if ( ! XdrvMailbox . data_len ) offset = - 1 ;
offset + = atoi ( ptr + 1 ) ;
}
if ( offset > = 0 & & offset < = 255 ) {
if ( offset ! = Light . sequence_offset ) {
2020-04-25 23:49:34 +01:00
if ( Light . sequence_offset ) free ( Light . channels_fifo ) ;
2020-11-08 14:10:11 +00:00
Light . sequence_offset = offset ;
2020-04-25 23:49:34 +01:00
if ( Light . sequence_offset ) Light . channels_fifo = ( uint8_t * ) calloc ( Light . sequence_offset , LST_MAX ) ;
}
}
ResponseCmndNumber ( Light . sequence_offset ) ;
}
# endif // USE_DGR_LIGHT_SEQUENCE
2019-08-01 14:47:00 +01:00
void CmndUndocA ( void )
2020-01-22 15:14:03 +00:00
{
// Theos legacy status
2019-08-01 14:47:00 +01:00
char scolor [ LIGHT_COLOR_SIZE ] ;
LightGetColor ( scolor , true ) ; // force hex whatever Option 17
scolor [ 6 ] = ' \0 ' ; // RGB only
2021-06-11 17:14:12 +01:00
Response_P ( PSTR ( " %s,%d,%d,%d,%d,%d " ) , scolor , Settings - > light_fade , Settings - > light_correction , Settings - > light_scheme , Settings - > light_speed , Settings - > light_width ) ;
2021-04-07 14:07:05 +01:00
MqttPublishPrefixTopicRulesProcess_P ( STAT , XdrvMailbox . topic ) ;
2020-10-30 11:29:48 +00:00
ResponseClear ( ) ;
2017-02-19 16:49:17 +00:00
}
2018-01-05 11:26:19 +00:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-11-11 09:44:56 +00:00
bool Xdrv04 ( uint32_t function )
2018-01-05 11:26:19 +00:00
{
2019-01-28 13:08:33 +00:00
bool result = false ;
2018-01-05 11:26:19 +00:00
2019-10-06 16:19:05 +01:00
if ( FUNC_MODULE_INIT = = function ) {
2020-02-24 13:19:15 +00:00
return LightModuleInit ( ) ;
2019-10-06 16:19:05 +01:00
}
2020-10-30 11:29:48 +00:00
else if ( TasmotaGlobal . light_type ) {
2018-01-05 11:26:19 +00:00
switch ( function ) {
2019-10-11 10:23:53 +01:00
case FUNC_SERIAL :
result = XlgtCall ( FUNC_SERIAL ) ;
break ;
2019-12-21 17:26:36 +00:00
case FUNC_LOOP :
if ( Light . fade_running ) {
if ( LightApplyFade ( ) ) {
2019-12-27 20:02:23 +00:00
LightSetOutputs ( Light . fade_cur_10 ) ;
2019-12-21 17:26:36 +00:00
}
}
2022-11-13 17:22:39 +00:00
# ifdef USE_LIGHT_ARTNET
ArtNetLoop ( ) ;
# endif // USE_LIGHT_ARTNET
2019-12-21 17:26:36 +00:00
break ;
2018-01-05 11:26:19 +00:00
case FUNC_EVERY_50_MSECOND :
LightAnimate ( ) ;
2018-02-07 16:50:02 +00:00
break ;
2020-02-21 15:09:21 +00:00
# ifdef USE_DEVICE_GROUPS
2020-03-12 17:51:54 +00:00
case FUNC_DEVICE_GROUP_ITEM :
2020-04-06 18:29:50 +01:00
LightHandleDevGroupItem ( ) ;
2020-02-21 15:09:21 +00:00
break ;
# endif // USE_DEVICE_GROUPS
2018-01-05 11:26:19 +00:00
case FUNC_SET_POWER :
LightSetPower ( ) ;
break ;
2021-11-30 13:55:45 +00:00
case FUNC_BUTTON_MULTI_PRESSED :
result = XlgtCall ( FUNC_BUTTON_MULTI_PRESSED ) ;
break ;
2021-12-15 10:15:30 +00:00
# ifdef USE_WEBSERVER
case FUNC_WEB_ADD_MAIN_BUTTON :
XlgtCall ( FUNC_WEB_ADD_MAIN_BUTTON ) ;
break ;
case FUNC_WEB_GET_ARG :
XlgtCall ( FUNC_WEB_GET_ARG ) ;
break ;
# endif // USE_WEBSERVER
2019-08-01 14:47:00 +01:00
case FUNC_COMMAND :
Add light synonyms
Add commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively
2021-01-26 13:56:58 +00:00
result = DecodeCommand ( kLightCommands , LightCommand , kLightSynonyms ) ;
2019-10-08 16:46:03 +01:00
if ( ! result ) {
result = XlgtCall ( FUNC_COMMAND ) ;
}
2019-08-01 14:47:00 +01:00
break ;
2019-10-06 16:19:05 +01:00
case FUNC_PRE_INIT :
LightInit ( ) ;
break ;
2022-11-13 17:22:39 +00:00
# ifdef USE_LIGHT_ARTNET
2023-12-27 21:03:56 +00:00
case FUNC_JSON_APPEND :
ArtNetJSONAppend ( ) ;
break ;
case FUNC_NETWORK_UP :
ArtNetFuncNetworkUp ( ) ;
break ;
case FUNC_NETWORK_DOWN :
ArtNetFuncNetworkDown ( ) ;
break ;
2022-11-13 17:22:39 +00:00
# endif // USE_LIGHT_ARTNET
2023-12-27 21:03:56 +00:00
case FUNC_ACTIVE :
result = true ;
break ;
}
2018-01-05 11:26:19 +00:00
}
return result ;
}
2019-06-16 15:43:23 +01:00
# endif // USE_LIGHT