diff --git a/wled00/FX.h b/wled00/FX.h index 4bdec48f..8a969181 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -247,7 +247,7 @@ class WS2812FX { // segment parameters public: - typedef struct Segment { // 29 (32 in memory?) bytes + typedef struct Segment { // 30 (33 in memory?) bytes uint16_t start; uint16_t stop; //segment invalid if stop == 0 uint16_t offset; @@ -259,6 +259,7 @@ class WS2812FX { uint8_t grouping, spacing; uint8_t opacity; uint32_t colors[NUM_COLORS]; + uint8_t cct; char *name; 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; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index f89df5ed..05914e38 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -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 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++) { uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j); 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 < customMappingSize) indexMir = customMappingTable[indexMir]; - busses.setPixelColor(indexMir, col); + busses.setPixelColor(indexMir, col, cct); } /* offset/phase */ indexSet += SEGMENT.offset; if (indexSet >= SEGMENT.stop) indexSet -= len; if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; - busses.setPixelColor(indexSet, col); + busses.setPixelColor(indexSet, col, cct); } } } else { //live data, etc. @@ -623,6 +634,7 @@ void WS2812FX::resetSegments() { _segments[0].setOption(SEG_OPTION_SELECTED, 1); _segments[0].setOption(SEG_OPTION_ON, 1); _segments[0].opacity = 255; + _segments[0].cct = 128; for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) { @@ -630,6 +642,7 @@ void WS2812FX::resetSegments() { _segments[i].grouping = 1; _segments[i].setOption(SEG_OPTION_ON, 1); _segments[i].opacity = 255; + _segments[i].cct = 128; _segments[i].speed = DEFAULT_SPEED; _segments[i].intensity = DEFAULT_INTENSITY; _segment_runtimes[i].reset(); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 799019f3..ffe6a04f 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -10,6 +10,9 @@ #include "bus_wrapper.h" #include +//color.cpp +uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); + // enable additional debug output #ifdef WLED_DEBUG #ifndef ESP8266 @@ -65,79 +68,48 @@ struct BusConfig { //parent class of BusDigital and BusPwm class Bus { public: - Bus(uint8_t type, uint16_t start) { - _type = type; - _start = start; - }; - - virtual void show() {} - virtual bool canShow() { return true; } + Bus(uint8_t type, uint16_t start) { + _type = type; + _start = start; + }; - virtual void setPixelColor(uint16_t pix, uint32_t c) {}; + virtual ~Bus() {} //throw the bus under the bus - virtual void setBrightness(uint8_t b) {}; + 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 uint32_t getPixelColor(uint16_t pix) { return 0; }; + 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 void cleanup() {}; + 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 ~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; + bool reversed = false; protected: - uint8_t _type = TYPE_NONE; - uint8_t _bri = 255; - uint16_t _start = 0; - bool _valid = false; - bool _needsRefresh = false; + uint8_t _type = TYPE_NONE; + uint8_t _bri = 255; + uint16_t _start = 0; + bool _valid = false; + bool _needsRefresh = false; }; @@ -190,6 +162,11 @@ class BusDigital : public Bus { 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) { if (reversed) pix = _len - pix -1; else pix += _skip; @@ -285,6 +262,34 @@ class BusPwm : public Bus { _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) { if (pix != 0 || !_valid) return; //only react to first pixel uint8_t r = c >> 16; @@ -295,14 +300,11 @@ class BusPwm : public Bus { 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, we'll need some nice handling here, for now just R+G channels + case TYPE_ANALOG_2CH: //warm white + cold white 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 _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); } + 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) { if (!_valid || pix >= _len) return 0; 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++) { Bus* b = busses[i]; uint16_t bstart = b->getStart(); 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 } } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 13bcdd96..d0bbc6f8 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -84,6 +84,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); CJSON(strip.rgbwMode, hw_led[F("rgbwm")]); + CJSON(allowCCT, hw_led["cct"]); JsonArray ins = hw_led["ins"]; @@ -530,6 +531,7 @@ void serializeConfig() { hw_led[F("maxpwr")] = strip.ablMilliampsMax; hw_led[F("ledma")] = strip.milliampsPerLed; hw_led[F("rgbwm")] = strip.rgbwMode; + hw_led["cct"] = allowCCT; JsonArray hw_led_ins = hw_led.createNestedArray("ins"); @@ -546,7 +548,7 @@ void serializeConfig() { ins[F("order")] = bus->getColorOrder(); ins["rev"] = bus->reversed; ins[F("skip")] = bus->skippedLeds(); - ins["type"] = bus->getType() & 0x7F;; + ins["type"] = bus->getType() & 0x7F; ins["ref"] = bus->isOffRefreshRequired(); ins[F("rgbw")] = bus->isRgbw(); } diff --git a/wled00/data/index.css b/wled00/data/index.css index f27024ac..cb5aac3d 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -467,6 +467,22 @@ img { 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 { width: 36px; line-height: 24px; @@ -492,6 +508,14 @@ input[type=range] { background-color: transparent; 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 { outline: none; } @@ -527,8 +551,24 @@ input[type=range]:active + .sliderbubble { display: inline; transform: translateX(-50%); } - -#wwrap { +#rwrap input[type=range]::-webkit-slider-thumb, +#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; } @@ -537,6 +577,14 @@ input[type=range]:active + .sliderbubble { width: 240px; position: relative; } +#rwrap .sliderwrap, +#gwrap .sliderwrap, +#bwrap .sliderwrap, +#wwrap .sliderwrap, +#wbal .sliderwrap { + width: 260px; + margin: 10px 0 0; +} .sbs { margin: 0px -20px 5px -6px; diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 530f4cd4..5be885f7 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -47,22 +47,29 @@
- +

- +

- +

White channel

- + +
+
+
+
+

White balance

+
+
@@ -98,7 +105,7 @@
diff --git a/wled00/data/index.js b/wled00/data/index.js index 7bf47b25..26d1afe3 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -51,7 +51,7 @@ var cpick = new iro.ColorPicker("#picker", { options: { sliderType: 'value' } - }, + }/*, { component: iro.ui.Slider, options: { @@ -59,7 +59,7 @@ var cpick = new iro.ColorPicker("#picker", { minTemperature: 2100, maxTemperature: 10000 } - } + }*/ ] }); @@ -854,21 +854,14 @@ function loadNodes() }); } -function updateTrail(e, slidercol) +function updateTrail(e) { if (e==null) return; var max = e.hasAttribute('max') ? e.attributes.max.value : 255; var perc = e.value * 100 / max; perc = parseInt(perc); if (perc < 50) perc += 2; - var scol; - 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}%)`; + var val = `linear-gradient(90deg, var(--c-f) ${perc}%, var(--c-4) ${perc}%)`; e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val; } @@ -939,8 +932,8 @@ function updateUI() updateTrail(d.getElementById('sliderBri')); updateTrail(d.getElementById('sliderSpeed')); updateTrail(d.getElementById('sliderIntensity')); - updateTrail(d.getElementById('sliderW')); - if (isRgbw) d.getElementById('wwrap').style.display = "block"; + d.getElementById('wwrap').style.display = (isRgbw) ? "block":"none"; + d.getElementById("wbal").style.display = (lastinfo.leds.cct) ? "block":"none"; updatePA(); updateHex(); @@ -1023,6 +1016,7 @@ function readState(s,command=false) { selectSlot(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('sliderIntensity').value = i.ix; @@ -1560,6 +1554,11 @@ function setSegBri(s){ requestJson(obj); } +function setBalance(b) +{ + var obj = {"seg": {"cct": parseInt(b)}}; + requestJson(obj); +} function setX(ind = null) { if (ind === null) { ind = parseInt(d.querySelector('#fxlist input[name="fx"]:checked').value); @@ -1723,7 +1722,6 @@ function selectSlot(b) { cd[csel].style.width="50px"; cpick.color.set(cd[csel].style.backgroundColor); d.getElementById('sliderW').value = whites[csel]; - updateTrail(d.getElementById('sliderW')); updateHex(); updateRgb(); redrawPalPrev(); @@ -1748,12 +1746,9 @@ function pC(col) function updateRgb() { var col = cpick.color.rgb; - var s = d.getElementById('sliderR'); - s.value = col.r; updateTrail(s,1); - s = d.getElementById('sliderG'); - s.value = col.g; updateTrail(s,2); - s = d.getElementById('sliderB'); - s.value = col.b; updateTrail(s,3); + d.getElementById('sliderR').value = col.r; + d.getElementById('sliderG').value = col.g; + d.getElementById('sliderB').value = col.b; } function updateHex() diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 187fd289..650d0d7c 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -247,8 +247,8 @@ gId('m0').innerHTML = memu; bquot = memu / maxM * 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.color = (sLC > maxPB || maxLC > maxPB || bquot > 100) ? 'red':'orange'; + gId('ledwarning').style.display = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none'; + 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 ? ` (ERROR: Using over ${maxM}B!)` : "") : "800 LEDs per output"; // calculate power var val = Math.ceil((100 + sPC * laprev)/500)/2; @@ -389,11 +389,71 @@ ${i+1}: req.send(formData); d.Sf.data.value = ''; 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{ + 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() { //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; }