|
|
|
@ -79,6 +79,7 @@
|
|
|
|
|
* .b For white bulbs with Cold/Warm colortone, use changeCW() or changeCT()
|
|
|
|
|
* to change color-tone. Set overall brightness separately.
|
|
|
|
|
* Color-tone temperature can range from 153 (Cold) to 500 (Warm).
|
|
|
|
|
* SetOption82 can expand the rendering from 200-380 due to Alexa reduced range.
|
|
|
|
|
* CW channels are stored at full brightness to avoid rounding errors.
|
|
|
|
|
* .c Alternatively, you can set all 5 channels at once with changeChannels(),
|
|
|
|
|
* in this case it will also set the corresponding brightness.
|
|
|
|
@ -160,6 +161,13 @@ struct LCwColor {
|
|
|
|
|
const uint8_t MAX_FIXED_COLD_WARM = 4;
|
|
|
|
|
const LCwColor kFixedColdWarm[MAX_FIXED_COLD_WARM] PROGMEM = { 0,0, 255,0, 0,255, 128,128 };
|
|
|
|
|
|
|
|
|
|
// CT min and max
|
|
|
|
|
const uint16_t CT_MIN = 153; // 6500K
|
|
|
|
|
const uint16_t CT_MAX = 500; // 2000K
|
|
|
|
|
// Ranges used for Alexa
|
|
|
|
|
const uint16_t CT_MIN_ALEXA = 200; // also 5000K
|
|
|
|
|
const uint16_t CT_MAX_ALEXA = 380; // also 2600K
|
|
|
|
|
|
|
|
|
|
// New version of Gamma correction compute
|
|
|
|
|
// Instead of a table, we do a multi-linear approximation, which is close enough
|
|
|
|
|
// At low levels, the slope is a bit higher than actual gamma, to make changes smoother
|
|
|
|
@ -344,12 +352,19 @@ class LightStateClass {
|
|
|
|
|
uint8_t _b = 255; // 0..255
|
|
|
|
|
|
|
|
|
|
uint8_t _subtype = 0; // local copy of Light.subtype, if we need multiple lights
|
|
|
|
|
uint16_t _ct = 153; // 153..500, default to 153 (cold white)
|
|
|
|
|
uint16_t _ct = CT_MIN; // 153..500, default to 153 (cold white)
|
|
|
|
|
uint8_t _wc = 255; // white cold channel
|
|
|
|
|
uint8_t _ww = 0; // white warm channel
|
|
|
|
|
uint8_t _briCT = 255;
|
|
|
|
|
|
|
|
|
|
uint8_t _color_mode = LCM_RGB; // RGB by default
|
|
|
|
|
// the CT range below represents the rendered range,
|
|
|
|
|
// This is due to Alexa whose CT range is 199..383
|
|
|
|
|
// Hence setting Min=200 and Max=380 makes Alexa use the full range
|
|
|
|
|
// Please note that you can still set CT to 153..500, but any
|
|
|
|
|
// value below _ct_min_range or above _ct_max_range not change the CT
|
|
|
|
|
uint16_t _ct_min_range = CT_MIN; // the minimum CT rendered range
|
|
|
|
|
uint16_t _ct_max_range = CT_MAX; // the maximum CT rendered range
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
LightStateClass() {
|
|
|
|
@ -367,7 +382,7 @@ class LightStateClass {
|
|
|
|
|
// 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
|
|
|
|
|
// LST_RGBCW: LCM_RGB, LCM_CT or LCM_BOTH
|
|
|
|
|
uint8_t setColorMode(uint8_t cm) {
|
|
|
|
|
uint8_t prev_cm = _color_mode;
|
|
|
|
|
if (cm < LCM_RGB) { cm = LCM_RGB; }
|
|
|
|
@ -387,7 +402,7 @@ class LightStateClass {
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case LST_RGBW:
|
|
|
|
|
case LST_RGBWC:
|
|
|
|
|
case LST_RGBCW:
|
|
|
|
|
_color_mode = cm;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
@ -498,8 +513,23 @@ class LightStateClass {
|
|
|
|
|
return BriToDimmer(bri);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline uint16_t getCT() {
|
|
|
|
|
return _ct; // 153..500
|
|
|
|
|
inline uint16_t getCT() const {
|
|
|
|
|
return _ct; // 153..500, or CT_MIN..CT_MAX
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get the CT value within the range into a 10 bits 0..1023 value
|
|
|
|
|
uint16_t getCT10bits() const {
|
|
|
|
|
return changeUIntScale(_ct, _ct_min_range, _ct_max_range, 0, 1023);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void setCTRange(uint16_t ct_min_range, uint16_t ct_max_range) {
|
|
|
|
|
_ct_min_range = ct_min_range;
|
|
|
|
|
_ct_max_range = ct_max_range;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void getCTRange(uint16_t *ct_min_range, uint16_t *ct_max_range) const {
|
|
|
|
|
if (ct_min_range) { *ct_min_range = _ct_min_range; }
|
|
|
|
|
if (ct_max_range) { *ct_max_range = _ct_max_range; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get current color in XY format
|
|
|
|
@ -546,8 +576,8 @@ class LightStateClass {
|
|
|
|
|
// disable ct mode
|
|
|
|
|
setColorMode(LCM_RGB); // try deactivating CT mode, setColorMode() will check which is legal
|
|
|
|
|
} else {
|
|
|
|
|
ct = (ct < 153 ? 153 : (ct > 500 ? 500 : ct));
|
|
|
|
|
_ww = changeUIntScale(ct, 153, 500, 0, 255);
|
|
|
|
|
ct = (ct < CT_MIN ? CT_MIN : (ct > CT_MAX ? CT_MAX : ct));
|
|
|
|
|
_ww = changeUIntScale(ct, _ct_min_range, _ct_max_range, 0, 255);
|
|
|
|
|
_wc = 255 - _ww;
|
|
|
|
|
_ct = ct;
|
|
|
|
|
addCTMode();
|
|
|
|
@ -587,7 +617,7 @@ class LightStateClass {
|
|
|
|
|
_ww = changeUIntScale(w, 0, max, 0, 255);
|
|
|
|
|
_wc = changeUIntScale(c, 0, max, 0, 255);
|
|
|
|
|
}
|
|
|
|
|
_ct = changeUIntScale(w, 0, sum, 153, 500);
|
|
|
|
|
_ct = changeUIntScale(w, 0, sum, _ct_min_range, _ct_max_range);
|
|
|
|
|
addCTMode(); // activate CT mode if needed
|
|
|
|
|
if (_color_mode & LCM_CT) { _briCT = free_range ? max : (sum > 255 ? 255 : sum); }
|
|
|
|
|
}
|
|
|
|
@ -860,6 +890,15 @@ public:
|
|
|
|
|
return prev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setAlexaCTRange(bool alexa_ct_range) {
|
|
|
|
|
// depending on SetOption82, full or limited CT range
|
|
|
|
|
if (alexa_ct_range) {
|
|
|
|
|
_state->setCTRange(CT_MIN_ALEXA, CT_MAX_ALEXA); // 200..380
|
|
|
|
|
} else {
|
|
|
|
|
_state->setCTRange(CT_MIN, CT_MAX); // 153..500
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool isCTRGBLinked() {
|
|
|
|
|
return _ct_rgb_linked;
|
|
|
|
|
}
|
|
|
|
@ -926,8 +965,8 @@ public:
|
|
|
|
|
void changeCTB(uint16_t new_ct, uint8_t briCT) {
|
|
|
|
|
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
|
|
|
|
|
*
|
|
|
|
|
* ct = 153 = 2000K = Warm = CCWW = 00FF
|
|
|
|
|
* ct = 500 = 6500K = Cold = CCWW = FF00
|
|
|
|
|
* ct = 153 = 6500K = Cold = CCWW = FF00
|
|
|
|
|
* ct = 500 = 2000K = Warm = CCWW = 00FF
|
|
|
|
|
*/
|
|
|
|
|
// don't set CT if not supported
|
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBW > Light.subtype)) {
|
|
|
|
@ -1012,8 +1051,8 @@ public:
|
|
|
|
|
current_color[1] = w;
|
|
|
|
|
break;
|
|
|
|
|
case LST_RGBW:
|
|
|
|
|
case LST_RGBWC:
|
|
|
|
|
if (LST_RGBWC == Light.subtype) {
|
|
|
|
|
case LST_RGBCW:
|
|
|
|
|
if (LST_RGBCW == Light.subtype) {
|
|
|
|
|
current_color[3] = c;
|
|
|
|
|
current_color[4] = w;
|
|
|
|
|
} else {
|
|
|
|
@ -1235,6 +1274,7 @@ void LightInit(void)
|
|
|
|
|
|
|
|
|
|
light_controller.setSubType(Light.subtype);
|
|
|
|
|
light_controller.loadSettings();
|
|
|
|
|
light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
|
|
|
|
|
|
|
|
|
|
if (LST_SINGLE == Light.subtype) {
|
|
|
|
|
Settings.light_color[0] = 255; // One channel only supports Dimmer but needs max color
|
|
|
|
@ -1353,11 +1393,11 @@ void LightSetColorTemp(uint16_t ct)
|
|
|
|
|
{
|
|
|
|
|
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
|
|
|
|
|
*
|
|
|
|
|
* ct = 153 = 2000K = Warm = CCWW = 00FF
|
|
|
|
|
* ct = 500 = 6500K = Cold = CCWW = FF00
|
|
|
|
|
* ct = 153 = 6500K = Cold = CCWW = FF00
|
|
|
|
|
* ct = 600 = 2000K = Warm = CCWW = 00FF
|
|
|
|
|
*/
|
|
|
|
|
// don't set CT if not supported
|
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != Light.subtype)) {
|
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
light_controller.changeCTB(ct, light_state.getBriCT());
|
|
|
|
@ -1366,7 +1406,7 @@ void LightSetColorTemp(uint16_t ct)
|
|
|
|
|
uint16_t LightGetColorTemp(void)
|
|
|
|
|
{
|
|
|
|
|
// don't calculate CT for unsupported devices
|
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != Light.subtype)) {
|
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return (light_state.getColorMode() & LCM_CT) ? light_state.getCT() : 0;
|
|
|
|
@ -1451,7 +1491,7 @@ void LightState(uint8_t append)
|
|
|
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_WHITE "\":%d"), light_state.getDimmer(2));
|
|
|
|
|
}
|
|
|
|
|
// Add CT
|
|
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
|
|
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
|
|
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_COLORTEMPERATURE "\":%d"), light_state.getCT());
|
|
|
|
|
}
|
|
|
|
|
// Add status for each channel
|
|
|
|
@ -1625,6 +1665,8 @@ void LightAnimate(void)
|
|
|
|
|
uint16_t light_still_on = 0;
|
|
|
|
|
bool power_off = false;
|
|
|
|
|
|
|
|
|
|
// make sure we update CT range in case SetOption82 was changed
|
|
|
|
|
light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
|
|
|
|
|
Light.strip_timer_counter++;
|
|
|
|
|
|
|
|
|
|
// set sleep parameter: either settings,
|
|
|
|
@ -1721,7 +1763,7 @@ void LightAnimate(void)
|
|
|
|
|
calcGammaBulbs(cur_col_10);
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
// Valid only for LST_RGBW, LST_RGBCW, rgbwwTable[4] is zero, and white is zero (see doc)
|
|
|
|
|
if ((LST_RGBW <= Light.subtype) && (0 == Settings.rgbwwTable[4]) && (0 == cur_col_10[3]+cur_col_10[4])) {
|
|
|
|
|
uint32_t min_rgb_10 = min3(cur_col_10[0], cur_col_10[1], cur_col_10[2]);
|
|
|
|
|
for (uint32_t i=0; i<3; i++) {
|
|
|
|
@ -1736,10 +1778,10 @@ void LightAnimate(void)
|
|
|
|
|
if (LST_RGBW == Light.subtype) {
|
|
|
|
|
// we simply set the white channel
|
|
|
|
|
cur_col_10[3] = white_10;
|
|
|
|
|
} else { // LST_RGBWC
|
|
|
|
|
} else { // LST_RGBCW
|
|
|
|
|
// we distribute white between cold and warm according to CT value
|
|
|
|
|
uint32_t ct = light_state.getCT();
|
|
|
|
|
cur_col_10[4] = changeUIntScale(ct, 153, 500, 0, white_10);
|
|
|
|
|
uint32_t ct = light_state.getCT10bits();
|
|
|
|
|
cur_col_10[4] = changeUIntScale(ct, 0, 1023, 0, white_10);
|
|
|
|
|
cur_col_10[3] = white_10 - cur_col_10[4];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -1793,7 +1835,7 @@ bool isChannelGammaCorrected(uint32_t channel) {
|
|
|
|
|
|
|
|
|
|
if (PHILIPS == my_module_type) {
|
|
|
|
|
if ((LST_COLDWARM == Light.subtype) && (1 == channel)) { return false; } // PMW reserved for CT
|
|
|
|
|
if ((LST_RGBWC == Light.subtype) && (4 == channel)) { return false; } // PMW reserved for CT
|
|
|
|
|
if ((LST_RGBCW == Light.subtype) && (4 == channel)) { return false; } // PMW reserved for CT
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
@ -1962,7 +2004,7 @@ void calcGammaBulbs(uint16_t cur_col_10[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
|
|
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
|
|
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
|
|
|
|
// channels for white are always the last two channels
|
|
|
|
|
uint32_t cw1 = Light.subtype - 1; // address for the ColorTone PWM
|
|
|
|
|
uint32_t cw0 = Light.subtype - 2; // address for the White Brightness PWM
|
|
|
|
@ -1971,9 +2013,7 @@ void calcGammaBulbs(uint16_t cur_col_10[5]) {
|
|
|
|
|
|
|
|
|
|
if (PHILIPS == my_module_type) { // channel 1 is the color tone, mapped to cold channel (0..255)
|
|
|
|
|
// Xiaomi Philips bulbs follow a different scheme:
|
|
|
|
|
uint8_t cold, warm;
|
|
|
|
|
light_state.getCW(&cold, &warm);
|
|
|
|
|
cur_col_10[cw1] = changeUIntScale(cold, 0, cold + warm, 0, 1023); //
|
|
|
|
|
cur_col_10[cw1] = light_state.getCT10bits();
|
|
|
|
|
// channel 0=intensity, channel1=temperature
|
|
|
|
|
if (Settings.light_correction) { // gamma correction
|
|
|
|
|
cur_col_10[cw0] = ledGamma10_10(white_bri10_1023); // 10 bits gamma correction
|
|
|
|
@ -2001,7 +2041,7 @@ void calcGammaBulbs(uint16_t cur_col_10[5]) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If RGBW or Single channel, also adjust White channel
|
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != Light.subtype)) {
|
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) {
|
|
|
|
|
cur_col_10[3] = ledGamma10_10(cur_col_10[3]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -2072,7 +2112,7 @@ bool LightColorEntry(char *buffer, uint32_t buffer_length)
|
|
|
|
|
memcpy_P(&Light.entry_color, &kFixedColdWarm[value -200], 2);
|
|
|
|
|
entry_type = 1; // Hexadecimal
|
|
|
|
|
}
|
|
|
|
|
else if (LST_RGBWC == Light.subtype) {
|
|
|
|
|
else if (LST_RGBCW == Light.subtype) {
|
|
|
|
|
memcpy_P(&Light.entry_color[3], &kFixedColdWarm[value -200], 2);
|
|
|
|
|
entry_type = 1; // Hexadecimal
|
|
|
|
|
}
|
|
|
|
@ -2288,17 +2328,17 @@ void CmndWakeup(void)
|
|
|
|
|
void CmndColorTemperature(void)
|
|
|
|
|
{
|
|
|
|
|
if (Light.pwm_multi_channels) { return; }
|
|
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) { // ColorTemp
|
|
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) { // ColorTemp
|
|
|
|
|
uint32_t ct = light_state.getCT();
|
|
|
|
|
if (1 == XdrvMailbox.data_len) {
|
|
|
|
|
if ('+' == XdrvMailbox.data[0]) {
|
|
|
|
|
XdrvMailbox.payload = (ct > (500-34)) ? 500 : ct + 34;
|
|
|
|
|
XdrvMailbox.payload = (ct > (CT_MAX-34)) ? CT_MAX : ct + 34;
|
|
|
|
|
}
|
|
|
|
|
else if ('-' == XdrvMailbox.data[0]) {
|
|
|
|
|
XdrvMailbox.payload = (ct < (153+34)) ? 153 : ct - 34;
|
|
|
|
|
XdrvMailbox.payload = (ct < (CT_MIN+34)) ? CT_MIN : ct - 34;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ((XdrvMailbox.payload >= 153) && (XdrvMailbox.payload <= 500)) { // https://developers.meethue.com/documentation/core-concepts
|
|
|
|
|
if ((XdrvMailbox.payload >= CT_MIN) && (XdrvMailbox.payload <= CT_MAX)) { // https://developers.meethue.com/documentation/core-concepts
|
|
|
|
|
light_controller.changeCTB(XdrvMailbox.payload, light_state.getBri());
|
|
|
|
|
LightPreparePower(2);
|
|
|
|
|
} else {
|
|
|
|
@ -2394,7 +2434,7 @@ 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++) {
|
|
|
|
|
for (uint32_t i = 0; i < LST_RGBCW; i++) {
|
|
|
|
|
char *substr;
|
|
|
|
|
|
|
|
|
|
if (0 == i) {
|
|
|
|
@ -2411,7 +2451,7 @@ void CmndRgbwwTable(void)
|
|
|
|
|
}
|
|
|
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
|
|
|
scolor[0] = '\0';
|
|
|
|
|
for (uint32_t i = 0; i < LST_RGBWC; i++) {
|
|
|
|
|
for (uint32_t i = 0; i < LST_RGBCW; i++) {
|
|
|
|
|
snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.rgbwwTable[i]);
|
|
|
|
|
}
|
|
|
|
|
ResponseCmndIdxChar(scolor);
|
|
|
|
|