From c0e92c5c4adddb0dd2f7f1e2df55f567b55dba9b Mon Sep 17 00:00:00 2001 From: Niklas Wagner Date: Wed, 4 Apr 2018 18:52:47 +0200 Subject: [PATCH 1/4] Implement RGBW correctly --- code/espurna/light.ino | 117 ++++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 42 deletions(-) diff --git a/code/espurna/light.ino b/code/espurna/light.ino index a64e4652..7c8e82d4 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.ino @@ -29,6 +29,8 @@ typedef struct { bool reverse; bool state; unsigned char value; // target or nominal value + unsigned char original; // original value before RGBW calculation + bool useOriginal; // determine if it should use the original or value variable unsigned char shadow; // represented value double current; // transition value } channel_t; @@ -73,6 +75,51 @@ const unsigned char _light_gamma_table[] = { // UTILS // ----------------------------------------------------------------------------- +// Returns the "correct" value for each channel +unsigned char _getChannel(char i) { + if (_light_channel[i].useOriginal) { + // Reset value when user disable the white channel without rebooting: + if (!_light_use_white) { + _light_channel[i].value = _light_channel[i].original; + _light_channel[i].useOriginal = false; + _light_channel[i].original = 0; + return _light_channel[i].value; + } + + return _light_channel[i].original; + } + + return _light_channel[i].value; +} + +void _setWhite() { + if (!_light_use_white) return; + + unsigned int white, max_in, max_out; + double factor = 0; + + white = std::min(_light_channel[0].value, std::min(_light_channel[1].value, _light_channel[2].value)); + max_in = std::max(_light_channel[0].value, std::max(_light_channel[1].value, _light_channel[2].value)); + + for (unsigned int i=0; i < 3; i++) { + _light_channel[i].useOriginal = true; + _light_channel[i].original = _light_channel[i].value; + _light_channel[i].value -= white; + } + _light_channel[3].value = white; + + max_out = std::max(std::max(_light_channel[0].value, _light_channel[1].value), std::max(_light_channel[2].value, _light_channel[3].value)); + + if (max_out > 0) { + factor = (double) (max_in / max_out); + } + + for (unsigned int i=0; i < 4; i++) { + _light_channel[i].value = round(_light_channel[i].value * factor);; + } +} + + void _fromLong(unsigned long value, bool brightness) { if (brightness) { @@ -93,38 +140,31 @@ void _fromRGB(const char * rgb) { char * p = (char *) rgb; if (strlen(p) == 0) return; - // if color begins with a # then assume HEX RGB - if (p[0] == '#') { - + switch (p[0]) { + case '#': // HEX Value if (_light_has_color) { - ++p; unsigned long value = strtoul(p, NULL, 16); - // RGBA values are interpreted like RGB + brightness _fromLong(value, strlen(p) > 7); - + _setWhite(); } - - // it's a temperature in mireds - } else if (p[0] == 'M') { - + break; + case 'M': // Mired Value if (_light_has_color) { unsigned long mireds = atol(p + 1); _fromMireds(mireds); + //_setWhite(); } - - // it's a temperature in kelvin - } else if (p[0] == 'K') { - + break; + case 'K': // Kelvin Value if (_light_has_color) { unsigned long kelvin = atol(p + 1); _fromKelvin(kelvin); + //_setWhite(); } - - // otherwise assume decimal values separated by commas - } else { - + break; + default: // assume decimal values separated by commas char * tok; unsigned char count = 0; unsigned char channels = _light_channel.size(); @@ -141,9 +181,9 @@ void _fromRGB(const char * rgb) { _light_channel[1].value = _light_channel[0].value; _light_channel[2].value = _light_channel[0].value; } - + _setWhite(); + break; } - } void _toRGB(char * rgb, size_t len, bool applyBrightness) { @@ -154,11 +194,11 @@ void _toRGB(char * rgb, size_t len, bool applyBrightness) { unsigned long value = 0; - value += _light_channel[0].value * b; + value += _getChannel(0) * b; value <<= 8; - value += _light_channel[1].value * b; + value += _getChannel(1) * b; value <<= 8; - value += _light_channel[2].value * b; + value += _getChannel(2) * b; snprintf_P(rgb, len, PSTR("#%06X"), value); @@ -238,8 +278,9 @@ void _fromHSV(const char * hsv) { break; } - _light_brightness = LIGHT_MAX_BRIGHTNESS; + _setWhite(); + _light_brightness = LIGHT_MAX_BRIGHTNESS; } void _toHSV(char * hsv, size_t len) { @@ -249,9 +290,9 @@ void _toHSV(char * hsv, size_t len) { double min, max; double h, s, v; - double r = (double) _light_channel[0].value / 255.0; - double g = (double) _light_channel[1].value / 255.0; - double b = (double) _light_channel[2].value / 255.0; + double r = (double) _getChannel(0) / 255.0; + double g = (double) _getChannel(1) / 255.0; + double b = (double) _getChannel(2) / 255.0; min = (r < g) ? r : g; min = (min < b) ? min : b; @@ -299,9 +340,9 @@ void _toLong(char * color, size_t len, bool applyBrightness) { float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1; snprintf_P(color, len, PSTR("%d,%d,%d"), - (int) (_light_channel[0].value * b), - (int) (_light_channel[1].value * b), - (int) (_light_channel[2].value * b) + (int) (_getChannel(0) * b), + (int) (_getChannel(1) * b), + (int) (_getChannel(2) * b) ); } @@ -379,12 +420,15 @@ void _shadow() { _light_steps_left--; if (_light_steps_left == 0) _light_transition_ticker.detach(); + // Update 4 Channels if RGBW else 3 + char channels = _light_use_white ? 4 : 3; + // Transitions unsigned char target; for (unsigned int i=0; i < _light_channel.size(); i++) { if (_light_state && _light_channel[i].state) { target = _light_channel[i].value; - if ((_light_brightness < LIGHT_MAX_BRIGHTNESS) && _light_has_color && (i < 3)) { + if ((_light_brightness < LIGHT_MAX_BRIGHTNESS) && _light_has_color && (i < channels)) { target *= ((float) _light_brightness / LIGHT_MAX_BRIGHTNESS); } } else { @@ -398,17 +442,6 @@ void _shadow() { } _light_channel[i].shadow = _light_channel[i].current; } - - // Use white channel for same RGB - if (_light_use_white && _light_has_color) { - if (_light_channel[0].shadow == _light_channel[1].shadow && _light_channel[1].shadow == _light_channel[2].shadow ) { - _light_channel[3].shadow = _light_channel[0].shadow * ((float) _light_brightness / LIGHT_MAX_BRIGHTNESS); - _light_channel[2].shadow = 0; - _light_channel[1].shadow = 0; - _light_channel[0].shadow = 0; - } - } - } void _lightProviderUpdate() { From 9e1352f5ef841200374beae4d53825f0f331814b Mon Sep 17 00:00:00 2001 From: Niklas Wagner Date: Thu, 5 Apr 2018 19:00:56 +0200 Subject: [PATCH 2/4] Fix color Restore functions --- code/espurna/light.ino | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/code/espurna/light.ino b/code/espurna/light.ino index 7c8e82d4..4f16ec44 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.ino @@ -474,8 +474,16 @@ void _lightProviderUpdate() { // ----------------------------------------------------------------------------- void _lightColorSave() { + + //TODO: Remove this once this code was in a stable release + // This force set ch3 to 0 which could be not zero when you update from a old version + if (_light_use_white) { + setSetting("ch", 3, 0); + } + for (unsigned int i=0; i < _light_channel.size(); i++) { - setSetting("ch", i, _light_channel[i].value); + if (_light_use_white && (i == 3)) continue; //Don't save white channel. + setSetting("ch", i, _getChannel(i)); } setSetting("brightness", _light_brightness); saveSettings(); @@ -485,6 +493,8 @@ void _lightColorRestore() { for (unsigned int i=0; i < _light_channel.size(); i++) { _light_channel[i].value = getSetting("ch", i, i==0 ? 255 : 0).toInt(); } + _setWhite(); + _light_brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt(); lightUpdate(false, false); } @@ -1079,8 +1089,8 @@ void lightSetup() { DEBUG_MSG_P(PSTR("[LIGHT] LIGHT_PROVIDER = %d\n"), LIGHT_PROVIDER); DEBUG_MSG_P(PSTR("[LIGHT] Number of channels: %d\n"), _light_channel.size()); - _lightColorRestore(); _lightConfigure(); + _lightColorRestore(); #if WEB_SUPPORT _lightAPISetup(); From 04b23cd1466f124872bc6d5a9e403ce75b3357d8 Mon Sep 17 00:00:00 2001 From: Niklas Wagner Date: Fri, 6 Apr 2018 19:09:55 +0200 Subject: [PATCH 3/4] WIP Kelvin --- code/espurna/light.ino | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/code/espurna/light.ino b/code/espurna/light.ino index 4f16ec44..03f9f193 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.ino @@ -119,6 +119,10 @@ void _setWhite() { } } +//Return 4 (RGBW) or 3 (RGB) +unsigned char _getRGBChannelWidth() { + return _light_use_white ? 4 : 3; +} void _fromLong(unsigned long value, bool brightness) { @@ -301,18 +305,12 @@ void _toHSV(char * hsv, size_t len) { v = 100.0 * max; if (v == 0) { - h = s = 0; - } else { - s = 100.0 * (max - min) / max; if (s == 0) { - h = 0; - } else { - if (max == r) { if (g >= b) { h = 0.0 + 60.0 * (g - b) / (max - min); @@ -325,12 +323,10 @@ void _toHSV(char * hsv, size_t len) { h = 240.0 + 60.0 * (r - g) / (max - min); } } - } // String snprintf_P(hsv, len, PSTR("%d,%d,%d"), round(h), round(s), round(v)); - } void _toLong(char * color, size_t len, bool applyBrightness) { @@ -410,7 +406,7 @@ unsigned int _toPWM(unsigned long value, bool gamma, bool reverse) { // Returns a PWM value for the given channel ID unsigned int _toPWM(unsigned char id) { - bool useGamma = _light_use_gamma && _light_has_color && (id < 3); + bool useGamma = _light_use_gamma && _light_has_color && (id < _getRGBChannelWidth()); return _toPWM(_light_channel[id].shadow, useGamma, _light_channel[id].reverse); } @@ -421,7 +417,7 @@ void _shadow() { if (_light_steps_left == 0) _light_transition_ticker.detach(); // Update 4 Channels if RGBW else 3 - char channels = _light_use_white ? 4 : 3; + unsigned char channels = _getRGBChannelWidth(); // Transitions unsigned char target; @@ -475,7 +471,7 @@ void _lightProviderUpdate() { void _lightColorSave() { - //TODO: Remove this once this code was in a stable release + // TODO: Remove this once this code was in a stable release // This force set ch3 to 0 which could be not zero when you update from a old version if (_light_use_white) { setSetting("ch", 3, 0); @@ -494,7 +490,7 @@ void _lightColorRestore() { _light_channel[i].value = getSetting("ch", i, i==0 ? 255 : 0).toInt(); } _setWhite(); - + _light_brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt(); lightUpdate(false, false); } From 2bc1e6f97ab52dcf91d7c7722374d5ac7e16b455 Mon Sep 17 00:00:00 2001 From: Niklas Wagner Date: Sun, 8 Apr 2018 00:18:03 +0200 Subject: [PATCH 4/4] Rework RGBW implementation and cleanup script --- code/espurna/config/general.h | 4 +- code/espurna/light.ino | 507 ++++++++++++++-------------------- 2 files changed, 217 insertions(+), 294 deletions(-) diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 8ca8ff3a..7b0652e4 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -597,7 +597,6 @@ PROGMEM const char* const custom_reset_string[] = { // Light module #define MQTT_TOPIC_CHANNEL "channel" -#define MQTT_TOPIC_COLOR "color" // DEPRECATED, use RGB instead #define MQTT_TOPIC_COLOR_RGB "rgb" #define MQTT_TOPIC_COLOR_HSV "hsv" #define MQTT_TOPIC_ANIM_MODE "anim_mode" @@ -692,6 +691,9 @@ PROGMEM const char* const custom_reset_string[] = { #endif #define LIGHT_MAX_BRIGHTNESS 255 // Maximun brightness value +//#define LIGHT_MIN_MIREDS 153 // NOT USED (yet)! // Default to the Philips Hue value that HA has always assumed +//#define LIGHT_MAX_MIREDS 500 // NOT USED (yet)! // https://developers.meethue.com/documentation/core-concepts +#define LIGHT_DEFAULT_MIREDS 153 // Default value used by MQTT. This value is __NEVRER__ applied! #define LIGHT_STEP 32 // Step size #define LIGHT_USE_COLOR 1 // Use 3 first channels as RGB #define LIGHT_USE_WHITE 0 // Use white channel whenever RGB have the same value diff --git a/code/espurna/light.ino b/code/espurna/light.ino index 03f9f193..842bf142 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.ino @@ -28,9 +28,8 @@ typedef struct { unsigned char pin; bool reverse; bool state; - unsigned char value; // target or nominal value - unsigned char original; // original value before RGBW calculation - bool useOriginal; // determine if it should use the original or value variable + unsigned char inputValue; // value that has been inputted + unsigned char value; // normalized value including brightness unsigned char shadow; // represented value double current; // transition value } channel_t; @@ -42,7 +41,8 @@ bool _light_has_color = false; bool _light_use_white = false; bool _light_use_gamma = false; unsigned long _light_steps_left = 1; -unsigned int _light_brightness = LIGHT_MAX_BRIGHTNESS; +unsigned char _light_brightness = LIGHT_MAX_BRIGHTNESS; +unsigned int _light_mireds = LIGHT_DEFAULT_MIREDS; #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX #include @@ -75,72 +75,64 @@ const unsigned char _light_gamma_table[] = { // UTILS // ----------------------------------------------------------------------------- -// Returns the "correct" value for each channel -unsigned char _getChannel(char i) { - if (_light_channel[i].useOriginal) { - // Reset value when user disable the white channel without rebooting: - if (!_light_use_white) { - _light_channel[i].value = _light_channel[i].original; - _light_channel[i].useOriginal = false; - _light_channel[i].original = 0; - return _light_channel[i].value; - } - - return _light_channel[i].original; - } - - return _light_channel[i].value; +void _setRGBInputValue(unsigned char red, unsigned char green, unsigned char blue) { + _light_channel[0].inputValue = red; + _light_channel[1].inputValue = green; + _light_channel[2].inputValue = blue; } -void _setWhite() { - if (!_light_use_white) return; +void _generateBrightness() { + double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS; - unsigned int white, max_in, max_out; - double factor = 0; + // Convert RGB to RGBW + if (_light_has_color && _light_use_white) { + unsigned char white, max_in, max_out; + double factor = 0; - white = std::min(_light_channel[0].value, std::min(_light_channel[1].value, _light_channel[2].value)); - max_in = std::max(_light_channel[0].value, std::max(_light_channel[1].value, _light_channel[2].value)); + white = std::min(_light_channel[0].inputValue, std::min(_light_channel[1].inputValue, _light_channel[2].inputValue)); + max_in = std::max(_light_channel[0].inputValue, std::max(_light_channel[1].inputValue, _light_channel[2].inputValue)); - for (unsigned int i=0; i < 3; i++) { - _light_channel[i].useOriginal = true; - _light_channel[i].original = _light_channel[i].value; - _light_channel[i].value -= white; - } - _light_channel[3].value = white; + for (unsigned int i=0; i < 3; i++) { + _light_channel[i].value = _light_channel[i].inputValue - white; + } + _light_channel[3].value = white; - max_out = std::max(std::max(_light_channel[0].value, _light_channel[1].value), std::max(_light_channel[2].value, _light_channel[3].value)); + max_out = std::max(std::max(_light_channel[0].value, _light_channel[1].value), std::max(_light_channel[2].value, _light_channel[3].value)); - if (max_out > 0) { - factor = (double) (max_in / max_out); - } + if (max_out > 0) { + factor = (double) (max_in / max_out); + } - for (unsigned int i=0; i < 4; i++) { - _light_channel[i].value = round(_light_channel[i].value * factor);; + // Scale up to equal input values. So [250,150,50] -> [200,100,0,50] -> [250, 125, 0, 63] + for (unsigned int i=0; i < 4; i++) { + _light_channel[i].value = round((double) _light_channel[i].value * factor * brightness); + } + // Don't apply brightness, it is already in the inputValue: + if (_light_channel.size() == 5) { + _light_channel[4].value = _light_channel[4].inputValue; + } + } else { + // Don't apply brightness, it is already in the inputValue: + for (unsigned char i=0; i < _light_channel.size(); i++) { + _light_channel[i].value = _light_channel[i].inputValue; + } } } -//Return 4 (RGBW) or 3 (RGB) -unsigned char _getRGBChannelWidth() { - return _light_use_white ? 4 : 3; -} +// ----------------------------------------------------------------------------- +// Input Values +// ----------------------------------------------------------------------------- void _fromLong(unsigned long value, bool brightness) { - if (brightness) { - _light_channel[0].value = (value >> 24) & 0xFF; - _light_channel[1].value = (value >> 16) & 0xFF; - _light_channel[2].value = (value >> 8) & 0xFF; + _setRGBInputValue((value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF); _light_brightness = (value & 0xFF) * LIGHT_MAX_BRIGHTNESS / 255; } else { - _light_channel[0].value = (value >> 16) & 0xFF; - _light_channel[1].value = (value >> 8) & 0xFF; - _light_channel[2].value = (value) & 0xFF; + _setRGBInputValue((value >> 16) & 0xFF, (value >> 8) & 0xFF, (value) & 0xFF); } - } void _fromRGB(const char * rgb) { - char * p = (char *) rgb; if (strlen(p) == 0) return; @@ -151,21 +143,18 @@ void _fromRGB(const char * rgb) { unsigned long value = strtoul(p, NULL, 16); // RGBA values are interpreted like RGB + brightness _fromLong(value, strlen(p) > 7); - _setWhite(); } break; case 'M': // Mired Value if (_light_has_color) { unsigned long mireds = atol(p + 1); _fromMireds(mireds); - //_setWhite(); } break; case 'K': // Kelvin Value if (_light_has_color) { unsigned long kelvin = atol(p + 1); _fromKelvin(kelvin); - //_setWhite(); } break; default: // assume decimal values separated by commas @@ -175,43 +164,24 @@ void _fromRGB(const char * rgb) { tok = strtok(p, ","); while (tok != NULL) { - _light_channel[count].value = atoi(tok); + _light_channel[count].inputValue = atoi(tok); if (++count == channels) break; tok = strtok(NULL, ","); } - // RGB but less than 3 values received + // RGB but less than 3 values received, assume it is 0 if (_light_has_color && (count < 3)) { - _light_channel[1].value = _light_channel[0].value; - _light_channel[2].value = _light_channel[0].value; + // check channel 1 and 2: + for (int i = 1; i <= 2; i++) { + if (count < (i+1)) { + _light_channel[i].inputValue = 0; + } + } } - _setWhite(); break; } } -void _toRGB(char * rgb, size_t len, bool applyBrightness) { - - if (!_light_has_color) return; - - float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1; - - unsigned long value = 0; - - value += _getChannel(0) * b; - value <<= 8; - value += _getChannel(1) * b; - value <<= 8; - value += _getChannel(2) * b; - - snprintf_P(rgb, len, PSTR("#%06X"), value); - -} - -void _toRGB(char * rgb, size_t len) { - _toRGB(rgb, len, false); -} - // HSV string is expected to be "H,S,V", where: // 0 <= H <= 360 // 0 <= S <= 100 @@ -236,72 +206,113 @@ void _fromHSV(const char * hsv) { // HSV to RGB transformation ----------------------------------------------- + //INPUT: [0,100,57] + //IS: [145,0,0] + //SHOULD: [255,0,0] + double h = (value[0] == 360) ? 0 : (double) value[0] / 60.0; double f = (h - floor(h)); double s = (double) value[1] / 100.0; - unsigned char v = round((double) value[2] * 255.0 / 100.0); - unsigned char p = round(v * (1.0 - s)); - unsigned char q = round(v * (1.0 - s * f)); - unsigned char t = round(v * (1.0 - s * (1.0 - f))); + + _light_brightness = round((double) value[2] * 2.55); // (255/100) + unsigned char p = round(255 * (1.0 - s)); + unsigned char q = round(255 * (1.0 - s * f)); + unsigned char t = round(255 * (1.0 - s * (1.0 - f))); switch (int(h)) { case 0: - _light_channel[0].value = v; - _light_channel[1].value = t; - _light_channel[2].value = p; + _setRGBInputValue(255, t, p); break; case 1: - _light_channel[0].value = q; - _light_channel[1].value = v; - _light_channel[2].value = p; + _setRGBInputValue(q, 255, p); break; case 2: - _light_channel[0].value = p; - _light_channel[1].value = v; - _light_channel[2].value = t; + _setRGBInputValue(p, 255, t); break; case 3: - _light_channel[0].value = p; - _light_channel[1].value = q; - _light_channel[2].value = v; + _setRGBInputValue(p, q, 255); break; case 4: - _light_channel[0].value = t; - _light_channel[1].value = p; - _light_channel[2].value = v; + _setRGBInputValue(t, p, 255); break; case 5: - _light_channel[0].value = v; - _light_channel[1].value = p; - _light_channel[2].value = q; + _setRGBInputValue(255, p, q); break; default: - _light_channel[0].value = 0; - _light_channel[1].value = 0; - _light_channel[2].value = 0; + _setRGBInputValue(0,0,0); break; } +} - _setWhite(); +// Thanks to Sacha Telgenhof for sharing this code in his AiLight library +// https://github.com/stelgenhof/AiLight +void _fromKelvin(unsigned long kelvin, bool setMireds) { - _light_brightness = LIGHT_MAX_BRIGHTNESS; + // Check we have RGB channels + if (!_light_has_color) return; + + if (setMireds) { + _light_mireds = round(1000000UL / kelvin); + } + + // Calculate colors + unsigned int red = (kelvin <= 66) + ? LIGHT_MAX_VALUE + : 329.698727446 * pow((kelvin - 60), -0.1332047592); + unsigned int green = (kelvin <= 66) + ? 99.4708025861 * log(kelvin) - 161.1195681661 + : 288.1221695283 * pow(kelvin, -0.0755148492); + unsigned int blue = (kelvin >= 66) + ? LIGHT_MAX_VALUE + : ((kelvin <= 19) + ? 0 + : 138.5177312231 * log(kelvin - 10) - 305.0447927307); + + _setRGBInputValue( + constrain(red, 0, LIGHT_MAX_VALUE), + constrain(green, 0, LIGHT_MAX_VALUE), + constrain(blue, 0, LIGHT_MAX_VALUE) + ); } -void _toHSV(char * hsv, size_t len) { +void _fromKelvin(unsigned long kelvin) { + _fromKelvin(kelvin, true); +} - if (!_light_has_color) return; +// Color temperature is measured in mireds (kelvin = 1e6/mired) +void _fromMireds(unsigned long mireds) { + if (mireds == 0) mireds = 1; + _light_mireds = mireds; + unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000) / 100; + _fromKelvin(kelvin, false); +} + +// ----------------------------------------------------------------------------- +// Output Values +// ----------------------------------------------------------------------------- + +void _toRGB(char * rgb, size_t len) { + unsigned long value = 0; + + value += _light_channel[0].inputValue; + value <<= 8; + value += _light_channel[1].inputValue; + value <<= 8; + value += _light_channel[2].inputValue; + + snprintf_P(rgb, len, PSTR("#%06X"), value); +} - double min, max; - double h, s, v; +void _toHSV(char * hsv, size_t len) { + double min, max, h, s, v; + double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS; - double r = (double) _getChannel(0) / 255.0; - double g = (double) _getChannel(1) / 255.0; - double b = (double) _getChannel(2) / 255.0; + double r = (double) (_light_channel[0].inputValue * brightness) / 255.0; + double g = (double) (_light_channel[1].inputValue * brightness) / 255.0; + double b = (double) (_light_channel[2].inputValue * brightness) / 255.0; - min = (r < g) ? r : g; - min = (min < b) ? min : b; - max = (r > g) ? r : g; - max = (max > b) ? max : b; + min = std::min(r, std::min(g, b)); + max = std::max(r, std::max(g, b)); v = 100.0 * max; if (v == 0) { @@ -329,69 +340,27 @@ void _toHSV(char * hsv, size_t len) { snprintf_P(hsv, len, PSTR("%d,%d,%d"), round(h), round(s), round(v)); } -void _toLong(char * color, size_t len, bool applyBrightness) { - +void _toLong(char * color, size_t len) { if (!_light_has_color) return; - float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1; - snprintf_P(color, len, PSTR("%d,%d,%d"), - (int) (_getChannel(0) * b), - (int) (_getChannel(1) * b), - (int) (_getChannel(2) * b) + (int) _light_channel[0].inputValue, + (int) _light_channel[1].inputValue, + (int) _light_channel[2].inputValue ); - -} - -void _toLong(char * color, size_t len) { - _toLong(color, len, false); } void _toCSV(char * buffer, size_t len, bool applyBrightness) { char num[10]; float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1; for (unsigned char i=0; i<_light_channel.size(); i++) { - itoa(_light_channel[i].value * b, num, 10); + itoa(_light_channel[i].inputValue * b, num, 10); if (i>0) strncat(buffer, ",", len--); strncat(buffer, num, len); len = len - strlen(num); } } -// Thanks to Sacha Telgenhof for sharing this code in his AiLight library -// https://github.com/stelgenhof/AiLight -void _fromKelvin(unsigned long kelvin) { - - // Check we have RGB channels - if (!_light_has_color) return; - - // Calculate colors - unsigned int red = (kelvin <= 66) - ? LIGHT_MAX_VALUE - : 329.698727446 * pow((kelvin - 60), -0.1332047592); - unsigned int green = (kelvin <= 66) - ? 99.4708025861 * log(kelvin) - 161.1195681661 - : 288.1221695283 * pow(kelvin, -0.0755148492); - unsigned int blue = (kelvin >= 66) - ? LIGHT_MAX_VALUE - : ((kelvin <= 19) - ? 0 - : 138.5177312231 * log(kelvin - 10) - 305.0447927307); - - // Save values - _light_channel[0].value = constrain(red, 0, LIGHT_MAX_VALUE); - _light_channel[1].value = constrain(green, 0, LIGHT_MAX_VALUE); - _light_channel[2].value = constrain(blue, 0, LIGHT_MAX_VALUE); - -} - -// Color temperature is measured in mireds (kelvin = 1e6/mired) -void _fromMireds(unsigned long mireds) { - if (mireds == 0) mireds = 1; - unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000) / 100; - _fromKelvin(kelvin); -} - // ----------------------------------------------------------------------------- // PROVIDER // ----------------------------------------------------------------------------- @@ -406,30 +375,19 @@ unsigned int _toPWM(unsigned long value, bool gamma, bool reverse) { // Returns a PWM value for the given channel ID unsigned int _toPWM(unsigned char id) { - bool useGamma = _light_use_gamma && _light_has_color && (id < _getRGBChannelWidth()); + bool useGamma = _light_use_gamma && _light_has_color && (id < 3); return _toPWM(_light_channel[id].shadow, useGamma, _light_channel[id].reverse); } void _shadow() { - // Update transition ticker _light_steps_left--; if (_light_steps_left == 0) _light_transition_ticker.detach(); - // Update 4 Channels if RGBW else 3 - unsigned char channels = _getRGBChannelWidth(); - // Transitions unsigned char target; for (unsigned int i=0; i < _light_channel.size(); i++) { - if (_light_state && _light_channel[i].state) { - target = _light_channel[i].value; - if ((_light_brightness < LIGHT_MAX_BRIGHTNESS) && _light_has_color && (i < channels)) { - target *= ((float) _light_brightness / LIGHT_MAX_BRIGHTNESS); - } - } else { - target = 0; - } + target = _light_state ? _light_channel[i].value : 0; if (_light_steps_left == 0) { _light_channel[i].current = target; } else { @@ -470,16 +428,8 @@ void _lightProviderUpdate() { // ----------------------------------------------------------------------------- void _lightColorSave() { - - // TODO: Remove this once this code was in a stable release - // This force set ch3 to 0 which could be not zero when you update from a old version - if (_light_use_white) { - setSetting("ch", 3, 0); - } - for (unsigned int i=0; i < _light_channel.size(); i++) { - if (_light_use_white && (i == 3)) continue; //Don't save white channel. - setSetting("ch", i, _getChannel(i)); + setSetting("ch", i, _light_channel[i].inputValue); } setSetting("brightness", _light_brightness); saveSettings(); @@ -487,10 +437,8 @@ void _lightColorSave() { void _lightColorRestore() { for (unsigned int i=0; i < _light_channel.size(); i++) { - _light_channel[i].value = getSetting("ch", i, i==0 ? 255 : 0).toInt(); + _light_channel[i].inputValue = getSetting("ch", i, i==0 ? 255 : 0).toInt(); } - _setWhite(); - _light_brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt(); lightUpdate(false, false); } @@ -510,7 +458,6 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl mqttSubscribe(MQTT_TOPIC_BRIGHTNESS); mqttSubscribe(MQTT_TOPIC_MIRED); mqttSubscribe(MQTT_TOPIC_KELVIN); - mqttSubscribe(MQTT_TOPIC_COLOR); // DEPRECATE mqttSubscribe(MQTT_TOPIC_COLOR_RGB); mqttSubscribe(MQTT_TOPIC_COLOR_HSV); } @@ -552,7 +499,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl } // Color - if (t.equals(MQTT_TOPIC_COLOR) || t.equals(MQTT_TOPIC_COLOR_RGB)) { // DEPRECATE MQTT_TOPIC_COLOR + if (t.equals(MQTT_TOPIC_COLOR_RGB)) { lightColor(payload, true); lightUpdate(true, mqttForward()); return; @@ -587,19 +534,18 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl } void lightMQTT() { - char buffer[20]; if (_light_has_color) { // Color if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) { - _toRGB(buffer, sizeof(buffer), false); + _toRGB(buffer, sizeof(buffer)); } else { - _toLong(buffer, sizeof(buffer), false); + _toLong(buffer, sizeof(buffer)); } - mqttSend(MQTT_TOPIC_COLOR, buffer); // DEPRECATE mqttSend(MQTT_TOPIC_COLOR_RGB, buffer); + _toHSV(buffer, sizeof(buffer)); mqttSend(MQTT_TOPIC_COLOR_HSV, buffer); @@ -607,11 +553,14 @@ void lightMQTT() { snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_brightness); mqttSend(MQTT_TOPIC_BRIGHTNESS, buffer); + // Mireds + snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_mireds); + mqttSend(MQTT_TOPIC_MIRED, buffer); } // Channels for (unsigned int i=0; i < _light_channel.size(); i++) { - itoa(_light_channel[i].value, buffer, 10); + itoa(_light_channel[i].inputValue, buffer, 10); mqttSend(MQTT_TOPIC_CHANNEL, i, buffer); } @@ -637,7 +586,7 @@ void lightMQTTGroup() { void lightBroker() { char buffer[10]; for (unsigned int i=0; i < _light_channel.size(); i++) { - itoa(_light_channel[i].value, buffer, 10); + itoa(_light_channel[i].inputValue, buffer, 10); brokerPublish(MQTT_TOPIC_CHANNEL, i, buffer); } } @@ -656,12 +605,10 @@ bool lightHasColor() { return _light_has_color; } -unsigned char lightWhiteChannels() { - return _light_channel.size() % 3; -} - void lightUpdate(bool save, bool forward, bool group_forward) { + _generateBrightness(); + // Configure color transition _light_steps_left = _light_use_transitions ? LIGHT_TRANSITION_STEPS : 1; _light_transition_ticker.attach_ms(LIGHT_TRANSITION_STEP, _lightProviderUpdate); @@ -735,7 +682,7 @@ void lightColor(unsigned long color) { String lightColor(bool rgb) { char str[12]; if (rgb) { - _toRGB(str, sizeof(str), false); + _toRGB(str, sizeof(str)); } else { _toHSV(str, sizeof(str)); } @@ -748,14 +695,14 @@ String lightColor() { unsigned int lightChannel(unsigned char id) { if (id <= _light_channel.size()) { - return _light_channel[id].value; + return _light_channel[id].inputValue; } return 0; } void lightChannel(unsigned char id, unsigned int value) { if (id <= _light_channel.size()) { - _light_channel[id].value = constrain(value, 0, LIGHT_MAX_VALUE); + _light_channel[id].inputValue = constrain(value, 0, LIGHT_MAX_VALUE); } } @@ -796,15 +743,13 @@ void _lightWebSocketOnSend(JsonObject& root) { } } JsonArray& channels = root.createNestedArray("channels"); - for (unsigned char id=0; id < lightChannels(); id++) { + for (unsigned char id=0; id < _light_channel.size(); id++) { channels.add(lightChannel(id)); } } void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { - if (_light_has_color) { - if (strcmp(action, "color") == 0) { if (data.containsKey("rgb")) { lightColor(data["rgb"], true); @@ -819,7 +764,6 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject lightUpdate(true, true); } } - } if (strcmp(action, "channel") == 0) { @@ -828,99 +772,76 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject lightUpdate(true, true); } } - } void _lightAPISetup() { - - // API entry points (protected with apikey) - if (_light_has_color) { - - // DEPRECATE - apiRegister(MQTT_TOPIC_COLOR, - [](char * buffer, size_t len) { - if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) { - _toRGB(buffer, len, false); - } else { - _toLong(buffer, len, false); - } - }, - [](const char * payload) { - lightColor(payload, true); - lightUpdate(true, true); - } - ); - - apiRegister(MQTT_TOPIC_COLOR_RGB, - [](char * buffer, size_t len) { - if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) { - _toRGB(buffer, len, false); - } else { - _toLong(buffer, len, false); - } - }, - [](const char * payload) { - lightColor(payload, true); - lightUpdate(true, true); - } - ); - - apiRegister(MQTT_TOPIC_COLOR_HSV, - [](char * buffer, size_t len) { - _toHSV(buffer, len); - }, - [](const char * payload) { - lightColor(payload, false); - lightUpdate(true, true); - } - ); - - apiRegister(MQTT_TOPIC_BRIGHTNESS, - [](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("%d"), _light_brightness); - }, - [](const char * payload) { - lightBrightness(atoi(payload)); - lightUpdate(true, true); - } - ); - - apiRegister(MQTT_TOPIC_KELVIN, - [](char * buffer, size_t len) {}, - [](const char * payload) { - _fromKelvin(atol(payload)); - lightUpdate(true, true); - } - ); - - apiRegister(MQTT_TOPIC_MIRED, - [](char * buffer, size_t len) {}, - [](const char * payload) { - _fromMireds(atol(payload)); - lightUpdate(true, true); + // API entry points (protected with apikey) + if (_light_has_color) { + apiRegister(MQTT_TOPIC_COLOR_RGB, + [](char * buffer, size_t len) { + if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) { + _toRGB(buffer, len); + } else { + _toLong(buffer, len); } - ); - - } - - for (unsigned int id=0; id