Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,42 @@ uint32_t Bus::autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const {
return c;
}

// AI: below section was generated by an AI
uint32_t Bus::mappedColorSum(uint32_t c, uint8_t ww, uint8_t cw) const {
const CustomBusConfig& custom = getCustomBusConfig();
if (!custom.active()) return R(c) + G(c) + B(c) + W(c);
// Custom buses may map one logical WLED color source to multiple physical
// wire channels. Example: paired RGB+WW bulbs can use RGB for one WS281x
// chip and duplicate logical W across the second chip's three wire channels.
// ABL should count what is actually emitted on the wire, not only RGBW once.
uint32_t sum = 0;
for (uint8_t i = 0; i < custom.numChannels; i++) {
switch (custom.channelColors[i]) {
case WLEDpixelBus::CH_R: sum += R(c); break;
case WLEDpixelBus::CH_G: sum += G(c); break;
case WLEDpixelBus::CH_B: sum += B(c); break;
case WLEDpixelBus::CH_W: sum += W(c); break;
case WLEDpixelBus::CH_WW: sum += ww; break;
case WLEDpixelBus::CH_CW: sum += cw; break;
}
}
return sum;
}

uint16_t Bus::currentEstimateChannels() const {
const CustomBusConfig& custom = getCustomBusConfig();
if (!custom.active()) return hasWhite() ? 4 : 3;
// Use the number of active mapped wire channels as the denominator for
// custom buses. This keeps duplicated sources (for example W,W,W) from
// being undercounted compared with native fixed RGB/RGBW layouts.
uint16_t channels = 0;
for (uint8_t i = 0; i < custom.numChannels; i++) {
if (custom.channelColors[i] != WLEDpixelBus::CH_UNUSED) channels++;
}
return std::max<uint16_t>(channels, 1);
}
// AI: end

// Default implementation for Bus::getCustomBusConfig() — returns a static default.
// BusDigital and BusPlaceholder override this when custom.active() == true.
const CustomBusConfig& Bus::getCustomBusConfig() const {
Expand Down Expand Up @@ -174,7 +210,9 @@ BusDigital::BusDigital(const BusConfig &bc)
if (bc.custom.invertOutput) _busPtr->setInverted(true); // invert output, needs to be set before bus->begin() (uses native hardware inversion capability)
// TODO: should inverted be supported for normal buses too? probably better not as it complicates things for normal users, one more option to screw up the settings.
}
// Derive instance capabilities from actual channel map
// Derive instance capabilities from the actual channel map instead of the
// selected base type. A custom RGB base type can therefore behave as a
// logical RGBW bus when its channel map contains W/WW/CW.
_hasRgb = false; _hasWhite = false; _hasCCT = false;
bool hasWW = false, hasCW = false;
for (uint8_t i = 0; i < bc.custom.numChannels; i++) {
Expand All @@ -187,6 +225,10 @@ BusDigital::BusDigital(const BusConfig &bc)
}
if (hasWW || hasCW) _hasWhite = true;
if (hasWW && hasCW) _hasCCT = true;
// The base Bus constructor clamps auto-white for native RGB-only types.
// Re-apply it after custom capability detection so config-driven RGBW maps
// can expose and use the W slider / auto-white path.
_autoWhiteMode = _hasWhite ? bc.autoWhite : RGBW_MODE_MANUAL_ONLY;
} else {
// create bus via PixelBusAllocator wrapper which will return a WLEDpixelBus::PixelBus
_busPtr = PixelBusAllocator::create(bc.type, _pins, lenToCreate + _skip, bc.colorOrder, _driverType, bc.busSpeedFactor, _frequencykHz);
Expand Down Expand Up @@ -272,8 +314,8 @@ void BusDigital::estimateCurrent() {
const uint8_t busBri = _busPtr->getBusBri();
if (busBri > 0) colorSum = ((uint64_t)colorSum * _bri) / busBri;
}
// colorSum has all the values of color channels summed, max would be getLength()*(3*255 + (255 if hasWhite()): convert to milliAmps
uint32_t clrUnitsPerChannel = hasWhite() ? 4*255 : 3*255;
// colorSum has all the values of color channels summed; convert to milliAmps
uint32_t clrUnitsPerChannel = currentEstimateChannels() * 255;
_milliAmpsTotal = ((uint64_t)colorSum * actualMilliampsPerLed) / clrUnitsPerChannel + getLength(); // add 1mA standby current per LED
}

Expand Down Expand Up @@ -359,10 +401,10 @@ void BusDigital::setPixelColor(unsigned pix, uint32_t c) {
if (BusManager::_useABL) {
// Accumulate brightness-scaled channel values for current estimation.
// For 16-bit types c is unscaled (bri=255 above); ABL slightly over-estimates at low brightness.
uint8_t r = R(c), g = G(c), b = B(c);
if (_milliAmpsPerLed < 255) { // normal ABL
_colorSum += r + g + b + W(c);
_colorSum += mappedColorSum(c, cctWW, cctCW);
} else { // wacky WS2815 power model, ignore white channel, use max of RGB (issue #549)
uint8_t r = R(c), g = G(c), b = B(c);
_colorSum += ((r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b));
}
}
Expand Down Expand Up @@ -1680,4 +1722,3 @@ uint16_t BusManager::_gMilliAmpsUsed = 0;
uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;
bool BusManager::_useABL = false;
Bus* BusManager::_lastBusCache = nullptr; // cache for setPixelColor() fast path

4 changes: 4 additions & 0 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ class Bus {
uint8_t _busSpeedFactor = 100; // percent, default 100 = default timings

uint32_t autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const;
// AI: below section was generated by an AI
uint32_t mappedColorSum(uint32_t c, uint8_t ww, uint8_t cw) const;
uint16_t currentEstimateChannels() const;
// AI: end
};


Expand Down
95 changes: 76 additions & 19 deletions wled00/data/settings_leds.htm
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@
if (is16b(t)) ch *= 2;
return ch;
}
// Effective bus capabilities come from the saved custom bus config when enabled.
// This lets the UI mirror hardware that is logically one bulb but physically
// several wire channels, e.g. RGB on channels 1-3 and W duplicated on 4-6.
// Example: a custom map containing W/WW/CW makes the white controls visible.
function customCaps(n) {
if (!isCst(n)) return 0;
let nch = parseInt(d.Sf["CBch"+n]?.value) || 3;
let caps = 0, hasWW = false, hasCW = false;
for (let ci = 0; ci < nch && ci < 6; ci++) {
let v = parseInt(d.Sf["CBc"+ci+n]?.value) || 0;
if (v >= 1 && v <= 3) caps |= 0x01;
if (v === 4) caps |= 0x02;
if (v === 5) hasWW = true;
if (v === 6) hasCW = true;
}
if (hasWW || hasCW) caps |= 0x02;
if (hasWW && hasCW) caps |= 0x04;
if (d.Sf["CBb"+n]?.checked) caps |= 0x10;
return caps;
}
function busCaps(t, n) {
return isCst(n) ? customCaps(n) : gT(t).c;
}
function busHasW(t, n) {
return !!(busCaps(t, n) & 0x02);
}
// pre-fill the channel-map override with the native layout of type t (called when "Customize" is first checked or the type changes)
function presetCustomDefaults(n, t) {
const order = ["GRB","RGB","BRG","RBG","BGR","GBR"][parseInt(d.Sf["CO"+n]?.value) || 0];
Expand All @@ -66,6 +92,30 @@
if (d.Sf["CBrst"+n]) d.Sf["CBrst"+n].value = type.trst || 300;
}
}
function applyCustomBusConfig(n, v) {
let enabled = typeof v.cch !== "undefined" && v.cch > 0;
let cb = d.Sf["CBen"+n];
if (cb) cb.checked = enabled;
if (!enabled) return;
// Restore custom-bus config into the form before UI() evaluates
// capabilities, otherwise controls like the W slider would still follow
// the base LED type instead of the saved channel map.
if (d.Sf["CBch"+n]) d.Sf["CBch"+n].value = v.cch;
if (d.Sf["CBb"+n]) d.Sf["CBb"+n].checked = !!v.c16;
if (d.Sf["CBio"+n]) d.Sf["CBio"+n].checked = !!v.cio;
if (d.Sf["CBt0h"+n]) d.Sf["CBt0h"+n].value = v.ct0h ?? 300;
if (d.Sf["CBt0l"+n]) d.Sf["CBt0l"+n].value = v.ct0l ?? 900;
if (d.Sf["CBt1h"+n]) d.Sf["CBt1h"+n].value = v.ct1h ?? 700;
if (d.Sf["CBt1l"+n]) d.Sf["CBt1l"+n].value = v.ct1l ?? 500;
if (d.Sf["CBrst"+n]) d.Sf["CBrst"+n].value = v.crst ?? 300;
let mask = v.cinv || 0;
for (let ci = 0; ci < 6; ci++) {
let color = d.Sf["CBc"+ci+n];
if (color) color.value = Array.isArray(v.cmap) && ci < v.cmap.length ? v.cmap[ci] : 0;
let inv = d.Sf["CBi"+ci+n];
if (inv) inv.checked = !!(mask & (1 << ci));
}
}
// iterate every configured LED bus: fn(n, t, sel, idx)
function forEachBus(fn) {
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((sel, idx) => {
Expand Down Expand Up @@ -106,8 +156,10 @@
try {
if (d && d.hw && d.hw.led && Array.isArray(d.hw.led.ins)) {
d.hw.led.ins.forEach((v,i)=>{
let el = d.getElementsByName("SF"+i)[0];
let n = chrID(i);
let el = d.getElementsByName("SF"+n)[0];
if (el) el.value = (typeof v.bsf !== 'undefined') ? (v.bsf || 100) : 100;
applyCustomBusConfig(n, v);
});
}
} catch(e) {}
Expand Down Expand Up @@ -422,7 +474,8 @@
d.Sf["MA"+n].min = (!isDig(t)) ? 0 : 250; // set minimum value for PSU mA
}
gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory
gRGBW |= hasW(t); // RGBW checkbox
let busHasWhite = busHasW(t, n);
gRGBW |= busHasWhite; // RGBW checkbox
gId("co"+n).style.display = (isVir(t) || isAna(t) || isHub75(t) || isCst(n)) ? "none":"inline"; // hide color order for PWM / customized bus
gId("dig"+n+"w").style.display = (isDig(t) && hasW(t) && !isCst(n)) ? "inline":"none"; // show swap channels dropdown (not for customized bus)
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
Expand All @@ -444,7 +497,9 @@
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
gId("dig"+n+"s").style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide skip 1st for virtual & analog
gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) && maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32)
gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white
// Show auto-white for native RGBW buses and for custom maps whose
// config exposes white, even if the selected base type is RGB.
gId("dig"+n+"a").style.display = busHasWhite ? "inline":"none"; // auto calculate white
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°)
//gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description
Expand Down Expand Up @@ -909,21 +964,23 @@
let l = c.hw.led;
l.ins.forEach((v,i,a)=>{
addLEDs(1);
for (var j=0; j<v.pin.length; j++) d.getElementsByName(`L${j}${i}`)[0].value = v.pin[j];
d.getElementsByName("LT"+i)[0].value = v.type;
d.getElementsByName("LD"+i)[0].value = v.drv || 0; // output driver type (RMT or I2S, default to RMT if not set)
d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order & 0x0F;
d.getElementsByName("SL"+i)[0].value = v.skip;
d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev;
d.getElementsByName("AW"+i)[0].value = v.rgbwm;
d.getElementsByName("WO"+i)[0].value = (v.order>>4) & 0x0F;
d.getElementsByName("SP"+i)[0].value = v.freq;
d.getElementsByName("SF"+i)[0].value = v.bsf || 100; // bus speed factor, default to 100%
d.getElementsByName("LA"+i)[0].value = v.ledma;
d.getElementsByName("MA"+i)[0].value = v.maxpwr;
let n = chrID(i);
for (var j=0; j<v.pin.length; j++) d.getElementsByName(`L${j}${n}`)[0].value = v.pin[j];
d.getElementsByName("LT"+n)[0].value = v.type;
d.getElementsByName("LD"+n)[0].value = v.drv || 0; // output driver type (RMT or I2S, default to RMT if not set)
d.getElementsByName("LS"+n)[0].value = v.start;
d.getElementsByName("LC"+n)[0].value = v.len;
d.getElementsByName("CO"+n)[0].value = v.order & 0x0F;
d.getElementsByName("SL"+n)[0].value = v.skip;
d.getElementsByName("RF"+n)[0].checked = v.ref;
d.getElementsByName("CV"+n)[0].checked = v.rev;
d.getElementsByName("AW"+n)[0].value = v.rgbwm;
d.getElementsByName("WO"+n)[0].value = (v.order>>4) & 0x0F;
d.getElementsByName("SP"+n)[0].value = v.freq;
d.getElementsByName("SF"+n)[0].value = v.bsf || 100; // bus speed factor, default to 100%
d.getElementsByName("LA"+n)[0].value = v.ledma;
d.getElementsByName("MA"+n)[0].value = v.maxpwr;
applyCustomBusConfig(n, v);
});
d.getElementsByName("MA")[0].value = l.maxpwr;
d.getElementsByName("ABL")[0].checked = l.maxpwr > 0;
Expand Down Expand Up @@ -1408,4 +1465,4 @@ <h3>Advanced</h3>
</form>
<div id="toast"></div>
</body>
</html>
</html>
Loading