diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f67f7e54..bdef8b135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ### Builds after release 0.12.0 +#### Build 2107020 + +- Send websockets on every state change +- Improved Aurora effect + #### Build 2107011 - Added MQTT button feedback option (PR #2011) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 9a399afd2..8d5095fb8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3957,7 +3957,6 @@ uint16_t WS2812FX::mode_tv_simulator(void) { */ //CONFIG -#define BACKLIGHT 5 #define W_MAX_COUNT 20 //Number of simultaneous waves #define W_MAX_SPEED 6 //Higher number, higher speed #define W_WIDTH_FACTOR 6 //Higher number, smaller waves @@ -4082,9 +4081,13 @@ uint16_t WS2812FX::mode_aurora(void) { } } + uint8_t backlight = 1; //dimmer backlight if less active colors + if (SEGCOLOR(0)) backlight++; + if (SEGCOLOR(1)) backlight++; + if (SEGCOLOR(2)) backlight++; //Loop through LEDs to determine color for(int i = 0; i < SEGLEN; i++) { - CRGB mixedRgb = CRGB(BACKLIGHT, BACKLIGHT, BACKLIGHT); + CRGB mixedRgb = CRGB(backlight, backlight, backlight); //For each LED we must check each wave if it is "active" at this position. //If there are multiple waves active on a LED we multiply their values. @@ -4096,7 +4099,7 @@ uint16_t WS2812FX::mode_aurora(void) { } } - setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2], BACKLIGHT); + setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2]); } return FRAMETIME; diff --git a/wled00/FX.h b/wled00/FX.h index 61b641893..c791a4cce 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -320,6 +320,27 @@ class WS2812FX { vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED return vLength; } + uint8_t differs(Segment& b) { + uint8_t d = 0; + if (start != b.start) d |= SEG_DIFFERS_BOUNDS; + if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; + if (offset != b.offset) d |= SEG_DIFFERS_GSO; + if (grouping != b.grouping) d |= SEG_DIFFERS_GSO; + if (spacing != b.spacing) d |= SEG_DIFFERS_GSO; + if (opacity != b.opacity) d |= SEG_DIFFERS_BRI; + if (mode != b.mode) d |= SEG_DIFFERS_FX; + if (speed != b.speed) d |= SEG_DIFFERS_FX; + if (intensity != b.intensity) d |= SEG_DIFFERS_FX; + if (palette != b.palette) d |= SEG_DIFFERS_FX; + + if ((options & 0b00101111) != (b.options & 0b00101111)) d |= SEG_DIFFERS_OPT; + for (uint8_t i = 0; i < NUM_COLORS; i++) + { + if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; + } + + return d; + } } segment; // segment runtime parameters @@ -613,7 +634,6 @@ class WS2812FX { gammaCorrectBri = false, gammaCorrectCol = true, applyToAllSelected = true, - segmentsAreIdentical(Segment* a, Segment* b), setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p), // return true if the strip is being sent pixel updates isUpdating(void); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 8a9d2cb3c..4113ab72e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1010,23 +1010,6 @@ uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8 return crgb_to_col(fastled_col); } -//@returns `true` if color, mode, speed, intensity and palette match -bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b) -{ - //if (a->start != b->start) return false; - //if (a->stop != b->stop) return false; - for (uint8_t i = 0; i < NUM_COLORS; i++) - { - if (a->colors[i] != b->colors[i]) return false; - } - if (a->mode != b->mode) return false; - if (a->speed != b->speed) return false; - if (a->intensity != b->intensity) return false; - if (a->palette != b->palette) return false; - //if (a->getOption(SEG_OPTION_REVERSED) != b->getOption(SEG_OPTION_REVERSED)) return false; - return true; -} - //load custom mapping table from JSON file void WS2812FX::deserializeMap(void) { diff --git a/wled00/const.h b/wled00/const.h index 10e130051..e6297a60c 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -77,6 +77,7 @@ #define NOTIFIER_CALL_MODE_PRESET_CYCLE 8 #define NOTIFIER_CALL_MODE_BLYNK 9 #define NOTIFIER_CALL_MODE_ALEXA 10 +#define NOTIFIER_CALL_MODE_WS_SEND 11 //special call mode, not for notifier, updates websocket only //RGB to RGBW conversion mode #define RGBW_MODE_MANUAL_ONLY 0 //No automatic white channel calculation. Manual white channel slider @@ -195,6 +196,14 @@ #define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed #define SEG_OPTION_TRANSITIONAL 7 +//Segment differs return byte +#define SEG_DIFFERS_BRI 0x01 +#define SEG_DIFFERS_OPT 0x02 +#define SEG_DIFFERS_COL 0x04 +#define SEG_DIFFERS_FX 0x08 +#define SEG_DIFFERS_BOUNDS 0x10 +#define SEG_DIFFERS_GSO 0x20 + //Playlist option byte #define PL_OPTION_SHUFFLE 0x01 diff --git a/wled00/json.cpp b/wled00/json.cpp index 5c5ef4794..ddad77025 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -9,158 +9,160 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) { byte id = elem["id"] | it; - if (id < strip.getMaxSegments()) - { - WS2812FX::Segment& seg = strip.getSegment(id); - uint16_t start = elem[F("start")] | seg.start; - int stop = elem["stop"] | -1; + if (id >= strip.getMaxSegments()) return; - if (stop < 0) { - uint16_t len = elem[F("len")]; - stop = (len > 0) ? start + len : seg.stop; - } - uint16_t grp = elem[F("grp")] | seg.grouping; - uint16_t spc = elem[F("spc")] | seg.spacing; - strip.setSegment(id, start, stop, grp, spc); - seg.offset = elem[F("of")] | seg.offset; - if (stop > start && seg.offset > stop - start -1) seg.offset = stop - start -1; + WS2812FX::Segment& seg = strip.getSegment(id); + //WS2812FX::Segment prev; + //prev = seg; //make a backup so we can tell if something changed - int segbri = elem["bri"] | -1; - if (segbri == 0) { - seg.setOption(SEG_OPTION_ON, 0, id); - } else if (segbri > 0) { - seg.setOpacity(segbri, id); - seg.setOption(SEG_OPTION_ON, 1, id); - } - - seg.setOption(SEG_OPTION_ON, elem["on"] | seg.getOption(SEG_OPTION_ON), id); - - JsonArray colarr = elem["col"]; - if (!colarr.isNull()) - { - for (uint8_t i = 0; i < 3; i++) - { - int rgbw[] = {0,0,0,0}; - bool colValid = false; - JsonArray colX = colarr[i]; - if (colX.isNull()) { - byte brgbw[] = {0,0,0,0}; - const char* hexCol = colarr[i]; - if (hexCol == nullptr) { //Kelvin color temperature (or invalid), e.g 2400 - int kelvin = colarr[i] | -1; - if (kelvin < 0) continue; - if (kelvin == 0) seg.setColor(i, 0, id); - if (kelvin > 0) colorKtoRGB(kelvin, brgbw); - colValid = true; - } else { //HEX string, e.g. "FFAA00" - colValid = colorFromHexString(brgbw, hexCol); - } - for (uint8_t c = 0; c < 4; c++) rgbw[c] = brgbw[c]; - } else { //Array of ints (RGB or RGBW color), e.g. [255,160,0] - byte sz = colX.size(); - if (sz == 0) continue; //do nothing on empty array - - byte cp = copyArray(colX, rgbw, 4); - if (cp == 1 && rgbw[0] == 0) - seg.setColor(i, 0, id); - colValid = true; - } - - if (!colValid) continue; - if (id == strip.getMainSegmentId() && i < 2) //temporary, to make transition work on main segment - { - if (i == 0) {col[0] = rgbw[0]; col[1] = rgbw[1]; col[2] = rgbw[2]; col[3] = rgbw[3];} - if (i == 1) {colSec[0] = rgbw[0]; colSec[1] = rgbw[1]; colSec[2] = rgbw[2]; colSec[3] = rgbw[3];} - } else { //normal case, apply directly to segment - seg.setColor(i, ((rgbw[3] << 24) | ((rgbw[0]&0xFF) << 16) | ((rgbw[1]&0xFF) << 8) | ((rgbw[2]&0xFF))), id); - if (seg.mode == FX_MODE_STATIC) strip.trigger(); //instant refresh - } - } - } - - // lx parser - #ifdef WLED_ENABLE_LOXONE - int lx = elem[F("lx")] | -1; - if (lx > 0) { - parseLxJson(lx, id, false); - } - int ly = elem[F("ly")] | -1; - if (ly > 0) { - parseLxJson(ly, id, true); - } - #endif - - //if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal); - seg.setOption(SEG_OPTION_SELECTED, elem[F("sel")] | seg.getOption(SEG_OPTION_SELECTED)); - seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED)); - seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR )); - - //temporary, strip object gets updated via colorUpdated() - if (id == strip.getMainSegmentId()) { - byte effectPrev = effectCurrent; - effectCurrent = elem[F("fx")] | effectCurrent; - if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually - effectSpeed = elem[F("sx")] | effectSpeed; - effectIntensity = elem[F("ix")] | effectIntensity; - effectPalette = elem["pal"] | effectPalette; - } else { //permanent - byte fx = elem[F("fx")] | seg.mode; - if (fx != seg.mode && fx < strip.getModeCount()) { - strip.setMode(id, fx); - if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually - } - seg.speed = elem[F("sx")] | seg.speed; - seg.intensity = elem[F("ix")] | seg.intensity; - seg.palette = elem["pal"] | seg.palette; - } - - JsonArray iarr = elem[F("i")]; //set individual LEDs - if (!iarr.isNull()) { - strip.setPixelSegment(id); - - //freeze and init to black - if (!seg.getOption(SEG_OPTION_FREEZE)) { - seg.setOption(SEG_OPTION_FREEZE, true); - strip.fill(0); - } - - uint16_t start = 0, stop = 0; - byte set = 0; //0 nothing set, 1 start set, 2 range set - - for (uint16_t i = 0; i < iarr.size(); i++) { - if(iarr[i].is()) { - if (!set) { - start = iarr[i]; - set = 1; - } else { - stop = iarr[i]; - set = 2; - } - } else { - JsonArray icol = iarr[i]; - if (icol.isNull()) break; - - byte sz = icol.size(); - if (sz == 0 || sz > 4) break; - - int rgbw[] = {0,0,0,0}; - copyArray(icol, rgbw); - - if (set < 2) stop = start + 1; - for (uint16_t i = start; i < stop; i++) { - strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]); - } - if (!set) start++; - set = 0; - } - } - strip.setPixelSegment(255); - strip.trigger(); - } else { //return to regular effect - seg.setOption(SEG_OPTION_FREEZE, false); - } + uint16_t start = elem[F("start")] | seg.start; + int stop = elem["stop"] | -1; + if (stop < 0) { + uint16_t len = elem[F("len")]; + stop = (len > 0) ? start + len : seg.stop; } + uint16_t grp = elem[F("grp")] | seg.grouping; + uint16_t spc = elem[F("spc")] | seg.spacing; + strip.setSegment(id, start, stop, grp, spc); + seg.offset = elem[F("of")] | seg.offset; + if (stop > start && seg.offset > stop - start -1) seg.offset = stop - start -1; + + int segbri = elem["bri"] | -1; + if (segbri == 0) { + seg.setOption(SEG_OPTION_ON, 0, id); + } else if (segbri > 0) { + seg.setOpacity(segbri, id); + seg.setOption(SEG_OPTION_ON, 1, id); + } + + seg.setOption(SEG_OPTION_ON, elem["on"] | seg.getOption(SEG_OPTION_ON), id); + + JsonArray colarr = elem["col"]; + if (!colarr.isNull()) + { + for (uint8_t i = 0; i < 3; i++) + { + int rgbw[] = {0,0,0,0}; + bool colValid = false; + JsonArray colX = colarr[i]; + if (colX.isNull()) { + byte brgbw[] = {0,0,0,0}; + const char* hexCol = colarr[i]; + if (hexCol == nullptr) { //Kelvin color temperature (or invalid), e.g 2400 + int kelvin = colarr[i] | -1; + if (kelvin < 0) continue; + if (kelvin == 0) seg.setColor(i, 0, id); + if (kelvin > 0) colorKtoRGB(kelvin, brgbw); + colValid = true; + } else { //HEX string, e.g. "FFAA00" + colValid = colorFromHexString(brgbw, hexCol); + } + for (uint8_t c = 0; c < 4; c++) rgbw[c] = brgbw[c]; + } else { //Array of ints (RGB or RGBW color), e.g. [255,160,0] + byte sz = colX.size(); + if (sz == 0) continue; //do nothing on empty array + + byte cp = copyArray(colX, rgbw, 4); + if (cp == 1 && rgbw[0] == 0) + seg.setColor(i, 0, id); + colValid = true; + } + + if (!colValid) continue; + if (id == strip.getMainSegmentId() && i < 2) //temporary, to make transition work on main segment + { + if (i == 0) {col[0] = rgbw[0]; col[1] = rgbw[1]; col[2] = rgbw[2]; col[3] = rgbw[3];} + if (i == 1) {colSec[0] = rgbw[0]; colSec[1] = rgbw[1]; colSec[2] = rgbw[2]; colSec[3] = rgbw[3];} + } else { //normal case, apply directly to segment + seg.setColor(i, ((rgbw[3] << 24) | ((rgbw[0]&0xFF) << 16) | ((rgbw[1]&0xFF) << 8) | ((rgbw[2]&0xFF))), id); + if (seg.mode == FX_MODE_STATIC) strip.trigger(); //instant refresh + } + } + } + + // lx parser + #ifdef WLED_ENABLE_LOXONE + int lx = elem[F("lx")] | -1; + if (lx > 0) { + parseLxJson(lx, id, false); + } + int ly = elem[F("ly")] | -1; + if (ly > 0) { + parseLxJson(ly, id, true); + } + #endif + + //if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal); + seg.setOption(SEG_OPTION_SELECTED, elem[F("sel")] | seg.getOption(SEG_OPTION_SELECTED)); + seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED)); + seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR )); + + //temporary, strip object gets updated via colorUpdated() + if (id == strip.getMainSegmentId()) { + byte effectPrev = effectCurrent; + effectCurrent = elem[F("fx")] | effectCurrent; + if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually + effectSpeed = elem[F("sx")] | effectSpeed; + effectIntensity = elem[F("ix")] | effectIntensity; + effectPalette = elem["pal"] | effectPalette; + } else { //permanent + byte fx = elem[F("fx")] | seg.mode; + if (fx != seg.mode && fx < strip.getModeCount()) { + strip.setMode(id, fx); + if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually + } + seg.speed = elem[F("sx")] | seg.speed; + seg.intensity = elem[F("ix")] | seg.intensity; + seg.palette = elem["pal"] | seg.palette; + } + + JsonArray iarr = elem[F("i")]; //set individual LEDs + if (!iarr.isNull()) { + strip.setPixelSegment(id); + + //freeze and init to black + if (!seg.getOption(SEG_OPTION_FREEZE)) { + seg.setOption(SEG_OPTION_FREEZE, true); + strip.fill(0); + } + + uint16_t start = 0, stop = 0; + byte set = 0; //0 nothing set, 1 start set, 2 range set + + for (uint16_t i = 0; i < iarr.size(); i++) { + if(iarr[i].is()) { + if (!set) { + start = iarr[i]; + set = 1; + } else { + stop = iarr[i]; + set = 2; + } + } else { + JsonArray icol = iarr[i]; + if (icol.isNull()) break; + + byte sz = icol.size(); + if (sz == 0 || sz > 4) break; + + int rgbw[] = {0,0,0,0}; + copyArray(icol, rgbw); + + if (set < 2) stop = start + 1; + for (uint16_t i = start; i < stop; i++) { + strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]); + } + if (!set) start++; + set = 0; + } + } + strip.setPixelSegment(255); + strip.trigger(); + } else { //return to regular effect + seg.setOption(SEG_OPTION_FREEZE, false); + } + return; // seg.hasChanged(prev); } bool deserializeState(JsonObject root, byte presetId) @@ -300,6 +302,8 @@ bool deserializeState(JsonObject root, byte presetId) if (!playlist.isNull()) { loadPlaylist(playlist, presetId); noNotification = true; //do not notify both for this request and the first playlist entry + } else { + interfaceUpdateCallMode = NOTIFIER_CALL_MODE_WS_SEND; } colorUpdated(noNotification ? NOTIFIER_CALL_MODE_NO_NOTIFY : NOTIFIER_CALL_MODE_DIRECT_CHANGE); diff --git a/wled00/led.cpp b/wled00/led.cpp index 344bc2a45..9f034ca2b 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -115,7 +115,7 @@ void colorUpdated(int callMode) notify(callMode); - //set flag to update blynk and mqtt + //set flag to update blynk, ws and mqtt interfaceUpdateCallMode = callMode; } else { if (nightlightActive && !nightlightActiveOld && @@ -180,6 +180,11 @@ void colorUpdated(int callMode) void updateInterfaces(uint8_t callMode) { sendDataWs(); + if (callMode == NOTIFIER_CALL_MODE_WS_SEND) { + lastInterfaceUpdate = millis(); + return; + } + #ifndef WLED_DISABLE_ALEXA if (espalexaDevice != nullptr && callMode != NOTIFIER_CALL_MODE_ALEXA) { espalexaDevice->setValue(bri); diff --git a/wled00/wled.h b/wled00/wled.h index 6e65af0c2..1d06859f0 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2107011 +#define VERSION 2107020 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 2d1747559..3a1c8686b 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -41,7 +41,8 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp verboseResponse = deserializeState(root); } - if (verboseResponse || millis() - lastInterfaceUpdate < 1900) sendDataWs(client); //update if it takes longer than 100ms until next "broadcast" + //update if it takes longer than 300ms until next "broadcast" + if (verboseResponse && (millis() - lastInterfaceUpdate < 1700 || !interfaceUpdateCallMode)) sendDataWs(client); } } else { //message is comprised of multiple frames or the frame is split into multiple packets