diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index b1687e5e3..a087ea907 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -11,6 +11,7 @@ - Fix Display handling of hexadecimal escape characters (#7387) - Fix Improved fade linearity with gamma correction - Fix wrong gamma correction for Module 48 lights (PWM5 for CT) +- Add SetOption82 to limit the CT range for Alexa to 200..380 ### 8.1.0.1 20191225 diff --git a/tasmota/settings.h b/tasmota/settings.h index 7dae8a586..b46c0dd48 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -101,7 +101,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu typedef union { // Restricted by MISRA-C Rule 18.4 but so useful... uint32_t data; // Allow bit manipulation using SetOption struct { // SetOption82 .. SetOption113 - uint32_t spare00 : 1; + uint32_t alexa_ct_range : 1; // bit 0 (v8.1.0.2) - SetOption82 - Reduced CT range for Alexa uint32_t spare01 : 1; uint32_t spare02 : 1; uint32_t spare03 : 1; diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 4ed6ad7ef..32a85c80a 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -259,7 +259,7 @@ enum DomoticzSensors {DZ_TEMP, DZ_TEMP_HUM, DZ_TEMP_HUM_BARO, DZ_POWER_ENERGY, D enum Ws2812ClockIndex { WS_SECOND, WS_MINUTE, WS_HOUR, WS_MARKER }; enum Ws2812Color { WS_RED, WS_GREEN, WS_BLUE }; -enum LightSubtypes { LST_NONE, LST_SINGLE, LST_COLDWARM, LST_RGB, LST_RGBW, LST_RGBWC, LST_MAX=5 }; // Do not insert new fields +enum LightSubtypes { LST_NONE, LST_SINGLE, LST_COLDWARM, LST_RGB, LST_RGBW, LST_RGBCW, LST_MAX=5 }; // Do not insert new fields enum LightTypes { LT_BASIC, LT_PWM1, LT_PWM2, LT_PWM3, LT_PWM4, LT_PWM5, LT_PWM6, LT_PWM7, LT_NU8, LT_SERIAL1, LT_SERIAL2, LT_RGB, LT_RGBW, LT_RGBWC, LT_NU14, LT_NU15 }; // Do not insert new fields diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index d5b838f0f..eb14933d4 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -1032,7 +1032,7 @@ void HandleRoot(void) if (light_type) { uint8_t light_subtype = light_type &7; if (!Settings.flag3.pwm_multi_channels) { // SetOption68 0 - Enable multi-channels PWM instead of Color PWM - if ((LST_COLDWARM == light_subtype) || (LST_RGBWC == light_subtype)) { + if ((LST_COLDWARM == light_subtype) || (LST_RGBCW == light_subtype)) { WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Cold Warm "a", // a - Unique HTML id diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index 736d46e5a..3eacfbcaa 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -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); diff --git a/tasmota/xdrv_12_home_assistant.ino b/tasmota/xdrv_12_home_assistant.ino index 03aeb3e6a..5babe4abb 100644 --- a/tasmota/xdrv_12_home_assistant.ino +++ b/tasmota/xdrv_12_home_assistant.ino @@ -292,7 +292,7 @@ void HAssAnnounceRelayLight(void) Shorten(&white_temp_command_topic, prefix); TryResponseAppend_P(HASS_DISCOVER_LIGHT_WHITE, white_temp_command_topic, state_topic); } - if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) { + if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) { char *color_temp_command_topic = stemp1; GetTopic_P(color_temp_command_topic, CMND, mqtt_topic, D_CMND_COLORTEMPERATURE); diff --git a/tasmota/xdrv_20_hue.ino b/tasmota/xdrv_20_hue.ino index 1df6543ac..37de3a902 100644 --- a/tasmota/xdrv_20_hue.ino +++ b/tasmota/xdrv_20_hue.ino @@ -231,7 +231,7 @@ void HueConfig(String *path) } // device is forced to CT mode instead of HSB -// only makes sense for LST_COLDWARM, LST_RGBW and LST_RGBWC +// only makes sense for LST_COLDWARM, LST_RGBW and LST_RGBCW bool g_gotct = false; // store previously set values from the Alexa app