2017-02-19 16:49:17 +00:00
|
|
|
/*
|
2018-05-28 10:35:23 +01:00
|
|
|
xdrv_04_light.ino - PWM, WS2812 and sonoff led support for Sonoff-Tasmota
|
2017-02-19 16:49:17 +00:00
|
|
|
|
2019-01-01 12:55:01 +00:00
|
|
|
Copyright (C) 2019 Theo Arends
|
2017-02-19 16:49:17 +00:00
|
|
|
|
2017-05-13 12:02:10 +01:00
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
2017-02-19 16:49:17 +00:00
|
|
|
|
2017-05-13 12:02:10 +01:00
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2017-02-19 16:49:17 +00:00
|
|
|
*/
|
|
|
|
|
2019-06-16 15:43:23 +01:00
|
|
|
#ifdef USE_LIGHT
|
2017-02-19 16:49:17 +00:00
|
|
|
/*********************************************************************************************\
|
2017-10-27 11:12:07 +01:00
|
|
|
* PWM, WS2812, Sonoff B1, AiLight, Sonoff Led and BN-SZ01, H801, MagicHome and Arilux
|
2017-09-02 13:37:02 +01:00
|
|
|
*
|
2017-10-27 11:12:07 +01:00
|
|
|
* light_type Module Color ColorTemp Modules
|
|
|
|
* ---------- --------- ----- --------- ----------------------------
|
2019-05-02 21:50:19 +01:00
|
|
|
* 0 - no (Sonoff Basic)
|
2017-10-27 11:12:07 +01:00
|
|
|
* 1 PWM1 W no (Sonoff BN-SZ)
|
|
|
|
* 2 PWM2 CW yes (Sonoff Led)
|
2017-11-15 22:07:45 +00:00
|
|
|
* 3 PWM3 RGB no (H801, MagicHome and Arilux LC01)
|
2017-10-27 11:12:07 +01:00
|
|
|
* 4 PWM4 RGBW no (H801, MagicHome and Arilux)
|
2017-11-15 22:07:45 +00:00
|
|
|
* 5 PWM5 RGBCW yes (H801, Arilux LC11)
|
2017-10-27 11:12:07 +01:00
|
|
|
* 9 reserved no
|
|
|
|
* 10 reserved yes
|
2017-11-11 15:02:18 +00:00
|
|
|
* 11 +WS2812 RGB(W) no (One WS2812 RGB or RGBW ledstrip)
|
2017-10-27 11:12:07 +01:00
|
|
|
* 12 AiLight RGBW no
|
|
|
|
* 13 Sonoff B1 RGBCW yes
|
2019-01-30 22:19:40 +00:00
|
|
|
* 19 SM16716 RGB no
|
|
|
|
* 20 SM16716+W RGBW no
|
|
|
|
* 21 SM16716+CW RGBCW yes
|
2017-09-16 16:34:03 +01:00
|
|
|
*
|
2017-10-27 11:12:07 +01:00
|
|
|
* light_scheme WS2812 3+ Colors 1+2 Colors Effect
|
|
|
|
* ------------ ------ --------- ---------- -----------------
|
|
|
|
* 0 yes yes yes Color On/Off
|
|
|
|
* 1 yes yes yes Wakeup light
|
|
|
|
* 2 yes yes no Color cycle RGB
|
|
|
|
* 3 yes yes no Color cycle RBG
|
|
|
|
* 4 yes yes no Random RGB colors
|
|
|
|
* 5 yes no no Clock
|
|
|
|
* 6 yes no no Incandescent
|
|
|
|
* 7 yes no no RGB
|
|
|
|
* 8 yes no no Christmas
|
|
|
|
* 9 yes no no Hanukkah
|
|
|
|
* 10 yes no no Kwanzaa
|
|
|
|
* 11 yes no no Rainbow
|
|
|
|
* 12 yes no no Fire
|
2017-09-16 16:34:03 +01:00
|
|
|
*
|
2017-02-19 16:49:17 +00:00
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2019-04-25 12:06:35 +01:00
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
*
|
|
|
|
* Light management has been refactored to provide a cleaner class-based interface.
|
|
|
|
* Also, now all values are stored as integer, no more floats that could generate
|
|
|
|
* rounding errors.
|
|
|
|
*
|
|
|
|
* Two singletons are now used to control the state of the light.
|
|
|
|
* - light_state (LightStateClass) stores the color / white temperature and
|
|
|
|
* brightness. Use this object to READ only.
|
|
|
|
* - light_controller (LightControllerClass) is used to change light state
|
|
|
|
* and adjust all Settings and levels accordingly.
|
|
|
|
* Always use this object to change light status.
|
2019-05-04 08:40:26 +01:00
|
|
|
*
|
|
|
|
* As there have been lots of changes in light control, here is a summary out
|
|
|
|
* the whole flow from setting colors to drving the PMW pins.
|
|
|
|
*
|
|
|
|
* 1. To change colors, always use 'light_controller' object.
|
|
|
|
* 'light_state' is only to be used to read current state.
|
|
|
|
* .a For color bulbs, set color via changeRGB() or changeHS() for Hue/Sat.
|
|
|
|
* Set the overall brightness changeBri(0..255) or changeDimmer(0..100%)
|
|
|
|
* RGB and Hue/Sat are always kept in sync. Internally, RGB are stored at
|
|
|
|
* full range (max brightness) so that when you reduce brightness and
|
|
|
|
* raise it back again, colors don't change due to rounding errors.
|
|
|
|
* .b For white bulbs with Cold/Warm colortone, use changeCW() or changeCT()
|
|
|
|
* to change color-tone. Set overall brightness separately.
|
|
|
|
* Color-tone temperature can range from 153 (Cold) to 500 (Warm).
|
|
|
|
* 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 supproted
|
|
|
|
* by the light_type: in calcLevels()
|
|
|
|
* 1 channel - 0:Brightness
|
2019-05-08 11:06:22 +01:00
|
|
|
* 2 channels - 0:Coldwhite 1:Warmwhite
|
2019-05-04 08:40:26 +01:00
|
|
|
* 3 channels - 0:Red 1:Green 2:Blue
|
|
|
|
* 4 chennels - 0:Red 1:Green 2:Blue 3:White
|
2019-05-08 11:06:22 +01:00
|
|
|
* 5 chennels - 0:Red 1:Green 2:Blue 3:ColdWhite 4:Warmwhite
|
2019-05-04 08:40:26 +01:00
|
|
|
*
|
|
|
|
* 3. In LightAnimate(), final PWM values are computed at next tick.
|
|
|
|
* .a If color did not change since last tick - ignore.
|
2019-07-07 09:15:50 +01:00
|
|
|
* .b Extend resolution from 8 bits to 10 bits, which makes a significant
|
2019-05-04 08:40:26 +01:00
|
|
|
* difference when applying gamma correction at low brightness.
|
2019-07-07 09:15:50 +01:00
|
|
|
* .c Apply Gamma Correction if LedTable==1 (by default).
|
2019-05-04 08:40:26 +01:00
|
|
|
* Gamma Correction uses an adaptative resolution table from 11 to 8 bits.
|
2019-07-07 09:15:50 +01:00
|
|
|
* .d For Warm/Cold-white channels, Gamma correction is calculated in combined mode.
|
2019-05-04 08:40:26 +01:00
|
|
|
* Ie. total white brightness (C+W) is used for Gamma correction and gives
|
|
|
|
* the overall light power required. Then this light power is split among
|
|
|
|
* Wamr/Cold channels.
|
2019-07-07 09:15:50 +01:00
|
|
|
* .e Gamma correction is still applied to 8 bits channels for compatibility
|
2019-05-04 08:40:26 +01:00
|
|
|
* with other non-PMW modules.
|
2019-07-07 09:15:50 +01:00
|
|
|
* .f Apply color balance correction from rgbwwTable[].
|
|
|
|
* Note: correction is done after Gamma correction, it is meant
|
|
|
|
* to adjust leds with different power
|
|
|
|
* .g If rgbwwTable[4] is zero, blend RGB with White and adjust the level of
|
|
|
|
* White channel according to rgbwwTable[3]
|
|
|
|
* .h Avoid PMW values between 1008 and 1022, issue #1146
|
|
|
|
* .i Scale ranges from 10 bits to 0..PWMRange (by default 1023) so no change
|
2019-05-04 08:40:26 +01:00
|
|
|
* by default.
|
2019-07-07 09:15:50 +01:00
|
|
|
* .j Apply port remapping from Option37
|
|
|
|
* .k Invert PWM value if port is of type PMWxi instead of PMWx
|
|
|
|
* .l Apply PWM value with analogWrite() - if pin is configured
|
2019-05-04 08:40:26 +01:00
|
|
|
*
|
2019-04-25 12:06:35 +01:00
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2018-11-07 09:30:03 +00:00
|
|
|
#define XDRV_04 4
|
2019-08-06 09:57:50 +01:00
|
|
|
// #define DEBUG_LIGHT
|
2018-11-07 09:30:03 +00:00
|
|
|
|
2019-08-01 14:47:00 +01:00
|
|
|
const uint8_t LIGHT_COLOR_SIZE = 25; // Char array scolor size
|
|
|
|
const uint8_t WS2812_SCHEMES = 7; // Number of additional WS2812 schemes supported by xdrv_ws2812.ino
|
2017-12-29 13:55:39 +00:00
|
|
|
|
2019-08-11 17:12:18 +01:00
|
|
|
const char kLightCommands[] PROGMEM = "|" // No prefix
|
2019-08-01 14:47:00 +01:00
|
|
|
#ifdef USE_WS2812
|
|
|
|
D_CMND_LED "|" D_CMND_PIXELS "|" D_CMND_ROTATION "|" D_CMND_WIDTH "|"
|
|
|
|
#endif // USE_WS2812
|
|
|
|
D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" 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 "|UNDOCA" ;
|
|
|
|
|
|
|
|
void (* const LightCommand[])(void) PROGMEM = {
|
|
|
|
#ifdef USE_WS2812
|
|
|
|
&CmndLed, &CmndPixels, &CmndRotation, &CmndWidth,
|
|
|
|
#endif // USE_WS2812
|
|
|
|
&CmndColor, &CmndColorTemperature, &CmndDimmer, &CmndLedTable, &CmndFade,
|
|
|
|
&CmndRgbwwTable, &CmndScheme, &CmndSpeed, &CmndWakeup, &CmndWakeupDuration,
|
|
|
|
&CmndWhite, &CmndChannel, &CmndHsbColor, &CmndUndocA };
|
2017-10-29 17:18:46 +00:00
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
// Light color mode, either RGB alone, or white-CT alone, or both only available if ct_rgb_linked is false
|
|
|
|
enum LightColorModes {
|
|
|
|
LCM_RGB = 1, LCM_CT = 2, LCM_BOTH = 3 };
|
|
|
|
|
2017-11-15 22:07:45 +00:00
|
|
|
struct LRgbColor {
|
|
|
|
uint8_t R, G, B;
|
|
|
|
};
|
2019-03-31 10:59:04 +01:00
|
|
|
const uint8_t MAX_FIXED_COLOR = 12;
|
2017-11-15 22:07:45 +00:00
|
|
|
const LRgbColor kFixedColor[MAX_FIXED_COLOR] PROGMEM =
|
|
|
|
{ 255,0,0, 0,255,0, 0,0,255, 228,32,0, 0,228,32, 0,32,228, 188,64,0, 0,160,96, 160,32,240, 255,255,0, 255,0,170, 255,255,255 };
|
|
|
|
|
2018-09-04 21:43:27 +01:00
|
|
|
struct LWColor {
|
|
|
|
uint8_t W;
|
|
|
|
};
|
2019-03-31 10:59:04 +01:00
|
|
|
const uint8_t MAX_FIXED_WHITE = 4;
|
2018-09-04 22:03:19 +01:00
|
|
|
const LWColor kFixedWhite[MAX_FIXED_WHITE] PROGMEM = { 0, 255, 128, 32 };
|
2018-09-04 21:43:27 +01:00
|
|
|
|
2017-11-15 22:07:45 +00:00
|
|
|
struct LCwColor {
|
|
|
|
uint8_t C, W;
|
|
|
|
};
|
2019-03-31 10:59:04 +01:00
|
|
|
const uint8_t MAX_FIXED_COLD_WARM = 4;
|
2017-11-15 22:07:45 +00:00
|
|
|
const LCwColor kFixedColdWarm[MAX_FIXED_COLD_WARM] PROGMEM = { 0,0, 255,0, 0,255, 128,128 };
|
|
|
|
|
2019-04-30 22:03:08 +01:00
|
|
|
// New version of Gamma correction table, with adaptative resolution
|
|
|
|
// from 11 bits (lower values) to 8 bits (upper values).
|
|
|
|
// We're using the fact that lower values are small and can fit within 8 bits
|
|
|
|
// To save flash space, the array is only 8 bits uint
|
2019-08-16 16:29:19 +01:00
|
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
|
|
const uint8_t _ledTable[] PROGMEM = {
|
|
|
|
#else
|
2019-04-30 22:03:08 +01:00
|
|
|
const uint8_t _ledTable[] = {
|
2019-08-16 16:29:19 +01:00
|
|
|
#endif
|
2019-04-30 22:03:08 +01:00
|
|
|
// 11 bits resolution
|
|
|
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, // 11 bits, 0..2047
|
|
|
|
2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, // 11 bits, 0..2047
|
|
|
|
7, 8, 8, 9, 10, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, // 11 bits, 0..2047
|
|
|
|
20, 21, 22, 24, 25, 26, 28, 29, 30, 32, 33, 35, 37, 38, 40, 42, // 11 bits, 0..2047
|
|
|
|
// 10 bits resolution
|
|
|
|
22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 36, 37, 38, 39, // 10 bits, 0..1023
|
|
|
|
41, 42, 44, 45, 47, 48, 50, 51, 53, 55, 56, 58, 60, 62, 64, 65, // 10 bits, 0..1023
|
|
|
|
67, 69, 71, 73, 75, 78, 80, 82, 84, 86, 89, 91, 93, 96, 98,101, // 10 bits, 0..1023
|
|
|
|
103,106,108,111,114,116,119,122,125,128,131,134,137,140,143,146, // 10 bits, 0..1023
|
|
|
|
// 9 bits resolution
|
|
|
|
75, 77, 78, 80, 82, 84, 85, 87, 89, 91, 93, 94, 96, 98,100,102, // 9 bits, 0..511
|
|
|
|
104,106,108,110,112,115,117,119,121,123,125,128,130,132,135,137, // 9 bits, 0..511
|
|
|
|
140,142,144,147,149,152,155,157,160,163,165,168,171,173,176,179, // 9 bits, 0..511
|
|
|
|
182,185,188,191,194,197,200,203,206,209,212,215,219,222,225,229, // 9 bits, 0..511
|
|
|
|
// 8 bits resolution
|
|
|
|
116,118,120,121,123,125,127,128,130,132,134,136,138,139,141,143, // 8 bits, 0..255
|
|
|
|
145,147,149,151,153,155,157,159,161,163,165,168,170,172,174,176, // 8 bits, 0..255
|
|
|
|
178,181,183,185,187,190,192,194,197,199,201,204,206,209,211,214, // 8 bits, 0..255
|
|
|
|
216,219,221,224,226,229,232,234,237,240,242,245,248,250,253,255 // 8 bits, 0..255
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
// 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,
|
|
|
|
// 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
|
|
|
|
// 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
|
|
|
|
// 11, 11, 11, 12, 12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17,
|
|
|
|
// 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26,
|
|
|
|
// 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37,
|
|
|
|
// 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 47, 48, 49, 50, 51,
|
|
|
|
// 52, 53, 54, 55, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69,
|
|
|
|
// 70, 71, 72, 74, 75, 76, 78, 79, 80, 82, 83, 84, 86, 87, 88, 90,
|
|
|
|
// 91, 93, 94, 96, 97, 99,100,102,103,105,106,108,110,111,113,115,
|
|
|
|
//116,118,120,121,123,125,127,128,130,132,134,136,138,139,141,143,
|
|
|
|
//145,147,149,151,153,155,157,159,161,163,165,168,170,172,174,176,
|
|
|
|
//178,181,183,185,187,190,192,194,197,199,201,204,206,209,211,214,
|
|
|
|
//216,219,221,224,226,229,232,234,237,240,242,245,248,250,253,255
|
|
|
|
//
|
|
|
|
// and for 10 bits output:
|
|
|
|
// 0, 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, 3, 3, 3, 3, 4,
|
|
|
|
// 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 10,
|
|
|
|
// 10, 11, 11, 12, 13, 13, 14, 15, 15, 16, 17, 18, 19, 19, 20, 21,
|
|
|
|
// 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 36, 37, 38, 39,
|
|
|
|
// 41, 42, 44, 45, 47, 48, 50, 51, 53, 55, 56, 58, 60, 62, 64, 65,
|
|
|
|
// 67, 69, 71, 73, 75, 78, 80, 82, 84, 86, 89, 91, 93, 96, 98,101,
|
|
|
|
//103,106,108,111,114,116,119,122,125,128,131,134,137,140,143,146,
|
|
|
|
//151,155,157,161,165,169,171,175,179,183,187,189,193,197,201,205,
|
|
|
|
//209,213,217,221,225,231,235,239,243,247,251,257,261,265,271,275,
|
|
|
|
//281,285,289,295,299,305,311,315,321,327,331,337,343,347,353,359,
|
|
|
|
//365,371,377,383,389,395,401,407,413,419,425,431,439,445,451,459,
|
|
|
|
//467,475,483,487,495,503,511,515,523,531,539,547,555,559,567,575,
|
|
|
|
//583,591,599,607,615,623,631,639,647,655,663,675,683,691,699,707,
|
|
|
|
//715,727,735,743,751,763,771,779,791,799,807,819,827,839,847,859,
|
|
|
|
//867,879,887,899,907,919,931,939,951,963,971,983,995,1003,1015,1023
|
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
struct LIGHT {
|
|
|
|
unsigned long 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 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;
|
|
|
|
|
|
|
|
bool update = true;
|
|
|
|
bool pwm_multi_channels = false; // SetOption68, treat each PWM channel as an independant dimmer
|
|
|
|
} Light;
|
|
|
|
|
|
|
|
power_t LightPower(void)
|
|
|
|
{
|
|
|
|
return Light.power; // Make external
|
|
|
|
}
|
2017-02-19 16:49:17 +00:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
uint8_t LightDevice(void)
|
|
|
|
{
|
|
|
|
return Light.device; // Make external
|
|
|
|
}
|
2017-09-16 16:34:03 +01:00
|
|
|
|
2019-07-07 09:15:50 +01:00
|
|
|
static uint32_t min3(uint32_t a, uint32_t b, uint32_t c) {
|
|
|
|
return (a < b && a < c) ? a : (b < c) ? b : c;
|
|
|
|
}
|
|
|
|
|
2019-04-25 12:06:35 +01:00
|
|
|
//
|
|
|
|
// LightStateClass
|
|
|
|
// This class is an abstraction of the current light state.
|
|
|
|
// It allows for b/w, full colors, or white colortone
|
|
|
|
//
|
2019-05-05 08:18:57 +01:00
|
|
|
// This class has 2 independant slots
|
2019-04-25 12:06:35 +01:00
|
|
|
// 1/ Brightness 0.255, dimmer controls both RGB and WC (warm-cold)
|
2019-05-05 08:18:57 +01:00
|
|
|
// 1/ RGB and Hue/Sat - always kept in sync and stored at full brightness,
|
|
|
|
// i.e. R G or B are 255
|
|
|
|
// briRGB specifies the brightness for the RGB slot.
|
2019-04-25 12:06:35 +01:00
|
|
|
// If Brightness is 0, it is equivalent to Off (for compatibility)
|
|
|
|
// Dimmer is Brightness converted to range 0..100
|
2019-05-08 11:06:22 +01:00
|
|
|
// 2/ White with colortone - or CW (Cold / Warm)
|
2019-05-05 08:18:57 +01:00
|
|
|
// ct is 153..500 temperature (153=cold, 500=warm)
|
|
|
|
// briCT specifies the brightness for white channel
|
2019-04-25 12:06:35 +01:00
|
|
|
//
|
2019-05-05 08:18:57 +01:00
|
|
|
// Dimmer (0.100) is automatically derived from brightness
|
2019-04-25 12:06:35 +01:00
|
|
|
//
|
2019-05-05 08:18:57 +01:00
|
|
|
// INVARIANTS:
|
|
|
|
// 1. RGB components are always stored at full brightness and modulated with briRGB
|
|
|
|
// ((R == 255) || (G == 255) || (B == 255))
|
|
|
|
// 2. RGB and Hue/Sat are always kept in sync whether you use setRGB() or setHS()
|
|
|
|
// 3. Warm/Cold white channels are always stored at full brightness
|
|
|
|
// ((WW == 255) || (WC == 255))
|
|
|
|
// 4. WC/WW and CT are always kept in sync.
|
|
|
|
// Note: if you use setCT() then WC+WW == 255 (both channels are linked)
|
2019-05-08 11:06:22 +01:00
|
|
|
// but if you use setCW() both channels can be set independantly
|
2019-05-05 08:18:57 +01:00
|
|
|
// 5. If RGB or CT channels are deactivated, then corresponding brightness is zero
|
|
|
|
// if (colot_tone == LCM_RGB) then briCT = 0
|
|
|
|
// if (color_tone == LCM_CT) then briRGB = 0
|
|
|
|
// if (colot_tone == LCM_BOTH) then briRGB and briCT can have any values
|
2019-04-25 12:06:35 +01:00
|
|
|
//
|
2019-05-05 08:18:57 +01:00
|
|
|
// Note: If you want the actual RGB, you need to multiply with Bri, or use getActualRGBCW()
|
2019-04-25 12:06:35 +01:00
|
|
|
// Note: all values are stored as unsigned integer, no floats.
|
|
|
|
// 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
|
2019-05-04 22:04:53 +01:00
|
|
|
uint8_t _briRGB = 255; // 0..255
|
2019-04-25 12:06:35 +01:00
|
|
|
// dimmer is same as _bri but with a range of 0%-100%
|
|
|
|
uint8_t _r = 255; // 0..255
|
|
|
|
uint8_t _g = 255; // 0..255
|
|
|
|
uint8_t _b = 255; // 0..255
|
2019-05-04 22:04:53 +01:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
uint8_t _subtype = 0; // local copy of Light.subtype, if we need multiple lights
|
2019-05-04 22:04:53 +01:00
|
|
|
uint16_t _ct = 153; // 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
|
2019-04-25 12:06:35 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
void setSubType(uint8_t sub_type) {
|
|
|
|
_subtype = sub_type; // set sub_type at initialization, shoudln't be changed afterwards
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
// 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_RGBWC: 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_RGBWC:
|
|
|
|
_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;
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
inline uint8_t getColorMode() {
|
|
|
|
return _color_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void addRGBMode() {
|
|
|
|
setColorMode(_color_mode | LCM_RGB);
|
|
|
|
}
|
|
|
|
void addCTMode() {
|
|
|
|
setColorMode(_color_mode | LCM_CT);
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2019-05-04 22:04:53 +01:00
|
|
|
if (r) { *r = _r; }
|
|
|
|
if (g) { *g = _g; }
|
|
|
|
if (b) { *b = _b; }
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// get full brightness values for wamr and cold channels.
|
2019-07-02 21:10:57 +01:00
|
|
|
// either w=c=0 (off) or w+c >= 255
|
2019-04-25 12:06:35 +01:00
|
|
|
void getCW(uint8_t *rc, uint8_t *rw) {
|
2019-05-04 22:04:53 +01:00
|
|
|
if (rc) { *rc = _wc; }
|
|
|
|
if (rw) { *rw = _ww; }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the actual values for each channel, ie multiply with brightness
|
2019-05-08 11:06:22 +01:00
|
|
|
void getActualRGBCW(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *c, uint8_t *w) {
|
2019-05-04 22:04:53 +01:00
|
|
|
bool rgb_channels_on = _color_mode & LCM_RGB;
|
|
|
|
bool ct_channels_on = _color_mode & LCM_CT;
|
|
|
|
|
|
|
|
if (r) { *r = rgb_channels_on ? changeUIntScale(_r, 0, 255, 0, _briRGB) : 0; }
|
|
|
|
if (g) { *g = rgb_channels_on ? changeUIntScale(_g, 0, 255, 0, _briRGB) : 0; }
|
|
|
|
if (b) { *b = rgb_channels_on ? changeUIntScale(_b, 0, 255, 0, _briRGB) : 0; }
|
|
|
|
|
|
|
|
if (c) { *c = ct_channels_on ? changeUIntScale(_wc, 0, 255, 0, _briCT) : 0; }
|
|
|
|
if (w) { *w = ct_channels_on ? changeUIntScale(_ww, 0, 255, 0, _briCT) : 0; }
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t getChannels(uint8_t *channels) {
|
2019-05-08 11:06:22 +01:00
|
|
|
getActualRGBCW(&channels[0], &channels[1], &channels[2], &channels[3], &channels[4]);
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void getHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri) {
|
2019-05-04 22:04:53 +01:00
|
|
|
if (hue) { *hue = _hue; }
|
|
|
|
if (sat) { *sat = _sat; }
|
|
|
|
if (bri) { *bri = _briRGB; }
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// getBri() is guaranteed to give the same result as setBri() - no rounding errors.
|
2019-05-04 22:04:53 +01:00
|
|
|
uint8_t getBri(void) {
|
|
|
|
// return the max of _briCT and _briRGB
|
|
|
|
return (_briRGB >= _briCT) ? _briRGB : _briCT;
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
// get the white Brightness
|
|
|
|
inline uint8_t getBriCT() {
|
|
|
|
return _briCT;
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
2019-07-02 21:10:57 +01:00
|
|
|
static inline uint8_t DimmerToBri(uint8_t dimmer) {
|
|
|
|
return changeUIntScale(dimmer, 0, 100, 0, 255); // 0..255
|
|
|
|
}
|
|
|
|
static uint8_t BriToDimmer(uint8_t bri) {
|
|
|
|
uint8_t dimmer = changeUIntScale(bri, 0, 255, 0, 100);
|
2019-04-25 12:06:35 +01:00
|
|
|
// if brightness is non zero, force dimmer to be non-zero too
|
2019-05-04 22:04:53 +01:00
|
|
|
if ((dimmer == 0) && (bri > 0)) { dimmer = 1; }
|
2019-04-25 12:06:35 +01:00
|
|
|
return dimmer;
|
|
|
|
}
|
|
|
|
|
2019-07-02 21:10:57 +01:00
|
|
|
uint8_t getDimmer() {
|
|
|
|
return BriToDimmer(getBri());
|
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
inline uint16_t getCT() {
|
|
|
|
return _ct; // 153..500
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2019-05-04 22:04:53 +01:00
|
|
|
void setBri(uint8_t bri) {
|
|
|
|
setBriRGB(_color_mode & LCM_RGB ? bri : 0);
|
|
|
|
setBriCT(_color_mode & LCM_CT ? bri : 0);
|
2019-04-25 12:06:35 +01:00
|
|
|
#ifdef DEBUG_LIGHT
|
2019-05-04 22:04:53 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setBri RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB);
|
2019-04-25 12:06:35 +01:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
// changes the RGB brightness alone
|
|
|
|
uint8_t setBriRGB(uint8_t bri_rgb) {
|
|
|
|
uint8_t prev_bri = _briRGB;
|
|
|
|
_briRGB = bri_rgb;
|
|
|
|
if (bri_rgb > 0) { addRGBMode(); }
|
|
|
|
return prev_bri;
|
|
|
|
}
|
|
|
|
|
|
|
|
// changes the white brightness alone
|
|
|
|
uint8_t setBriCT(uint8_t bri_ct) {
|
|
|
|
uint8_t prev_bri = _briCT;
|
|
|
|
_briCT = bri_ct;
|
|
|
|
if (bri_ct > 0) { addCTMode(); }
|
|
|
|
return prev_bri;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline uint8_t getBriRGB() {
|
|
|
|
return _briRGB;
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void setDimmer(uint8_t dimmer) {
|
2019-07-02 21:10:57 +01:00
|
|
|
setBri(DimmerToBri(dimmer));
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void setCT(uint16_t ct) {
|
|
|
|
if (0 == ct) {
|
|
|
|
// disable ct mode
|
2019-05-04 22:04:53 +01:00
|
|
|
setColorMode(LCM_RGB); // try deactivating CT mode, setColorMode() will check which is legal
|
2019-04-25 12:06:35 +01:00
|
|
|
} else {
|
2019-05-04 22:04:53 +01:00
|
|
|
ct = (ct < 153 ? 153 : (ct > 500 ? 500 : ct));
|
|
|
|
_ww = changeUIntScale(ct, 153, 500, 0, 255);
|
|
|
|
_wc = 255 - _ww;
|
|
|
|
_ct = ct;
|
|
|
|
addCTMode();
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
#ifdef DEBUG_LIGHT
|
2019-05-04 22:04:53 +01:00
|
|
|
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);
|
2019-04-25 12:06:35 +01:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-05-08 11:06:22 +01:00
|
|
|
// Manually set Cold/Warm channels.
|
2019-05-04 22:04:53 +01:00
|
|
|
// There are two modes:
|
|
|
|
// 1. (free_range == false, default)
|
|
|
|
// In this mode there is only one virtual white channel with color temperature
|
|
|
|
// As a side effect, WC+WW = 255. It means also that the sum of light power
|
|
|
|
// from white LEDs is always equal to briCT. It is not possible here
|
|
|
|
// to set both white LEDs at full power, hence protecting power supplies
|
|
|
|
// from overlaoding.
|
|
|
|
// 2. (free_range == true)
|
|
|
|
// In this mode, values of WC and WW are free -- both channels can be set
|
|
|
|
// at full power.
|
|
|
|
// In this mode, we always scale both channels so that one at least is 255.
|
|
|
|
//
|
|
|
|
// We automatically adjust briCT to have the right values of channels
|
2019-05-08 11:06:22 +01:00
|
|
|
void setCW(uint8_t c, uint8_t w, bool free_range = false) {
|
2019-05-04 22:04:53 +01:00
|
|
|
uint16_t max = (w > c) ? w : c; // 0..255
|
|
|
|
uint16_t sum = c + w;
|
|
|
|
|
|
|
|
if (0 == max) {
|
|
|
|
_briCT = 0; // brightness set to null
|
|
|
|
setColorMode(LCM_RGB); // try deactivating CT mode, setColorMode() will check which is legal
|
2019-04-25 12:06:35 +01:00
|
|
|
} else {
|
2019-05-04 22:04:53 +01:00
|
|
|
if (!free_range) {
|
|
|
|
// we need to normalize to sum = 255
|
|
|
|
_ww = changeUIntScale(w, 0, sum, 0, 255);
|
|
|
|
_wc = 255 - _ww;
|
|
|
|
} else { // we normalize to max = 255
|
|
|
|
_ww = changeUIntScale(w, 0, max, 0, 255);
|
|
|
|
_wc = changeUIntScale(c, 0, max, 0, 255);
|
|
|
|
}
|
|
|
|
_ct = changeUIntScale(w, 0, sum, 153, 500);
|
|
|
|
addCTMode(); // activate CT mode if needed
|
|
|
|
if (_color_mode & LCM_CT) { _briCT = free_range ? max : (sum > 255 ? 255 : sum); }
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
#ifdef DEBUG_LIGHT
|
2019-05-08 11:06:22 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setCW CW (%d %d) CT (%d) briCT (%d)", c, w, _ct, _briCT);
|
2019-04-25 12:06:35 +01:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
// sets RGB and returns the Brightness. Bri is updated unless keep_bri is true
|
|
|
|
uint8_t setRGB(uint8_t r, uint8_t g, uint8_t b, bool keep_bri = false) {
|
2019-04-25 12:06:35 +01:00
|
|
|
uint16_t hue;
|
|
|
|
uint8_t sat;
|
2019-05-04 22:04:53 +01:00
|
|
|
#ifdef DEBUG_LIGHT
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setRGB RGB input (%d %d %d)", r, g, b);
|
|
|
|
#endif
|
2019-04-25 12:06:35 +01:00
|
|
|
|
|
|
|
uint32_t max = (r > g && r > b) ? r : (g > b) ? g : b; // 0..255
|
|
|
|
|
|
|
|
if (0 == max) {
|
|
|
|
r = g = b = 255;
|
2019-05-04 22:04:53 +01:00
|
|
|
setColorMode(LCM_CT); // try deactivating RGB, setColorMode() will check if this is legal
|
|
|
|
} else {
|
|
|
|
if (255 > max) {
|
|
|
|
// we need to normalize rgb
|
|
|
|
r = changeUIntScale(r, 0, max, 0, 255);
|
|
|
|
g = changeUIntScale(g, 0, max, 0, 255);
|
|
|
|
b = changeUIntScale(b, 0, max, 0, 255);
|
|
|
|
}
|
|
|
|
addRGBMode();
|
|
|
|
}
|
|
|
|
if (!keep_bri) {
|
|
|
|
_briRGB = (_color_mode & LCM_RGB) ? max : 0;
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
RgbToHsb(r, g, b, &hue, &sat, nullptr);
|
|
|
|
_r = r;
|
|
|
|
_g = g;
|
|
|
|
_b = b;
|
|
|
|
_hue = hue;
|
|
|
|
_sat = sat;
|
|
|
|
#ifdef DEBUG_LIGHT
|
2019-05-04 22:04:53 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setRGB RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB);
|
2019-04-25 12:06:35 +01:00
|
|
|
#endif
|
|
|
|
return max;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2019-05-04 22:04:53 +01:00
|
|
|
addRGBMode();
|
2019-04-25 12:06:35 +01:00
|
|
|
#ifdef DEBUG_LIGHT
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setHS HS (%d %d) rgb (%d %d %d)", hue, sat, r, g, b);
|
2019-05-04 22:04:53 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setHS RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB);
|
2019-04-25 12:06:35 +01:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// set all 5 channels at once.
|
|
|
|
// Channels are: R G B CW WW
|
|
|
|
// Brightness is automatically recalculated to adjust channels to the desired values
|
|
|
|
void setChannels(uint8_t *channels) {
|
2019-05-04 22:04:53 +01:00
|
|
|
setRGB(channels[0], channels[1], channels[2]);
|
2019-05-08 11:06:22 +01:00
|
|
|
setCW(channels[3], channels[4], true); // free range for WC and WW
|
2019-04-25 12:06:35 +01:00
|
|
|
#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]);
|
2019-05-04 22:04:53 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setChannels CT (%d) briRGB (%d) briCT (%d)", _ct, _briRGB, _briCT);
|
2019-04-25 12:06:35 +01:00
|
|
|
#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);
|
2019-04-26 15:39:26 +01:00
|
|
|
static void XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb);
|
2019-04-25 12:06:35 +01:00
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2019-05-18 14:59:40 +01:00
|
|
|
#define POW FastPrecisePowf
|
|
|
|
|
2019-04-25 12:06:35 +01:00
|
|
|
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
|
2019-05-18 14:59:40 +01:00
|
|
|
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);
|
2019-04-25 12:06:35 +01:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2019-04-26 15:39:26 +01:00
|
|
|
void LightStateClass::XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb)
|
2019-04-25 12:06:35 +01:00
|
|
|
{
|
|
|
|
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;
|
2019-04-26 15:39:26 +01:00
|
|
|
//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;
|
2019-05-18 14:59:40 +01:00
|
|
|
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;
|
2019-04-26 15:39:26 +01:00
|
|
|
//
|
|
|
|
// 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)); }
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class LightControllerClass {
|
2019-05-04 22:04:53 +01:00
|
|
|
private:
|
2019-04-25 12:06:35 +01:00
|
|
|
LightStateClass *_state;
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
// are RGB and CT linked, i.e. if we set CT then RGB channels are off
|
|
|
|
bool _ct_rgb_linked = true;
|
2019-08-06 09:57:50 +01:00
|
|
|
bool _pwm_multi_channels = false; // treat each channel as independant dimmer
|
2019-05-04 22:04:53 +01:00
|
|
|
|
2019-04-25 12:06:35 +01:00
|
|
|
public:
|
|
|
|
LightControllerClass(LightStateClass& state) {
|
|
|
|
_state = &state;
|
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
void setSubType(uint8_t sub_type) {
|
|
|
|
_state->setSubType(sub_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bool setCTRGBLinked(bool ct_rgb_linked) {
|
|
|
|
bool prev = _ct_rgb_linked;
|
2019-08-06 09:57:50 +01:00
|
|
|
if (_pwm_multi_channels) {
|
|
|
|
_ct_rgb_linked = false; // force to false if _pwm_multi_channels is set
|
|
|
|
} else {
|
|
|
|
_ct_rgb_linked = ct_rgb_linked;
|
|
|
|
}
|
2019-05-04 22:04:53 +01:00
|
|
|
return prev;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bool isCTRGBLinked() {
|
|
|
|
return _ct_rgb_linked;
|
|
|
|
}
|
|
|
|
|
2019-08-06 09:57:50 +01:00
|
|
|
inline bool setPWMMultiChannel(bool pwm_multi_channels) {
|
|
|
|
bool prev = _pwm_multi_channels;
|
|
|
|
_pwm_multi_channels = pwm_multi_channels;
|
|
|
|
if (pwm_multi_channels) setCTRGBLinked(false); // if pwm multi channel, then unlink RGB and CT
|
|
|
|
return prev;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bool isPWMMultiChannel(void) {
|
|
|
|
return _pwm_multi_channels;
|
|
|
|
}
|
|
|
|
|
2019-04-25 12:06:35 +01:00
|
|
|
#ifdef DEBUG_LIGHT
|
|
|
|
void debugLogs() {
|
|
|
|
uint8_t r,g,b,c,w;
|
2019-05-08 11:06:22 +01:00
|
|
|
_state->getActualRGBCW(&r,&g,&b,&c,&w);
|
2019-04-25 12:06:35 +01:00
|
|
|
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)",
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[0], Light.current_color[1], Light.current_color[2],
|
|
|
|
Light.current_color[3], Light.current_color[4]);
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void loadSettings() {
|
|
|
|
#ifdef DEBUG_LIGHT
|
|
|
|
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)",
|
2019-08-17 12:17:30 +01:00
|
|
|
light_type, Light.subtype);
|
2019-04-25 12:06:35 +01:00
|
|
|
#endif
|
2019-07-02 21:10:57 +01:00
|
|
|
// first try setting CW, if zero, it select RGB mode
|
|
|
|
_state->setCW(Settings.light_color[3], Settings.light_color[4], true);
|
2019-04-25 12:06:35 +01:00
|
|
|
_state->setRGB(Settings.light_color[0], Settings.light_color[1], Settings.light_color[2]);
|
2019-08-06 09:57:50 +01:00
|
|
|
if (!_pwm_multi_channels) {
|
|
|
|
// only if non-multi channel
|
|
|
|
// We apply dimmer in priority to RGB
|
|
|
|
uint8_t bri = _state->DimmerToBri(Settings.light_dimmer);
|
|
|
|
if (Settings.light_color[0] + Settings.light_color[1] + Settings.light_color[2] > 0) {
|
|
|
|
_state->setBriRGB(bri);
|
|
|
|
} else {
|
|
|
|
_state->setBriCT(bri);
|
|
|
|
}
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
void changeCTB(uint16_t new_ct, uint8_t briCT) {
|
2019-04-25 12:06:35 +01:00
|
|
|
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
|
|
|
|
*
|
|
|
|
* ct = 153 = 2000K = Warm = CCWW = 00FF
|
|
|
|
* ct = 500 = 6500K = Cold = CCWW = FF00
|
|
|
|
*/
|
|
|
|
// don't set CT if not supported
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBW > Light.subtype)) {
|
2019-04-25 12:06:35 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
_state->setCT(new_ct);
|
2019-05-04 22:04:53 +01:00
|
|
|
_state->setBriCT(briCT);
|
|
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_CT); } // try to force CT
|
2019-04-25 12:06:35 +01:00
|
|
|
saveSettings();
|
|
|
|
calcLevels();
|
|
|
|
//debugLogs();
|
|
|
|
}
|
|
|
|
|
|
|
|
void changeDimmer(uint8_t dimmer) {
|
|
|
|
uint8_t bri = changeUIntScale(dimmer, 0, 100, 0, 255);
|
|
|
|
changeBri(bri);
|
|
|
|
}
|
|
|
|
|
|
|
|
void changeBri(uint8_t bri) {
|
|
|
|
_state->setBri(bri);
|
|
|
|
saveSettings();
|
|
|
|
calcLevels();
|
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
void changeRGB(uint8_t r, uint8_t g, uint8_t b, bool keep_bri = false) {
|
|
|
|
_state->setRGB(r, g, b, keep_bri);
|
|
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); } // try to force RGB
|
2019-04-25 12:06:35 +01:00
|
|
|
saveSettings();
|
|
|
|
calcLevels();
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate the levels for each channel
|
|
|
|
void calcLevels() {
|
2019-05-08 11:06:22 +01:00
|
|
|
uint8_t r,g,b,c,w,briRGB,briCT;
|
|
|
|
_state->getActualRGBCW(&r,&g,&b,&c,&w);
|
2019-08-06 09:57:50 +01:00
|
|
|
|
|
|
|
if (_pwm_multi_channels) { // if PWM multi channel, no more transformation required
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[0] = r;
|
|
|
|
Light.current_color[1] = g;
|
|
|
|
Light.current_color[2] = b;
|
|
|
|
Light.current_color[3] = c;
|
|
|
|
Light.current_color[4] = w;
|
2019-08-06 09:57:50 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-05-04 22:04:53 +01:00
|
|
|
briRGB = _state->getBriRGB();
|
|
|
|
briCT = _state->getBriCT();
|
2019-04-25 12:06:35 +01:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[0] = Light.current_color[1] = Light.current_color[2] = 0;
|
|
|
|
Light.current_color[3] = Light.current_color[4] = 0;
|
|
|
|
switch (Light.subtype) {
|
2019-05-05 17:18:20 +01:00
|
|
|
case LST_NONE:
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[0] = 255;
|
2019-05-05 17:18:20 +01:00
|
|
|
break;
|
|
|
|
case LST_SINGLE:
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[0] = briRGB;
|
2019-05-05 17:18:20 +01:00
|
|
|
break;
|
|
|
|
case LST_COLDWARM:
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[0] = c;
|
|
|
|
Light.current_color[1] = w;
|
2019-05-05 17:18:20 +01:00
|
|
|
break;
|
|
|
|
case LST_RGBW:
|
|
|
|
case LST_RGBWC:
|
2019-08-17 12:17:30 +01:00
|
|
|
if (LST_RGBWC == Light.subtype) {
|
|
|
|
Light.current_color[3] = c;
|
|
|
|
Light.current_color[4] = w;
|
2019-05-05 17:18:20 +01:00
|
|
|
} else {
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[3] = briCT;
|
2019-05-05 17:18:20 +01:00
|
|
|
}
|
|
|
|
// continue
|
|
|
|
case LST_RGB:
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[0] = r;
|
|
|
|
Light.current_color[1] = g;
|
|
|
|
Light.current_color[2] = b;
|
2019-05-05 17:18:20 +01:00
|
|
|
break;
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-04 22:04:53 +01:00
|
|
|
void changeHSB(uint16_t hue, uint8_t sat, uint8_t briRGB) {
|
2019-04-25 12:06:35 +01:00
|
|
|
_state->setHS(hue, sat);
|
2019-05-04 22:04:53 +01:00
|
|
|
_state->setBriRGB(briRGB);
|
|
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); } // try to force RGB
|
2019-04-25 12:06:35 +01:00
|
|
|
saveSettings();
|
2019-05-04 22:04:53 +01:00
|
|
|
calcLevels();
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// save the current light state to Settings.
|
|
|
|
void saveSettings() {
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.pwm_multi_channels) {
|
2019-08-06 09:57:50 +01:00
|
|
|
// simply save each channel
|
|
|
|
_state->getActualRGBCW(&Settings.light_color[0], &Settings.light_color[1],
|
|
|
|
&Settings.light_color[2], &Settings.light_color[3],
|
|
|
|
&Settings.light_color[4]);
|
|
|
|
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));
|
|
|
|
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());
|
2019-07-02 21:10:57 +01:00
|
|
|
}
|
2019-05-04 22:04:53 +01:00
|
|
|
}
|
2019-04-25 12:06:35 +01:00
|
|
|
#ifdef DEBUG_LIGHT
|
|
|
|
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) {
|
2019-08-17 12:17:30 +01:00
|
|
|
if (LST_COLDWARM == Light.subtype) {
|
2019-06-12 21:49:22 +01:00
|
|
|
// remap channels 0-1 to 3-4 if cold/warm
|
|
|
|
uint8_t remapped_channels[5] = {0,0,0,channels[0],channels[1]};
|
|
|
|
_state->setChannels(remapped_channels);
|
|
|
|
} else {
|
|
|
|
_state->setChannels(channels);
|
|
|
|
}
|
2019-04-25 12:06:35 +01:00
|
|
|
saveSettings();
|
|
|
|
calcLevels();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// the singletons for light state and Light Controller
|
|
|
|
LightStateClass light_state = LightStateClass();
|
|
|
|
LightControllerClass light_controller = LightControllerClass(light_state);
|
|
|
|
|
2019-04-30 22:03:08 +01:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Gamma correction
|
|
|
|
\*********************************************************************************************/
|
2019-05-03 19:13:05 +01:00
|
|
|
// Calculate the gamma corrected value for LEDS
|
|
|
|
// You can request 11, 10, 9 or 8 bits resolution via 'bits_out' parameter
|
2019-04-30 22:03:08 +01:00
|
|
|
uint16_t ledGamma(uint8_t v, uint16_t bits_out = 8) {
|
|
|
|
uint16_t result;
|
|
|
|
// bits_resolution: the resolution of _ledTable[v], between 8 and 11
|
|
|
|
uint32_t bits_resolution = 11 - (v / 64); // 8..11
|
|
|
|
int32_t bits_correction = bits_out - bits_resolution; // -3..3
|
2019-08-16 16:29:19 +01:00
|
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
|
|
uint32_t uncorrected_value = pgm_read_byte(_ledTable + v); // 0..255
|
|
|
|
#else
|
2019-04-30 22:03:08 +01:00
|
|
|
uint32_t uncorrected_value = _ledTable[v]; // 0..255
|
2019-08-16 16:29:19 +01:00
|
|
|
#endif
|
2019-04-30 22:03:08 +01:00
|
|
|
if (0 == bits_correction) {
|
|
|
|
// we already match the required resolution, no change
|
|
|
|
result = uncorrected_value;
|
|
|
|
} else if (bits_correction > 0) {
|
|
|
|
// the output resolution is higher than our value, we need to extrapolate
|
|
|
|
// we shift by bits_correction, and force last bits to 1
|
|
|
|
uint32_t bits_mask = (1 << bits_correction) - 1; // 1, 3, 7
|
|
|
|
result = (uncorrected_value << bits_correction) | bits_mask;
|
|
|
|
} else { // bits_correction < 0
|
|
|
|
// our resolution is too high, we need to remove bits
|
|
|
|
// we add 1, 3 or 7 to force rouding to the nearest high value
|
|
|
|
uint32_t bits_mask = (1 << -bits_correction) - 1; // 1, 3, 7
|
|
|
|
result = ((uncorrected_value + bits_mask) >> -bits_correction);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2019-04-25 12:06:35 +01:00
|
|
|
|
2017-11-15 22:07:45 +00:00
|
|
|
#ifdef USE_ARILUX_RF
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Arilux LC11 Rf support stripped from RCSwitch library
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2019-03-31 10:59:04 +01:00
|
|
|
const uint32_t ARILUX_RF_TIME_AVOID_DUPLICATE = 1000; // Milliseconds
|
2017-11-15 22:07:45 +00:00
|
|
|
|
2019-03-31 10:59:04 +01:00
|
|
|
const uint8_t ARILUX_RF_MAX_CHANGES = 51; // Pulses (sync + 2 x 24 bits)
|
|
|
|
const uint32_t ARILUX_RF_SEPARATION_LIMIT = 4300; // Microseconds
|
|
|
|
const uint32_t ARILUX_RF_RECEIVE_TOLERANCE = 60; // Percentage
|
2017-11-15 22:07:45 +00:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
struct ARILUX {
|
|
|
|
unsigned int rf_timings[ARILUX_RF_MAX_CHANGES];
|
2017-11-17 16:52:31 +00:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
unsigned long rf_received_value = 0;
|
|
|
|
unsigned long rf_last_received_value = 0;
|
|
|
|
unsigned long rf_last_time = 0;
|
|
|
|
unsigned long rf_lasttime = 0;
|
2017-11-15 22:07:45 +00:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
unsigned int rf_change_count = 0;
|
|
|
|
unsigned int rf_repeat_count = 0;
|
2017-11-15 22:07:45 +00:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
uint8_t rf_toggle = 0;
|
|
|
|
} Arilux;
|
2018-02-07 16:50:02 +00:00
|
|
|
|
2019-05-22 09:39:37 +01:00
|
|
|
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 // Fix core 2.5.x ISR not in IRAM Exception
|
|
|
|
#ifndef USE_WS2812_DMA // Collides with Neopixelbus but solves RF misses
|
2018-11-14 13:32:09 +00:00
|
|
|
void AriluxRfInterrupt(void) ICACHE_RAM_ATTR; // As iram is tight and it works this way too
|
2017-11-15 22:07:45 +00:00
|
|
|
#endif // USE_WS2812_DMA
|
2018-02-07 16:50:02 +00:00
|
|
|
#endif // ARDUINO_ESP8266_RELEASE_2_3_0
|
2017-11-15 22:07:45 +00:00
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void AriluxRfInterrupt(void)
|
2017-11-15 22:07:45 +00:00
|
|
|
{
|
|
|
|
unsigned long time = micros();
|
2019-08-17 12:17:30 +01:00
|
|
|
unsigned int duration = time - Arilux.rf_lasttime;
|
2017-11-15 22:07:45 +00:00
|
|
|
|
|
|
|
if (duration > ARILUX_RF_SEPARATION_LIMIT) {
|
2019-08-17 12:17:30 +01:00
|
|
|
if (abs(duration - Arilux.rf_timings[0]) < 200) {
|
|
|
|
Arilux.rf_repeat_count++;
|
|
|
|
if (Arilux.rf_repeat_count == 2) {
|
2017-11-15 22:07:45 +00:00
|
|
|
unsigned long code = 0;
|
2019-08-17 12:17:30 +01:00
|
|
|
const unsigned int delay = Arilux.rf_timings[0] / 31;
|
2017-11-15 22:07:45 +00:00
|
|
|
const unsigned int delayTolerance = delay * ARILUX_RF_RECEIVE_TOLERANCE / 100;
|
2019-08-17 12:17:30 +01:00
|
|
|
for (unsigned int i = 1; i < Arilux.rf_change_count -1; i += 2) {
|
2017-11-15 22:07:45 +00:00
|
|
|
code <<= 1;
|
2019-08-17 12:17:30 +01:00
|
|
|
if (abs(Arilux.rf_timings[i] - (delay *3)) < delayTolerance && abs(Arilux.rf_timings[i +1] - delay) < delayTolerance) {
|
2017-11-15 22:07:45 +00:00
|
|
|
code |= 1;
|
|
|
|
}
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Arilux.rf_change_count > 49) { // Need 1 sync bit and 24 data bits
|
|
|
|
Arilux.rf_received_value = code;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Arilux.rf_repeat_count = 0;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Arilux.rf_change_count = 0;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Arilux.rf_change_count >= ARILUX_RF_MAX_CHANGES) {
|
|
|
|
Arilux.rf_change_count = 0;
|
|
|
|
Arilux.rf_repeat_count = 0;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Arilux.rf_timings[Arilux.rf_change_count++] = duration;
|
|
|
|
Arilux.rf_lasttime = time;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void AriluxRfHandler(void)
|
2017-11-15 22:07:45 +00:00
|
|
|
{
|
|
|
|
unsigned long now = millis();
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Arilux.rf_received_value && !((Arilux.rf_received_value == Arilux.rf_last_received_value) && (now - Arilux.rf_last_time < ARILUX_RF_TIME_AVOID_DUPLICATE))) {
|
|
|
|
Arilux.rf_last_received_value = Arilux.rf_received_value;
|
|
|
|
Arilux.rf_last_time = now;
|
2017-11-15 22:07:45 +00:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
uint16_t hostcode = Arilux.rf_received_value >> 8 & 0xFFFF;
|
2017-11-15 22:07:45 +00:00
|
|
|
if (Settings.rf_code[1][6] == Settings.rf_code[1][7]) {
|
|
|
|
Settings.rf_code[1][6] = hostcode >> 8 & 0xFF;
|
|
|
|
Settings.rf_code[1][7] = hostcode & 0xFF;
|
|
|
|
}
|
|
|
|
uint16_t stored_hostcode = Settings.rf_code[1][6] << 8 | Settings.rf_code[1][7];
|
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
DEBUG_DRIVER_LOG(PSTR(D_LOG_RFR D_HOST D_CODE " 0x%04X, " D_RECEIVED " 0x%06X"), stored_hostcode, Arilux.rf_received_value);
|
2017-11-15 22:07:45 +00:00
|
|
|
|
2017-11-17 16:52:31 +00:00
|
|
|
if (hostcode == stored_hostcode) {
|
2018-01-13 14:53:02 +00:00
|
|
|
char command[33];
|
2017-11-17 16:52:31 +00:00
|
|
|
char value = '-';
|
2017-11-15 22:07:45 +00:00
|
|
|
command[0] = '\0';
|
2019-08-17 12:17:30 +01:00
|
|
|
uint8_t keycode = Arilux.rf_received_value & 0xFF;
|
2017-11-15 22:07:45 +00:00
|
|
|
switch (keycode) {
|
|
|
|
case 1: // Power On
|
|
|
|
case 3: // Power Off
|
|
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_POWER " %d"), (1 == keycode) ? 1 : 0);
|
|
|
|
break;
|
|
|
|
case 2: // Toggle
|
2019-08-17 12:17:30 +01:00
|
|
|
Arilux.rf_toggle++;
|
|
|
|
Arilux.rf_toggle &= 0x3;
|
|
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), 200 + Arilux.rf_toggle);
|
2017-11-15 22:07:45 +00:00
|
|
|
break;
|
|
|
|
case 4: // Speed +
|
|
|
|
value = '+';
|
|
|
|
case 7: // Speed -
|
|
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_SPEED " %c"), value);
|
|
|
|
break;
|
|
|
|
case 5: // Scheme +
|
|
|
|
value = '+';
|
|
|
|
case 8: // Scheme -
|
|
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_SCHEME " %c"), value);
|
|
|
|
break;
|
|
|
|
case 6: // Dimmer +
|
|
|
|
value = '+';
|
|
|
|
case 9: // Dimmer -
|
|
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_DIMMER " %c"), value);
|
|
|
|
break;
|
|
|
|
default: {
|
|
|
|
if ((keycode >= 10) && (keycode <= 21)) {
|
|
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), keycode -9);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (strlen(command)) {
|
2018-05-28 14:52:42 +01:00
|
|
|
ExecuteCommand(command, SRC_LIGHT);
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Arilux.rf_received_value = 0;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void AriluxRfInit(void)
|
2017-11-15 22:07:45 +00:00
|
|
|
{
|
2019-05-21 17:11:39 +01:00
|
|
|
if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_ARIRFSEL] < 99)) {
|
2017-11-15 22:07:45 +00:00
|
|
|
if (Settings.last_module != Settings.module) {
|
|
|
|
Settings.rf_code[1][6] = 0;
|
|
|
|
Settings.rf_code[1][7] = 0;
|
|
|
|
Settings.last_module = Settings.module;
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Arilux.rf_received_value = 0;
|
2019-05-21 17:11:39 +01:00
|
|
|
|
|
|
|
digitalWrite(pin[GPIO_ARIRFSEL], 0); // Turn on RF
|
2017-11-21 15:06:51 +00:00
|
|
|
attachInterrupt(pin[GPIO_ARIRFRCV], AriluxRfInterrupt, CHANGE);
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-24 16:31:50 +00:00
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void AriluxRfDisable(void)
|
2017-11-24 16:31:50 +00:00
|
|
|
{
|
2019-05-21 17:11:39 +01:00
|
|
|
if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_ARIRFSEL] < 99)) {
|
2017-11-24 16:31:50 +00:00
|
|
|
detachInterrupt(pin[GPIO_ARIRFRCV]);
|
2019-05-21 17:11:39 +01:00
|
|
|
digitalWrite(pin[GPIO_ARIRFSEL], 1); // Turn off RF
|
2017-11-24 16:31:50 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-15 22:07:45 +00:00
|
|
|
#endif // USE_ARILUX_RF
|
|
|
|
|
2017-08-12 16:55:20 +01:00
|
|
|
/*********************************************************************************************\
|
2017-08-16 16:05:36 +01:00
|
|
|
* Sonoff B1 and AiLight inspired by OpenLight https://github.com/icamgo/noduino-sdk
|
2017-08-12 16:55:20 +01:00
|
|
|
\*********************************************************************************************/
|
2017-07-15 14:07:30 +01:00
|
|
|
|
2017-08-13 10:19:34 +01:00
|
|
|
extern "C" {
|
|
|
|
void os_delay_us(unsigned int);
|
|
|
|
}
|
|
|
|
|
2017-10-23 11:18:15 +01:00
|
|
|
uint8_t light_pdi_pin;
|
|
|
|
uint8_t light_pdcki_pin;
|
2017-08-12 16:55:20 +01:00
|
|
|
|
2017-10-18 17:22:34 +01:00
|
|
|
void LightDiPulse(uint8_t times)
|
2017-02-19 16:49:17 +00:00
|
|
|
{
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < times; i++) {
|
2017-10-23 11:18:15 +01:00
|
|
|
digitalWrite(light_pdi_pin, HIGH);
|
|
|
|
digitalWrite(light_pdi_pin, LOW);
|
2017-07-03 10:45:15 +01:00
|
|
|
}
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
|
|
|
|
2017-10-18 17:22:34 +01:00
|
|
|
void LightDckiPulse(uint8_t times)
|
2017-08-12 16:55:20 +01:00
|
|
|
{
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < times; i++) {
|
2017-10-23 11:18:15 +01:00
|
|
|
digitalWrite(light_pdcki_pin, HIGH);
|
|
|
|
digitalWrite(light_pdcki_pin, LOW);
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-18 17:22:34 +01:00
|
|
|
void LightMy92x1Write(uint8_t data)
|
2017-08-12 16:55:20 +01:00
|
|
|
{
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < 4; i++) { // Send 8bit Data
|
2017-10-23 11:18:15 +01:00
|
|
|
digitalWrite(light_pdcki_pin, LOW);
|
|
|
|
digitalWrite(light_pdi_pin, (data & 0x80));
|
|
|
|
digitalWrite(light_pdcki_pin, HIGH);
|
2017-08-16 16:05:36 +01:00
|
|
|
data = data << 1;
|
2017-10-23 11:18:15 +01:00
|
|
|
digitalWrite(light_pdi_pin, (data & 0x80));
|
|
|
|
digitalWrite(light_pdcki_pin, LOW);
|
|
|
|
digitalWrite(light_pdi_pin, LOW);
|
2017-08-16 16:05:36 +01:00
|
|
|
data = data << 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void LightMy92x1Init(void)
|
2017-08-16 16:05:36 +01:00
|
|
|
{
|
2018-11-16 11:22:15 +00:00
|
|
|
uint8_t chips = 1; // 1 (AiLight)
|
|
|
|
if (LT_RGBWC == light_type) {
|
|
|
|
chips = 2; // 2 (Sonoff B1)
|
|
|
|
}
|
2017-08-12 16:55:20 +01:00
|
|
|
|
2017-10-23 11:18:15 +01:00
|
|
|
LightDckiPulse(chips * 32); // Clear all duty register
|
|
|
|
os_delay_us(12); // TStop > 12us.
|
2017-08-12 16:55:20 +01:00
|
|
|
// Send 12 DI pulse, after 6 pulse's falling edge store duty data, and 12
|
|
|
|
// pulse's rising edge convert to command mode.
|
2017-10-18 17:22:34 +01:00
|
|
|
LightDiPulse(12);
|
2017-10-23 11:18:15 +01:00
|
|
|
os_delay_us(12); // Delay >12us, begin send CMD data
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t n = 0; n < chips; n++) { // Send CMD data
|
2017-10-23 11:18:15 +01:00
|
|
|
LightMy92x1Write(0x18); // ONE_SHOT_DISABLE, REACTION_FAST, BIT_WIDTH_8, FREQUENCY_DIVIDE_1, SCATTER_APDM
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
2017-10-23 11:18:15 +01:00
|
|
|
os_delay_us(12); // TStart > 12us. Delay 12 us.
|
2017-08-12 16:55:20 +01:00
|
|
|
// Send 16 DI pulse, at 14 pulse's falling edge store CMD data, and
|
|
|
|
// at 16 pulse's falling edge convert to duty mode.
|
2017-10-18 17:22:34 +01:00
|
|
|
LightDiPulse(16);
|
2017-10-23 11:18:15 +01:00
|
|
|
os_delay_us(12); // TStop > 12us.
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
|
|
|
|
2017-10-18 17:22:34 +01:00
|
|
|
void LightMy92x1Duty(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b, uint8_t duty_w, uint8_t duty_c)
|
2017-08-12 16:55:20 +01:00
|
|
|
{
|
2017-08-16 16:05:36 +01:00
|
|
|
uint8_t channels[2] = { 4, 6 };
|
2018-11-16 11:22:15 +00:00
|
|
|
|
|
|
|
uint8_t didx = 0; // 0 (AiLight)
|
|
|
|
if (LT_RGBWC == light_type) {
|
|
|
|
didx = 1; // 1 (Sonoff B1)
|
|
|
|
}
|
|
|
|
|
2017-08-16 16:05:36 +01:00
|
|
|
uint8_t duty[2][6] = {{ duty_r, duty_g, duty_b, duty_w, 0, 0 }, // Definition for RGBW channels
|
|
|
|
{ duty_w, duty_c, 0, duty_g, duty_r, duty_b }}; // Definition for RGBWC channels
|
|
|
|
|
2017-10-23 11:18:15 +01:00
|
|
|
os_delay_us(12); // TStop > 12us.
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t channel = 0; channel < channels[didx]; channel++) {
|
2017-10-18 17:22:34 +01:00
|
|
|
LightMy92x1Write(duty[didx][channel]); // Send 8bit Data
|
2017-08-13 14:31:49 +01:00
|
|
|
}
|
2017-10-23 11:18:15 +01:00
|
|
|
os_delay_us(12); // TStart > 12us. Ready for send DI pulse.
|
|
|
|
LightDiPulse(8); // Send 8 DI pulse. After 8 pulse falling edge, store old data.
|
|
|
|
os_delay_us(12); // TStop > 12us.
|
2017-08-13 14:31:49 +01:00
|
|
|
}
|
|
|
|
|
2019-01-30 22:19:40 +00:00
|
|
|
#ifdef USE_SM16716
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* SM16716 - Controlling RGB over a synchronous serial line
|
|
|
|
* Copyright (C) 2019 Gabor Simon
|
|
|
|
*
|
|
|
|
* Source: https://community.home-assistant.io/t/cheap-uk-wifi-bulbs-with-tasmota-teardown-help-tywe3s/40508/27
|
|
|
|
*
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2019-08-08 16:51:49 +01:00
|
|
|
#define D_LOG_SM16716 "SM16716: "
|
2019-01-30 22:19:40 +00:00
|
|
|
|
|
|
|
uint8_t sm16716_pin_clk = 100;
|
|
|
|
uint8_t sm16716_pin_dat = 100;
|
2019-01-31 22:17:01 +00:00
|
|
|
uint8_t sm16716_pin_sel = 100;
|
2019-02-01 05:59:53 +00:00
|
|
|
uint8_t sm16716_enabled = 0;
|
2019-01-30 22:19:40 +00:00
|
|
|
|
|
|
|
void SM16716_SendBit(uint8_t v)
|
|
|
|
{
|
|
|
|
/* NOTE:
|
|
|
|
* According to the spec sheet, max freq is 30 MHz, that is 16.6 ns per high/low half of the
|
|
|
|
* clk square wave. That is less than the overhead of 'digitalWrite' at this clock rate,
|
|
|
|
* so no additional delays are needed yet. */
|
|
|
|
|
|
|
|
digitalWrite(sm16716_pin_dat, (v != 0) ? HIGH : LOW);
|
|
|
|
//delayMicroseconds(1);
|
|
|
|
digitalWrite(sm16716_pin_clk, HIGH);
|
|
|
|
//delayMicroseconds(1);
|
|
|
|
digitalWrite(sm16716_pin_clk, LOW);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SM16716_SendByte(uint8_t v)
|
|
|
|
{
|
|
|
|
uint8_t mask;
|
|
|
|
|
|
|
|
for (mask = 0x80; mask; mask >>= 1) {
|
|
|
|
SM16716_SendBit(v & mask);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SM16716_Update(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b)
|
|
|
|
{
|
2019-01-31 22:17:01 +00:00
|
|
|
if (sm16716_pin_sel < 99) {
|
2019-02-01 05:59:53 +00:00
|
|
|
uint8_t sm16716_should_enable = (duty_r | duty_g | duty_b);
|
|
|
|
if (!sm16716_enabled && sm16716_should_enable) {
|
2019-08-08 16:51:49 +01:00
|
|
|
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color on"));
|
2019-02-01 05:59:53 +00:00
|
|
|
sm16716_enabled = 1;
|
2019-01-31 22:17:01 +00:00
|
|
|
digitalWrite(sm16716_pin_sel, HIGH);
|
2019-02-01 22:33:21 +00:00
|
|
|
// in testing I found it takes a minimum of ~380us to wake up the chip
|
|
|
|
// tested on a Merkury RGBW with an SM726EB
|
2019-02-10 08:33:05 +00:00
|
|
|
delayMicroseconds(1000);
|
2019-01-31 22:17:01 +00:00
|
|
|
SM16716_Init();
|
2019-02-01 05:59:53 +00:00
|
|
|
}
|
|
|
|
else if (sm16716_enabled && !sm16716_should_enable) {
|
2019-08-08 16:51:49 +01:00
|
|
|
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color off"));
|
2019-02-01 05:59:53 +00:00
|
|
|
sm16716_enabled = 0;
|
2019-01-31 22:17:01 +00:00
|
|
|
digitalWrite(sm16716_pin_sel, LOW);
|
|
|
|
}
|
|
|
|
}
|
2019-08-08 16:51:49 +01:00
|
|
|
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "Update; rgb=%02x%02x%02x"), duty_r, duty_g, duty_b);
|
2019-01-30 22:19:40 +00:00
|
|
|
|
|
|
|
// send start bit
|
|
|
|
SM16716_SendBit(1);
|
2019-02-24 12:07:15 +00:00
|
|
|
SM16716_SendByte(duty_r);
|
|
|
|
SM16716_SendByte(duty_g);
|
|
|
|
SM16716_SendByte(duty_b);
|
2019-02-24 05:26:10 +00:00
|
|
|
|
2019-01-30 22:19:40 +00:00
|
|
|
// send a 'do it' pulse
|
|
|
|
// (if multiple chips are chained, each one processes the 1st '1rgb' 25-bit block and
|
|
|
|
// passes on the rest, right until the one starting with 0)
|
2019-01-31 22:17:01 +00:00
|
|
|
//SM16716_Init();
|
2019-01-30 22:19:40 +00:00
|
|
|
SM16716_SendBit(0);
|
2019-02-24 12:07:15 +00:00
|
|
|
SM16716_SendByte(0);
|
|
|
|
SM16716_SendByte(0);
|
|
|
|
SM16716_SendByte(0);
|
2019-01-30 22:19:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SM16716_ModuleSelected(void)
|
|
|
|
{
|
|
|
|
sm16716_pin_clk = pin[GPIO_SM16716_CLK];
|
|
|
|
sm16716_pin_dat = pin[GPIO_SM16716_DAT];
|
2019-01-31 22:17:01 +00:00
|
|
|
sm16716_pin_sel = pin[GPIO_SM16716_SEL];
|
2019-08-08 16:51:49 +01:00
|
|
|
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "ModuleSelected; clk_pin=%d, dat_pin=%d)"), sm16716_pin_clk, sm16716_pin_dat);
|
2019-01-30 22:19:40 +00:00
|
|
|
return (sm16716_pin_clk < 99) && (sm16716_pin_dat < 99);
|
|
|
|
}
|
|
|
|
|
2019-01-30 23:32:37 +00:00
|
|
|
void SM16716_Init(void)
|
2019-01-30 22:19:40 +00:00
|
|
|
{
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t t_init = 0; t_init < 50; ++t_init) {
|
2019-01-30 22:19:40 +00:00
|
|
|
SM16716_SendBit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-30 22:32:17 +00:00
|
|
|
#endif // ifdef USE_SM16716
|
|
|
|
|
2017-08-12 16:55:20 +01:00
|
|
|
/********************************************************************************************/
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void LightInit(void)
|
2017-02-19 16:49:17 +00:00
|
|
|
{
|
2017-10-25 13:27:30 +01:00
|
|
|
uint8_t max_scheme = LS_MAX -1;
|
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
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;
|
2017-11-11 15:02:18 +00:00
|
|
|
|
2019-05-29 18:24:47 +01:00
|
|
|
#if defined(USE_WS2812) && (USE_WS2812_CTYPE > NEO_3LED)
|
|
|
|
if (LT_WS2812 == light_type) {
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.subtype++; // from RGB to RGBW
|
2019-05-29 18:24:47 +01:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((LST_SINGLE < Light.subtype) && Light.pwm_multi_channels) {
|
2019-08-06 09:57:50 +01:00
|
|
|
// we treat each PWM channel as an independant one, hence we switch to
|
|
|
|
light_controller.setPWMMultiChannel(true);
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.device = devices_present - Light.subtype + 1; // adjust if we also have relays
|
2019-08-06 09:57:50 +01:00
|
|
|
}
|
|
|
|
#ifdef DEBUG_LIGHT
|
2019-08-17 12:17:30 +01:00
|
|
|
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);
|
2019-08-06 09:57:50 +01:00
|
|
|
#endif
|
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
light_controller.setSubType(Light.subtype);
|
2019-04-25 12:06:35 +01:00
|
|
|
light_controller.loadSettings();
|
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
if (LST_SINGLE == Light.subtype) {
|
2018-11-16 11:22:15 +00:00
|
|
|
Settings.light_color[0] = 255; // One channel only supports Dimmer but needs max color
|
|
|
|
}
|
2017-10-25 13:27:30 +01:00
|
|
|
if (light_type < LT_PWM6) { // PWM
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < light_type; i++) {
|
2017-10-23 11:18:15 +01:00
|
|
|
Settings.pwm_value[i] = 0; // Disable direct PWM control
|
2017-12-16 19:11:12 +00:00
|
|
|
if (pin[GPIO_PWM1 +i] < 99) {
|
|
|
|
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
|
|
|
|
}
|
2017-10-05 12:28:31 +01:00
|
|
|
}
|
2019-02-11 18:21:49 +00:00
|
|
|
if (SONOFF_LED == my_module_type) { // Fix Sonoff Led instabilities
|
2018-12-29 16:19:13 +00:00
|
|
|
if (!my_module.io[4]) {
|
2017-10-05 12:28:31 +01:00
|
|
|
pinMode(4, OUTPUT); // Stop floating outputs
|
2017-10-01 13:32:36 +01:00
|
|
|
digitalWrite(4, LOW);
|
|
|
|
}
|
2018-12-29 16:19:13 +00:00
|
|
|
if (!my_module.io[5]) {
|
2017-10-05 12:28:31 +01:00
|
|
|
pinMode(5, OUTPUT); // Stop floating outputs
|
2017-10-01 13:32:36 +01:00
|
|
|
digitalWrite(5, LOW);
|
|
|
|
}
|
2018-12-29 16:19:13 +00:00
|
|
|
if (!my_module.io[14]) {
|
2017-10-05 12:28:31 +01:00
|
|
|
pinMode(14, OUTPUT); // Stop floating outputs
|
2017-10-01 13:32:36 +01:00
|
|
|
digitalWrite(14, LOW);
|
|
|
|
}
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
2017-11-21 15:06:51 +00:00
|
|
|
if (pin[GPIO_ARIRFRCV] < 99) {
|
2019-05-21 17:11:39 +01:00
|
|
|
if (pin[GPIO_ARIRFSEL] < 99) {
|
|
|
|
pinMode(pin[GPIO_ARIRFSEL], OUTPUT);
|
|
|
|
digitalWrite(pin[GPIO_ARIRFSEL], 1); // Turn off RF
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
|
|
|
}
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
|
|
|
#ifdef USE_WS2812 // ************************************************************************
|
2017-10-25 13:27:30 +01:00
|
|
|
else if (LT_WS2812 == light_type) {
|
2017-10-18 17:22:34 +01:00
|
|
|
Ws2812Init();
|
2017-12-29 13:55:39 +00:00
|
|
|
max_scheme = LS_MAX + WS2812_SCHEMES;
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
|
|
|
#endif // USE_WS2812 ************************************************************************
|
2019-01-30 22:19:40 +00:00
|
|
|
#ifdef USE_SM16716
|
2019-08-17 12:17:30 +01:00
|
|
|
else if (LT_SM16716 == light_type - Light.subtype) {
|
2019-01-30 22:28:25 +00:00
|
|
|
// init PWM
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
2019-01-30 22:28:25 +00:00
|
|
|
Settings.pwm_value[i] = 0; // Disable direct PWM control
|
|
|
|
if (pin[GPIO_PWM1 +i] < 99) {
|
|
|
|
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
|
|
|
|
}
|
|
|
|
}
|
2019-01-30 23:32:37 +00:00
|
|
|
// init sm16716
|
|
|
|
pinMode(sm16716_pin_clk, OUTPUT);
|
|
|
|
digitalWrite(sm16716_pin_clk, LOW);
|
|
|
|
|
|
|
|
pinMode(sm16716_pin_dat, OUTPUT);
|
|
|
|
digitalWrite(sm16716_pin_dat, LOW);
|
|
|
|
|
2019-01-31 22:17:01 +00:00
|
|
|
if (sm16716_pin_sel < 99) {
|
|
|
|
pinMode(sm16716_pin_sel, OUTPUT);
|
|
|
|
digitalWrite(sm16716_pin_sel, LOW);
|
2019-02-01 22:37:37 +00:00
|
|
|
// no need to call SM16716_Init here, it will be called after sel goes HIGH
|
|
|
|
} else {
|
|
|
|
// no sel pin means you have an 'always on' chip, so init right away
|
|
|
|
SM16716_Init();
|
2019-01-31 22:17:01 +00:00
|
|
|
}
|
2019-01-30 22:19:40 +00:00
|
|
|
}
|
|
|
|
#endif // ifdef USE_SM16716
|
2017-09-16 16:34:03 +01:00
|
|
|
else {
|
2017-10-23 11:18:15 +01:00
|
|
|
light_pdi_pin = pin[GPIO_DI];
|
|
|
|
light_pdcki_pin = pin[GPIO_DCKI];
|
2017-09-02 13:37:02 +01:00
|
|
|
|
2017-10-23 11:18:15 +01:00
|
|
|
pinMode(light_pdi_pin, OUTPUT);
|
|
|
|
pinMode(light_pdcki_pin, OUTPUT);
|
|
|
|
digitalWrite(light_pdi_pin, LOW);
|
|
|
|
digitalWrite(light_pdcki_pin, LOW);
|
2017-08-12 16:55:20 +01:00
|
|
|
|
2017-10-18 17:22:34 +01:00
|
|
|
LightMy92x1Init();
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.subtype < LST_RGB) {
|
2017-10-26 15:33:33 +01:00
|
|
|
max_scheme = LS_POWER;
|
|
|
|
}
|
|
|
|
if ((LS_WAKEUP == Settings.light_scheme) || (Settings.light_scheme > max_scheme)) {
|
|
|
|
Settings.light_scheme = LS_POWER;
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.power = 0;
|
|
|
|
Light.update = true;
|
|
|
|
Light.wakeup_active = 0;
|
2019-02-24 20:03:33 +00:00
|
|
|
|
2019-02-24 23:48:03 +00:00
|
|
|
LightUpdateColorMapping();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LightUpdateColorMapping(void)
|
|
|
|
{
|
2019-02-25 21:29:39 +00:00
|
|
|
uint8_t param = Settings.param[P_RGB_REMAP] & 127;
|
2019-04-25 12:06:35 +01:00
|
|
|
if (param > 119){ param = 0; }
|
|
|
|
|
2019-02-24 20:56:44 +00:00
|
|
|
uint8_t tmp[] = {0,1,2,3,4};
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.color_remap[0] = tmp[param / 24];
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = param / 24; i<4; ++i){
|
2019-02-24 20:56:44 +00:00
|
|
|
tmp[i] = tmp[i+1];
|
|
|
|
}
|
2019-02-24 20:03:33 +00:00
|
|
|
param = param % 24;
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.color_remap[1] = tmp[(param / 6)];
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = param / 6; i<3; ++i){
|
2019-02-24 20:56:44 +00:00
|
|
|
tmp[i] = tmp[i+1];
|
|
|
|
}
|
2019-02-24 20:03:33 +00:00
|
|
|
param = param % 6;
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.color_remap[2] = tmp[(param / 2)];
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = param / 2; i<2; ++i){
|
2019-02-24 20:56:44 +00:00
|
|
|
tmp[i] = tmp[i+1];
|
|
|
|
}
|
2019-02-24 20:03:33 +00:00
|
|
|
param = param % 2;
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.color_remap[3] = tmp[param];
|
|
|
|
Light.color_remap[4] = tmp[1-param];
|
2019-02-24 20:03:33 +00:00
|
|
|
|
2019-04-25 12:06:35 +01:00
|
|
|
// do not allow independant RGV and WC colors
|
|
|
|
bool ct_rgb_linked = !(Settings.param[P_RGB_REMAP] & 128);
|
2019-05-04 22:04:53 +01:00
|
|
|
light_controller.setCTRGBLinked(ct_rgb_linked);
|
2019-02-25 21:29:39 +00:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
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]);
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
|
|
|
|
2019-05-13 10:27:46 +01:00
|
|
|
void LightSetDimmer(uint8_t dimmer) {
|
|
|
|
light_controller.changeDimmer(dimmer);
|
|
|
|
}
|
|
|
|
|
2019-08-06 09:57:50 +01:00
|
|
|
// If SetOption68 is set, get the brightness for a specific device
|
|
|
|
uint8_t LightGetBri(uint8_t device) {
|
|
|
|
uint8_t bri = 254; // default value if relay
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.pwm_multi_channels) {
|
|
|
|
if ((device >= Light.device) && (device < Light.device + LST_MAX) && (device <= devices_present)) {
|
|
|
|
bri = Light.current_color[device - Light.device];
|
2019-08-06 09:57:50 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
} else if (device == Light.device) {
|
2019-08-06 09:57:50 +01:00
|
|
|
bri = light_state.getBri();
|
|
|
|
}
|
|
|
|
return bri;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If SetOption68 is set, get the brightness for a specific device
|
|
|
|
|
|
|
|
void LightSetBri(uint8_t device, uint8_t bri) {
|
2019-08-17 12:17:30 +01:00
|
|
|
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);
|
2019-08-06 09:57:50 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
} else if (device == Light.device) {
|
2019-08-06 09:57:50 +01:00
|
|
|
light_controller.changeBri(bri);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-18 17:22:34 +01:00
|
|
|
void LightSetColorTemp(uint16_t ct)
|
2017-08-16 16:05:36 +01:00
|
|
|
{
|
|
|
|
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
|
2017-09-02 13:37:02 +01:00
|
|
|
*
|
2017-08-16 16:05:36 +01:00
|
|
|
* ct = 153 = 2000K = Warm = CCWW = 00FF
|
|
|
|
* ct = 500 = 6500K = Cold = CCWW = FF00
|
|
|
|
*/
|
2019-04-17 20:21:56 +01:00
|
|
|
// don't set CT if not supported
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != Light.subtype)) {
|
2019-04-17 20:21:56 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-05-04 22:04:53 +01:00
|
|
|
light_controller.changeCTB(ct, light_state.getBriCT());
|
2017-08-16 16:05:36 +01:00
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
uint16_t LightGetColorTemp(void)
|
2017-08-16 16:05:36 +01:00
|
|
|
{
|
2019-04-17 20:21:56 +01:00
|
|
|
// don't calculate CT for unsupported devices
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != Light.subtype)) {
|
2019-04-17 20:21:56 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2019-05-04 22:04:53 +01:00
|
|
|
return (light_state.getColorMode() & LCM_CT) ? light_state.getCT() : 0;
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
|
|
|
|
2017-12-27 13:10:55 +00:00
|
|
|
void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value)
|
|
|
|
{
|
|
|
|
/* lo - below lo is green
|
|
|
|
hi - above hi is red
|
|
|
|
*/
|
|
|
|
if (Settings.flag.light_signal) {
|
2019-04-25 12:06:35 +01:00
|
|
|
uint16_t signal = changeUIntScale(value, lo, hi, 0, 255); // 0..255
|
2019-03-08 14:15:42 +00:00
|
|
|
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Light signal %d"), signal);
|
2019-05-04 22:04:53 +01:00
|
|
|
light_controller.changeRGB(signal, 255 - signal, 0, true); // keep bri
|
2017-12-27 13:10:55 +00:00
|
|
|
Settings.light_scheme = 0;
|
2019-04-25 12:06:35 +01:00
|
|
|
if (0 == light_state.getBri()) {
|
|
|
|
light_controller.changeBri(50);
|
2017-12-27 13:10:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-25 12:06:35 +01:00
|
|
|
// convert channels to string, use Option 17 to foce decimal, unless force_hex
|
|
|
|
char* LightGetColor(char* scolor, boolean force_hex = false)
|
2017-08-12 16:55:20 +01:00
|
|
|
{
|
2019-04-25 12:06:35 +01:00
|
|
|
light_controller.calcLevels();
|
2017-08-12 16:55:20 +01:00
|
|
|
scolor[0] = '\0';
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
2019-04-25 12:06:35 +01:00
|
|
|
if (!force_hex && Settings.flag.decimal_text) {
|
2019-08-17 12:17:30 +01:00
|
|
|
snprintf_P(scolor, LIGHT_COLOR_SIZE, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Light.current_color[i]);
|
2017-10-23 11:18:15 +01:00
|
|
|
} else {
|
2019-08-17 12:17:30 +01:00
|
|
|
snprintf_P(scolor, LIGHT_COLOR_SIZE, PSTR("%s%02X"), scolor, Light.current_color[i]);
|
2017-10-23 11:18:15 +01:00
|
|
|
}
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
|
|
|
return scolor;
|
2017-07-15 14:07:30 +01:00
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void LightPowerOn(void)
|
2017-10-25 13:27:30 +01:00
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
if (light_state.getBri() && !(Light.power)) {
|
|
|
|
ExecuteCommandPower(Light.device, POWER_ON, SRC_LIGHT);
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-20 11:12:39 +00:00
|
|
|
void LightState(uint8_t append)
|
2017-07-15 14:07:30 +01:00
|
|
|
{
|
2019-08-01 14:47:00 +01:00
|
|
|
char scolor[LIGHT_COLOR_SIZE];
|
2018-01-13 14:53:02 +00:00
|
|
|
char scommand[33];
|
2017-09-02 13:37:02 +01:00
|
|
|
|
2018-01-20 11:12:39 +00:00
|
|
|
if (append) {
|
2019-03-23 16:00:59 +00:00
|
|
|
ResponseAppend_P(PSTR(","));
|
2018-01-20 11:12:39 +00:00
|
|
|
} else {
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(PSTR("{"));
|
2017-07-15 14:07:30 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
if (!Light.pwm_multi_channels) {
|
|
|
|
GetPowerDevice(scommand, Light.device, sizeof(scommand), Settings.flag.device_index_enable);
|
|
|
|
ResponseAppend_P(PSTR("\"%s\":\"%s\",\"" D_CMND_DIMMER "\":%d"), scommand, GetStateText(Light.power), light_state.getDimmer());
|
2019-08-16 17:33:41 +01:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.subtype > LST_SINGLE) {
|
2019-08-16 17:33:41 +01:00
|
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor));
|
|
|
|
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 status for each channel
|
|
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_CHANNEL "\":[" ));
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
|
|
uint8_t channel_raw = Light.current_color[i];
|
2019-08-16 17:33:41 +01:00
|
|
|
uint8_t channel = changeUIntScale(channel_raw,0,255,0,100);
|
|
|
|
// if non null, force to be at least 1
|
|
|
|
if ((0 == channel) && (channel_raw > 0)) { channel = 1; }
|
|
|
|
ResponseAppend_P(PSTR("%s%d" ), (i > 0 ? "," : ""), channel);
|
|
|
|
}
|
|
|
|
ResponseAppend_P(PSTR("]"));
|
2018-03-21 08:56:39 +00:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
|
2019-08-16 17:33:41 +01:00
|
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_COLORTEMPERATURE "\":%d"), light_state.getCT());
|
2018-01-20 11:12:39 +00:00
|
|
|
}
|
2019-08-16 17:33:41 +01:00
|
|
|
|
|
|
|
if (append) {
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.subtype >= LST_RGB) {
|
2019-08-16 17:33:41 +01:00
|
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_SCHEME "\":%d"), Settings.light_scheme);
|
|
|
|
}
|
|
|
|
if (LT_WS2812 == light_type) {
|
|
|
|
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));
|
2018-01-20 11:12:39 +00:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
} else { // Light.pwm_multi_channels
|
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
|
|
GetPowerDevice(scommand, Light.device + i, sizeof(scommand), 1);
|
|
|
|
uint32_t light_power_masked = Light.power & (1 << i); // the Light.power value for this device
|
2019-08-16 17:33:41 +01:00
|
|
|
light_power_masked = light_power_masked ? 1 : 0; // convert to on/off
|
2019-08-17 12:17:30 +01:00
|
|
|
ResponseAppend_P(PSTR("\"%s\":\"%s\",\"" D_CMND_CHANNEL "%d\":%d,"), scommand, GetStateText(light_power_masked), Light.device + i,
|
|
|
|
changeUIntScale(Light.current_color[i], 0, 255, 0, 100));
|
2019-08-16 17:33:41 +01:00
|
|
|
}
|
2019-08-17 10:06:49 +01:00
|
|
|
ResponseAppend_P(PSTR("\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor));
|
2019-08-17 12:17:30 +01:00
|
|
|
} // Light.pwm_multi_channels
|
2019-08-16 17:33:41 +01:00
|
|
|
|
|
|
|
if (!append) {
|
Add support for Shelly 1PM Template
Add support for Shelly 1PM Template {"NAME":"Shelly 1PM","GPIO":[56,0,0,0,82,134,0,0,0,0,0,21,0],"FLAG":2,"BASE":18} (#5716)
2019-05-13 17:26:07 +01:00
|
|
|
ResponseJsonEnd();
|
2018-01-20 11:12:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void LightPreparePower(void)
|
2018-01-20 11:12:39 +00:00
|
|
|
{
|
2019-08-06 09:57:50 +01:00
|
|
|
#ifdef DEBUG_LIGHT
|
2019-08-17 12:17:30 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower power=%d Light.power=%d", power, Light.power);
|
2019-08-06 09:57:50 +01:00
|
|
|
#endif
|
|
|
|
// If multi-channels, then we only switch off channels with a value of zero
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.pwm_multi_channels) {
|
|
|
|
// for (uint32_t i = 0; i < Light.subtype; i++) {
|
2019-08-06 09:57:50 +01:00
|
|
|
// // if channel is non-null, channel is supposed to be on, but it is off, do Power On
|
2019-08-17 12:17:30 +01:00
|
|
|
// if ((Light.current_color[i]) && (bitRead(Light.power, i)) && (0 == bitRead(power, i + Light.device - 1))) {
|
|
|
|
// ExecuteCommandPower(Light.device + i, POWER_ON_NO_STATE, SRC_LIGHT);
|
|
|
|
// //bitSet(Settings.power, i + Light.device - 1);
|
2019-08-06 09:57:50 +01:00
|
|
|
// #ifdef DEBUG_LIGHT
|
2019-08-17 12:17:30 +01:00
|
|
|
// AddLog_P2(LOG_LEVEL_DEBUG, "ExecuteCommandPower ON device=%d", Light.device + i);
|
2019-08-06 09:57:50 +01:00
|
|
|
// #endif
|
|
|
|
// }
|
|
|
|
// // if channel is zero and channel is on, set it off
|
2019-08-17 12:17:30 +01:00
|
|
|
// if ((0 == Light.current_color[i]) && bitRead(power, i + Light.device - 1)) {
|
|
|
|
// ExecuteCommandPower(Light.device + i, POWER_OFF_NO_STATE, SRC_LIGHT);
|
|
|
|
// //bitClear(Settings.power, i + Light.device - 1);
|
2019-08-06 09:57:50 +01:00
|
|
|
// #ifdef DEBUG_LIGHT
|
2019-08-17 12:17:30 +01:00
|
|
|
// AddLog_P2(LOG_LEVEL_DEBUG, "ExecuteCommandPower OFF device=%d", Light.device + i);
|
2019-08-06 09:57:50 +01:00
|
|
|
// #endif
|
|
|
|
// }
|
|
|
|
// #ifdef USE_DOMOTICZ
|
2019-08-17 12:17:30 +01:00
|
|
|
// DomoticzUpdatePowerState(Light.device + i);
|
2019-08-06 09:57:50 +01:00
|
|
|
// #endif // USE_DOMOTICZ
|
|
|
|
// }
|
|
|
|
} else {
|
2019-08-17 12:17:30 +01:00
|
|
|
if (light_state.getBri() && !(Light.power)) {
|
2019-08-06 09:57:50 +01:00
|
|
|
if (!Settings.flag.not_power_linked) {
|
2019-08-17 12:17:30 +01:00
|
|
|
ExecuteCommandPower(Light.device, POWER_ON_NO_STATE, SRC_LIGHT);
|
2019-08-06 09:57:50 +01:00
|
|
|
}
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
else if (!light_state.getBri() && Light.power) {
|
|
|
|
ExecuteCommandPower(Light.device, POWER_OFF_NO_STATE, SRC_LIGHT);
|
2018-01-27 16:52:48 +00:00
|
|
|
}
|
2018-01-20 11:12:39 +00:00
|
|
|
#ifdef USE_DOMOTICZ
|
2019-08-17 12:17:30 +01:00
|
|
|
DomoticzUpdatePowerState(Light.device);
|
2018-01-20 11:12:39 +00:00
|
|
|
#endif // USE_DOMOTICZ
|
2019-08-06 09:57:50 +01:00
|
|
|
}
|
|
|
|
|
2019-03-20 13:05:43 +00:00
|
|
|
if (Settings.flag3.hass_tele_on_power) { MqttPublishTeleState(); }
|
2018-01-20 11:12:39 +00:00
|
|
|
|
2019-08-06 09:57:50 +01:00
|
|
|
#ifdef DEBUG_LIGHT
|
2019-08-17 12:17:30 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower End power=%d Light.power=%d", power, Light.power);
|
2019-08-06 09:57:50 +01:00
|
|
|
#endif
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.power = power >> (Light.device - 1); // reset next state
|
2018-01-20 11:12:39 +00:00
|
|
|
LightState(0);
|
2017-07-15 14:07:30 +01:00
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void LightFade(void)
|
2017-10-25 13:27:30 +01:00
|
|
|
{
|
2017-10-26 15:33:33 +01:00
|
|
|
if (0 == Settings.light_fade) {
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
|
|
Light.new_color[i] = Light.current_color[i];
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
|
|
|
} else {
|
2017-10-26 15:33:33 +01:00
|
|
|
uint8_t shift = Settings.light_speed;
|
|
|
|
if (Settings.light_speed > 6) {
|
2019-08-17 12:17:30 +01:00
|
|
|
shift = (Light.strip_timer_counter % (Settings.light_speed -6)) ? 0 : 8;
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
|
|
|
if (shift) {
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
|
|
if (Light.new_color[i] != Light.current_color[i]) {
|
|
|
|
if (Light.new_color[i] < Light.current_color[i]) {
|
|
|
|
Light.new_color[i] += ((Light.current_color[i] - Light.new_color[i]) >> shift) +1;
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.new_color[i] > Light.current_color[i]) {
|
|
|
|
Light.new_color[i] -= ((Light.new_color[i] - Light.current_color[i]) >> shift) +1;
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LightWheel(uint8_t wheel_pos)
|
|
|
|
{
|
|
|
|
wheel_pos = 255 - wheel_pos;
|
|
|
|
if (wheel_pos < 85) {
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.entry_color[0] = 255 - wheel_pos * 3;
|
|
|
|
Light.entry_color[1] = 0;
|
|
|
|
Light.entry_color[2] = wheel_pos * 3;
|
2017-10-25 13:27:30 +01:00
|
|
|
} else if (wheel_pos < 170) {
|
|
|
|
wheel_pos -= 85;
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.entry_color[0] = 0;
|
|
|
|
Light.entry_color[1] = wheel_pos * 3;
|
|
|
|
Light.entry_color[2] = 255 - wheel_pos * 3;
|
2017-10-25 13:27:30 +01:00
|
|
|
} else {
|
|
|
|
wheel_pos -= 170;
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.entry_color[0] = wheel_pos * 3;
|
|
|
|
Light.entry_color[1] = 255 - wheel_pos * 3;
|
|
|
|
Light.entry_color[2] = 0;
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.entry_color[3] = 0;
|
|
|
|
Light.entry_color[4] = 0;
|
2017-10-26 15:33:33 +01:00
|
|
|
float dimmer = 100 / (float)Settings.light_dimmer;
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < LST_RGB; i++) {
|
2019-08-17 12:17:30 +01:00
|
|
|
float temp = (float)Light.entry_color[i] / dimmer + 0.5f;
|
|
|
|
Light.entry_color[i] = (uint8_t)temp;
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LightCycleColor(int8_t direction)
|
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.strip_timer_counter % (Settings.light_speed * 2)) {
|
2017-10-25 13:27:30 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.wheel += direction;
|
|
|
|
LightWheel(Light.wheel);
|
|
|
|
memcpy(Light.new_color, Light.entry_color, sizeof(Light.new_color));
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void LightRandomColor(void)
|
2017-10-25 13:27:30 +01:00
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
bool update = false;
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < LST_RGB; i++) {
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.new_color[i] != Light.current_color[i]) {
|
|
|
|
update = true;
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
if (!update) {
|
|
|
|
Light.wheel = random(255);
|
|
|
|
LightWheel(Light.wheel);
|
|
|
|
memcpy(Light.current_color, Light.entry_color, sizeof(Light.current_color));
|
2017-10-25 13:27:30 +01:00
|
|
|
}
|
|
|
|
LightFade();
|
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void LightSetPower(void)
|
2017-02-19 16:49:17 +00:00
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
// Light.power = XdrvMailbox.index;
|
|
|
|
Light.old_power = Light.power;
|
|
|
|
//Light.power = bitRead(XdrvMailbox.index, Light.device -1);
|
2019-08-06 09:57:50 +01:00
|
|
|
uint32_t mask = 1; // default mask
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.pwm_multi_channels) {
|
|
|
|
mask = (1 << Light.subtype) - 1; // wider mask
|
2019-08-06 09:57:50 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
uint32_t shift = Light.device - 1;
|
2019-08-06 09:57:50 +01:00
|
|
|
// If PWM multi_channels
|
2019-08-17 12:17:30 +01:00
|
|
|
// Ex: 3 Relays and 4 PWM - devices_present = 7, Light.device = 4, Light.subtype = 4
|
2019-08-06 09:57:50 +01:00
|
|
|
// Result: mask = 0b00001111 = 0x0F, shift = 3.
|
|
|
|
// Power bits we consider are: 0b01111000 = 0x78
|
2019-08-17 12:17:30 +01:00
|
|
|
// If regular situation: devices_present == Light.subtype
|
|
|
|
Light.power = (XdrvMailbox.index & (mask << shift)) >> shift;
|
|
|
|
if (Light.wakeup_active) {
|
|
|
|
Light.wakeup_active--;
|
2017-07-03 10:45:15 +01:00
|
|
|
}
|
2019-08-06 09:57:50 +01:00
|
|
|
#ifdef DEBUG_LIGHT
|
2019-08-17 12:17:30 +01:00
|
|
|
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);
|
2019-08-06 09:57:50 +01:00
|
|
|
#endif
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.power != Light.old_power) {
|
|
|
|
Light.update = true;
|
2017-09-18 17:06:46 +01:00
|
|
|
}
|
2017-10-18 17:22:34 +01:00
|
|
|
LightAnimate();
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void LightAnimate(void)
|
2017-02-19 16:49:17 +00:00
|
|
|
{
|
2019-05-02 21:50:19 +01:00
|
|
|
uint8_t cur_col[LST_MAX];
|
2018-02-03 22:25:05 +00:00
|
|
|
uint16_t light_still_on = 0;
|
2017-09-02 13:37:02 +01:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.strip_timer_counter++;
|
|
|
|
if (!Light.power) { // Power Off
|
2017-10-18 17:22:34 +01:00
|
|
|
sleep = Settings.sleep;
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.strip_timer_counter = 0;
|
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
|
|
light_still_on += Light.new_color[i];
|
2017-10-26 15:33:33 +01:00
|
|
|
}
|
2017-10-27 11:12:07 +01:00
|
|
|
if (light_still_on && Settings.light_fade && (Settings.light_scheme < LS_MAX)) {
|
2017-10-26 15:33:33 +01:00
|
|
|
uint8_t speed = Settings.light_speed;
|
|
|
|
if (speed > 6) {
|
|
|
|
speed = 6;
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
|
|
if (Light.new_color[i] > 0) {
|
|
|
|
Light.new_color[i] -= (Light.new_color[i] >> speed) +1;
|
2017-10-26 15:33:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
|
|
Light.new_color[i] = 0;
|
2017-10-26 15:33:33 +01:00
|
|
|
}
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
|
|
|
else {
|
2019-02-25 18:47:45 +00:00
|
|
|
#ifdef PWM_LIGHTSCHEME0_IGNORE_SLEEP
|
2019-01-27 10:25:28 +00:00
|
|
|
sleep = (LS_POWER == Settings.light_scheme) ? Settings.sleep : 0; // If no animation then use sleep as is
|
2019-02-18 06:21:57 +00:00
|
|
|
#else
|
|
|
|
sleep = 0;
|
2019-02-25 18:47:45 +00:00
|
|
|
#endif // PWM_LIGHTSCHEME0_IGNORE_SLEEP
|
2017-10-26 15:33:33 +01:00
|
|
|
switch (Settings.light_scheme) {
|
2017-10-25 13:27:30 +01:00
|
|
|
case LS_POWER:
|
2019-04-25 12:06:35 +01:00
|
|
|
light_controller.calcLevels();
|
2017-10-25 13:27:30 +01:00
|
|
|
LightFade();
|
2017-09-16 16:34:03 +01:00
|
|
|
break;
|
2017-10-25 13:27:30 +01:00
|
|
|
case LS_WAKEUP:
|
2019-08-17 12:17:30 +01:00
|
|
|
if (2 == Light.wakeup_active) {
|
|
|
|
Light.wakeup_active = 1;
|
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
|
|
Light.new_color[i] = 0;
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.wakeup_counter = 0;
|
|
|
|
Light.wakeup_dimmer = 0;
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
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);
|
2019-04-25 12:06:35 +01:00
|
|
|
light_controller.calcLevels();
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
|
|
Light.new_color[i] = Light.current_color[i];
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
|
|
|
} else {
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\"}"));
|
2018-01-18 15:19:28 +00:00
|
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_CMND_WAKEUP));
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.wakeup_active = 0;
|
2017-10-26 15:33:33 +01:00
|
|
|
Settings.light_scheme = LS_POWER;
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2017-10-25 13:27:30 +01:00
|
|
|
case LS_CYCLEUP:
|
|
|
|
LightCycleColor(1);
|
|
|
|
break;
|
|
|
|
case LS_CYCLEDN:
|
|
|
|
LightCycleColor(-1);
|
|
|
|
break;
|
|
|
|
case LS_RANDOM:
|
|
|
|
LightRandomColor();
|
|
|
|
break;
|
2017-09-16 16:34:03 +01:00
|
|
|
#ifdef USE_WS2812 // ************************************************************************
|
|
|
|
default:
|
2017-10-25 13:27:30 +01:00
|
|
|
if (LT_WS2812 == light_type) {
|
2017-10-26 15:33:33 +01:00
|
|
|
Ws2812ShowScheme(Settings.light_scheme -LS_MAX);
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
2017-09-16 16:34:03 +01:00
|
|
|
#endif // USE_WS2812 ************************************************************************
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
|
|
|
}
|
2017-09-16 16:34:03 +01:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((Settings.light_scheme < LS_MAX) || !Light.power) {
|
2019-08-06 09:57:50 +01:00
|
|
|
|
|
|
|
// If SetOption68, multi_channels
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.pwm_multi_channels) {
|
|
|
|
// if multi-channels, specifically apply the Light.power bits
|
2019-08-06 09:57:50 +01:00
|
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
2019-08-17 12:17:30 +01:00
|
|
|
if (0 == bitRead(Light.power,i)) { // if power down bit is zero
|
|
|
|
Light.new_color[i] = 0; // shut down this channel
|
2019-08-06 09:57:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// #ifdef DEBUG_LIGHT
|
2019-08-17 12:17:30 +01:00
|
|
|
// 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]);
|
2019-08-06 09:57:50 +01:00
|
|
|
// #endif
|
|
|
|
}
|
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
if (memcmp(Light.last_color, Light.new_color, Light.subtype)) {
|
|
|
|
Light.update = true;
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.update) {
|
2019-05-03 19:10:13 +01:00
|
|
|
uint16_t cur_col_10bits[LST_MAX]; // 10 bits version of cur_col for PWM
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.update = false;
|
2019-05-02 21:50:19 +01:00
|
|
|
|
2019-07-07 09:15:50 +01:00
|
|
|
// first set 8 and 10 bits channels
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
2019-08-17 12:17:30 +01:00
|
|
|
cur_col[i] = Light.last_color[i] = Light.new_color[i];
|
2019-05-03 19:10:13 +01:00
|
|
|
// Extend from 8 to 10 bits if no correction (in case no gamma correction is required)
|
|
|
|
cur_col_10bits[i] = changeUIntScale(cur_col[i], 0, 255, 0, 1023);
|
2019-02-24 12:07:15 +00:00
|
|
|
}
|
|
|
|
|
2019-05-05 17:18:20 +01:00
|
|
|
if (PHILIPS == my_module_type) {
|
2019-08-06 09:57:50 +01:00
|
|
|
calcGammaXiaomiBulbs(cur_col, cur_col_10bits);
|
2019-08-17 12:17:30 +01:00
|
|
|
} else if (Light.pwm_multi_channels) {
|
2019-08-06 09:57:50 +01:00
|
|
|
calcGammaMultiChannels(cur_col, cur_col_10bits);
|
2019-07-07 09:15:50 +01:00
|
|
|
} else { // PHILIPS != my_module_type
|
2019-08-06 09:57:50 +01:00
|
|
|
calcGammaBulbs(cur_col, cur_col_10bits);
|
2019-07-07 09:15:50 +01:00
|
|
|
|
|
|
|
// Now see if we need to mix RGB and True White
|
|
|
|
// Valid only for LST_RGBW, LST_RGBWC, rgbwwTable[4] is zero, and white is zero (see doc)
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((LST_RGBW <= Light.subtype) && (0 == Settings.rgbwwTable[4]) && (0 == cur_col[3]+cur_col[4])) {
|
2019-07-07 09:15:50 +01:00
|
|
|
uint32_t min_rgb_10 = min3(cur_col_10bits[0], cur_col_10bits[1], cur_col_10bits[2]);
|
|
|
|
uint8_t min_rgb = min3(cur_col[0], cur_col[1], cur_col[2]);
|
|
|
|
for (uint32_t i=0; i<3; i++) {
|
|
|
|
// substract white and adjust according to rgbwwTable
|
|
|
|
cur_col_10bits[i] = changeUIntScale(cur_col_10bits[i] - min_rgb_10, 0, 255, 0, Settings.rgbwwTable[i]);
|
|
|
|
cur_col[i] = changeUIntScale(cur_col[i] - min_rgb, 0, 255, 0, Settings.rgbwwTable[i]);
|
|
|
|
}
|
|
|
|
// compute the adjusted white levels for 10 and 8 bits
|
|
|
|
uint32_t white_10 = changeUIntScale(min_rgb_10, 0, 255, 0, Settings.rgbwwTable[3]); // set white power down corrected with rgbwwTable[3]
|
|
|
|
uint32_t white = changeUIntScale(min_rgb, 0, 255, 0, Settings.rgbwwTable[3]); // set white power down corrected with rgbwwTable[3]
|
2019-08-17 12:17:30 +01:00
|
|
|
if (LST_RGBW == Light.subtype) {
|
2019-07-07 09:15:50 +01:00
|
|
|
// we simply set the white channel
|
|
|
|
cur_col_10bits[3] = white_10;
|
|
|
|
cur_col[3] = white;
|
|
|
|
} else { // LST_RGBWC
|
|
|
|
// we distribute white between cold and warm according to CT value
|
|
|
|
uint32_t ct = light_state.getCT();
|
|
|
|
cur_col_10bits[4] = changeUIntScale(ct, 153, 500, 0, white_10);
|
|
|
|
cur_col_10bits[3] = white_10 - cur_col_10bits[4];
|
|
|
|
cur_col[4] = changeUIntScale(ct, 153, 500, 0, white);
|
|
|
|
cur_col[3] = white - cur_col[4];
|
2019-05-03 19:10:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// final adjusments for PMW, post-gamma correction
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
2019-05-05 17:18:20 +01:00
|
|
|
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2)
|
2019-05-03 19:10:13 +01:00
|
|
|
// Fix unwanted blinking and PWM watchdog errors for values close to pwm_range (H801, Arilux and BN-SZ01)
|
|
|
|
// but keep value 1023 if full range (PWM will be deactivated in this case)
|
|
|
|
if ((cur_col_10bits[i] > 1008) && (cur_col_10bits[i] < 1023)) {
|
|
|
|
cur_col_10bits[i] = 1008;
|
|
|
|
}
|
2019-05-05 17:18:20 +01:00
|
|
|
#endif
|
2019-05-03 19:10:13 +01:00
|
|
|
// scale from 0..1023 to 0..pwm_range, but keep any non-zero value to at least 1
|
|
|
|
cur_col_10bits[i] = (cur_col_10bits[i] > 0) ? changeUIntScale(cur_col_10bits[i], 1, 1023, 1, Settings.pwm_range) : 0;
|
2019-02-24 12:07:15 +00:00
|
|
|
}
|
|
|
|
|
2019-05-03 19:10:13 +01:00
|
|
|
// apply port remapping on both 8 bits and 10 bits versions
|
|
|
|
uint8_t orig_col[LST_MAX];
|
|
|
|
uint16_t orig_col_10bits[LST_MAX];
|
2019-02-24 20:03:33 +00:00
|
|
|
memcpy(orig_col, cur_col, sizeof(orig_col));
|
2019-05-03 19:10:13 +01:00
|
|
|
memcpy(orig_col_10bits, cur_col_10bits, sizeof(orig_col_10bits));
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
2019-08-17 12:17:30 +01:00
|
|
|
cur_col[i] = orig_col[Light.color_remap[i]];
|
|
|
|
cur_col_10bits[i] = orig_col_10bits[Light.color_remap[i]];
|
2019-02-24 12:07:15 +00:00
|
|
|
}
|
|
|
|
|
2019-05-03 19:10:13 +01:00
|
|
|
// now apply the actual PWM values, adjusted and remapped 10-bits range
|
2019-05-02 21:50:19 +01:00
|
|
|
if (light_type < LT_PWM6) { // only for direct PWM lights, not for Tuya, Armtronix...
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
2017-09-16 16:34:03 +01:00
|
|
|
if (pin[GPIO_PWM1 +i] < 99) {
|
2019-07-07 09:15:50 +01:00
|
|
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "Cur_Col%d 10 bits %d, Pwm%d %d"), i, cur_col_10bits[i], i+1, cur_col[i]);
|
2019-05-03 19:10:13 +01:00
|
|
|
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - cur_col_10bits[i] : cur_col_10bits[i]);
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-01-13 04:33:54 +00:00
|
|
|
|
|
|
|
char *tmp_data = XdrvMailbox.data;
|
|
|
|
uint16_t tmp_data_len = XdrvMailbox.data_len;
|
|
|
|
|
2018-12-17 16:34:55 +00:00
|
|
|
XdrvMailbox.data = (char*)cur_col;
|
|
|
|
XdrvMailbox.data_len = sizeof(cur_col);
|
|
|
|
if (XdrvCall(FUNC_SET_CHANNELS)) {
|
|
|
|
// Serviced
|
|
|
|
}
|
2017-09-16 16:34:03 +01:00
|
|
|
#ifdef USE_WS2812 // ************************************************************************
|
2018-12-17 16:34:55 +00:00
|
|
|
else if (LT_WS2812 == light_type) {
|
2017-11-11 15:02:18 +00:00
|
|
|
Ws2812SetColor(0, cur_col[0], cur_col[1], cur_col[2], cur_col[3]);
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
|
|
|
#endif // USE_ES2812 ************************************************************************
|
2019-01-30 22:19:40 +00:00
|
|
|
#ifdef USE_SM16716
|
2019-08-17 12:17:30 +01:00
|
|
|
else if (LT_SM16716 == light_type - Light.subtype) {
|
2019-02-01 05:59:53 +00:00
|
|
|
// handle any PWM pins, skipping the first 3 values for sm16716
|
2019-08-17 12:17:30 +01:00
|
|
|
for (uint32_t i = 3; i < Light.subtype; i++) {
|
2019-01-30 23:15:25 +00:00
|
|
|
if (pin[GPIO_PWM1 +i-3] < 99) {
|
2019-05-03 19:10:13 +01:00
|
|
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "Cur_Col%d 10 bits %d, Pwm%d %d"), i, cur_col[i], i+1, curcol);
|
|
|
|
analogWrite(pin[GPIO_PWM1 +i-3], bitRead(pwm_inverted, i-3) ? Settings.pwm_range - cur_col_10bits[i] : cur_col_10bits[i]);
|
2019-01-30 23:15:25 +00:00
|
|
|
}
|
|
|
|
}
|
2019-02-01 05:59:53 +00:00
|
|
|
// handle sm16716 update
|
2019-01-30 23:32:37 +00:00
|
|
|
SM16716_Update(cur_col[0], cur_col[1], cur_col[2]);
|
2019-01-30 22:19:40 +00:00
|
|
|
}
|
|
|
|
#endif // ifdef USE_SM16716
|
2018-12-17 16:34:55 +00:00
|
|
|
else if (light_type > LT_WS2812) {
|
2019-07-07 09:15:50 +01:00
|
|
|
//AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION "Cur_Col %d,%d,%d,%d,%d"), cur_col[0], cur_col[1], cur_col[2], cur_col[3], cur_col[4]);
|
2017-10-18 17:22:34 +01:00
|
|
|
LightMy92x1Duty(cur_col[0], cur_col[1], cur_col[2], cur_col[3], cur_col[4]);
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2019-01-13 04:33:54 +00:00
|
|
|
XdrvMailbox.data = tmp_data;
|
|
|
|
XdrvMailbox.data_len = tmp_data_len;
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-06 09:57:50 +01:00
|
|
|
// Do specific computation for Xiaomi Bulbs
|
|
|
|
void calcGammaXiaomiBulbs(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
|
|
|
|
// Xiaomi Philips bulbs follow a different scheme:
|
|
|
|
uint8_t cold; // channel 1 is the color tone, mapped to cold channel (0..255)
|
|
|
|
light_state.getCW(&cold, nullptr);
|
|
|
|
cur_col[1] = cold;
|
|
|
|
cur_col_10bits[1] = changeUIntScale(cur_col[1], 0, 255, 0, 1023);
|
|
|
|
// now set channel 0 to overall brightness
|
|
|
|
uint8_t pxBri = light_state.getBriCT();
|
|
|
|
// channel 0=intensity, channel1=temperature
|
|
|
|
if (Settings.light_correction) { // gamma correction
|
|
|
|
cur_col[0] = ledGamma(pxBri);
|
|
|
|
cur_col_10bits[0] = ledGamma(pxBri, 10); // 10 bits gamma correction
|
|
|
|
} else {
|
|
|
|
cur_col[0] = pxBri;
|
|
|
|
cur_col_10bits[0] = changeUIntScale(pxBri, 0, 255, 0, 1023); // no gamma, extend to 10 bits
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Just apply basic Gamma to each channel
|
|
|
|
void calcGammaMultiChannels(uint8_t cur_col[5], uint16_t cur_col_10bits[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_10bits[i] = ledGamma(cur_col[i], 10);
|
|
|
|
cur_col[i] = ledGamma(cur_col[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void calcGammaBulbs(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
|
|
|
|
// Apply gamma correction for 8 and 10 bits resolutions, if needed
|
|
|
|
if (Settings.light_correction) {
|
|
|
|
// First apply combined correction to the overall white power
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
|
2019-08-06 09:57:50 +01:00
|
|
|
uint8_t w_idx[2] = {0, 1}; // if LST_COLDWARM, channels 0 and 1
|
2019-08-17 12:17:30 +01:00
|
|
|
if (LST_RGBWC == Light.subtype) { // if LST_RGBWC, channels 3 and 4
|
2019-08-06 09:57:50 +01:00
|
|
|
w_idx[0] = 3;
|
|
|
|
w_idx[1] = 4;
|
|
|
|
}
|
|
|
|
uint16_t white_bri = cur_col[w_idx[0]] + cur_col[w_idx[1]];
|
|
|
|
// if sum of both channels is > 255, then channels are probablu uncorrelated
|
|
|
|
if (white_bri <= 255) {
|
|
|
|
// we calculate the gamma corrected sum of CW + WW
|
|
|
|
uint16_t white_bri_10bits = ledGamma(white_bri, 10);
|
|
|
|
uint8_t white_bri_8bits = ledGamma(white_bri);
|
|
|
|
// then we split the total energy among the cold and warm leds
|
|
|
|
cur_col_10bits[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_10bits);
|
|
|
|
cur_col_10bits[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_10bits);
|
|
|
|
cur_col[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_8bits);
|
|
|
|
cur_col[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_8bits);
|
|
|
|
} else {
|
|
|
|
cur_col_10bits[w_idx[0]] = ledGamma(cur_col[w_idx[0]], 10);
|
|
|
|
cur_col_10bits[w_idx[1]] = ledGamma(cur_col[w_idx[1]], 10);
|
|
|
|
cur_col[w_idx[0]] = ledGamma(cur_col[w_idx[0]]);
|
|
|
|
cur_col[w_idx[1]] = ledGamma(cur_col[w_idx[1]]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// then apply gamma correction to RGB channels
|
2019-08-17 12:17:30 +01:00
|
|
|
if (LST_RGB <= Light.subtype) {
|
2019-08-06 09:57:50 +01:00
|
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
|
|
cur_col_10bits[i] = ledGamma(cur_col[i], 10);
|
|
|
|
cur_col[i] = ledGamma(cur_col[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If RGBW or Single channel, also adjust White channel
|
2019-08-17 12:17:30 +01:00
|
|
|
if (LST_COLDWARM != Light.subtype) {
|
2019-08-06 09:57:50 +01:00
|
|
|
cur_col_10bits[3] = ledGamma(cur_col[3], 10);
|
|
|
|
cur_col[3] = ledGamma(cur_col[3]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-19 16:49:17 +00:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Commands
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2019-08-01 14:47:00 +01:00
|
|
|
bool LightColorEntry(char *buffer, uint32_t buffer_length)
|
2017-10-23 11:18:15 +01:00
|
|
|
{
|
|
|
|
char scolor[10];
|
|
|
|
char *p;
|
|
|
|
char *str;
|
2019-08-01 14:47:00 +01:00
|
|
|
uint32_t entry_type = 0; // Invalid
|
2019-08-17 12:17:30 +01:00
|
|
|
uint8_t value = Light.fixed_color_index;
|
2017-10-23 11:18:15 +01:00
|
|
|
|
2017-10-24 13:57:10 +01:00
|
|
|
if (buffer[0] == '#') { // Optional hexadecimal entry
|
2017-10-23 11:18:15 +01:00
|
|
|
buffer++;
|
|
|
|
buffer_length--;
|
|
|
|
}
|
2017-11-17 16:52:31 +00:00
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.subtype >= LST_RGB) {
|
2017-11-17 16:52:31 +00:00
|
|
|
char option = (1 == buffer_length) ? buffer[0] : '\0';
|
2019-08-17 12:17:30 +01:00
|
|
|
if (('+' == option) && (Light.fixed_color_index < MAX_FIXED_COLOR)) {
|
2017-11-17 16:52:31 +00:00
|
|
|
value++;
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
else if (('-' == option) && (Light.fixed_color_index > 1)) {
|
2017-11-17 16:52:31 +00:00
|
|
|
value--;
|
|
|
|
} else {
|
|
|
|
value = atoi(buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-17 12:17:30 +01:00
|
|
|
memset(&Light.entry_color, 0x00, sizeof(Light.entry_color));
|
2019-03-26 17:26:50 +00:00
|
|
|
if (strstr(buffer, ",") != nullptr) { // Decimal entry
|
2017-10-23 11:18:15 +01:00
|
|
|
int8_t i = 0;
|
2019-03-26 17:26:50 +00:00
|
|
|
for (str = strtok_r(buffer, ",", &p); str && i < 6; str = strtok_r(nullptr, ",", &p)) {
|
2019-05-02 21:50:19 +01:00
|
|
|
if (i < LST_MAX) {
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.entry_color[i++] = atoi(str);
|
2017-10-24 13:57:10 +01:00
|
|
|
}
|
2017-10-23 11:18:15 +01:00
|
|
|
}
|
2018-01-18 15:19:28 +00:00
|
|
|
entry_type = 2; // Decimal
|
2017-10-23 11:18:15 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
else if (((2 * Light.subtype) == buffer_length) || (buffer_length > 3)) { // Hexadecimal entry
|
|
|
|
for (uint32_t i = 0; i < tmin((uint)(buffer_length / 2), sizeof(Light.entry_color)); i++) {
|
2017-10-23 11:18:15 +01:00
|
|
|
strlcpy(scolor, buffer + (i *2), 3);
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.entry_color[i] = (uint8_t)strtol(scolor, &p, 16);
|
2017-10-23 11:18:15 +01:00
|
|
|
}
|
2017-10-24 13:57:10 +01:00
|
|
|
entry_type = 1; // Hexadecimal
|
2017-10-23 11:18:15 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
else if ((Light.subtype >= LST_RGB) && (value > 0) && (value <= MAX_FIXED_COLOR)) {
|
|
|
|
Light.fixed_color_index = value;
|
|
|
|
memcpy_P(&Light.entry_color, &kFixedColor[value -1], 3);
|
2017-11-15 22:07:45 +00:00
|
|
|
entry_type = 1; // Hexadecimal
|
|
|
|
}
|
|
|
|
else if ((value > 199) && (value <= 199 + MAX_FIXED_COLD_WARM)) {
|
2019-08-17 12:17:30 +01:00
|
|
|
if (LST_RGBW == Light.subtype) {
|
|
|
|
memcpy_P(&Light.entry_color[3], &kFixedWhite[value -200], 1);
|
2018-09-04 21:43:27 +01:00
|
|
|
entry_type = 1; // Hexadecimal
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
else if (LST_COLDWARM == Light.subtype) {
|
|
|
|
memcpy_P(&Light.entry_color, &kFixedColdWarm[value -200], 2);
|
2017-11-17 16:52:31 +00:00
|
|
|
entry_type = 1; // Hexadecimal
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
else if (LST_RGBWC == Light.subtype) {
|
|
|
|
memcpy_P(&Light.entry_color[3], &kFixedColdWarm[value -200], 2);
|
2017-11-17 16:52:31 +00:00
|
|
|
entry_type = 1; // Hexadecimal
|
|
|
|
}
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
2017-10-23 11:18:15 +01:00
|
|
|
if (entry_type) {
|
|
|
|
Settings.flag.decimal_text = entry_type -1;
|
|
|
|
}
|
|
|
|
return (entry_type);
|
|
|
|
}
|
|
|
|
|
2017-10-26 15:33:33 +01:00
|
|
|
/********************************************************************************************/
|
|
|
|
|
2019-08-01 14:47:00 +01:00
|
|
|
void CmndSupportColor(void)
|
2017-02-19 16:49:17 +00:00
|
|
|
{
|
2019-01-28 13:08:33 +00:00
|
|
|
bool valid_entry = false;
|
2019-08-01 14:47:00 +01:00
|
|
|
bool coldim = false;
|
2019-04-25 12:06:35 +01:00
|
|
|
|
2019-08-01 14:47:00 +01:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
|
|
valid_entry = LightColorEntry(XdrvMailbox.data, XdrvMailbox.data_len);
|
|
|
|
if (valid_entry) {
|
|
|
|
if (XdrvMailbox.index <= 2) { // Color(1), 2
|
|
|
|
uint32_t old_bri = light_state.getBri();
|
|
|
|
// change all channels to specified values
|
2019-08-17 12:17:30 +01:00
|
|
|
light_controller.changeChannels(Light.entry_color);
|
2019-08-01 14:47:00 +01:00
|
|
|
if (2 == XdrvMailbox.index) {
|
|
|
|
// If Color2, set back old brightness
|
|
|
|
light_controller.changeBri(old_bri);
|
|
|
|
}
|
|
|
|
|
|
|
|
Settings.light_scheme = 0;
|
|
|
|
coldim = true;
|
|
|
|
} else { // Color3, 4, 5 and 6
|
|
|
|
for (uint32_t i = 0; i < LST_RGB; i++) {
|
2019-08-17 12:17:30 +01:00
|
|
|
Settings.ws_color[XdrvMailbox.index -3][i] = Light.entry_color[i];
|
2017-10-23 11:18:15 +01:00
|
|
|
}
|
|
|
|
}
|
2017-09-08 11:57:08 +01:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
|
|
if (!valid_entry && (XdrvMailbox.index <= 2)) {
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndChar(LightGetColor(scolor));
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
if (XdrvMailbox.index >= 3) {
|
|
|
|
scolor[0] = '\0';
|
|
|
|
for (uint32_t i = 0; i < LST_RGB; i++) {
|
|
|
|
if (Settings.flag.decimal_text) {
|
|
|
|
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]);
|
2017-08-12 16:55:20 +01:00
|
|
|
}
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndIdxChar(scolor);
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
if (coldim) {
|
|
|
|
LightPreparePower();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CmndColor(void)
|
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((Light.subtype > LST_SINGLE) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) {
|
2019-08-01 14:47:00 +01:00
|
|
|
CmndSupportColor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CmndWhite(void)
|
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((Light.subtype == LST_RGBW) && (XdrvMailbox.index == 1)) {
|
2019-08-01 14:47:00 +01:00
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
|
|
uint32_t whiteBri = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
|
|
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
|
|
snprintf_P(scolor, sizeof(scolor), PSTR("0,0,0,%d"), whiteBri);
|
|
|
|
light_state.setBri(whiteBri); // save target Bri, will be confirmed below
|
|
|
|
XdrvMailbox.data = scolor;
|
|
|
|
XdrvMailbox.data_len = strlen(scolor);
|
|
|
|
} else {
|
|
|
|
XdrvMailbox.data_len = 0;
|
|
|
|
}
|
|
|
|
CmndSupportColor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CmndChannel(void)
|
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((XdrvMailbox.index >= Light.device) && (XdrvMailbox.index < Light.device + Light.subtype )) {
|
2019-08-01 14:47:00 +01:00
|
|
|
bool coldim = false;
|
2018-03-20 15:28:18 +00:00
|
|
|
// Set "Channel" directly - this allows Color and Direct PWM control to coexist
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[XdrvMailbox.index - Light.device] = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
|
|
|
|
if (Light.pwm_multi_channels) {
|
2019-08-06 09:57:50 +01:00
|
|
|
// if (!Settings.flag.not_power_linked) { // SetOption20
|
2019-08-17 12:17:30 +01:00
|
|
|
// Light.power = Light.power | (1 << (XdrvMailbox.index - Light.device)); // ask to turn on channel
|
2019-08-06 09:57:50 +01:00
|
|
|
// }
|
|
|
|
} else {
|
|
|
|
// if we change channels 1,2,3 then turn off CT mode (unless non-linked)
|
|
|
|
if ((XdrvMailbox.index <= 3) && (light_controller.isCTRGBLinked())) {
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.current_color[3] = Light.current_color[4] = 0;
|
2019-08-06 09:57:50 +01:00
|
|
|
}
|
2019-04-25 12:06:35 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
light_controller.changeChannels(Light.current_color);
|
2018-03-20 15:28:18 +00:00
|
|
|
coldim = true;
|
|
|
|
}
|
2019-09-25 16:56:03 +01:00
|
|
|
ResponseCmndIdxNumber(changeUIntScale(Light.current_color[XdrvMailbox.index -1],0,255,0,100));
|
2019-08-01 14:47:00 +01:00
|
|
|
if (coldim) {
|
|
|
|
LightPreparePower();
|
|
|
|
}
|
2018-03-20 15:28:18 +00:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndHsbColor(void)
|
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.subtype >= LST_RGB) {
|
2018-08-29 15:05:27 +01:00
|
|
|
bool validHSB = (XdrvMailbox.data_len > 0);
|
|
|
|
if (validHSB) {
|
|
|
|
uint16_t HSB[3];
|
2019-03-26 17:26:50 +00:00
|
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) { // Command with 3 comma separated parameters, Hue (0<H<360), Saturation (0<S<100) AND Brightness (0<B<100)
|
2019-06-30 15:44:36 +01:00
|
|
|
for (uint32_t i = 0; i < 3; i++) {
|
2018-08-29 15:05:27 +01:00
|
|
|
char *substr;
|
2018-03-20 15:28:18 +00:00
|
|
|
|
2018-08-29 15:05:27 +01:00
|
|
|
if (0 == i) {
|
|
|
|
substr = strtok(XdrvMailbox.data, ",");
|
|
|
|
} else {
|
2019-03-26 17:26:50 +00:00
|
|
|
substr = strtok(nullptr, ",");
|
2018-08-29 15:05:27 +01:00
|
|
|
}
|
2019-03-26 17:26:50 +00:00
|
|
|
if (substr != nullptr) {
|
2018-08-29 15:05:27 +01:00
|
|
|
HSB[i] = atoi(substr);
|
2019-04-25 12:06:35 +01:00
|
|
|
if (0 < i) {
|
|
|
|
HSB[i] = changeUIntScale(HSB[i], 0, 100, 0, 255); // change sat and bri to 0..255
|
|
|
|
}
|
2018-08-29 15:05:27 +01:00
|
|
|
} else {
|
|
|
|
validHSB = false;
|
|
|
|
}
|
2018-08-28 02:46:04 +01:00
|
|
|
}
|
2018-08-29 15:05:27 +01:00
|
|
|
} else { // Command with only 1 parameter, Hue (0<H<360), Saturation (0<S<100) OR Brightness (0<B<100)
|
2019-04-25 12:06:35 +01:00
|
|
|
uint16_t c_hue;
|
2019-05-04 22:04:53 +01:00
|
|
|
uint8_t c_sat;
|
|
|
|
light_state.getHSB(&c_hue, &c_sat, nullptr);
|
2019-04-25 12:06:35 +01:00
|
|
|
HSB[0] = c_hue;
|
|
|
|
HSB[1] = c_sat;
|
2019-05-04 22:04:53 +01:00
|
|
|
HSB[2] = light_state.getBri();
|
2019-04-25 12:06:35 +01:00
|
|
|
|
|
|
|
if (1 == XdrvMailbox.index) {
|
|
|
|
HSB[0] = XdrvMailbox.payload;
|
|
|
|
} else if ((XdrvMailbox.index > 1) && (XdrvMailbox.index < 4)) {
|
|
|
|
HSB[XdrvMailbox.index-1] = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
|
2018-08-28 02:46:04 +01:00
|
|
|
} else {
|
|
|
|
validHSB = false;
|
|
|
|
}
|
2018-03-20 15:28:18 +00:00
|
|
|
}
|
2018-08-29 15:05:27 +01:00
|
|
|
if (validHSB) {
|
2019-05-04 22:04:53 +01:00
|
|
|
light_controller.changeHSB(HSB[0], HSB[1], HSB[2]);
|
2019-04-25 12:06:35 +01:00
|
|
|
LightPreparePower();
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
|
2018-03-20 15:28:18 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LightState(0);
|
|
|
|
}
|
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
2017-09-16 16:34:03 +01:00
|
|
|
#ifdef USE_WS2812 // ***********************************************************************
|
2019-08-01 14:47:00 +01:00
|
|
|
void CmndLed(void)
|
|
|
|
{
|
|
|
|
if ((LT_WS2812 == light_type) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= Settings.light_pixels)) {
|
2018-01-05 11:26:19 +00:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
2018-03-31 18:56:16 +01:00
|
|
|
char *p;
|
|
|
|
uint16_t idx = XdrvMailbox.index;
|
2018-06-16 14:23:11 +01:00
|
|
|
Ws2812ForceSuspend();
|
2019-03-26 17:26:50 +00:00
|
|
|
for (char *color = strtok_r(XdrvMailbox.data, " ", &p); color; color = strtok_r(nullptr, " ", &p)) {
|
2018-03-31 18:56:16 +01:00
|
|
|
if (LightColorEntry(color, strlen(color))) {
|
2019-08-17 12:17:30 +01:00
|
|
|
Ws2812SetColor(idx, Light.entry_color[0], Light.entry_color[1], Light.entry_color[2], Light.entry_color[3]);
|
2018-03-31 18:56:16 +01:00
|
|
|
idx++;
|
2019-08-01 14:47:00 +01:00
|
|
|
if (idx > Settings.light_pixels) { break; }
|
2018-03-31 18:56:16 +01:00
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2018-06-16 14:23:11 +01:00
|
|
|
|
|
|
|
Ws2812ForceUpdate();
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
char scolor[LIGHT_COLOR_SIZE];
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndIdxChar(Ws2812GetColor(XdrvMailbox.index, scolor));
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndPixels(void)
|
|
|
|
{
|
|
|
|
if (LT_WS2812 == light_type) {
|
2018-01-05 11:26:19 +00:00
|
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= WS2812_MAX_LEDS)) {
|
|
|
|
Settings.light_pixels = XdrvMailbox.payload;
|
2018-03-10 16:58:54 +00:00
|
|
|
Settings.light_rotation = 0;
|
2017-10-18 17:22:34 +01:00
|
|
|
Ws2812Clear();
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.update = true;
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndNumber(Settings.light_pixels);
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndRotation(void)
|
|
|
|
{
|
|
|
|
if (LT_WS2812 == light_type) {
|
2018-03-10 16:58:54 +00:00
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < Settings.light_pixels)) {
|
|
|
|
Settings.light_rotation = XdrvMailbox.payload;
|
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndNumber(Settings.light_rotation);
|
2018-03-10 16:58:54 +00:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndWidth(void)
|
|
|
|
{
|
|
|
|
if ((LT_WS2812 == light_type) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
|
2018-01-05 11:26:19 +00:00
|
|
|
if (1 == XdrvMailbox.index) {
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 4)) {
|
|
|
|
Settings.light_width = XdrvMailbox.payload;
|
2017-10-23 11:18:15 +01:00
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndNumber(Settings.light_width);
|
2017-10-23 11:18:15 +01:00
|
|
|
} else {
|
2019-07-31 15:59:12 +01:00
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32)) {
|
2018-01-05 11:26:19 +00:00
|
|
|
Settings.ws_width[XdrvMailbox.index -2] = XdrvMailbox.payload;
|
2017-10-23 11:18:15 +01:00
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndIdxNumber(Settings.ws_width[XdrvMailbox.index -2]);
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
2017-10-25 13:27:30 +01:00
|
|
|
#endif // USE_WS2812 ************************************************************************
|
2019-08-01 14:47:00 +01:00
|
|
|
|
|
|
|
void CmndScheme(void)
|
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
if (Light.subtype >= LST_RGB) {
|
2019-08-01 14:47:00 +01:00
|
|
|
uint32_t max_scheme = (LT_WS2812 == light_type) ? LS_MAX + WS2812_SCHEMES : LS_MAX -1;
|
|
|
|
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
|
|
|
|
}
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
2018-01-05 11:26:19 +00:00
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= max_scheme)) {
|
|
|
|
Settings.light_scheme = XdrvMailbox.payload;
|
2017-10-26 15:33:33 +01:00
|
|
|
if (LS_WAKEUP == Settings.light_scheme) {
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.wakeup_active = 3;
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2017-10-25 13:27:30 +01:00
|
|
|
LightPowerOn();
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.strip_timer_counter = 0;
|
2018-12-12 19:32:10 +00:00
|
|
|
// Publish state message for Hass
|
2019-03-20 13:05:43 +00:00
|
|
|
if (Settings.flag3.hass_tele_on_power) { MqttPublishTeleState(); }
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndNumber(Settings.light_scheme);
|
2017-09-16 16:34:03 +01:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndWakeup(void)
|
|
|
|
{
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
|
|
Settings.light_dimmer = XdrvMailbox.payload;
|
2017-09-18 17:06:46 +01:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.wakeup_active = 3;
|
2019-08-01 14:47:00 +01:00
|
|
|
Settings.light_scheme = LS_WAKEUP;
|
|
|
|
LightPowerOn();
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndChar(D_JSON_STARTED);
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndColorTemperature(void)
|
|
|
|
{
|
2019-08-17 12:17:30 +01:00
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) { // ColorTemp
|
2019-08-01 14:47:00 +01:00
|
|
|
uint32_t ct = light_state.getCT();
|
|
|
|
if (1 == XdrvMailbox.data_len) {
|
|
|
|
if ('+' == XdrvMailbox.data[0]) {
|
2019-04-28 10:33:38 +01:00
|
|
|
XdrvMailbox.payload = (ct > (500-34)) ? 500 : ct + 34;
|
2017-11-17 16:52:31 +00:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
else if ('-' == XdrvMailbox.data[0]) {
|
2019-04-28 10:33:38 +01:00
|
|
|
XdrvMailbox.payload = (ct < (153+34)) ? 153 : ct - 34;
|
2017-11-17 16:52:31 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-28 10:33:38 +01:00
|
|
|
if ((XdrvMailbox.payload >= 153) && (XdrvMailbox.payload <= 500)) { // https://developers.meethue.com/documentation/core-concepts
|
2019-05-04 22:04:53 +01:00
|
|
|
light_controller.changeCTB(XdrvMailbox.payload, light_state.getBri());
|
2019-08-01 14:47:00 +01:00
|
|
|
LightPreparePower();
|
2017-08-16 16:05:36 +01:00
|
|
|
} else {
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndNumber(ct);
|
2017-08-16 16:05:36 +01:00
|
|
|
}
|
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndDimmer(void)
|
|
|
|
{
|
|
|
|
uint32_t dimmer = light_state.getDimmer();
|
|
|
|
if (1 == XdrvMailbox.data_len) {
|
|
|
|
if ('+' == XdrvMailbox.data[0]) {
|
2019-04-25 12:06:35 +01:00
|
|
|
XdrvMailbox.payload = (dimmer > 89) ? 100 : dimmer + 10;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
else if ('-' == XdrvMailbox.data[0]) {
|
2019-04-25 12:06:35 +01:00
|
|
|
XdrvMailbox.payload = (dimmer < 11) ? 1 : dimmer - 10;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
|
|
light_controller.changeDimmer(XdrvMailbox.payload);
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.update = true;
|
2019-08-01 14:47:00 +01:00
|
|
|
LightPreparePower();
|
|
|
|
} else {
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndNumber(Settings.light_dimmer);
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
2018-09-29 15:55:53 +01:00
|
|
|
|
2019-08-01 14:47:00 +01:00
|
|
|
void CmndLedTable(void)
|
|
|
|
{
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
|
2018-01-05 11:26:19 +00:00
|
|
|
switch (XdrvMailbox.payload) {
|
2017-06-06 22:23:23 +01:00
|
|
|
case 0: // Off
|
|
|
|
case 1: // On
|
2019-08-01 14:47:00 +01:00
|
|
|
Settings.light_correction = XdrvMailbox.payload;
|
2017-06-06 22:23:23 +01:00
|
|
|
break;
|
|
|
|
case 2: // Toggle
|
2019-08-01 14:47:00 +01:00
|
|
|
Settings.light_correction ^= 1;
|
2017-06-06 22:23:23 +01:00
|
|
|
break;
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.update = true;
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndStateText(Settings.light_correction);
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndRgbwwTable(void)
|
|
|
|
{
|
|
|
|
if ((XdrvMailbox.data_len > 0)) {
|
|
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) { // Command with up to 5 comma separated parameters
|
|
|
|
for (uint32_t i = 0; i < LST_RGBWC; i++) {
|
|
|
|
char *substr;
|
|
|
|
|
|
|
|
if (0 == i) {
|
|
|
|
substr = strtok(XdrvMailbox.data, ",");
|
|
|
|
} else {
|
|
|
|
substr = strtok(nullptr, ",");
|
|
|
|
}
|
|
|
|
if (substr != nullptr) {
|
|
|
|
Settings.rgbwwTable[i] = atoi(substr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.update = true;
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
|
|
scolor[0] = '\0';
|
|
|
|
for (uint32_t i = 0; i < LST_RGBWC; i++) {
|
|
|
|
snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.rgbwwTable[i]);
|
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndIdxChar(scolor);
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndFade(void)
|
|
|
|
{
|
|
|
|
switch (XdrvMailbox.payload) {
|
|
|
|
case 0: // Off
|
|
|
|
case 1: // On
|
|
|
|
Settings.light_fade = XdrvMailbox.payload;
|
|
|
|
break;
|
|
|
|
case 2: // Toggle
|
|
|
|
Settings.light_fade ^= 1;
|
|
|
|
break;
|
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndStateText(Settings.light_fade);
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndSpeed(void)
|
|
|
|
{ // 1 - fast, 20 - very slow
|
|
|
|
if (1 == XdrvMailbox.data_len) {
|
|
|
|
if (('+' == XdrvMailbox.data[0]) && (Settings.light_speed > 1)) {
|
2018-01-05 11:26:19 +00:00
|
|
|
XdrvMailbox.payload = Settings.light_speed -1;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
else if (('-' == XdrvMailbox.data[0]) && (Settings.light_speed < STATES)) {
|
2018-01-05 11:26:19 +00:00
|
|
|
XdrvMailbox.payload = Settings.light_speed +1;
|
2017-11-15 22:07:45 +00:00
|
|
|
}
|
2017-09-08 16:14:39 +01:00
|
|
|
}
|
2019-08-01 14:47:00 +01:00
|
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= STATES)) {
|
|
|
|
Settings.light_speed = XdrvMailbox.payload;
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndNumber(Settings.light_speed);
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
2018-05-09 09:49:43 +01:00
|
|
|
|
2019-08-01 14:47:00 +01:00
|
|
|
void CmndWakeupDuration(void)
|
|
|
|
{
|
|
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3001)) {
|
|
|
|
Settings.light_wakeup = XdrvMailbox.payload;
|
2019-08-17 12:17:30 +01:00
|
|
|
Light.wakeup_active = 0;
|
2017-03-12 17:36:33 +00:00
|
|
|
}
|
2019-08-03 12:01:34 +01:00
|
|
|
ResponseCmndNumber(Settings.light_wakeup);
|
2019-08-01 14:47:00 +01:00
|
|
|
}
|
2018-05-28 10:35:23 +01:00
|
|
|
|
2019-08-01 14:47:00 +01:00
|
|
|
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';
|
2017-02-19 16:49:17 +00:00
|
|
|
}
|
|
|
|
|
2018-01-05 11:26:19 +00:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Interface
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2019-01-28 13:08:33 +00:00
|
|
|
bool Xdrv04(uint8_t function)
|
2018-01-05 11:26:19 +00:00
|
|
|
{
|
2019-01-28 13:08:33 +00:00
|
|
|
bool result = false;
|
2018-01-05 11:26:19 +00:00
|
|
|
|
|
|
|
if (light_type) {
|
|
|
|
switch (function) {
|
2018-06-04 17:10:38 +01:00
|
|
|
case FUNC_PRE_INIT:
|
2018-01-05 11:26:19 +00:00
|
|
|
LightInit();
|
|
|
|
break;
|
|
|
|
case FUNC_EVERY_50_MSECOND:
|
|
|
|
LightAnimate();
|
|
|
|
#ifdef USE_ARILUX_RF
|
2019-08-01 14:47:00 +01:00
|
|
|
if (pin[GPIO_ARIRFRCV] < 99) { AriluxRfHandler(); }
|
2018-01-05 11:26:19 +00:00
|
|
|
#endif // USE_ARILUX_RF
|
|
|
|
break;
|
2018-02-07 16:50:02 +00:00
|
|
|
#ifdef USE_ARILUX_RF
|
|
|
|
case FUNC_EVERY_SECOND:
|
2019-08-01 14:47:00 +01:00
|
|
|
if (10 == uptime) { AriluxRfInit(); } // Needs rest before enabling RF interrupts
|
2018-02-07 16:50:02 +00:00
|
|
|
break;
|
|
|
|
#endif // USE_ARILUX_RF
|
2018-01-05 11:26:19 +00:00
|
|
|
case FUNC_SET_POWER:
|
|
|
|
LightSetPower();
|
|
|
|
break;
|
2019-08-01 14:47:00 +01:00
|
|
|
case FUNC_COMMAND:
|
|
|
|
result = DecodeCommand(kLightCommands, LightCommand);
|
|
|
|
break;
|
2018-01-05 11:26:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2019-06-16 15:43:23 +01:00
|
|
|
|
|
|
|
#endif // USE_LIGHT
|