CCT (color white balance support)

This commit is contained in:
Blaz Kristan 2021-10-20 20:29:13 +02:00
parent 00f1b483eb
commit 39b7b3ad53
10 changed files with 251 additions and 113 deletions

View File

@ -247,7 +247,7 @@ class WS2812FX {
// segment parameters // segment parameters
public: public:
typedef struct Segment { // 29 (32 in memory?) bytes typedef struct Segment { // 30 (33 in memory?) bytes
uint16_t start; uint16_t start;
uint16_t stop; //segment invalid if stop == 0 uint16_t stop; //segment invalid if stop == 0
uint16_t offset; uint16_t offset;
@ -259,6 +259,7 @@ class WS2812FX {
uint8_t grouping, spacing; uint8_t grouping, spacing;
uint8_t opacity; uint8_t opacity;
uint32_t colors[NUM_COLORS]; uint32_t colors[NUM_COLORS];
uint8_t cct;
char *name; char *name;
bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed
if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false; if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false;

View File

@ -220,6 +220,17 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
uint16_t realIndex = realPixelIndex(i); uint16_t realIndex = realPixelIndex(i);
uint16_t len = SEGMENT.length(); uint16_t len = SEGMENT.length();
// determine if we can do white balance
int16_t cct = -1;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (bus == nullptr || !bus->containsPixel(realIndex)) continue;
if (allowCCT || bus->getType() == TYPE_ANALOG_2CH || bus->getType() == TYPE_ANALOG_5CH) {
cct = SEGMENT.cct;
break;
}
}
for (uint16_t j = 0; j < SEGMENT.grouping; j++) { for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j); uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j);
if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) { if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
@ -230,14 +241,14 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
if (indexMir >= SEGMENT.stop) indexMir -= len; if (indexMir >= SEGMENT.stop) indexMir -= len;
if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir]; if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir];
busses.setPixelColor(indexMir, col); busses.setPixelColor(indexMir, col, cct);
} }
/* offset/phase */ /* offset/phase */
indexSet += SEGMENT.offset; indexSet += SEGMENT.offset;
if (indexSet >= SEGMENT.stop) indexSet -= len; if (indexSet >= SEGMENT.stop) indexSet -= len;
if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet];
busses.setPixelColor(indexSet, col); busses.setPixelColor(indexSet, col, cct);
} }
} }
} else { //live data, etc. } else { //live data, etc.
@ -623,6 +634,7 @@ void WS2812FX::resetSegments() {
_segments[0].setOption(SEG_OPTION_SELECTED, 1); _segments[0].setOption(SEG_OPTION_SELECTED, 1);
_segments[0].setOption(SEG_OPTION_ON, 1); _segments[0].setOption(SEG_OPTION_ON, 1);
_segments[0].opacity = 255; _segments[0].opacity = 255;
_segments[0].cct = 128;
for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++)
{ {
@ -630,6 +642,7 @@ void WS2812FX::resetSegments() {
_segments[i].grouping = 1; _segments[i].grouping = 1;
_segments[i].setOption(SEG_OPTION_ON, 1); _segments[i].setOption(SEG_OPTION_ON, 1);
_segments[i].opacity = 255; _segments[i].opacity = 255;
_segments[i].cct = 128;
_segments[i].speed = DEFAULT_SPEED; _segments[i].speed = DEFAULT_SPEED;
_segments[i].intensity = DEFAULT_INTENSITY; _segments[i].intensity = DEFAULT_INTENSITY;
_segment_runtimes[i].reset(); _segment_runtimes[i].reset();

View File

@ -10,6 +10,9 @@
#include "bus_wrapper.h" #include "bus_wrapper.h"
#include <Arduino.h> #include <Arduino.h>
//color.cpp
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
// enable additional debug output // enable additional debug output
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
#ifndef ESP8266 #ifndef ESP8266
@ -65,79 +68,48 @@ struct BusConfig {
//parent class of BusDigital and BusPwm //parent class of BusDigital and BusPwm
class Bus { class Bus {
public: public:
Bus(uint8_t type, uint16_t start) { Bus(uint8_t type, uint16_t start) {
_type = type; _type = type;
_start = start; _start = start;
}; };
virtual void show() {} virtual ~Bus() {} //throw the bus under the bus
virtual bool canShow() { return true; }
virtual void setPixelColor(uint16_t pix, uint32_t c) {}; virtual void show() {}
virtual bool canShow() { return true; }
virtual void setPixelColor(uint16_t pix, uint32_t c) {};
virtual void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {};
virtual uint32_t getPixelColor(uint16_t pix) { return 0; };
virtual void setBrightness(uint8_t b) {};
virtual void cleanup() {};
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
virtual uint16_t getLength() { return 1; }
virtual void setColorOrder() {}
virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
virtual uint8_t skippedLeds() { return 0; }
virtual void setBrightness(uint8_t b) {}; inline uint16_t getStart() { return _start; }
inline void setStart(uint16_t start) { _start = start; }
inline uint8_t getType() { return _type; }
inline bool isOk() { return _valid; }
inline bool isOffRefreshRequired() { return _needsRefresh; }
inline bool containsPixel(uint16_t pix) { return pix >= _start; }
virtual uint32_t getPixelColor(uint16_t pix) { return 0; }; virtual bool isRgbw() { return false; }
static bool isRgbw(uint8_t type) {
if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
return false;
}
virtual void cleanup() {}; bool reversed = false;
virtual ~Bus() { //throw the bus under the bus
}
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
inline uint16_t getStart() {
return _start;
}
inline void setStart(uint16_t start) {
_start = start;
}
virtual uint16_t getLength() {
return 1;
}
virtual void setColorOrder() {}
virtual uint8_t getColorOrder() {
return COL_ORDER_RGB;
}
virtual bool isRgbw() {
return false;
}
virtual uint8_t skippedLeds() {
return 0;
}
inline uint8_t getType() {
return _type;
}
inline bool isOk() {
return _valid;
}
static bool isRgbw(uint8_t type) {
if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
return false;
}
inline bool isOffRefreshRequired() {
return _needsRefresh;
}
bool reversed = false;
protected: protected:
uint8_t _type = TYPE_NONE; uint8_t _type = TYPE_NONE;
uint8_t _bri = 255; uint8_t _bri = 255;
uint16_t _start = 0; uint16_t _start = 0;
bool _valid = false; bool _valid = false;
bool _needsRefresh = false; bool _needsRefresh = false;
}; };
@ -190,6 +162,11 @@ class BusDigital : public Bus {
PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder); PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder);
} }
void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {
c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT
setPixelColor(pix, c);
}
uint32_t getPixelColor(uint16_t pix) { uint32_t getPixelColor(uint16_t pix) {
if (reversed) pix = _len - pix -1; if (reversed) pix = _len - pix -1;
else pix += _skip; else pix += _skip;
@ -285,6 +262,34 @@ class BusPwm : public Bus {
_valid = true; _valid = true;
}; };
void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {
if (pix != 0 || !_valid) return; //only react to first pixel
c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT (w remains unchanged)
uint8_t r = c >> 16;
uint8_t g = c >> 8;
uint8_t b = c ;
uint8_t w = c >> 24;
switch (_type) {
case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value
_data[0] = max(r, max(g, max(b, w)));
break;
case TYPE_ANALOG_2CH: //warm white + cold white
// perhaps a non-linear adjustment would be in order. need to test
_data[1] = (w * cct) / 255;
_data[0] = 255 - _data[1]; // or (w * (255-cct)) / 255;
break;
case TYPE_ANALOG_5CH: //RGB + warm white + cold white
// perhaps a non-linear adjustment would be in order. need to test
_data[4] = (w * cct) / 255; w = 255 - w; // or (w * (255-cct)) / 255;
case TYPE_ANALOG_4CH: //RGBW
_data[3] = w;
case TYPE_ANALOG_3CH: //standard dumb RGB
_data[0] = r; _data[1] = g; _data[2] = b;
break;
}
}
void setPixelColor(uint16_t pix, uint32_t c) { void setPixelColor(uint16_t pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel if (pix != 0 || !_valid) return; //only react to first pixel
uint8_t r = c >> 16; uint8_t r = c >> 16;
@ -295,14 +300,11 @@ class BusPwm : public Bus {
switch (_type) { switch (_type) {
case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value
_data[0] = max(r, max(g, max(b, w))); break; _data[0] = max(r, max(g, max(b, w))); break;
case TYPE_ANALOG_2CH: //warm white + cold white
case TYPE_ANALOG_2CH: //warm white + cold white, we'll need some nice handling here, for now just R+G channels
case TYPE_ANALOG_3CH: //standard dumb RGB case TYPE_ANALOG_3CH: //standard dumb RGB
case TYPE_ANALOG_4CH: //RGBW case TYPE_ANALOG_4CH: //standard dumb RGBW
case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB
_data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = 0; break; _data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = 0; break;
default: return;
} }
} }
@ -417,6 +419,11 @@ class BusNetwork : public Bus {
if (_rgbw) _data[offset+3] = 0xFF & (c >> 24); if (_rgbw) _data[offset+3] = 0xFF & (c >> 24);
} }
void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {
c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT
setPixelColor(pix, c);
}
uint32_t getPixelColor(uint16_t pix) { uint32_t getPixelColor(uint16_t pix) {
if (!_valid || pix >= _len) return 0; if (!_valid || pix >= _len) return 0;
uint16_t offset = pix * _UDPchannels; uint16_t offset = pix * _UDPchannels;
@ -538,12 +545,13 @@ class BusManager {
} }
} }
void setPixelColor(uint16_t pix, uint32_t c) { void setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1) {
for (uint8_t i = 0; i < numBusses; i++) { for (uint8_t i = 0; i < numBusses; i++) {
Bus* b = busses[i]; Bus* b = busses[i];
uint16_t bstart = b->getStart(); uint16_t bstart = b->getStart();
if (pix < bstart || pix >= bstart + b->getLength()) continue; if (pix < bstart || pix >= bstart + b->getLength()) continue;
busses[i]->setPixelColor(pix - bstart, c); if (cct<0) busses[i]->setPixelColor(pix - bstart, c); // no white balance
else busses[i]->setPixelColor(pix - bstart, c, cct); // do white balance
} }
} }

View File

@ -84,6 +84,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
CJSON(strip.rgbwMode, hw_led[F("rgbwm")]); CJSON(strip.rgbwMode, hw_led[F("rgbwm")]);
CJSON(allowCCT, hw_led["cct"]);
JsonArray ins = hw_led["ins"]; JsonArray ins = hw_led["ins"];
@ -530,6 +531,7 @@ void serializeConfig() {
hw_led[F("maxpwr")] = strip.ablMilliampsMax; hw_led[F("maxpwr")] = strip.ablMilliampsMax;
hw_led[F("ledma")] = strip.milliampsPerLed; hw_led[F("ledma")] = strip.milliampsPerLed;
hw_led[F("rgbwm")] = strip.rgbwMode; hw_led[F("rgbwm")] = strip.rgbwMode;
hw_led["cct"] = allowCCT;
JsonArray hw_led_ins = hw_led.createNestedArray("ins"); JsonArray hw_led_ins = hw_led.createNestedArray("ins");
@ -546,7 +548,7 @@ void serializeConfig() {
ins[F("order")] = bus->getColorOrder(); ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->reversed; ins["rev"] = bus->reversed;
ins[F("skip")] = bus->skippedLeds(); ins[F("skip")] = bus->skippedLeds();
ins["type"] = bus->getType() & 0x7F;; ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired(); ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbw")] = bus->isRgbw(); ins[F("rgbw")] = bus->isRgbw();
} }

View File

@ -467,6 +467,22 @@ img {
z-index: -1; z-index: -1;
} }
#rwrap .sliderdisplay,
#gwrap .sliderdisplay,
#bwrap .sliderdisplay,
#wwrap .sliderdisplay,
#wbal .sliderdisplay {
height: 28px;
top: 0; bottom: 0;
left: 0; right: 0;
/*border: 1px solid var(--c-b);*/
}
#rwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #f00); }
#gwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #0f0); }
#bwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #00f); }
#wwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #fff); }
#wbal .sliderdisplay { background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #d4e0ff); }
.sliderbubble { .sliderbubble {
width: 36px; width: 36px;
line-height: 24px; line-height: 24px;
@ -492,6 +508,14 @@ input[type=range] {
background-color: transparent; background-color: transparent;
cursor: pointer; cursor: pointer;
} }
#rwrap input[type=range],
#gwrap input[type=range],
#bwrap input[type=range],
#wwrap input[type=range],
#wbal input[type=range] {
width: 252px;
margin: 0;
}
input[type=range]:focus { input[type=range]:focus {
outline: none; outline: none;
} }
@ -527,8 +551,24 @@ input[type=range]:active + .sliderbubble {
display: inline; display: inline;
transform: translateX(-50%); transform: translateX(-50%);
} }
#rwrap input[type=range]::-webkit-slider-thumb,
#wwrap { #gwrap input[type=range]::-webkit-slider-thumb,
#bwrap input[type=range]::-webkit-slider-thumb,
#wwrap input[type=range]::-webkit-slider-thumb,
#wbal input[type=range]::-webkit-slider-thumb {
height: 18px;
width: 18px;
border: 2px solid #000;
margin-top: 5px;
}
#rwrap input[type=range]::-moz-range-thumb,
#gwrap input[type=range]::-moz-range-thumb,
#bwrap input[type=range]::-moz-range-thumb,
#wwrap input[type=range]::-moz-range-thumb,
#wbal input[type=range]::-moz-range-thumb {
border: 2px solid var(--c-1);
}
#wwrap, #wbal {
display: none; display: none;
} }
@ -537,6 +577,14 @@ input[type=range]:active + .sliderbubble {
width: 240px; width: 240px;
position: relative; position: relative;
} }
#rwrap .sliderwrap,
#gwrap .sliderwrap,
#bwrap .sliderwrap,
#wwrap .sliderwrap,
#wbal .sliderwrap {
width: 260px;
margin: 10px 0 0;
}
.sbs { .sbs {
margin: 0px -20px 5px -6px; margin: 0px -20px 5px -6px;

View File

@ -47,22 +47,29 @@
<div id="picker" class="noslide"></div> <div id="picker" class="noslide"></div>
<div id="rgbwrap"> <div id="rgbwrap">
<div class="sliderwrap il"> <div class="sliderwrap il">
<input id="sliderR" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,1)" max="255" min="0" type="range" value="128" /> <input id="sliderR" class="noslide" onchange="fromRgb()" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div> <div class="sliderdisplay"></div>
</div><br> </div><br>
<div class="sliderwrap il"> <div class="sliderwrap il">
<input id="sliderG" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,2)" max="255" min="0" type="range" value="128" /> <input id="sliderG" class="noslide" onchange="fromRgb()" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div> <div class="sliderdisplay"></div>
</div><br> </div><br>
<div class="sliderwrap il"> <div class="sliderwrap il">
<input id="sliderB" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,3)" max="255" min="0" type="range" value="128" /> <input id="sliderB" class="noslide" onchange="fromRgb()" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div> <div class="sliderdisplay"></div>
</div><br> </div><br>
</div> </div>
<div id="wwrap"> <div id="wwrap">
<p class="labels">White channel</p> <p class="labels">White channel</p>
<div class="sliderwrap il"> <div class="sliderwrap il">
<input id="sliderW" class="noslide" onchange="setColor(0)" oninput="updateTrail(this)" max="255" min="0" type="range" value="128" /> <input id="sliderW" class="noslide" onchange="setColor(0)" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div>
</div>
</div>
<div id="wbal">
<p class="labels">White balance</p>
<div class="sliderwrap il">
<input id="sliderA" class="noslide" onchange="setBalance(this.value)" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div> <div class="sliderdisplay"></div>
</div> </div>
</div> </div>
@ -98,7 +105,7 @@
<label class="check schkl"> <label class="check schkl">
&nbsp; &nbsp;
<input type="radio" value="${palettes[i].id}" name="palette" onChange="setPalette()"> <input type="radio" value="${palettes[i].id}" name="palette" onChange="setPalette()">
<span class="checkmark schk"></span> <span class="radiomark schk"></span>
</label> </label>
<div class="lstIcontent"> <div class="lstIcontent">
<span class="lstIname"> <span class="lstIname">

View File

@ -51,7 +51,7 @@ var cpick = new iro.ColorPicker("#picker", {
options: { options: {
sliderType: 'value' sliderType: 'value'
} }
}, }/*,
{ {
component: iro.ui.Slider, component: iro.ui.Slider,
options: { options: {
@ -59,7 +59,7 @@ var cpick = new iro.ColorPicker("#picker", {
minTemperature: 2100, minTemperature: 2100,
maxTemperature: 10000 maxTemperature: 10000
} }
} }*/
] ]
}); });
@ -854,21 +854,14 @@ function loadNodes()
}); });
} }
function updateTrail(e, slidercol) function updateTrail(e)
{ {
if (e==null) return; if (e==null) return;
var max = e.hasAttribute('max') ? e.attributes.max.value : 255; var max = e.hasAttribute('max') ? e.attributes.max.value : 255;
var perc = e.value * 100 / max; var perc = e.value * 100 / max;
perc = parseInt(perc); perc = parseInt(perc);
if (perc < 50) perc += 2; if (perc < 50) perc += 2;
var scol; var val = `linear-gradient(90deg, var(--c-f) ${perc}%, var(--c-4) ${perc}%)`;
switch (slidercol) {
case 1: scol = "#f00"; break;
case 2: scol = "#0f0"; break;
case 3: scol = "#00f"; break;
default: scol = "var(--c-f)";
}
var val = `linear-gradient(90deg, ${scol} ${perc}%, var(--c-4) ${perc}%)`;
e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val; e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val;
} }
@ -939,8 +932,8 @@ function updateUI()
updateTrail(d.getElementById('sliderBri')); updateTrail(d.getElementById('sliderBri'));
updateTrail(d.getElementById('sliderSpeed')); updateTrail(d.getElementById('sliderSpeed'));
updateTrail(d.getElementById('sliderIntensity')); updateTrail(d.getElementById('sliderIntensity'));
updateTrail(d.getElementById('sliderW')); d.getElementById('wwrap').style.display = (isRgbw) ? "block":"none";
if (isRgbw) d.getElementById('wwrap').style.display = "block"; d.getElementById("wbal").style.display = (lastinfo.leds.cct) ? "block":"none";
updatePA(); updatePA();
updateHex(); updateHex();
@ -1023,6 +1016,7 @@ function readState(s,command=false) {
selectSlot(csel); selectSlot(csel);
} }
d.getElementById('sliderW').value = whites[csel]; d.getElementById('sliderW').value = whites[csel];
if (i.cct && i.cct>=0) d.getElementById("sliderA").value = i.cct;
d.getElementById('sliderSpeed').value = i.sx; d.getElementById('sliderSpeed').value = i.sx;
d.getElementById('sliderIntensity').value = i.ix; d.getElementById('sliderIntensity').value = i.ix;
@ -1560,6 +1554,11 @@ function setSegBri(s){
requestJson(obj); requestJson(obj);
} }
function setBalance(b)
{
var obj = {"seg": {"cct": parseInt(b)}};
requestJson(obj);
}
function setX(ind = null) { function setX(ind = null) {
if (ind === null) { if (ind === null) {
ind = parseInt(d.querySelector('#fxlist input[name="fx"]:checked').value); ind = parseInt(d.querySelector('#fxlist input[name="fx"]:checked').value);
@ -1723,7 +1722,6 @@ function selectSlot(b) {
cd[csel].style.width="50px"; cd[csel].style.width="50px";
cpick.color.set(cd[csel].style.backgroundColor); cpick.color.set(cd[csel].style.backgroundColor);
d.getElementById('sliderW').value = whites[csel]; d.getElementById('sliderW').value = whites[csel];
updateTrail(d.getElementById('sliderW'));
updateHex(); updateHex();
updateRgb(); updateRgb();
redrawPalPrev(); redrawPalPrev();
@ -1748,12 +1746,9 @@ function pC(col)
function updateRgb() function updateRgb()
{ {
var col = cpick.color.rgb; var col = cpick.color.rgb;
var s = d.getElementById('sliderR'); d.getElementById('sliderR').value = col.r;
s.value = col.r; updateTrail(s,1); d.getElementById('sliderG').value = col.g;
s = d.getElementById('sliderG'); d.getElementById('sliderB').value = col.b;
s.value = col.g; updateTrail(s,2);
s = d.getElementById('sliderB');
s.value = col.b; updateTrail(s,3);
} }
function updateHex() function updateHex()

View File

@ -247,8 +247,8 @@
gId('m0').innerHTML = memu; gId('m0').innerHTML = memu;
bquot = memu / maxM * 100; bquot = memu / maxM * 100;
gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`; gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`;
gId('ledwarning').style.display = (sLC > maxPB || maxLC > 800 || bquot > 80) ? 'inline':'none'; gId('ledwarning').style.display = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none';
gId('ledwarning').style.color = (sLC > maxPB || maxLC > maxPB || bquot > 100) ? 'red':'orange'; gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || bquot > 100) ? 'red':'orange';
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>ERROR: Using over ${maxM}B!</b>)` : "") : "800 LEDs per output"; gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>ERROR: Using over ${maxM}B!</b>)` : "") : "800 LEDs per output";
// calculate power // calculate power
var val = Math.ceil((100 + sPC * laprev)/500)/2; var val = Math.ceil((100 + sPC * laprev)/500)/2;
@ -389,11 +389,71 @@ ${i+1}:
req.send(formData); req.send(formData);
d.Sf.data.value = ''; d.Sf.data.value = '';
return false; return false;
}
// https://stackoverflow.com/questions/7346563/loading-local-json-file
function loadCfg(o) {
var f, fr;
if (typeof window.FileReader !== 'function') {
alert("The file API isn't supported on this browser yet.");
return;
}
if (!o.files) {
alert("This browser doesn't seem to support the `files` property of file inputs.");
} else if (!o.files[0]) {
alert("Please select a JSON file before clicking 'Apply'");
} else {
f = o.files[0];
fr = new FileReader();
fr.onload = receivedText;
fr.readAsText(f);
}
o.value = '';
function receivedText(e) {
let lines = e.target.result;
var c = JSON.parse(lines);
if (c.hw) {
if (c.hw.led) {
for (var i=0; i<10; i++) addLEDs(-1);
var l = c.hw.led;
l.ins.forEach((v,i,a)=>{
addLEDs(1);
for (var j=0; j<v.pin.length; j++) d.getElementsByName(`L${j}${i}`)[0].value = v.pin[j];
d.getElementsByName("LT"+i)[0].value = v.type;
d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order;
d.getElementsByName("SL"+i)[0].checked = v.skip;
d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev;
});
}
if (c.hw.btn) {
var b = c.hw.btn;
if (Array.isArray(b.ins)) gId("btns").innerHTML = "";
b.ins.forEach((v,i,a)=>{
addBtn(i,v.pin[0],v.type);
});
d.getElementsByName("TT")[0].value = b.tt;
}
if (c.hw.ir) {
d.getElementsByName("IR")[0].value = c.hw.ir.pin;
d.getElementsByName("IT")[0].value = c.hw.ir.type;
}
if (c.hw.relay) {
d.getElementsByName("RL")[0].value = c.hw.relay.pin;
d.getElementsByName("RM")[0].checked = c.hw.relay.inv;
}
UI();
}
}
} }
function GetV() function GetV()
{ {
//values injected by server while sending HTML //values injected by server while sending HTML
//d.um_p=[6,7,8,9,10,11,1];bLimits(3,4096,4000,1664);d.Sf.MS.checked=1;addLEDs(1);d.Sf.L00.value=2;d.Sf.LC0.value=30;d.Sf.LT0.value=22;d.Sf.CO0.value=0;d.Sf.LS0.value=15;d.Sf.CV0.checked=1;d.Sf.SL0.checked=0;addLEDs(1);d.Sf.L01.value=10;d.Sf.L11.value=10;d.Sf.L21.value=1;d.Sf.L31.value=10;d.Sf.LC1.value=60;d.Sf.LT1.value=80;d.Sf.CO1.value=1;d.Sf.LS1.value=0;d.Sf.CV1.checked=0;d.Sf.SL1.checked=0;d.Sf.MA.value=850;d.Sf.LA.value=0;d.Sf.CA.value=56;d.Sf.AW.value=3;d.Sf.BO.checked=1;d.Sf.BP.value=80;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=0;d.Sf.BF.value=100;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=0;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=12;d.Sf.RM.checked=1;addBtn(0,0,0);addBtn(1,-1,0);d.Sf.TT.value=32;d.Sf.IR.value=-1;d.Sf.IT.value=0; //d.um_p=[6,7,8,9,10,11,14,15,13,1,21,19,22,25,26,27,5,23,18,17];bLimits(10,2048,64000,8192);d.Sf.MS.checked=1;d.Sf.CCT.checked=0;addLEDs(1);d.Sf.L00.value=192;d.Sf.L10.value=168;d.Sf.L20.value=0;d.Sf.L30.value=61;d.Sf.LC0.value=421;d.Sf.LT0.value=80;d.Sf.CO0.value=1;d.Sf.LS0.value=0;d.Sf.CV0.checked=0;d.Sf.SL0.checked=0;d.Sf.RF0.checked=0;d.Sf.MA.value=850;d.Sf.LA.value=0;d.Sf.CA.value=127;d.Sf.AW.value=3;d.Sf.BO.checked=0;d.Sf.BP.value=0;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=1;d.Sf.BF.value=100;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=1;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=-1;d.Sf.RM.checked=1;addBtn(0,-1,0);addBtn(1,-1,0);addBtn(2,-1,0);addBtn(3,-1,0);d.Sf.TT.value=32;d.Sf.IR.value=-1;d.Sf.IT.value=8;
} }
</script> </script>
<style> <style>
@ -448,6 +508,7 @@ ${i+1}:
<hr style="width:260px"> <hr style="width:260px">
Make a segment for each output: <input type="checkbox" name="MS"> <br> Make a segment for each output: <input type="checkbox" name="MS"> <br>
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"> <br> Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"> <br>
Allow WB correction: <input type="checkbox" name="CCT"> <br>
<hr style="width:260px"> <hr style="width:260px">
<div id="btns"></div> <div id="btns"></div>
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br> Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
@ -506,7 +567,10 @@ ${i+1}:
<option value=3>Dual</option> <option value=3>Dual</option>
<option value=4>Legacy</option> <option value=4>Legacy</option>
</select> </select>
<br></span><hr> <br></span>
<hr style="width:260px">
<div id="cfg">Config template: <input type="file" name="data2" accept=".json"> <input type="button" value="Apply" onclick="loadCfg(d.Sf.data2);"><br></div>
<hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button> <button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form> </form>
</body> </body>

View File

@ -95,6 +95,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
uint8_t pins[5] = {255, 255, 255, 255, 255}; uint8_t pins[5] = {255, 255, 255, 255, 255};
autoSegments = request->hasArg(F("MS")); autoSegments = request->hasArg(F("MS"));
allowCCT = request->hasArg(F("CCT"));
for (uint8_t s = 0; s < WLED_MAX_BUSSES; s++) { for (uint8_t s = 0; s < WLED_MAX_BUSSES; s++) {
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin

View File

@ -270,6 +270,7 @@ WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load
//if false, only one segment spanning the total LEDs is created, //if false, only one segment spanning the total LEDs is created,
//but not on LED settings save if there is more than one segment currently //but not on LED settings save if there is more than one segment currently
WLED_GLOBAL bool autoSegments _INIT(false); WLED_GLOBAL bool autoSegments _INIT(false);
WLED_GLOBAL bool allowCCT _INIT(false); //CCT color correction
WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color. WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color.
WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color
@ -508,7 +509,6 @@ WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 25
WLED_GLOBAL bool blynkEnabled _INIT(false); WLED_GLOBAL bool blynkEnabled _INIT(false);
//playlists //playlists
WLED_GLOBAL unsigned long presetCycledTime _INIT(0);
WLED_GLOBAL int16_t currentPlaylist _INIT(-1); WLED_GLOBAL int16_t currentPlaylist _INIT(-1);
//still used for "PL=~" HTTP API command //still used for "PL=~" HTTP API command
WLED_GLOBAL byte presetCycCurr _INIT(0); WLED_GLOBAL byte presetCycCurr _INIT(0);
@ -637,10 +637,9 @@ WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) #define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0)
#define WLED_MQTT_CONNECTED (mqtt != nullptr && mqtt->connected()) #define WLED_MQTT_CONNECTED (mqtt != nullptr && mqtt->connected())
// append new c string to temp buffer efficiently //macro to convert F to const
bool oappend(const char* txt); #define SET_F(x) (const char*)F(x)
// append new number to temp buffer efficiently
bool oappendi(int i);
class WLED { class WLED {
public: public: