CCT (color white balance support)
This commit is contained in:
parent
00f1b483eb
commit
39b7b3ad53
@ -247,7 +247,7 @@ class WS2812FX {
|
|||||||
|
|
||||||
// segment parameters
|
// segment parameters
|
||||||
public:
|
public:
|
||||||
typedef struct Segment { // 29 (32 in memory?) bytes
|
typedef struct Segment { // 30 (33 in memory?) bytes
|
||||||
uint16_t start;
|
uint16_t start;
|
||||||
uint16_t stop; //segment invalid if stop == 0
|
uint16_t stop; //segment invalid if stop == 0
|
||||||
uint16_t offset;
|
uint16_t offset;
|
||||||
@ -259,6 +259,7 @@ class WS2812FX {
|
|||||||
uint8_t grouping, spacing;
|
uint8_t grouping, spacing;
|
||||||
uint8_t opacity;
|
uint8_t opacity;
|
||||||
uint32_t colors[NUM_COLORS];
|
uint32_t colors[NUM_COLORS];
|
||||||
|
uint8_t cct;
|
||||||
char *name;
|
char *name;
|
||||||
bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed
|
bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed
|
||||||
if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false;
|
if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false;
|
||||||
|
@ -220,6 +220,17 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
|
|||||||
uint16_t realIndex = realPixelIndex(i);
|
uint16_t realIndex = realPixelIndex(i);
|
||||||
uint16_t len = SEGMENT.length();
|
uint16_t len = SEGMENT.length();
|
||||||
|
|
||||||
|
// determine if we can do white balance
|
||||||
|
int16_t cct = -1;
|
||||||
|
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
|
||||||
|
Bus *bus = busses.getBus(b);
|
||||||
|
if (bus == nullptr || !bus->containsPixel(realIndex)) continue;
|
||||||
|
if (allowCCT || bus->getType() == TYPE_ANALOG_2CH || bus->getType() == TYPE_ANALOG_5CH) {
|
||||||
|
cct = SEGMENT.cct;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
|
for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
|
||||||
uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j);
|
uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j);
|
||||||
if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
|
if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
|
||||||
@ -230,14 +241,14 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
|
|||||||
if (indexMir >= SEGMENT.stop) indexMir -= len;
|
if (indexMir >= SEGMENT.stop) indexMir -= len;
|
||||||
|
|
||||||
if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir];
|
if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir];
|
||||||
busses.setPixelColor(indexMir, col);
|
busses.setPixelColor(indexMir, col, cct);
|
||||||
}
|
}
|
||||||
/* offset/phase */
|
/* offset/phase */
|
||||||
indexSet += SEGMENT.offset;
|
indexSet += SEGMENT.offset;
|
||||||
if (indexSet >= SEGMENT.stop) indexSet -= len;
|
if (indexSet >= SEGMENT.stop) indexSet -= len;
|
||||||
|
|
||||||
if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet];
|
if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet];
|
||||||
busses.setPixelColor(indexSet, col);
|
busses.setPixelColor(indexSet, col, cct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { //live data, etc.
|
} else { //live data, etc.
|
||||||
@ -623,6 +634,7 @@ void WS2812FX::resetSegments() {
|
|||||||
_segments[0].setOption(SEG_OPTION_SELECTED, 1);
|
_segments[0].setOption(SEG_OPTION_SELECTED, 1);
|
||||||
_segments[0].setOption(SEG_OPTION_ON, 1);
|
_segments[0].setOption(SEG_OPTION_ON, 1);
|
||||||
_segments[0].opacity = 255;
|
_segments[0].opacity = 255;
|
||||||
|
_segments[0].cct = 128;
|
||||||
|
|
||||||
for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++)
|
for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++)
|
||||||
{
|
{
|
||||||
@ -630,6 +642,7 @@ void WS2812FX::resetSegments() {
|
|||||||
_segments[i].grouping = 1;
|
_segments[i].grouping = 1;
|
||||||
_segments[i].setOption(SEG_OPTION_ON, 1);
|
_segments[i].setOption(SEG_OPTION_ON, 1);
|
||||||
_segments[i].opacity = 255;
|
_segments[i].opacity = 255;
|
||||||
|
_segments[i].cct = 128;
|
||||||
_segments[i].speed = DEFAULT_SPEED;
|
_segments[i].speed = DEFAULT_SPEED;
|
||||||
_segments[i].intensity = DEFAULT_INTENSITY;
|
_segments[i].intensity = DEFAULT_INTENSITY;
|
||||||
_segment_runtimes[i].reset();
|
_segment_runtimes[i].reset();
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
#include "bus_wrapper.h"
|
#include "bus_wrapper.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
//color.cpp
|
||||||
|
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
|
||||||
|
|
||||||
// enable additional debug output
|
// enable additional debug output
|
||||||
#ifdef WLED_DEBUG
|
#ifdef WLED_DEBUG
|
||||||
#ifndef ESP8266
|
#ifndef ESP8266
|
||||||
@ -65,79 +68,48 @@ struct BusConfig {
|
|||||||
//parent class of BusDigital and BusPwm
|
//parent class of BusDigital and BusPwm
|
||||||
class Bus {
|
class Bus {
|
||||||
public:
|
public:
|
||||||
Bus(uint8_t type, uint16_t start) {
|
Bus(uint8_t type, uint16_t start) {
|
||||||
_type = type;
|
_type = type;
|
||||||
_start = start;
|
_start = start;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual void show() {}
|
virtual ~Bus() {} //throw the bus under the bus
|
||||||
virtual bool canShow() { return true; }
|
|
||||||
|
|
||||||
virtual void setPixelColor(uint16_t pix, uint32_t c) {};
|
virtual void show() {}
|
||||||
|
virtual bool canShow() { return true; }
|
||||||
|
virtual void setPixelColor(uint16_t pix, uint32_t c) {};
|
||||||
|
virtual void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {};
|
||||||
|
virtual uint32_t getPixelColor(uint16_t pix) { return 0; };
|
||||||
|
virtual void setBrightness(uint8_t b) {};
|
||||||
|
virtual void cleanup() {};
|
||||||
|
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
|
||||||
|
virtual uint16_t getLength() { return 1; }
|
||||||
|
virtual void setColorOrder() {}
|
||||||
|
virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
|
||||||
|
virtual uint8_t skippedLeds() { return 0; }
|
||||||
|
|
||||||
virtual void setBrightness(uint8_t b) {};
|
inline uint16_t getStart() { return _start; }
|
||||||
|
inline void setStart(uint16_t start) { _start = start; }
|
||||||
|
inline uint8_t getType() { return _type; }
|
||||||
|
inline bool isOk() { return _valid; }
|
||||||
|
inline bool isOffRefreshRequired() { return _needsRefresh; }
|
||||||
|
inline bool containsPixel(uint16_t pix) { return pix >= _start; }
|
||||||
|
|
||||||
virtual uint32_t getPixelColor(uint16_t pix) { return 0; };
|
virtual bool isRgbw() { return false; }
|
||||||
|
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;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
virtual void cleanup() {};
|
bool reversed = false;
|
||||||
|
|
||||||
virtual ~Bus() { //throw the bus under the bus
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
|
|
||||||
|
|
||||||
inline uint16_t getStart() {
|
|
||||||
return _start;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void setStart(uint16_t start) {
|
|
||||||
_start = start;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual uint16_t getLength() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void setColorOrder() {}
|
|
||||||
|
|
||||||
virtual uint8_t getColorOrder() {
|
|
||||||
return COL_ORDER_RGB;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool isRgbw() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual uint8_t skippedLeds() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint8_t getType() {
|
|
||||||
return _type;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool isOk() {
|
|
||||||
return _valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool isOffRefreshRequired() {
|
|
||||||
return _needsRefresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool reversed = false;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint8_t _type = TYPE_NONE;
|
uint8_t _type = TYPE_NONE;
|
||||||
uint8_t _bri = 255;
|
uint8_t _bri = 255;
|
||||||
uint16_t _start = 0;
|
uint16_t _start = 0;
|
||||||
bool _valid = false;
|
bool _valid = false;
|
||||||
bool _needsRefresh = false;
|
bool _needsRefresh = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -190,6 +162,11 @@ class BusDigital : public Bus {
|
|||||||
PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder);
|
PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {
|
||||||
|
c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT
|
||||||
|
setPixelColor(pix, c);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t getPixelColor(uint16_t pix) {
|
uint32_t getPixelColor(uint16_t pix) {
|
||||||
if (reversed) pix = _len - pix -1;
|
if (reversed) pix = _len - pix -1;
|
||||||
else pix += _skip;
|
else pix += _skip;
|
||||||
@ -285,6 +262,34 @@ class BusPwm : public Bus {
|
|||||||
_valid = true;
|
_valid = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {
|
||||||
|
if (pix != 0 || !_valid) return; //only react to first pixel
|
||||||
|
c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT (w remains unchanged)
|
||||||
|
uint8_t r = c >> 16;
|
||||||
|
uint8_t g = c >> 8;
|
||||||
|
uint8_t b = c ;
|
||||||
|
uint8_t w = c >> 24;
|
||||||
|
|
||||||
|
switch (_type) {
|
||||||
|
case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value
|
||||||
|
_data[0] = max(r, max(g, max(b, w)));
|
||||||
|
break;
|
||||||
|
case TYPE_ANALOG_2CH: //warm white + cold white
|
||||||
|
// perhaps a non-linear adjustment would be in order. need to test
|
||||||
|
_data[1] = (w * cct) / 255;
|
||||||
|
_data[0] = 255 - _data[1]; // or (w * (255-cct)) / 255;
|
||||||
|
break;
|
||||||
|
case TYPE_ANALOG_5CH: //RGB + warm white + cold white
|
||||||
|
// perhaps a non-linear adjustment would be in order. need to test
|
||||||
|
_data[4] = (w * cct) / 255; w = 255 - w; // or (w * (255-cct)) / 255;
|
||||||
|
case TYPE_ANALOG_4CH: //RGBW
|
||||||
|
_data[3] = w;
|
||||||
|
case TYPE_ANALOG_3CH: //standard dumb RGB
|
||||||
|
_data[0] = r; _data[1] = g; _data[2] = b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setPixelColor(uint16_t pix, uint32_t c) {
|
void setPixelColor(uint16_t pix, uint32_t c) {
|
||||||
if (pix != 0 || !_valid) return; //only react to first pixel
|
if (pix != 0 || !_valid) return; //only react to first pixel
|
||||||
uint8_t r = c >> 16;
|
uint8_t r = c >> 16;
|
||||||
@ -295,14 +300,11 @@ class BusPwm : public Bus {
|
|||||||
switch (_type) {
|
switch (_type) {
|
||||||
case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value
|
case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value
|
||||||
_data[0] = max(r, max(g, max(b, w))); break;
|
_data[0] = max(r, max(g, max(b, w))); break;
|
||||||
|
case TYPE_ANALOG_2CH: //warm white + cold white
|
||||||
case TYPE_ANALOG_2CH: //warm white + cold white, we'll need some nice handling here, for now just R+G channels
|
|
||||||
case TYPE_ANALOG_3CH: //standard dumb RGB
|
case TYPE_ANALOG_3CH: //standard dumb RGB
|
||||||
case TYPE_ANALOG_4CH: //RGBW
|
case TYPE_ANALOG_4CH: //standard dumb RGBW
|
||||||
case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB
|
case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB
|
||||||
_data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = 0; break;
|
_data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = 0; break;
|
||||||
|
|
||||||
default: return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,6 +419,11 @@ class BusNetwork : public Bus {
|
|||||||
if (_rgbw) _data[offset+3] = 0xFF & (c >> 24);
|
if (_rgbw) _data[offset+3] = 0xFF & (c >> 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {
|
||||||
|
c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT
|
||||||
|
setPixelColor(pix, c);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t getPixelColor(uint16_t pix) {
|
uint32_t getPixelColor(uint16_t pix) {
|
||||||
if (!_valid || pix >= _len) return 0;
|
if (!_valid || pix >= _len) return 0;
|
||||||
uint16_t offset = pix * _UDPchannels;
|
uint16_t offset = pix * _UDPchannels;
|
||||||
@ -538,12 +545,13 @@ class BusManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPixelColor(uint16_t pix, uint32_t c) {
|
void setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1) {
|
||||||
for (uint8_t i = 0; i < numBusses; i++) {
|
for (uint8_t i = 0; i < numBusses; i++) {
|
||||||
Bus* b = busses[i];
|
Bus* b = busses[i];
|
||||||
uint16_t bstart = b->getStart();
|
uint16_t bstart = b->getStart();
|
||||||
if (pix < bstart || pix >= bstart + b->getLength()) continue;
|
if (pix < bstart || pix >= bstart + b->getLength()) continue;
|
||||||
busses[i]->setPixelColor(pix - bstart, c);
|
if (cct<0) busses[i]->setPixelColor(pix - bstart, c); // no white balance
|
||||||
|
else busses[i]->setPixelColor(pix - bstart, c, cct); // do white balance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
|||||||
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
|
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
|
||||||
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
|
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
|
||||||
CJSON(strip.rgbwMode, hw_led[F("rgbwm")]);
|
CJSON(strip.rgbwMode, hw_led[F("rgbwm")]);
|
||||||
|
CJSON(allowCCT, hw_led["cct"]);
|
||||||
|
|
||||||
JsonArray ins = hw_led["ins"];
|
JsonArray ins = hw_led["ins"];
|
||||||
|
|
||||||
@ -530,6 +531,7 @@ void serializeConfig() {
|
|||||||
hw_led[F("maxpwr")] = strip.ablMilliampsMax;
|
hw_led[F("maxpwr")] = strip.ablMilliampsMax;
|
||||||
hw_led[F("ledma")] = strip.milliampsPerLed;
|
hw_led[F("ledma")] = strip.milliampsPerLed;
|
||||||
hw_led[F("rgbwm")] = strip.rgbwMode;
|
hw_led[F("rgbwm")] = strip.rgbwMode;
|
||||||
|
hw_led["cct"] = allowCCT;
|
||||||
|
|
||||||
JsonArray hw_led_ins = hw_led.createNestedArray("ins");
|
JsonArray hw_led_ins = hw_led.createNestedArray("ins");
|
||||||
|
|
||||||
@ -546,7 +548,7 @@ void serializeConfig() {
|
|||||||
ins[F("order")] = bus->getColorOrder();
|
ins[F("order")] = bus->getColorOrder();
|
||||||
ins["rev"] = bus->reversed;
|
ins["rev"] = bus->reversed;
|
||||||
ins[F("skip")] = bus->skippedLeds();
|
ins[F("skip")] = bus->skippedLeds();
|
||||||
ins["type"] = bus->getType() & 0x7F;;
|
ins["type"] = bus->getType() & 0x7F;
|
||||||
ins["ref"] = bus->isOffRefreshRequired();
|
ins["ref"] = bus->isOffRefreshRequired();
|
||||||
ins[F("rgbw")] = bus->isRgbw();
|
ins[F("rgbw")] = bus->isRgbw();
|
||||||
}
|
}
|
||||||
|
@ -467,6 +467,22 @@ img {
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#rwrap .sliderdisplay,
|
||||||
|
#gwrap .sliderdisplay,
|
||||||
|
#bwrap .sliderdisplay,
|
||||||
|
#wwrap .sliderdisplay,
|
||||||
|
#wbal .sliderdisplay {
|
||||||
|
height: 28px;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
left: 0; right: 0;
|
||||||
|
/*border: 1px solid var(--c-b);*/
|
||||||
|
}
|
||||||
|
#rwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #f00); }
|
||||||
|
#gwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #0f0); }
|
||||||
|
#bwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #00f); }
|
||||||
|
#wwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #fff); }
|
||||||
|
#wbal .sliderdisplay { background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #d4e0ff); }
|
||||||
|
|
||||||
.sliderbubble {
|
.sliderbubble {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
@ -492,6 +508,14 @@ input[type=range] {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
#rwrap input[type=range],
|
||||||
|
#gwrap input[type=range],
|
||||||
|
#bwrap input[type=range],
|
||||||
|
#wwrap input[type=range],
|
||||||
|
#wbal input[type=range] {
|
||||||
|
width: 252px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
input[type=range]:focus {
|
input[type=range]:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
@ -527,8 +551,24 @@ input[type=range]:active + .sliderbubble {
|
|||||||
display: inline;
|
display: inline;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
#rwrap input[type=range]::-webkit-slider-thumb,
|
||||||
#wwrap {
|
#gwrap input[type=range]::-webkit-slider-thumb,
|
||||||
|
#bwrap input[type=range]::-webkit-slider-thumb,
|
||||||
|
#wwrap input[type=range]::-webkit-slider-thumb,
|
||||||
|
#wbal input[type=range]::-webkit-slider-thumb {
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
border: 2px solid #000;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
#rwrap input[type=range]::-moz-range-thumb,
|
||||||
|
#gwrap input[type=range]::-moz-range-thumb,
|
||||||
|
#bwrap input[type=range]::-moz-range-thumb,
|
||||||
|
#wwrap input[type=range]::-moz-range-thumb,
|
||||||
|
#wbal input[type=range]::-moz-range-thumb {
|
||||||
|
border: 2px solid var(--c-1);
|
||||||
|
}
|
||||||
|
#wwrap, #wbal {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,6 +577,14 @@ input[type=range]:active + .sliderbubble {
|
|||||||
width: 240px;
|
width: 240px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
#rwrap .sliderwrap,
|
||||||
|
#gwrap .sliderwrap,
|
||||||
|
#bwrap .sliderwrap,
|
||||||
|
#wwrap .sliderwrap,
|
||||||
|
#wbal .sliderwrap {
|
||||||
|
width: 260px;
|
||||||
|
margin: 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sbs {
|
.sbs {
|
||||||
margin: 0px -20px 5px -6px;
|
margin: 0px -20px 5px -6px;
|
||||||
|
@ -47,22 +47,29 @@
|
|||||||
<div id="picker" class="noslide"></div>
|
<div id="picker" class="noslide"></div>
|
||||||
<div id="rgbwrap">
|
<div id="rgbwrap">
|
||||||
<div class="sliderwrap il">
|
<div class="sliderwrap il">
|
||||||
<input id="sliderR" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,1)" max="255" min="0" type="range" value="128" />
|
<input id="sliderR" class="noslide" onchange="fromRgb()" max="255" min="0" type="range" value="128" />
|
||||||
<div class="sliderdisplay"></div>
|
<div class="sliderdisplay"></div>
|
||||||
</div><br>
|
</div><br>
|
||||||
<div class="sliderwrap il">
|
<div class="sliderwrap il">
|
||||||
<input id="sliderG" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,2)" max="255" min="0" type="range" value="128" />
|
<input id="sliderG" class="noslide" onchange="fromRgb()" max="255" min="0" type="range" value="128" />
|
||||||
<div class="sliderdisplay"></div>
|
<div class="sliderdisplay"></div>
|
||||||
</div><br>
|
</div><br>
|
||||||
<div class="sliderwrap il">
|
<div class="sliderwrap il">
|
||||||
<input id="sliderB" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,3)" max="255" min="0" type="range" value="128" />
|
<input id="sliderB" class="noslide" onchange="fromRgb()" max="255" min="0" type="range" value="128" />
|
||||||
<div class="sliderdisplay"></div>
|
<div class="sliderdisplay"></div>
|
||||||
</div><br>
|
</div><br>
|
||||||
</div>
|
</div>
|
||||||
<div id="wwrap">
|
<div id="wwrap">
|
||||||
<p class="labels">White channel</p>
|
<p class="labels">White channel</p>
|
||||||
<div class="sliderwrap il">
|
<div class="sliderwrap il">
|
||||||
<input id="sliderW" class="noslide" onchange="setColor(0)" oninput="updateTrail(this)" max="255" min="0" type="range" value="128" />
|
<input id="sliderW" class="noslide" onchange="setColor(0)" max="255" min="0" type="range" value="128" />
|
||||||
|
<div class="sliderdisplay"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="wbal">
|
||||||
|
<p class="labels">White balance</p>
|
||||||
|
<div class="sliderwrap il">
|
||||||
|
<input id="sliderA" class="noslide" onchange="setBalance(this.value)" max="255" min="0" type="range" value="128" />
|
||||||
<div class="sliderdisplay"></div>
|
<div class="sliderdisplay"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -98,7 +105,7 @@
|
|||||||
<label class="check schkl">
|
<label class="check schkl">
|
||||||
|
|
||||||
<input type="radio" value="${palettes[i].id}" name="palette" onChange="setPalette()">
|
<input type="radio" value="${palettes[i].id}" name="palette" onChange="setPalette()">
|
||||||
<span class="checkmark schk"></span>
|
<span class="radiomark schk"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="lstIcontent">
|
<div class="lstIcontent">
|
||||||
<span class="lstIname">
|
<span class="lstIname">
|
||||||
|
@ -51,7 +51,7 @@ var cpick = new iro.ColorPicker("#picker", {
|
|||||||
options: {
|
options: {
|
||||||
sliderType: 'value'
|
sliderType: 'value'
|
||||||
}
|
}
|
||||||
},
|
}/*,
|
||||||
{
|
{
|
||||||
component: iro.ui.Slider,
|
component: iro.ui.Slider,
|
||||||
options: {
|
options: {
|
||||||
@ -59,7 +59,7 @@ var cpick = new iro.ColorPicker("#picker", {
|
|||||||
minTemperature: 2100,
|
minTemperature: 2100,
|
||||||
maxTemperature: 10000
|
maxTemperature: 10000
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -854,21 +854,14 @@ function loadNodes()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTrail(e, slidercol)
|
function updateTrail(e)
|
||||||
{
|
{
|
||||||
if (e==null) return;
|
if (e==null) return;
|
||||||
var max = e.hasAttribute('max') ? e.attributes.max.value : 255;
|
var max = e.hasAttribute('max') ? e.attributes.max.value : 255;
|
||||||
var perc = e.value * 100 / max;
|
var perc = e.value * 100 / max;
|
||||||
perc = parseInt(perc);
|
perc = parseInt(perc);
|
||||||
if (perc < 50) perc += 2;
|
if (perc < 50) perc += 2;
|
||||||
var scol;
|
var val = `linear-gradient(90deg, var(--c-f) ${perc}%, var(--c-4) ${perc}%)`;
|
||||||
switch (slidercol) {
|
|
||||||
case 1: scol = "#f00"; break;
|
|
||||||
case 2: scol = "#0f0"; break;
|
|
||||||
case 3: scol = "#00f"; break;
|
|
||||||
default: scol = "var(--c-f)";
|
|
||||||
}
|
|
||||||
var val = `linear-gradient(90deg, ${scol} ${perc}%, var(--c-4) ${perc}%)`;
|
|
||||||
e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val;
|
e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -939,8 +932,8 @@ function updateUI()
|
|||||||
updateTrail(d.getElementById('sliderBri'));
|
updateTrail(d.getElementById('sliderBri'));
|
||||||
updateTrail(d.getElementById('sliderSpeed'));
|
updateTrail(d.getElementById('sliderSpeed'));
|
||||||
updateTrail(d.getElementById('sliderIntensity'));
|
updateTrail(d.getElementById('sliderIntensity'));
|
||||||
updateTrail(d.getElementById('sliderW'));
|
d.getElementById('wwrap').style.display = (isRgbw) ? "block":"none";
|
||||||
if (isRgbw) d.getElementById('wwrap').style.display = "block";
|
d.getElementById("wbal").style.display = (lastinfo.leds.cct) ? "block":"none";
|
||||||
|
|
||||||
updatePA();
|
updatePA();
|
||||||
updateHex();
|
updateHex();
|
||||||
@ -1023,6 +1016,7 @@ function readState(s,command=false) {
|
|||||||
selectSlot(csel);
|
selectSlot(csel);
|
||||||
}
|
}
|
||||||
d.getElementById('sliderW').value = whites[csel];
|
d.getElementById('sliderW').value = whites[csel];
|
||||||
|
if (i.cct && i.cct>=0) d.getElementById("sliderA").value = i.cct;
|
||||||
|
|
||||||
d.getElementById('sliderSpeed').value = i.sx;
|
d.getElementById('sliderSpeed').value = i.sx;
|
||||||
d.getElementById('sliderIntensity').value = i.ix;
|
d.getElementById('sliderIntensity').value = i.ix;
|
||||||
@ -1560,6 +1554,11 @@ function setSegBri(s){
|
|||||||
requestJson(obj);
|
requestJson(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setBalance(b)
|
||||||
|
{
|
||||||
|
var obj = {"seg": {"cct": parseInt(b)}};
|
||||||
|
requestJson(obj);
|
||||||
|
}
|
||||||
function setX(ind = null) {
|
function setX(ind = null) {
|
||||||
if (ind === null) {
|
if (ind === null) {
|
||||||
ind = parseInt(d.querySelector('#fxlist input[name="fx"]:checked').value);
|
ind = parseInt(d.querySelector('#fxlist input[name="fx"]:checked').value);
|
||||||
@ -1723,7 +1722,6 @@ function selectSlot(b) {
|
|||||||
cd[csel].style.width="50px";
|
cd[csel].style.width="50px";
|
||||||
cpick.color.set(cd[csel].style.backgroundColor);
|
cpick.color.set(cd[csel].style.backgroundColor);
|
||||||
d.getElementById('sliderW').value = whites[csel];
|
d.getElementById('sliderW').value = whites[csel];
|
||||||
updateTrail(d.getElementById('sliderW'));
|
|
||||||
updateHex();
|
updateHex();
|
||||||
updateRgb();
|
updateRgb();
|
||||||
redrawPalPrev();
|
redrawPalPrev();
|
||||||
@ -1748,12 +1746,9 @@ function pC(col)
|
|||||||
function updateRgb()
|
function updateRgb()
|
||||||
{
|
{
|
||||||
var col = cpick.color.rgb;
|
var col = cpick.color.rgb;
|
||||||
var s = d.getElementById('sliderR');
|
d.getElementById('sliderR').value = col.r;
|
||||||
s.value = col.r; updateTrail(s,1);
|
d.getElementById('sliderG').value = col.g;
|
||||||
s = d.getElementById('sliderG');
|
d.getElementById('sliderB').value = col.b;
|
||||||
s.value = col.g; updateTrail(s,2);
|
|
||||||
s = d.getElementById('sliderB');
|
|
||||||
s.value = col.b; updateTrail(s,3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHex()
|
function updateHex()
|
||||||
|
@ -247,8 +247,8 @@
|
|||||||
gId('m0').innerHTML = memu;
|
gId('m0').innerHTML = memu;
|
||||||
bquot = memu / maxM * 100;
|
bquot = memu / maxM * 100;
|
||||||
gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`;
|
gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`;
|
||||||
gId('ledwarning').style.display = (sLC > maxPB || maxLC > 800 || bquot > 80) ? 'inline':'none';
|
gId('ledwarning').style.display = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none';
|
||||||
gId('ledwarning').style.color = (sLC > maxPB || maxLC > maxPB || bquot > 100) ? 'red':'orange';
|
gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || bquot > 100) ? 'red':'orange';
|
||||||
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>ERROR: Using over ${maxM}B!</b>)` : "") : "800 LEDs per output";
|
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>ERROR: Using over ${maxM}B!</b>)` : "") : "800 LEDs per output";
|
||||||
// calculate power
|
// calculate power
|
||||||
var val = Math.ceil((100 + sPC * laprev)/500)/2;
|
var val = Math.ceil((100 + sPC * laprev)/500)/2;
|
||||||
@ -389,11 +389,71 @@ ${i+1}:
|
|||||||
req.send(formData);
|
req.send(formData);
|
||||||
d.Sf.data.value = '';
|
d.Sf.data.value = '';
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
// https://stackoverflow.com/questions/7346563/loading-local-json-file
|
||||||
|
function loadCfg(o) {
|
||||||
|
var f, fr;
|
||||||
|
|
||||||
|
if (typeof window.FileReader !== 'function') {
|
||||||
|
alert("The file API isn't supported on this browser yet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!o.files) {
|
||||||
|
alert("This browser doesn't seem to support the `files` property of file inputs.");
|
||||||
|
} else if (!o.files[0]) {
|
||||||
|
alert("Please select a JSON file before clicking 'Apply'");
|
||||||
|
} else {
|
||||||
|
f = o.files[0];
|
||||||
|
fr = new FileReader();
|
||||||
|
fr.onload = receivedText;
|
||||||
|
fr.readAsText(f);
|
||||||
|
}
|
||||||
|
o.value = '';
|
||||||
|
|
||||||
|
function receivedText(e) {
|
||||||
|
let lines = e.target.result;
|
||||||
|
var c = JSON.parse(lines);
|
||||||
|
if (c.hw) {
|
||||||
|
if (c.hw.led) {
|
||||||
|
for (var i=0; i<10; i++) addLEDs(-1);
|
||||||
|
var 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("LS"+i)[0].value = v.start;
|
||||||
|
d.getElementsByName("LC"+i)[0].value = v.len;
|
||||||
|
d.getElementsByName("CO"+i)[0].value = v.order;
|
||||||
|
d.getElementsByName("SL"+i)[0].checked = v.skip;
|
||||||
|
d.getElementsByName("RF"+i)[0].checked = v.ref;
|
||||||
|
d.getElementsByName("CV"+i)[0].checked = v.rev;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (c.hw.btn) {
|
||||||
|
var b = c.hw.btn;
|
||||||
|
if (Array.isArray(b.ins)) gId("btns").innerHTML = "";
|
||||||
|
b.ins.forEach((v,i,a)=>{
|
||||||
|
addBtn(i,v.pin[0],v.type);
|
||||||
|
});
|
||||||
|
d.getElementsByName("TT")[0].value = b.tt;
|
||||||
|
}
|
||||||
|
if (c.hw.ir) {
|
||||||
|
d.getElementsByName("IR")[0].value = c.hw.ir.pin;
|
||||||
|
d.getElementsByName("IT")[0].value = c.hw.ir.type;
|
||||||
|
}
|
||||||
|
if (c.hw.relay) {
|
||||||
|
d.getElementsByName("RL")[0].value = c.hw.relay.pin;
|
||||||
|
d.getElementsByName("RM")[0].checked = c.hw.relay.inv;
|
||||||
|
}
|
||||||
|
UI();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function GetV()
|
function GetV()
|
||||||
{
|
{
|
||||||
//values injected by server while sending HTML
|
//values injected by server while sending HTML
|
||||||
//d.um_p=[6,7,8,9,10,11,1];bLimits(3,4096,4000,1664);d.Sf.MS.checked=1;addLEDs(1);d.Sf.L00.value=2;d.Sf.LC0.value=30;d.Sf.LT0.value=22;d.Sf.CO0.value=0;d.Sf.LS0.value=15;d.Sf.CV0.checked=1;d.Sf.SL0.checked=0;addLEDs(1);d.Sf.L01.value=10;d.Sf.L11.value=10;d.Sf.L21.value=1;d.Sf.L31.value=10;d.Sf.LC1.value=60;d.Sf.LT1.value=80;d.Sf.CO1.value=1;d.Sf.LS1.value=0;d.Sf.CV1.checked=0;d.Sf.SL1.checked=0;d.Sf.MA.value=850;d.Sf.LA.value=0;d.Sf.CA.value=56;d.Sf.AW.value=3;d.Sf.BO.checked=1;d.Sf.BP.value=80;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=0;d.Sf.BF.value=100;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=0;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=12;d.Sf.RM.checked=1;addBtn(0,0,0);addBtn(1,-1,0);d.Sf.TT.value=32;d.Sf.IR.value=-1;d.Sf.IT.value=0;
|
//d.um_p=[6,7,8,9,10,11,14,15,13,1,21,19,22,25,26,27,5,23,18,17];bLimits(10,2048,64000,8192);d.Sf.MS.checked=1;d.Sf.CCT.checked=0;addLEDs(1);d.Sf.L00.value=192;d.Sf.L10.value=168;d.Sf.L20.value=0;d.Sf.L30.value=61;d.Sf.LC0.value=421;d.Sf.LT0.value=80;d.Sf.CO0.value=1;d.Sf.LS0.value=0;d.Sf.CV0.checked=0;d.Sf.SL0.checked=0;d.Sf.RF0.checked=0;d.Sf.MA.value=850;d.Sf.LA.value=0;d.Sf.CA.value=127;d.Sf.AW.value=3;d.Sf.BO.checked=0;d.Sf.BP.value=0;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=1;d.Sf.BF.value=100;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=1;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=-1;d.Sf.RM.checked=1;addBtn(0,-1,0);addBtn(1,-1,0);addBtn(2,-1,0);addBtn(3,-1,0);d.Sf.TT.value=32;d.Sf.IR.value=-1;d.Sf.IT.value=8;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@ -448,6 +508,7 @@ ${i+1}:
|
|||||||
<hr style="width:260px">
|
<hr style="width:260px">
|
||||||
Make a segment for each output: <input type="checkbox" name="MS"> <br>
|
Make a segment for each output: <input type="checkbox" name="MS"> <br>
|
||||||
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"> <br>
|
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"> <br>
|
||||||
|
Allow WB correction: <input type="checkbox" name="CCT"> <br>
|
||||||
<hr style="width:260px">
|
<hr style="width:260px">
|
||||||
<div id="btns"></div>
|
<div id="btns"></div>
|
||||||
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
|
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
|
||||||
@ -506,7 +567,10 @@ ${i+1}:
|
|||||||
<option value=3>Dual</option>
|
<option value=3>Dual</option>
|
||||||
<option value=4>Legacy</option>
|
<option value=4>Legacy</option>
|
||||||
</select>
|
</select>
|
||||||
<br></span><hr>
|
<br></span>
|
||||||
|
<hr style="width:260px">
|
||||||
|
<div id="cfg">Config template: <input type="file" name="data2" accept=".json"> <input type="button" value="Apply" onclick="loadCfg(d.Sf.data2);"><br></div>
|
||||||
|
<hr>
|
||||||
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
@ -95,6 +95,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
uint8_t pins[5] = {255, 255, 255, 255, 255};
|
uint8_t pins[5] = {255, 255, 255, 255, 255};
|
||||||
|
|
||||||
autoSegments = request->hasArg(F("MS"));
|
autoSegments = request->hasArg(F("MS"));
|
||||||
|
allowCCT = request->hasArg(F("CCT"));
|
||||||
|
|
||||||
for (uint8_t s = 0; s < WLED_MAX_BUSSES; s++) {
|
for (uint8_t s = 0; s < WLED_MAX_BUSSES; s++) {
|
||||||
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
|
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
|
||||||
|
@ -270,6 +270,7 @@ WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load
|
|||||||
//if false, only one segment spanning the total LEDs is created,
|
//if false, only one segment spanning the total LEDs is created,
|
||||||
//but not on LED settings save if there is more than one segment currently
|
//but not on LED settings save if there is more than one segment currently
|
||||||
WLED_GLOBAL bool autoSegments _INIT(false);
|
WLED_GLOBAL bool autoSegments _INIT(false);
|
||||||
|
WLED_GLOBAL bool allowCCT _INIT(false); //CCT color correction
|
||||||
|
|
||||||
WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color.
|
WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color.
|
||||||
WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color
|
WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color
|
||||||
@ -508,7 +509,6 @@ WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 25
|
|||||||
WLED_GLOBAL bool blynkEnabled _INIT(false);
|
WLED_GLOBAL bool blynkEnabled _INIT(false);
|
||||||
|
|
||||||
//playlists
|
//playlists
|
||||||
WLED_GLOBAL unsigned long presetCycledTime _INIT(0);
|
|
||||||
WLED_GLOBAL int16_t currentPlaylist _INIT(-1);
|
WLED_GLOBAL int16_t currentPlaylist _INIT(-1);
|
||||||
//still used for "PL=~" HTTP API command
|
//still used for "PL=~" HTTP API command
|
||||||
WLED_GLOBAL byte presetCycCurr _INIT(0);
|
WLED_GLOBAL byte presetCycCurr _INIT(0);
|
||||||
@ -637,10 +637,9 @@ WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
|
|||||||
#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0)
|
#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0)
|
||||||
#define WLED_MQTT_CONNECTED (mqtt != nullptr && mqtt->connected())
|
#define WLED_MQTT_CONNECTED (mqtt != nullptr && mqtt->connected())
|
||||||
|
|
||||||
// append new c string to temp buffer efficiently
|
//macro to convert F to const
|
||||||
bool oappend(const char* txt);
|
#define SET_F(x) (const char*)F(x)
|
||||||
// append new number to temp buffer efficiently
|
|
||||||
bool oappendi(int i);
|
|
||||||
|
|
||||||
class WLED {
|
class WLED {
|
||||||
public:
|
public:
|
||||||
|
Loading…
Reference in New Issue
Block a user