Browse Source

lights: switch to signed arithmetic, introduce adjustment and update step functions

master
Max Prokhorov 5 years ago
parent
commit
559c75d3c7
3 changed files with 128 additions and 100 deletions
  1. +8
    -5
      code/espurna/config/prototypes.h
  2. +11
    -11
      code/espurna/light.h
  3. +109
    -84
      code/espurna/light.ino

+ 8
- 5
code/espurna/config/prototypes.h View File

@ -181,7 +181,7 @@ void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len);
// Lights // Lights
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
unsigned char lightChannels();
size_t lightChannels();
void lightState(unsigned char i, bool state); void lightState(unsigned char i, bool state);
bool lightState(unsigned char i); bool lightState(unsigned char i);
@ -189,11 +189,14 @@ bool lightState(unsigned char i);
void lightState(bool state); void lightState(bool state);
bool lightState(); 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 // MQTT


+ 11
- 11
code/espurna/light.h View File

@ -5,22 +5,22 @@
#pragma once #pragma once
namespace Light { 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. // Default to the Philips Hue value that HA also use.
// https://developers.meethue.com/documentation/core-concepts // 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;
} }

+ 109
- 84
code/espurna/light.ino View File

@ -127,7 +127,7 @@ void _setCCTInputValue(unsigned char warm, unsigned char cold) {
_setInputValue(1, constrain(cold, Light::VALUE_MIN, Light::VALUE_MAX)); _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<double>(_light_brightness) / static_cast<double>(Light::BRIGHTNESS_MAX); double brightness = static_cast<double>(_light_brightness) / static_cast<double>(Light::BRIGHTNESS_MAX);
@ -218,7 +218,7 @@ String lightDesc(unsigned char id) {
void _fromLong(unsigned long value, bool brightness) { void _fromLong(unsigned long value, bool brightness) {
if (brightness) { if (brightness) {
_setRGBInputValue((value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF); _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 { } else {
_setRGBInputValue((value >> 16) & 0xFF, (value >> 8) & 0xFF, (value) & 0xFF); _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 default: // assume decimal values separated by commas
char * tok; char * tok;
unsigned char count = 0; 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) { while (tok != NULL) {
_setInputValue(count, atoi(tok)); _setInputValue(count, atoi(tok));
if (++count == channels) break; if (++count == channels) break;
tok = strtok(NULL, ","); 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 // Thanks to Sacha Telgenhof for sharing this code in his AiLight library
// https://github.com/stelgenhof/AiLight // 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<long>(1000000L / mireds), Light::KELVIN_WARMWHITE, Light::KELVIN_COLDWHITE);
}
if (!_light_has_color) {
long _toMireds(const long kelvin) {
return constrain(static_cast<long>(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<unsigned int>(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<unsigned int>(lround(1000000UL / kelvin)), Light::MIREDS_COLDWHITE, Light::MIREDS_WARMWHITE);
_lightMireds(kelvin);
if (_light_use_cct) { if (_light_use_cct) {
_setRGBInputValue(Light::VALUE_MAX, Light::VALUE_MAX, Light::VALUE_MAX); _setRGBInputValue(Light::VALUE_MAX, Light::VALUE_MAX, Light::VALUE_MAX);
@ -359,13 +369,13 @@ void _fromKelvin(unsigned long kelvin) {
// Calculate colors // Calculate colors
kelvin /= 100; kelvin /= 100;
unsigned int red = (kelvin <= 66)
const unsigned int red = (kelvin <= 66)
? Light::VALUE_MAX ? Light::VALUE_MAX
: 329.698727446 * fs_pow((double) (kelvin - 60), -0.1332047592); : 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 ? 99.4708025861 * fs_log(kelvin) - 161.1195681661
: 288.1221695283 * fs_pow((double) kelvin, -0.0755148492); : 288.1221695283 * fs_pow((double) kelvin, -0.0755148492);
unsigned int blue = (kelvin >= 66)
const unsigned int blue = (kelvin >= 66)
? Light::VALUE_MAX ? Light::VALUE_MAX
: ((kelvin <= 19) : ((kelvin <= 19)
? 0 ? 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<unsigned int>(1000000UL / mireds), Light::KELVIN_WARMWHITE, Light::KELVIN_COLDWHITE);
_fromKelvin(kelvin);
void _fromMireds(const long mireds) {
_fromKelvin(_toKelvin(mireds));
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -480,6 +488,35 @@ template <typename T, typename Tin, typename Tout> T _lightMap(T x, Tin in_min,
return (x - in_min) * (out_max - out_min) / divisor + out_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 // PROVIDER
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -657,14 +694,14 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
// Color temperature in mireds // Color temperature in mireds
if (t.equals(MQTT_TOPIC_MIRED)) { if (t.equals(MQTT_TOPIC_MIRED)) {
_fromMireds(atol(payload));
_lightAdjustMireds(payload);
lightUpdate(true, mqttForward()); lightUpdate(true, mqttForward());
return; return;
} }
// Color temperature in kelvins // Color temperature in kelvins
if (t.equals(MQTT_TOPIC_KELVIN)) { if (t.equals(MQTT_TOPIC_KELVIN)) {
_fromKelvin(atol(payload));
_lightAdjustKelvin(payload);
lightUpdate(true, mqttForward()); lightUpdate(true, mqttForward());
return; return;
} }
@ -683,7 +720,7 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
// Brightness // Brightness
if (t.equals(MQTT_TOPIC_BRIGHTNESS)) { if (t.equals(MQTT_TOPIC_BRIGHTNESS)) {
lightBrightness(atoi(payload));
_lightAdjustBrightness(payload);
lightUpdate(true, mqttForward()); lightUpdate(true, mqttForward());
return; 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); DEBUG_MSG_P(PSTR("[LIGHT] Wrong channelID (%d)\n"), channelID);
return; return;
} }
lightChannel(channelID, atoi(payload));
_lightAdjustChannel(channelID, payload);
lightUpdate(true, mqttForward()); lightUpdate(true, mqttForward());
return; return;
} }
@ -780,7 +817,7 @@ void lightBroker() {
// API // API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
unsigned char lightChannels() {
size_t lightChannels() {
return _light_channel.size(); return _light_channel.size();
} }
@ -910,33 +947,32 @@ String lightColor() {
return lightColor(true); return lightColor(true);
} }
unsigned int lightChannel(unsigned char id) {
long lightChannel(unsigned char id) {
if (id <= _light_channel.size()) { if (id <= _light_channel.size()) {
return _light_channel[id].inputValue; return _light_channel[id].inputValue;
} }
return 0; 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<int>(lightChannel(id)) + (steps * multiplier));
} }
unsigned int lightBrightness() {
long lightBrightness() {
return _light_brightness; return _light_brightness;
} }
void lightBrightness(unsigned int brightness) {
void lightBrightness(long brightness) {
_light_brightness = constrain(brightness, Light::BRIGHTNESS_MIN, Light::BRIGHTNESS_MAX); _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<int>(_light_brightness) + (steps * multiplier));
} }
unsigned int lightTransitionTime() { unsigned int lightTransitionTime() {
@ -1032,14 +1068,14 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
if (strcmp(action, "channel") == 0) { if (strcmp(action, "channel") == 0) {
if (data.containsKey("id") && data.containsKey("value")) { if (data.containsKey("id") && data.containsKey("value")) {
lightChannel(data["id"], data["value"]);
lightChannel(data["id"].as<unsigned char>(), data["value"].as<int>());
lightUpdate(true, true); lightUpdate(true, true);
} }
} }
if (strcmp(action, "brightness") == 0) { if (strcmp(action, "brightness") == 0) {
if (data.containsKey("value")) { if (data.containsKey("value")) {
lightBrightness(data["value"]);
lightBrightness(data["value"].as<int>());
lightUpdate(true, true); lightUpdate(true, true);
} }
} }
@ -1081,7 +1117,7 @@ void _lightAPISetup() {
apiRegister(MQTT_TOPIC_KELVIN, apiRegister(MQTT_TOPIC_KELVIN,
[](char * buffer, size_t len) {}, [](char * buffer, size_t len) {},
[](const char * payload) { [](const char * payload) {
_fromKelvin(atol(payload));
_lightAdjustKelvin(payload);
lightUpdate(true, true); lightUpdate(true, true);
} }
); );
@ -1089,7 +1125,7 @@ void _lightAPISetup() {
apiRegister(MQTT_TOPIC_MIRED, apiRegister(MQTT_TOPIC_MIRED,
[](char * buffer, size_t len) {}, [](char * buffer, size_t len) {},
[](const char * payload) { [](const char * payload) {
_fromMireds(atol(payload));
_lightAdjustMireds(payload);
lightUpdate(true, true); lightUpdate(true, true);
} }
); );
@ -1105,7 +1141,7 @@ void _lightAPISetup() {
snprintf_P(buffer, len, PSTR("%d"), _light_channel[id].target); snprintf_P(buffer, len, PSTR("%d"), _light_channel[id].target);
}, },
[id](const char * payload) { [id](const char * payload) {
lightChannel(id, atoi(payload));
_lightAdjustChannel(id, payload);
lightUpdate(true, true); lightUpdate(true, true);
} }
); );
@ -1126,7 +1162,7 @@ void _lightAPISetup() {
snprintf_P(buffer, len, PSTR("%d"), _light_brightness); snprintf_P(buffer, len, PSTR("%d"), _light_brightness);
}, },
[](const char * payload) { [](const char * payload) {
lightBrightness(atoi(payload));
_lightAdjustBrightness(payload);
lightUpdate(true, true); lightUpdate(true, true);
} }
); );
@ -1141,38 +1177,37 @@ void _lightInitCommands() {
terminalRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) { terminalRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) {
if (e->argc > 1) { 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(); terminalOK();
}); });
terminalRegisterCommand(F("CHANNEL"), [](Embedis* e) { terminalRegisterCommand(F("CHANNEL"), [](Embedis* e) {
if (e->argc < 2) { if (e->argc < 2) {
terminalError(F("Wrong arguments"));
terminalError(F("CHANNEL <ID> [<VALUE>]"));
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) { if (e->argc > 2) {
int value = String(e->argv[2]).toInt();
lightChannel(id, value);
_lightAdjustChannel(id, e->argv[2]);
lightUpdate(true, true); 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(); terminalOK();
}); });
terminalRegisterCommand(F("COLOR"), [](Embedis* e) { terminalRegisterCommand(F("COLOR"), [](Embedis* e) {
if (e->argc > 1) { if (e->argc > 1) {
String color = String(e->argv[1]);
lightColor(color.c_str());
lightColor(e->argv[1]);
lightUpdate(true, true); lightUpdate(true, true);
} }
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
@ -1181,8 +1216,7 @@ void _lightInitCommands() {
terminalRegisterCommand(F("KELVIN"), [](Embedis* e) { terminalRegisterCommand(F("KELVIN"), [](Embedis* e) {
if (e->argc > 1) { if (e->argc > 1) {
String color = String("K") + String(e->argv[1]);
lightColor(color.c_str());
_lightAdjustKelvin(e->argv[1]);
lightUpdate(true, true); lightUpdate(true, true);
} }
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
@ -1191,17 +1225,8 @@ void _lightInitCommands() {
terminalRegisterCommand(F("MIRED"), [](Embedis* e) { terminalRegisterCommand(F("MIRED"), [](Embedis* e) {
if (e->argc > 1) { 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()); DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
terminalOK(); terminalOK();


Loading…
Cancel
Save