diff --git a/platformio.ini b/platformio.ini index e700ed2be..371403303 100644 --- a/platformio.ini +++ b/platformio.ini @@ -434,6 +434,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 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 # ------------------------------------------------------------------------------ diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 09cef8328..d0767713b 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1150,10 +1150,10 @@ uint16_t WS2812FX::mode_fire_flicker(void) { uint32_t it = now / cycleTime; if (SEGENV.step == it) return FRAMETIME; - byte w = (SEGCOLOR(0) >> 24) & 0xFF; - byte r = (SEGCOLOR(0) >> 16) & 0xFF; - byte g = (SEGCOLOR(0) >> 8) & 0xFF; - byte b = (SEGCOLOR(0) & 0xFF); + byte w = (SEGCOLOR(0) >> 24); + byte r = (SEGCOLOR(0) >> 16); + byte g = (SEGCOLOR(0) >> 8); + byte b = (SEGCOLOR(0) ); byte lum = (SEGMENT.palette == 0) ? MAX(w, MAX(r, MAX(g, b))) : 255; lum /= (((256-SEGMENT.intensity)/16)+1); for(uint16_t i = 0; i < SEGLEN; i++) { @@ -2117,7 +2117,7 @@ typedef struct Ripple { #endif uint16_t WS2812FX::ripple_base(bool rainbow) { - uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 18 segment ESP8266 + uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266 uint16_t dataSize = sizeof(ripple) * maxRipples; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -2900,7 +2900,6 @@ uint16_t WS2812FX::mode_starburst(void) { return FRAMETIME; } #undef STARBURST_MAX_FRAG -#undef STARBURST_MAX_STARS /* * Exploding fireworks effect @@ -3645,7 +3644,7 @@ typedef struct Spotlight { */ uint16_t WS2812FX::mode_dancing_shadows(void) { - uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 18 segment ESP8266 + uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266 bool initialize = SEGENV.aux0 != numSpotlights; SEGENV.aux0 = numSpotlights; @@ -3784,7 +3783,7 @@ uint16_t WS2812FX::mode_washing_machine(void) { Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e */ uint16_t WS2812FX::mode_blends(void) { - uint16_t dataSize = sizeof(uint32_t) * SEGLEN; // max segment length of 56 pixels on 18 segment ESP8266 + uint16_t dataSize = sizeof(uint32_t) * SEGLEN; // max segment length of 56 pixels on 16 segment ESP8266 if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed uint32_t* pixels = reinterpret_cast(SEGENV.data); uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); @@ -4037,7 +4036,7 @@ uint16_t WS2812FX::mode_aurora(void) { SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); SEGENV.aux0 = SEGMENT.intensity; - if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 18 segment ESP8266 + if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 16 segment ESP8266 return mode_static(); //allocation failed } diff --git a/wled00/FX.h b/wled00/FX.h index 2c1274061..14e486b19 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -165,12 +165,12 @@ #define FX_MODE_FIRE_FLICKER 45 #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 48 // candidate for removal (after below three) +#define FX_MODE_POLICE_ALL 49 // candidate for removal #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_HALLOWEEN 53 +#define FX_MODE_HALLOWEEN 53 // candidate for removal #define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_WIPE 55 #define FX_MODE_TRICOLOR_FADE 56 @@ -231,7 +231,7 @@ #define FX_MODE_CHUNCHUN 111 #define FX_MODE_DANCING_SHADOWS 112 #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_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 @@ -247,35 +247,44 @@ class WS2812FX { // segment parameters public: - typedef struct Segment { // 29 (32 in memory?) bytes + typedef struct Segment { // 30 (32 in memory) bytes uint16_t start; uint16_t stop; //segment invalid if stop == 0 uint16_t offset; - uint8_t speed; - uint8_t intensity; - uint8_t palette; - uint8_t mode; - uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected - uint8_t grouping, spacing; - uint8_t opacity; + uint8_t speed; + uint8_t intensity; + uint8_t palette; + uint8_t mode; + uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected + uint8_t grouping, spacing; + uint8_t opacity; uint32_t colors[NUM_COLORS]; + uint8_t cct; //0==1900K, 255==10091K 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; 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; } + 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) { if (segn >= MAX_NUM_SEGMENTS) return; if (opacity == o) return; ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0); 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) { bool prevOn = false; @@ -445,7 +454,7 @@ class WS2812FX { if (t.segment == s) //this is an active transition on the same segment+color { bool wasTurningOff = (oldBri == 0); - t.briOld = t.currentBri(wasTurningOff); + t.briOld = t.currentBri(wasTurningOff, slot); t.colorOld = t.currentColor(oldCol); } else { t.briOld = oldBri; @@ -477,11 +486,15 @@ class WS2812FX { uint32_t currentColor(uint32_t colorNew) { 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; if (segn >= MAX_NUM_SEGMENTS) return 0; uint8_t briNew = instance->_segments[segn].opacity; - if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0; + if (slot == 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; return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16; } @@ -652,15 +665,16 @@ class WS2812FX { applyToAllSelected = true, setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p), checkSegmentAlignment(void), + hasCCTBus(void), // return true if the strip is being sent pixel updates isUpdating(void); uint8_t mainSegment = 0, - rgbwMode = RGBW_MODE_DUAL, paletteFade = 0, paletteBlend = 0, milliampsPerLed = 55, + cctBlending = 0, getBrightness(void), getMode(void), getSpeed(void), diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 82a2283e5..9077cdaa6 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -139,13 +139,16 @@ void WS2812FX::service() { if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen _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]; + uint8_t _cct_t = SEGMENT.cct; if (!IS_SEGMENT_ON) _bri_t = 0; for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) { if ((transitions[t].segment & 0x3F) != i) continue; uint8_t slot = transitions[t].segment >> 6; 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]); } + if (!cctFromRgb || correctWB) busses.setSegmentCCT(_cct_t, correctWB); for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]); handle_palette(); delay = (this->*_mode[SEGMENT.mode])(); //effect function @@ -156,6 +159,7 @@ void WS2812FX::service() { } } _virtualSegmentLength = 0; + busses.setSegmentCCT(-1); if(doShow) { yield(); show(); @@ -164,11 +168,7 @@ void WS2812FX::service() { } void WS2812FX::setPixelColor(uint16_t n, uint32_t c) { - uint8_t w = (c >> 24); - uint8_t r = (c >> 16); - uint8_t g = (c >> 8); - uint8_t b = c ; - setPixelColor(n, r, g, b, w); + setPixelColor(n, R(c), G(c), B(c), W(c)); } //used to map from segment index to physical pixel, taking into account grouping, offsets, reverse and mirroring @@ -191,21 +191,10 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) { void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) { - //auto calculate white channel value if enabled - if (isRgbw) { - if (rgbwMode == RGBW_MODE_AUTO_BRIGHTER || (w == 0 && (rgbwMode == RGBW_MODE_DUAL || rgbwMode == RGBW_MODE_LEGACY))) - { - //white value is set to lowest RGB channel - //thank you to @Def3nder! - w = r < g ? (r < b ? r : b) : (g < b ? g : b); - } else if (rgbwMode == RGBW_MODE_AUTO_ACCURATE && w == 0) - { - w = r < g ? (r < b ? r : b) : (g < b ? g : b); - r -= w; g -= w; b -= w; - } - } - if (SEGLEN) {//from segment + uint16_t realIndex = realPixelIndex(i); + uint16_t len = SEGMENT.length(); + //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments) if (_bri_t < 255) { r = scale8(r, _bri_t); @@ -213,12 +202,9 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) b = scale8(b, _bri_t); w = scale8(w, _bri_t); } - uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b)); + uint32_t col = RGBW32(r, g, b, w); /* Set all the pixels in the group */ - uint16_t realIndex = realPixelIndex(i); - uint16_t len = SEGMENT.length(); - for (uint16_t j = 0; j < SEGMENT.grouping; j++) { uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j); if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) { @@ -241,8 +227,7 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) } } else { //live data, etc. if (i < customMappingSize) i = customMappingTable[i]; - uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b)); - busses.setPixelColor(i, col); + busses.setPixelColor(i, RGBW32(r, g, b, w)); } } @@ -295,7 +280,7 @@ void WS2812FX::estimateCurrentAndLimitBri() { uint32_t busPowerSum = 0; for (uint16_t i = 0; i < len; i++) { //sum up the usage of each LED uint32_t c = bus->getPixelColor(i); - byte r = c >> 16, g = c >> 8, b = c, w = c >> 24; + byte r = R(c), g = G(c), b = B(c), w = W(c); if(useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation busPowerSum += (MAX(MAX(r,g),b)) * 3; @@ -431,7 +416,7 @@ bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t p) { } void WS2812FX::setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { - setColor(slot, ((uint32_t)w << 24) |((uint32_t)r << 16) | ((uint32_t)g << 8) | b); + setColor(slot, RGBW32(r, g, b, w)); } void WS2812FX::setColor(uint8_t slot, uint32_t c) { @@ -568,6 +553,20 @@ uint16_t WS2812FX::getLengthPhysical(void) { 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) { if (n >= MAX_NUM_SEGMENTS) return; Segment& seg = _segments[n]; @@ -622,6 +621,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 = 127; for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) { @@ -629,6 +629,7 @@ void WS2812FX::resetSegments() { _segments[i].grouping = 1; _segments[i].setOption(SEG_OPTION_ON, 1); _segments[i].opacity = 255; + _segments[i].cct = 127; _segments[i].speed = DEFAULT_SPEED; _segments[i].intensity = DEFAULT_INTENSITY; _segment_runtimes[i].reset(); @@ -752,22 +753,22 @@ uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend, if(blend == blendmax) return color2; uint8_t shift = b16 ? 16 : 8; - uint32_t w1 = (color1 >> 24) & 0xFF; - uint32_t r1 = (color1 >> 16) & 0xFF; - uint32_t g1 = (color1 >> 8) & 0xFF; - uint32_t b1 = color1 & 0xFF; + uint32_t w1 = W(color1); + uint32_t r1 = R(color1); + uint32_t g1 = G(color1); + uint32_t b1 = B(color1); - uint32_t w2 = (color2 >> 24) & 0xFF; - uint32_t r2 = (color2 >> 16) & 0xFF; - uint32_t g2 = (color2 >> 8) & 0xFF; - uint32_t b2 = color2 & 0xFF; + uint32_t w2 = W(color2); + uint32_t r2 = R(color2); + uint32_t g2 = G(color2); + uint32_t b2 = B(color2); uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift; uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift; uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift; uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift; - return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3)); + return RGBW32(r3, g3, b3, w3); } /* @@ -795,17 +796,17 @@ void WS2812FX::fade_out(uint8_t rate) { float mappedRate = float(rate) +1.1; uint32_t color = SEGCOLOR(1); // target color - int w2 = (color >> 24) & 0xff; - int r2 = (color >> 16) & 0xff; - int g2 = (color >> 8) & 0xff; - int b2 = color & 0xff; + int w2 = W(color); + int r2 = R(color); + int g2 = G(color); + int b2 = B(color); for(uint16_t i = 0; i < SEGLEN; i++) { color = getPixelColor(i); - int w1 = (color >> 24) & 0xff; - int r1 = (color >> 16) & 0xff; - int g1 = (color >> 8) & 0xff; - int b1 = color & 0xff; + int w1 = W(color); + int r1 = R(color); + int g1 = G(color); + int b1 = B(color); int wdelta = (w2 - w1) / mappedRate; int rdelta = (r2 - r1) / mappedRate; @@ -839,9 +840,9 @@ void WS2812FX::blur(uint8_t blur_amount) cur += carryover; if(i > 0) { uint32_t c = getPixelColor(i-1); - uint8_t r = (c >> 16 & 0xFF); - uint8_t g = (c >> 8 & 0xFF); - uint8_t b = (c & 0xFF); + uint8_t r = R(c); + uint8_t g = G(c); + uint8_t b = B(c); setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue)); } setPixelColor(i,cur.red, cur.green, cur.blue); @@ -924,16 +925,16 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { uint32_t WS2812FX::crgb_to_col(CRGB fastled) { - return (((uint32_t)fastled.red << 16) | ((uint32_t)fastled.green << 8) | fastled.blue); + return RGBW32(fastled.red, fastled.green, fastled.blue, 0); } CRGB WS2812FX::col_to_crgb(uint32_t color) { CRGB fastled_col; - fastled_col.red = (color >> 16 & 0xFF); - fastled_col.green = (color >> 8 & 0xFF); - fastled_col.blue = (color & 0xFF); + fastled_col.red = R(color); + fastled_col.green = G(color); + fastled_col.blue = B(color); return fastled_col; } @@ -1153,15 +1154,20 @@ uint8_t WS2812FX::gamma8(uint8_t b) uint32_t WS2812FX::gamma32(uint32_t color) { if (!gammaCorrectCol) return color; - uint8_t w = (color >> 24); - uint8_t r = (color >> 16); - uint8_t g = (color >> 8); - uint8_t b = color; + uint8_t w = W(color); + uint8_t r = R(color); + uint8_t g = G(color); + uint8_t b = B(color); w = gammaT[w]; r = gammaT[r]; g = gammaT[g]; b = gammaT[b]; - return ((w << 24) | (r << 16) | (g << 8) | (b)); + return RGBW32(r, g, b, w); } -WS2812FX* WS2812FX::instance = nullptr; \ No newline at end of file +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; \ No newline at end of file diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index 2e5b9b17b..1b1d8b6df 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -73,17 +73,26 @@ void onAlexaChange(EspalexaDevice* dev) if (espalexaDevice->getColorMode() == EspalexaColorMode::ct) //shade of white { 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 + } else if (strip.isRgbw) { 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 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 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; + default : colorKtoRGB(k, col); } } else { - colorCTtoRGB(ct, col); + colorKtoRGB(k, col); } } else { uint32_t color = espalexaDevice->getRGB(); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index c8bb2d1c7..c3d913270 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -10,6 +10,10 @@ #include "bus_wrapper.h" #include +//colors.cpp +uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); +void colorRGBtoRGBW(byte* rgb); + // enable additional debug output #ifdef WLED_DEBUG #ifndef ESP8266 @@ -28,13 +32,20 @@ #define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit))) #define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit)))) +//color mangling macros +#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) +#define R(c) (byte((c) >> 16)) +#define G(c) (byte((c) >> 8)) +#define B(c) (byte(c)) +#define W(c) (byte((c) >> 24)) + //temporary struct for passing bus configuration to bus struct BusConfig { uint8_t type = TYPE_WS2812_RGB; - uint16_t count = 1; - uint16_t start = 0; - uint8_t colorOrder = COL_ORDER_GRB; - bool reversed = false; + uint16_t count; + uint16_t start; + uint8_t colorOrder; + bool reversed; uint8_t skipAmount; bool refreshReq; uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; @@ -62,82 +73,80 @@ struct BusConfig { } }; -//parent class of BusDigital and BusPwm +//parent class of BusDigital, BusPwm, and BusNetwork 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 setStatusPixel(uint32_t c) {} + virtual void setPixelColor(uint16_t pix, uint32_t c) {} + 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; } + inline uint16_t getLength() { return _len; } + virtual void setColorOrder() {} + virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } + virtual uint8_t skippedLeds() { 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; } + bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; } - virtual uint32_t getPixelColor(uint16_t pix) { return 0; }; + virtual bool isRgbw() { return Bus::isRgbw(_type); } + 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; + } + 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; } - virtual void cleanup() {}; - - 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; + uint16_t _len = 1; + bool _valid = false; + bool _needsRefresh = false; + static uint8_t _autoWhiteMode; + static int16_t _cct; + static uint8_t _cctBlend; + + uint32_t autoWhiteCalc(uint32_t 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 g = G(c); + uint8_t b = B(c); + 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); + } }; @@ -184,7 +193,18 @@ class BusDigital : public Bus { 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) { + if (_skip && canShow()) { + PolyBus::setPixelColor(_busPtr, _iType, 0, c, _colorOrder); + PolyBus::show(_busPtr, _iType); + } + } + void setPixelColor(uint16_t pix, uint32_t 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; else pix += _skip; PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder); @@ -215,10 +235,6 @@ class BusDigital : public Bus { _colorOrder = colorOrder; } - inline bool isRgbw() { - return Bus::isRgbw(_type); - } - inline uint8_t skippedLeds() { return _skip; } @@ -245,7 +261,6 @@ class BusDigital : public Bus { uint8_t _colorOrder = COL_ORDER_GRB; uint8_t _pins[2] = {255, 255}; uint8_t _iType = I_NONE; - uint16_t _len = 0; uint8_t _skip = 0; void * _busPtr = nullptr; }; @@ -287,29 +302,57 @@ class BusPwm : public Bus { void setPixelColor(uint16_t pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel - uint8_t r = c >> 16; - uint8_t g = c >> 8; - uint8_t b = c ; - uint8_t w = c >> 24; + 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 g = G(c); + uint8_t b = B(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) { - 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_3CH: //standard dumb RGB + case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation + _data[0] = w; + break; + case TYPE_ANALOG_2CH: //warm white + cold white + _data[1] = cw; + _data[0] = ww; + break; + case TYPE_ANALOG_5CH: //RGB + warm white + cold white + // perhaps a non-linear adjustment would be in order. need to test + _data[4] = cw; + w = ww; case TYPE_ANALOG_4CH: //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; + _data[3] = w; + case TYPE_ANALOG_3CH: //standard dumb RGB + _data[0] = r; _data[1] = g; _data[2] = b; + break; } } //does no index check uint32_t getPixelColor(uint16_t pix) { if (!_valid) return 0; - return ((_data[3] << 24) | (_data[0] << 16) | (_data[1] << 8) | (_data[2])); + return RGBW32(_data[0], _data[1], _data[2], _data[3]); } void show() { @@ -333,14 +376,12 @@ class BusPwm : public Bus { uint8_t getPins(uint8_t* pinArray) { if (!_valid) return 0; uint8_t numPins = NUM_PWM_PINS(_type); - for (uint8_t i = 0; i < numPins; i++) pinArray[i] = _pins[i]; + for (uint8_t i = 0; i < numPins; i++) { + pinArray[i] = _pins[i]; + } return numPins; } - bool isRgbw() { - return Bus::isRgbw(_type); - } - inline void cleanup() { deallocatePins(); } @@ -397,12 +438,10 @@ class BusNetwork : public Bus { // break; // } _UDPchannels = _rgbw ? 4 : 3; - //_rgbw |= bc.rgbwOverride; // RGBW override in bit 7 or can have a special type _data = (byte *)malloc(bc.count * _UDPchannels); if (_data == nullptr) return; memset(_data, 0, bc.count * _UDPchannels); _len = bc.count; - //_colorOrder = bc.colorOrder; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); _broadcastLock = false; _valid = true; @@ -410,22 +449,19 @@ class BusNetwork : public Bus { void setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; + if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + c = autoWhiteCalc(c); uint16_t offset = pix * _UDPchannels; - _data[offset] = 0xFF & (c >> 16); - _data[offset+1] = 0xFF & (c >> 8); - _data[offset+2] = 0xFF & (c ); - if (_rgbw) _data[offset+3] = 0xFF & (c >> 24); + _data[offset] = R(c); + _data[offset+1] = G(c); + _data[offset+2] = B(c); + if (_rgbw) _data[offset+3] = W(c); } uint32_t getPixelColor(uint16_t pix) { if (!_valid || pix >= _len) return 0; uint16_t offset = pix * _UDPchannels; - return ( - (_rgbw ? (_data[offset+3] << 24) : 0) - | (_data[offset] << 16) - | (_data[offset+1] << 8) - | (_data[offset+2] ) - ); + return RGBW32(_data[offset], _data[offset+1], _data[offset+2], _rgbw ? (_data[offset+3] << 24) : 0); } void show() { @@ -472,8 +508,6 @@ class BusNetwork : public Bus { private: IPAddress _client; - uint16_t _len = 0; - //uint8_t _colorOrder; uint8_t _bri = 255; uint8_t _UDPtype; uint8_t _UDPchannels; @@ -538,7 +572,13 @@ class BusManager { } } - void setPixelColor(uint16_t pix, uint32_t c) { + void setStatusPixel(uint32_t 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) { for (uint8_t i = 0; i < numBusses; i++) { Bus* b = busses[i]; uint16_t bstart = b->getStart(); @@ -553,6 +593,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) { for (uint8_t i = 0; i < numBusses; i++) { Bus* b = busses[i]; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 878be53c4..47471eef7 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -61,7 +61,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(apBehavior, ap[F("behav")]); - /* JsonArray ap_ip = ap["ip"]; for (byte i = 0; i < 4; i++) { @@ -80,7 +79,11 @@ 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")]); + Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | Bus::getAutoWhiteMode()); + 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"]; @@ -521,7 +524,9 @@ void serializeConfig() { hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade hw_led[F("maxpwr")] = strip.ablMilliampsMax; hw_led[F("ledma")] = strip.milliampsPerLed; - hw_led[F("rgbwm")] = strip.rgbwMode; + hw_led["cct"] = correctWB; + hw_led[F("cr")] = cctFromRgb; + hw_led[F("cb")] = strip.cctBlending; JsonArray hw_led_ins = hw_led.createNestedArray("ins"); @@ -538,9 +543,10 @@ 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(); + ins[F("rgbwm")] = bus->getAutoWhiteMode(); } // button(s) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index dfdd53e07..2677f61aa 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -6,36 +6,25 @@ void colorFromUint32(uint32_t in, bool secondary) { - if (secondary) { - colSec[3] = in >> 24 & 0xFF; - colSec[0] = in >> 16 & 0xFF; - colSec[1] = in >> 8 & 0xFF; - colSec[2] = in & 0xFF; - } else { - col[3] = in >> 24 & 0xFF; - col[0] = in >> 16 & 0xFF; - col[1] = in >> 8 & 0xFF; - col[2] = in & 0xFF; - } + byte *_col = secondary ? colSec : col; + _col[0] = R(in); + _col[1] = G(in); + _col[2] = B(in); + _col[3] = W(in); } //load a color without affecting the white channel void colorFromUint24(uint32_t in, bool secondary) { - if (secondary) { - colSec[0] = in >> 16 & 0xFF; - colSec[1] = in >> 8 & 0xFF; - colSec[2] = in & 0xFF; - } else { - col[0] = in >> 16 & 0xFF; - col[1] = in >> 8 & 0xFF; - col[2] = in & 0xFF; - } + byte *_col = secondary ? colSec : col; + _col[0] = R(in); + _col[1] = G(in); + _col[2] = B(in); } //store color components in uint32_t uint32_t colorFromRgbw(byte* rgbw) { - return (rgbw[0] << 16) + (rgbw[1] << 8) + rgbw[2] + (rgbw[3] << 24); + return RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]); } //relatively change white brightness, minumum A=5 @@ -64,9 +53,9 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb case 4: rgb[0]=t,rgb[1]=p,rgb[2]=255;break; case 5: rgb[0]=255,rgb[1]=p,rgb[2]=q; } - 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 +73,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 += 12; //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); @@ -111,7 +100,6 @@ void colorCTtoRGB(uint16_t mired, byte* rgb) //white spectrum to rgb, bins } else { rgb[0]=237;rgb[1]=255;rgb[2]=239;//150 } - if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col); } #ifndef WLED_DISABLE_HUESYNC @@ -169,7 +157,6 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www rgb[0] = 255.0*r; rgb[1] = 255.0*g; rgb[2] = 255.0*b; - if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col); } void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) @@ -197,10 +184,10 @@ void colorFromDecOrHexString(byte* rgb, char* in) c = strtoul(in, NULL, 10); } - rgb[3] = (c >> 24) & 0xFF; - rgb[0] = (c >> 16) & 0xFF; - rgb[1] = (c >> 8) & 0xFF; - rgb[2] = c & 0xFF; + rgb[0] = R(c); + rgb[1] = G(c); + rgb[2] = B(c); + rgb[3] = W(c); } //contrary to the colorFromDecOrHexString() function, this uses the more standard RRGGBB / RRGGBBWW order @@ -212,14 +199,14 @@ bool colorFromHexString(byte* rgb, const char* in) { uint32_t c = strtoul(in, NULL, 16); if (inputSize == 6) { - rgb[0] = (c >> 16) & 0xFF; - rgb[1] = (c >> 8) & 0xFF; - rgb[2] = c & 0xFF; + rgb[0] = (c >> 16); + rgb[1] = (c >> 8); + rgb[2] = c ; } else { - rgb[0] = (c >> 24) & 0xFF; - rgb[1] = (c >> 16) & 0xFF; - rgb[2] = (c >> 8) & 0xFF; - rgb[3] = c & 0xFF; + rgb[0] = (c >> 24); + rgb[1] = (c >> 16); + rgb[2] = (c >> 8); + rgb[3] = c ; } return true; } @@ -236,6 +223,18 @@ float maxf (float v, float w) return v; } +/* +uint32_t colorRGBtoRGBW(uint32_t c) +{ + byte rgb[4]; + rgb[0] = R(c); + rgb[1] = G(c); + rgb[2] = B(c); + rgb[3] = W(c); + colorRGBtoRGBW(rgb); + return RGBW32(rgb[0], rgb[1], rgb[2], rgb[3]); +} + void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) { float low = minf(rgb[0],minf(rgb[1],rgb[2])); @@ -244,3 +243,60 @@ 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); } +*/ + +byte correctionRGB[4] = {0,0,0,0}; +uint16_t lastKelvin = 0; + +// 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) +{ + //remember so that slow colorKtoRGB() doesn't have to run for every setPixelColor() + if (lastKelvin != kelvin) colorKtoRGB(kelvin, correctionRGB); // convert Kelvin to RGB + lastKelvin = kelvin; + byte rgbw[4]; + 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); + 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; + } +} \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index 5b834bf36..04cdfb7b1 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -124,7 +124,7 @@ // - 0b010 (dec. 32-47) analog (PWM) // - 0b011 (dec. 48-63) digital (data + clock / SPI) // - 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 // - 0b111 (dec. 112-127) unused/reserved //bit 7 is reserved and set to 0 diff --git a/wled00/data/index.css b/wled00/data/index.css index f27024ac8..a7588ddbc 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -459,12 +459,20 @@ img { .sliderdisplay { content:''; position: absolute; - top: 13px; bottom: 13px; - left: 10px; right: 10px; + top: 12.5px; bottom: 12.5px; + left: 13px; right: 13px; background: var(--c-4); border-radius: 17px; pointer-events: none; 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); } .sliderbubble { @@ -492,6 +500,7 @@ input[type=range] { background-color: transparent; cursor: pointer; } + input[type=range]:focus { outline: none; } @@ -527,8 +536,7 @@ input[type=range]:active + .sliderbubble { display: inline; transform: translateX(-50%); } - -#wwrap { +#wwrap, #wbal { display: none; } diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 530f4cd44..25787aa34 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -45,17 +45,30 @@
+
+
+ +
+

+
+
+
+ +
+
+
-
- +

RGB color

+
+

-
- +
+

-
- +
+

@@ -66,6 +79,13 @@
+
+

White balance

+
+ +
+
+
@@ -98,7 +118,7 @@
diff --git a/wled00/data/index.js b/wled00/data/index.js index 7bf47b25e..c8e44d0c9 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -40,25 +40,12 @@ var hol = [ var cpick = new iro.ColorPicker("#picker", { width: 260, wheelLightness: false, - wheelAngle: 90, + wheelAngle: 270, + wheelDirection: "clockwise", layout: [ { component: iro.ui.Wheel, options: {} - }, - { - component: iro.ui.Slider, - options: { - sliderType: 'value' - } - }, - { - component: iro.ui.Slider, - options: { - sliderType: 'kelvin', - minTemperature: 2100, - maxTemperature: 10000 - } } ] }); @@ -81,6 +68,8 @@ function applyCfg() var ccfg = cfg.comp.colors; d.getElementById('hexw').style.display = ccfg.hex ? "block":"none"; d.getElementById('picker').style.display = ccfg.picker ? "block":"none"; + d.getElementById('vwrap').style.display = ccfg.picker ? "block":"none"; + d.getElementById('kwrap').style.display = ccfg.picker ? "block":"none"; d.getElementById('rgbwrap').style.display = ccfg.rgb ? "block":"none"; d.getElementById('qcs-w').style.display = ccfg.quick ? "block":"none"; var l = cfg.comp.labels; @@ -202,11 +191,10 @@ function onLoad() { if (window.location.protocol == "file:") { loc = true; locip = localStorage.getItem('locIp'); - if (!locip) - { - locip = prompt("File Mode. Please enter WLED IP!"); - localStorage.setItem('locIp', locip); - } + if (!locip) { + locip = prompt("File Mode. Please enter WLED IP!"); + localStorage.setItem('locIp', locip); + } } var sett = localStorage.getItem('wledUiCfg'); if (sett) cfg = mergeDeep(cfg, JSON.parse(sett)); @@ -229,9 +217,9 @@ function onLoad() { .catch(function (error) { console.log("holidays.json does not contain array of holidays. Defaults loaded."); }) - .finally(function(){ - loadBg(cfg.theme.bg.url); - }); + .finally(function(){ + loadBg(cfg.theme.bg.url); + }); } else loadBg(cfg.theme.bg.url); if (cfg.comp.css) loadSkinCSS('skinCss'); @@ -246,6 +234,7 @@ function onLoad() { cpick.on("input:end", function() { setColor(1); }); + cpick.on("color:change", updatePSliders); pmtLS = localStorage.getItem('wledPmt'); setTimeout(function(){requestJson(null, false);}, 50); d.addEventListener("visibilitychange", handleVisibilityChange, false); @@ -320,11 +309,10 @@ function inforow(key, val, unit = "") function getLowestUnusedP() { var l = 1; - for (var key in pJson) - { + for (var key in pJson) { if (key == l) l++; - } - if (l > 250) l = 250; + } + if (l > 250) l = 250; return l; } @@ -357,16 +345,16 @@ function papiVal(i) { function qlName(i) { if (!pJson[i]) return ""; - if (!pJson[i].ql) return ""; - return pJson[i].ql; + if (!pJson[i].ql) return ""; + return pJson[i].ql; } function cpBck() { var copyText = d.getElementById("bck"); - copyText.select(); - copyText.setSelectionRange(0, 999999); - d.execCommand("copy"); + copyText.select(); + copyText.setSelectionRange(0, 999999); + d.execCommand("copy"); showToast("Copied to clipboard!"); } @@ -452,21 +440,20 @@ function populateQL() { var cn = ""; if (pQL.length > 0) { - cn += `

Quick load

`; + cn += `

Quick load

`; - var it = 0; - for (var key of (pQL||[])) - { - cn += ``; - it++; - if (it > 4) { - it = 0; - cn += '
'; - } - } - if (it != 0) cn+= '
'; + var it = 0; + for (var key of (pQL||[])) { + cn += ``; + it++; + if (it > 4) { + it = 0; + cn += '
'; + } + } + if (it != 0) cn+= '
'; - cn += `

All presets

`; + cn += `

All presets

`; } d.getElementById('pql').innerHTML = cn; } @@ -478,25 +465,25 @@ function populatePresets(fromls) var cn = ""; var arr = Object.entries(pJson); arr.sort(cmpP); - pQL = []; - var is = []; - pNum = 0; + pQL = []; + var is = []; + pNum = 0; for (var key of (arr||[])) { if (!isObject(key[1])) continue; let i = parseInt(key[0]); var qll = key[1].ql; - if (qll) pQL.push([i, qll]); - is.push(i); + if (qll) pQL.push([i, qll]); + is.push(i); - cn += `
`; - if (cfg.comp.pid) cn += `
${i}
`; - cn += `
${isPlaylist(i)?"":""}${pName(i)}
- -
-

`; - pNum++; + cn += `
`; + if (cfg.comp.pid) cn += `
${i}
`; + cn += `
${isPlaylist(i)?"":""}${pName(i)}
+ +
+

`; + pNum++; } d.getElementById('pcont').innerHTML = cn; @@ -526,16 +513,15 @@ function populateInfo(i) var pwru = "Not calculated"; if (pwr > 1000) {pwr /= 1000; pwr = pwr.toFixed((pwr > 10) ? 0 : 1); pwru = pwr + " A";} else if (pwr > 0) {pwr = 50 * Math.round(pwr/50); pwru = pwr + " mA";} - var urows=""; - if (i.u) { - for (const [k, val] of Object.entries(i.u)) - { - if (val[1]) { - urows += inforow(k,val[0],val[1]); - } else { - urows += inforow(k,val); - } - } + var urows=""; + if (i.u) { + for (const [k, val] of Object.entries(i.u)) { + if (val[1]) { + urows += inforow(k,val[0],val[1]); + } else { + urows += inforow(k,val); + } + } } var vcn = "Kuuhaku"; @@ -580,7 +566,7 @@ function populateSegments(s)
${inst.n ? inst.n : "Segment "+i}
- +
@@ -651,7 +637,7 @@ function populateSegments(s) function populateEffects(effects) { var html = ``; +
`; effects.shift(); //remove solid for (let i = 0; i < effects.length; i++) { @@ -697,7 +683,7 @@ function populatePalettes(palettes) }); var html = ``; +
`; for (let i = 0; i < palettes.length; i++) { html += generateListItemHtml( 'palette', @@ -780,15 +766,15 @@ function genPalPrevCss(id) function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', extraClass = '') { return `
- - - ${name} - - ${extraHtml} -
`; + + + ${name} + +${extraHtml} +
`; } function btype(b){ @@ -815,16 +801,16 @@ function populateNodes(i,n) if (o.name) { var url = ``; urows += inforow(url,`${btype(o.type)}
${o.vid==0?"N/A":o.vid}`); - nnodes++; + nnodes++; } } } - if (i.ndc < 0) cn += `Instance List is disabled.`; - else if (nnodes == 0) cn += `No other instances found.`; + if (i.ndc < 0) cn += `Instance List is disabled.`; + else if (nnodes == 0) cn += `No other instances found.`; cn += ` - ${urows} - ${inforow("Current instance:",i.name)} -
`; +${urows} +${inforow("Current instance:",i.name)} +`; d.getElementById('kn').innerHTML = cn; } @@ -854,28 +840,20 @@ 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}%)`; + if (perc < 50) perc += 2; + var val = `linear-gradient(90deg, var(--bg) ${perc}%, var(--c-4) ${perc}%)`; e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val; } function updateBubble(e) { var bubble = e.target.parentNode.getElementsByTagName('output')[0]; - if (bubble) { bubble.innerHTML = e.target.value; } @@ -939,12 +917,13 @@ 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"; + d.getElementById('kwrap').style.display = (lastinfo.leds.cct) ? "none":"block"; updatePA(); - updateHex(); - updateRgb(); + //updateHex(); + //updateRgb(); } function displayRover(i,s) @@ -961,32 +940,32 @@ function compare(a, b) { } function cmpP(a, b) { if (!a[1].n) return (a[0] > b[0]); - return a[1].n.localeCompare(b[1].n,undefined, {numeric: true}); + return a[1].n.localeCompare(b[1].n,undefined, {numeric: true}); } function makeWS() { - if (ws) return; - ws = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws'); - ws.onmessage = function(event) { - var json = JSON.parse(event.data); - if (json.leds) return; //liveview packet - clearTimeout(jsonTimeout); + if (ws) return; + ws = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws'); + ws.onmessage = function(event) { + var json = JSON.parse(event.data); + if (json.leds) return; //liveview packet + clearTimeout(jsonTimeout); jsonTimeout = null; clearErrorToast(); - d.getElementById('connind').style.backgroundColor = "#079"; - var info = json.info; - d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none"; - lastinfo = info; - if (isInfo) { - populateInfo(info); - } - s = json.state; - displayRover(info, s); + d.getElementById('connind').style.backgroundColor = "#079"; + var info = json.info; + d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none"; + lastinfo = info; + if (isInfo) { + populateInfo(info); + } + s = json.state; + displayRover(info, s); readState(json.state); }; - ws.onclose = function(event) { - d.getElementById('connind').style.backgroundColor = "#831"; - } + ws.onclose = function(event) { + d.getElementById('connind').style.backgroundColor = "#831"; + } } function readState(s,command=false) { @@ -1022,7 +1001,8 @@ function readState(s,command=false) { if (isRgbw) whites[e] = parseInt(i.col[e][3]); selectSlot(csel); } - d.getElementById('sliderW').value = whites[csel]; + //d.getElementById('sliderW').value = whites[csel]; + if (i.cct != null && i.cct>=0) d.getElementById("sliderA").value = i.cct; d.getElementById('sliderSpeed').value = i.sx; d.getElementById('sliderIntensity').value = i.ix; @@ -1081,7 +1061,7 @@ var reqsLegal = false; function requestJson(command, rinfo = true) { d.getElementById('connind').style.backgroundColor = "#a90"; - if (command && !reqsLegal) return; //stop post requests from chrome onchange event on page restore + if (command && !reqsLegal) return; //stop post requests from chrome onchange event on page restore lastUpdate = new Date(); if (!jsonTimeout) jsonTimeout = setTimeout(showErrorToast, 3000); var req = null; @@ -1091,7 +1071,7 @@ function requestJson(command, rinfo = true) { url = `http://${locip}${url}`; } - var useWs = ((command || rinfo) && ws && ws.readyState === WebSocket.OPEN); + var useWs = ((command || rinfo) && ws && ws.readyState === WebSocket.OPEN); var type = command ? 'post':'get'; if (command) @@ -1107,10 +1087,10 @@ function requestJson(command, rinfo = true) { if (req.length > 1000) useWs = false; //do not send very long requests over websocket } - if (useWs) { - ws.send(req?req:'{"v":true}'); - return; - } + if (useWs) { + ws.send(req?req:'{"v":true}'); + return; + } fetch (url, { @@ -1153,7 +1133,7 @@ function requestJson(command, rinfo = true) { }); },25); - reqsLegal = true; + reqsLegal = true; } var info = json.info; @@ -1172,12 +1152,12 @@ function requestJson(command, rinfo = true) { isRgbw = info.leds.wv; ledCount = info.leds.count; syncTglRecv = info.str; - maxSeg = info.leds.maxseg; + maxSeg = info.leds.maxseg; pmt = info.fs.pmt; if (!command && rinfo) setTimeout(loadPresets, 99); - d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none"; + d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none"; lastinfo = info; if (isInfo) { populateInfo(info); @@ -1186,7 +1166,7 @@ function requestJson(command, rinfo = true) { displayRover(info, s); } - readState(s,command); + readState(s,command); }) .catch(function (error) { showToast(error, true); @@ -1231,7 +1211,7 @@ function toggleLiveview() { var url = loc ? `http://${locip}/liveview`:"/liveview"; d.getElementById('liveview').src = (isLv) ? url:"about:blank"; d.getElementById('buttonSr').className = (isLv) ? "active":""; - if (!isLv && ws && ws.readyState === WebSocket.OPEN) ws.send('{"lv":false}'); + if (!isLv && ws && ws.readyState === WebSocket.OPEN) ws.send('{"lv":false}'); size(); } @@ -1244,11 +1224,11 @@ function toggleInfo() { } function toggleNodes() { - if (isInfo) toggleInfo(); + if (isInfo) toggleInfo(); isNodes = !isNodes; d.getElementById('nodes').style.transform = (isNodes) ? "translateY(0px)":"translateY(100%)"; - d.getElementById('buttonNodes').className = (isNodes) ? "active":""; - if (isNodes) loadNodes(); + d.getElementById('buttonNodes').className = (isNodes) ? "active":""; + if (isNodes) loadNodes(); } function makeSeg() { @@ -1258,27 +1238,27 @@ function makeSeg() { if (pend < ledCount) ns = pend; } var cn = `
-
- New segment ${lowestUnused} - -
-
-
- - - - - - - - - - -
Start LED${cfg.comp.seglen?"Length":"Stop LED"}
-
${ledCount - ns} LED${ledCount - ns >1 ? "s":""}
- -
-
`; +
+ New segment ${lowestUnused} + +
+
+
+ + + + + + + + + + +
Start LED${cfg.comp.seglen?"Length":"Stop LED"}
+
${ledCount - ns} LED${ledCount - ns >1 ? "s":""}
+ +
+
`; d.getElementById('segutil').innerHTML = cn; } @@ -1376,75 +1356,74 @@ function makeP(i,pl) { var content = ""; if (pl) { var rep = plJson[i].repeat ? plJson[i].repeat : 0; - content = ` -
Playlist Entries
-
- -
-
Repeat 0?rep:1}> times
- End preset:
- + + + +
+
Repeat 0?rep:1}> times
+ End preset:
+ -
- `; + ${makePlSel(true)} + +
+`; } else content = ` - `; + Include brightness + + + +`; - return ` -
-
Quick load label:
-
(leave empty for no Quick load button)
-
+ return `
+
Quick load label:
+
(leave empty for no Quick load button)
+

-
-
- API command
- -
-
- ${content} -
-
Save to ID 0)?i:getLowestUnusedP()}>
-
- - ${(i>0)?' -
-
+ ${pl?"Show playlist editor":(i>0)?"Overwrite with state":"Use current state"} + + +
+
+
+ API command
+ +
+
+ ${content} +
+
Save to ID 0)?i:getLowestUnusedP()}>
+
+ + ${(i>0)?' +
+
-
- ${(i>0)? ('
ID ' +i+ '
'):""}`; +
+${(i>0)? ('
ID ' +i+ '
'):""}`; } function makePUtil() { d.getElementById('putil').innerHTML = `
-
- New preset
-
- ${makeP(0)}
`; +
+ New preset
+
+ ${makeP(0)}
`; } function makePlEntry(p,i) { @@ -1478,7 +1457,7 @@ function makePlUtil() { function resetPUtil() { var cn = `
-
`; +
`; d.getElementById('putil').innerHTML = cn; } @@ -1490,7 +1469,7 @@ function tglCs(i){ function tglSegn(s) { - d.getElementById(`seg${s}t`).style.display = + d.getElementById(`seg${s}t`).style.display = (window.getComputedStyle(d.getElementById(`seg${s}t`)).display === "none") ? "inline":"none"; } @@ -1660,7 +1639,7 @@ function saveP(i,pl) { var pQN = d.getElementById(`p${i}ql`).value; if (pQN.length > 0) obj.ql = pQN; - showToast("Saving " + pN +" (" + pI + ")"); + showToast("Saving " + pN +" (" + pI + ")"); requestJson(obj); if (obj.o) { pJson[pI] = obj; @@ -1724,8 +1703,8 @@ function selectSlot(b) { cpick.color.set(cd[csel].style.backgroundColor); d.getElementById('sliderW').value = whites[csel]; updateTrail(d.getElementById('sliderW')); - updateHex(); - updateRgb(); + //updateHex(); + //updateRgb(); redrawPalPrev(); } @@ -1745,6 +1724,19 @@ function pC(col) setColor(0); } +function updatePSliders() { + updateRgb(); + updateHex(); + var v = d.getElementById('sliderV'); + v.value = cpick.color.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); + d.getElementById('sliderK').value = cpick.color.kelvin; +} + function updateRgb() { var col = cpick.color.rgb; @@ -1784,20 +1776,30 @@ function fromHex() 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() { var r = d.getElementById('sliderR').value; var g = d.getElementById('sliderG').value; var b = d.getElementById('sliderB').value; cpick.color.set(`rgb(${r},${g},${b})`); - setColor(0); } +//sr 0: from RGB sliders, 1: from picker, 2: from hex function setColor(sr) { var cd = d.getElementById('csl').children; if (sr == 1 && cd[csel].style.backgroundColor == 'rgb(0, 0, 0)') cpick.color.setChannel('hsv', 'v', 100); cd[csel].style.backgroundColor = cpick.color.rgbString; - if (sr != 2) whites[csel] = d.getElementById('sliderW').value; + if (sr != 2) whites[csel] = parseInt(d.getElementById('sliderW').value); var col = cpick.color.rgb; var obj = {"seg": {"col": [[col.r, col.g, col.b, whites[csel]],[],[]]}}; if (csel == 1) { @@ -1805,8 +1807,14 @@ function setColor(sr) { } else if (csel == 2) { obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}}; } - updateHex(); - updateRgb(); + //updateHex(); + //updateRgb(); + requestJson(obj); +} + +function setBalance(b) +{ + var obj = {"seg": {"cct": parseInt(b)}}; requestJson(obj); } @@ -1825,9 +1833,9 @@ function cnfReset() if (!cnfr) { var bt = d.getElementById('resetbtn'); - bt.style.color = "#f00"; - bt.innerHTML = "Confirm Reboot"; - cnfr = true; return; + bt.style.color = "#f00"; + bt.innerHTML = "Confirm Reboot"; + cnfr = true; return; } window.location.href = "/reset"; } @@ -1838,9 +1846,9 @@ function rSegs() var bt = d.getElementById('rsbtn'); if (!cnfrS) { - bt.style.color = "#f00"; - bt.innerHTML = "Confirm reset"; - cnfrS = true; return; + bt.style.color = "#f00"; + bt.innerHTML = "Confirm reset"; + cnfrS = true; return; } cnfrS = false; bt.style.color = "#fff"; @@ -1916,7 +1924,7 @@ function getPalettesData(page, callback) function search(searchField) { var searchText = searchField.value.toUpperCase(); - searchField.parentElement.getElementsByClassName('search-cancel-icon')[0].style.display = (searchText.length < 1)?"none":"inline"; + searchField.parentElement.getElementsByClassName('search-cancel-icon')[0].style.display = (searchText.length < 1)?"none":"inline"; var elements = searchField.parentElement.parentElement.querySelectorAll('.lstI'); for (i = 0; i < elements.length; i++) { var item = elements[i]; @@ -2039,10 +2047,10 @@ function move(e) { var s = Math.sign(dx); var f = +(s*dx/w).toFixed(2); - if((clientX != 0) && - (iSlide > 0 || s < 0) && (iSlide < N - 1 || s > 0) && - f > 0.12 && - d.getElementsByClassName("tabcontent")[iSlide].scrollTop == scrollS) { + if ((clientX != 0) && + (iSlide > 0 || s < 0) && (iSlide < N - 1 || s > 0) && + f > 0.12 && + d.getElementsByClassName("tabcontent")[iSlide].scrollTop == scrollS) { _C.style.setProperty('--i', iSlide -= s); f = 1 - f; updateTablinks(iSlide); @@ -2054,7 +2062,7 @@ function move(e) { function size() { w = window.innerWidth; - d.getElementById('buttonNodes').style.display = (lastinfo.ndc > 0 && w > 770) ? "block":"none"; + d.getElementById('buttonNodes').style.display = (lastinfo.ndc > 0 && w > 770) ? "block":"none"; var h = d.getElementById('top').clientHeight; sCol('--th', h + "px"); sCol('--bh', d.getElementById('bot').clientHeight + "px"); @@ -2079,7 +2087,7 @@ function togglePcMode(fromB = false) d.getElementById('buttonPcm').className = (pcMode) ? "active":""; d.getElementById('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto"; sCol('--bh', d.getElementById('bot').clientHeight + "px"); - _C.style.width = (pcMode)?'100%':'400%'; + _C.style.width = (pcMode)?'100%':'400%'; lastw = w; } diff --git a/wled00/data/iro.js b/wled00/data/iro.js index f459e417c..3d63d0418 100644 --- a/wled00/data/iro.js +++ b/wled00/data/iro.js @@ -1,7 +1,7 @@ /*! - * iro.js v5.3.1 - * 2016-2020 James Daniel + * iro.js v5.5.2 + * 2016-2021 James Daniel * Licensed under MPL 2.0 * github.com/jaames/iro.js */ -!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).iro=n()}(this,function(){"use strict";var k,s,n,i,o,m={},M=[],r=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i;function j(t,n){for(var i in n)t[i]=n[i];return t}function b(t){var n=t.parentNode;n&&n.removeChild(t)}function d(t,n,i){var r,e,u,o,l=arguments;if(n=j({},n),3=r/i?u=n:e=n}return n},function(t,n,i){n&&g(t.prototype,n),i&&g(t,i)}(l,[{key:"hsv",get:function(){var t=this.$;return{h:t.h,s:t.s,v:t.v}},set:function(t){var n=this.$;if(t=p({},n,t),this.onChange){var i={h:!1,v:!1,s:!1,a:!1};for(var r in n)i[r]=t[r]!=n[r];this.$=t,(i.h||i.s||i.v||i.a)&&this.onChange(this,i)}else this.$=t}},{key:"hsva",get:function(){return p({},this.$)},set:function(t){this.hsv=t}},{key:"hue",get:function(){return this.$.h},set:function(t){this.hsv={h:t}}},{key:"saturation",get:function(){return this.$.s},set:function(t){this.hsv={s:t}}},{key:"value",get:function(){return this.$.v},set:function(t){this.hsv={v:t}}},{key:"alpha",get:function(){return this.$.a},set:function(t){this.hsv=p({},this.hsv,{a:t})}},{key:"kelvin",get:function(){return l.rgbToKelvin(this.rgb)},set:function(t){this.rgb=l.kelvinToRgb(t)}},{key:"red",get:function(){return this.rgb.r},set:function(t){this.rgb=p({},this.rgb,{r:t})}},{key:"green",get:function(){return this.rgb.g},set:function(t){this.rgb=p({},this.rgb,{g:t})}},{key:"blue",get:function(){return this.rgb.b},set:function(t){this.rgb=p({},this.rgb,{b:t})}},{key:"rgb",get:function(){var t=l.hsvToRgb(this.$),n=t.r,i=t.g,r=t.b;return{r:U(n),g:U(i),b:U(r)}},set:function(t){this.hsv=p({},l.rgbToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"rgba",get:function(){return p({},this.rgb,{a:this.alpha})},set:function(t){this.rgb=t}},{key:"hsl",get:function(){var t=l.hsvToHsl(this.$),n=t.h,i=t.s,r=t.l;return{h:U(n),s:U(i),l:U(r)}},set:function(t){this.hsv=p({},l.hslToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"hsla",get:function(){return p({},this.hsl,{a:this.alpha})},set:function(t){this.hsl=t}},{key:"rgbString",get:function(){var t=this.rgb;return"rgb("+t.r+", "+t.g+", "+t.b+")"},set:function(t){var n,i,r,e,u=1;if((n=P.exec(t))?(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255)):(n=z.exec(t))&&(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255),u=K(n[4],1)),!n)throw new Error("Invalid rgb string");this.rgb={r:i,g:r,b:e,a:u}}},{key:"rgbaString",get:function(){var t=this.rgba;return"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a+")"},set:function(t){this.rgbString=t}},{key:"hexString",get:function(){var t=this.rgb;return"#"+V(t.r)+V(t.g)+V(t.b)},set:function(t){var n,i,r,e,u=255;if((n=C.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3])):(n=D.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3]),u=17*Q(n[4])):(n=F.exec(t))?(i=Q(n[1]),r=Q(n[2]),e=Q(n[3])):(n=G.exec(t))&&(i=Q(n[1]),r=Q(n[2]),e=Q(n[3]),u=Q(n[4])),!n)throw new Error("Invalid hex string");this.rgb={r:i,g:r,b:e,a:u/255}}},{key:"hex8String",get:function(){var t=this.rgba;return"#"+V(t.r)+V(t.g)+V(t.b)+V(q(255*t.a))},set:function(t){this.hexString=t}},{key:"hslString",get:function(){var t=this.hsl;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%)"},set:function(t){var n,i,r,e,u=1;if((n=H.exec(t))?(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100)):(n=$.exec(t))&&(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100),u=K(n[4],1)),!n)throw new Error("Invalid hsl string");this.hsl={h:i,s:r,l:e,a:u}}},{key:"hslaString",get:function(){var t=this.hsla;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%, "+t.a+")"},set:function(t){this.hslString=t}}]),l}();function Z(t){var n,i=t.width,r=t.sliderSize,e=t.borderWidth,u=t.handleRadius,o=t.padding,l=t.sliderShape,s="horizontal"===t.layoutDirection;return r=null!=(n=r)?n:2*o+2*u+2*e,"circle"===l?{handleStart:t.padding+t.handleRadius,handleRange:i-2*o-2*u-2*e,width:i,height:i,cx:i/2,cy:i/2,radius:i/2-e/2}:{handleStart:r/2,handleRange:i-r,radius:r/2,x:0,y:0,width:s?r:i,height:s?i:r}}function tt(t,n){var i=Z(t),r=i.width,e=i.height,u=i.handleRange,o=i.handleStart,l="horizontal"===t.layoutDirection,s=l?r/2:e/2,c=o+function(t,n){var i=n.hsva,r=n.rgb;switch(t.sliderType){case"red":return r.r/2.55;case"green":return r.g/2.55;case"blue":return r.b/2.55;case"alpha":return 100*i.a;case"kelvin":var e=t.minTemperature,u=t.maxTemperature-e,o=(n.kelvin-e)/u*100;return Math.max(0,Math.min(o,100));case"hue":return i.h/=3.6;case"saturation":return i.s;case"value":default:return i.v}}(t,n)/100*u;return l&&(c=-1*c+u+2*o),{x:l?s:c,y:l?c:s}}function nt(t){var n=t.width/2;return{width:t.width,radius:n-t.borderWidth,cx:n,cy:n}}function it(t,n,i){var r=t.wheelAngle,e=t.wheelDirection;return((n=!i&&"clockwise"===e||i&&"anticlockwise"===e?(i?180:360)-(r-n):r+n)%360+360)%360}function rt(t,n,i){var r=nt(t),e=r.cx,u=r.cy,o=t.width/2-t.padding-t.handleRadius-t.borderWidth;n=e-n,i=u-i;var l=it(t,Math.atan2(-i,-n)*(180/Math.PI)),s=Math.min(Math.sqrt(n*n+i*i),o);return{h:Math.round(l),s:Math.round(100/o*s)}}function et(t){var n=t.width,i=t.boxHeight;return{width:n,height:null!=i?i:n,radius:t.padding+t.handleRadius}}function ut(t,n,i){var r=et(t),e=r.width,u=r.height,o=r.radius,l=(n-o)/(e-2*o)*100,s=(i-o)/(u-2*o)*100;return{s:Math.max(0,Math.min(l,100)),v:Math.max(0,Math.min(100-s,100))}}function ot(t){X=X||document.getElementsByTagName("base");var n=window.navigator.userAgent,i=/^((?!chrome|android).)*safari/i.test(n),r=/iPhone|iPod|iPad/i.test(n),e=window.location;return(i||r)&&0=r/i?u=n:e=n}return n},function(t,n,i){n&&g(t.prototype,n),i&&g(t,i)}(l,[{key:"hsv",get:function(){var t=this.$;return{h:t.h,s:t.s,v:t.v}},set:function(t){var n=this.$;if(t=b({},n,t),this.onChange){var i={h:!1,v:!1,s:!1,a:!1};for(var r in n)i[r]=t[r]!=n[r];this.$=t,(i.h||i.s||i.v||i.a)&&this.onChange(this,i)}else this.$=t}},{key:"hsva",get:function(){return b({},this.$)},set:function(t){this.hsv=t}},{key:"hue",get:function(){return this.$.h},set:function(t){this.hsv={h:t}}},{key:"saturation",get:function(){return this.$.s},set:function(t){this.hsv={s:t}}},{key:"value",get:function(){return this.$.v},set:function(t){this.hsv={v:t}}},{key:"alpha",get:function(){return this.$.a},set:function(t){this.hsv=b({},this.hsv,{a:t})}},{key:"kelvin",get:function(){return l.rgbToKelvin(this.rgb)},set:function(t){this.rgb=l.kelvinToRgb(t)}},{key:"red",get:function(){return this.rgb.r},set:function(t){this.rgb=b({},this.rgb,{r:t})}},{key:"green",get:function(){return this.rgb.g},set:function(t){this.rgb=b({},this.rgb,{g:t})}},{key:"blue",get:function(){return this.rgb.b},set:function(t){this.rgb=b({},this.rgb,{b:t})}},{key:"rgb",get:function(){var t=l.hsvToRgb(this.$),n=t.r,i=t.g,r=t.b;return{r:G(n),g:G(i),b:G(r)}},set:function(t){this.hsv=b({},l.rgbToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"rgba",get:function(){return b({},this.rgb,{a:this.alpha})},set:function(t){this.rgb=t}},{key:"hsl",get:function(){var t=l.hsvToHsl(this.$),n=t.h,i=t.s,r=t.l;return{h:G(n),s:G(i),l:G(r)}},set:function(t){this.hsv=b({},l.hslToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"hsla",get:function(){return b({},this.hsl,{a:this.alpha})},set:function(t){this.hsl=t}},{key:"rgbString",get:function(){var t=this.rgb;return"rgb("+t.r+", "+t.g+", "+t.b+")"},set:function(t){var n,i,r,e,u=1;if((n=_.exec(t))?(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255)):(n=H.exec(t))&&(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255),u=K(n[4],1)),!n)throw new Error("Invalid rgb string");this.rgb={r:i,g:r,b:e,a:u}}},{key:"rgbaString",get:function(){var t=this.rgba;return"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a+")"},set:function(t){this.rgbString=t}},{key:"hexString",get:function(){var t=this.rgb;return"#"+U(t.r)+U(t.g)+U(t.b)},set:function(t){var n,i,r,e,u=255;if((n=D.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3])):(n=F.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3]),u=17*Q(n[4])):(n=L.exec(t))?(i=Q(n[1]),r=Q(n[2]),e=Q(n[3])):(n=B.exec(t))&&(i=Q(n[1]),r=Q(n[2]),e=Q(n[3]),u=Q(n[4])),!n)throw new Error("Invalid hex string");this.rgb={r:i,g:r,b:e,a:u/255}}},{key:"hex8String",get:function(){var t=this.rgba;return"#"+U(t.r)+U(t.g)+U(t.b)+U(Z(255*t.a))},set:function(t){this.hexString=t}},{key:"hslString",get:function(){var t=this.hsl;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%)"},set:function(t){var n,i,r,e,u=1;if((n=P.exec(t))?(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100)):(n=$.exec(t))&&(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100),u=K(n[4],1)),!n)throw new Error("Invalid hsl string");this.hsl={h:i,s:r,l:e,a:u}}},{key:"hslaString",get:function(){var t=this.hsla;return"hsla("+t.h+", "+t.s+"%, "+t.l+"%, "+t.a+")"},set:function(t){this.hslString=t}}]),l}();function X(t){var n,i=t.width,r=t.sliderSize,e=t.borderWidth,u=t.handleRadius,o=t.padding,l=t.sliderShape,s="horizontal"===t.layoutDirection;return r=null!=(n=r)?n:2*o+2*u,"circle"===l?{handleStart:t.padding+t.handleRadius,handleRange:i-2*o-2*u,width:i,height:i,cx:i/2,cy:i/2,radius:i/2-e/2}:{handleStart:r/2,handleRange:i-r,radius:r/2,x:0,y:0,width:s?r:i,height:s?i:r}}function Y(t,n){var i=X(t),r=i.width,e=i.height,u=i.handleRange,o=i.handleStart,l="horizontal"===t.layoutDirection,s=l?r/2:e/2,c=o+function(t,n){var i=n.hsva,r=n.rgb;switch(t.sliderType){case"red":return r.r/2.55;case"green":return r.g/2.55;case"blue":return r.b/2.55;case"alpha":return 100*i.a;case"kelvin":var e=t.minTemperature,u=t.maxTemperature-e,o=(n.kelvin-e)/u*100;return Math.max(0,Math.min(o,100));case"hue":return i.h/=3.6;case"saturation":return i.s;case"value":default:return i.v}}(t,n)/100*u;return l&&(c=-1*c+u+2*o),{x:l?s:c,y:l?c:s}}var tt,nt=2*Math.PI,it=function(t,n){return(t%n+n)%n},rt=function(t,n){return Math.sqrt(t*t+n*n)};function et(t){return t.width/2-t.padding-t.handleRadius-t.borderWidth}function ut(t){var n=t.width/2;return{width:t.width,radius:n-t.borderWidth,cx:n,cy:n}}function ot(t,n,i){var r=t.wheelAngle,e=t.wheelDirection;return i&&"clockwise"===e?n=r+n:"clockwise"===e?n=360-r+n:i&&"anticlockwise"===e?n=r+180-n:"anticlockwise"===e&&(n=r-n),it(n,360)}function lt(t,n,i){var r=ut(t),e=r.cx,u=r.cy,o=et(t);n=e-n,i=u-i;var l=ot(t,Math.atan2(-i,-n)*(360/nt)),s=Math.min(rt(n,i),o);return{h:Math.round(l),s:Math.round(100/o*s)}}function st(t){var n=t.width,i=t.boxHeight;return{width:n,height:null!=i?i:n,radius:t.padding+t.handleRadius}}function ct(t,n,i){var r=st(t),e=r.width,u=r.height,o=r.radius,l=(n-o)/(e-2*o)*100,s=(i-o)/(u-2*o)*100;return{s:Math.max(0,Math.min(l,100)),v:Math.max(0,Math.min(100-s,100))}}function at(t,n,i,r){for(var e=0;e - - +
diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index f337e71e7..34ccf90e1 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; @@ -305,10 +305,11 @@ ${i+1}: - + - + + @@ -333,7 +334,7 @@ ${i+1}:

Reversed:

Skip 1st LED:
-

Off Refresh:  
+

Off Refresh:
`; f.insertAdjacentHTML("beforeend", cn); } @@ -389,11 +390,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 support the `files` property of file inputs."); + } else if (!o.files[0]) { + alert("Please select a JSON file first!"); + } 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; }
%DMXMENU%
-
-)====="; +body{text-align:center;background:#222;height:100px;margin:0}html{--h:10.2vh}button{background:#333;color:#fff;font-family:Verdana,Helvetica,sans-serif;border:1px solid #333;border-radius:var(--h);font-size:6vmin;height:var(--h);width:calc(100%% - 40px);margin-top:2vh} +
+
+
)====="; // Autogenerated from wled00/data/settings_wifi.htm, do not edit!! @@ -77,7 +74,7 @@ onclick="B()">Back // Autogenerated from wled00/data/settings_leds.htm, do not edit!! const char PAGE_settings_leds[] PROGMEM = R"=====(LED Settings