Hello, I would like to share with you my extension of the code. I have some new lamps that have 2 PWM channels that are not classically wired. The first one is for brightness and the other channel is for color temperature. Tasmota has already integrated the possibility to switch between CWWW and CTBRI. I only built the possibility to set it up manually. greetings, Jens PS: This is my first commit. If I did something wrong, please report it.
3002 lines
110 KiB
C++
3002 lines
110 KiB
C++
/*
|
|
xdrv_04_light.ino - PWM, WS2812 and sonoff led support for Tasmota
|
|
|
|
Copyright (C) 2020 Theo Arends
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef USE_LIGHT
|
|
/*********************************************************************************************\
|
|
* PWM, WS2812, Sonoff B1, AiLight, Sonoff Led and BN-SZ01, H801, MagicHome and Arilux
|
|
*
|
|
* light_type Module Color ColorTemp Modules
|
|
* ---------- --------- ----- --------- ----------------------------
|
|
* 0 - no (Sonoff Basic)
|
|
* 1 PWM1 W no (Sonoff BN-SZ)
|
|
* 2 PWM2 CW yes (Sonoff Led)
|
|
* 3 PWM3 RGB no (H801, MagicHome and Arilux LC01)
|
|
* 4 PWM4 RGBW no (H801, MagicHome and Arilux)
|
|
* 5 PWM5 RGBCW yes (H801, Arilux LC11)
|
|
* 9 reserved no
|
|
* 10 reserved yes
|
|
* 11 +WS2812 RGB no (One WS2812 RGB or RGBW ledstrip)
|
|
* 12 AiLight RGBW no
|
|
* 13 Sonoff B1 RGBCW yes
|
|
*
|
|
* 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
|
|
*
|
|
\*********************************************************************************************/
|
|
|
|
/*********************************************************************************************\
|
|
*
|
|
* 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.
|
|
*
|
|
* 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).
|
|
* SetOption82 can expand the rendering from 200-380 due to Alexa reduced range.
|
|
* 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()
|
|
* .c The 5 internal channels RGBWC are mapped to the actual channels supported
|
|
* by the light_type: in calcLevels()
|
|
* 1 channel - 0:Brightness
|
|
* 2 channels - 0:Coldwhite 1:Warmwhite
|
|
* 3 channels - 0:Red 1:Green 2:Blue
|
|
* 4 chennels - 0:Red 1:Green 2:Blue 3:White
|
|
* 5 chennels - 0:Red 1:Green 2:Blue 3:ColdWhite 4:Warmwhite
|
|
*
|
|
* 3. In LightAnimate(), final PWM values are computed at next tick.
|
|
* .a If color did not change since last tick - ignore.
|
|
* .b Extend resolution from 8 bits to 10 bits, which makes a significant
|
|
* difference when applying gamma correction at low brightness.
|
|
* .c Apply Gamma Correction if LedTable==1 (by default).
|
|
* Gamma Correction uses an adaptative resolution table from 11 to 8 bits.
|
|
* .d For Warm/Cold-white channels, Gamma correction is calculated in combined mode.
|
|
* 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.
|
|
* .e Gamma correction is still applied to 8 bits channels for compatibility
|
|
* with other non-PMW modules.
|
|
* .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]
|
|
* .h Scale ranges from 10 bits to 0..PWMRange (by default 1023) so no change
|
|
* by default.
|
|
* .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
|
|
*
|
|
\*********************************************************************************************/
|
|
|
|
#define XDRV_04 4
|
|
// #define DEBUG_LIGHT
|
|
|
|
enum LightSchemes { LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_MAX };
|
|
|
|
const uint8_t LIGHT_COLOR_SIZE = 25; // Char array scolor size
|
|
|
|
const char kLightCommands[] PROGMEM = "|" // No prefix
|
|
D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" D_CMND_DIMMER_RANGE "|" D_CMND_LEDTABLE "|" D_CMND_FADE "|"
|
|
D_CMND_RGBWWTABLE "|" D_CMND_SCHEME "|" D_CMND_SPEED "|" D_CMND_WAKEUP "|" D_CMND_WAKEUPDURATION "|"
|
|
D_CMND_WHITE "|" D_CMND_CHANNEL "|" D_CMND_HSBCOLOR
|
|
#ifdef USE_LIGHT_PALETTE
|
|
"|" D_CMND_PALETTE
|
|
#endif // USE_LIGHT_PALETTE
|
|
"|UNDOCA" ;
|
|
|
|
void (* const LightCommand[])(void) PROGMEM = {
|
|
&CmndColor, &CmndColorTemperature, &CmndDimmer, &CmndDimmerRange, &CmndLedTable, &CmndFade,
|
|
&CmndRgbwwTable, &CmndScheme, &CmndSpeed, &CmndWakeup, &CmndWakeupDuration,
|
|
&CmndWhite, &CmndChannel, &CmndHsbColor,
|
|
#ifdef USE_LIGHT_PALETTE
|
|
&CmndPalette,
|
|
#endif // USE_LIGHT_PALETTE
|
|
&CmndUndocA };
|
|
|
|
// Light color mode, either RGB alone, or white-CT alone, or both only available if ct_rgb_linked is false
|
|
enum LightColorModes {
|
|
LCM_RGB = 1, LCM_CT = 2, LCM_BOTH = 3 };
|
|
|
|
struct LRgbColor {
|
|
uint8_t R, G, B;
|
|
};
|
|
const uint8_t MAX_FIXED_COLOR = 12;
|
|
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 };
|
|
|
|
struct LWColor {
|
|
uint8_t W;
|
|
};
|
|
const uint8_t MAX_FIXED_WHITE = 4;
|
|
const LWColor kFixedWhite[MAX_FIXED_WHITE] PROGMEM = { 0, 255, 128, 32 };
|
|
|
|
struct LCwColor {
|
|
uint8_t C, W;
|
|
};
|
|
const uint8_t MAX_FIXED_COLD_WARM = 4;
|
|
const LCwColor kFixedColdWarm[MAX_FIXED_COLD_WARM] PROGMEM = { 0,0, 255,0, 0,255, 128,128 };
|
|
|
|
// 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
|
|
|
|
// New version of Gamma correction compute
|
|
// Instead of a table, we do a multi-linear approximation, which is close enough
|
|
// At low levels, the slope is a bit higher than actual gamma, to make changes smoother
|
|
// Internal resolution is 10 bits.
|
|
|
|
typedef struct gamma_table_t {
|
|
uint16_t to_src;
|
|
uint16_t to_gamma;
|
|
} gamma_table_t;
|
|
|
|
const gamma_table_t gamma_table[] = { // don't put in PROGMEM for performance reasons
|
|
{ 1, 1 },
|
|
{ 4, 1 },
|
|
{ 209, 13 },
|
|
{ 312, 41 },
|
|
{ 457, 106 },
|
|
{ 626, 261 },
|
|
{ 762, 450 },
|
|
{ 895, 703 },
|
|
{ 1023, 1023 },
|
|
{ 0xFFFF, 0xFFFF } // fail-safe if out of range
|
|
};
|
|
|
|
// simplified Gamma table for Fade, cheating a little at low brightness
|
|
const gamma_table_t gamma_table_fast[] = {
|
|
{ 384, 192 },
|
|
{ 768, 576 },
|
|
{ 1023, 1023 },
|
|
{ 0xFFFF, 0xFFFF } // fail-safe if out of range
|
|
};
|
|
|
|
// For reference, below are the computed gamma tables, via ledGamma()
|
|
// for 8 bits output:
|
|
// 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
// 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
|
|
// 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
|
|
// 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6,
|
|
// 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 11,
|
|
// 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18,
|
|
// 18, 19, 19, 20, 20, 21, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25,
|
|
// 25, 26, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 37, 38,
|
|
// 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, 51, 52, 53,
|
|
// 54, 55, 56, 57, 58, 59, 60, 61, 61, 62, 63, 64, 65, 67, 68, 69,
|
|
// 71, 72, 73, 75, 76, 78, 79, 80, 82, 83, 85, 86, 87, 89, 90, 91,
|
|
// 93, 94, 95, 97, 98,100,101,102,104,105,107,108,109,111,112,114,
|
|
// 116,118,120,122,124,125,127,129,131,133,135,137,139,141,143,144,
|
|
// 146,148,150,152,154,156,158,160,162,164,166,168,170,171,173,175,
|
|
// 178,180,183,185,188,190,193,195,198,200,203,205,208,210,213,215,
|
|
// 218,220,223,225,228,230,233,235,238,240,243,245,248,250,253,255
|
|
//
|
|
// and for 10 bits output:
|
|
// 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4,
|
|
// 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8,
|
|
// 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12,
|
|
// 12, 12, 13, 13, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25,
|
|
// 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43,
|
|
// 45, 47, 49, 50, 52, 54, 56, 58, 59, 61, 63, 65, 67, 68, 70, 72,
|
|
// 74, 76, 77, 79, 81, 83, 84, 86, 88, 90, 92, 93, 95, 97, 99, 101,
|
|
// 102, 104, 106, 110, 113, 117, 121, 124, 128, 132, 135, 139, 143, 146, 150, 154,
|
|
// 158, 162, 166, 169, 173, 177, 180, 184, 188, 191, 195, 199, 202, 206, 210, 213,
|
|
// 217, 221, 224, 228, 232, 235, 239, 243, 246, 250, 254, 257, 261, 267, 272, 278,
|
|
// 283, 289, 294, 300, 305, 311, 317, 322, 328, 333, 339, 344, 350, 356, 361, 367,
|
|
// 372, 378, 383, 389, 394, 400, 406, 411, 417, 422, 428, 433, 439, 444, 450, 458,
|
|
// 465, 473, 480, 488, 496, 503, 511, 518, 526, 534, 541, 549, 557, 564, 572, 579,
|
|
// 587, 595, 602, 610, 617, 627, 635, 642, 650, 657, 665, 673, 680, 688, 695, 703,
|
|
// 713, 723, 733, 743, 753, 763, 773, 783, 793, 803, 813, 823, 833, 843, 853, 863,
|
|
// 873, 883, 893, 903, 913, 923, 933, 943, 953, 963, 973, 983, 993,1003,1013,1023
|
|
//
|
|
// Output for Dimmer 0..100 values
|
|
// 0, 1, 2, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 8, 9,
|
|
// 10, 10, 11, 12, 12, 13, 15, 17, 21, 23, 26, 28, 31, 34, 37,
|
|
// 40, 43, 49, 52, 58, 61, 67, 70, 76, 79, 84, 90, 93, 99,102,
|
|
// 110,117,128,135,146,158,166,177,184,195,202,213,221,232,239,
|
|
// 250,261,272,289,300,317,328,344,356,372,389,400,417,428,444,
|
|
// 458,480,496,518,534,557,579,595,617,635,657,673,695,713,743,
|
|
// 773,793,823,843,873,893,923,943,973,993,1023
|
|
|
|
struct LIGHT {
|
|
uint32_t strip_timer_counter = 0; // Bars and Gradient
|
|
power_t power = 0; // Power<x> for each channel if SetOption68, or boolean if single light
|
|
|
|
uint16_t wakeup_counter = 0;
|
|
|
|
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];
|
|
|
|
uint8_t wheel = 0;
|
|
uint8_t random = 0;
|
|
uint8_t subtype = 0; // LST_ subtype
|
|
uint8_t device = 0;
|
|
uint8_t old_power = 1;
|
|
uint8_t wakeup_active = 0;
|
|
uint8_t wakeup_dimmer = 0;
|
|
uint8_t fixed_color_index = 1;
|
|
uint8_t pwm_offset = 0; // Offset in color buffer
|
|
uint8_t max_scheme = LS_MAX -1;
|
|
|
|
bool update = true;
|
|
bool pwm_multi_channels = false; // SetOption68, treat each PWM channel as an independant dimmer
|
|
|
|
bool fade_initialized = false; // dont't fade at startup
|
|
bool fade_running = false;
|
|
#ifdef USE_DEVICE_GROUPS
|
|
uint8_t last_scheme = 0;
|
|
bool devgrp_no_channels_out = false; // don't share channels with device group (e.g. if scheme set by other device)
|
|
#endif // USE_DEVICE_GROUPS
|
|
#ifdef USE_LIGHT_PALETTE
|
|
uint8_t palette_count = 0; // palette entry count
|
|
uint8_t * palette; // dynamically allocated palette color array
|
|
#endif // USE_LIGHT_PALETTE
|
|
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
|
|
uint16_t fade_duration = 0; // duration of fade in milliseconds
|
|
uint32_t fade_start = 0; // fade start time in milliseconds, compared to millis()
|
|
|
|
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
|
|
} Light;
|
|
|
|
power_t LightPower(void)
|
|
{
|
|
return Light.power; // Make external
|
|
}
|
|
|
|
// IRAM variant for rotary
|
|
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 // Fix core 2.5.x ISR not in IRAM Exception
|
|
power_t LightPowerIRAM(void) ICACHE_RAM_ATTR;
|
|
#endif // ARDUINO_ESP8266_RELEASE_2_3_0
|
|
|
|
power_t LightPowerIRAM(void)
|
|
{
|
|
return Light.power; // Make external
|
|
}
|
|
|
|
uint8_t LightDevice(void)
|
|
{
|
|
return Light.device; // Make external
|
|
}
|
|
|
|
static uint32_t min3(uint32_t a, uint32_t b, uint32_t c) {
|
|
return (a < b && a < c) ? a : (b < c) ? b : c;
|
|
}
|
|
|
|
//
|
|
// LightStateClass
|
|
// This class is an abstraction of the current light state.
|
|
// It allows for b/w, full colors, or white colortone
|
|
//
|
|
// This class has 2 independant slots
|
|
// 1/ Brightness 0.255, dimmer controls both RGB and WC (warm-cold)
|
|
// 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.
|
|
// If Brightness is 0, it is equivalent to Off (for compatibility)
|
|
// Dimmer is Brightness converted to range 0..100
|
|
// 2/ White with colortone - or CW (Cold / Warm)
|
|
// ct is 153..500 temperature (153=cold, 500=warm)
|
|
// briCT specifies the brightness for white channel
|
|
//
|
|
// Dimmer (0.100) is automatically derived from brightness
|
|
//
|
|
// 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)
|
|
// but if you use setCW() both channels can be set independantly
|
|
// 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
|
|
//
|
|
// Note: If you want the actual RGB, you need to multiply with Bri, or use getActualRGBCW()
|
|
// Note: all values are stored as unsigned integer, no floats.
|
|
// Note: you can query vaules from this singleton. But to change values,
|
|
// use the LightController - changing this object will have no effect on actual light.
|
|
//
|
|
class LightStateClass {
|
|
private:
|
|
uint16_t _hue = 0; // 0..359
|
|
uint8_t _sat = 255; // 0..255
|
|
uint8_t _briRGB = 255; // 0..255
|
|
// 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
|
|
|
|
uint8_t _subtype = 0; // local copy of Light.subtype, if we need multiple lights
|
|
uint16_t _ct = CT_MIN; // 153..500, default to 153 (cold white)
|
|
uint8_t _wc = 255; // white cold channel
|
|
uint8_t _ww = 0; // white warm channel
|
|
uint8_t _briCT = 255;
|
|
|
|
uint8_t _color_mode = LCM_RGB; // RGB by default
|
|
// the CT range below represents the rendered range,
|
|
// This is due to Alexa whose CT range is 199..383
|
|
// Hence setting Min=200 and Max=380 makes Alexa use the full range
|
|
// Please note that you can still set CT to 153..500, but any
|
|
// value below _ct_min_range or above _ct_max_range not change the CT
|
|
uint16_t _ct_min_range = CT_MIN; // the minimum CT rendered range
|
|
uint16_t _ct_max_range = CT_MAX; // the maximum CT rendered range
|
|
|
|
public:
|
|
LightStateClass() {
|
|
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::Constructor RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _bri);
|
|
}
|
|
|
|
void setSubType(uint8_t sub_type) {
|
|
_subtype = sub_type; // set sub_type at initialization, shoudln't be changed afterwards
|
|
}
|
|
|
|
// This function is a bit hairy, it will try to match the rerquired
|
|
// 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
|
|
// LST_RGBCW: LCM_RGB, LCM_CT or LCM_BOTH
|
|
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:
|
|
case LST_RGBCW:
|
|
_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
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setColorMode prev_cm (%d) req_cm (%d) new_cm (%d)", prev_cm, cm, _color_mode);
|
|
#endif
|
|
return prev_cm;
|
|
}
|
|
|
|
inline uint8_t getColorMode() {
|
|
return _color_mode;
|
|
}
|
|
|
|
void addRGBMode() {
|
|
setColorMode(_color_mode | LCM_RGB);
|
|
}
|
|
void addCTMode() {
|
|
setColorMode(_color_mode | LCM_CT);
|
|
}
|
|
|
|
// Get RGB color, always at full brightness (ie. one of the components is 255)
|
|
void getRGB(uint8_t *r, uint8_t *g, uint8_t *b) {
|
|
if (r) { *r = _r; }
|
|
if (g) { *g = _g; }
|
|
if (b) { *b = _b; }
|
|
}
|
|
|
|
// get full brightness values for warm and cold channels.
|
|
// either w=c=0 (off) or w+c >= 255
|
|
void getCW(uint8_t *rc, uint8_t *rw) {
|
|
if (rc) { *rc = _wc; }
|
|
if (rw) { *rw = _ww; }
|
|
}
|
|
|
|
// Get the actual values for each channel, ie multiply with brightness
|
|
void getActualRGBCW(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *c, uint8_t *w) {
|
|
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; }
|
|
}
|
|
|
|
uint8_t getChannels(uint8_t *channels) {
|
|
getActualRGBCW(&channels[0], &channels[1], &channels[2], &channels[3], &channels[4]);
|
|
}
|
|
|
|
void getChannelsRaw(uint8_t *channels) {
|
|
channels[0] = _r;
|
|
channels[1] = _g;
|
|
channels[2] = _b;
|
|
channels[3] = _wc;
|
|
channels[4] = _ww;
|
|
}
|
|
|
|
void getHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri) {
|
|
if (hue) { *hue = _hue; }
|
|
if (sat) { *sat = _sat; }
|
|
if (bri) { *bri = _briRGB; }
|
|
}
|
|
|
|
// getBri() is guaranteed to give the same result as setBri() - no rounding errors.
|
|
uint8_t getBri(void) {
|
|
// return the max of _briCT and _briRGB
|
|
return (_briRGB >= _briCT) ? _briRGB : _briCT;
|
|
}
|
|
|
|
// get the white Brightness
|
|
inline uint8_t getBriCT() {
|
|
return _briCT;
|
|
}
|
|
|
|
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);
|
|
// if brightness is non zero, force dimmer to be non-zero too
|
|
if ((dimmer == 0) && (bri > 0)) { dimmer = 1; }
|
|
return dimmer;
|
|
}
|
|
|
|
uint8_t getDimmer(uint32_t mode = 0) {
|
|
uint8_t bri;
|
|
switch (mode) {
|
|
case 1:
|
|
bri = getBriRGB();
|
|
break;
|
|
case 2:
|
|
bri = getBriCT();
|
|
break;
|
|
default:
|
|
bri = getBri();
|
|
break;
|
|
}
|
|
return BriToDimmer(bri);
|
|
}
|
|
|
|
inline uint16_t getCT() const {
|
|
return _ct; // 153..500, or CT_MIN..CT_MAX
|
|
}
|
|
|
|
// get the CT value within the range into a 10 bits 0..1023 value
|
|
uint16_t getCT10bits() const {
|
|
return changeUIntScale(_ct, _ct_min_range, _ct_max_range, 0, 1023);
|
|
}
|
|
|
|
inline void setCTRange(uint16_t ct_min_range, uint16_t ct_max_range) {
|
|
_ct_min_range = ct_min_range;
|
|
_ct_max_range = ct_max_range;
|
|
}
|
|
|
|
inline void getCTRange(uint16_t *ct_min_range, uint16_t *ct_max_range) const {
|
|
if (ct_min_range) { *ct_min_range = _ct_min_range; }
|
|
if (ct_max_range) { *ct_max_range = _ct_max_range; }
|
|
}
|
|
|
|
// get current color in XY format
|
|
void getXY(float *x, float *y) {
|
|
RgbToXy(_r, _g, _b, x, y);
|
|
}
|
|
|
|
// setters -- do not use directly, use the light_controller instead
|
|
// sets both master Bri and whiteBri
|
|
void setBri(uint8_t bri) {
|
|
setBriRGB(_color_mode & LCM_RGB ? bri : 0);
|
|
setBriCT(_color_mode & LCM_CT ? bri : 0);
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setBri RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB);
|
|
#endif
|
|
#ifdef USE_PWM_DIMMER
|
|
if (PWM_DIMMER == my_module_type) PWMDimmerSetBrightnessLeds(0);
|
|
#endif // USE_PWM_DIMMER
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
inline uint8_t getBriRGB() {
|
|
return _briRGB;
|
|
}
|
|
|
|
void setDimmer(uint8_t dimmer) {
|
|
setBri(DimmerToBri(dimmer));
|
|
}
|
|
|
|
void setCT(uint16_t ct) {
|
|
if (0 == ct) {
|
|
// disable ct mode
|
|
setColorMode(LCM_RGB); // try deactivating CT mode, setColorMode() will check which is legal
|
|
} else {
|
|
ct = (ct < CT_MIN ? CT_MIN : (ct > CT_MAX ? CT_MAX : ct));
|
|
_ww = changeUIntScale(ct, _ct_min_range, _ct_max_range, 0, 255);
|
|
_wc = 255 - _ww;
|
|
_ct = ct;
|
|
addCTMode();
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(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);
|
|
#endif
|
|
}
|
|
|
|
// Manually set Cold/Warm channels.
|
|
// 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
|
|
void setCW(uint8_t c, uint8_t w, bool free_range = false) {
|
|
uint16_t max = (w > c) ? w : c; // 0..255
|
|
uint16_t sum = c + w;
|
|
if (sum <= 257) { free_range = false; } // if we don't allow free range or if sum is below 255 (with tolerance of 2)
|
|
|
|
if (0 == max) {
|
|
_briCT = 0; // brightness set to null
|
|
setColorMode(LCM_RGB); // try deactivating CT mode, setColorMode() will check which is legal
|
|
} else {
|
|
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);
|
|
}
|
|
_ct = changeUIntScale(w, 0, sum, _ct_min_range, _ct_max_range);
|
|
addCTMode(); // activate CT mode if needed
|
|
if (_color_mode & LCM_CT) { _briCT = free_range ? max : (sum > 255 ? 255 : sum); }
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setCW CW (%d %d) CT (%d) briCT (%d)", c, w, _ct, _briCT);
|
|
#endif
|
|
}
|
|
|
|
// 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) {
|
|
uint16_t hue;
|
|
uint8_t sat;
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setRGB RGB input (%d %d %d)", r, g, b);
|
|
#endif
|
|
|
|
uint32_t max = (r > g && r > b) ? r : (g > b) ? g : b; // 0..255
|
|
|
|
if (0 == max) {
|
|
r = g = b = 255;
|
|
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;
|
|
}
|
|
|
|
RgbToHsb(r, g, b, &hue, &sat, nullptr);
|
|
_r = r;
|
|
_g = g;
|
|
_b = b;
|
|
_hue = hue;
|
|
_sat = sat;
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setRGB RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB);
|
|
#endif
|
|
return max;
|
|
}
|
|
|
|
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;
|
|
_sat = sat;
|
|
addRGBMode();
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setHS HS (%d %d) rgb (%d %d %d)", hue, sat, r, g, b);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setHS RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB);
|
|
#endif
|
|
}
|
|
|
|
// 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];
|
|
}
|
|
|
|
// 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) {
|
|
setRGB(channels[0], channels[1], channels[2]);
|
|
setCW(channels[3], channels[4], true); // free range for WC and WW
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setChannels (%d %d %d %d %d)",
|
|
channels[0], channels[1], channels[2], channels[3], channels[4]);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setChannels CT (%d) briRGB (%d) briCT (%d)", _ct, _briRGB, _briCT);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setChannels Actuals (%d %d %d %d %d)",
|
|
_r, _g, _b, _wc, _ww);
|
|
#endif
|
|
}
|
|
|
|
// new version of RGB to HSB with only integer calculation
|
|
static void RgbToHsb(uint8_t r, uint8_t g, uint8_t b, uint16_t *r_hue, uint8_t *r_sat, uint8_t *r_bri);
|
|
static void HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b);
|
|
static void RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y);
|
|
static void XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb);
|
|
|
|
};
|
|
|
|
|
|
/*********************************************************************************************\
|
|
* LightStateClass implementation
|
|
\*********************************************************************************************/
|
|
|
|
// new version with only integer computing
|
|
// brightness is not needed, it is controlled via Dimmer
|
|
void LightStateClass::RgbToHsb(uint8_t ir, uint8_t ig, uint8_t ib, uint16_t *r_hue, uint8_t *r_sat, uint8_t *r_bri) {
|
|
uint32_t r = ir;
|
|
uint32_t g = ig;
|
|
uint32_t b = ib;
|
|
uint32_t max = (r > g && r > b) ? r : (g > b) ? g : b; // 0..255
|
|
uint32_t min = (r < g && r < b) ? r : (g < b) ? g : b; // 0..255
|
|
uint32_t d = max - min; // 0..255
|
|
|
|
uint16_t hue = 0; // hue value in degrees ranges from 0 to 359
|
|
uint8_t sat = 0; // 0..255
|
|
uint8_t bri = max; // 0..255
|
|
|
|
if (d != 0) {
|
|
sat = changeUIntScale(d, 0, max, 0, 255);
|
|
if (r == max) {
|
|
hue = (g > b) ? changeUIntScale(g-b,0,d,0,60) : 360 - changeUIntScale(b-g,0,d,0,60);
|
|
} else if (g == max) {
|
|
hue = (b > r) ? 120 + changeUIntScale(b-r,0,d,0,60) : 120 - changeUIntScale(r-b,0,d,0,60);
|
|
} else {
|
|
hue = (r > g) ? 240 + changeUIntScale(r-g,0,d,0,60) : 240 - changeUIntScale(g-r,0,d,0,60);
|
|
}
|
|
hue = hue % 360; // 0..359
|
|
}
|
|
|
|
if (r_hue) *r_hue = hue;
|
|
if (r_sat) *r_sat = sat;
|
|
if (r_bri) *r_bri = bri;
|
|
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, "RgbToHsb rgb (%d %d %d) hsb (%d %d %d)", r, g, b, hue, sat, bri);
|
|
}
|
|
|
|
void LightStateClass::HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b) {
|
|
uint32_t r = 255; // default to white
|
|
uint32_t g = 255;
|
|
uint32_t b = 255;
|
|
// we take brightness at 100%, brightness should be set separately
|
|
hue = hue % 360; // normalize to 0..359
|
|
|
|
if (sat > 0) {
|
|
uint32_t i = hue / 60; // quadrant 0..5
|
|
uint32_t f = hue % 60; // 0..59
|
|
uint32_t q = 255 - changeUIntScale(f, 0, 60, 0, sat); // 0..59
|
|
uint32_t p = 255 - sat;
|
|
uint32_t t = 255 - changeUIntScale(60 - f, 0, 60, 0, sat);
|
|
|
|
switch (i) {
|
|
case 0:
|
|
//r = 255;
|
|
g = t;
|
|
b = p;
|
|
break;
|
|
case 1:
|
|
r = q;
|
|
//g = 255;
|
|
b = p;
|
|
break;
|
|
case 2:
|
|
r = p;
|
|
//g = 255;
|
|
b = t;
|
|
break;
|
|
case 3:
|
|
r = p;
|
|
g = q;
|
|
//b = 255;
|
|
break;
|
|
case 4:
|
|
r = t;
|
|
g = p;
|
|
//b = 255;
|
|
break;
|
|
default:
|
|
//r = 255;
|
|
g = p;
|
|
b = q;
|
|
break;
|
|
}
|
|
}
|
|
if (r_r) *r_r = r;
|
|
if (r_g) *r_g = g;
|
|
if (r_b) *r_b = b;
|
|
}
|
|
|
|
#define POW FastPrecisePowf
|
|
|
|
void LightStateClass::RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y) {
|
|
float x = 0.31271f; // default medium white
|
|
float y = 0.32902f;
|
|
|
|
if (i_r + i_b + i_g > 0) {
|
|
float r = (float)i_r / 255.0f;
|
|
float g = (float)i_g / 255.0f;
|
|
float b = (float)i_b / 255.0f;
|
|
// https://gist.github.com/popcorn245/30afa0f98eea1c2fd34d
|
|
// Gamma correction
|
|
r = (r > 0.04045f) ? POW((r + 0.055f) / (1.0f + 0.055f), 2.4f) : (r / 12.92f);
|
|
g = (g > 0.04045f) ? POW((g + 0.055f) / (1.0f + 0.055f), 2.4f) : (g / 12.92f);
|
|
b = (b > 0.04045f) ? POW((b + 0.055f) / (1.0f + 0.055f), 2.4f) : (b / 12.92f);
|
|
|
|
// conversion to X, Y, Z
|
|
// Y is also the Luminance
|
|
float X = r * 0.649926f + g * 0.103455f + b * 0.197109f;
|
|
float Y = r * 0.234327f + g * 0.743075f + b * 0.022598f;
|
|
float Z = r * 0.000000f + g * 0.053077f + b * 1.035763f;
|
|
|
|
x = X / (X + Y + Z);
|
|
y = Y / (X + Y + Z);
|
|
// we keep the raw gamut, one nice thing could be to convert to a narrower gamut
|
|
}
|
|
if (r_x) *r_x = x;
|
|
if (r_y) *r_y = y;
|
|
}
|
|
|
|
void LightStateClass::XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb)
|
|
{
|
|
x = (x > 0.99f ? 0.99f : (x < 0.01f ? 0.01f : x));
|
|
y = (y > 0.99f ? 0.99f : (y < 0.01f ? 0.01f : y));
|
|
float z = 1.0f - x - y;
|
|
//float Y = 1.0f;
|
|
float X = x / y;
|
|
float Z = z / y;
|
|
// float r = X * 1.4628067f - 0.1840623f - Z * 0.2743606f;
|
|
// float g = -X * 0.5217933f + 1.4472381f + Z * 0.0677227f;
|
|
// float b = X * 0.0349342f - 0.0968930f + Z * 1.2884099f;
|
|
float r = X * 3.2406f - 1.5372f - Z * 0.4986f;
|
|
float g = -X * 0.9689f + 1.8758f + Z * 0.0415f;
|
|
float b = X * 0.0557f - 0.2040f + Z * 1.0570f;
|
|
float max = (r > g && r > b) ? r : (g > b) ? g : b;
|
|
r = r / max; // normalize to max == 1.0
|
|
g = g / max;
|
|
b = b / max;
|
|
r = (r <= 0.0031308f) ? 12.92f * r : 1.055f * POW(r, (1.0f / 2.4f)) - 0.055f;
|
|
g = (g <= 0.0031308f) ? 12.92f * g : 1.055f * POW(g, (1.0f / 2.4f)) - 0.055f;
|
|
b = (b <= 0.0031308f) ? 12.92f * b : 1.055f * POW(b, (1.0f / 2.4f)) - 0.055f;
|
|
//
|
|
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, "XyToRgb XZ (%s %s) rgb (%s %s %s)",
|
|
// String(X,5).c_str(), String(Z,5).c_str(),
|
|
// String(r,5).c_str(), String(g,5).c_str(),String(b,5).c_str());
|
|
|
|
int32_t ir = r * 255.0f + 0.5f;
|
|
int32_t ig = g * 255.0f + 0.5f;
|
|
int32_t ib = b * 255.0f + 0.5f;
|
|
if (rr) { *rr = (ir > 255 ? 255: (ir < 0 ? 0 : ir)); }
|
|
if (rg) { *rg = (ig > 255 ? 255: (ig < 0 ? 0 : ig)); }
|
|
if (rb) { *rb = (ib > 255 ? 255: (ib < 0 ? 0 : ib)); }
|
|
}
|
|
|
|
class LightControllerClass {
|
|
private:
|
|
LightStateClass *_state;
|
|
|
|
// are RGB and CT linked, i.e. if we set CT then RGB channels are off
|
|
bool _ct_rgb_linked = true;
|
|
bool _pwm_multi_channels = false; // treat each channel as independant dimmer
|
|
|
|
public:
|
|
LightControllerClass(LightStateClass& state) {
|
|
_state = &state;
|
|
}
|
|
|
|
void setSubType(uint8_t sub_type) {
|
|
_state->setSubType(sub_type);
|
|
}
|
|
|
|
inline bool setCTRGBLinked(bool ct_rgb_linked) {
|
|
bool prev = _ct_rgb_linked;
|
|
if (_pwm_multi_channels) {
|
|
_ct_rgb_linked = false; // force to false if _pwm_multi_channels is set
|
|
} else {
|
|
_ct_rgb_linked = ct_rgb_linked;
|
|
}
|
|
return prev;
|
|
}
|
|
|
|
void setAlexaCTRange(bool alexa_ct_range) {
|
|
// depending on SetOption82, full or limited CT range
|
|
if (alexa_ct_range) {
|
|
_state->setCTRange(CT_MIN_ALEXA, CT_MAX_ALEXA); // 200..380
|
|
} else {
|
|
_state->setCTRange(CT_MIN, CT_MAX); // 153..500
|
|
}
|
|
}
|
|
|
|
inline bool isCTRGBLinked() {
|
|
return _ct_rgb_linked;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef DEBUG_LIGHT
|
|
void debugLogs() {
|
|
uint8_t r,g,b,c,w;
|
|
_state->getActualRGBCW(&r,&g,&b,&c,&w);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::debugLogs rgb (%d %d %d) cw (%d %d)",
|
|
r, g, b, c, w);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::debugLogs lightCurrent (%d %d %d %d %d)",
|
|
Light.current_color[0], Light.current_color[1], Light.current_color[2],
|
|
Light.current_color[3], Light.current_color[4]);
|
|
}
|
|
#endif
|
|
|
|
void loadSettings() {
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(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);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::loadSettings light_type/sub (%d %d)",
|
|
light_type, Light.subtype);
|
|
#endif
|
|
if (_pwm_multi_channels) {
|
|
_state->setChannelsRaw(Settings.light_color);
|
|
} else {
|
|
// first try setting CW, if zero, it select RGB mode
|
|
_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]);
|
|
|
|
// only if non-multi channel
|
|
// We apply dimmer in priority to RGB
|
|
uint8_t bri = _state->DimmerToBri(Settings.light_dimmer);
|
|
|
|
// The default values are #FFFFFFFFFF, in this case we avoid setting all channels
|
|
// at the same time, see #6534 and #8120
|
|
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) ) {
|
|
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)
|
|
}
|
|
_state->setBriCT(bri);
|
|
_state->setBriRGB(bri);
|
|
_state->setColorMode(LCM_RGB);
|
|
}
|
|
|
|
if (Settings.light_color[0] + Settings.light_color[1] + Settings.light_color[2] > 0) {
|
|
_state->setBriRGB(bri);
|
|
} else {
|
|
_state->setBriCT(bri);
|
|
}
|
|
}
|
|
}
|
|
|
|
void changeCTB(uint16_t new_ct, uint8_t briCT) {
|
|
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
|
|
*
|
|
* ct = 153 = 6500K = Cold = CCWW = FF00
|
|
* ct = 500 = 2000K = Warm = CCWW = 00FF
|
|
*/
|
|
// don't set CT if not supported
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBW > Light.subtype)) {
|
|
return;
|
|
}
|
|
_state->setCT(new_ct);
|
|
_state->setBriCT(briCT);
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_CT); } // try to force CT
|
|
saveSettings();
|
|
calcLevels();
|
|
//debugLogs();
|
|
}
|
|
|
|
void changeDimmer(uint8_t dimmer, uint32_t mode = 0) {
|
|
uint8_t bri = changeUIntScale(dimmer, 0, 100, 0, 255);
|
|
switch (mode) {
|
|
case 1:
|
|
changeBriRGB(bri);
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); } // try to force CT
|
|
break;
|
|
case 2:
|
|
changeBriCT(bri);
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_CT); } // try to force CT
|
|
break;
|
|
default:
|
|
changeBri(bri);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void changeBri(uint8_t bri) {
|
|
_state->setBri(bri);
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
void changeBriRGB(uint8_t bri) {
|
|
_state->setBriRGB(bri);
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
void changeBriCT(uint8_t bri) {
|
|
_state->setBriCT(bri);
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
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
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
// calculate the levels for each channel
|
|
// if no parameter, results are stored in Light.current_color
|
|
void calcLevels(uint8_t *current_color = nullptr) {
|
|
uint8_t r,g,b,c,w,briRGB,briCT;
|
|
if (current_color == nullptr) { current_color = Light.current_color; }
|
|
|
|
if (_pwm_multi_channels) { // if PWM multi channel, no more transformation required
|
|
_state->getChannelsRaw(current_color);
|
|
return;
|
|
}
|
|
|
|
_state->getActualRGBCW(&r,&g,&b,&c,&w);
|
|
briRGB = _state->getBriRGB();
|
|
briCT = _state->getBriCT();
|
|
|
|
current_color[0] = current_color[1] = current_color[2] = 0;
|
|
current_color[3] = current_color[4] = 0;
|
|
switch (Light.subtype) {
|
|
case LST_NONE:
|
|
current_color[0] = 255;
|
|
break;
|
|
case LST_SINGLE:
|
|
current_color[0] = briRGB;
|
|
break;
|
|
case LST_COLDWARM:
|
|
current_color[0] = c;
|
|
current_color[1] = w;
|
|
break;
|
|
case LST_RGBW:
|
|
case LST_RGBCW:
|
|
if (LST_RGBCW == Light.subtype) {
|
|
current_color[3] = c;
|
|
current_color[4] = w;
|
|
} else {
|
|
current_color[3] = briCT;
|
|
}
|
|
// continue
|
|
case LST_RGB:
|
|
current_color[0] = r;
|
|
current_color[1] = g;
|
|
current_color[2] = b;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void changeHSB(uint16_t hue, uint8_t sat, uint8_t briRGB) {
|
|
_state->setHS(hue, sat);
|
|
_state->setBriRGB(briRGB);
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); } // try to force RGB
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
// save the current light state to Settings.
|
|
void saveSettings() {
|
|
if (Light.pwm_multi_channels) {
|
|
// simply save each channel
|
|
_state->getChannelsRaw(Settings.light_color);
|
|
Settings.light_dimmer = 100; // arbitrary value, unused in this mode
|
|
} else {
|
|
uint8_t cm = _state->getColorMode();
|
|
|
|
memset(&Settings.light_color[0], 0, sizeof(Settings.light_color)); // clear all channels
|
|
if (LCM_RGB & cm) { // can be either LCM_RGB or LCM_BOTH
|
|
_state->getRGB(&Settings.light_color[0], &Settings.light_color[1], &Settings.light_color[2]);
|
|
Settings.light_dimmer = _state->BriToDimmer(_state->getBriRGB());
|
|
// anyways we always store RGB with BrightnessRGB
|
|
if (LCM_BOTH == cm) {
|
|
// then store at actual brightness CW/WW if dual mode
|
|
_state->getActualRGBCW(nullptr, nullptr, nullptr, &Settings.light_color[3], &Settings.light_color[4]);
|
|
}
|
|
} else if (LCM_CT == cm) { // cm can only be LCM_CT
|
|
_state->getCW(&Settings.light_color[3], &Settings.light_color[4]);
|
|
Settings.light_dimmer = _state->BriToDimmer(_state->getBriCT());
|
|
}
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(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);
|
|
#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
|
|
void changeChannels(uint8_t *channels) {
|
|
if (Light.pwm_multi_channels) {
|
|
_state->setChannelsRaw(channels);
|
|
} else if (LST_COLDWARM == Light.subtype) {
|
|
// 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);
|
|
}
|
|
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
};
|
|
|
|
|
|
// the singletons for light state and Light Controller
|
|
LightStateClass light_state = LightStateClass();
|
|
LightControllerClass light_controller = LightControllerClass(light_state);
|
|
|
|
/*********************************************************************************************\
|
|
* Change scales from 8 bits to 10 bits and vice versa
|
|
\*********************************************************************************************/
|
|
// 8 to 10 to 8 is garanteed to give the same result
|
|
uint16_t change8to10(uint8_t v) {
|
|
return changeUIntScale(v, 0, 255, 0, 1023);
|
|
}
|
|
// change from 10 bits to 8 bits, but any non-zero input will be non-zero
|
|
uint8_t change10to8(uint16_t v) {
|
|
return (0 == v) ? 0 : changeUIntScale(v, 4, 1023, 1, 255);
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* Gamma correction
|
|
\*********************************************************************************************/
|
|
// Calculate the gamma corrected value for LEDS
|
|
uint16_t ledGamma_internal(uint16_t v, const struct gamma_table_t *gt_ptr) {
|
|
uint16_t from_src = 0;
|
|
uint16_t from_gamma = 0;
|
|
|
|
for (const gamma_table_t *gt = gt_ptr; ; gt++) {
|
|
uint16_t to_src = gt->to_src;
|
|
uint16_t to_gamma = gt->to_gamma;
|
|
if (v <= to_src) {
|
|
return changeUIntScale(v, from_src, to_src, from_gamma, to_gamma);
|
|
}
|
|
from_src = to_src;
|
|
from_gamma = to_gamma;
|
|
}
|
|
}
|
|
// Calculate the reverse gamma value for LEDS
|
|
uint16_t ledGammaReverse_internal(uint16_t vg, const struct gamma_table_t *gt_ptr) {
|
|
uint16_t from_src = 0;
|
|
uint16_t from_gamma = 0;
|
|
|
|
for (const gamma_table_t *gt = gt_ptr; ; gt++) {
|
|
uint16_t to_src = gt->to_src;
|
|
uint16_t to_gamma = gt->to_gamma;
|
|
if (vg <= to_gamma) {
|
|
return changeUIntScale(vg, from_gamma, to_gamma, from_src, to_src);
|
|
}
|
|
from_src = to_src;
|
|
from_gamma = to_gamma;
|
|
}
|
|
}
|
|
|
|
// 10 bits in, 10 bits out
|
|
uint16_t ledGamma10_10(uint16_t v) {
|
|
return ledGamma_internal(v, gamma_table);
|
|
}
|
|
// 10 bits resolution, 8 bits in
|
|
uint16_t ledGamma10(uint8_t v) {
|
|
return ledGamma10_10(change8to10(v));
|
|
}
|
|
|
|
// Legacy function
|
|
uint8_t ledGamma(uint8_t v) {
|
|
return change10to8(ledGamma10(v));
|
|
}
|
|
|
|
/********************************************************************************************/
|
|
|
|
void LightPwmOffset(uint32_t offset)
|
|
{
|
|
Light.pwm_offset = offset;
|
|
}
|
|
|
|
bool LightModuleInit(void)
|
|
{
|
|
light_type = LT_BASIC; // Use basic PWM control if SetOption15 = 0
|
|
|
|
if (Settings.flag.pwm_control) { // SetOption15 - Switch between commands PWM or COLOR/DIMMER/CT/CHANNEL
|
|
for (uint32_t i = 0; i < MAX_PWMS; i++) {
|
|
if (pin[GPIO_PWM1 +i] < 99) { light_type++; } // Use Dimmer/Color control for all PWM as SetOption15 = 1
|
|
}
|
|
}
|
|
|
|
light_flg = 0;
|
|
if (XlgtCall(FUNC_MODULE_INIT)) {
|
|
// serviced
|
|
}
|
|
#ifdef ESP8266
|
|
else if (SONOFF_BN == my_module_type) { // PWM Single color led (White)
|
|
light_type = LT_PWM1;
|
|
}
|
|
else if (SONOFF_LED == my_module_type) { // PWM Dual color led (White warm and cold)
|
|
if (!my_module.io[4]) { // Fix Sonoff Led instabilities
|
|
pinMode(4, OUTPUT); // Stop floating outputs
|
|
digitalWrite(4, LOW);
|
|
}
|
|
if (!my_module.io[5]) {
|
|
pinMode(5, OUTPUT); // Stop floating outputs
|
|
digitalWrite(5, LOW);
|
|
}
|
|
if (!my_module.io[14]) {
|
|
pinMode(14, OUTPUT); // Stop floating outputs
|
|
digitalWrite(14, LOW);
|
|
}
|
|
light_type = LT_PWM2;
|
|
}
|
|
#endif // ESP8266
|
|
|
|
if (light_type > LT_BASIC) {
|
|
devices_present++;
|
|
}
|
|
|
|
// post-process for lights
|
|
if (Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
|
|
uint32_t pwm_channels = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7);
|
|
if (0 == pwm_channels) { pwm_channels = 1; }
|
|
devices_present += pwm_channels - 1; // add the pwm channels controls at the end
|
|
} else if ((Settings.param[P_RGB_REMAP] & 128) && (LST_RGBW <= (light_type & 7))) {
|
|
// if RGBW or RGBCW, and SetOption37 >= 128, we manage RGB and W separately, hence adding a device
|
|
devices_present++;
|
|
}
|
|
|
|
return (light_type > LT_BASIC);
|
|
}
|
|
|
|
// 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;
|
|
|
|
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) {
|
|
pwm_min = ledGamma10_10(pwm_min); // apply gamma correction
|
|
pwm_max = ledGamma10_10(pwm_max); // 0..1023
|
|
}
|
|
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
|
|
|
|
Light.pwm_min = pwm_min;
|
|
Light.pwm_max = pwm_max;
|
|
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("LightCalcPWMRange %d %d - %d %d"), Settings.dimmer_hw_min, Settings.dimmer_hw_max, Light.pwm_min, Light.pwm_max);
|
|
}
|
|
|
|
void LightInit(void)
|
|
{
|
|
Light.device = devices_present;
|
|
Light.subtype = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7); // Always 0 - LST_MAX (5)
|
|
Light.pwm_multi_channels = Settings.flag3.pwm_multi_channels; // SetOption68 - Enable multi-channels PWM instead of Color PWM
|
|
|
|
if (LST_RGBW <= Light.subtype) {
|
|
// only change if RGBW or RGBCW
|
|
// do not allow independant RGB and WC colors
|
|
bool ct_rgb_linked = !(Settings.param[P_RGB_REMAP] & 128);
|
|
light_controller.setCTRGBLinked(ct_rgb_linked);
|
|
}
|
|
|
|
if ((LST_SINGLE <= Light.subtype) && Light.pwm_multi_channels) {
|
|
// we treat each PWM channel as an independant one, hence we switch to
|
|
light_controller.setPWMMultiChannel(true);
|
|
Light.device = devices_present - Light.subtype + 1; // adjust if we also have relays
|
|
} 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
|
|
}
|
|
LightCalcPWMRange();
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightInit Light.pwm_multi_channels=%d Light.subtype=%d Light.device=%d devices_present=%d",
|
|
Light.pwm_multi_channels, Light.subtype, Light.device, devices_present);
|
|
#endif
|
|
|
|
light_controller.setSubType(Light.subtype);
|
|
light_controller.loadSettings();
|
|
light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
|
|
light_controller.calcLevels(); // calculate the initial values (#8058)
|
|
|
|
if (LST_SINGLE == Light.subtype) {
|
|
Settings.light_color[0] = 255; // One channel only supports Dimmer but needs max color
|
|
}
|
|
if (light_type < LT_PWM6) { // PWM
|
|
for (uint32_t i = 0; i < light_type; i++) {
|
|
Settings.pwm_value[i] = 0; // Disable direct PWM control
|
|
if (pin[GPIO_PWM1 +i] < 99) {
|
|
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
|
|
}
|
|
}
|
|
if (pin[GPIO_ARIRFRCV] < 99) {
|
|
if (pin[GPIO_ARIRFSEL] < 99) {
|
|
pinMode(pin[GPIO_ARIRFSEL], OUTPUT);
|
|
digitalWrite(pin[GPIO_ARIRFSEL], 1); // Turn off RF
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t max_scheme = Light.max_scheme;
|
|
if (Light.subtype < LST_RGB) {
|
|
max_scheme = LS_POWER;
|
|
}
|
|
if ((LS_WAKEUP == Settings.light_scheme) || (Settings.light_scheme > max_scheme)) {
|
|
Settings.light_scheme = LS_POWER;
|
|
}
|
|
Light.power = 0;
|
|
Light.update = true;
|
|
Light.wakeup_active = 0;
|
|
if (Settings.flag4.fade_at_startup) {
|
|
Light.fade_initialized = true; // consider fade intialized starting from black
|
|
}
|
|
|
|
LightUpdateColorMapping();
|
|
}
|
|
|
|
void LightUpdateColorMapping(void)
|
|
{
|
|
uint8_t param = Settings.param[P_RGB_REMAP] & 127;
|
|
if (param > 119){ param = 0; }
|
|
|
|
uint8_t tmp[] = {0,1,2,3,4};
|
|
Light.color_remap[0] = tmp[param / 24];
|
|
for (uint32_t i = param / 24; i<4; ++i){
|
|
tmp[i] = tmp[i+1];
|
|
}
|
|
param = param % 24;
|
|
Light.color_remap[1] = tmp[(param / 6)];
|
|
for (uint32_t i = param / 6; i<3; ++i){
|
|
tmp[i] = tmp[i+1];
|
|
}
|
|
param = param % 6;
|
|
Light.color_remap[2] = tmp[(param / 2)];
|
|
for (uint32_t i = param / 2; i<2; ++i){
|
|
tmp[i] = tmp[i+1];
|
|
}
|
|
param = param % 2;
|
|
Light.color_remap[3] = tmp[param];
|
|
Light.color_remap[4] = tmp[1-param];
|
|
|
|
Light.update = true;
|
|
//AddLog_P2(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]);
|
|
}
|
|
|
|
uint8_t LightGetDimmer(uint8_t dimmer) {
|
|
return light_state.getDimmer(dimmer);
|
|
}
|
|
|
|
void LightSetDimmer(uint8_t dimmer) {
|
|
light_controller.changeDimmer(dimmer);
|
|
}
|
|
|
|
void LightGetHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri) {
|
|
light_state.getHSB(hue, sat, bri);
|
|
}
|
|
|
|
void LightHsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b) {
|
|
light_state.HsToRgb(hue, sat, r_r, r_g, r_b);
|
|
}
|
|
|
|
// 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
|
|
if (Light.pwm_multi_channels) {
|
|
if ((device >= Light.device) && (device < Light.device + LST_MAX) && (device <= devices_present)) {
|
|
bri = Light.current_color[device - Light.device];
|
|
}
|
|
} 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();
|
|
}
|
|
}
|
|
return bri;
|
|
}
|
|
|
|
// If SetOption68 is set, set the brightness for a specific device
|
|
void LightSetBri(uint8_t device, uint8_t bri) {
|
|
if (Light.pwm_multi_channels) {
|
|
if ((device >= Light.device) && (device < Light.device + LST_MAX) && (device <= devices_present)) {
|
|
Light.current_color[device - Light.device] = bri;
|
|
light_controller.changeChannels(Light.current_color);
|
|
}
|
|
} 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LightSetColorTemp(uint16_t ct)
|
|
{
|
|
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
|
|
*
|
|
* ct = 153 = 6500K = Cold = CCWW = FF00
|
|
* ct = 600 = 2000K = Warm = CCWW = 00FF
|
|
*/
|
|
// don't set CT if not supported
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) {
|
|
return;
|
|
}
|
|
light_controller.changeCTB(ct, light_state.getBriCT());
|
|
}
|
|
|
|
uint16_t LightGetColorTemp(void)
|
|
{
|
|
// don't calculate CT for unsupported devices
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) {
|
|
return 0;
|
|
}
|
|
return (light_state.getColorMode() & LCM_CT) ? light_state.getCT() : 0;
|
|
}
|
|
|
|
void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value)
|
|
{
|
|
/* lo - below lo is green
|
|
hi - above hi is red
|
|
*/
|
|
if (Settings.flag.light_signal) { // SetOption18 - Pair light signal with CO2 sensor
|
|
uint16_t signal = changeUIntScale(value, lo, hi, 0, 255); // 0..255
|
|
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Light signal %d"), signal);
|
|
light_controller.changeRGB(signal, 255 - signal, 0, true); // keep bri
|
|
Settings.light_scheme = 0;
|
|
if (0 == light_state.getBri()) {
|
|
light_controller.changeBri(50);
|
|
}
|
|
}
|
|
}
|
|
|
|
// convert channels to string, use Option 17 to foce decimal, unless force_hex
|
|
char* LightGetColor(char* scolor, boolean force_hex = false)
|
|
{
|
|
if ((0 == Settings.light_scheme) || (!Light.pwm_multi_channels)) {
|
|
light_controller.calcLevels(); // recalculate levels only if Scheme 0, otherwise we mess up levels
|
|
}
|
|
scolor[0] = '\0';
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
if (!force_hex && Settings.flag.decimal_text) { // SetOption17 - Switch between decimal or hexadecimal output
|
|
snprintf_P(scolor, LIGHT_COLOR_SIZE, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Light.current_color[i]);
|
|
} else {
|
|
snprintf_P(scolor, LIGHT_COLOR_SIZE, PSTR("%s%02X"), scolor, Light.current_color[i]);
|
|
}
|
|
}
|
|
return scolor;
|
|
}
|
|
|
|
void LightPowerOn(void)
|
|
{
|
|
if (light_state.getBri() && !(Light.power)) {
|
|
ExecuteCommandPower(Light.device, POWER_ON, SRC_LIGHT);
|
|
}
|
|
}
|
|
|
|
void LightState(uint8_t append)
|
|
{
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
char scommand[33];
|
|
bool unlinked = !light_controller.isCTRGBLinked() && (Light.subtype >= LST_RGBW); // there are 2 power and dimmers for RGB and White
|
|
|
|
if (append) {
|
|
ResponseAppend_P(PSTR(","));
|
|
} else {
|
|
Response_P(PSTR("{"));
|
|
}
|
|
if (!Light.pwm_multi_channels) {
|
|
if (unlinked) {
|
|
// RGB and W are unlinked, we display the second Power/Dimmer
|
|
ResponseAppend_P(PSTR("\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "%d\":%d"
|
|
",\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "%d\":%d"),
|
|
Light.device, GetStateText(Light.power & 1), Light.device, light_state.getDimmer(1),
|
|
Light.device + 1, GetStateText(Light.power & 2 ? 1 : 0), Light.device + 1, light_state.getDimmer(2));
|
|
} else {
|
|
GetPowerDevice(scommand, Light.device, sizeof(scommand), Settings.flag.device_index_enable); // SetOption26 - Switch between POWER or POWER1
|
|
ResponseAppend_P(PSTR("\"%s\":\"%s\",\"" D_CMND_DIMMER "\":%d"), scommand, GetStateText(Light.power & 1),
|
|
light_state.getDimmer());
|
|
}
|
|
|
|
|
|
if (Light.subtype > LST_SINGLE) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor));
|
|
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);
|
|
}
|
|
// Add White level
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_WHITE "\":%d"), light_state.getDimmer(2));
|
|
}
|
|
// Add CT
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_COLORTEMPERATURE "\":%d"), light_state.getCT());
|
|
}
|
|
// Add status for each channel
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_CHANNEL "\":[" ));
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
uint8_t channel_raw = Light.current_color[i];
|
|
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("]"));
|
|
}
|
|
|
|
if (append) {
|
|
if (Light.subtype >= LST_RGB) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_SCHEME "\":%d"), Settings.light_scheme);
|
|
}
|
|
if (Light.max_scheme > LS_MAX) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_WIDTH "\":%d"), Settings.light_width);
|
|
}
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_FADE "\":\"%s\",\"" D_CMND_SPEED "\":%d,\"" D_CMND_LEDTABLE "\":\"%s\""),
|
|
GetStateText(Settings.light_fade), Settings.light_speed, GetStateText(Settings.light_correction));
|
|
}
|
|
} 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
|
|
light_power_masked = light_power_masked ? 1 : 0; // convert to on/off
|
|
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));
|
|
}
|
|
ResponseAppend_P(PSTR("\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor));
|
|
} // Light.pwm_multi_channels
|
|
|
|
if (!append) {
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
|
|
void LightPreparePower(power_t channels = 0xFFFFFFFF) { // 1 = only RGB, 2 = only CT, 3 = both RGB and CT
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower power=%d Light.power=%d", power, Light.power);
|
|
#endif
|
|
// If multi-channels, then we only switch off channels with a value of zero
|
|
if (Light.pwm_multi_channels) {
|
|
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))) {
|
|
if (!Settings.flag.not_power_linked) { // SetOption20 - Control power in relation to Dimmer/Color/Ct changes
|
|
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
|
|
}
|
|
}
|
|
} else {
|
|
if (light_controller.isCTRGBLinked()) { // linked, standard
|
|
if (light_state.getBri() && !(Light.power)) {
|
|
if (!Settings.flag.not_power_linked) { // SetOption20 - Control power in relation to Dimmer/Color/Ct changes
|
|
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
|
|
if (channels & 1) {
|
|
if (light_state.getBriRGB() && !(Light.power & 1)) {
|
|
if (!Settings.flag.not_power_linked) { // SetOption20 - Control power in relation to Dimmer/Color/Ct changes
|
|
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);
|
|
}
|
|
}
|
|
// White CT
|
|
if (channels & 2) {
|
|
if (light_state.getBriCT() && !(Light.power & 2)) {
|
|
if (!Settings.flag.not_power_linked) { // SetOption20 - Control power in relation to Dimmer/Color/Ct changes
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzUpdatePowerState(Light.device);
|
|
#endif // USE_DOMOTICZ
|
|
}
|
|
|
|
if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
|
|
MqttPublishTeleState();
|
|
}
|
|
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower End power=%d Light.power=%d", power, Light.power);
|
|
#endif
|
|
Light.power = power >> (Light.device - 1); // reset next state, works also with unlinked RGB/CT
|
|
LightState(0);
|
|
}
|
|
|
|
#ifdef USE_LIGHT_PALETTE
|
|
void LightSetPaletteEntry(void)
|
|
{
|
|
uint8_t bri = light_state.getBri();
|
|
uint8_t * palette_entry = &Light.palette[Light.wheel * LST_MAX];
|
|
for (int i = 0; i < LST_MAX; i++) {
|
|
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
|
|
|
|
void LightCycleColor(int8_t direction)
|
|
{
|
|
// 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
|
|
}
|
|
|
|
#ifdef USE_LIGHT_PALETTE
|
|
if (Light.palette_count) {
|
|
if (0 == direction) {
|
|
Light.wheel = random(Light.palette_count);
|
|
}
|
|
else {
|
|
Light.wheel += direction;
|
|
if (Light.wheel >= Light.palette_count) {
|
|
Light.wheel = 0;
|
|
if (direction < 0) Light.wheel = Light.palette_count - 1;
|
|
}
|
|
}
|
|
LightSetPaletteEntry();
|
|
return;
|
|
}
|
|
#endif // USE_LIGHT_PALETTE
|
|
|
|
if (0 == direction) {
|
|
if (Light.random == Light.wheel) {
|
|
Light.random = random(255);
|
|
|
|
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;
|
|
|
|
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: random %d"), Light.random);
|
|
}
|
|
// direction = (Light.random < Light.wheel) ? -1 : 1;
|
|
direction = (Light.random &0x01) ? 1 : -1;
|
|
}
|
|
|
|
// 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
|
|
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)
|
|
|
|
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: random %d, wheel %d, hue %d"), Light.random, Light.wheel, hue);
|
|
|
|
if (!Light.pwm_multi_channels) {
|
|
uint8_t sat;
|
|
light_state.getHSB(nullptr, &sat, nullptr); // Allow user control over Saturation
|
|
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);
|
|
}
|
|
|
|
void LightSetPower(void)
|
|
{
|
|
// Light.power = XdrvMailbox.index;
|
|
Light.old_power = Light.power;
|
|
//Light.power = bitRead(XdrvMailbox.index, Light.device -1);
|
|
uint32_t mask = 1; // default mask
|
|
if (Light.pwm_multi_channels) {
|
|
mask = (1 << Light.subtype) - 1; // wider mask
|
|
} else if (!light_controller.isCTRGBLinked()) {
|
|
mask = 3; // we got 2 devices, for RGB and White
|
|
}
|
|
uint32_t shift = Light.device - 1;
|
|
// If PWM multi_channels
|
|
// Ex: 3 Relays and 4 PWM - devices_present = 7, Light.device = 4, Light.subtype = 4
|
|
// Result: mask = 0b00001111 = 0x0F, shift = 3.
|
|
// Power bits we consider are: 0b01111000 = 0x78
|
|
// If regular situation: devices_present == Light.subtype
|
|
Light.power = (XdrvMailbox.index & (mask << shift)) >> shift;
|
|
if (Light.wakeup_active) {
|
|
Light.wakeup_active--;
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightSetPower XdrvMailbox.index=%d Light.old_power=%d Light.power=%d mask=%d shift=%d",
|
|
XdrvMailbox.index, Light.old_power, Light.power, mask, shift);
|
|
#endif
|
|
if (Light.power != Light.old_power) {
|
|
Light.update = true;
|
|
}
|
|
LightAnimate();
|
|
}
|
|
|
|
// 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
|
|
void LightAnimate(void)
|
|
{
|
|
uint16_t light_still_on = 0;
|
|
bool power_off = false;
|
|
|
|
// make sure we update CT range in case SetOption82 was changed
|
|
light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
|
|
Light.strip_timer_counter++;
|
|
|
|
// set sleep parameter: either settings,
|
|
// or set a maximum of PWM_MAX_SLEEP if light is on or Fade is running
|
|
if (Light.power || Light.fade_running) {
|
|
if (Settings.sleep > PWM_MAX_SLEEP) {
|
|
ssleep = PWM_MAX_SLEEP; // set a maxumum value of 50 milliseconds to ensure that animations are smooth
|
|
} else {
|
|
ssleep = Settings.sleep; // or keep the current sleep if it's lower than 50
|
|
}
|
|
} else {
|
|
ssleep = Settings.sleep;
|
|
}
|
|
|
|
if (!Light.power) { // All channels powered off
|
|
Light.strip_timer_counter = 0;
|
|
if (Settings.light_scheme >= LS_MAX) {
|
|
power_off = true;
|
|
}
|
|
} else {
|
|
switch (Settings.light_scheme) {
|
|
case LS_POWER:
|
|
light_controller.calcLevels(Light.new_color);
|
|
break;
|
|
case LS_WAKEUP:
|
|
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_counter = 0;
|
|
Light.wakeup_dimmer = 0;
|
|
}
|
|
Light.wakeup_counter++;
|
|
if (Light.wakeup_counter > ((Settings.light_wakeup * STATES) / Settings.light_dimmer)) {
|
|
Light.wakeup_counter = 0;
|
|
Light.wakeup_dimmer++;
|
|
if (Light.wakeup_dimmer <= Settings.light_dimmer) {
|
|
light_state.setDimmer(Light.wakeup_dimmer);
|
|
light_controller.calcLevels();
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
Light.new_color[i] = Light.current_color[i];
|
|
}
|
|
} else {
|
|
/*
|
|
Response_P(PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\"}"));
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_CMND_WAKEUP));
|
|
*/
|
|
Response_P(PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\""));
|
|
LightState(1);
|
|
ResponseJsonEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_WAKEUP));
|
|
XdrvRulesProcess();
|
|
|
|
Light.wakeup_active = 0;
|
|
Settings.light_scheme = LS_POWER;
|
|
}
|
|
}
|
|
break;
|
|
case LS_CYCLEUP:
|
|
case LS_CYCLEDN:
|
|
case LS_RANDOM:
|
|
if (LS_CYCLEUP == Settings.light_scheme) {
|
|
LightCycleColor(1);
|
|
} else if (LS_CYCLEDN == Settings.light_scheme) {
|
|
LightCycleColor(-1);
|
|
} else {
|
|
LightCycleColor(0);
|
|
}
|
|
if (Light.pwm_multi_channels) { // See #8058
|
|
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]);
|
|
}
|
|
break;
|
|
default:
|
|
XlgtCall(FUNC_SET_SCHEME);
|
|
}
|
|
|
|
#ifdef USE_DEVICE_GROUPS
|
|
if (Settings.light_scheme != Light.last_scheme) {
|
|
Light.last_scheme = Settings.light_scheme;
|
|
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme);
|
|
Light.devgrp_no_channels_out = false;
|
|
}
|
|
#endif // USE_DEVICE_GROUPS
|
|
}
|
|
|
|
if ((Settings.light_scheme < LS_MAX) || power_off) { // exclude WS281X Neopixel schemes
|
|
|
|
// Apply power modifiers to Light.new_color
|
|
LightApplyPower(Light.new_color, Light.power);
|
|
|
|
// AddLog_P2(LOG_LEVEL_INFO, PSTR("last_color (%02X%02X%02X%02X%02X) new_color (%02X%02X%02X%02X%02X) power %d"),
|
|
// 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],
|
|
// Light.power
|
|
// );
|
|
|
|
if (memcmp(Light.last_color, Light.new_color, Light.subtype)) {
|
|
Light.update = true;
|
|
}
|
|
if (Light.update) {
|
|
#ifdef USE_DEVICE_GROUPS
|
|
if (Light.power) LightSendDeviceGroupStatus(false);
|
|
#endif // USE_DEVICE_GROUPS
|
|
|
|
uint16_t cur_col_10[LST_MAX]; // 10 bits resolution
|
|
Light.update = false;
|
|
|
|
// first set 8 and 10 bits channels
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
Light.last_color[i] = Light.new_color[i];
|
|
// Extend from 8 to 10 bits if no correction (in case no gamma correction is required)
|
|
cur_col_10[i] = change8to10(Light.new_color[i]);
|
|
}
|
|
|
|
if (Light.pwm_multi_channels) {
|
|
calcGammaMultiChannels(cur_col_10);
|
|
} else {
|
|
calcGammaBulbs(cur_col_10);
|
|
|
|
// Now see if we need to mix RGB and True White
|
|
// Valid only for LST_RGBW, LST_RGBCW, rgbwwTable[4] is zero, and white is zero (see doc)
|
|
if ((LST_RGBW <= Light.subtype) && (0 == Settings.rgbwwTable[4]) && (0 == cur_col_10[3]+cur_col_10[4])) {
|
|
uint32_t min_rgb_10 = min3(cur_col_10[0], cur_col_10[1], cur_col_10[2]);
|
|
for (uint32_t i=0; i<3; i++) {
|
|
// substract white and adjust according to rgbwwTable
|
|
uint32_t adjust10 = change8to10(Settings.rgbwwTable[i]);
|
|
cur_col_10[i] = changeUIntScale(cur_col_10[i] - min_rgb_10, 0, 1023, 0, adjust10);
|
|
}
|
|
|
|
// compute the adjusted white levels for 10 and 8 bits
|
|
uint32_t adjust_w_10 = changeUIntScale(Settings.rgbwwTable[3], 0, 255, 0, 1023);
|
|
uint32_t white_10 = changeUIntScale(min_rgb_10, 0, 1023, 0, adjust_w_10); // set white power down corrected with rgbwwTable[3]
|
|
if (LST_RGBW == Light.subtype) {
|
|
// we simply set the white channel
|
|
cur_col_10[3] = white_10;
|
|
} else { // LST_RGBCW
|
|
// we distribute white between cold and warm according to CT value
|
|
uint32_t ct = light_state.getCT10bits();
|
|
cur_col_10[4] = changeUIntScale(ct, 0, 1023, 0, white_10);
|
|
cur_col_10[3] = white_10 - cur_col_10[4];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply RGBWWTable only if Settings.rgbwwTable[4] != 0
|
|
if (0 != Settings.rgbwwTable[4]) {
|
|
for (uint32_t i = 0; i<Light.subtype; i++) {
|
|
uint32_t adjust = change8to10(Settings.rgbwwTable[i]);
|
|
cur_col_10[i] = changeUIntScale(cur_col_10[i], 0, 1023, 0, adjust);
|
|
}
|
|
}
|
|
|
|
// final adjusments for PMW, post-gamma correction
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
// scale from 0..1023 to 0..pwm_range, but keep any non-zero value to at least 1
|
|
cur_col_10[i] = (cur_col_10[i] > 0) ? changeUIntScale(cur_col_10[i], 1, 1023, 1, Settings.pwm_range) : 0;
|
|
}
|
|
|
|
// apply port remapping on both 8 bits and 10 bits versions
|
|
uint16_t orig_col_10bits[LST_MAX];
|
|
memcpy(orig_col_10bits, cur_col_10, sizeof(orig_col_10bits));
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
cur_col_10[i] = orig_col_10bits[Light.color_remap[i]];
|
|
}
|
|
|
|
if (!Settings.light_fade || skip_light_fade || power_off || (!Light.fade_initialized)) { // no fade
|
|
// record the current value for a future Fade
|
|
memcpy(Light.fade_start_10, cur_col_10, sizeof(Light.fade_start_10));
|
|
// push the final values at 8 and 10 bits resolution to the PWMs
|
|
LightSetOutputs(cur_col_10);
|
|
Light.fade_initialized = true; // it is now ok to fade
|
|
} 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));
|
|
}
|
|
memcpy(Light.fade_end_10, cur_col_10, sizeof(Light.fade_start_10));
|
|
Light.fade_running = true;
|
|
Light.fade_duration = 0; // set the value to zero to force a recompute
|
|
Light.fade_start = 0;
|
|
// Fade will applied immediately below
|
|
}
|
|
}
|
|
if (Light.fade_running) {
|
|
if (LightApplyFade()) {
|
|
// AddLog_P2(LOG_LEVEL_INFO, PSTR("LightApplyFade %d %d %d %d %d"),
|
|
// 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]);
|
|
|
|
LightSetOutputs(Light.fade_cur_10);
|
|
}
|
|
}
|
|
#ifdef USE_PWM_DIMMER
|
|
// If the power is off and the fade is done, turn the relay off.
|
|
if (PWM_DIMMER == my_module_type && !Light.power && !Light.fade_running) PWMDimmerSetPower();
|
|
#endif // USE_PWM_DIMMER
|
|
}
|
|
}
|
|
|
|
bool isChannelGammaCorrected(uint32_t channel) {
|
|
if (!Settings.light_correction) { return false; } // Gamma correction not activated
|
|
if (channel >= Light.subtype) { return false; } // Out of range
|
|
#ifdef ESP8266
|
|
if ((PHILIPS == my_module_type) || (Settings.flag4.pwm_ct_mode)) {
|
|
if ((LST_COLDWARM == Light.subtype) && (1 == channel)) { return false; } // PMW reserved for CT
|
|
if ((LST_RGBCW == Light.subtype) && (4 == channel)) { return false; } // PMW reserved for CT
|
|
}
|
|
#endif // ESP8266
|
|
return true;
|
|
}
|
|
|
|
// is the channel a regular PWM or ColorTemp control
|
|
bool isChannelCT(uint32_t channel) {
|
|
#ifdef ESP8266
|
|
if ((PHILIPS == my_module_type) || (Settings.flag4.pwm_ct_mode)) {
|
|
if ((LST_COLDWARM == Light.subtype) && (1 == channel)) { return true; } // PMW reserved for CT
|
|
if ((LST_RGBCW == Light.subtype) && (4 == channel)) { return true; } // PMW reserved for CT
|
|
}
|
|
#endif // ESP8266
|
|
return false;
|
|
}
|
|
|
|
// 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)) {
|
|
return ledGamma_internal(v, gamma_table_fast);
|
|
} else {
|
|
return v;
|
|
}
|
|
}
|
|
uint16_t fadeGammaReverse(uint32_t channel, uint16_t vg) {
|
|
if (isChannelGammaCorrected(channel)) {
|
|
return ledGammaReverse_internal(vg, gamma_table_fast);
|
|
} else {
|
|
return vg;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// Check if we need to calculate the duration
|
|
if (0 == Light.fade_duration) {
|
|
Light.fade_start = now;
|
|
// 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++) {
|
|
int32_t channel_distance = fadeGammaReverse(i, Light.fade_end_10[i]) - fadeGammaReverse(i, Light.fade_start_10[i]);
|
|
if (channel_distance < 0) { channel_distance = - channel_distance; }
|
|
if (channel_distance > distance) { distance = channel_distance; }
|
|
}
|
|
if (distance > 0) {
|
|
// compute the duration of the animation
|
|
// Note: Settings.light_speed is the number of half-seconds for a 100% fade,
|
|
// i.e. light_speed=1 means 1024 steps in 500ms
|
|
Light.fade_duration = (distance * Settings.light_speed * 500) / 1023;
|
|
if (Settings.save_data) {
|
|
// Also postpone the save_data for the duration of the Fade (in seconds)
|
|
uint32_t delay_seconds = 1 + (Light.fade_duration + 999) / 1000; // add one more second
|
|
// AddLog_P2(LOG_LEVEL_INFO, PSTR("delay_seconds %d, save_data_counter %d"), delay_seconds, save_data_counter);
|
|
if (save_data_counter < delay_seconds) {
|
|
save_data_counter = delay_seconds; // pospone
|
|
}
|
|
}
|
|
} else {
|
|
// no fade needed, we keep the duration at zero, it will fallback directly to end of fade
|
|
Light.fade_running = false;
|
|
}
|
|
}
|
|
|
|
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);
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
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]);
|
|
}
|
|
} else {
|
|
// stop fade
|
|
//AddLop_P2(LOG_LEVEL_DEBUG, PSTR("Stop fade"));
|
|
Light.fade_running = false;
|
|
Light.fade_start = 0;
|
|
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));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// 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
|
|
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, "Animate>> Light.power=%d Light.new_color=[%d,%d,%d,%d,%d]",
|
|
// 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;
|
|
}
|
|
} else if (!power) {
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
new_color[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LightSetOutputs(const uint16_t *cur_col_10) {
|
|
// now apply the actual PWM values, adjusted and remapped 10-bits range
|
|
if (light_type < LT_PWM6) { // only for direct PWM lights, not for Tuya, Armtronix...
|
|
for (uint32_t i = 0; i < (Light.subtype - Light.pwm_offset); i++) {
|
|
if (pin[GPIO_PWM1 +i] < 99) {
|
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "Cur_Col%d 10 bits %d"), i, cur_col_10[i]);
|
|
uint16_t cur_col = cur_col_10[i + Light.pwm_offset];
|
|
if (!isChannelCT(i)) { // if CT don't use pwm_min and pwm_max
|
|
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
|
|
}
|
|
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - cur_col : cur_col);
|
|
}
|
|
}
|
|
}
|
|
|
|
// char msg[24];
|
|
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: Channels %s"), ToHex_P((const unsigned char *)cur_col_10, 10, msg, sizeof(msg)));
|
|
|
|
uint8_t cur_col[LST_MAX];
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
cur_col[i] = change10to8(cur_col_10[i]);
|
|
}
|
|
// Some devices need scaled RGB like Sonoff L1
|
|
// TODO, should be probably moved to the Sonoff L1 support code
|
|
uint8_t scale_col[3];
|
|
uint32_t max = (cur_col[0] > cur_col[1] && cur_col[0] > cur_col[2]) ? cur_col[0] : (cur_col[1] > cur_col[2]) ? cur_col[1] : cur_col[2]; // 0..255
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
scale_col[i] = (0 == max) ? 255 : (255 > max) ? changeUIntScale(cur_col[i], 0, max, 0, 255) : cur_col[i];
|
|
}
|
|
|
|
char *tmp_data = XdrvMailbox.data;
|
|
char *tmp_topic = XdrvMailbox.topic;
|
|
XdrvMailbox.data = (char*)cur_col;
|
|
XdrvMailbox.topic = (char*)scale_col;
|
|
if (XlgtCall(FUNC_SET_CHANNELS)) { /* Serviced */ }
|
|
else if (XdrvCall(FUNC_SET_CHANNELS)) { /* Serviced */ }
|
|
XdrvMailbox.data = tmp_data;
|
|
XdrvMailbox.topic = tmp_topic;
|
|
}
|
|
|
|
// Just apply basic Gamma to each channel
|
|
void calcGammaMultiChannels(uint16_t cur_col_10[5]) {
|
|
// Apply gamma correction for 8 and 10 bits resolutions, if needed
|
|
if (Settings.light_correction) {
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
cur_col_10[i] = ledGamma10_10(cur_col_10[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void calcGammaBulbs(uint16_t cur_col_10[5]) {
|
|
// Apply gamma correction for 8 and 10 bits resolutions, if needed
|
|
|
|
// First apply combined correction to the overall white power
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
|
// channels for white are always the last two channels
|
|
uint32_t cw1 = Light.subtype - 1; // address for the ColorTone PWM
|
|
uint32_t cw0 = Light.subtype - 2; // address for the White Brightness PWM
|
|
uint16_t white_bri10 = cur_col_10[cw0] + cur_col_10[cw1]; // cumulated brightness
|
|
uint16_t white_bri10_1023 = (white_bri10 > 1023) ? 1023 : white_bri10; // max 1023
|
|
|
|
#ifdef ESP8266
|
|
if ((PHILIPS == my_module_type) || (Settings.flag4.pwm_ct_mode)) { // channel 1 is the color tone, mapped to cold channel (0..255)
|
|
// Xiaomi Philips bulbs follow a different scheme:
|
|
cur_col_10[cw1] = light_state.getCT10bits();
|
|
// channel 0=intensity, channel1=temperature
|
|
if (Settings.light_correction) { // gamma correction
|
|
cur_col_10[cw0] = ledGamma10_10(white_bri10_1023); // 10 bits gamma correction
|
|
} else {
|
|
cur_col_10[cw0] = white_bri10_1023; // no gamma, extend to 10 bits
|
|
}
|
|
} else
|
|
#endif // ESP8266
|
|
if (Settings.light_correction) {
|
|
// if sum of both channels is > 255, then channels are probably uncorrelated
|
|
if (white_bri10 <= 1031) { // take a margin of 8 above 1023 to account for rounding errors
|
|
// we calculate the gamma corrected sum of CW + WW
|
|
uint16_t white_bri_gamma10 = ledGamma10_10(white_bri10_1023);
|
|
// then we split the total energy among the cold and warm leds
|
|
cur_col_10[cw0] = changeUIntScale(cur_col_10[cw0], 0, white_bri10_1023, 0, white_bri_gamma10);
|
|
cur_col_10[cw1] = changeUIntScale(cur_col_10[cw1], 0, white_bri10_1023, 0, white_bri_gamma10);
|
|
} else {
|
|
cur_col_10[cw0] = ledGamma10_10(cur_col_10[cw0]);
|
|
cur_col_10[cw1] = ledGamma10_10(cur_col_10[cw1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Settings.light_correction) {
|
|
// then apply gamma correction to RGB channels
|
|
if (LST_RGB <= Light.subtype) {
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
cur_col_10[i] = ledGamma10_10(cur_col_10[i]);
|
|
}
|
|
}
|
|
// If RGBW or Single channel, also adjust White channel
|
|
if ((LST_SINGLE == Light.subtype) || (LST_RGBW == Light.subtype)) {
|
|
cur_col_10[Light.subtype - 1] = ledGamma10_10(cur_col_10[Light.subtype - 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_DEVICE_GROUPS
|
|
void LightSendDeviceGroupStatus(bool force)
|
|
{
|
|
static uint8_t last_channels[LST_MAX];
|
|
static uint8_t last_bri;
|
|
|
|
uint8_t bri = light_state.getBri();
|
|
bool send_bri_update = (force || bri != last_bri);
|
|
|
|
if (Light.subtype > LST_SINGLE && !Light.devgrp_no_channels_out) {
|
|
uint8_t channels[LST_MAX];
|
|
light_state.getChannels(channels);
|
|
if (force || memcmp(last_channels, channels, LST_MAX)) {
|
|
memcpy(last_channels, channels, LST_MAX);
|
|
SendLocalDeviceGroupMessage((send_bri_update ? DGR_MSGTYP_PARTIAL_UPDATE : DGR_MSGTYP_UPDATE), DGR_ITEM_LIGHT_CHANNELS, channels);
|
|
}
|
|
}
|
|
if (send_bri_update) {
|
|
last_bri = bri;
|
|
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_BRI, light_state.getBri());
|
|
}
|
|
}
|
|
|
|
void LightHandleDevGroupItem(void)
|
|
{
|
|
static bool send_state = false;
|
|
static bool restore_power = false;
|
|
bool more_to_come;
|
|
uint32_t value = XdrvMailbox.payload;
|
|
#ifdef USE_PWM_DIMMER_REMOTE
|
|
if (*XdrvMailbox.topic) return; // Ignore updates from other device groups
|
|
#endif // USE_PWM_DIMMER_REMOTE
|
|
switch (XdrvMailbox.command_code) {
|
|
case DGR_ITEM_EOL:
|
|
more_to_come = (XdrvMailbox.index & DGR_FLAG_MORE_TO_COME);
|
|
if (restore_power && !more_to_come) {
|
|
restore_power = false;
|
|
Light.power = Light.old_power;
|
|
}
|
|
|
|
LightAnimate();
|
|
|
|
if (send_state && !more_to_come) {
|
|
light_controller.saveSettings();
|
|
if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
|
|
MqttPublishTeleState();
|
|
}
|
|
send_state = false;
|
|
}
|
|
break;
|
|
case DGR_ITEM_LIGHT_BRI:
|
|
if (light_state.getBri() != value) {
|
|
light_state.setBri(value);
|
|
send_state = true;
|
|
}
|
|
break;
|
|
case DGR_ITEM_LIGHT_SCHEME:
|
|
if (Settings.light_scheme != value) {
|
|
Light.last_scheme = Settings.light_scheme = value;
|
|
Light.devgrp_no_channels_out = (value != 0);
|
|
send_state = true;
|
|
}
|
|
break;
|
|
case DGR_ITEM_LIGHT_CHANNELS:
|
|
light_controller.changeChannels((uint8_t *)XdrvMailbox.data);
|
|
send_state = true;
|
|
break;
|
|
case DGR_ITEM_LIGHT_FIXED_COLOR:
|
|
if (Light.subtype >= LST_RGBW) {
|
|
send_state = true;
|
|
#ifdef USE_LIGHT_PALETTE
|
|
if (Light.palette_count) {
|
|
Light.wheel = value % Light.palette_count;
|
|
LightSetPaletteEntry();
|
|
break;
|
|
}
|
|
#endif // !USE_LIGHT_PALETTE
|
|
value = value % MAX_FIXED_COLOR;
|
|
if (value) {
|
|
bool save_decimal_text = Settings.flag.decimal_text;
|
|
char str[16];
|
|
LightColorEntry(str, sprintf_P(str, PSTR("%u"), value));
|
|
Settings.flag.decimal_text = save_decimal_text;
|
|
uint32_t old_bri = light_state.getBri();
|
|
light_controller.changeChannels(Light.entry_color);
|
|
light_controller.changeBri(old_bri);
|
|
Settings.light_scheme = 0;
|
|
Light.devgrp_no_channels_out = false;
|
|
}
|
|
else {
|
|
light_state.setColorMode(LCM_CT);
|
|
}
|
|
if (!restore_power && !Light.power) {
|
|
Light.old_power = Light.power;
|
|
Light.power = 0xff;
|
|
restore_power = true;
|
|
}
|
|
}
|
|
break;
|
|
case DGR_ITEM_LIGHT_FADE:
|
|
if (Settings.light_fade != value) {
|
|
Settings.light_fade = value;
|
|
send_state = true;
|
|
}
|
|
break;
|
|
case DGR_ITEM_LIGHT_SPEED:
|
|
if (Settings.light_speed != value && value > 0 && value <= 40) {
|
|
Settings.light_speed = value;
|
|
send_state = true;
|
|
}
|
|
break;
|
|
case DGR_ITEM_STATUS:
|
|
SendLocalDeviceGroupMessage(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);
|
|
LightSendDeviceGroupStatus(true);
|
|
break;
|
|
}
|
|
}
|
|
#endif // USE_DEVICE_GROUPS
|
|
|
|
/*********************************************************************************************\
|
|
* Commands
|
|
\*********************************************************************************************/
|
|
|
|
bool LightColorEntry(char *buffer, uint32_t buffer_length)
|
|
{
|
|
char scolor[10];
|
|
char *p;
|
|
char *str;
|
|
uint32_t entry_type = 0; // Invalid
|
|
uint8_t value = Light.fixed_color_index;
|
|
#ifdef USE_LIGHT_PALETTE
|
|
if (Light.palette_count) value = Light.wheel;
|
|
#endif // USE_LIGHT_PALETTE
|
|
|
|
if (buffer[0] == '#') { // Optional hexadecimal entry
|
|
buffer++;
|
|
buffer_length--;
|
|
}
|
|
|
|
if (Light.subtype >= LST_RGB) {
|
|
char option = (1 == buffer_length) ? buffer[0] : '\0';
|
|
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++;
|
|
}
|
|
}
|
|
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--;
|
|
}
|
|
} else {
|
|
value = atoi(buffer);
|
|
}
|
|
#ifdef USE_LIGHT_PALETTE
|
|
if (Light.palette_count) value = value % Light.palette_count;
|
|
#endif // USE_LIGHT_PALETTE
|
|
}
|
|
|
|
memset(&Light.entry_color, 0x00, sizeof(Light.entry_color));
|
|
// erase all channels except if the last character is '=', #6799
|
|
while ((buffer_length > 0) && ('=' == buffer[buffer_length - 1])) {
|
|
buffer_length--; // remove all trailing '='
|
|
memcpy(&Light.entry_color, &Light.current_color, sizeof(Light.entry_color));
|
|
}
|
|
if (strstr(buffer, ",") != nullptr) { // Decimal entry
|
|
int8_t i = 0;
|
|
for (str = strtok_r(buffer, ",", &p); str && i < 6; str = strtok_r(nullptr, ",", &p)) {
|
|
if (i < LST_MAX) {
|
|
Light.entry_color[i++] = atoi(str);
|
|
}
|
|
}
|
|
entry_type = 2; // Decimal
|
|
}
|
|
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++) {
|
|
strlcpy(scolor, buffer + (i *2), 3);
|
|
Light.entry_color[i] = (uint8_t)strtol(scolor, &p, 16);
|
|
}
|
|
entry_type = 1; // Hexadecimal
|
|
}
|
|
#ifdef USE_LIGHT_PALETTE
|
|
else if (Light.palette_count) {
|
|
value--;
|
|
Light.wheel = value;
|
|
memcpy_P(&Light.entry_color, &Light.palette[value * LST_MAX], LST_MAX);
|
|
entry_type = 1; // Hexadecimal
|
|
}
|
|
#endif // USE_LIGHT_PALETTE
|
|
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);
|
|
entry_type = 1; // Hexadecimal
|
|
}
|
|
else if ((value > 199) && (value <= 199 + MAX_FIXED_COLD_WARM)) {
|
|
if (LST_RGBW == Light.subtype) {
|
|
memcpy_P(&Light.entry_color[3], &kFixedWhite[value -200], 1);
|
|
entry_type = 1; // Hexadecimal
|
|
}
|
|
else if (LST_COLDWARM == Light.subtype) {
|
|
memcpy_P(&Light.entry_color, &kFixedColdWarm[value -200], 2);
|
|
entry_type = 1; // Hexadecimal
|
|
}
|
|
else if (LST_RGBCW == Light.subtype) {
|
|
memcpy_P(&Light.entry_color[3], &kFixedColdWarm[value -200], 2);
|
|
entry_type = 1; // Hexadecimal
|
|
}
|
|
}
|
|
if (entry_type) {
|
|
Settings.flag.decimal_text = entry_type -1; // SetOption17 - Switch between decimal or hexadecimal output
|
|
}
|
|
return (entry_type);
|
|
}
|
|
|
|
/********************************************************************************************/
|
|
|
|
void CmndSupportColor(void)
|
|
{
|
|
bool valid_entry = false;
|
|
bool coldim = false;
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
valid_entry = LightColorEntry(XdrvMailbox.data, XdrvMailbox.data_len);
|
|
if (valid_entry) {
|
|
if (XdrvMailbox.index <= 2) { // Color(1), 2
|
|
#ifdef USE_LIGHT_PALETTE
|
|
if (Light.palette_count && XdrvMailbox.index == 2) {
|
|
LightSetPaletteEntry();
|
|
}
|
|
else {
|
|
#endif // USE_LIGHT_PALETTE
|
|
uint32_t old_bri = light_state.getBri();
|
|
// change all channels to specified values
|
|
light_controller.changeChannels(Light.entry_color);
|
|
if (2 == XdrvMailbox.index) {
|
|
// If Color2, set back old brightness
|
|
light_controller.changeBri(old_bri);
|
|
}
|
|
#ifdef USE_LIGHT_PALETTE
|
|
}
|
|
#endif // USE_LIGHT_PALETTE
|
|
Settings.light_scheme = 0;
|
|
coldim = true;
|
|
} else { // Color3, 4, 5 and 6
|
|
for (uint32_t i = 0; i < LST_RGB; i++) {
|
|
Settings.ws_color[XdrvMailbox.index -3][i] = Light.entry_color[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
if (!valid_entry && (XdrvMailbox.index <= 2)) {
|
|
ResponseCmndChar(LightGetColor(scolor));
|
|
}
|
|
if (XdrvMailbox.index >= 3) {
|
|
scolor[0] = '\0';
|
|
for (uint32_t i = 0; i < LST_RGB; i++) {
|
|
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]);
|
|
} else {
|
|
snprintf_P(scolor, sizeof(scolor), PSTR("%s%02X"), scolor, Settings.ws_color[XdrvMailbox.index -3][i]);
|
|
}
|
|
}
|
|
ResponseCmndIdxChar(scolor);
|
|
}
|
|
if (coldim) {
|
|
LightPreparePower(); // no parameter, recalculate Power for all channels
|
|
}
|
|
}
|
|
|
|
void CmndColor(void)
|
|
{
|
|
// 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
|
|
if ((Light.subtype > LST_SINGLE) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) {
|
|
CmndSupportColor();
|
|
}
|
|
}
|
|
|
|
void CmndWhite(void)
|
|
{
|
|
// White - Show current White (=Dimmer2) state
|
|
// White 0..100 - Set White colors dimmer state
|
|
if (Light.pwm_multi_channels) { return; }
|
|
if ( ((Light.subtype >= LST_RGBW) || (LST_COLDWARM == Light.subtype)) && (XdrvMailbox.index == 1)) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
light_controller.changeDimmer(XdrvMailbox.payload, 2);
|
|
LightPreparePower(2);
|
|
} else {
|
|
ResponseCmndNumber(light_state.getDimmer(2));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndChannel(void)
|
|
{
|
|
// 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
|
|
if ((XdrvMailbox.index >= Light.device) && (XdrvMailbox.index < Light.device + Light.subtype )) {
|
|
uint32_t light_index = XdrvMailbox.index - Light.device;
|
|
power_t coldim = 0; // bit flag to update
|
|
|
|
// Handle +/- special command
|
|
if (1 == XdrvMailbox.data_len) {
|
|
uint8_t channel = changeUIntScale(Light.current_color[light_index],0,255,0,100);
|
|
if ('+' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (channel > 89) ? 100 : channel + 10;
|
|
} else if ('-' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (channel < 11) ? 1 : channel - 10;
|
|
}
|
|
}
|
|
|
|
// Set "Channel" directly - this allows Color and Direct PWM control to coexist
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
Light.current_color[light_index] = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
|
|
if (Light.pwm_multi_channels) {
|
|
coldim = 1 << light_index; // change the specified channel
|
|
} else {
|
|
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
|
|
}
|
|
}
|
|
light_controller.changeChannels(Light.current_color);
|
|
}
|
|
ResponseCmndIdxNumber(changeUIntScale(Light.current_color[light_index],0,255,0,100));
|
|
if (coldim) {
|
|
LightPreparePower(coldim);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndHsbColor(void)
|
|
{
|
|
// 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
|
|
if (Light.subtype >= LST_RGB) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
uint16_t c_hue;
|
|
uint8_t c_sat;
|
|
light_state.getHSB(&c_hue, &c_sat, nullptr);
|
|
uint32_t HSB[3];
|
|
HSB[0] = c_hue;
|
|
HSB[1] = c_sat;
|
|
HSB[2] = light_state.getBriRGB();
|
|
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++) {
|
|
if (HSB[i] > 100) { HSB[i] == 100; }
|
|
HSB[i] = changeUIntScale(HSB[i], 0, 100, 0, 255); // change sat and bri to 0..255
|
|
}
|
|
}
|
|
light_controller.changeHSB(HSB[0], HSB[1], HSB[2]);
|
|
LightPreparePower(1);
|
|
} else {
|
|
LightState(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndScheme(void)
|
|
{
|
|
// Scheme 0..12 - Select one of schemes 0 to 12
|
|
// 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
|
|
if (Light.subtype >= LST_RGB) {
|
|
uint32_t max_scheme = Light.max_scheme;
|
|
|
|
if (1 == XdrvMailbox.data_len) {
|
|
if (('+' == XdrvMailbox.data[0]) && (Settings.light_scheme < max_scheme)) {
|
|
XdrvMailbox.payload = Settings.light_scheme + ((0 == Settings.light_scheme) ? 2 : 1); // Skip wakeup
|
|
}
|
|
else if (('-' == XdrvMailbox.data[0]) && (Settings.light_scheme > 0)) {
|
|
XdrvMailbox.payload = Settings.light_scheme - ((2 == Settings.light_scheme) ? 2 : 1); // Skip wakeup
|
|
}
|
|
}
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= max_scheme)) {
|
|
uint32_t parm[2];
|
|
if (ParseParameters(2, parm) > 1) {
|
|
Light.wheel = parm[1];
|
|
#ifdef USE_LIGHT_PALETTE
|
|
Light.wheel--;
|
|
#endif // USE_LIGHT_PALETTE
|
|
}
|
|
Settings.light_scheme = XdrvMailbox.payload;
|
|
if (LS_WAKEUP == Settings.light_scheme) {
|
|
Light.wakeup_active = 3;
|
|
}
|
|
LightPowerOn();
|
|
Light.strip_timer_counter = 0;
|
|
// Publish state message for Hass
|
|
if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
|
|
MqttPublishTeleState();
|
|
}
|
|
}
|
|
ResponseCmndNumber(Settings.light_scheme);
|
|
}
|
|
}
|
|
|
|
void CmndWakeup(void)
|
|
{
|
|
// Wakeup - Start wakeup light
|
|
// Wakeup 0..100 - Start wakeup light to dimmer value 0..100
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
light_controller.changeDimmer(XdrvMailbox.payload);
|
|
}
|
|
Light.wakeup_active = 3;
|
|
Settings.light_scheme = LS_WAKEUP;
|
|
LightPowerOn();
|
|
ResponseCmndChar(D_JSON_STARTED);
|
|
}
|
|
|
|
void CmndColorTemperature(void)
|
|
{
|
|
// 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
|
|
if (Light.pwm_multi_channels) { return; }
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) { // ColorTemp
|
|
uint32_t ct = light_state.getCT();
|
|
if (1 == XdrvMailbox.data_len) {
|
|
if ('+' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (ct > (CT_MAX-34)) ? CT_MAX : ct + 34;
|
|
}
|
|
else if ('-' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (ct < (CT_MIN+34)) ? CT_MIN : ct - 34;
|
|
}
|
|
}
|
|
if ((XdrvMailbox.payload >= CT_MIN) && (XdrvMailbox.payload <= CT_MAX)) { // https://developers.meethue.com/documentation/core-concepts
|
|
light_controller.changeCTB(XdrvMailbox.payload, light_state.getBriCT());
|
|
LightPreparePower(2);
|
|
} else {
|
|
ResponseCmndNumber(ct);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndDimmer(void)
|
|
{
|
|
// 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
|
|
// Dimmer3 0..100 - Change both RGB and W(W) Dimmers with no fading
|
|
// Dimmer<x> + - Incerement Dimmer in steps of 10
|
|
// Dimmer<x> - - Decrement Dimmer in steps of 10
|
|
uint32_t dimmer;
|
|
if (XdrvMailbox.index == 3) {
|
|
skip_light_fade = true;
|
|
XdrvMailbox.index = 0;
|
|
}
|
|
else if (XdrvMailbox.index > 2) {
|
|
XdrvMailbox.index = 1;
|
|
}
|
|
|
|
if ((light_controller.isCTRGBLinked()) || (0 == XdrvMailbox.index)) {
|
|
dimmer = light_state.getDimmer();
|
|
} else {
|
|
dimmer = light_state.getDimmer(XdrvMailbox.index);
|
|
}
|
|
// Handle +/- special command
|
|
if (1 == XdrvMailbox.data_len) {
|
|
if ('+' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (dimmer > 89) ? 100 : dimmer + 10;
|
|
} else if ('-' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (dimmer < 11) ? 1 : dimmer - 10;
|
|
}
|
|
}
|
|
// If value is ok, change it, otherwise report old value
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
if (light_controller.isCTRGBLinked()) {
|
|
// normal state, linked RGB and CW
|
|
light_controller.changeDimmer(XdrvMailbox.payload);
|
|
LightPreparePower();
|
|
} else {
|
|
if (0 != XdrvMailbox.index) {
|
|
light_controller.changeDimmer(XdrvMailbox.payload, XdrvMailbox.index);
|
|
LightPreparePower(1 << (XdrvMailbox.index - 1)); // recalculate only the target dimmer
|
|
} else {
|
|
// change both dimmers
|
|
light_controller.changeDimmer(XdrvMailbox.payload, 1);
|
|
light_controller.changeDimmer(XdrvMailbox.payload, 2);
|
|
LightPreparePower();
|
|
}
|
|
}
|
|
#if defined(USE_PWM_DIMMER) && defined(USE_DEVICE_GROUPS)
|
|
uint8_t bri = light_state.getBri();
|
|
if (bri != Settings.bri_power_on) {
|
|
Settings.bri_power_on = bri;
|
|
SendLocalDeviceGroupMessage(DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_BRI_POWER_ON, Settings.bri_power_on);
|
|
}
|
|
#endif // USE_PWM_DIMMER && USE_DEVICE_GROUPS
|
|
Light.update = true;
|
|
if (skip_light_fade) LightAnimate();
|
|
} else {
|
|
ResponseCmndNumber(dimmer);
|
|
}
|
|
skip_light_fade = false;
|
|
}
|
|
|
|
void CmndDimmerRange(void)
|
|
{
|
|
// 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
|
|
if (XdrvMailbox.data_len > 0) {
|
|
uint32_t parm[2];
|
|
parm[0] = Settings.dimmer_hw_min;
|
|
parm[1] = Settings.dimmer_hw_max;
|
|
ParseParameters(2, parm);
|
|
if (parm[0] < parm[1]) {
|
|
Settings.dimmer_hw_min = parm[0];
|
|
Settings.dimmer_hw_max = parm[1];
|
|
} else {
|
|
Settings.dimmer_hw_min = parm[1];
|
|
Settings.dimmer_hw_max = parm[0];
|
|
}
|
|
LightCalcPWMRange();
|
|
Light.update = true;
|
|
}
|
|
Response_P(PSTR("{\"" D_CMND_DIMMER_RANGE "\":{\"Min\":%d,\"Max\":%d}}"), Settings.dimmer_hw_min, Settings.dimmer_hw_max);
|
|
}
|
|
|
|
void CmndLedTable(void)
|
|
{
|
|
// LedTable - Show current LedTable state
|
|
// LedTable 0 - Turn LedTable Off
|
|
// LedTable On - Turn LedTable On
|
|
// LedTable Toggle - Toggle LedTable state
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
|
|
switch (XdrvMailbox.payload) {
|
|
case 0: // Off
|
|
case 1: // On
|
|
Settings.light_correction = XdrvMailbox.payload;
|
|
break;
|
|
case 2: // Toggle
|
|
Settings.light_correction ^= 1;
|
|
break;
|
|
}
|
|
LightCalcPWMRange();
|
|
Light.update = true;
|
|
}
|
|
ResponseCmndStateText(Settings.light_correction);
|
|
}
|
|
|
|
void CmndRgbwwTable(void)
|
|
{
|
|
// RgbWwTable - Show current RGBWW State
|
|
// RgbWwTable 255,255,255,255,255 - Set RGBWW state to maximum
|
|
if ((XdrvMailbox.data_len > 0)) {
|
|
uint32_t parm[LST_RGBCW -1];
|
|
uint32_t parmcount = ParseParameters(LST_RGBCW, parm);
|
|
for (uint32_t i = 0; i < parmcount; i++) {
|
|
Settings.rgbwwTable[i] = parm[i];
|
|
}
|
|
Light.update = true;
|
|
}
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
scolor[0] = '\0';
|
|
for (uint32_t i = 0; i < LST_RGBCW; i++) {
|
|
snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.rgbwwTable[i]);
|
|
}
|
|
ResponseCmndChar(scolor);
|
|
}
|
|
|
|
void CmndFade(void)
|
|
{
|
|
// 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
|
|
Settings.light_fade = XdrvMailbox.payload;
|
|
break;
|
|
case 2: // Toggle
|
|
Settings.light_fade ^= 1;
|
|
break;
|
|
}
|
|
#ifdef USE_DEVICE_GROUPS
|
|
if (XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 2) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade);
|
|
#endif // USE_DEVICE_GROUPS
|
|
#ifdef USE_LIGHT
|
|
if (!Settings.light_fade) { Light.fade_running = false; }
|
|
#endif // USE_LIGHT
|
|
ResponseCmndStateText(Settings.light_fade);
|
|
}
|
|
|
|
void CmndSpeed(void)
|
|
{
|
|
// Speed 1 - Fast
|
|
// Speed 40 - Very slow
|
|
// Speed + - Increment Speed
|
|
// Speed - - Decrement Speed
|
|
if (1 == XdrvMailbox.data_len) {
|
|
if (('+' == XdrvMailbox.data[0]) && (Settings.light_speed > 1)) {
|
|
XdrvMailbox.payload = Settings.light_speed - 1;
|
|
}
|
|
else if (('-' == XdrvMailbox.data[0]) && (Settings.light_speed < 40)) {
|
|
XdrvMailbox.payload = Settings.light_speed + 1;
|
|
}
|
|
}
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 40)) {
|
|
Settings.light_speed = XdrvMailbox.payload;
|
|
#ifdef USE_DEVICE_GROUPS
|
|
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SPEED, Settings.light_speed);
|
|
#endif // USE_DEVICE_GROUPS
|
|
}
|
|
ResponseCmndNumber(Settings.light_speed);
|
|
}
|
|
|
|
void CmndWakeupDuration(void)
|
|
{
|
|
// WakeUpDuration - Show current Wake Up duration in seconds
|
|
// WakeUpDuration 60 - Set Wake Up duration to 60 seconds
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3001)) {
|
|
Settings.light_wakeup = XdrvMailbox.payload;
|
|
Light.wakeup_active = 0;
|
|
}
|
|
ResponseCmndNumber(Settings.light_wakeup);
|
|
}
|
|
|
|
#ifdef USE_LIGHT_PALETTE
|
|
void CmndPalette(void)
|
|
{
|
|
uint8_t * palette_entry;
|
|
char * p;
|
|
|
|
// Palette Color[ ...]
|
|
if (XdrvMailbox.data_len) {
|
|
Light.wheel = 0;
|
|
Light.palette_count = 0;
|
|
if (Light.palette) {
|
|
free(Light.palette);
|
|
Light.palette = nullptr;
|
|
}
|
|
if (XdrvMailbox.data_len > 1 || XdrvMailbox.data[0] != '0') {
|
|
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++;
|
|
}
|
|
if (!p) break;
|
|
color = p + 1;
|
|
}
|
|
if (!(Light.palette = (uint8_t *)realloc(Light.palette, palette_count * Light.subtype))) return;
|
|
Light.palette_count = palette_count;
|
|
}
|
|
}
|
|
|
|
char palette_str[5 * Light.subtype * Light.palette_count + 3];
|
|
p = palette_str;
|
|
*p++ = '[';
|
|
if (Light.palette_count) {
|
|
palette_entry = Light.palette;
|
|
for (int entry = 0; entry < Light.palette_count; entry++) {
|
|
if (Settings.flag.decimal_text) { // SetOption17 - Switch between decimal or hexadecimal output
|
|
*p++ = '"';
|
|
}
|
|
memcpy(Light.current_color, palette_entry, Light.subtype);
|
|
LightGetColor(p);
|
|
p += strlen(p);
|
|
if (Settings.flag.decimal_text) { // SetOption17 - Switch between decimal or hexadecimal output
|
|
*p++ = '"';
|
|
}
|
|
*p++ = ',';
|
|
}
|
|
p--;
|
|
}
|
|
*p++ = ']';
|
|
*p = 0;
|
|
ResponseCmndChar(palette_str);
|
|
}
|
|
#endif // USE_LIGHT_PALETTE
|
|
|
|
void CmndUndocA(void)
|
|
{
|
|
// Theos legacy status
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
LightGetColor(scolor, true); // force hex whatever Option 17
|
|
scolor[6] = '\0'; // RGB only
|
|
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);
|
|
MqttPublishPrefixTopic_P(STAT, XdrvMailbox.topic);
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* Interface
|
|
\*********************************************************************************************/
|
|
|
|
bool Xdrv04(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (FUNC_MODULE_INIT == function) {
|
|
return LightModuleInit();
|
|
}
|
|
else if (light_type) {
|
|
switch (function) {
|
|
case FUNC_SERIAL:
|
|
result = XlgtCall(FUNC_SERIAL);
|
|
break;
|
|
case FUNC_LOOP:
|
|
if (Light.fade_running) {
|
|
if (LightApplyFade()) {
|
|
LightSetOutputs(Light.fade_cur_10);
|
|
}
|
|
}
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
LightAnimate();
|
|
break;
|
|
#ifdef USE_DEVICE_GROUPS
|
|
case FUNC_DEVICE_GROUP_ITEM:
|
|
LightHandleDevGroupItem();
|
|
break;
|
|
#endif // USE_DEVICE_GROUPS
|
|
case FUNC_SET_POWER:
|
|
LightSetPower();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kLightCommands, LightCommand);
|
|
if (!result) {
|
|
result = XlgtCall(FUNC_COMMAND);
|
|
}
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
LightInit();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif // USE_LIGHT
|