From d6a25fa8529724211d12de0921505bcba1d48343 Mon Sep 17 00:00:00 2001 From: Yannic van Moerkerk Date: Tue, 9 Dec 2025 23:43:58 +0100 Subject: [PATCH 1/2] Added support for LSC Smart connect LED strip Support is added for LSC Smart Connect led strips which are sold at ACTION stores around Europe. These strips are special because they use dual SM16703 chips, the first is used for controlling a RGB LED, the second is used for WW and CW. For controlling the strip this gives 6 channels in a row: G,R,B,WW,CW,N/C which means the 3th channel of the second SM16703 chip is not used. Firmware was built using these files and tested on Wemo D1 mini. --- wled00/bus_manager.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ wled00/bus_manager.h | 5 +++-- wled00/bus_wrapper.h | 2 ++ wled00/const.h | 1 + 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 4fa5c40a57..df081b0349 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -186,6 +186,7 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) _hasWhite = hasWhite(bc.type); _hasCCT = hasCCT(bc.type); uint16_t lenToCreate = bc.count; + if (bc.type == TYPE_SM16703_DUAL) lenToCreate = bc.count * 2; // two SM16703 chips per logical pixel if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr); _valid = (_busPtr != nullptr) && bc.count > 0; @@ -293,6 +294,45 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT c = color_fade(c, _bri, true); // apply brightness + if (_type == TYPE_SM16703_DUAL) { + // logical pixel uses two SM16703 chips: first RGB, second WW/CW (B unused) + uint8_t r = R(c), g = G(c), b = B(c); + uint8_t ww = 0, cw = 0; + if (hasCCT()) Bus::calculateCCT(c, ww, cw); + + if (BusManager::_useABL) { + if (_milliAmpsPerLed < 255) { + _colorSum += r + g + b + ww + cw; // include both chips' channels for ABL + } else { + // wacky model not expected; fall back to max RGB + uint8_t maxRgb = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); + _colorSum += maxRgb; + } + } + + unsigned logicalPix = pix; + if (_reversed) logicalPix = _len - pix - 1; + logicalPix += _skip; + unsigned firstIdx = logicalPix * 2; + unsigned secondIdx = firstIdx + 1; + + // bounds safeguard (should not trigger if lenToCreate set correctly) + unsigned hwLen = _len * 2 + _skip; + if (secondIdx >= hwLen) return; + + const uint8_t coFirst = _colorOrderMap.getPixelColorOrder(logicalPix + _start, _colorOrder); + PolyBus::setPixelColor(_busPtr, _iType, firstIdx, c, coFirst, 0); + + // Second chip: R=WW, G=CW by default; respect WW/CW swap flag (upper nibble of color order) + const uint8_t coSecond = _colorOrderMap.getPixelColorOrder(logicalPix + _start, _colorOrder); + bool swapWhites = true; // hardware wiring has WW/CW reversed relative to CCT + if ((coSecond >> 4) & 0x01) swapWhites = !swapWhites; // UI swap toggles + if (swapWhites) { uint8_t tmp = ww; ww = cw; cw = tmp; } + uint32_t wwCwColor = RGBW32(ww, cw, 0, 0); + PolyBus::setPixelColor(_busPtr, _iType, secondIdx, wwCwColor, COL_ORDER_GRB, 0); + return; + } + if (BusManager::_useABL) { // if using ABL, sum all color channels to estimate current and limit brightness in show() uint8_t r = R(c), g = G(c), b = B(c); @@ -370,6 +410,7 @@ void BusDigital::setColorOrder(uint8_t colorOrder) { std::vector BusDigital::getLEDTypes() { return { {TYPE_WS2812_RGB, "D", PSTR("WS281x")}, + {TYPE_SM16703_DUAL, "D", PSTR("SM16703 RGB+CCT (2x)")}, {TYPE_SK6812_RGBW, "D", PSTR("SK6812/WS2814 RGBW")}, {TYPE_TM1814, "D", PSTR("TM1814")}, {TYPE_WS2811_400KHZ, "D", PSTR("400kHz")}, diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 95772a443f..b5856cdc5a 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -174,7 +174,8 @@ class Bus { static constexpr bool hasWhite(uint8_t type) { return (type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || - type == TYPE_FW1906 || type == TYPE_WS2805 || type == TYPE_SM16825 || // digital types with white channel + type == TYPE_FW1906 || type == TYPE_WS2805 || type == TYPE_SM16825 || + type == TYPE_SM16703_DUAL || // digital types with white channel (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) || // analog types with white channel type == TYPE_NET_DDP_RGBW || type == TYPE_NET_ARTNET_RGBW; // network types with white channel } @@ -182,7 +183,7 @@ class Bus { return type == TYPE_WS2812_2CH_X3 || type == TYPE_WS2812_WWA || type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || type == TYPE_FW1906 || type == TYPE_WS2805 || - type == TYPE_SM16825; + type == TYPE_SM16825 || type == TYPE_SM16703_DUAL; } static constexpr bool isTypeValid(uint8_t type) { return (type > 15 && type < 128); } static constexpr bool isDigital(uint8_t type) { return (type >= TYPE_DIGITAL_MIN && type <= TYPE_DIGITAL_MAX) || is2Pin(type); } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index b2ff947418..88629ac0e6 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1315,6 +1315,7 @@ class PolyBus { case TYPE_WS2812_2CH_X3: case TYPE_WS2812_RGB: case TYPE_WS2812_WWA: + case TYPE_SM16703_DUAL: return I_8266_U0_NEO_3 + offset; case TYPE_SK6812_RGBW: return I_8266_U0_NEO_4 + offset; @@ -1378,6 +1379,7 @@ class PolyBus { case TYPE_WS2812_2CH_X3: case TYPE_WS2812_RGB: case TYPE_WS2812_WWA: + case TYPE_SM16703_DUAL: return I_32_RN_NEO_3 + offset; case TYPE_SK6812_RGBW: return I_32_RN_NEO_4 + offset; diff --git a/wled00/const.h b/wled00/const.h index ac48838435..67ad07fbd3 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -305,6 +305,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define TYPE_WS2805 32 //RGB + WW + CW #define TYPE_TM1914 33 //RGB #define TYPE_SM16825 34 //RGB + WW + CW +#define TYPE_SM16703_DUAL 35 //dual SM16703: RGB chip + WW/CW chip (B unused) #define TYPE_DIGITAL_MAX 39 // last usable digital type //"Analog" types (40-47) #define TYPE_ONOFF 40 //binary output (relays etc.; NOT PWM) From 792dc21bae268207670668afcf0aa97d3ef6280f Mon Sep 17 00:00:00 2001 From: Yannic van Moerkerk Date: Wed, 10 Dec 2025 00:10:10 +0100 Subject: [PATCH 2/2] Added support for LSC Smart connect led strips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this commit,support is added for LSC Smart Connect led strips which are sold at ACTION stores around Europe. These strips are special because they use dual SM16703 chips, the first is used for controlling a RGB LED, the second is used for WW and CW. For controlling the strip this gives 6 channels in a row: G,R,B,WW,CW,N/C which means the 3th channel of the second SM16703 chip is not used. Recommitted because CodeRabbit found that the skipping LED function calculation was not yet up to date with the usage of 2 IC`s - Firmware was built using these files and tested on Wemo D1 mini. - In the UI the strip is called “SM16703 RGB+CCT (2x)” - Product: https://www.action.com/nl-nl/p/3218153/lsc-smart-connect-ledstrip/ --- wled00/bus_manager.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index df081b0349..5673f2db62 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -186,12 +186,17 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) _hasWhite = hasWhite(bc.type); _hasCCT = hasCCT(bc.type); uint16_t lenToCreate = bc.count; - if (bc.type == TYPE_SM16703_DUAL) lenToCreate = bc.count * 2; // two SM16703 chips per logical pixel + if (bc.type == TYPE_SM16703_DUAL) lenToCreate = (bc.count + _skip) * 2; // two SM16703 chips per logical pixel (including skip) if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus - _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr); + if (bc.type == TYPE_SM16703_DUAL) { + _busPtr = PolyBus::create(_iType, _pins, lenToCreate, nr); // lenToCreate already includes skip * 2 + } else { + _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr); + } _valid = (_busPtr != nullptr) && bc.count > 0; // fix for wled#4759 - if (_valid) for (unsigned i = 0; i < _skip; i++) { + unsigned skipHW = (bc.type == TYPE_SM16703_DUAL) ? _skip * 2 : _skip; + if (_valid) for (unsigned i = 0; i < skipHW; i++) { PolyBus::setPixelColor(_busPtr, _iType, i, 0, COL_ORDER_GRB); // set sacrificial pixels to black (CO does not matter here) } DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"), @@ -317,7 +322,7 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { unsigned secondIdx = firstIdx + 1; // bounds safeguard (should not trigger if lenToCreate set correctly) - unsigned hwLen = _len * 2 + _skip; + unsigned hwLen = (_len + _skip) * 2; if (secondIdx >= hwLen) return; const uint8_t coFirst = _colorOrderMap.getPixelColorOrder(logicalPix + _start, _colorOrder);