Support white addressable LED strips (#3073)

* Support white addressable LED strips

* Various white handling tweaks

Allow RGB controls for white-only busses depending on AWM (makes palette-only FX work on non-RGB addressable busses)
Fixed RGB controls hidden if segment contained any non-RGB bus (even though there is also an RGB bus in that segment)
New Max auto white mode
Added hasCCT() bus method
Rename methods to be clearer
WS2811 White getPixelColor fix()

* Fix merge conflict (bus manager cpp)
This commit is contained in:
Christian Schwinne 2023-02-14 01:33:06 +01:00 committed by GitHub
parent f2459ea904
commit 74156b7ed8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 3403 additions and 3364 deletions

View File

@ -749,7 +749,7 @@ uint8_t Segment::differs(Segment& b) const {
}
void Segment::refreshLightCapabilities() {
uint8_t capabilities = 0x01;
uint8_t capabilities = 0;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
@ -759,19 +759,17 @@ void Segment::refreshLightCapabilities() {
if (bus->getStart() + bus->getLength() <= start) continue;
uint8_t type = bus->getType();
if (type == TYPE_ONOFF || type == TYPE_ANALOG_1CH || (!cctFromRgb && type == TYPE_ANALOG_2CH)) capabilities &= 0xFE; // does not support RGB
if (bus->isRgbw()) capabilities |= 0x02; // segment supports white channel
if (!cctFromRgb) {
switch (type) {
case TYPE_ANALOG_5CH:
case TYPE_ANALOG_2CH:
capabilities |= 0x04; //segment supports white CCT
}
if (bus->hasRGB() || (cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB;
if (!cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT;
if (correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider)
if (bus->hasWhite()) {
uint8_t aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode();
bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed
// if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses
if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB;
// if auto white calculation from RGB is disabled/optional (None/Dual), allow white channel adjustments
if ( whiteSlider) capabilities |= SEG_CAPABILITY_W;
}
if (correctWB && !(type == TYPE_ANALOG_1CH || type == TYPE_ONOFF)) capabilities |= 0x04; //white balance correction (uses CCT slider)
uint8_t aWM = Bus::getAutoWhiteMode()<255 ? Bus::getAutoWhiteMode() : bus->getAWMode();
bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed
if (bus->isRgbw() && (whiteSlider || !(capabilities & 0x01))) capabilities |= 0x08; // allow white channel adjustments (AWM allows or is not RGB)
}
_capabilities = capabilities;
}
@ -1000,7 +998,7 @@ void WS2812FX::finalizeInit(void)
if (bus == nullptr) continue;
if (bus->getStart() + bus->getLength() > MAX_LEDS) break;
//RGBW mode is enabled if at least one of the strips is RGBW
_hasWhiteChannel |= bus->isRgbw();
_hasWhiteChannel |= bus->hasWhite();
//refresh is required to remain off if at least one of the strips requires the refresh.
_isOffRefreshRequired |= bus->isOffRefreshRequired();
uint16_t busEnd = bus->getStart() + bus->getLength();
@ -1167,7 +1165,7 @@ void WS2812FX::estimateCurrentAndLimitBri() {
}
}
if (bus->isRgbw()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
if (bus->hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
busPowerSum *= 3;
busPowerSum = busPowerSum >> 2; //same as /= 4
}
@ -1335,12 +1333,7 @@ bool WS2812FX::hasRGBWBus(void) {
for (size_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (bus == nullptr || bus->getLength()==0) break;
switch (bus->getType()) {
case TYPE_SK6812_RGBW:
case TYPE_TM1814:
case TYPE_ANALOG_4CH:
return true;
}
if (bus->hasRGB() && bus->hasWhite()) return true;
}
return false;
}

View File

@ -85,6 +85,7 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) {
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
if (aWM == RGBW_MODE_MAX) return RGBW32(r, g, b, r > g ? (r > b ? r : b) : (g > b ? g : b)); // brightest RGB channel
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
if (aWM == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode
return RGBW32(r, g, b, w);
@ -107,7 +108,9 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) : Bu
_len = bc.count + _skip;
_iType = PolyBus::getI(bc.type, _pins, nr);
if (_iType == I_NONE) return;
_busPtr = PolyBus::create(_iType, _pins, _len, nr);
uint16_t lenToCreate = _len;
if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus
_busPtr = PolyBus::create(_iType, _pins, lenToCreate, nr);
_valid = (_busPtr != nullptr);
_colorOrder = bc.colorOrder;
DEBUG_PRINTF("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n", _valid?"S":"Uns", nr, _len, bc.type, _pins[0],_pins[1],_iType);
@ -142,17 +145,40 @@ void BusDigital::setStatusPixel(uint32_t c) {
}
void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) {
if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814) c = autoWhiteCalc(c);
if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814 || _type == TYPE_WS2812_1CH_X3) 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, _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder));
uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
uint16_t pOld = pix;
pix = IC_INDEX_WS2812_1CH_3X(pix);
uint32_t cOld = PolyBus::getPixelColor(_busPtr, _iType, pix, co);
switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set)
case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break;
case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break;
case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break;
}
}
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co);
}
uint32_t BusDigital::getPixelColor(uint16_t pix) {
if (reversed) pix = _len - pix -1;
else pix += _skip;
return PolyBus::getPixelColor(_busPtr, _iType, pix, _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder));
uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
uint16_t pOld = pix;
pix = IC_INDEX_WS2812_1CH_3X(pix);
uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, pix, co);
switch (pOld % 3) { // get only the single channel
case 0: c = RGBW32(G(c), G(c), G(c), G(c)); break;
case 1: c = RGBW32(R(c), R(c), R(c), R(c)); break;
case 2: c = RGBW32(B(c), B(c), B(c), B(c)); break;
}
return c;
}
return PolyBus::getPixelColor(_busPtr, _iType, pix, co);
}
uint8_t BusDigital::getPins(uint8_t* pinArray) {
@ -387,7 +413,7 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
if (!_valid || pix >= _len) return;
if (isRgbw()) c = autoWhiteCalc(c);
if (hasWhite()) c = autoWhiteCalc(c);
if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
uint16_t offset = pix * _UDPchannels;
_data[offset] = R(c);

View File

@ -11,6 +11,13 @@
#define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit)))
#define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit))))
#define NUM_ICS_WS2812_1CH_3X(len) (((len)+2)/3) // 1 WS2811 IC controls 3 zones (each zone has 1 LED, W)
#define IC_INDEX_WS2812_1CH_3X(i) ((i)/3)
#define NUM_ICS_WS2812_2CH_3X(len) (((len)+1)*2/3) // 2 WS2811 ICs control 3 zones (each zone has 2 LEDs, CW and WW)
#define IC_INDEX_WS2812_2CH_3X(i) ((i)*2/3)
#define WS2812_2CH_3X_SPANS_2_ICS(i) ((i)&0x01) // every other LED zone is on two different ICs
//temporary struct for passing bus configuration to bus
struct BusConfig {
uint8_t type;
@ -90,7 +97,7 @@ class Bus {
{
_type = type;
_start = start;
_autoWhiteMode = Bus::isRgbw(_type) ? aw : RGBW_MODE_MANUAL_ONLY;
_autoWhiteMode = Bus::hasWhite(_type) ? aw : RGBW_MODE_MANUAL_ONLY;
};
virtual ~Bus() {} //throw the bus under the bus
@ -114,20 +121,20 @@ class Bus {
inline bool isOffRefreshRequired() { return _needsRefresh; }
bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; }
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;
if (type == TYPE_NET_DDP_RGBW) return true;
return false;
}
virtual bool hasRGB() {
if (_type == TYPE_WS2812_1CH || _type == TYPE_WS2812_WWA || _type == TYPE_ANALOG_1CH || _type == TYPE_ANALOG_2CH || _type == TYPE_ONOFF) return false;
if ((_type >= TYPE_WS2812_1CH && _type <= TYPE_WS2812_WWA) || _type == TYPE_ANALOG_1CH || _type == TYPE_ANALOG_2CH || _type == TYPE_ONOFF) return false;
return true;
}
virtual bool hasWhite() {
if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814 || _type == TYPE_WS2812_1CH || _type == TYPE_WS2812_WWA ||
_type == TYPE_ANALOG_1CH || _type == TYPE_ANALOG_2CH || _type == TYPE_ANALOG_4CH || _type == TYPE_ANALOG_5CH || _type == TYPE_NET_DDP_RGBW) return true;
virtual bool hasWhite() { return Bus::hasWhite(_type); }
static bool hasWhite(uint8_t type) {
if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true; // digital types with white channel
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; // analog types with white channel
if (type == TYPE_NET_DDP_RGBW) return true; // network types with white channel
return false;
}
virtual bool hasCCT() {
if (_type == TYPE_WS2812_2CH_X3 || _type == TYPE_WS2812_WWA ||
_type == TYPE_ANALOG_2CH || _type == TYPE_ANALOG_5CH) return true;
return false;
}
static void setCCT(uint16_t cct) {
@ -141,10 +148,10 @@ class Bus {
if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND;
#endif
}
inline void setAWMode(uint8_t m) { if (m < 4) _autoWhiteMode = m; }
inline uint8_t getAWMode() { return _autoWhiteMode; }
inline static void setAutoWhiteMode(uint8_t m) { if (m < 4) _gAWM = m; else _gAWM = 255; }
inline static uint8_t getAutoWhiteMode() { return _gAWM; }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; }
inline static void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; }
inline static uint8_t getGlobalAWMode() { return _gAWM; }
bool reversed = false;
@ -292,10 +299,6 @@ class BusNetwork : public Bus {
uint8_t getPins(uint8_t* pinArray);
bool isRgbw() {
return _rgbw;
}
uint16_t getLength() {
return _len;
}

View File

@ -894,6 +894,8 @@ class PolyBus {
uint8_t offset = pins[0] -1; //for driver: 0 = uart0, 1 = uart1, 2 = dma, 3 = bitbang
if (offset > 3) offset = 3;
switch (busType) {
case TYPE_WS2812_1CH_X3:
case TYPE_WS2812_2CH_X3:
case TYPE_WS2812_RGB:
case TYPE_WS2812_WWA:
return I_8266_U0_NEO_3 + offset;
@ -926,6 +928,8 @@ class PolyBus {
if (num > 7) offset = num -7;
#endif
switch (busType) {
case TYPE_WS2812_1CH_X3:
case TYPE_WS2812_2CH_X3:
case TYPE_WS2812_RGB:
case TYPE_WS2812_WWA:
return I_32_RN_NEO_3 + offset;

View File

@ -84,7 +84,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t autoWhiteMode = RGBW_MODE_MANUAL_ONLY;
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | 255);
Bus::setGlobalAWMode(hw_led[F("rgbwm")] | 255);
CJSON(correctWB, hw_led["cct"]);
CJSON(cctFromRgb, hw_led[F("cr")]);
CJSON(strip.cctBlending, hw_led[F("cb")]);
@ -700,7 +700,7 @@ void serializeConfig() {
hw_led[F("cr")] = cctFromRgb;
hw_led[F("cb")] = strip.cctBlending;
hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getAutoWhiteMode(); // global override
hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override
hw_led[F("ld")] = strip.useLedsArray;
#ifndef WLED_DISABLE_2D
@ -745,7 +745,7 @@ void serializeConfig() {
ins[F("skip")] = bus->skippedLeds();
ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbwm")] = bus->getAWMode();
ins[F("rgbwm")] = bus->getAutoWhiteMode();
}
JsonArray hw_com = hw.createNestedArray(F("com"));

View File

@ -143,11 +143,13 @@
#define CALL_MODE_BUTTON_PRESET 12 //button/IR JSON preset/macro
//RGB to RGBW conversion mode
#define RGBW_MODE_MANUAL_ONLY 0 //No automatic white channel calculation. Manual white channel slider
#define RGBW_MODE_AUTO_BRIGHTER 1 //New algorithm. Adds as much white as the darkest RGBW channel
#define RGBW_MODE_AUTO_ACCURATE 2 //New algorithm. Adds as much white as the darkest RGBW channel and subtracts this amount from each RGB channel
#define RGBW_MODE_DUAL 3 //Manual slider + auto calculation. Automatically calculates only if manual slider is set to off (0)
#define RGBW_MODE_LEGACY 4 //Old floating algorithm. Too slow for realtime and palette support
#define RGBW_MODE_MANUAL_ONLY 0 // No automatic white channel calculation. Manual white channel slider
#define RGBW_MODE_AUTO_BRIGHTER 1 // New algorithm. Adds as much white as the darkest RGBW channel
#define RGBW_MODE_AUTO_ACCURATE 2 // New algorithm. Adds as much white as the darkest RGBW channel and subtracts this amount from each RGB channel
#define RGBW_MODE_DUAL 3 // Manual slider + auto calculation. Automatically calculates only if manual slider is set to off (0)
#define RGBW_MODE_MAX 4 // Sets white to the value of the brightest RGB channel (good for white-only LEDs without any RGB)
//#define RGBW_MODE_LEGACY 4 // Old floating algorithm. Too slow for realtime and palette support (unused)
#define AW_GLOBAL_DISABLED 255 // Global auto white mode override disabled. Per-bus setting is used
//realtime modes
#define REALTIME_MODE_INACTIVE 0
@ -193,7 +195,9 @@
#define TYPE_NONE 0 //light is not configured
#define TYPE_RESERVED 1 //unused. Might indicate a "virtual" light
//Digital types (data pin only) (16-31)
#define TYPE_WS2812_1CH 20 //white-only chips
#define TYPE_WS2812_1CH 18 //white-only chips (1 channel per IC) (unused)
#define TYPE_WS2812_1CH_X3 19 //white-only chips (3 channels per IC)
#define TYPE_WS2812_2CH_X3 20 //CCT chips (1st IC controls WW + CW of 1st zone and CW of 2nd zone, 2nd IC controls WW of 2nd zone and WW + CW of 3rd zone)
#define TYPE_WS2812_WWA 21 //amber + warm + cold white
#define TYPE_WS2812_RGB 22
#define TYPE_GS8608 23 //same driver as WS2812, but will require signal 2x per second (else displays test pattern)
@ -290,6 +294,11 @@
//Playlist option byte
#define PL_OPTION_SHUFFLE 0x01
// Segment capability byte
#define SEG_CAPABILITY_RGB 0x01
#define SEG_CAPABILITY_W 0x02
#define SEG_CAPABILITY_CCT 0x04
// WLED Error modes
#define ERR_NONE 0 // All good :)
#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?)

View File

@ -1319,7 +1319,7 @@ function readState(s,command=false)
sellvl = 2;
var lc = lastinfo.leds.seglc[s.seg[i].id];
hasRGB |= !!(lc & 0x01);
hasWhite |= !!(lc & 0x08); // & 0x02 contains W channel
hasWhite |= !!(lc & 0x02);
hasCCT |= !!(lc & 0x04);
}
}
@ -1327,7 +1327,7 @@ function readState(s,command=false)
if (sellvl == 1) {
var lc = lastinfo.leds.seglc[i.id];
hasRGB = !!(lc & 0x01);
hasWhite = !!(lc & 0x08); // & 0x02 contains W channel
hasWhite = !!(lc & 0x02);
hasCCT = !!(lc & 0x04);
}
if (!i) {

View File

@ -184,7 +184,7 @@
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) ? (()=>{return false}) : (()=>{}); // prevent change for TM1814
isRGBW = (t == 30 || t == 31 || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h
isRGBW = ((t > 17 && t < 22) || t == 30 || t == 31 || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h
gId("co"+n).style.display = ((t >= 80 && t < 96) || (t >= 40 && t < 48)) ? "none":"inline"; // hide color order for PWM
gId("dig"+n+"w").style.display = (t == 30 || t == 31) ? "inline":"none"; // show swap channels dropdown
if (!(t == 30 || t == 31)) d.getElementsByName("WO"+n)[0].value = 0; // reset swapping
@ -335,6 +335,7 @@ ${i+1}:
<option value="52">LPD8806</option>\
<option value="54">LPD6803</option>\
<option value="53">P9813</option>\
<option value="19">WS2811 White</option>\
<option value="40">On/Off</option>\
<option value="41">PWM White</option>\
<option value="42">PWM CCT</option>\
@ -369,7 +370,7 @@ ${i+1}:
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
<div id="dig${i}a" style="display:inline"><br>Auto-calculate white channel from RGB:<br><select name="AW${i}"><option value=0>None</option><option value=1>Brighter</option><option value=2>Accurate</option><option value=3>Dual</option></select>&nbsp;</div>
<div id="dig${i}a" style="display:inline"><br>Auto-calculate white channel from RGB:<br><select name="AW${i}"><option value=0>None</option><option value=1>Brighter</option><option value=2>Accurate</option><option value=3>Dual</option><option value=4>Max</option></select>&nbsp;</div>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
}
@ -667,6 +668,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
<option value=1>Brighter</option>
<option value=2>Accurate</option>
<option value=3>Dual</option>
<option value=4>Max</option>
</select>
<br>
Calculate CCT from RGB: <input type="checkbox" name="CR"> <br>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -83,7 +83,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
cctFromRgb = request->hasArg(F("CR"));
strip.cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending);
Bus::setAutoWhiteMode(request->arg(F("AW")).toInt());
Bus::setGlobalAWMode(request->arg(F("AW")).toInt());
strip.setTargetFps(request->arg(F("FR")).toInt());
strip.useLedsArray = request->hasArg(F("LD"));

View File

@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2302080
#define VERSION 2302120
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG

View File

@ -382,7 +382,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("CR"),cctFromRgb);
sappend('v',SET_F("CB"),strip.cctBlending);
sappend('v',SET_F("FR"),strip.getTargetFps());
sappend('v',SET_F("AW"),Bus::getAutoWhiteMode());
sappend('v',SET_F("AW"),Bus::getGlobalAWMode());
sappend('v',SET_F("LD"),strip.useLedsArray);
for (uint8_t s=0; s < busses.getNumBusses(); s++) {
@ -412,7 +412,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',cv,bus->reversed);
sappend('v',sl,bus->skippedLeds());
sappend('c',rf,bus->isOffRefreshRequired());
sappend('v',aw,bus->getAWMode());
sappend('v',aw,bus->getAutoWhiteMode());
sappend('v',wo,bus->getColorOrder() >> 4);
}
sappend('v',SET_F("MA"),strip.ablMilliampsMax);