Merge pull request #7434 from s-hadinger/reduce_ct_range

Add SetOption82 to limit the CT range for Alexa to 200..380
This commit is contained in:
Theo Arends 2020-01-04 12:34:09 +01:00 committed by GitHub
commit bacc9e4899
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 80 additions and 39 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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