Browse Source

Support for HSV colors

fastled
Xose Pérez 6 years ago
parent
commit
57ac756abd
7 changed files with 3718 additions and 3531 deletions
  1. +5
    -4
      code/espurna/config/general.h
  2. BIN
      code/espurna/data/index.html.gz
  3. +193
    -13
      code/espurna/light.ino
  4. +3462
    -3459
      code/espurna/static/index.html.gz.h
  5. +29
    -33
      code/espurna/web.ino
  6. +19
    -20
      code/html/custom.js
  7. +10
    -2
      code/html/index.html

+ 5
- 4
code/espurna/config/general.h View File

@ -312,7 +312,7 @@ PROGMEM const char* const custom_reset_string[] = {
// This will only be enabled if WEB_SUPPORT is 1 (this is the default value)
#define API_ENABLED 0 // Do not enable API by default
#define API_BUFFER_SIZE 10 // Size of the buffer for HTTP GET API responses
#define API_BUFFER_SIZE 15 // Size of the buffer for HTTP GET API responses
#define API_REAL_TIME_VALUES 0 // Show filtered/median values by default (0 => median, 1 => real time)
// -----------------------------------------------------------------------------
@ -435,9 +435,9 @@ PROGMEM const char* const custom_reset_string[] = {
// Light module
#define MQTT_TOPIC_CHANNEL "channel"
#define MQTT_TOPIC_COLOR "color"
#define MQTT_TOPIC_COLOR_RGB "color_rgb"
#define MQTT_TOPIC_COLOR_HSV "color_hsv"
#define MQTT_TOPIC_COLOR "color" // DEPRECATED, use RGB instead
#define MQTT_TOPIC_COLOR_RGB "rgb"
#define MQTT_TOPIC_COLOR_HSV "hsv"
#define MQTT_TOPIC_ANIM_MODE "anim_mode"
#define MQTT_TOPIC_ANIM_SPEED "anim_speed"
#define MQTT_TOPIC_BRIGHTNESS "brightness"
@ -523,6 +523,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define LIGHT_USE_WHITE 0 // Use white channel whenever RGB have the same value
#define LIGHT_USE_GAMMA 0 // Use gamma correction for color channels
#define LIGHT_USE_CSS 1 // Use CSS style to report colors (1=> "#FF0000", 0=> "255,0,0")
#define LIGHT_USE_RGB 0 // Use RGB color selector (1=> RGB, 0=> HSV)
// -----------------------------------------------------------------------------
// POWER METERING


BIN
code/espurna/data/index.html.gz View File


+ 193
- 13
code/espurna/light.ino View File

@ -156,6 +156,130 @@ 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
// 0 <= V <= 100
void _fromHSV(const char * hsv) {
char * ptr = (char *) hsv;
if (strlen(ptr) == 0) return;
if (!lightHasColor()) return;
char * tok;
unsigned char count = 0;
unsigned int value[3] = {0};
tok = strtok(ptr, ",");
while (tok != NULL) {
value[count] = atoi(tok);
if (++count == 3) break;
tok = strtok(NULL, ",");
}
if (count != 3) return;
// HSV to RGB transformation -----------------------------------------------
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)));
switch (int(h)) {
case 0:
_channels[0].value = v;
_channels[1].value = t;
_channels[2].value = p;
break;
case 1:
_channels[0].value = q;
_channels[1].value = v;
_channels[2].value = p;
break;
case 2:
_channels[0].value = p;
_channels[1].value = v;
_channels[2].value = t;
break;
case 3:
_channels[0].value = p;
_channels[1].value = q;
_channels[2].value = v;
break;
case 4:
_channels[0].value = t;
_channels[1].value = p;
_channels[2].value = v;
break;
case 5:
_channels[0].value = v;
_channels[1].value = p;
_channels[2].value = q;
break;
default:
_channels[0].value = 0;
_channels[1].value = 0;
_channels[2].value = 0;
break;
}
_brightness = LIGHT_MAX_BRIGHTNESS;
}
void _toHSV(char * hsv, size_t len) {
if (!lightHasColor()) return;
double min, max;
double h, s, v;
double r = (double) _channels[0].value / 255.0;
double g = (double) _channels[1].value / 255.0;
double b = (double) _channels[2].value / 255.0;
min = (r < g) ? r : g;
min = (min < b) ? min : b;
max = (r > g) ? r : g;
max = (max > b) ? max : b;
v = 100.0 * max;
if (v == 0) {
h = s = 0;
} else {
s = 100.0 * (max - min) / max;
if (s == 0) {
h = 0;
} else {
if (max == r) {
if (g >= b) {
h = 0.0 + 60.0 * (g - b) / (max - min);
} else {
h = 360.0 + 60.0 * (g - b) / (max - min);
}
} else if (max == g) {
h = 120.0 + 60.0 * (b - r) / (max - min);
} else {
h = 240.0 + 60.0 * (r - g) / (max - min);
}
}
}
// String
snprintf_P(hsv, len, PSTR("%d,%d,%d"), round(h), round(s), round(v));
}
void _toLong(char * color, size_t len, bool applyBrightness) {
if (!lightHasColor()) return;
@ -328,7 +452,9 @@ 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);
mqttSubscribe(MQTT_TOPIC_COLOR); // DEPRECATE
mqttSubscribe(MQTT_TOPIC_COLOR_RGB);
mqttSubscribe(MQTT_TOPIC_COLOR_HSV);
}
char buffer[strlen(MQTT_TOPIC_CHANNEL) + 3];
@ -355,8 +481,12 @@ void _lightMQTTCallback(unsigned int type, const char * topic, const char * payl
}
// Color
if (t.equals(MQTT_TOPIC_COLOR)) {
lightColor(payload);
if (t.equals(MQTT_TOPIC_COLOR) || t.equals(MQTT_TOPIC_COLOR_RGB)) { // DEPRECATE MQTT_TOPIC_COLOR
lightColor(payload, true);
lightUpdate(true, mqttForward());
}
if (t.equals(MQTT_TOPIC_COLOR_HSV)) {
lightColor(payload, false);
lightUpdate(true, mqttForward());
}
@ -406,11 +536,14 @@ void lightMQTT() {
// Color
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, 12, false);
_toRGB(buffer, sizeof(buffer), false);
} else {
_toLong(buffer, 12, false);
_toLong(buffer, sizeof(buffer), false);
}
mqttSend(MQTT_TOPIC_COLOR, buffer);
mqttSend(MQTT_TOPIC_COLOR, buffer); // DEPRECATE
mqttSend(MQTT_TOPIC_COLOR_RGB, buffer);
_toHSV(buffer, sizeof(buffer));
mqttSend(MQTT_TOPIC_COLOR_HSV, buffer);
// Brightness
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _brightness);
@ -443,8 +576,13 @@ void lightUpdate(bool save, bool forward) {
root["useWhite"] = getSetting("useWhite", LIGHT_USE_WHITE).toInt() == 1;
root["useGamma"] = getSetting("useGamma", LIGHT_USE_GAMMA).toInt() == 1;
if (lightHasColor()) {
root["color"] = lightColor();
root["brightness"] = lightBrightness();
bool useRGB = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1;
if (useRGB) {
root["rgb"] = lightColor(true);
root["brightness"] = lightBrightness();
} else {
root["hsv"] = lightColor(false);
}
}
JsonArray& channels = root.createNestedArray("channels");
for (unsigned char id=0; id < lightChannels(); id++) {
@ -477,18 +615,35 @@ bool lightState() {
return _lightState;
}
void lightColor(const char * color, bool rgb) {
DEBUG_MSG_P(PSTR("[LIGHT] %s: %s\n"), rgb ? "RGB" : "HSV", color);
if (rgb) {
_fromRGB(color);
} else {
_fromHSV(color);
}
}
void lightColor(const char * color) {
_fromRGB(color);
lightColor(color, true);
}
void lightColor(unsigned long color) {
_fromLong(color, false);
}
String lightColor(bool rgb) {
char str[12];
if (rgb) {
_toRGB(str, sizeof(str), false);
} else {
_toHSV(str, sizeof(str));
}
return String(str);
}
String lightColor() {
char rgb[8];
_toRGB(rgb, 8, false);
return String(rgb);
return lightColor(true);
}
unsigned int lightChannel(unsigned char id) {
@ -527,6 +682,7 @@ void _lightAPISetup() {
// API entry points (protected with apikey)
if (lightHasColor()) {
// DEPRECATE
apiRegister(MQTT_TOPIC_COLOR, MQTT_TOPIC_COLOR,
[](char * buffer, size_t len) {
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
@ -536,7 +692,31 @@ void _lightAPISetup() {
}
},
[](const char * payload) {
lightColor(payload);
lightColor(payload, true);
lightUpdate(true, true);
}
);
apiRegister(MQTT_TOPIC_COLOR_RGB, 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, MQTT_TOPIC_COLOR_HSV,
[](char * buffer, size_t len) {
_toHSV(buffer, len);
},
[](const char * payload) {
lightColor(payload, false);
lightUpdate(true, true);
}
);


+ 3462
- 3459
code/espurna/static/index.html.gz.h
File diff suppressed because it is too large
View File


+ 29
- 33
code/espurna/web.ino View File

@ -191,31 +191,11 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
#endif
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#ifdef LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
if (lightHasColor()) {
if (action.equals("color_hsv") && root.containsKey("data")) {
JsonObject& data = root["data"];
setLightColor(data["h"],data["s"],data["v"]);
lightUpdate(true, true);
}
}
if (action.equals("anim_mode") && root.containsKey("data")) {
lightAnimMode(root["data"]);
lightUpdate(true, true);
}
if (action.equals("anim_speed") && root.containsKey("data")) {
lightAnimSpeed(root["data"]);
lightUpdate(true, true);
}
#else
if (lightHasColor()) {
if (action.equals("color") && root.containsKey("data")) {
lightColor((const char *) root["data"]);
if (action.equals("rgb") && root.containsKey("data")) {
lightColor((const char *) root["data"], true);
lightUpdate(true, true);
}
@ -224,6 +204,11 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
lightUpdate(true, true);
}
if (action.equals("hsv") && root.containsKey("data")) {
lightColor((const char *) root["data"], false);
lightUpdate(true, true);
}
}
if (action.equals("channel") && root.containsKey("data")) {
@ -233,7 +218,18 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
lightUpdate(true, true);
}
}
#endif //LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
#ifdef LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
if (action.equals("anim_mode") && root.containsKey("data")) {
lightAnimMode(root["data"]);
lightUpdate(true, true);
}
if (action.equals("anim_speed") && root.containsKey("data")) {
lightAnimSpeed(root["data"]);
lightUpdate(true, true);
}
#endif //LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
#endif //LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
};
@ -531,19 +527,19 @@ void _wsStart(uint32_t client_id) {
root["useWhite"] = getSetting("useWhite", LIGHT_USE_WHITE).toInt() == 1;
root["useGamma"] = getSetting("useGamma", LIGHT_USE_GAMMA).toInt() == 1;
root["useCSS"] = getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1;
bool useRGB = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1;
root["useRGB"] = useRGB;
if (lightHasColor()) {
#ifdef LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
if (useRGB) {
root["rgb"] = lightColor(true);
root["brightness"] = lightBrightness();
} else {
root["hsv"] = lightColor(false);
}
#ifdef LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
root["anim_mode"] = lightAnimMode();
root["anim_speed"] = lightAnimSpeed();
JsonObject& color_hsv = root.createNestedObject("color_hsv");
color_hsv["h"] = lightColorH();
color_hsv["s"] = lightColorS();
color_hsv["v"] = lightColorV();
#else
root["color"] = lightColor();
root["brightness"] = lightBrightness();
#endif
#endif // LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
}
JsonArray& channels = root.createNestedArray("channels");
for (unsigned char id=0; id < lightChannels(); id++) {


+ 19
- 20
code/html/custom.js View File

@ -421,14 +421,14 @@ function addNetwork() {
}
function initColor() {
function initColorRGB() {
// check if already initialized
var done = $("#colors > div").length;
if (done > 0) return;
// add template
var template = $("#colorTemplate").children();
var template = $("#colorRGBTemplate").children();
var line = $(template).clone();
line.appendTo("#colors");
@ -437,7 +437,7 @@ function initColor() {
sliders: 'wrgbp'
}).on('sliderup', function() {
var value = $(this).wheelColorPicker('getValue', 'css');
websock.send(JSON.stringify({'action': 'color', 'data' : value}));
websock.send(JSON.stringify({'action': 'rgb', 'data' : value}));
});
// init bright slider
@ -458,14 +458,14 @@ function initColor() {
}
function initColorHsv() {
function initColorHSV() {
// check if already initialized
var done = $("#colors > div").length;
if (done > 0) return;
// add template
var template = $("#colorHsvTemplate").children();
var template = $("#colorHSVTemplate").children();
var line = $(template).clone();
line.appendTo("#colors");
@ -473,12 +473,9 @@ function initColorHsv() {
$('input[name="color"]').wheelColorPicker({
sliders: 'whsvp'
}).on('sliderup', function() {
var color_all = $(this).wheelColorPicker('getColor');
var color_hsv ={};
color_hsv.h =Math.floor(color_all.h * 255);
color_hsv.s =Math.floor(color_all.s * 255);
color_hsv.v =Math.floor(color_all.v * 255);
websock.send(JSON.stringify({'action': 'color_hsv', 'data' : color_hsv}));
var color = $(this).wheelColorPicker('getColor');
var value = parseInt(color.h * 360) + "," + parseInt(color.s * 100) + "," + parseInt(color.v * 100);
websock.send(JSON.stringify({'action': 'hsv', 'data': value}));
});
}
@ -667,19 +664,21 @@ function processData(data) {
return;
}
if (key == "color") {
initColor();
if (key == "rgb") {
initColorRGB();
$("input[name='color']").wheelColorPicker('setValue', data[key], true);
return;
}
if (key == "color_hsv") {
initColorHsv();
var color_hsv ={};
color_hsv.h = data[key]['h'] / 255;
color_hsv.s = data[key]['s'] / 255;
color_hsv.v = data[key]['v'] / 255;
$("input[name='color']").wheelColorPicker('setColor', color_hsv);
if (key == "hsv") {
initColorHSV();
// wheelColorPicker expects HSV to be between 0 and 1 all of them
var chunks = data[key].split(",");
var obj = {};
obj.h = chunks[0] / 360;
obj.s = chunks[1] / 100;
obj.v = chunks[2] / 100;
$("input[name='color']").wheelColorPicker('setColor', obj);
return;
}


+ 10
- 2
code/html/index.html View File

@ -378,6 +378,14 @@
<div class="pure-u-1 pure-u-md-3-4 hint">Use color picker for the first 3 channels as RGB.<br />Will only work if the device has at least 3 dimmable channels.<br />Reload the page to update the web interface.</div>
</div>
<div class="pure-g module module-color">
<div class="pure-u-1 pure-u-sm-1-4"><label for="useRGB">Use RGB picker</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="useRGB" action="reload" tabindex="11" /></div>
<div class="pure-u-0 pure-u-md-1-2">&nbsp;</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Use RGB color picker if enabled (plus brightness), otherwise use HSV (hue-saturation-value) style</div>
</div>
<div class="pure-g module module-color">
<div class="pure-u-1 pure-u-sm-1-4"><label for="useWhite">Use white channel</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="useWhite" action="reload" tabindex="9" /></div>
@ -1007,7 +1015,7 @@
</div>
</div>
<div id="colorTemplate" class="template">
<div id="colorRGBTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4">Color</label>
<input class="pure-u-1 pure-u-sm-1-4" data-wcp-layout="block" name="color" readonly />
@ -1019,7 +1027,7 @@
</div>
</div>
<div id="colorHsvTemplate" class="template">
<div id="colorHSVTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4">Color</label>
<input class="pure-u-1 pure-u-sm-1-4" data-wcp-layout="block" name="color" readonly />


Loading…
Cancel
Save