|
|
@ -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 <my92xx.h>
|
|
|
@ -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<lightChannels(); id++) { |
|
|
|
}, |
|
|
|
[](const char * payload) { |
|
|
|
lightColor(payload, true); |
|
|
|
lightUpdate(true, true); |
|
|
|
} |
|
|
|
); |
|
|
|
|
|
|
|
char key[15]; |
|
|
|
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id); |
|
|
|
apiRegister(MQTT_TOPIC_COLOR_HSV, |
|
|
|
[](char * buffer, size_t len) { |
|
|
|
_toHSV(buffer, len); |
|
|
|
}, |
|
|
|
[](const char * payload) { |
|
|
|
lightColor(payload, false); |
|
|
|
lightUpdate(true, true); |
|
|
|
} |
|
|
|
); |
|
|
|
|
|
|
|
apiRegister(key, |
|
|
|
[id](char * buffer, size_t len) { |
|
|
|
snprintf_P(buffer, len, PSTR("%d"), lightChannel(id)); |
|
|
|
}, |
|
|
|
[id](const char * payload) { |
|
|
|
lightChannel(id, atoi(payload)); |
|
|
|
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); |
|
|
|
} |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
for (unsigned int id=0; id<_light_channel.size(); id++) { |
|
|
|
char key[15]; |
|
|
|
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id); |
|
|
|
|
|
|
|
apiRegister(key, |
|
|
|
[id](char * buffer, size_t len) { |
|
|
|
snprintf_P(buffer, len, PSTR("%d"), lightChannel(id)); |
|
|
|
}, |
|
|
|
[id](const char * payload) { |
|
|
|
lightChannel(id, atoi(payload)); |
|
|
|
lightUpdate(true, true); |
|
|
|
} |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#endif // WEB_SUPPORT
|
|
|
|