diff --git a/wled00/FX.h b/wled00/FX.h index 19660a88..c49f9acb 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -619,7 +619,7 @@ class WS2812FX { } void - finalizeInit(uint16_t countPixels), + finalizeInit(), service(void), blur(uint8_t), fill(uint32_t), @@ -636,7 +636,8 @@ class WS2812FX { trigger(void), setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0), resetSegments(), - populateDefaultSegments(), + makeAutoSegments(), + fixInvalidSegments(), setPixelColor(uint16_t n, uint32_t c), setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0), show(void), @@ -650,6 +651,7 @@ class WS2812FX { gammaCorrectCol = true, applyToAllSelected = true, setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p), + checkSegmentAlignment(void), // 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 598148fe..74404e99 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -65,25 +65,22 @@ #endif //do not call this method from system context (network callback) -void WS2812FX::finalizeInit(uint16_t countPixels) +void WS2812FX::finalizeInit(void) { RESET_RUNTIME; - _length = countPixels; + isRgbw = isOffRefreshRequred = false; - //if busses failed to load, add default (FS issue...) + //if busses failed to load, add default (fresh install, FS issue, ...) if (busses.getNumBusses() == 0) { const uint8_t defDataPins[] = {DATA_PINS}; const uint16_t defCounts[] = {PIXEL_COUNTS}; const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0])); const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); uint16_t prevLen = 0; - for (uint8_t i = 0; i < defNumBusses; i++) { + for (uint8_t i = 0; i < defNumBusses && i < WLED_MAX_BUSSES; i++) { uint8_t defPin[] = {defDataPins[i]}; uint16_t start = prevLen; - uint16_t count = _length; - if (defNumBusses > 1 && defNumCounts) { - count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; - } + uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; prevLen += count; BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, COL_ORDER_GRB); busses.add(defCfg); @@ -92,60 +89,30 @@ void WS2812FX::finalizeInit(uint16_t countPixels) deserializeMap(); - uint16_t segStarts[MAX_NUM_SEGMENTS] = {0}; - uint16_t segStops [MAX_NUM_SEGMENTS] = {0}; - - setBrightness(_brightness); - - //TODO make sure segments are only refreshed when bus config actually changed (new settings page) - uint8_t s = 0; - for (uint8_t i = 0; i < busses.getNumBusses(); i++) { - Bus* b = busses.getBus(i); - - if (autoSegments) { //make one segment per bus - segStarts[s] = b->getStart(); - segStops[s] = segStarts[s] + b->getLength(); - - //check for overlap with previous segments - for (uint8_t j = 0; j < s; j++) { - if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) { - //segments overlap, merge - segStarts[j] = min(segStarts[s],segStarts[j]); - segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0; - s--; - } - } - s++; - } - + _length = 0; + for (uint8_t i=0; igetStart() + bus->getLength() > MAX_LEDS) break; + //RGBW mode is enabled if at least one of the strips is RGBW + isRgbw |= bus->isRgbw(); + //refresh is required to remain off if at least one of the strips requires the refresh. + isOffRefreshRequred |= bus->isOffRefreshRequired(); + uint16_t busEnd = bus->getStart() + bus->getLength(); + if (busEnd > _length) _length = busEnd; #ifdef ESP8266 - if ((!IS_DIGITAL(b->getType()) || IS_2PIN(b->getType()))) continue; + if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue; uint8_t pins[5]; - b->getPins(pins); - BusDigital* bd = static_cast(b); + if (!bus->getPins(pins)) continue; + BusDigital* bd = static_cast(bus); if (pins[0] == 3) bd->reinit(); #endif } + ledCount = _length; - if (autoSegments) { - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { - setSegment(i, segStarts[i], segStops[i]); - } - } else { - //expand the main seg to the entire length, but only if there are no other segments - uint8_t mainSeg = getMainSegmentId(); - - if (getActiveSegmentsNum() < 2) { - setSegment(mainSeg, 0, _length); - } else { - //there are multiple segments, leave them, but prune length to total - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { - if (_segments[i].start >= _length) setSegment(i, 0, 0); - if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length); - } - } - } + //segments are created in makeAutoSegments(); + + setBrightness(_brightness); } void WS2812FX::service() { @@ -654,23 +621,67 @@ void WS2812FX::resetSegments() { _segment_runtimes[0].reset(); } -void WS2812FX::populateDefaultSegments() { - uint16_t length = 0; - for (uint8_t i=0; igetStart(); - length += bus->getLength(); - _segments[i].stop = _segments[i].start + bus->getLength(); - _segments[i].mode = DEFAULT_MODE; - _segments[i].colors[0] = DEFAULT_COLOR; - _segments[i].speed = DEFAULT_SPEED; - _segments[i].intensity = DEFAULT_INTENSITY; - _segments[i].grouping = 1; - _segments[i].setOption(SEG_OPTION_SELECTED, 1); - _segments[i].setOption(SEG_OPTION_ON, 1); - _segments[i].opacity = 255; +void WS2812FX::makeAutoSegments() { + uint16_t segStarts[MAX_NUM_SEGMENTS] = {0}; + uint16_t segStops [MAX_NUM_SEGMENTS] = {0}; + + if (autoSegments) { //make one segment per bus + uint8_t s = 0; + for (uint8_t i = 0; i < busses.getNumBusses(); i++) { + Bus* b = busses.getBus(i); + + segStarts[s] = b->getStart(); + segStops[s] = segStarts[s] + b->getLength(); + + //check for overlap with previous segments + for (uint8_t j = 0; j < s; j++) { + if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) { + //segments overlap, merge + segStarts[j] = min(segStarts[s],segStarts[j]); + segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0; + s--; + } + } + s++; + } + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { + setSegment(i, segStarts[i], segStops[i]); + } + } else { + //expand the main seg to the entire length, but only if there are no other segments + uint8_t mainSeg = getMainSegmentId(); + + if (getActiveSegmentsNum() < 2) { + setSegment(mainSeg, 0, _length); + } } + + fixInvalidSegments(); +} + +void WS2812FX::fixInvalidSegments() { + //make sure no segment is longer than total (sanity check) + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + if (_segments[i].start >= _length) setSegment(i, 0, 0); + if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length); + } +} + +//true if all segments align with a bus, or if a segment covers the total length +bool WS2812FX::checkSegmentAlignment() { + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + if (_segments[i].start >= _segments[i].stop) continue; //inactive segment + bool aligned = false; + for (uint8_t b = 0; bgetStart() && _segments[i].stop == bus->getStart() + bus->getLength()) aligned = true; + } + if (_segments[i].start == 0 && _segments[i].stop == _length) aligned = true; + if (!aligned) return false; + } + return true; } //After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply) diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 380959c1..799019f3 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -24,6 +24,10 @@ #define DEBUG_PRINTF(x...) #endif +#define GET_BIT(var,bit) (((var)>>(bit))&0x01) +#define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit))) +#define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit)))) + //temporary struct for passing bus configuration to bus struct BusConfig { uint8_t type = TYPE_WS2812_RGB; @@ -32,10 +36,12 @@ struct BusConfig { uint8_t colorOrder = COL_ORDER_GRB; bool reversed = false; uint8_t skipAmount; + bool refreshReq; uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip=0) { - type = busType; count = len; start = pstart; - colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0) { + refreshReq = (bool) GET_BIT(busType,7); + type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) + count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; uint8_t nPins = 1; if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address else if (type > 47) nPins = 2; @@ -120,6 +126,10 @@ class Bus { return false; } + inline bool isOffRefreshRequired() { + return _needsRefresh; + } + bool reversed = false; protected: @@ -127,6 +137,7 @@ class Bus { uint8_t _bri = 255; uint16_t _start = 0; bool _valid = false; + bool _needsRefresh = false; }; @@ -143,6 +154,7 @@ class BusDigital : public Bus { _pins[1] = bc.pins[1]; } reversed = bc.reversed; + _needsRefresh = bc.refreshReq || bc.type == TYPE_TM1814; _skip = bc.skipAmount; //sacrificial pixels _len = bc.count + _skip; _iType = PolyBus::getI(bc.type, _pins, nr); @@ -204,7 +216,7 @@ class BusDigital : public Bus { } inline bool isRgbw() { - return (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814); + return Bus::isRgbw(_type); } inline uint8_t skippedLeds() { @@ -216,7 +228,7 @@ class BusDigital : public Bus { } void cleanup() { - DEBUG_PRINTLN("Digital Cleanup"); + DEBUG_PRINTLN(F("Digital Cleanup.")); PolyBus::cleanup(_busPtr, _iType); _iType = I_NONE; _valid = false; @@ -326,7 +338,7 @@ class BusPwm : public Bus { } bool isRgbw() { - return (_type > TYPE_ONOFF && _type <= TYPE_ANALOG_5CH && _type != TYPE_ANALOG_3CH); + return Bus::isRgbw(_type); } inline void cleanup() { @@ -481,7 +493,7 @@ class BusManager { static uint32_t memUsage(BusConfig &bc) { uint8_t type = bc.type; uint16_t len = bc.count; - if (type < 32) { + if (type > 15 && type < 32) { #ifdef ESP8266 if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem if (type > 29) return len*20; //RGBW @@ -496,7 +508,7 @@ class BusManager { } if (type > 31 && type < 48) return 5; if (type == 44 || type == 45) return len*4; //RGBW - return len*3; + return len*3; //RGB } int add(BusConfig &bc) { @@ -513,7 +525,7 @@ class BusManager { //do not call this method from system context (network callback) void removeAll() { - //Serial.println("Removing all."); + DEBUG_PRINTLN(F("Removing all.")); //prevents crashes due to deleting busses while in use. while (!canAllShow()) yield(); for (uint8_t i = 0; i < numBusses; i++) delete busses[i]; @@ -573,16 +585,6 @@ class BusManager { return len; } - // a workaround - static inline bool isRgbw(uint8_t type) { - return Bus::isRgbw(type); - } - - //Return true if the strip requires a refresh to stay off. - static bool isOffRefreshRequred(uint8_t type) { - return type == TYPE_TM1814; - } - private: uint8_t numBusses = 0; Bus* busses[WLED_MAX_BUSSES]; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 1a499404..13bcdd96 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -1,4 +1,5 @@ #include "wled.h" +#include "wled_ethernet.h" /* * Serializes and parses the cfg.json and wsec.json settings files, stored in internal FS. @@ -85,10 +86,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(strip.rgbwMode, hw_led[F("rgbwm")]); JsonArray ins = hw_led["ins"]; + + uint16_t lC = 0; + if (fromFS || !ins.isNull()) { - uint8_t s = 0; //bus iterator - strip.isRgbw = false; - strip.isOffRefreshRequred = false; + uint8_t s = 0; // bus iterator busses.removeAll(); uint32_t mem = 0; for (JsonObject elm : ins) { @@ -107,22 +109,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint8_t colorOrder = (int)elm[F("order")]; uint8_t skipFirst = elm[F("skip")]; uint16_t start = elm["start"] | 0; + if (length==0 || start + length > MAX_LEDS) continue; // zero length or we reached max. number of LEDs, just stop uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; bool reversed = elm["rev"]; - + bool refresh = elm["ref"] | false; + ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh + s++; + uint16_t busEnd = start + length; + if (busEnd > lC) lC = busEnd; BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst); - if (bc.adjustBounds(ledCount)) { - //RGBW mode is enabled if at least one of the strips is RGBW - strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(ledType)); - //refresh is required to remain off if at least one of the strips requires the refresh. - strip.isOffRefreshRequred |= BusManager::isOffRefreshRequred(ledType); - s++; - mem += busses.memUsage(bc); - if (mem <= MAX_LED_MEMORY) busses.add(bc); - } + mem += BusManager::memUsage(bc); + if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip() } - strip.finalizeInit(ledCount); + // finalization done in beginStrip() } + if (lC > ledCount) ledCount = lC; // fix incorrect total length (honour analog setup) if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus // read multiple button configuration @@ -501,6 +502,25 @@ void serializeConfig() { #ifdef WLED_USE_ETHERNET JsonObject ethernet = doc.createNestedObject("eth"); ethernet["type"] = ethernetType; + if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { + JsonArray pins = ethernet.createNestedArray("pin"); + for (uint8_t p=0; p=0) pins.add(ethernetBoards[ethernetType].eth_power); + if (ethernetBoards[ethernetType].eth_mdc>=0) pins.add(ethernetBoards[ethernetType].eth_mdc); + if (ethernetBoards[ethernetType].eth_mdio>=0) pins.add(ethernetBoards[ethernetType].eth_mdio); + switch (ethernetBoards[ethernetType].eth_clk_mode) { + case ETH_CLOCK_GPIO0_IN: + case ETH_CLOCK_GPIO0_OUT: + pins.add(0); + break; + case ETH_CLOCK_GPIO16_OUT: + pins.add(16); + break; + case ETH_CLOCK_GPIO17_OUT: + pins.add(17); + break; + } + } #endif JsonObject hw = doc.createNestedObject("hw"); @@ -526,7 +546,9 @@ void serializeConfig() { ins[F("order")] = bus->getColorOrder(); ins["rev"] = bus->reversed; ins[F("skip")] = bus->skippedLeds(); - ins["type"] = bus->getType(); + ins["type"] = bus->getType() & 0x7F;; + ins["ref"] = bus->isOffRefreshRequired(); + ins[F("rgbw")] = bus->isRgbw(); } // button(s) @@ -551,7 +573,7 @@ void serializeConfig() { JsonObject hw_ir = hw.createNestedObject("ir"); hw_ir["pin"] = irPin; - hw_ir[F("type")] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled ) + hw_ir["type"] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled ) JsonObject hw_relay = hw.createNestedObject(F("relay")); hw_relay["pin"] = rlyPin; diff --git a/wled00/const.h b/wled00/const.h index 770a6de2..42a01d81 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -240,10 +240,10 @@ #define NTP_PACKET_SIZE 48 -// maximum number of LEDs - more than 1500 LEDs (or 500 DMA "LEDPIN 3" driven ones) will cause a low memory condition on ESP8266 +//maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses #ifndef MAX_LEDS #ifdef ESP8266 -#define MAX_LEDS 1664 // can't rely on memory limit to limit this to 1600 LEDs +#define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs #else #define MAX_LEDS 8192 #endif diff --git a/wled00/data/index.js b/wled00/data/index.js index e77a3797..7bf47b25 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -1296,9 +1296,9 @@ var plJson = {"0":{ "end": 0 }}; -//var plSelContent = ""; function makePlSel(incPl=false) { var plSelContent = ""; + delete pJson["0"]; // remove filler preset var arr = Object.entries(pJson); for (var i = 0; i < arr.length; i++) { var n = arr[i][1].n ? arr[i][1].n : "Preset " + arr[i][0]; diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 53f1b43d..187fd289 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -160,13 +160,16 @@ } } if (change) { + gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state if (t > 31 && t < 48) d.getElementsByName("LC"+n)[0].value = 1; // for sanity change analog count just to 1 LED } + gId("rf"+n).onclick = (t == 31) ? (function(){return false}) : (function(){}); // prevent change for TM1814 isRGBW |= (t == 30 || t == 31 || (t > 40 && t < 46 && t != 43)); // RGBW checkbox, TYPE_xxxx values from const.h gId("co"+n).style.display = ((t>=80 && t<96) || t == 41 || t == 42) ? "none":"inline"; // hide color order for PWM W & WW/CW gId("dig"+n+"c").style.display = (t > 40 && t < 48) ? "none":"inline"; // hide count for analog gId("dig"+n+"r").style.display = (t>=80 && t<96) ? "none":"inline"; // hide reversed for virtual gId("dig"+n+"s").style.display = ((t>=80 && t<96) || (t > 40 && t < 48)) ? "none":"inline"; // hide skip 1st for virtual & analog + gId("dig"+n+"f").style.display = (t>=16 && t<32 || t>=50 && t<64) ? "inline":"none"; // hide refresh gId("rev"+n).innerHTML = (t > 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog gId("psd"+n).innerHTML = (t > 40 && t < 48) ? "Index:":"Start:"; // change analog start description } @@ -328,9 +331,9 @@ ${i+1}: -
-
Reversed:  
-
Skip 1st LED:
+

Reversed:
+

Skip 1st LED:
+

Off Refresh:  
`; f.insertAdjacentHTML("beforeend", cn); } diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 8d001ad8..5c2b07cb 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -77,7 +77,7 @@ onclick="B()">Back // Autogenerated from wled00/data/settings_leds.htm, do not edit!! const char PAGE_settings_leds[] PROGMEM = R"=====(LED Settings