diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 265a14f6..21ecd0c0 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -181,7 +181,7 @@ void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len); // Lights // ----------------------------------------------------------------------------- -unsigned char lightChannels(); +size_t lightChannels(); void lightState(unsigned char i, bool state); bool lightState(unsigned char i); @@ -189,11 +189,14 @@ bool lightState(unsigned char i); void lightState(bool state); bool lightState(); -void lightBrightness(unsigned int brightness); -unsigned int lightBrightness(); +void lightBrightness(long brightness); +long lightBrightness(); -unsigned int lightChannel(unsigned char id); -void lightChannel(unsigned char id, unsigned char value); +long lightChannel(unsigned char id); +void lightChannel(unsigned char id, long value); + +void lightBrightnessStep(long steps, long multiplier = LIGHT_STEP); +void lightChannelStep(unsigned char id, long steps, long multiplier = LIGHT_STEP); // ----------------------------------------------------------------------------- // MQTT diff --git a/code/espurna/light.h b/code/espurna/light.h index 464a791d..ab48c5f0 100644 --- a/code/espurna/light.h +++ b/code/espurna/light.h @@ -5,22 +5,22 @@ #pragma once namespace Light { - constexpr const unsigned char VALUE_MIN = LIGHT_MIN_VALUE; - constexpr const unsigned char VALUE_MAX = LIGHT_MAX_VALUE; + constexpr const long VALUE_MIN = LIGHT_MIN_VALUE; + constexpr const long VALUE_MAX = LIGHT_MAX_VALUE; - constexpr const unsigned int BRIGHTNESS_MIN = LIGHT_MIN_BRIGHTNESS; - constexpr const unsigned int BRIGHTNESS_MAX = LIGHT_MAX_BRIGHTNESS; + constexpr const long BRIGHTNESS_MIN = LIGHT_MIN_BRIGHTNESS; + constexpr const long BRIGHTNESS_MAX = LIGHT_MAX_BRIGHTNESS; // Default to the Philips Hue value that HA also use. // https://developers.meethue.com/documentation/core-concepts - constexpr const unsigned int MIREDS_COLDWHITE = LIGHT_COLDWHITE_MIRED; - constexpr const unsigned int MIREDS_WARMWHITE = LIGHT_WARMWHITE_MIRED; + constexpr const long MIREDS_COLDWHITE = LIGHT_COLDWHITE_MIRED; + constexpr const long MIREDS_WARMWHITE = LIGHT_WARMWHITE_MIRED; - constexpr const unsigned int KELVIN_WARMWHITE = LIGHT_WARMWHITE_KELVIN; - constexpr const unsigned int KELVIN_COLDWHITE = LIGHT_COLDWHITE_KELVIN; + constexpr const long KELVIN_WARMWHITE = LIGHT_WARMWHITE_KELVIN; + constexpr const long KELVIN_COLDWHITE = LIGHT_COLDWHITE_KELVIN; - constexpr const unsigned int PWM_MIN = LIGHT_MIN_PWM; - constexpr const unsigned int PWM_MAX = LIGHT_MAX_PWM; - constexpr const unsigned int PWM_LIMIT = LIGHT_LIMIT_PWM; + constexpr const long PWM_MIN = LIGHT_MIN_PWM; + constexpr const long PWM_MAX = LIGHT_MAX_PWM; + constexpr const long PWM_LIMIT = LIGHT_LIMIT_PWM; } diff --git a/code/espurna/light.ino b/code/espurna/light.ino index 037d0077..4959511e 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.ino @@ -127,7 +127,7 @@ void _setCCTInputValue(unsigned char warm, unsigned char cold) { _setInputValue(1, constrain(cold, Light::VALUE_MIN, Light::VALUE_MAX)); } -void _lightApplyBrightness(unsigned char channels = lightChannels()) { +void _lightApplyBrightness(size_t channels = lightChannels()) { double brightness = static_cast(_light_brightness) / static_cast(Light::BRIGHTNESS_MAX); @@ -218,7 +218,7 @@ String lightDesc(unsigned char id) { void _fromLong(unsigned long value, bool brightness) { if (brightness) { _setRGBInputValue((value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF); - _light_brightness = (value & 0xFF) * Light::BRIGHTNESS_MAX / 255; + lightBrightness((value & 0xFF) * Light::BRIGHTNESS_MAX / 255); } else { _setRGBInputValue((value >> 16) & 0xFF, (value >> 8) & 0xFF, (value) & 0xFF); } @@ -246,25 +246,21 @@ void _fromRGB(const char * rgb) { default: // assume decimal values separated by commas char * tok; unsigned char count = 0; - unsigned char channels = _light_channel.size(); - tok = strtok(p, ","); + const auto channels = _light_channel.size(); + char buf[16] = {0}; + memmove(buf, rgb, sizeof(buf)); + char *tok = strtok(buf, ","); while (tok != NULL) { _setInputValue(count, atoi(tok)); if (++count == channels) break; tok = strtok(NULL, ","); } - // RGB but less than 3 values received, assume it is 0 - if (_light_has_color && (count < 3)) { - // check channel 1 and 2: - for (int i = 1; i <= 2; i++) { - if (count < (i+1)) { - _setInputValue(i, 0); - } - } - } - break; + // If less than 3 values received, set the rest to 0 + if (count < 2) _setInputValue(1, 0); + if (count < 3) _setInputValue(2, 0); + return; } } @@ -332,25 +328,39 @@ void _fromHSV(const char * hsv) { // Thanks to Sacha Telgenhof for sharing this code in his AiLight library // https://github.com/stelgenhof/AiLight -void _fromKelvin(unsigned long kelvin) { +// Color temperature is measured in mireds (kelvin = 1e6/mired) +long _toKelvin(const long mireds) { + return constrain(static_cast(1000000L / mireds), Light::KELVIN_WARMWHITE, Light::KELVIN_COLDWHITE); +} - if (!_light_has_color) { +long _toMireds(const long kelvin) { + return constrain(static_cast(lround(1000000L / kelvin)), Light::MIREDS_COLDWHITE, Light::MIREDS_WARMWHITE); +} - if(!_light_use_cct) return; +void _lightMireds(const long kelvin) { + _light_mireds = _toMireds(kelvin); +} - _light_mireds = constrain(static_cast(lround(1000000UL / kelvin)), Light::MIREDS_COLDWHITE, Light::MIREDS_WARMWHITE); +void _lightMiredsCCT(const long kelvin) { + _lightMireds(kelvin); - // This change the range from 153-500 to 0-347 so we get a value between 0 and 1 in the end. - double factor = ((double) _light_mireds - (double) Light::MIREDS_COLDWHITE)/((double) Light::MIREDS_WARMWHITE - (double) Light::MIREDS_COLDWHITE); - unsigned char warm = lround(factor * Light::VALUE_MAX); - unsigned char cold = lround(((double) 1.0 - factor) * Light::VALUE_MAX); + // This change the range from 153-500 to 0-347 so we get a value between 0 and 1 in the end. + const double factor = ((double) _light_mireds - (double) Light::MIREDS_COLDWHITE)/((double) Light::MIREDS_WARMWHITE - (double) Light::MIREDS_COLDWHITE); + _setCCTInputValue( + lround(factor * Light::VALUE_MAX), + lround(((double) 1.0 - factor) * Light::VALUE_MAX) + ); +} - _setCCTInputValue(warm, cold); +void _fromKelvin(long kelvin) { - return; + if (!_light_has_color) { + if (!_light_use_cct) return; + _lightMiredsCCT(kelvin); + return; } - _light_mireds = constrain(static_cast(lround(1000000UL / kelvin)), Light::MIREDS_COLDWHITE, Light::MIREDS_WARMWHITE); + _lightMireds(kelvin); if (_light_use_cct) { _setRGBInputValue(Light::VALUE_MAX, Light::VALUE_MAX, Light::VALUE_MAX); @@ -359,13 +369,13 @@ void _fromKelvin(unsigned long kelvin) { // Calculate colors kelvin /= 100; - unsigned int red = (kelvin <= 66) + const unsigned int red = (kelvin <= 66) ? Light::VALUE_MAX : 329.698727446 * fs_pow((double) (kelvin - 60), -0.1332047592); - unsigned int green = (kelvin <= 66) + const unsigned int green = (kelvin <= 66) ? 99.4708025861 * fs_log(kelvin) - 161.1195681661 : 288.1221695283 * fs_pow((double) kelvin, -0.0755148492); - unsigned int blue = (kelvin >= 66) + const unsigned int blue = (kelvin >= 66) ? Light::VALUE_MAX : ((kelvin <= 19) ? 0 @@ -375,10 +385,8 @@ void _fromKelvin(unsigned long kelvin) { } -// Color temperature is measured in mireds (kelvin = 1e6/mired) -void _fromMireds(unsigned long mireds) { - unsigned long kelvin = constrain(static_cast(1000000UL / mireds), Light::KELVIN_WARMWHITE, Light::KELVIN_COLDWHITE); - _fromKelvin(kelvin); +void _fromMireds(const long mireds) { + _fromKelvin(_toKelvin(mireds)); } // ----------------------------------------------------------------------------- @@ -480,6 +488,35 @@ template T _lightMap(T x, Tin in_min, return (x - in_min) * (out_max - out_min) / divisor + out_min; } +int _lightAdjustValue(const int& value, const String& operation) { + if (!operation.length()) return value; + + // if prefixed with a sign, treat expression as numerical operation + // otherwise, use as the new value + int updated = operation.toInt(); + if (operation[0] == '+' || operation[0] == '-') { + updated = value + updated; + } + + return updated; +} + +void _lightAdjustBrightness(const char *payload) { + lightBrightness(_lightAdjustValue(lightBrightness(), payload)); +} + +void _lightAdjustChannel(unsigned char id, const char *payload) { + lightChannel(id, _lightAdjustValue(lightChannel(id), payload)); +} + +void _lightAdjustKelvin(const char *payload) { + _fromKelvin(_lightAdjustValue(_toKelvin(_light_mireds), payload)); +} + +void _lightAdjustMireds(const char *payload) { + _fromMireds(_lightAdjustValue(_light_mireds, payload)); +} + // ----------------------------------------------------------------------------- // PROVIDER // ----------------------------------------------------------------------------- @@ -657,14 +694,14 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl // Color temperature in mireds if (t.equals(MQTT_TOPIC_MIRED)) { - _fromMireds(atol(payload)); + _lightAdjustMireds(payload); lightUpdate(true, mqttForward()); return; } // Color temperature in kelvins if (t.equals(MQTT_TOPIC_KELVIN)) { - _fromKelvin(atol(payload)); + _lightAdjustKelvin(payload); lightUpdate(true, mqttForward()); return; } @@ -683,7 +720,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl // Brightness if (t.equals(MQTT_TOPIC_BRIGHTNESS)) { - lightBrightness(atoi(payload)); + _lightAdjustBrightness(payload); lightUpdate(true, mqttForward()); return; } @@ -701,7 +738,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl DEBUG_MSG_P(PSTR("[LIGHT] Wrong channelID (%d)\n"), channelID); return; } - lightChannel(channelID, atoi(payload)); + _lightAdjustChannel(channelID, payload); lightUpdate(true, mqttForward()); return; } @@ -780,7 +817,7 @@ void lightBroker() { // API // ----------------------------------------------------------------------------- -unsigned char lightChannels() { +size_t lightChannels() { return _light_channel.size(); } @@ -910,33 +947,32 @@ String lightColor() { return lightColor(true); } -unsigned int lightChannel(unsigned char id) { +long lightChannel(unsigned char id) { if (id <= _light_channel.size()) { return _light_channel[id].inputValue; } return 0; } -void lightChannel(unsigned char id, unsigned char value) { - if (id <= _light_channel.size()) { - _setInputValue(id, constrain(value, Light::VALUE_MIN, Light::VALUE_MAX)); - } +void lightChannel(unsigned char id, long value) { + if (id > _light_channel.size()) return; + _setInputValue(id, constrain(value, Light::VALUE_MIN, Light::VALUE_MAX)); } -void lightChannelStep(unsigned char id, int steps) { - lightChannel(id, lightChannel(id) + steps * LIGHT_STEP); +void lightChannelStep(unsigned char id, long steps, long multiplier) { + lightChannel(id, static_cast(lightChannel(id)) + (steps * multiplier)); } -unsigned int lightBrightness() { +long lightBrightness() { return _light_brightness; } -void lightBrightness(unsigned int brightness) { +void lightBrightness(long brightness) { _light_brightness = constrain(brightness, Light::BRIGHTNESS_MIN, Light::BRIGHTNESS_MAX); } -void lightBrightnessStep(int steps) { - lightBrightness(_light_brightness + steps * LIGHT_STEP); +void lightBrightnessStep(long steps, long multiplier) { + lightBrightness(static_cast(_light_brightness) + (steps * multiplier)); } unsigned int lightTransitionTime() { @@ -1032,14 +1068,14 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject if (strcmp(action, "channel") == 0) { if (data.containsKey("id") && data.containsKey("value")) { - lightChannel(data["id"], data["value"]); + lightChannel(data["id"].as(), data["value"].as()); lightUpdate(true, true); } } if (strcmp(action, "brightness") == 0) { if (data.containsKey("value")) { - lightBrightness(data["value"]); + lightBrightness(data["value"].as()); lightUpdate(true, true); } } @@ -1081,7 +1117,7 @@ void _lightAPISetup() { apiRegister(MQTT_TOPIC_KELVIN, [](char * buffer, size_t len) {}, [](const char * payload) { - _fromKelvin(atol(payload)); + _lightAdjustKelvin(payload); lightUpdate(true, true); } ); @@ -1089,7 +1125,7 @@ void _lightAPISetup() { apiRegister(MQTT_TOPIC_MIRED, [](char * buffer, size_t len) {}, [](const char * payload) { - _fromMireds(atol(payload)); + _lightAdjustMireds(payload); lightUpdate(true, true); } ); @@ -1105,7 +1141,7 @@ void _lightAPISetup() { snprintf_P(buffer, len, PSTR("%d"), _light_channel[id].target); }, [id](const char * payload) { - lightChannel(id, atoi(payload)); + _lightAdjustChannel(id, payload); lightUpdate(true, true); } ); @@ -1126,7 +1162,7 @@ void _lightAPISetup() { snprintf_P(buffer, len, PSTR("%d"), _light_brightness); }, [](const char * payload) { - lightBrightness(atoi(payload)); + _lightAdjustBrightness(payload); lightUpdate(true, true); } ); @@ -1141,38 +1177,37 @@ void _lightInitCommands() { terminalRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) { if (e->argc > 1) { - const String value(e->argv[1]); - if( value.length() > 0 ) { - if( value[0] == '+' || value[0] == '-' ) { - lightBrightness(lightBrightness()+String(e->argv[1]).toInt()); - } else { - lightBrightness(String(e->argv[1]).toInt()); - } - lightUpdate(true, true); - } + _lightAdjustBrightness(e->argv[1]); + lightUpdate(true, true); } - DEBUG_MSG_P(PSTR("Brightness: %d\n"), lightBrightness()); + DEBUG_MSG_P(PSTR("Brightness: %u\n"), lightBrightness()); terminalOK(); }); terminalRegisterCommand(F("CHANNEL"), [](Embedis* e) { if (e->argc < 2) { - terminalError(F("Wrong arguments")); + terminalError(F("CHANNEL []")); + return; + } + + const int id = String(e->argv[1]).toInt(); + if (id < 0 || id >= lightChannels()) { + terminalError(F("Channel value out of range")); + return; } - int id = String(e->argv[1]).toInt(); + if (e->argc > 2) { - int value = String(e->argv[2]).toInt(); - lightChannel(id, value); + _lightAdjustChannel(id, e->argv[2]); lightUpdate(true, true); } - DEBUG_MSG_P(PSTR("Channel #%d (%s): %d\n"), id, lightDesc(id).c_str(), lightChannel(id)); + + DEBUG_MSG_P(PSTR("Channel #%u (%s): %d\n"), id, lightDesc(id).c_str(), lightChannel(id)); terminalOK(); }); terminalRegisterCommand(F("COLOR"), [](Embedis* e) { if (e->argc > 1) { - String color = String(e->argv[1]); - lightColor(color.c_str()); + lightColor(e->argv[1]); lightUpdate(true, true); } DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); @@ -1181,8 +1216,7 @@ void _lightInitCommands() { terminalRegisterCommand(F("KELVIN"), [](Embedis* e) { if (e->argc > 1) { - String color = String("K") + String(e->argv[1]); - lightColor(color.c_str()); + _lightAdjustKelvin(e->argv[1]); lightUpdate(true, true); } DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); @@ -1191,17 +1225,8 @@ void _lightInitCommands() { terminalRegisterCommand(F("MIRED"), [](Embedis* e) { if (e->argc > 1) { - const String value(e->argv[1]); - String color = String("M"); - if( value.length() > 0 ) { - if( value[0] == '+' || value[0] == '-' ) { - color += String(_light_mireds + String(e->argv[1]).toInt()); - } else { - color += String(e->argv[1]); - } - lightColor(color.c_str()); - lightUpdate(true, true); - } + _lightAdjustMireds(e->argv[1]); + lightUpdate(true, true); } DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); terminalOK();