Merge branch 'master' of https://github.com/Aircoookie/WLED into new-CCT

This commit is contained in:
Blaž Kristan 2021-12-01 14:51:45 +01:00
commit aa34df7a4c
25 changed files with 1971 additions and 1745 deletions

View File

@ -2,6 +2,12 @@
### Builds after release 0.12.0 ### Builds after release 0.12.0
#### Build 2111300
- Added CCT and white balance correction support (PR #2285)
- Unified UI slider style
- Added LED settings config template upload
#### Build 2111220 #### Build 2111220
- Fixed preset cycle not working from preset called by UI - Fixed preset cycle not working from preset called by UI

View File

@ -435,6 +435,13 @@ board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:athom7w]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_MAX_CCT_BLEND=0 -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# travis test board configurations # travis test board configurations
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -165,12 +165,12 @@
#define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_FIRE_FLICKER 45
#define FX_MODE_GRADIENT 46 #define FX_MODE_GRADIENT 46
#define FX_MODE_LOADING 47 #define FX_MODE_LOADING 47
#define FX_MODE_POLICE 48 #define FX_MODE_POLICE 48 // candidate for removal (after below three)
#define FX_MODE_POLICE_ALL 49 // candidate for removal #define FX_MODE_POLICE_ALL 49 // candidate for removal
#define FX_MODE_TWO_DOTS 50 #define FX_MODE_TWO_DOTS 50
#define FX_MODE_TWO_AREAS 51 #define FX_MODE_TWO_AREAS 51 // candidate for removal
#define FX_MODE_RUNNING_DUAL 52 #define FX_MODE_RUNNING_DUAL 52
#define FX_MODE_HALLOWEEN 53 #define FX_MODE_HALLOWEEN 53 // candidate for removal
#define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_CHASE 54
#define FX_MODE_TRICOLOR_WIPE 55 #define FX_MODE_TRICOLOR_WIPE 55
#define FX_MODE_TRICOLOR_FADE 56 #define FX_MODE_TRICOLOR_FADE 56
@ -231,7 +231,7 @@
#define FX_MODE_CHUNCHUN 111 #define FX_MODE_CHUNCHUN 111
#define FX_MODE_DANCING_SHADOWS 112 #define FX_MODE_DANCING_SHADOWS 112
#define FX_MODE_WASHING_MACHINE 113 #define FX_MODE_WASHING_MACHINE 113
#define FX_MODE_CANDY_CANE 114 #define FX_MODE_CANDY_CANE 114 // candidate for removal
#define FX_MODE_BLENDS 115 #define FX_MODE_BLENDS 115
#define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_TV_SIMULATOR 116
#define FX_MODE_DYNAMIC_SMOOTH 117 #define FX_MODE_DYNAMIC_SMOOTH 117
@ -247,7 +247,7 @@ class WS2812FX {
// segment parameters // segment parameters
public: public:
typedef struct Segment { // 30 (33 in memory?) bytes typedef struct Segment { // 30 (32 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,24 +259,32 @@ 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; //0==2000K, 255==10160K uint8_t cct; //0==1900K, 255==10091K
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;
if (c == colors[slot]) return false; if (c == colors[slot]) return false;
ColorTransition::startTransition(opacity, colors[slot], instance->_transitionDur, segn, slot); uint8_t b = (slot == 1) ? cct : opacity;
ColorTransition::startTransition(b, colors[slot], instance->_transitionDur, segn, slot);
colors[slot] = c; return true; colors[slot] = c; return true;
} }
void setCCT(uint16_t k, uint8_t segn) {
if (segn >= MAX_NUM_SEGMENTS) return;
if (k > 255) { //kelvin value, convert to 0-255
if (k < 1900) k = 1900;
if (k > 10091) k = 10091;
k = (k - 1900) >> 5;
}
if (cct == k) return;
ColorTransition::startTransition(cct, colors[1], instance->_transitionDur, segn, 1);
cct = k;
}
void setOpacity(uint8_t o, uint8_t segn) { void setOpacity(uint8_t o, uint8_t segn) {
if (segn >= MAX_NUM_SEGMENTS) return; if (segn >= MAX_NUM_SEGMENTS) return;
if (opacity == o) return; if (opacity == o) return;
ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0); ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
opacity = o; opacity = o;
} }
/*uint8_t actualOpacity() { //respects On/Off state
if (!getOption(SEG_OPTION_ON)) return 0;
return opacity;
}*/
void setOption(uint8_t n, bool val, uint8_t segn = 255) void setOption(uint8_t n, bool val, uint8_t segn = 255)
{ {
bool prevOn = false; bool prevOn = false;
@ -446,7 +454,7 @@ class WS2812FX {
if (t.segment == s) //this is an active transition on the same segment+color if (t.segment == s) //this is an active transition on the same segment+color
{ {
bool wasTurningOff = (oldBri == 0); bool wasTurningOff = (oldBri == 0);
t.briOld = t.currentBri(wasTurningOff); t.briOld = t.currentBri(wasTurningOff, slot);
t.colorOld = t.currentColor(oldCol); t.colorOld = t.currentColor(oldCol);
} else { } else {
t.briOld = oldBri; t.briOld = oldBri;
@ -478,11 +486,15 @@ class WS2812FX {
uint32_t currentColor(uint32_t colorNew) { uint32_t currentColor(uint32_t colorNew) {
return instance->color_blend(colorOld, colorNew, progress(true), true); return instance->color_blend(colorOld, colorNew, progress(true), true);
} }
uint8_t currentBri(bool turningOff = false) { uint8_t currentBri(bool turningOff = false, uint8_t slot = 0) {
uint8_t segn = segment & 0x3F; uint8_t segn = segment & 0x3F;
if (segn >= MAX_NUM_SEGMENTS) return 0; if (segn >= MAX_NUM_SEGMENTS) return 0;
uint8_t briNew = instance->_segments[segn].opacity; uint8_t briNew = instance->_segments[segn].opacity;
if (slot == 0) {
if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0; if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0;
} else { //transition slot 1 brightness for CCT transition
briNew = instance->_segments[segn].cct;
}
uint32_t prog = progress() + 1; uint32_t prog = progress() + 1;
return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16; return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16;
} }
@ -653,6 +665,7 @@ class WS2812FX {
applyToAllSelected = true, applyToAllSelected = true,
setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p), setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p),
checkSegmentAlignment(void), checkSegmentAlignment(void),
hasCCTBus(void),
// return true if the strip is being sent pixel updates // return true if the strip is being sent pixel updates
isUpdating(void); isUpdating(void);
@ -661,6 +674,7 @@ class WS2812FX {
paletteFade = 0, paletteFade = 0,
paletteBlend = 0, paletteBlend = 0,
milliampsPerLed = 55, milliampsPerLed = 55,
cctBlending = 0,
getBrightness(void), getBrightness(void),
getMode(void), getMode(void),
getSpeed(void), getSpeed(void),

View File

@ -137,13 +137,16 @@ void WS2812FX::service() {
if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen
_virtualSegmentLength = SEGMENT.virtualLength(); _virtualSegmentLength = SEGMENT.virtualLength();
_bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2]; _bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2];
uint8_t _cct_t = SEGMENT.cct;
if (!IS_SEGMENT_ON) _bri_t = 0; if (!IS_SEGMENT_ON) _bri_t = 0;
for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) { for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) {
if ((transitions[t].segment & 0x3F) != i) continue; if ((transitions[t].segment & 0x3F) != i) continue;
uint8_t slot = transitions[t].segment >> 6; uint8_t slot = transitions[t].segment >> 6;
if (slot == 0) _bri_t = transitions[t].currentBri(); if (slot == 0) _bri_t = transitions[t].currentBri();
if (slot == 1) _cct_t = transitions[t].currentBri(false, 1);
_colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]); _colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]);
} }
if (!cctFromRgb || correctWB) busses.setSegmentCCT(_cct_t, correctWB);
for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]); for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]);
handle_palette(); handle_palette();
delay = (this->*_mode[SEGMENT.mode])(); //effect function delay = (this->*_mode[SEGMENT.mode])(); //effect function
@ -154,6 +157,7 @@ void WS2812FX::service() {
} }
} }
_virtualSegmentLength = 0; _virtualSegmentLength = 0;
busses.setSegmentCCT(-1);
if(doShow) { if(doShow) {
yield(); yield();
show(); show();
@ -189,21 +193,6 @@ 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 and accurate W calc
// NOTE & TODO: does not work correctly with custom mapping if map spans different strips
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 (bus == nullptr || bus->getStart()<realIndex || bus->getStart()+bus->getLength()>realIndex) continue;
uint8_t busType = bus->getType();
if (allowCCT
|| busType == TYPE_ANALOG_2CH
|| busType == TYPE_ANALOG_5CH) {
if (cct<0) cct = SEGMENT.cct;
}
}
//color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments) //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
if (_bri_t < 255) { if (_bri_t < 255) {
r = scale8(r, _bri_t); r = scale8(r, _bri_t);
@ -563,6 +552,20 @@ uint16_t WS2812FX::getLengthPhysical(void) {
return len; return len;
} }
bool WS2812FX::hasCCTBus(void) {
if (cctFromRgb && !correctWB) return false;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (bus == nullptr || bus->getLength()==0) break;
switch (bus->getType()) {
case TYPE_ANALOG_5CH:
case TYPE_ANALOG_2CH:
return true;
}
}
return false;
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) { void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) {
if (n >= MAX_NUM_SEGMENTS) return; if (n >= MAX_NUM_SEGMENTS) return;
Segment& seg = _segments[n]; Segment& seg = _segments[n];
@ -617,7 +620,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; _segments[0].cct = 127;
for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++)
{ {
@ -625,7 +628,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].cct = 127;
_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();
@ -1172,3 +1175,8 @@ uint32_t WS2812FX::gamma32(uint32_t color)
} }
WS2812FX* WS2812FX::instance = nullptr; WS2812FX* WS2812FX::instance = nullptr;
//Bus static member definition, would belong in bus_manager.cpp
int16_t Bus::_cct = -1;
uint8_t Bus::_cctBlend = 0;
uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL;

View File

@ -73,17 +73,27 @@ void onAlexaChange(EspalexaDevice* dev)
if (espalexaDevice->getColorMode() == EspalexaColorMode::ct) //shade of white if (espalexaDevice->getColorMode() == EspalexaColorMode::ct) //shade of white
{ {
uint16_t ct = espalexaDevice->getCt(); uint16_t ct = espalexaDevice->getCt();
if (strip.isRgbw) if (!ct) return;
{ uint16_t k = 1000000 / ct; //mireds to kelvin
if (strip.hasCCTBus()) {
uint8_t segid = strip.getMainSegmentId();
WS2812FX::Segment& seg = strip.getSegment(segid);
uint8_t cctPrev = seg.cct;
seg.setCCT(k, segid);
if (seg.cct != cctPrev) effectChanged = true; //send UDP
col[0]= 0; col[1]= 0; col[2]= 0; col[3]= 255;
} else if (strip.isRgbw) {
switch (ct) { //these values empirically look good on RGBW switch (ct) { //these values empirically look good on RGBW
case 199: col[0]=255; col[1]=255; col[2]=255; col[3]=255; break; case 199: col[0]=255; col[1]=255; col[2]=255; col[3]=255; break;
case 234: col[0]=127; col[1]=127; col[2]=127; col[3]=255; break; case 234: col[0]=127; col[1]=127; col[2]=127; col[3]=255; break;
case 284: col[0]= 0; col[1]= 0; col[2]= 0; col[3]=255; break; case 284: col[0]= 0; col[1]= 0; col[2]= 0; col[3]=255; break;
case 350: col[0]=130; col[1]= 90; col[2]= 0; col[3]=255; break; case 350: col[0]=130; col[1]= 90; col[2]= 0; col[3]=255; break;
case 383: col[0]=255; col[1]=153; col[2]= 0; col[3]=255; break; case 383: col[0]=255; col[1]=153; col[2]= 0; col[3]=255; break;
default : colorKtoRGB(k, col);
} }
} else { } else {
colorCTtoRGB(ct, col); colorKtoRGB(k, col);
} }
} else { } else {
uint32_t color = espalexaDevice->getRGB(); uint32_t color = espalexaDevice->getRGB();

View File

@ -74,13 +74,12 @@ struct BusConfig {
} }
}; };
//parent class of BusDigital and BusPwm //parent class of BusDigital, BusPwm, and BusNetwork
class Bus { class Bus {
public: public:
Bus(uint8_t type, uint16_t start, uint8_t aw) { Bus(uint8_t type, uint16_t start) {
_type = type; _type = type;
_start = start; _start = start;
_autoWhiteMode = isRgbw(_type) ? aw : RGBW_MODE_MANUAL_ONLY;
}; };
virtual ~Bus() {} //throw the bus under the bus virtual ~Bus() {} //throw the bus under the bus
@ -89,7 +88,6 @@ class Bus {
virtual bool canShow() { return true; } virtual bool canShow() { return true; }
virtual void setStatusPixel(uint32_t c) {} virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(uint16_t pix, uint32_t c) {} 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 uint32_t getPixelColor(uint16_t pix) { return 0; }
virtual void setBrightness(uint8_t b) {} virtual void setBrightness(uint8_t b) {}
virtual void cleanup() {} virtual void cleanup() {}
@ -98,7 +96,6 @@ class Bus {
virtual void setColorOrder() {} virtual void setColorOrder() {}
virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
virtual uint8_t skippedLeds() { return 0; } virtual uint8_t skippedLeds() { return 0; }
inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; }
inline uint16_t getStart() { return _start; } inline uint16_t getStart() { return _start; }
inline void setStart(uint16_t start) { _start = start; } inline void setStart(uint16_t start) { _start = start; }
inline uint8_t getType() { return _type; } inline uint8_t getType() { return _type; }
@ -112,6 +109,19 @@ class Bus {
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
return false; return false;
} }
static void setCCT(uint16_t cct) {
_cct = cct;
}
static void setCCTBlend(uint8_t b) {
if (b > 100) b = 100;
_cctBlend = (b * 127) / 100;
//compile-time limiter for hardware that can't power both white channels at max
#ifdef WLED_MAX_CCT_BLEND
if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND;
#endif
}
inline static void setAutoWhiteMode(uint8_t m) { if (m < 4) _autoWhiteMode = m; }
inline static uint8_t getAutoWhiteMode() { return _autoWhiteMode; }
bool reversed = false; bool reversed = false;
@ -122,17 +132,20 @@ class Bus {
uint16_t _len = 1; uint16_t _len = 1;
bool _valid = false; bool _valid = false;
bool _needsRefresh = false; bool _needsRefresh = false;
uint8_t _autoWhiteMode = 0; static uint8_t _autoWhiteMode;
static int16_t _cct;
static uint8_t _cctBlend;
uint32_t autoWhiteCalc(uint32_t c) { uint32_t autoWhiteCalc(uint32_t c) {
if (_autoWhiteMode == RGBW_MODE_MANUAL_ONLY) return c; if (_autoWhiteMode == RGBW_MODE_MANUAL_ONLY) return c;
uint8_t w = W(c);
//ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0)
if (w > 0 && _autoWhiteMode == RGBW_MODE_DUAL) return c;
uint8_t r = R(c); uint8_t r = R(c);
uint8_t g = G(c); uint8_t g = G(c);
uint8_t b = B(c); uint8_t b = B(c);
uint8_t w = W(c); w = r < g ? (r < b ? r : b) : (g < b ? g : b);
// ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode
if (!(w > 0 && _autoWhiteMode == RGBW_MODE_DUAL)) w = r < g ? (r < b ? r : b) : (g < b ? g : b);
if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } // subtract w in ACCURATE mode
return RGBW32(r, g, b, w); return RGBW32(r, g, b, w);
} }
}; };
@ -181,15 +194,18 @@ class BusDigital : public Bus {
PolyBus::setBrightness(_busPtr, _iType, b); PolyBus::setBrightness(_busPtr, _iType, b);
} }
//If LEDs are skipped, it is possible to use the first as a status LED.
//TODO only show if no new show due in the next 50ms
void setStatusPixel(uint32_t c) { void setStatusPixel(uint32_t c) {
if (_skip && canShow()) { if (_skip && canShow()) {
for (uint8_t i=0; i<_skip; i--) PolyBus::setPixelColor(_busPtr, _iType, i, c, _colorOrder); PolyBus::setPixelColor(_busPtr, _iType, 0, c, _colorOrder);
PolyBus::show(_busPtr, _iType); PolyBus::show(_busPtr, _iType);
} }
} }
void setPixelColor(uint16_t pix, uint32_t c) { void setPixelColor(uint16_t pix, uint32_t c) {
if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814) c = autoWhiteCalc(c);
if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
if (reversed) pix = _len - pix -1; if (reversed) pix = _len - pix -1;
else pix += _skip; else pix += _skip;
PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder); PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder);
@ -323,20 +339,49 @@ class BusPwm : public Bus {
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
if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c);
if (_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
}
uint8_t r = R(c); uint8_t r = R(c);
uint8_t g = G(c); uint8_t g = G(c);
uint8_t b = B(c); uint8_t b = B(c);
uint8_t w = W(c); uint8_t w = W(c);
uint8_t cct = 0; //0 - full warm white, 255 - full cold white
if (_cct > -1) {
if (_cct >= 1900) cct = (_cct - 1900) >> 5;
else if (_cct < 256) cct = _cct;
} else {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5;
}
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
uint8_t ww, cw;
if (cct < _cctBlend) ww = 255;
else ww = ((255-cct) * 255) / (255 - _cctBlend);
if ((255-cct) < _cctBlend) cw = 255;
else cw = (cct * 255) / (255 - _cctBlend);
ww = (w * ww) / 255; //brightness scaling
cw = (w * cw) / 255;
switch (_type) { switch (_type) {
case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
_data[0] = max(r, max(g, max(b, w))); break; _data[0] = w;
break;
case TYPE_ANALOG_2CH: //warm white + cold white case TYPE_ANALOG_2CH: //warm white + cold white
_data[1] = cw;
_data[0] = ww;
break;
case TYPE_ANALOG_5CH: //RGB + warm white + cold white
_data[4] = cw;
w = ww;
case TYPE_ANALOG_4CH: //RGBW
_data[3] = w;
case TYPE_ANALOG_3CH: //standard dumb RGB case TYPE_ANALOG_3CH: //standard dumb RGB
case TYPE_ANALOG_4CH: //standard dumb RGBW _data[0] = r; _data[1] = g; _data[2] = b;
case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB break;
_data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = w; break;
} }
} }
@ -440,7 +485,8 @@ class BusNetwork : public Bus {
void setPixelColor(uint16_t pix, uint32_t c) { void setPixelColor(uint16_t pix, uint32_t c) {
if (!_valid || pix >= _len) return; if (!_valid || pix >= _len) return;
if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); if (_rgbw) c = autoWhiteCalc(c);
if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
uint16_t offset = pix * _UDPchannels; uint16_t offset = pix * _UDPchannels;
_data[offset] = R(c); _data[offset] = R(c);
_data[offset+1] = G(c); _data[offset+1] = G(c);
@ -448,11 +494,6 @@ class BusNetwork : public Bus {
if (_rgbw) _data[offset+3] = W(c); if (_rgbw) _data[offset+3] = W(c);
} }
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;
@ -568,7 +609,9 @@ class BusManager {
} }
void setStatusPixel(uint32_t c) { void setStatusPixel(uint32_t c) {
for (uint8_t i = 0; i < numBusses; i++) busses[i]->setStatusPixel(c); for (uint8_t i = 0; i < numBusses; i++) {
busses[i]->setStatusPixel(c);
}
} }
void setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1) { void setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1) {
@ -587,6 +630,15 @@ class BusManager {
} }
} }
void setSegmentCCT(int16_t cct, bool allowWBCorrection = false) {
if (cct > 255) cct = 255;
if (cct >= 0) {
//if white balance correction allowed, save as kelvin value instead of 0-255
if (allowWBCorrection) cct = 1900 + (cct << 5);
} else cct = -1;
Bus::setCCT(cct);
}
uint32_t getPixelColor(uint16_t pix) { uint32_t getPixelColor(uint16_t pix) {
for (uint8_t i = 0; i < numBusses; i++) { for (uint8_t i = 0; i < numBusses; i++) {
Bus* b = busses[i]; Bus* b = busses[i];

View File

@ -83,8 +83,11 @@ 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")]);
uint8_t rgbwMode = hw_led[F("rgbwm")] | RGBW_MODE_DUAL; // use global setting (legacy) Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | Bus::getAutoWhiteMode());
CJSON(allowCCT, hw_led["cct"]); CJSON(correctWB, hw_led["cct"]);
CJSON(cctFromRgb, hw_led[F("cr")]);
CJSON(strip.cctBlending, hw_led[F("cb")]);
Bus::setCCTBlend(strip.cctBlending);
JsonArray ins = hw_led["ins"]; JsonArray ins = hw_led["ins"];
@ -405,6 +408,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (i > 14) break; if (i > 14) break;
CJSON(DMXFixtureMap[i],dmx_fixmap[i]); CJSON(DMXFixtureMap[i],dmx_fixmap[i]);
} }
CJSON(e131ProxyUniverse, dmx[F("e131proxy")]);
#endif #endif
DEBUG_PRINTLN(F("Starting usermod config.")); DEBUG_PRINTLN(F("Starting usermod config."));
@ -536,7 +541,9 @@ void serializeConfig() {
hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade
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["cct"] = allowCCT; hw_led["cct"] = correctWB;
hw_led[F("cr")] = cctFromRgb;
hw_led[F("cb")] = strip.cctBlending;
JsonArray hw_led_ins = hw_led.createNestedArray("ins"); JsonArray hw_led_ins = hw_led.createNestedArray("ins");
@ -754,8 +761,11 @@ void serializeConfig() {
dmx[F("start-led")] = DMXStartLED; dmx[F("start-led")] = DMXStartLED;
JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap")); JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap"));
for (byte i = 0; i < 15; i++) for (byte i = 0; i < 15; i++) {
dmx_fixmap.add(DMXFixtureMap[i]); dmx_fixmap.add(DMXFixtureMap[i]);
}
dmx[F("e131proxy")] = e131ProxyUniverse;
#endif #endif
JsonObject usermods_settings = doc.createNestedObject("um"); JsonObject usermods_settings = doc.createNestedObject("um");

View File

@ -73,7 +73,7 @@ void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc
g = round(288.1221695283 * pow((temp - 60), -0.0755148492)); g = round(288.1221695283 * pow((temp - 60), -0.0755148492));
b = 255; b = 255;
} }
//g += 15; //mod by Aircoookie, a bit less accurate but visibly less pinkish //g += 12; //mod by Aircoookie, a bit less accurate but visibly less pinkish
rgb[0] = (uint8_t) constrain(r, 0, 255); rgb[0] = (uint8_t) constrain(r, 0, 255);
rgb[1] = (uint8_t) constrain(g, 0, 255); rgb[1] = (uint8_t) constrain(g, 0, 255);
rgb[2] = (uint8_t) constrain(b, 0, 255); rgb[2] = (uint8_t) constrain(b, 0, 255);
@ -245,23 +245,58 @@ void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_M
} }
*/ */
// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance) byte correctionRGB[4] = {0,0,0,0};
void colorBalanceFromKelvin(uint16_t kelvin, byte *rgb) uint16_t lastKelvin = 0;
{
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
}
// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance)
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb)
{ {
byte rgbw[4] = {0,0,0,0}; //remember so that slow colorKtoRGB() doesn't have to run for every setPixelColor()
colorKtoRGB(kelvin, rgbw); // convert Kelvin to RGB if (lastKelvin != kelvin) colorKtoRGB(kelvin, correctionRGB); // convert Kelvin to RGB
rgbw[0] = ((uint16_t) rgbw[0] * R(rgb)) / 255; // correct R lastKelvin = kelvin;
rgbw[1] = ((uint16_t) rgbw[1] * G(rgb)) / 255; // correct G byte rgbw[4];
rgbw[2] = ((uint16_t) rgbw[2] * B(rgb)) / 255; // correct B rgbw[0] = ((uint16_t) correctionRGB[0] * R(rgb)) /255; // correct R
rgbw[1] = ((uint16_t) correctionRGB[1] * G(rgb)) /255; // correct G
rgbw[2] = ((uint16_t) correctionRGB[2] * B(rgb)) /255; // correct B
rgbw[3] = W(rgb); rgbw[3] = W(rgb);
return colorFromRgbw(rgbw); return colorFromRgbw(rgbw);
} }
//approximates a Kelvin color temperature from an RGB color.
//this does no check for the "whiteness" of the color,
//so should be used combined with a saturation check (as done by auto-white)
//values from http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html (10deg)
//equation spreadsheet at https://bit.ly/30RkHaN
//accuracy +-50K from 1900K up to 8000K
//minimum returned: 1900K, maximum returned: 10091K (range of 8192)
uint16_t approximateKelvinFromRGB(uint32_t rgb) {
//if not either red or blue is 255, color is dimmed. Scale up
uint8_t r = R(rgb), b = B(rgb);
if (r == b) return 6550; //red == blue at about 6600K (also can't go further if both R and B are 0)
if (r > b) {
//scale blue up as if red was at 255
uint16_t scale = 0xFFFF / r; //get scale factor (range 257-65535)
b = ((uint16_t)b * scale) >> 8;
//For all temps K<6600 R is bigger than B (for full bri colors R=255)
//-> Use 9 linear approximations for blackbody radiation blue values from 2000-6600K (blue is always 0 below 2000K)
if (b < 33) return 1900 + b *6;
if (b < 72) return 2100 + (b-33) *10;
if (b < 101) return 2492 + (b-72) *14;
if (b < 132) return 2900 + (b-101) *16;
if (b < 159) return 3398 + (b-132) *19;
if (b < 186) return 3906 + (b-159) *22;
if (b < 210) return 4500 + (b-186) *25;
if (b < 230) return 5100 + (b-210) *30;
return 5700 + (b-230) *34;
} else {
//scale red up as if blue was at 255
uint16_t scale = 0xFFFF / b; //get scale factor (range 257-65535)
r = ((uint16_t)r * scale) >> 8;
//For all temps K>6600 B is bigger than R (for full bri colors B=255)
//-> Use 2 linear approximations for blackbody radiation red values from 6600-10091K (blue is always 0 below 2000K)
if (r > 225) return 6600 + (254-r) *50;
uint16_t k = 8080 + (225-r) *86;
return (k > 10091) ? 10091 : k;
}
}

View File

@ -124,7 +124,7 @@
// - 0b010 (dec. 32-47) analog (PWM) // - 0b010 (dec. 32-47) analog (PWM)
// - 0b011 (dec. 48-63) digital (data + clock / SPI) // - 0b011 (dec. 48-63) digital (data + clock / SPI)
// - 0b100 (dec. 64-79) unused/reserved // - 0b100 (dec. 64-79) unused/reserved
// - 0b101 (dec. 80-95) digital (data + clock / SPI) // - 0b101 (dec. 80-95) virtual network busses
// - 0b110 (dec. 96-111) unused/reserved // - 0b110 (dec. 96-111) unused/reserved
// - 0b111 (dec. 112-127) unused/reserved // - 0b111 (dec. 112-127) unused/reserved
//bit 7 is reserved and set to 0 //bit 7 is reserved and set to 0

View File

@ -513,12 +513,24 @@ img {
.sliderdisplay { .sliderdisplay {
content:''; content:'';
position: absolute; position: absolute;
/*
top: 13px; left: 8px; right: 8px; top: 13px; left: 8px; right: 8px;
height: 4px; height: 4px;
*/
top: 12.5px; bottom: 12.5px;
left: 13px; right: 13px;
background: var(--c-4); background: var(--c-4);
border-radius: 16px; border-radius: 16px;
pointer-events: none; pointer-events: none;
z-index: -1; z-index: -1;
--bg: var(--c-f);
}
#rwrap .sliderdisplay { --bg: #f00; }
#gwrap .sliderdisplay { --bg: #0f0; }
#bwrap .sliderdisplay { --bg: #00f; }
#wbal .sliderdisplay, #kwrap .sliderdisplay {
background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #cbdbff);
} }
#rwrap .sliderdisplay, #rwrap .sliderdisplay,
#gwrap .sliderdisplay, #gwrap .sliderdisplay,
@ -530,12 +542,15 @@ img {
left: 0; right: 0; left: 0; right: 0;
/*border: 1px solid var(--c-b);*/ /*border: 1px solid var(--c-b);*/
} }
/*
#rwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #f00); } #rwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #f00); }
#gwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #0f0); } #gwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #0f0); }
#bwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #00f); } #bwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #00f); }
*/
#wwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #fff); } #wwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #fff); }
/*
#wbal .sliderdisplay { background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #d4e0ff); } #wbal .sliderdisplay { background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #d4e0ff); }
*/
.sliderbubble { .sliderbubble {
width: 24px; width: 24px;
position: relative; position: relative;
@ -568,6 +583,7 @@ input[type=range] {
background-color: transparent; background-color: transparent;
cursor: pointer; cursor: pointer;
} }
/*
#rwrap input[type=range], #rwrap input[type=range],
#gwrap input[type=range], #gwrap input[type=range],
#bwrap input[type=range], #bwrap input[type=range],
@ -576,6 +592,7 @@ input[type=range] {
width: 252px; width: 252px;
margin: 0; margin: 0;
} }
*/
input[type=range]:focus { input[type=range]:focus {
outline: none; outline: none;
} }

View File

@ -46,22 +46,35 @@
<div class ="container"> <div class ="container">
<div id="Colors" class="tabcontent"> <div id="Colors" class="tabcontent">
<div id="picker" class="noslide"></div> <div id="picker" class="noslide"></div>
<div id="vwrap" class="IL">
<div class="sliderwrap il">
<input id="sliderV" class="noslide" oninput="fromV()" onchange="setColor(0)" max="100" min="0" type="range" value="100" step="any" />
<div class="sliderdisplay"></div>
</div><br>
</div>
<div id="kwrap">
<div class="sliderwrap il">
<input id="sliderK" class="noslide" oninput="fromK()" onchange="setColor(0)" max="10091" min="1900" type="range" value="6550" />
<div class="sliderdisplay"></div>
</div>
</div>
<div id="rgbwrap"> <div id="rgbwrap">
<p class="labels">RGB color</p>
<div id="rwrap" class="il"> <div id="rwrap" class="il">
<div class="sliderwrap il"> <div class="sliderwrap il">
<input id="sliderR" class="noslide" onchange="fromRgb()" max="255" min="0" type="range" value="128" /> <input id="sliderR" class="noslide" oninput="fromRgb()" onchange="setColor(0)" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div> <div class="sliderdisplay"></div>
</div> </div>
</div><br> </div><br>
<div id="gwrap" class="il"> <div id="gwrap" class="il">
<div class="sliderwrap il"> <div class="sliderwrap il">
<input id="sliderG" class="noslide" onchange="fromRgb()" max="255" min="0" type="range" value="128" /> <input id="sliderG" class="noslide" oninput="fromRgb()" onchange="setColor(0)" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div> <div class="sliderdisplay"></div>
</div> </div>
</div><br> </div><br>
<div id="bwrap" class="il"> <div id="bwrap" class="il">
<div class="sliderwrap il"> <div class="sliderwrap il">
<input id="sliderB" class="noslide" onchange="fromRgb()" max="255" min="0" type="range" value="128" /> <input id="sliderB" class="noslide" oninput="fromRgb()" onchange="setColor(0)" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div> <div class="sliderdisplay"></div>
</div> </div>
</div><br> </div><br>

View File

@ -42,25 +42,12 @@ var hol = [
var cpick = new iro.ColorPicker("#picker", { var cpick = new iro.ColorPicker("#picker", {
width: 260, width: 260,
wheelLightness: false, wheelLightness: false,
wheelAngle: 90, wheelAngle: 270,
layout: [ wheelDirection: "clockwise",
{ layout: [{
component: iro.ui.Wheel, component: iro.ui.Wheel,
options: {} options: {}
}, }]
{
component: iro.ui.Slider,
options: { sliderType: 'value' }
}/*,
{
component: iro.ui.Slider,
options: {
sliderType: 'kelvin',
minTemperature: 2000,
maxTemperature: 10160
}
}*/
]
}); });
function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();} function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();}
@ -78,6 +65,8 @@ function applyCfg()
var ccfg = cfg.comp.colors; var ccfg = cfg.comp.colors;
gId('hexw').style.display = ccfg.hex ? "block":"none"; gId('hexw').style.display = ccfg.hex ? "block":"none";
gId('picker').style.display = ccfg.picker ? "block":"none"; gId('picker').style.display = ccfg.picker ? "block":"none";
gId('vwrap').style.display = ccfg.picker ? "block":"none";
gId('kwrap').style.display = ccfg.picker ? "block":"none";
gId('rgbwrap').style.display = ccfg.rgb ? "block":"none"; gId('rgbwrap').style.display = ccfg.rgb ? "block":"none";
gId('qcs-w').style.display = ccfg.quick ? "block":"none"; gId('qcs-w').style.display = ccfg.quick ? "block":"none";
var l = cfg.comp.labels; var l = cfg.comp.labels;
@ -204,8 +193,7 @@ function onLoad()
if (window.location.protocol == "file:") { if (window.location.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) if (!locip) {
{
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
@ -247,6 +235,7 @@ function onLoad()
cpick.on("input:end", function() { cpick.on("input:end", function() {
setColor(1); setColor(1);
}); });
cpick.on("color:change", updatePSliders);
pmtLS = localStorage.getItem('wledPmt'); pmtLS = localStorage.getItem('wledPmt');
// Load initial data // Load initial data
@ -992,6 +981,7 @@ function loadNodes()
}); });
} }
//update the 'sliderdisplay' background div of a slider for a visual indication of slider position
function updateTrail(e) function updateTrail(e)
{ {
if (e==null) return; if (e==null) return;
@ -1005,12 +995,14 @@ function updateTrail(e)
if (b) b.innerHTML = e.value; if (b) b.innerHTML = e.value;
} }
//rangetouch slider function
function toggleBubble(e) function toggleBubble(e)
{ {
var b = e.target.parentNode.parentNode.getElementsByTagName('output')[0]; var b = e.target.parentNode.parentNode.getElementsByTagName('output')[0];
b.classList.toggle('sliderbubbleshow'); b.classList.toggle('sliderbubbleshow');
} }
//updates segment length upon input of segment values
function updateLen(s) function updateLen(s)
{ {
if (!gId(`seg${s}s`)) return; if (!gId(`seg${s}s`)) return;
@ -1036,6 +1028,7 @@ function updateLen(s)
gId(`seg${s}len`).innerHTML = out; gId(`seg${s}len`).innerHTML = out;
} }
//updates background color of currently selected preset
function updatePA() function updatePA()
{ {
var ps = gEBCN("pres"); var ps = gEBCN("pres");
@ -1081,10 +1074,10 @@ function updateUI()
gId('wwrap').style.display = (isRgbw) ? "block":"none"; gId('wwrap').style.display = (isRgbw) ? "block":"none";
gId("wbal").style.display = (cct) ? "block":"none"; gId("wbal").style.display = (cct) ? "block":"none";
gId('kwrap').style.display = (lastinfo.leds.cct) ? "none":"block";
updatePA(); updatePA();
updateHex(); updatePSliders();
updateRgb();
} }
function updateSelectedPalette() function updateSelectedPalette()
@ -1997,8 +1990,7 @@ function selectSlot(b)
cd[csel].classList.add('xxs-w'); cd[csel].classList.add('xxs-w');
cpick.color.set(cd[csel].style.backgroundColor); cpick.color.set(cd[csel].style.backgroundColor);
gId('sliderW').value = whites[csel]; gId('sliderW').value = whites[csel];
updateHex(); updatePSliders();
updateRgb();
} }
var lasth = 0; var lasth = 0;
@ -2016,21 +2008,32 @@ function pC(col)
setColor(0); setColor(0);
} }
function updateRgb() function updatePSliders() {
{ //update RGB sliders
var col = cpick.color.rgb; var col = cpick.color.rgb;
gId('sliderR').value = col.r; gId('sliderR').value = col.r;
gId('sliderG').value = col.g; gId('sliderG').value = col.g;
gId('sliderB').value = col.b; gId('sliderB').value = col.b;
}
function updateHex() //update hex field
{ var str = cpick.color.hexString.substring(1);
var str = cpick.color.hexString;
str = str.substring(1);
var w = whites[csel]; var w = whites[csel];
if (w > 0) str += w.toString(16); if (w > 0) str += w.toString(16);
gId('hexc').value = str; gId('hexc').value = str;
gId('hexcnf').style.backgroundColor = "var(--c-3)";
//update value slider
var v = gId('sliderV');
v.value = cpick.color.value;
//background color as if color had full value
var hsv = {"h":cpick.color.hue,"s":cpick.color.saturation,"v":100};
var c = iro.Color.hsvToRgb(hsv);
var cs = 'rgb('+c.r+','+c.g+','+c.b+')';
v.parentNode.getElementsByClassName('sliderdisplay')[0].style.setProperty('--bg',cs);
updateTrail(v);
//update Kelvin slider
gId('sliderK').value = cpick.color.kelvin;
} }
function hexEnter() function hexEnter()
@ -2051,16 +2054,25 @@ function fromHex()
setColor(2); setColor(2);
} }
function fromV()
{
cpick.color.setChannel('hsv', 'v', d.getElementById('sliderV').value);
}
function fromK()
{
cpick.color.set({ kelvin: d.getElementById('sliderK').value });
}
function fromRgb() function fromRgb()
{ {
var r = gId('sliderR').value; var r = gId('sliderR').value;
var g = gId('sliderG').value; var g = gId('sliderG').value;
var b = gId('sliderB').value; var b = gId('sliderB').value;
cpick.color.set(`rgb(${r},${g},${b})`); cpick.color.set(`rgb(${r},${g},${b})`);
setColor(0);
} }
// sets color from picker: 0=all, 1=leaving picker/HSV, 2=ignore white channel //sr 0: from RGB sliders, 1: from picker, 2: from hex
function setColor(sr) function setColor(sr)
{ {
var cd = gId('csl').children; // color slots var cd = gId('csl').children; // color slots
@ -2074,8 +2086,12 @@ function setColor(sr)
} else if (csel == 2) { } else if (csel == 2) {
obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}}; obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}};
} }
updateHex(); requestJson(obj);
updateRgb(); }
function setBalance(b)
{
var obj = {"seg": {"cct": parseInt(b)}};
requestJson(obj); requestJson(obj);
} }

File diff suppressed because one or more lines are too long

View File

@ -21,21 +21,12 @@
border-radius: var(--h); border-radius: var(--h);
font-size: 6vmin; font-size: 6vmin;
height: var(--h); height: var(--h);
width: 95%; width: calc(100% - 40px);
margin-top: 2vh; margin-top: 2vh;
} }
</style> </style>
<script>
function BB()
{
if (window.frameElement) {
document.getElementById("b").style.display = "none";
document.documentElement.style.setProperty('--h',"13.86vh");
}
}
</script>
</head> </head>
<body onload="BB()"> <body>
<form action="/"><button type=submit id="b">Back</button></form> <form action="/"><button type=submit id="b">Back</button></form>
<form action="/settings/wifi"><button type="submit">WiFi Setup</button></form> <form action="/settings/wifi"><button type="submit">WiFi Setup</button></form>
<form action="/settings/leds"><button type="submit">LED Preferences</button></form> <form action="/settings/leds"><button type="submit">LED Preferences</button></form>

View File

@ -306,10 +306,11 @@ ${i+1}:
<option value="52">LPD8806</option> <option value="52">LPD8806</option>
<option value="53">P9813</option> <option value="53">P9813</option>
<option value="41">PWM White</option> <option value="41">PWM White</option>
<option value="42">PWM WWCW</option> <option value="42">PWM CCT</option>
<option value="43">PWM RGB</option> <option value="43">PWM RGB</option>
<option value="44">PWM RGBW</option> <option value="44">PWM RGBW</option>
<option value="45">PWM RGBWC</option> <option value="45">PWM RGB+CCT</option>
<!--option value="46">PWM RGB+DCCT</option-->
<option value="80">DDP RGB (network)</option> <option value="80">DDP RGB (network)</option>
<!--option value="81">E1.31 RGB (network)</option--> <!--option value="81">E1.31 RGB (network)</option-->
<!--option value="82">ArtNet RGB (network)</option--> <!--option value="82">ArtNet RGB (network)</option-->
@ -334,8 +335,7 @@ ${i+1}:
<span id="p4d${i}"></span><input type="number" name="L4${i}" min="0" max="33" class="s" onchange="UI()"/> <span id="p4d${i}"></span><input type="number" name="L4${i}" min="0" max="33" class="s" onchange="UI()"/>
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div> <div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"></div> <div id="dig${i}s" style="display:inline"><br>Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"></div>
<div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}">&nbsp;</div> <div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
<div id="dig${i}a" style="display:inline"><br>Auto-calculate white channel from RGB:<br><select name="AW${i}"><option value=0>None</option><option value=1>Brighter</option><option value=2>Accurate</option><option value=3>Dual</option></select>&nbsp;</div>
</div>`; </div>`;
f.insertAdjacentHTML("beforeend", cn); f.insertAdjacentHTML("beforeend", cn);
} }
@ -402,9 +402,9 @@ ${i+1}:
} }
if (!o.files) { if (!o.files) {
alert("This browser doesn't seem to support the `files` property of file inputs."); alert("This browser doesn't support the `files` property of file inputs.");
} else if (!o.files[0]) { } else if (!o.files[0]) {
alert("Please select a JSON file before clicking 'Apply'"); alert("Please select a JSON file first!");
} else { } else {
f = o.files[0]; f = o.files[0];
fr = new FileReader(); fr = new FileReader();
@ -538,7 +538,7 @@ ${i+1}:
<br><br> <br><br>
Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br> Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br>
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br> Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br>
Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> % Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %%
<h3>Transitions</h3> <h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br> Crossfade: <input type="checkbox" name="TF"><br>
Transition Time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br> Transition Time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
@ -553,6 +553,19 @@ ${i+1}:
<option value="2">Fade Color</option> <option value="2">Fade Color</option>
<option value="3">Sunrise</option> <option value="3">Sunrise</option>
</select> </select>
<h3>White management</h3>
White Balance correction: <input type="checkbox" name="CCT"> <br>
<span class="wc">
Auto-calculate white channel from RGB:<br>
<select name="AW">
<option value=0>None</option>
<option value=1>Brighter</option>
<option value=2>Accurate</option>
<option value=3>Dual</option>
</select>
<br>
Calculate CCT from RGB: <input type="checkbox" name="CR"> <br>
CCT additive blending: <input type="number" class="s" min="0" max="100" name="CB" required> %%</span>
<h3>Advanced</h3> <h3>Advanced</h3>
Palette blending: Palette blending:
<select name="PB"> <select name="PB">

View File

@ -70,8 +70,8 @@ void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TOD
void colorFromDecOrHexString(byte* rgb, char* in); void colorFromDecOrHexString(byte* rgb, char* in);
bool colorFromHexString(byte* rgb, const char* in); bool colorFromHexString(byte* rgb, const char* in);
void colorBalanceFromKelvin(uint16_t kelvin, byte *rgb);
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
//dmx.cpp //dmx.cpp
void initDMX(); void initDMX();

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -93,7 +93,9 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
if (elem["frz"].is<const char*>() && elem["frz"].as<const char*>()[0] == 't') frz = !seg.getOption(SEG_OPTION_FREEZE); if (elem["frz"].is<const char*>() && elem["frz"].as<const char*>()[0] == 't') frz = !seg.getOption(SEG_OPTION_FREEZE);
seg.setOption(SEG_OPTION_FREEZE, frz, id); seg.setOption(SEG_OPTION_FREEZE, frz, id);
seg.cct = elem["cct"] | seg.cct; uint8_t cctPrev = seg.cct;
seg.setCCT(elem["cct"] | seg.cct, id);
if (seg.cct != cctPrev && id == strip.getMainSegmentId()) effectChanged = true; //send UDP
JsonArray colarr = elem["col"]; JsonArray colarr = elem["col"];
if (!colarr.isNull()) if (!colarr.isNull())
@ -504,23 +506,14 @@ void serializeInfo(JsonObject root)
leds[F("count")] = strip.getLengthTotal(); leds[F("count")] = strip.getLengthTotal();
leds[F("rgbw")] = strip.isRgbw; leds[F("rgbw")] = strip.isRgbw;
leds[F("wv")] = false; leds[F("wv")] = false;
leds["cct"] = allowCCT;
for (uint8_t s = 0; s < busses.getNumBusses(); s++) { leds["cct"] = correctWB || strip.hasCCTBus();
Bus *bus = busses.getBus(s); switch (Bus::getAutoWhiteMode()) {
if (bus == nullptr || bus->getLength()==0) break;
switch (bus->getType()) {
case TYPE_ANALOG_5CH:
case TYPE_ANALOG_2CH:
leds["cct"] = true;
break;
}
switch (bus->getAutoWhiteMode()) {
case RGBW_MODE_MANUAL_ONLY: case RGBW_MODE_MANUAL_ONLY:
case RGBW_MODE_DUAL: case RGBW_MODE_DUAL:
if (bus->isRgbw()) leds[F("wv")] = true; if (strip.isRgbw) leds[F("wv")] = true;
break; break;
} }
}
JsonArray leds_pin = leds.createNestedArray("pin"); JsonArray leds_pin = leds.createNestedArray("pin");
for (uint8_t s=0; s<busses.getNumBusses(); s++) { for (uint8_t s=0; s<busses.getNumBusses(); s++) {

View File

@ -119,7 +119,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
void handlePlaylist() { void handlePlaylist() {
static unsigned long presetCycledTime = 0; static unsigned long presetCycledTime = 0;
if (currentPlaylist<0 || playlistEntries == nullptr) return; if (currentPlaylist < 0 || playlistEntries == nullptr) return;
if (millis() - presetCycledTime > (100*playlistEntryDur)) { if (millis() - presetCycledTime > (100*playlistEntryDur)) {
presetCycledTime = millis(); presetCycledTime = millis();

View File

@ -72,7 +72,11 @@ 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")); correctWB = request->hasArg(F("CCT"));
cctFromRgb = request->hasArg(F("CR"));
strip.cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending);
Bus::setAutoWhiteMode(request->arg(F("AW")).toInt());
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
@ -95,7 +99,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
type = request->arg(lt).toInt(); type = request->arg(lt).toInt();
type |= request->hasArg(rf) << 7; // off refresh override type |= request->hasArg(rf) << 7; // off refresh override
skip = request->hasArg(sl) ? LED_SKIP_AMOUNT : 0; skip = request->hasArg(sl) ? LED_SKIP_AMOUNT : 0;
awMode = request->arg(aw).toInt();
colorOrder = request->arg(co).toInt(); colorOrder = request->arg(co).toInt();
start = (request->hasArg(ls)) ? request->arg(ls).toInt() : t; start = (request->hasArg(ls)) ? request->arg(ls).toInt() : t;
if (request->hasArg(lc) && request->arg(lc).toInt() > 0) { if (request->hasArg(lc) && request->arg(lc).toInt() > 0) {
@ -599,6 +602,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//apply preset //apply preset
if (updateVal(&req, "PL=", &presetCycCurr, presetCycMin, presetCycMax)) { if (updateVal(&req, "PL=", &presetCycCurr, presetCycMin, presetCycMax)) {
unloadPlaylist();
applyPreset(presetCycCurr); applyPreset(presetCycCurr);
} }

View File

@ -22,7 +22,7 @@ private:
ColorCallbackFunction _callbackCol = nullptr; ColorCallbackFunction _callbackCol = nullptr;
uint8_t _val, _val_last, _sat = 0; uint8_t _val, _val_last, _sat = 0;
uint16_t _hue = 0, _ct = 0; uint16_t _hue = 0, _ct = 0;
float _x = 0.5, _y = 0.5; float _x = 0.5f, _y = 0.5f;
uint32_t _rgb = 0; uint32_t _rgb = 0;
uint8_t _id = 0; uint8_t _id = 0;
EspalexaDeviceType _type; EspalexaDeviceType _type;

View File

@ -4,7 +4,7 @@
* UDP sync notifier / Realtime / Hyperion / TPM2.NET * UDP sync notifier / Realtime / Hyperion / TPM2.NET
*/ */
#define WLEDPACKETSIZE 37 #define WLEDPACKETSIZE 39
#define UDP_IN_MAXSIZE 1472 #define UDP_IN_MAXSIZE 1472
#define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times #define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times
@ -25,6 +25,7 @@ void notify(byte callMode, bool followUp)
default: return; default: return;
} }
byte udpOut[WLEDPACKETSIZE]; byte udpOut[WLEDPACKETSIZE];
WS2812FX::Segment& mainseg = strip.getSegment(strip.getMainSegmentId());
udpOut[0] = 0; //0: wled notifier protocol 1: WARLS protocol udpOut[0] = 0; //0: wled notifier protocol 1: WARLS protocol
udpOut[1] = callMode; udpOut[1] = callMode;
udpOut[2] = bri; udpOut[2] = bri;
@ -40,8 +41,8 @@ void notify(byte callMode, bool followUp)
//0: old 1: supports white 2: supports secondary color //0: old 1: supports white 2: supports secondary color
//3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette //3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette
//6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet //6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet
//9: supports sync groups, 37 byte packet //9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet
udpOut[11] = 9; udpOut[11] = 10;
udpOut[12] = colSec[0]; udpOut[12] = colSec[0];
udpOut[13] = colSec[1]; udpOut[13] = colSec[1];
udpOut[14] = colSec[2]; udpOut[14] = colSec[2];
@ -50,7 +51,7 @@ void notify(byte callMode, bool followUp)
udpOut[17] = (transitionDelay >> 0) & 0xFF; udpOut[17] = (transitionDelay >> 0) & 0xFF;
udpOut[18] = (transitionDelay >> 8) & 0xFF; udpOut[18] = (transitionDelay >> 8) & 0xFF;
udpOut[19] = effectPalette; udpOut[19] = effectPalette;
uint32_t colTer = strip.getSegment(strip.getMainSegmentId()).colors[2]; uint32_t colTer = mainseg.colors[2];
udpOut[20] = (colTer >> 16) & 0xFF; udpOut[20] = (colTer >> 16) & 0xFF;
udpOut[21] = (colTer >> 8) & 0xFF; udpOut[21] = (colTer >> 8) & 0xFF;
udpOut[22] = (colTer >> 0) & 0xFF; udpOut[22] = (colTer >> 0) & 0xFF;
@ -78,6 +79,11 @@ void notify(byte callMode, bool followUp)
//sync groups //sync groups
udpOut[36] = syncGroups; udpOut[36] = syncGroups;
//Might be changed to Kelvin in the future, receiver code should handle that case
//0: byte 38 contains 0-255 value, 255: no valid CCT, 1-254: Kelvin value MSB
udpOut[37] = strip.hasCCTBus() ? 0 : 255; //check this is 0 for the next value to be significant
udpOut[38] = mainseg.cct;
IPAddress broadcastIp; IPAddress broadcastIp;
broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP()); broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP());
@ -260,6 +266,14 @@ void handleNotifications()
{ {
strip.setColor(2, udpIn[20], udpIn[21], udpIn[22], udpIn[23]); //tertiary color strip.setColor(2, udpIn[20], udpIn[21], udpIn[22], udpIn[23]); //tertiary color
} }
if (version > 9 && version < 200 && udpIn[37] < 255) { //valid CCT/Kelvin value
uint8_t cct = udpIn[38];
if (udpIn[37] > 0) { //Kelvin
cct = (((udpIn[37] << 8) + udpIn[38]) - 1900) >> 5;
}
uint8_t segid = strip.getMainSegmentId();
strip.getSegment(segid).setCCT(cct, segid);
}
} }
} }

View File

@ -272,7 +272,8 @@ 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 bool correctWB _INIT(false); //CCT color correction of RGB color
WLED_GLOBAL bool cctFromRgb _INIT(false); //CCT is calculated from RGB instead of using seg.cct
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

View File

@ -332,7 +332,10 @@ void getSettingsJS(byte subPage, char* dest)
oappend(SET_F(");")); oappend(SET_F(");"));
sappend('c',SET_F("MS"),autoSegments); sappend('c',SET_F("MS"),autoSegments);
sappend('c',SET_F("CCT"),allowCCT); sappend('c',SET_F("CCT"),correctWB);
sappend('c',SET_F("CR"),cctFromRgb);
sappend('v',SET_F("CB"),strip.cctBlending);
sappend('v',SET_F("AW"),Bus::getAutoWhiteMode());
for (uint8_t s=0; s < busses.getNumBusses(); s++) { for (uint8_t s=0; s < busses.getNumBusses(); s++) {
Bus* bus = busses.getBus(s); Bus* bus = busses.getBus(s);