diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index ae8f31f0..b2ee8d5c 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -92,7 +92,7 @@ class St7789DisplayUsermod : public Usermod { updateLocalTime(); byte minuteCurrent = minute(localTime); byte hourCurrent = hour(localTime); - byte secondCurrent = second(localTime); + //byte secondCurrent = second(localTime); knownMinute = minuteCurrent; knownHour = hourCurrent; @@ -140,7 +140,7 @@ class St7789DisplayUsermod : public Usermod { void setup() { PinManagerPinType pins[] = { { TFT_MOSI, true }, { TFT_MISO, false}, { TFT_SCLK, true }, { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; - if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_FourLineDisplay)) { return; } + if (!pinManager.allocateMultiplePins(pins, 7, PinOwner::UM_FourLineDisplay)) { return; } tft.init(); tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip. @@ -364,19 +364,14 @@ class St7789DisplayUsermod : public Usermod { * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Below it is shown how this could be used for e.g. a light sensor */ - /* void addToJsonInfo(JsonObject& root) { - int reading = 20; - //this code adds "u":{"Light":[20," lux"]} to the info object JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); - JsonArray lightArr = user.createNestedArray("Light"); //name - lightArr.add(reading); //value - lightArr.add(" lux"); //unit + JsonArray lightArr = user.createNestedArray("ST7789"); //name + lightArr.add(F("installed")); //unit } - */ /* diff --git a/wled00/FX.h b/wled00/FX.h index 4bdec48f..e24f5215 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -166,7 +166,7 @@ #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 #define FX_MODE_POLICE 48 -#define FX_MODE_POLICE_ALL 49 +#define FX_MODE_POLICE_ALL 49 // candidate for removal #define FX_MODE_TWO_DOTS 50 #define FX_MODE_TWO_AREAS 51 #define FX_MODE_RUNNING_DUAL 52 @@ -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; //0==2000K, 255==10160K 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 ff1fb6cd..c7d67175 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. @@ -624,6 +635,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++) { @@ -631,6 +643,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 4a49be07..c39b5a3e 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; // is this ok? shouldn't it be 0 in virtual function? - } - - 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; } } @@ -419,6 +421,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; @@ -540,12 +547,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 f13ce120..205b50f5 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -86,6 +86,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"]; @@ -534,6 +535,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"); @@ -550,7 +552,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/colors.cpp b/wled00/colors.cpp index dfdd53e0..233121d1 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -67,6 +67,7 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col); } +//get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc { float r = 0, g = 0, b = 0; @@ -84,7 +85,7 @@ void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc g = round(288.1221695283 * pow((temp - 60), -0.0755148492)); b = 255; } - g += 15; //mod by Aircoookie, a bit less accurate but visibly less pinkish + //g += 15; //mod by Aircoookie, a bit less accurate but visibly less pinkish rgb[0] = (uint8_t) constrain(r, 0, 255); rgb[1] = (uint8_t) constrain(g, 0, 255); rgb[2] = (uint8_t) constrain(b, 0, 255); @@ -244,3 +245,24 @@ void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_M float sat = 100.0f * ((high - low) / high);; // maximum saturation is 100 (corrected from 255) rgb[3] = (byte)((255.0f - sat) / 255.0f * (rgb[0] + rgb[1] + rgb[2]) / 3); } + +// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance) +void colorBalanceFromKelvin(uint16_t kelvin, byte *rgb) +{ + byte rgbw[4] = {0,0,0,0}; + colorKtoRGB(kelvin, rgbw); // convert Kelvin to RGB + rgb[0] = ((uint16_t) rgbw[0] * rgb[0]) / 255; // correct R + rgb[1] = ((uint16_t) rgbw[1] * rgb[1]) / 255; // correct G + rgb[2] = ((uint16_t) rgbw[2] * rgb[2]) / 255; // correct B +} + +uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) +{ + byte rgbw[4] = {0,0,0,0}; + colorKtoRGB(kelvin, rgbw); // convert Kelvin to RGB + rgbw[0] = ((uint16_t) rgbw[0] * ((rgb>>16) & 0xFF)) / 255; // correct R + rgbw[1] = ((uint16_t) rgbw[1] * ((rgb>> 8) & 0xFF)) / 255; // correct G + rgbw[2] = ((uint16_t) rgbw[2] * ((rgb ) & 0xFF)) / 255; // correct B + rgbw[3] = ((rgb>>24) & 0xFF); + return colorFromRgbw(rgbw); +} diff --git a/wled00/data/index.css b/wled00/data/index.css index e58a5d95..917f6185 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -456,17 +456,23 @@ button { #info table, #nodes table { table-layout: fixed; - width: 490px; - margin: auto; + width: 100%; } #info td, #nodes td { padding-bottom: 8px; } -#info .btn, #nodes .btn { +#info .btn { + margin: 5px; +} +#info table .btn, #nodes table .btn { margin: 0; } +#info div, #nodes div { + width: 490px; + margin: 0 auto; +} #lv { max-width: 600px; @@ -498,13 +504,28 @@ img { .sliderdisplay { content:''; position: absolute; - top: 13px; bottom: 13px; - left: 10px; right: 10px; + top: 13px; left: 8px; right: 8px; + height: 4px; background: var(--c-4); - border-radius: 17px; + border-radius: 16px; pointer-events: none; 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: 24px; @@ -515,7 +536,7 @@ img { color: var(--c-f); padding: 4px 4px 2px; font-size: 14px; - right: 5px; + right: 3px; transition: visibility 0.25s ease, opacity 0.25s ease; opacity: 0; visibility: hidden; @@ -532,12 +553,20 @@ output.sliderbubbleshow { input[type=range] { -webkit-appearance: none; - width: 220px; - padding: 0px; - margin: 0px 10px 0px 10px; + width: 100%; + padding: 0; + margin: 0; 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; } @@ -550,7 +579,7 @@ input[type=range]::-webkit-slider-runnable-track { input[type=range]::-webkit-slider-thumb { height: 16px; width: 16px; - border-radius: 17px; + border-radius: 50%; background: var(--c-f); cursor: pointer; -webkit-appearance: none; @@ -565,19 +594,44 @@ input[type=range]::-moz-range-thumb { border: 0px solid rgba(0, 0, 0, 0); height: 16px; width: 16px; - border-radius: 17px; + border-radius: 50%; background: var(--c-f); - transform: translateY(7px); + transform: translateY(5px); } -#wwrap { - display: none; +#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: block; } .sliderwrap { height: 30px; - width: 240px; + width: 230px; position: relative; } +#rwrap .sliderwrap, +#gwrap .sliderwrap, +#bwrap .sliderwrap, +#wwrap .sliderwrap, +#wbal .sliderwrap { + width: 260px; + margin: 10px 0 0; +} .hd { display: var(--bhd); @@ -589,7 +643,7 @@ input[type=range]::-moz-range-thumb { } #picker { - margin: 10px auto; + margin: 10px auto 0; width: 260px; } @@ -1141,9 +1195,9 @@ input[type="text"].fnd:hover { @media all and (max-width: 550px) and (min-width: 374px) { #info .btn, #nodes .btn { - width: 155px; + width: 150px; } - #info table, #nodes table { + #info div, #nodes div { width: 320px; } } diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 5a24c59d..4e7b075f 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -49,33 +49,41 @@
- +
- +

- +
- +

- +
- +

-

White channel

-
- +

White channel

+
+
-
+ +
+
+

White balance

+
+ +
+
+
@@ -201,28 +209,32 @@

Loading...

- +
+ +

Made with ❤︎ by Aircoookie and the WLED community
+
+ +