diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index bc314071..95cd8c9e 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -50,7 +50,7 @@ #define HEARTBEAT_REPORT_FREEHEAP 1 #define HEARTBEAT_REPORT_VCC 1 #define HEARTBEAT_REPORT_RELAY 1 -#define HEARTBEAT_REPORT_COLOR 1 +#define HEARTBEAT_REPORT_LIGHT 1 #define HEARTBEAT_REPORT_HOSTNAME 1 #define HEARTBEAT_REPORT_APP 1 #define HEARTBEAT_REPORT_VERSION 1 @@ -187,6 +187,7 @@ PROGMEM const char* const custom_reset_string[] = { #define WEBSERVER_PORT 80 // HTTP port #define DNS_PORT 53 // MDNS port #define ENABLE_MDNS 1 // Enabled MDNS +#define API_BUFFER_SIZE 10 // Size of the buffer for HTTP GET API responses #define WEB_MODE_NORMAL 0 #define WEB_MODE_PASSWORD 1 @@ -234,10 +235,6 @@ PROGMEM const char* const custom_reset_string[] = { #define MQTT_TOPIC_ACTION "action" #define MQTT_TOPIC_RELAY "relay" #define MQTT_TOPIC_LED "led" -#define MQTT_TOPIC_COLOR "color" -#define MQTT_TOPIC_WHITE "white" -#define MQTT_TOPIC_BRIGHTNESS "brightness" -#define MQTT_TOPIC_COLORTEMP "color/temperature" #define MQTT_TOPIC_BUTTON "button" #define MQTT_TOPIC_IP "ip" #define MQTT_TOPIC_VERSION "version" @@ -253,6 +250,13 @@ PROGMEM const char* const custom_reset_string[] = { #define MQTT_TOPIC_TIME "time" #define MQTT_TOPIC_ANALOG "analog" +// Lights +#define MQTT_TOPIC_CHANNEL "channel" +#define MQTT_TOPIC_COLOR "color" +#define MQTT_TOPIC_BRIGHTNESS "brightness" +#define MQTT_TOPIC_MIRED "mired" +#define MQTT_TOPIC_KELVIN "kelvin" + #define MQTT_STATUS_ONLINE "1" // Value for the device ON message #define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will) @@ -282,31 +286,28 @@ PROGMEM const char* const custom_reset_string[] = { // LIGHT // ----------------------------------------------------------------------------- -#define ENABLE_GAMMA_CORRECTION 0 - #define LIGHT_PROVIDER_NONE 0 -#define LIGHT_PROVIDER_WS2812 1 -#define LIGHT_PROVIDER_RGB 2 -#define LIGHT_PROVIDER_RGBW 3 -#define LIGHT_PROVIDER_MY9192 4 -#define LIGHT_PROVIDER_RGB2W 5 - -#define LIGHT_DEFAULT_COLOR "#000080" -#define LIGHT_SAVE_DELAY 5 -#define LIGHT_MAX_VALUE 255 -#define LIGHT_MAX_BRIGHTNESS LIGHT_MAX_VALUE - -// Settings for MY9291 bulbs (AI Light) -#define MY9291_DI_PIN 13 -#define MY9291_DCKI_PIN 15 -#define MY9291_COMMAND MY9291_COMMAND_DEFAULT - -// Shared settings between RGB and RGBW lights -#define RGBW_INVERSE_LOGIC 1 -#define RGBW_RED_PIN 14 -#define RGBW_GREEN_PIN 5 -#define RGBW_BLUE_PIN 12 -#define RGBW_WHITE_PIN 13 +#define LIGHT_PROVIDER_MY9192 1 +#define LIGHT_PROVIDER_DIMMER 2 + +// LIGHT_PROVIDER_DIMMER can have from 1 to 5 different channels. +// They have to be defined for each device in the hardware.h file. +// If 3 or more channels first 3 will be considered RGB. +// Usual configurations are: +// 1 channels => W +// 2 channels => WW +// 3 channels => RGB +// 4 channels => RGBW +// 5 channels => RGBWW + +#define LIGHT_DEFAULT_COLOR "#000080" // Default start color +#define LIGHT_SAVE_DELAY 5 // Persist color after 5 seconds to avoid wearing out +#define LIGHT_PWM_FREQUENCY 1000 // PWM frequency +#define LIGHT_MAX_PWM 4095 // Maximum PWM value +#define LIGHT_MAX_VALUE 255 // Maximum light value +#define LIGHT_MAX_BRIGHTNESS 255 // Maximun brightness value +#define LIGHT_USE_WHITE 0 // Use white channel whenever RGB have the same value +#define LIGHT_ENABLE_GAMMA 0 // Enable gamma correction // ----------------------------------------------------------------------------- // DOMOTICZ diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 12b2b48e..7d34279d 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -270,6 +270,10 @@ #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define LIGHT_PROVIDER LIGHT_PROVIDER_MY9192 + #define MY9291_DI_PIN 13 + #define MY9291_DCKI_PIN 15 + #define MY9291_COMMAND MY9291_COMMAND_DEFAULT + // ----------------------------------------------------------------------------- // LED Controller // ----------------------------------------------------------------------------- @@ -281,19 +285,17 @@ #define LED1_PIN 2 #define LED1_PIN_INVERSE 1 #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT - #define LIGHT_PROVIDER LIGHT_PROVIDER_RGB + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER - #undef RGBW_INVERSE_LOGIC - #undef RGBW_RED_PIN - #undef RGBW_GREEN_PIN - #undef RGBW_BLUE_PIN - #undef RGBW_WHITE_PIN + #define LIGHT_CH1_PIN 14 // RED + #define LIGHT_CH2_PIN 5 // GREEN + #define LIGHT_CH3_PIN 12 // BLUE + #define LIGHT_CH4_PIN 13 // WHITE - #define RGBW_INVERSE_LOGIC 1 - #define RGBW_RED_PIN 14 - #define RGBW_GREEN_PIN 5 - #define RGBW_BLUE_PIN 12 - #define RGBW_WHITE_PIN 13 + #define LIGHT_CH1_INVERSE 1 + #define LIGHT_CH2_INVERSE 1 + #define LIGHT_CH3_INVERSE 1 + #define LIGHT_CH4_INVERSE 1 // ----------------------------------------------------------------------------- // HUACANXING H801 @@ -306,20 +308,19 @@ #define LED1_PIN 5 #define LED1_PIN_INVERSE 1 #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT - #define LIGHT_PROVIDER LIGHT_PROVIDER_RGB2W - - #undef RGBW_INVERSE_LOGIC - #undef RGBW_RED_PIN - #undef RGBW_GREEN_PIN - #undef RGBW_BLUE_PIN - #undef RGBW_WHITE_PIN - - #define RGBW_INVERSE_LOGIC 1 - #define RGBW_RED_PIN 15 - #define RGBW_GREEN_PIN 13 - #define RGBW_BLUE_PIN 12 - #define RGBW_WHITE_PIN 14 - #define RGBW_WHITE2_PIN 4 + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + + #define LIGHT_CH1_PIN 15 // RED + #define LIGHT_CH2_PIN 13 // GREEN + #define LIGHT_CH3_PIN 12 // BLUE + #define LIGHT_CH4_PIN 14 // WHITE1 + #define LIGHT_CH5_PIN 4 // WHITE2 + + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + #define LIGHT_CH5_INVERSE 0 // ----------------------------------------------------------------------------- // Jan Goedeke Wifi Relay diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index e8edfd43..cf61c80e 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -88,10 +88,8 @@ void heartbeat() { #if (HEARTBEAT_REPORT_RELAY) relayMQTT(); #endif - #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE - #if (HEARTBEAT_REPORT_COLOR) - mqttSend(MQTT_TOPIC_COLOR, lightColor().c_str()); - #endif + #if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) & (HEARTBEAT_REPORT_LIGHT) + lightMQTT(); #endif #if (HEARTBEAT_REPORT_VCC) #if ENABLE_ADC_VCC @@ -250,6 +248,7 @@ void setup() { // Prepare configuration for version 2.0 hwUpwardsCompatibility(); + //settingsDump(); } diff --git a/code/espurna/hardware.ino b/code/espurna/hardware.ino index a34e4466..db499306 100644 --- a/code/espurna/hardware.ino +++ b/code/espurna/hardware.ino @@ -13,17 +13,6 @@ the migration to future version 2 will be straigh forward. */ - -#define RELAY_PROVIDER_RELAY 0 -#define RELAY_PROVIDER_DUAL 1 -#define RELAY_PROVIDER_LIGHT 2 - -#define LIGHT_PROVIDER_NONE 0 -#define LIGHT_PROVIDER_WS2812 1 -#define LIGHT_PROVIDER_RGB 2 -#define LIGHT_PROVIDER_RGBW 3 -#define LIGHT_PROVIDER_MY9192 4 - void hwUpwardsCompatibility() { unsigned int board = getSetting("board", 0).toInt(); @@ -234,14 +223,17 @@ void hwUpwardsCompatibility() { #ifdef LED_CONTROLLER setSetting("board", 21); setSetting("relayProvider", RELAY_PROVIDER_LIGHT); - setSetting("lightProvider", LIGHT_PROVIDER_RGB); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); setSetting("ledGPIO", 1, 2); setSetting("ledLogic", 1, 1); - setSetting("redGPIO", 14); - setSetting("greenGPIO", 5); - setSetting("blueGPIO", 12); - setSetting("whiteGPIO", 13); - setSetting("lightLogic", 1); + setSetting("ch1GPIO", 14); + setSetting("ch2GPIO", 5); + setSetting("ch3GPIO", 12); + setSetting("ch4GPIO", 13); + setSetting("ch1Logic", 1); + setSetting("ch2Logic", 1); + setSetting("ch3Logic", 1); + setSetting("ch4Logic", 1); #endif #ifdef ITEAD_MOTOR @@ -267,15 +259,19 @@ void hwUpwardsCompatibility() { #ifdef H801_LED_CONTROLLER setSetting("board", 24); setSetting("relayProvider", RELAY_PROVIDER_LIGHT); - setSetting("lightProvider", LIGHT_PROVIDER_RGB2W); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); setSetting("ledGPIO", 5, 1); setSetting("ledLogic", 1, 1); - setSetting("redGPIO", 15); - setSetting("greenGPIO", 13); - setSetting("blueGPIO", 12); - setSetting("whiteGPIO", 14); - setSetting("white2GPIO", 4); - setSetting("lightLogic", 1); + setSetting("ch1GPIO", 15); + setSetting("ch2GPIO", 13); + setSetting("ch3GPIO", 12); + setSetting("ch4GPIO", 14); + setSetting("ch5GPIO", 4); + setSetting("ch1Logic", 1); + setSetting("ch2Logic", 1); + setSetting("ch3Logic", 1); + setSetting("ch4Logic", 1); + setSetting("ch5Logic", 1); #endif saveSettings(); diff --git a/code/espurna/light.ino b/code/espurna/light.ino index 42d6faf0..1ebf894a 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.ino @@ -9,56 +9,55 @@ Copyright (C) 2016-2017 by Xose Pérez #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE #include +#include +#include + +typedef struct { + unsigned char pin; + bool reverse; + unsigned char value; + unsigned char shadow; +} channel_t; +std::vector _channels; + Ticker colorTicker; bool _lightState = false; -float brightness = 1.0; -unsigned int _lightColor[3] = {0}; +unsigned int _brightness = LIGHT_MAX_BRIGHTNESS; #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192 #include my9291 * _my9291; #endif -#if ENABLE_GAMMA_CORRECTION - - #define GAMMA_TABLE_SIZE (256) - #undef LIGHT_PWM_RANGE - #define LIGHT_PWM_RANGE (4095) - - // Gamma Correction lookup table for gamma=2.8 and 12 bit (4095) full scale - const unsigned short gamma_table[GAMMA_TABLE_SIZE] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 8, 9, 10, 11, - 12, 13, 15, 16, 17, 18, 20, 21, 23, 25, 26, 28, 30, 32, 34, 36, - 38, 40, 43, 45, 48, 50, 53, 56, 59, 62, 65, 68, 71, 75, 78, 82, - 85, 89, 93, 97, 101, 105, 110, 114, 119, 123, 128, 133, 138, 143, 149, 154, - 159, 165, 171, 177, 183, 189, 195, 202, 208, 215, 222, 229, 236, 243, 250, 258, - 266, 273, 281, 290, 298, 306, 315, 324, 332, 341, 351, 360, 369, 379, 389, 399, - 409, 419, 430, 440, 451, 462, 473, 485, 496, 508, 520, 532, 544, 556, 569, 582, - 594, 608, 621, 634, 648, 662, 676, 690, 704, 719, 734, 749, 764, 779, 795, 811, - 827, 843, 859, 876, 893, 910, 927, 944, 962, 980, 998,1016,1034,1053,1072,1091, - 1110,1130,1150,1170,1190,1210,1231,1252,1273,1294,1316,1338,1360,1382,1404,1427, - 1450,1473,1497,1520,1544,1568,1593,1617,1642,1667,1693,1718,1744,1770,1797,1823, - 1850,1877,1905,1932,1960,1988,2017,2045,2074,2103,2133,2162,2192,2223,2253,2284, - 2315,2346,2378,2410,2442,2474,2507,2540,2573,2606,2640,2674,2708,2743,2778,2813, - 2849,2884,2920,2957,2993,3030,3067,3105,3143,3181,3219,3258,3297,3336,3376,3416, - 3456,3496,3537,3578,3619,3661,3703,3745,3788,3831,3874,3918,3962,4006,4050,4095 }; - -#endif - -#ifndef LIGHT_PWM_FREQUENCY - #define LIGHT_PWM_FREQUENCY (1000) -#endif +#if LIGHT_ENABLE_GAMMA + +// Gamma Correction lookup table for gamma=2.8 and 12 bit (4095) full scale +// TODO: move to PROGMEM +const unsigned short gamma_table[LIGHT_MAX_VALUE+1] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 8, 9, 10, 11, + 12, 13, 15, 16, 17, 18, 20, 21, 23, 25, 26, 28, 30, 32, 34, 36, + 38, 40, 43, 45, 48, 50, 53, 56, 59, 62, 65, 68, 71, 75, 78, 82, + 85, 89, 93, 97, 101, 105, 110, 114, 119, 123, 128, 133, 138, 143, 149, 154, + 159, 165, 171, 177, 183, 189, 195, 202, 208, 215, 222, 229, 236, 243, 250, 258, + 266, 273, 281, 290, 298, 306, 315, 324, 332, 341, 351, 360, 369, 379, 389, 399, + 409, 419, 430, 440, 451, 462, 473, 485, 496, 508, 520, 532, 544, 556, 569, 582, + 594, 608, 621, 634, 648, 662, 676, 690, 704, 719, 734, 749, 764, 779, 795, 811, + 827, 843, 859, 876, 893, 910, 927, 944, 962, 980, 998,1016,1034,1053,1072,1091, +1110,1130,1150,1170,1190,1210,1231,1252,1273,1294,1316,1338,1360,1382,1404,1427, +1450,1473,1497,1520,1544,1568,1593,1617,1642,1667,1693,1718,1744,1770,1797,1823, +1850,1877,1905,1932,1960,1988,2017,2045,2074,2103,2133,2162,2192,2223,2253,2284, +2315,2346,2378,2410,2442,2474,2507,2540,2573,2606,2640,2674,2708,2743,2778,2813, +2849,2884,2920,2957,2993,3030,3067,3105,3143,3181,3219,3258,3297,3336,3376,3416, +3456,3496,3537,3578,3619,3661,3703,3745,3788,3831,3874,3918,3962,4006,4050,4095 }; -#ifndef LIGHT_PWM_RANGE - #define LIGHT_PWM_RANGE (255) #endif // ----------------------------------------------------------------------------- // UTILS // ----------------------------------------------------------------------------- -void _color_string2array(const char * rgb, unsigned int * array) { +void _fromRGB(const char * rgb) { char * p = (char *) rgb; if (strlen(p) == 0) return; @@ -66,70 +65,87 @@ void _color_string2array(const char * rgb, unsigned int * array) { // if color begins with a # then assume HEX RGB if (p[0] == '#') { + if (!lightHasColor()) return; + ++p; - unsigned long value = strtol(p, NULL, 16); + unsigned long value = strtoul(p, NULL, 16); // RGBA values are interpreted like RGB + brightness if (strlen(p) > 7) { - array[0] = (value >> 24) & 0xFF; - array[1] = (value >> 16) & 0xFF; - array[2] = (value >> 8) & 0xFF; - brightness =float(value & 0xFF) / 255; + _channels[0].value = (value >> 24) & 0xFF; + _channels[1].value = (value >> 16) & 0xFF; + _channels[2].value = (value >> 8) & 0xFF; + _brightness = (value & 0xFF) * LIGHT_MAX_BRIGHTNESS / 255; } else { - array[0] = (value >> 16) & 0xFF; - array[1] = (value >> 8) & 0xFF; - array[2] = (value) & 0xFF; + _channels[0].value = (value >> 16) & 0xFF; + _channels[1].value = (value >> 8) & 0xFF; + _channels[2].value = (value) & 0xFF; } - // it's a temperature - } else if (p[strlen(p)-1] == 'K') { + // it's a temperature in mireds + } else if (p[0] == 'M') { + + unsigned long mireds = atol(p + 1); + _fromMireds(mireds); + + // it's a temperature in kelvin + } else if (p[0] == 'K') { - p[strlen(p)-1] = 0; - unsigned long temperature = atol(p); - _color_temperature2array(temperature, array); + unsigned long kelvin = atol(p + 1); + _fromKelvin(kelvin); // otherwise assume decimal values separated by commas } else { char * tok; - tok = strtok(p, ","); - array[0] = atoi(tok); - tok = strtok(NULL, ","); + unsigned char count = 0; + unsigned char channels = _channels.size(); - // if there are more than one value assume R,G,B - if (tok != NULL) { - array[1] = atoi(tok); + tok = strtok(p, ","); + while (tok != NULL) { + _channels[count].value = atoi(tok); + if (++count == channels) break; tok = strtok(NULL, ","); - if (tok != NULL) { - array[2] = atoi(tok); - } else { - array[2] = 0; - } + } - // only one value set red, green and blue to the same value - } else { - array[2] = array[1] = array[0]; + // RGB but less than 3 values received + if (channels > 2 & count < 3) { + _channels[1].value = _channels[0].value; + _channels[2].value = _channels[0].value; } } } -void _color_array2rgb(unsigned int * array, float brightness, char * rgb) { - unsigned long value = array[0] * brightness; - value = (value << 8) + array[1] * brightness; - value = (value << 8) + array[2] * brightness; - sprintf(rgb, "#%06X", value); +void _toRGB(char * rgb, size_t len, bool applyBrightness) { + + if (!lightHasColor()) return; + + float b = applyBrightness ? (float) _brightness / LIGHT_MAX_BRIGHTNESS : 1; + + unsigned long value = 0; + + value += _channels[0].value * b; + value <<= 8; + value += _channels[1].value * b; + value <<= 8; + value += _channels[2].value * b; + + snprintf(rgb, len, "#%06X", value); + +} + +void _toRGB(char * rgb, size_t len) { + _toRGB(rgb, len, false); } // Thanks to Sacha Telgenhof for sharing this code in his AiLight library -// Color temperature is measured in mireds (kelvin = 1e6/mired) // https://github.com/stelgenhof/AiLight -void _color_temperature2array(unsigned long mireds, unsigned int * array) { +void _fromKelvin(unsigned long kelvin) { - // Force boundaries and conversion - if (mireds == 0) mireds = 1; - unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000) / 100; + // Check we have RGB channels + if (!lightHasColor()) return; // Calculate colors unsigned int red = (kelvin <= 66) @@ -145,152 +161,100 @@ void _color_temperature2array(unsigned long mireds, unsigned int * array) { : 138.5177312231 * log(kelvin - 10) - 305.0447927307); // Save values - array[0] = constrain(red, 0, LIGHT_MAX_VALUE); - array[1] = constrain(green, 0, LIGHT_MAX_VALUE); - array[2] = constrain(blue, 0, LIGHT_MAX_VALUE); + _channels[0].value = constrain(red, 0, LIGHT_MAX_VALUE); + _channels[1].value = constrain(green, 0, LIGHT_MAX_VALUE); + _channels[2].value = constrain(blue, 0, LIGHT_MAX_VALUE); } -// Converts a color intensity value (0..255) to a pwm value -// This takes care of positive or negative logic and brightness -unsigned int _intensity2pwm(unsigned int intensity, float brightness) { - - intensity = brightness * intensity; +// 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); +} - #if ENABLE_GAMMA_CORRECTION - unsigned int pwm = (intensity < GAMMA_TABLE_SIZE) ? gamma_table[intensity] : LIGHT_PWM_RANGE; +unsigned int _toPWM(unsigned long value, bool gamma, bool reverse) { + value = constrain(value, 0, LIGHT_MAX_VALUE); + value *= ((float) _brightness / LIGHT_MAX_BRIGHTNESS); + #if LIGHT_ENABLE_GAMMA + unsigned int pwm = gamma ? gamma_table[value] : map(value, 0, LIGHT_MAX_VALUE, 0, LIGHT_MAX_PWM); #else - unsigned int pwm = intensity; + unsigned int pwm = map(value, 0, LIGHT_MAX_VALUE, 0, LIGHT_MAX_PWM); #endif - - #if RGBW_INVERSE_LOGIC != 1 - pwm = LIGHT_PWM_RANGE - pwm; - #endif - + if (reverse) pwm = LIGHT_MAX_PWM - pwm; return pwm; - } -unsigned int _intensity2pwm(unsigned int intensity) { - return _intensity2pwm(intensity, LIGHT_MAX_VALUE); +// Returns a PWM valule for the given channel ID +unsigned int _toPWM(unsigned char id) { + if (id < _channels.size()) { + #if LIGHT_ENABLE_GAMMA + bool gamma = (lightHasColor() && id < 3); + #else + bool gamma = false; + #endif + return _toPWM(_channels[id].shadow, gamma, _channels[id].reverse); + } + return 0; } // ----------------------------------------------------------------------------- // PROVIDER // ----------------------------------------------------------------------------- -void _lightProviderSet(bool state, unsigned int red, unsigned int green, unsigned int blue, float brightness) { - - unsigned int white = 0; - - #if (LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB2W) - // If all set to the same value use white instead - if ((red == green) && (green == blue)) { - white = red; - red = green = blue = 0; - } - #endif - - #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192 - _my9291->setState(state); - red *= brightness; - green *= brightness; - blue *= brightness; - white *= brightness; - _my9291->setColor((my9291_color_t) { red, green, blue, white }); - #endif - - #if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB2W) - - // Check state - if (!state) red = green = blue = white = 0; - - analogWrite(RGBW_RED_PIN, _intensity2pwm(red, brightness)); - analogWrite(RGBW_GREEN_PIN, _intensity2pwm(green, brightness)); - analogWrite(RGBW_BLUE_PIN, _intensity2pwm(blue, brightness)); - #if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW) - analogWrite(RGBW_WHITE_PIN, _intensity2pwm(white, brightness)); - #endif - #if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB2W) - analogWrite(RGBW_WHITE_PIN, _intensity2pwm(white, brightness)); - analogWrite(RGBW_WHITE2_PIN, _intensity2pwm(white, brightness)); - #endif - - #endif +void _shadow() { -} + bool useWhite = getSetting("useWhite", LIGHT_USE_WHITE).toInt() == 1; -// ----------------------------------------------------------------------------- -// LIGHT MANAGEMENT -// ----------------------------------------------------------------------------- + for (unsigned int i=0; i < _channels.size(); i++) { + _channels[i].shadow = _lightState ? _channels[i].value : 0; + } -void lightState(bool state) { - _lightState = state; - _lightProviderSet(_lightState, _lightColor[0], _lightColor[1], _lightColor[2], brightness); -} + if (_lightState && useWhite && _channels.size() > 3) { + if (_channels[0].shadow == _channels[1].shadow && _channels[1].shadow == _channels[2].shadow ) { + _channels[3].shadow = _channels[0].shadow; + _channels[2].shadow = 0; + _channels[1].shadow = 0; + _channels[0].shadow = 0; + } + } -bool lightState() { - return _lightState; -} -void parseColor(const char * color) { - brightness = 1.0; - _color_string2array(color, _lightColor); } +void _lightProviderUpdate() { -void lightColor(bool save, bool forward) { + _shadow(); - _lightProviderSet(_lightState, _lightColor[0], _lightColor[1], _lightColor[2], brightness); - - // Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily - if (save) colorTicker.once(LIGHT_SAVE_DELAY, _lightColorSave); - - // Report color & brightness to MQTT broker - if (forward) { - - // Color - char rgb[8]; - _color_array2rgb(_lightColor, 1.0, rgb); - mqttSend(MQTT_TOPIC_COLOR, rgb); + #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192 - if ((_lightColor[0] == _lightColor[1]) & (_lightColor[1] == _lightColor[2])) { + if (_lightState) { - // White - char buffer[5]; - sprintf(buffer, "%d", (int) _lightColor[0]); - mqttSend(MQTT_TOPIC_WHITE, buffer); + float ratio = (float) LIGHT_MAX_VALUE / LIGHT_MAX_PWM; + unsigned int red = _toPWM(0) * ratio; + unsigned int green = _toPWM(1) * ratio; + unsigned int blue = _toPWM(2) * ratio; + unsigned int white = _toPWM(3) * ratio; + _my9291->setColor((my9291_color_t) { red, green, blue, white }); + _my9291->setState(true); } else { - // Brightness - char buffer[5]; - sprintf(buffer, "%d", (int) (brightness * LIGHT_MAX_BRIGHTNESS)); - mqttSend(MQTT_TOPIC_BRIGHTNESS, buffer); + _my9291->setState(false); } - } + #endif - // Report color to WS clients - { - char rgb[8]; - _color_array2rgb(_lightColor, brightness, rgb); - char message[64]; - sprintf(message, "{\"color\": \"%s\"}", rgb); - wsSend(message); - } + #if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER -} + for (unsigned int i=0; i < _channels.size(); i++) { + analogWrite(_channels[i].pin, _toPWM(i)); + } -String lightColor(float b) { - char rgb[8]; - _color_array2rgb(_lightColor, b, rgb); - return String(rgb); -} + #endif -String lightColor() { - return lightColor(brightness); } // ----------------------------------------------------------------------------- @@ -298,29 +262,39 @@ String lightColor() { // ----------------------------------------------------------------------------- void _lightColorSave() { - setSetting("color", lightColor(1.0)); - setSetting("brightness", brightness * LIGHT_MAX_BRIGHTNESS); + for (unsigned int i=0; i < _channels.size(); i++) { + setSetting("ch", i, _channels[i].value); + } + setSetting("brightness", _brightness); saveSettings(); } void _lightColorRestore() { - String color = getSetting("color", LIGHT_DEFAULT_COLOR); - _color_string2array(color.c_str(), _lightColor); - brightness = getSetting("brightness", 1).toFloat() / LIGHT_MAX_BRIGHTNESS; + for (unsigned int i=0; i < _channels.size(); i++) { + _channels[i].value = getSetting("ch", i, 0).toInt(); + } + _brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt(); + lightUpdate(false, false); } // ----------------------------------------------------------------------------- // MQTT // ----------------------------------------------------------------------------- -void lightMQTTCallback(unsigned int type, const char * topic, const char * payload) { +void _lightMQTTCallback(unsigned int type, const char * topic, const char * payload) { if (type == MQTT_CONNECT_EVENT) { + mqttSubscribe(MQTT_TOPIC_BRIGHTNESS); - mqttSubscribe(MQTT_TOPIC_COLORTEMP); + mqttSubscribe(MQTT_TOPIC_MIRED); + mqttSubscribe(MQTT_TOPIC_KELVIN); mqttSubscribe(MQTT_TOPIC_COLOR); - mqttSubscribe(MQTT_TOPIC_WHITE); + + char buffer[strlen(MQTT_TOPIC_CHANNEL) + 3]; + sprintf(buffer, "%s/+", MQTT_TOPIC_CHANNEL); + mqttSubscribe(buffer); + } if (type == MQTT_MESSAGE_EVENT) { @@ -328,86 +302,260 @@ void lightMQTTCallback(unsigned int type, const char * topic, const char * paylo // Match topic String t = mqttSubtopic((char *) topic); - // Color temperature - if (t.equals(MQTT_TOPIC_COLORTEMP)) { - char buffer[10]; - sprintf(buffer, "%sK", payload); - parseColor(buffer); - lightColor(true, mqttForward()); + // Color temperature in mireds + if (t.equals(MQTT_TOPIC_MIRED)) { + _fromMireds(atol(payload)); + lightUpdate(true, mqttForward()); } - // Color - if (t.equals(MQTT_TOPIC_COLOR)) { - parseColor(payload); - lightColor(true, mqttForward()); + // Color temperature in kelvins + if (t.equals(MQTT_TOPIC_KELVIN)) { + _fromKelvin(atol(payload)); + lightUpdate(true, mqttForward()); } - // White - if (t.equals(MQTT_TOPIC_WHITE)) { - parseColor(payload); - lightColor(true, mqttForward()); + // Color + if (t.equals(MQTT_TOPIC_COLOR)) { + lightColor(payload); + lightUpdate(true, mqttForward()); } // Brightness if (t.equals(MQTT_TOPIC_BRIGHTNESS)) { - brightness = (float) atoi(payload) / LIGHT_MAX_BRIGHTNESS; - lightColor(true, mqttForward()); + _brightness = constrain(atoi(payload), 0, LIGHT_MAX_BRIGHTNESS); + lightUpdate(true, mqttForward()); } + // Channel + if (t.startsWith(MQTT_TOPIC_CHANNEL)) { + unsigned int channelID = t.substring(strlen(MQTT_TOPIC_CHANNEL)+1).toInt(); + if (channelID >= _channels.size()) { + DEBUG_MSG_P(PSTR("[LIGHT] Wrong channelID (%d)\n"), channelID); + return; + } + lightChannel(channelID, atoi(payload)); + lightUpdate(true, mqttForward()); + } } } // ----------------------------------------------------------------------------- -// SETUP +// API // ----------------------------------------------------------------------------- -void lightSetup() { +unsigned char lightChannels() { + return _channels.size(); +} - #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192 - _my9291 = new my9291(MY9291_DI_PIN, MY9291_DCKI_PIN, MY9291_COMMAND); - #endif +bool lightHasColor() { + return _channels.size() > 2; +} - #if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW) - analogWriteRange(LIGHT_PWM_RANGE); - analogWriteFreq(LIGHT_PWM_FREQUENCY); - pinMode(RGBW_RED_PIN, OUTPUT); - pinMode(RGBW_GREEN_PIN, OUTPUT); - pinMode(RGBW_BLUE_PIN, OUTPUT); - #if LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW - pinMode(RGBW_WHITE_PIN, OUTPUT); - #endif - #if LIGHT_PROVIDER == LIGHT_PROVIDER_RGB2W - pinMode(RGBW_WHITE_PIN, OUTPUT); - pinMode(RGBW_WHITE2_PIN, OUTPUT); - #endif - #endif +unsigned char lightWhiteChannels() { + return _channels.size() % 3; +} - _lightColorRestore(); +void lightMQTT() { + + char buffer[8]; + + // Color + if (lightHasColor()) { + _toRGB(buffer, 8, false); + mqttSend(MQTT_TOPIC_COLOR, buffer); + } + + // Channels + for (unsigned int i=0; i < _channels.size(); i++) { + sprintf(buffer, "%d", _channels[i].value); + mqttSend(MQTT_TOPIC_CHANNEL, i, buffer); + } + + // Brightness + sprintf(buffer, "%d", _brightness); + mqttSend(MQTT_TOPIC_BRIGHTNESS, buffer); + +} + +void lightUpdate(bool save, bool forward) { + + _lightProviderUpdate(); + + // Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily + if (save) colorTicker.once(LIGHT_SAVE_DELAY, _lightColorSave); + + // Report color & brightness to MQTT broker + if (forward) lightMQTT(); + + // Report color to WS clients (using current brightness setting) + { + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.createObject(); + root["colorVisible"] = 1; + root["color"] = lightColor(); + JsonArray& channels = root.createNestedArray("channels"); + for (unsigned char id=0; id < lightChannels(); id++) { + channels.add(lightChannel(id)); + } + root["brightness"] = lightBrightness(); + String output; + root.printTo(output); + wsSend(output.c_str()); + } + +}; + +void lightState(bool state) { + _lightState = state; +} + +bool lightState() { + return _lightState; +} + +void lightColor(const char * color) { + _fromRGB(color); +} + +String lightColor() { + char rgb[8]; + _toRGB(rgb, 8, false); + return String(rgb); +} + +unsigned int lightChannel(unsigned char id) { + if (id <= _channels.size()) { + return _channels[id].value; + } + return 0; +} + +void lightChannel(unsigned char id, unsigned int value) { + if (id <= _channels.size()) { + _channels[id].value = constrain(value, 0, LIGHT_MAX_VALUE); + } +} + +unsigned int lightBrightness() { + return _brightness; +} + +void lightBrightness(unsigned int b) { + _brightness = constrain(b, 0, LIGHT_MAX_BRIGHTNESS); +} + +// ----------------------------------------------------------------------------- +// SETUP +// ----------------------------------------------------------------------------- + +void _lightAPISetup() { // API entry points (protected with apikey) - apiRegister(MQTT_TOPIC_COLOR, MQTT_TOPIC_COLOR, - [](char * buffer, size_t len) { - snprintf(buffer, len, "%s", lightColor().c_str()); - }, + if (_channels.size() > 2) { + apiRegister(MQTT_TOPIC_COLOR, MQTT_TOPIC_COLOR, + [](char * buffer, size_t len) { + _toRGB(buffer, len, false); + }, + [](const char * payload) { + lightColor(payload); + lightUpdate(true, true); + } + ); + } + + apiRegister(MQTT_TOPIC_KELVIN, MQTT_TOPIC_KELVIN, + [](char * buffer, size_t len) {}, [](const char * payload) { - parseColor(payload); - lightColor(true, true); + _fromKelvin(atol(payload)); + lightUpdate(true, true); + } + ); + + apiRegister(MQTT_TOPIC_MIRED, MQTT_TOPIC_MIRED, + [](char * buffer, size_t len) {}, + [](const char * payload) { + _fromMireds(atol(payload)); + lightUpdate(true, true); } ); apiRegister(MQTT_TOPIC_BRIGHTNESS, MQTT_TOPIC_BRIGHTNESS, [](char * buffer, size_t len) { - snprintf(buffer, len, "%d", (int) (brightness * LIGHT_MAX_BRIGHTNESS)); + snprintf(buffer, len, "%d", _brightness); }, [](const char * payload) { - brightness = (float) atoi(payload) / LIGHT_MAX_BRIGHTNESS; - lightColor(true, true); + lightBrightness(atoi(payload)); + lightUpdate(true, true); } ); - mqttRegister(lightMQTTCallback); + for (unsigned int id=0; id #define AUTO_SAVE 1 +#ifdef DEBUG_PORT +Embedis embedis(DEBUG_PORT); +#else Embedis embedis(Serial); +#endif // ----------------------------------------------------------------------------- // Settings @@ -134,15 +138,60 @@ void settingsSetup() { }); #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE - Embedis::command( F("COLOR"), [](Embedis* e) { - if (e->argc > 1) { - String color = String(e->argv[1]); - parseColor(color.c_str()); - lightColor(true, true); - } - e->stream->printf("Color: %s\n", lightColor().c_str()); - e->response(Embedis::OK); - }); + + Embedis::command( F("COLOR"), [](Embedis* e) { + if (e->argc > 1) { + String color = String(e->argv[1]); + lightColor(color.c_str()); + lightUpdate(true, true); + } + e->stream->printf("Color: %s\n", lightColor().c_str()); + e->response(Embedis::OK); + }); + + Embedis::command( F("MIRED"), [](Embedis* e) { + if (e->argc > 1) { + String color = String("M") + String(e->argv[1]); + lightColor(color.c_str()); + lightUpdate(true, true); + } + e->stream->printf("Color: %s\n", lightColor().c_str()); + e->response(Embedis::OK); + }); + + Embedis::command( F("KELVIN"), [](Embedis* e) { + if (e->argc > 1) { + String color = String("K") + String(e->argv[1]); + lightColor(color.c_str()); + lightUpdate(true, true); + } + e->stream->printf("Color: %s\n", lightColor().c_str()); + e->response(Embedis::OK); + }); + + Embedis::command( F("BRIGHTNESS"), [](Embedis* e) { + if (e->argc > 1) { + lightBrightness(String(e->argv[1]).toInt()); + lightUpdate(true, true); + } + e->stream->printf("Brightness: %d\n", lightBrightness()); + e->response(Embedis::OK); + }); + + Embedis::command( F("CHANNEL"), [](Embedis* e) { + if (e->argc < 2) { + return e->response(Embedis::ARGS_ERROR); + } + int id = String(e->argv[1]).toInt(); + if (e->argc > 2) { + int value = String(e->argv[2]).toInt(); + lightChannel(id, value); + lightUpdate(true, true); + } + e->stream->printf("Channel #%d: %d\n", id, lightChannel(id)); + e->response(Embedis::OK); + }); + #endif Embedis::command( F("EEPROM"), [](Embedis* e) { @@ -176,6 +225,15 @@ void settingsSetup() { } +void settingsDump() { + unsigned int size = settingsKeyCount(); + for (unsigned int i=0; i %s\n"), key.c_str(), value.c_str()); + } +} + void settingsLoop() { embedis.process(); } diff --git a/code/espurna/web.ino b/code/espurna/web.ino index 2f28dbc0..d1d9d3cc 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -145,8 +145,8 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) { #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE if (action.equals("color") && root.containsKey("data")) { - parseColor(root["data"]); - lightColor(true, true); + lightColor(root["data"]); + lightUpdate(true, true); } #endif @@ -364,7 +364,7 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) { #if ENABLE_INFLUXDB influxDBConfigure(); #endif - buildTopics(); + mqttConfigure(); #if ENABLE_RF rfBuildCodes(); @@ -454,6 +454,11 @@ void _wsStart(uint32_t client_id) { #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE root["colorVisible"] = 1; root["color"] = lightColor(); + JsonArray& channels = root.createNestedArray("channels"); + for (unsigned char id=0; id < lightChannels(); id++) { + channels.add(lightChannel(id)); + } + root["brightness"] = lightBrightness(); #endif root["relayMode"] = getSetting("relayMode", RELAY_MODE); @@ -715,8 +720,8 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) { } // Get response from callback - char value[10]; - (api.getFn)(value, 10); + char value[API_BUFFER_SIZE]; + (api.getFn)(value, API_BUFFER_SIZE); char *p = ltrim(value); // The response will be a 404 NOT FOUND if the resource is not available @@ -779,7 +784,7 @@ void _onAPIs(AsyncWebServerRequest *request) { } else { for (unsigned int i=0; i < _apis.size(); i++) { - output += _apis[i].key + String(" -> ") + _apis[i].url + String("\n
"); + output += _apis[i].key + String(" -> ") + _apis[i].url + String("\n"); } request->send(200, "text/plain", output); }