Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

280 lines
7.7 KiB

/*
LIGHT MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#include <Ticker.h>
Ticker colorTicker;
bool _lightState = false;
unsigned int _lightColor[3] = {0};
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
#include <my9291.h>
my9291 * _my9291;
#endif
#ifndef LIGHT_PWM_FREQUENCY
#define LIGHT_PWM_FREQUENCY (1000)
#endif
#ifndef LIGHT_PWM_RANGE
#define LIGHT_PWM_RANGE (255)
#endif
// -----------------------------------------------------------------------------
// UTILS
// -----------------------------------------------------------------------------
void color_string2array(const char * rgb, unsigned int * array) {
char * p = (char *) rgb;
if (strlen(p) == 0) return;
// if color begins with a # then assume HEX RGB
if (p[0] == '#') {
++p;
unsigned long value = strtol(p, NULL, 16);
array[0] = (value >> 16) & 0xFF;
array[1] = (value >> 8) & 0xFF;
array[2] = (value) & 0xFF;
// it's a temperature
} else if (p[strlen(p)-1] == 'K') {
p[strlen(p)-1] = 0;
unsigned int temperature = atoi(p);
color_temperature2array(temperature, array);
// otherwise assume decimal values separated by commas
} else {
char * tok;
tok = strtok(p, ",");
array[0] = atoi(tok);
tok = strtok(NULL, ",");
// if there are more than one value assume R,G,B
if (tok != NULL) {
array[1] = atoi(tok);
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];
}
}
}
void color_array2rgb(unsigned int * array, char * rgb) {
unsigned long value = array[0];
value = (value << 8) + array[1];
value = (value << 8) + array[2];
sprintf(rgb, "#%06X", value);
}
// Thanks to Sacha Telgenhof for sharing this code in his AiLight library
// https://github.com/stelgenhof/AiLight
void color_temperature2array(unsigned int temperature, unsigned int * array) {
// Force boundaries and conversion
temperature = constrain(temperature, 1000, 40000) / 100;
// Calculate colors
unsigned int red = (temperature <= 66)
? LIGHT_MAX_VALUE
: 329.698727446 * pow((temperature - 60), -0.1332047592);
unsigned int green = (temperature <= 66)
? 99.4708025861 * log(temperature) - 161.1195681661
: 288.1221695283 * pow(temperature, -0.0755148492);
unsigned int blue = (temperature >= 66)
? LIGHT_MAX_VALUE
: ((temperature <= 19)
? 0
: 138.5177312231 * log(temperature - 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);
}
// Converts a color intensity value (0..255) to a pwm value
// This takes care of positive or negative logic
unsigned int intensity2pwm(unsigned int intensity)
{
unsigned int pwm;
// Support integer multiples of 256 (-1) for the LIGHT_PWM_RANGE
// The divide should happen at compile time
pwm = intensity * ((LIGHT_PWM_RANGE+1) / (LIGHT_MAX_VALUE+1));
#if RGBW_INVERSE_LOGIC != 1
pwm = LIGHT_PWM_RANGE - pwm;
#endif
return pwm;
}
// -----------------------------------------------------------------------------
// PROVIDER
// -----------------------------------------------------------------------------
void _lightProviderSet(bool state, unsigned int red, unsigned int green, unsigned int blue) {
unsigned int white = 0;
#if (LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW)
// 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);
_my9291->setColor((my9291_color_t) { red, green, blue, white });
#endif
#if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW)
// Check state
if (!state) red = green = blue = white = 0;
analogWrite(RGBW_RED_PIN, intensity2pwm(red));
analogWrite(RGBW_GREEN_PIN, intensity2pwm(green));
analogWrite(RGBW_BLUE_PIN, intensity2pwm(blue));
#if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW)
analogWrite(RGBW_WHITE_PIN, intensity2pwm(white));
#endif
#endif
}
// -----------------------------------------------------------------------------
// LIGHT MANAGEMENT
// -----------------------------------------------------------------------------
void lightState(bool state) {
_lightState = state;
_lightProviderSet(_lightState, _lightColor[0], _lightColor[1], _lightColor[2]);
}
bool lightState() {
return _lightState;
}
void lightColor(const char * color, bool save, bool forward) {
color_string2array(color, _lightColor);
_lightProviderSet(_lightState, _lightColor[0], _lightColor[1], _lightColor[2]);
char rgb[8];
color_array2rgb(_lightColor, rgb);
// Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily
if (save) colorTicker.once(LIGHT_SAVE_DELAY, _lightColorSave);
// Report color to MQTT broker
if (forward) mqttSend(MQTT_TOPIC_COLOR, rgb);
// Report color to WS clients
char message[20];
sprintf(message, "{\"color\": \"%s\"}", rgb);
wsSend(message);
}
String lightColor() {
char rgb[8];
color_array2rgb(_lightColor, rgb);
return String(rgb);
}
// -----------------------------------------------------------------------------
// PERSISTANCE
// -----------------------------------------------------------------------------
void _lightColorSave() {
setSetting("color", lightColor());
saveSettings();
}
void _lightColorRestore() {
String color = getSetting("color", LIGHT_DEFAULT_COLOR);
color_string2array(color.c_str(), _lightColor);
}
// -----------------------------------------------------------------------------
// MQTT
// -----------------------------------------------------------------------------
void lightMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_COLOR);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttSubtopic((char *) topic);
if (!t.equals(MQTT_TOPIC_COLOR)) return;
lightColor(payload, true, mqttForward());
}
}
// -----------------------------------------------------------------------------
// SETUP
// -----------------------------------------------------------------------------
void lightSetup() {
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
_my9291 = new my9291(MY9291_DI_PIN, MY9291_DCKI_PIN, MY9291_COMMAND);
#endif
#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
#endif
_lightColorRestore();
// 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());
},
[](const char * payload) {
lightColor(payload, true, mqttForward());
}
);
mqttRegister(lightMQTTCallback);
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE