WLED/wled00/json.cpp

1146 lines
36 KiB
C++
Raw Normal View History

2021-11-25 22:05:16 +01:00
#include "wled.h"
#include "palettes.h"
#define JSON_PATH_STATE 1
#define JSON_PATH_INFO 2
#define JSON_PATH_STATE_INFO 3
#define JSON_PATH_NODES 4
#define JSON_PATH_PALETTES 5
#define JSON_PATH_FXDATA 6
#define JSON_PATH_NETWORKS 7
#define JSON_PATH_EFFECTS 8
2021-11-25 22:05:16 +01:00
/*
* JSON API (De)serialization
*/
bool deserializeSegment(JsonObject elem, byte it, byte presetId)
2021-11-25 22:05:16 +01:00
{
byte id = elem["id"] | it;
if (id >= strip.getMaxSegments()) return false;
2021-11-25 22:05:16 +01:00
2023-10-01 13:26:31 +02:00
bool newSeg = false;
int stop = elem["stop"] | -1;
// append segment
2022-07-19 16:16:43 +02:00
if (id >= strip.getSegmentsNum()) {
if (stop <= 0) return false; // ignore empty/inactive segments
strip.appendSegment(Segment(0, strip.getLengthTotal()));
2022-07-19 16:16:43 +02:00
id = strip.getSegmentsNum()-1; // segments are added at the end of list
2023-10-01 13:26:31 +02:00
newSeg = true;
}
2023-08-07 16:50:18 +02:00
//DEBUG_PRINTLN("-- JSON deserialize segment.");
Segment& seg = strip.getSegment(id);
2023-08-07 16:50:18 +02:00
//DEBUG_PRINTF("-- Original segment: %p\n", &seg);
Segment prev = seg; //make a backup so we can tell if something changed
2023-08-07 16:50:18 +02:00
//DEBUG_PRINTF("-- Duplicate segment: %p\n", &prev);
2021-11-25 22:05:16 +01:00
uint16_t start = elem["start"] | seg.start;
if (stop < 0) {
int len = elem["len"];
2021-11-25 22:05:16 +01:00
stop = (len > 0) ? start + len : seg.stop;
}
// 2D segments
uint16_t startY = elem["startY"] | seg.startY;
uint16_t stopY = elem["stopY"] | seg.stopY;
2021-11-25 22:05:16 +01:00
2022-01-24 16:44:47 +01:00
//repeat, multiplies segment until all LEDs are used, or max segments reached
2021-11-25 22:17:40 +01:00
bool repeat = elem["rpt"] | false;
if (repeat && stop>0) {
elem.remove("id"); // remove for recursive call
elem.remove("rpt"); // remove for recursive call
elem.remove("n"); // remove for recursive call
uint16_t len = stop - start;
for (size_t i=id+1; i<strip.getMaxSegments(); i++) {
2021-11-25 22:17:40 +01:00
start = start + len;
if (start >= strip.getLengthTotal()) break;
//TODO: add support for 2D
2021-11-25 22:17:40 +01:00
elem["start"] = start;
elem["stop"] = start + len;
elem["rev"] = !elem["rev"]; // alternate reverse on even/odd segments
deserializeSegment(elem, i, presetId); // recursive call with new id
}
return true;
2021-11-25 22:17:40 +01:00
}
2021-11-25 22:05:16 +01:00
if (elem["n"]) {
// name field exists
if (seg.name) { //clear old name
delete[] seg.name;
seg.name = nullptr;
}
const char * name = elem["n"].as<const char*>();
size_t len = 0;
if (name != nullptr) len = strlen(name);
if (len > 0) {
if (len > WLED_MAX_SEGNAME_LEN) len = WLED_MAX_SEGNAME_LEN;
2021-11-25 22:05:16 +01:00
seg.name = new char[len+1];
if (seg.name) strlcpy(seg.name, name, WLED_MAX_SEGNAME_LEN+1);
2021-11-25 22:05:16 +01:00
} else {
// but is empty (already deleted above)
elem.remove("n");
}
} else if (start != seg.start || stop != seg.stop) {
// clearing or setting segment without name field
if (seg.name) {
delete[] seg.name;
seg.name = nullptr;
}
}
uint16_t grp = elem["grp"] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing;
uint16_t of = seg.offset;
uint8_t soundSim = elem["si"] | seg.soundSim;
uint8_t map1D2D = elem["m12"] | seg.map1D2D;
2021-11-25 22:05:16 +01:00
if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps
seg.map1D2D = constrain(map1D2D, 0, 7);
seg.soundSim = constrain(soundSim, 0, 1);
2023-05-15 17:06:29 +02:00
uint8_t set = elem[F("set")] | seg.set;
seg.set = constrain(set, 0, 3);
2021-11-25 22:05:16 +01:00
uint16_t len = 1;
if (stop > start) len = stop - start;
int offset = elem[F("of")] | INT32_MAX;
if (offset != INT32_MAX) {
int offsetAbs = abs(offset);
if (offsetAbs > len - 1) offsetAbs %= len;
if (offset < 0) offsetAbs = len - offsetAbs;
of = offsetAbs;
2021-11-25 22:05:16 +01:00
}
if (stop > start && of > len -1) of = len -1;
// update segment (delete if necessary)
// do not call seg.setUp() here, as it may cause a crash due to concurrent access if the segment is currently drawing effects
// WS2812FX handles queueing of the change
strip.setSegment(id, start, stop, grp, spc, of, startY, stopY);
2023-10-01 13:26:31 +02:00
if (newSeg) seg.refreshLightCapabilities(); // fix for #3403
2021-11-25 22:05:16 +01:00
2023-10-01 13:26:31 +02:00
if (seg.reset && seg.stop == 0) {
if (id == strip.getMainSegmentId()) strip.setMainSegmentId(0); // fix for #3403
return true; // segment was deleted & is marked for reset, no need to change anything else
}
byte segbri = seg.opacity;
2021-11-25 22:05:16 +01:00
if (getVal(elem["bri"], &segbri)) {
if (segbri > 0) seg.setOpacity(segbri);
seg.setOption(SEG_OPTION_ON, segbri); // use transition
2021-11-25 22:05:16 +01:00
}
2022-08-19 21:37:26 +02:00
bool on = elem["on"] | seg.on;
2021-11-25 22:05:16 +01:00
if (elem["on"].is<const char*>() && elem["on"].as<const char*>()[0] == 't') on = !on;
seg.setOption(SEG_OPTION_ON, on); // use transition
2022-08-19 21:37:26 +02:00
bool frz = elem["frz"] | seg.freeze;
if (elem["frz"].is<const char*>() && elem["frz"].as<const char*>()[0] == 't') frz = !seg.freeze;
seg.freeze = frz;
2021-11-25 22:05:16 +01:00
seg.setCCT(elem["cct"] | seg.cct);
2021-11-25 22:05:16 +01:00
JsonArray colarr = elem["col"];
if (!colarr.isNull())
{
if (seg.getLightCapabilities() & 3) {
// segment has RGB or White
for (size_t i = 0; i < 3; i++)
{
int rgbw[] = {0,0,0,0};
bool colValid = false;
JsonArray colX = colarr[i];
if (colX.isNull()) {
byte brgbw[] = {0,0,0,0};
const char* hexCol = colarr[i];
if (hexCol == nullptr) { //Kelvin color temperature (or invalid), e.g 2400
int kelvin = colarr[i] | -1;
if (kelvin < 0) continue;
if (kelvin == 0) seg.setColor(i, 0);
if (kelvin > 0) colorKtoRGB(kelvin, brgbw);
colValid = true;
} else { //HEX string, e.g. "FFAA00"
colValid = colorFromHexString(brgbw, hexCol);
}
for (size_t c = 0; c < 4; c++) rgbw[c] = brgbw[c];
} else { //Array of ints (RGB or RGBW color), e.g. [255,160,0]
byte sz = colX.size();
if (sz == 0) continue; //do nothing on empty array
copyArray(colX, rgbw, 4);
2021-11-25 22:05:16 +01:00
colValid = true;
}
if (!colValid) continue;
seg.setColor(i, RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3]));
if (seg.mode == FX_MODE_STATIC) strip.trigger(); //instant refresh
}
} else {
// non RGB & non White segment (usually On/Off bus)
seg.setColor(0, ULTRAWHITE);
seg.setColor(1, BLACK);
2021-11-25 22:05:16 +01:00
}
}
// lx parser
#ifdef WLED_ENABLE_LOXONE
int lx = elem[F("lx")] | -1;
if (lx > 0) {
parseLxJson(lx, id, false);
}
int ly = elem[F("ly")] | -1;
if (ly > 0) {
parseLxJson(ly, id, true);
}
#endif
#ifndef WLED_DISABLE_2D
bool reverse = seg.reverse;
bool mirror = seg.mirror;
#endif
seg.selected = elem["sel"] | seg.selected;
seg.reverse = elem["rev"] | seg.reverse;
seg.mirror = elem["mi"] | seg.mirror;
#ifndef WLED_DISABLE_2D
bool reverse_y = seg.reverse_y;
bool mirror_y = seg.mirror_y;
seg.reverse_y = elem["rY"] | seg.reverse_y;
seg.mirror_y = elem["mY"] | seg.mirror_y;
seg.transpose = elem[F("tp")] | seg.transpose;
if (seg.is2D() && seg.map1D2D == M12_pArc && (reverse != seg.reverse || reverse_y != seg.reverse_y || mirror != seg.mirror || mirror_y != seg.mirror_y)) seg.fill(BLACK); // clear entire segment (in case of Arc 1D to 2D expansion)
#endif
2021-11-25 22:05:16 +01:00
byte fx = seg.mode;
if (getVal(elem["fx"], &fx, 0, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 0-255 exact value)
2022-01-27 21:01:03 +01:00
if (!presetId && currentPlaylist>=0) unloadPlaylist();
if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]);
2022-07-23 22:00:19 +02:00
}
//getVal also supports inc/decrementing and random
getVal(elem["sx"], &seg.speed);
getVal(elem["ix"], &seg.intensity);
2022-07-29 16:26:15 +02:00
uint8_t pal = seg.palette;
if (seg.getLightCapabilities() & 1) { // ignore palette for White and On/Off segments
if (getVal(elem["pal"], &pal)) seg.setPalette(pal);
}
getVal(elem["c1"], &seg.custom1);
getVal(elem["c2"], &seg.custom2);
2022-08-12 17:58:20 +02:00
uint8_t cust3 = seg.custom3;
getVal(elem["c3"], &cust3); // we can't pass reference to bifield
seg.custom3 = constrain(cust3, 0, 31);
2022-08-12 17:58:20 +02:00
seg.check1 = elem["o1"] | seg.check1;
seg.check2 = elem["o2"] | seg.check2;
seg.check3 = elem["o3"] | seg.check3;
2023-01-06 09:24:29 +01:00
2021-11-25 22:05:16 +01:00
JsonArray iarr = elem[F("i")]; //set individual LEDs
if (!iarr.isNull()) {
uint8_t oldMap1D2D = seg.map1D2D;
seg.map1D2D = M12_Pixels; // no mapping
// set brightness immediately and disable transition
transitionDelayTemp = 0;
jsonTransitionOnce = true;
strip.setBrightness(scaledBri(bri), true);
// freeze and init to black
2022-08-19 21:37:26 +02:00
if (!seg.freeze) {
seg.freeze = true;
seg.fill(BLACK);
2021-11-25 22:05:16 +01:00
}
uint16_t start = 0, stop = 0;
byte set = 0; //0 nothing set, 1 start set, 2 range set
for (size_t i = 0; i < iarr.size(); i++) {
2021-11-25 22:05:16 +01:00
if(iarr[i].is<JsonInteger>()) {
if (!set) {
2023-01-14 16:01:46 +01:00
start = abs(iarr[i].as<int>());
set++;
2021-11-25 22:05:16 +01:00
} else {
2023-01-14 16:01:46 +01:00
stop = abs(iarr[i].as<int>());
set++;
2021-11-25 22:05:16 +01:00
}
} else { //color
uint8_t rgbw[] = {0,0,0,0};
2021-11-25 22:05:16 +01:00
JsonArray icol = iarr[i];
if (!icol.isNull()) { //array, e.g. [255,0,0]
byte sz = icol.size();
if (sz > 0 && sz < 5) copyArray(icol, rgbw);
2021-11-25 22:05:16 +01:00
} else { //hex string, e.g. "FF0000"
byte brgbw[] = {0,0,0,0};
const char* hexCol = iarr[i];
if (colorFromHexString(brgbw, hexCol)) {
for (size_t c = 0; c < 4; c++) rgbw[c] = brgbw[c];
2021-11-25 22:05:16 +01:00
}
}
2023-01-14 16:01:46 +01:00
if (set < 2 || stop <= start) stop = start + 1;
uint32_t c = gamma32(RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
2023-01-14 16:01:46 +01:00
while (start < stop) seg.setPixelColor(start++, c);
2021-11-25 22:05:16 +01:00
set = 0;
}
}
seg.map1D2D = oldMap1D2D; // restore mapping
2023-01-14 16:01:46 +01:00
strip.trigger(); // force segment update
2021-11-25 22:05:16 +01:00
}
// send UDP/WS if segment options changed (except selection; will also deselect current preset)
if (seg.differs(prev) & 0x7F) stateChanged = true;
return true;
2021-11-25 22:05:16 +01:00
}
// deserializes WLED state (fileDoc points to doc object if called from web server)
// presetId is non-0 if called from handlePreset()
2021-11-25 22:05:16 +01:00
bool deserializeState(JsonObject root, byte callMode, byte presetId)
{
bool stateResponse = root[F("v")] | false;
#if defined(WLED_DEBUG) && defined(WLED_DEBUG_HOST)
netDebugEnabled = root[F("debug")] | netDebugEnabled;
#endif
bool onBefore = bri;
getVal(root["bri"], &bri);
2021-11-25 22:05:16 +01:00
bool on = root["on"] | (bri > 0);
if (!on != !bri) toggleOnOff();
if (root["on"].is<const char*>() && root["on"].as<const char*>()[0] == 't') {
if (onBefore || !bri) toggleOnOff(); // do not toggle off again if just turned on by bri (makes e.g. "{"on":"t","bri":32}" work)
2022-08-08 13:36:13 +02:00
}
2021-11-25 22:05:16 +01:00
if (bri && !onBefore) { // unfreeze all segments when turning on
for (size_t s=0; s < strip.getSegmentsNum(); s++) {
2022-08-19 21:37:26 +02:00
strip.getSegment(s).freeze = false;
}
if (realtimeMode && !realtimeOverride && useMainSegmentOnly) { // keep live segment frozen if live
2022-08-19 21:37:26 +02:00
strip.getMainSegment().freeze = true;
}
}
2021-11-25 22:05:16 +01:00
int tr = -1;
if (!presetId || currentPlaylist < 0) { //do not apply transition time from preset if playlist active, as it would override playlist transition times
tr = root[F("transition")] | -1;
if (tr >= 0)
{
transitionDelay = tr;
transitionDelay *= 100;
transitionDelayTemp = transitionDelay;
}
}
// temporary transition (applies only once)
2021-11-25 22:05:16 +01:00
tr = root[F("tt")] | -1;
if (tr >= 0)
{
transitionDelayTemp = tr;
transitionDelayTemp *= 100;
jsonTransitionOnce = true;
}
strip.setTransition(transitionDelayTemp); // required here for color transitions to have correct duration
2021-11-25 22:05:16 +01:00
tr = root[F("tb")] | -1;
if (tr >= 0) strip.timebase = ((uint32_t)tr) - millis();
JsonObject nl = root["nl"];
nightlightActive = nl["on"] | nightlightActive;
nightlightDelayMins = nl["dur"] | nightlightDelayMins;
nightlightMode = nl["mode"] | nightlightMode;
2021-11-25 22:05:16 +01:00
nightlightTargetBri = nl[F("tbri")] | nightlightTargetBri;
JsonObject udpn = root["udpn"];
notifyDirect = udpn["send"] | notifyDirect;
syncGroups = udpn["sgrp"] | syncGroups;
2021-11-25 22:05:16 +01:00
receiveNotifications = udpn["recv"] | receiveNotifications;
receiveGroups = udpn["rgrp"] | receiveGroups;
2021-11-25 22:05:16 +01:00
if ((bool)udpn[F("nn")]) callMode = CALL_MODE_NO_NOTIFY; //send no notification just for this request
2022-10-09 12:09:46 +02:00
unsigned long timein = root["time"] | UINT32_MAX; //backup time source if NTP not synced
2021-11-25 22:05:16 +01:00
if (timein != UINT32_MAX) {
setTimeFromAPI(timein);
if (presetsModifiedTime == 0) presetsModifiedTime = timein;
}
if (root[F("psave")].isNull()) doReboot = root[F("rb")] | doReboot;
2021-11-25 22:05:16 +01:00
// do not allow changing main segment while in realtime mode (may get odd results else)
if (!realtimeMode) strip.setMainSegmentId(root[F("mainseg")] | strip.getMainSegmentId()); // must be before realtimeLock() if "live"
2021-11-25 22:05:16 +01:00
realtimeOverride = root[F("lor")] | realtimeOverride;
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) {
2022-08-19 21:37:26 +02:00
strip.getMainSegment().freeze = !realtimeOverride;
}
2021-11-25 22:05:16 +01:00
if (root.containsKey("live")) {
if (root["live"].as<bool>()) {
transitionDelayTemp = 0;
jsonTransitionOnce = true;
realtimeLock(65000);
} else {
exitRealtime();
}
2021-11-25 22:05:16 +01:00
}
int it = 0;
JsonVariant segVar = root["seg"];
if (segVar.is<JsonObject>())
{
int id = segVar["id"] | -1;
//if "seg" is not an array and ID not specified, apply to all selected/checked segments
if (id < 0) {
//apply all selected segments
//bool didSet = false;
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
Segment &sg = strip.getSegment(s);
if (sg.isSelected()) {
deserializeSegment(segVar, s, presetId);
//didSet = true;
2021-11-25 22:05:16 +01:00
}
}
//TODO: not sure if it is good idea to change first active but unselected segment
//if (!didSet) deserializeSegment(segVar, strip.getMainSegmentId(), presetId);
} else {
deserializeSegment(segVar, id, presetId); //apply only the segment with the specified ID
2021-11-25 22:05:16 +01:00
}
} else {
size_t deleted = 0;
2021-11-25 22:05:16 +01:00
JsonArray segs = segVar.as<JsonArray>();
2022-07-19 16:16:43 +02:00
for (JsonObject elem : segs) {
if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++;
2021-11-25 22:05:16 +01:00
}
if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments
2021-11-25 22:05:16 +01:00
}
2021-11-25 22:05:16 +01:00
usermods.readFromJsonState(root);
loadLedmap = root[F("ledmap")] | loadLedmap;
2021-11-25 22:05:16 +01:00
byte ps = root[F("psave")];
if (ps > 0 && ps < 251) savePreset(ps, nullptr, root);
2021-11-25 22:05:16 +01:00
ps = root[F("pdel")]; //deletion
if (ps > 0 && ps < 251) deletePreset(ps);
// HTTP API commands (must be handled before "ps")
const char* httpwin = root["win"];
if (httpwin) {
String apireq = "win"; apireq += '&'; // reduce flash string usage
apireq += httpwin;
handleSet(nullptr, apireq, false); // may set stateChanged
}
// applying preset (2 cases: a) API call includes all preset values ("pd"), b) API only specifies preset ID ("ps"))
byte presetToRestore = 0;
// a) already applied preset content (requires "seg" or "win" but will ignore the rest)
if (!root["pd"].isNull() && stateChanged) {
currentPreset = root[F("pd")] | currentPreset;
if (root["win"].isNull()) presetCycCurr = currentPreset; // otherwise it was set in handleSet() [set.cpp]
presetToRestore = currentPreset; // stateUpdated() will clear the preset, so we need to restore it after
//unloadPlaylist(); // applying a preset unloads the playlist, may be needed here too?
} else if (!root["ps"].isNull()) {
ps = presetCycCurr;
if (root["win"].isNull() && getVal(root["ps"], &ps, 0, 0) && ps > 0 && ps < 251 && ps != currentPreset) {
// b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal())
presetCycCurr = ps;
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(ps, callMode); // async load from file system (only preset ID was specified)
return stateResponse;
2021-11-25 22:05:16 +01:00
}
}
JsonObject playlist = root[F("playlist")];
if (!playlist.isNull() && loadPlaylist(playlist, presetId)) {
//do not notify here, because the first playlist entry will do
if (root["on"].isNull()) callMode = CALL_MODE_NO_NOTIFY;
else callMode = CALL_MODE_DIRECT_CHANGE; // possible bugfix for playlist only containing HTTP API preset FX=~
}
2023-04-14 17:15:02 +02:00
if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as<bool>()) {
if (strip.customPalettes.size()) {
char fileName[32];
sprintf_P(fileName, PSTR("/palette%d.json"), strip.customPalettes.size()-1);
if (WLED_FS.exists(fileName)) WLED_FS.remove(fileName);
strip.loadCustomPalettes();
}
}
stateUpdated(callMode);
if (presetToRestore) currentPreset = presetToRestore;
2021-11-25 22:05:16 +01:00
return stateResponse;
}
void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset, bool segmentBounds)
2021-11-25 22:05:16 +01:00
{
2022-01-25 12:47:14 +01:00
root["id"] = id;
2021-11-25 22:05:16 +01:00
if (segmentBounds) {
root["start"] = seg.start;
root["stop"] = seg.stop;
#ifndef WLED_DISABLE_2D
if (strip.isMatrix) {
root[F("startY")] = seg.startY;
root[F("stopY")] = seg.stopY;
}
#endif
2021-11-25 22:05:16 +01:00
}
if (!forPreset) root["len"] = seg.stop - seg.start;
2022-08-19 21:37:26 +02:00
root["grp"] = seg.grouping;
2021-11-25 22:05:16 +01:00
root[F("spc")] = seg.spacing;
2022-08-19 21:37:26 +02:00
root[F("of")] = seg.offset;
root["on"] = seg.on;
root["frz"] = seg.freeze;
byte segbri = seg.opacity;
root["bri"] = (segbri) ? segbri : 255;
root["cct"] = seg.cct;
2023-05-15 17:06:29 +02:00
root[F("set")] = seg.set;
2021-11-25 22:05:16 +01:00
if (segmentBounds && seg.name != nullptr) root["n"] = reinterpret_cast<const char *>(seg.name); //not good practice, but decreases required JSON buffer
2021-11-25 22:05:16 +01:00
// to conserve RAM we will serialize the col array manually
// this will reduce RAM footprint from ~300 bytes to 84 bytes per segment
char colstr[70]; colstr[0] = '['; colstr[1] = '\0'; //max len 68 (5 chan, all 255)
2022-02-04 13:28:00 +01:00
const char *format = strip.hasWhiteChannel() ? PSTR("[%u,%u,%u,%u]") : PSTR("[%u,%u,%u]");
for (size_t i = 0; i < 3; i++)
2021-11-25 22:05:16 +01:00
{
byte segcol[4]; byte* c = segcol;
segcol[0] = R(seg.colors[i]);
segcol[1] = G(seg.colors[i]);
segcol[2] = B(seg.colors[i]);
segcol[3] = W(seg.colors[i]);
2021-11-25 22:05:16 +01:00
char tmpcol[22];
sprintf_P(tmpcol, format, (unsigned)c[0], (unsigned)c[1], (unsigned)c[2], (unsigned)c[3]);
2022-02-01 18:21:30 +01:00
strcat(colstr, i<2 ? strcat(tmpcol, ",") : tmpcol);
2021-11-25 22:05:16 +01:00
}
2022-02-01 18:21:30 +01:00
strcat(colstr, "]");
2021-11-25 22:05:16 +01:00
root["col"] = serialized(colstr);
root["fx"] = seg.mode;
root["sx"] = seg.speed;
root["ix"] = seg.intensity;
root["pal"] = seg.palette;
root["c1"] = seg.custom1;
root["c2"] = seg.custom2;
root["c3"] = seg.custom3;
root["sel"] = seg.isSelected();
root["rev"] = seg.reverse;
root["mi"] = seg.mirror;
2023-01-06 09:24:29 +01:00
#ifndef WLED_DISABLE_2D
if (strip.isMatrix) {
root["rY"] = seg.reverse_y;
root["mY"] = seg.mirror_y;
2022-08-19 21:37:26 +02:00
root[F("tp")] = seg.transpose;
}
2023-01-06 09:24:29 +01:00
#endif
root["o1"] = seg.check1;
root["o2"] = seg.check2;
root["o3"] = seg.check3;
root["si"] = seg.soundSim;
root["m12"] = seg.map1D2D;
2021-11-25 22:05:16 +01:00
}
2022-10-08 18:25:51 +02:00
void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segmentBounds, bool selectedSegmentsOnly)
2021-11-25 22:05:16 +01:00
{
if (includeBri) {
root["on"] = (bri > 0);
root["bri"] = briLast;
root[F("transition")] = transitionDelay/100; //in 100ms
}
if (!forPreset) {
if (errorFlag) {root[F("error")] = errorFlag; errorFlag = ERR_NONE;} //prevent error message to persist on screen
2021-11-25 22:05:16 +01:00
2021-11-23 13:17:33 +01:00
root["ps"] = (currentPreset > 0) ? currentPreset : -1;
2021-11-25 22:05:16 +01:00
root[F("pl")] = currentPlaylist;
usermods.addToJsonState(root);
JsonObject nl = root.createNestedObject("nl");
nl["on"] = nightlightActive;
nl["dur"] = nightlightDelayMins;
nl["mode"] = nightlightMode;
2021-11-25 22:05:16 +01:00
nl[F("tbri")] = nightlightTargetBri;
if (nightlightActive) {
nl[F("rem")] = (nightlightDelayMs - (millis() - nightlightStartTime)) / 1000; // seconds remaining
} else {
nl[F("rem")] = -1;
}
JsonObject udpn = root.createNestedObject("udpn");
udpn["send"] = notifyDirect;
udpn["recv"] = receiveNotifications;
udpn["sgrp"] = syncGroups;
udpn["rgrp"] = receiveGroups;
2021-11-25 22:05:16 +01:00
2022-03-28 22:36:58 +02:00
root[F("lor")] = realtimeOverride;
2021-11-25 22:05:16 +01:00
}
root[F("mainseg")] = strip.getMainSegmentId();
JsonArray seg = root.createNestedArray("seg");
for (size_t s = 0; s < strip.getMaxSegments(); s++) {
2022-07-19 16:16:43 +02:00
if (s >= strip.getSegmentsNum()) {
2022-10-08 18:25:51 +02:00
if (forPreset && segmentBounds && !selectedSegmentsOnly) { //disable segments not part of preset
JsonObject seg0 = seg.createNestedObject();
seg0["stop"] = 0;
continue;
} else
break;
}
Segment &sg = strip.getSegment(s);
2022-10-08 18:25:51 +02:00
if (forPreset && selectedSegmentsOnly && !sg.isSelected()) continue;
if (sg.isActive()) {
2021-11-25 22:05:16 +01:00
JsonObject seg0 = seg.createNestedObject();
serializeSegment(seg0, sg, s, forPreset, segmentBounds);
} else if (forPreset && segmentBounds) { //disable segments not part of preset
JsonObject seg0 = seg.createNestedObject();
seg0["stop"] = 0;
}
}
}
void serializeInfo(JsonObject root)
{
root[F("ver")] = versionString;
root[F("vid")] = VERSION;
//root[F("cn")] = WLED_CODENAME;
JsonObject leds = root.createNestedObject("leds");
leds[F("count")] = strip.getLengthTotal();
leds[F("pwr")] = strip.currentMilliamps;
leds["fps"] = strip.getFps();
2021-11-25 22:05:16 +01:00
leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0;
leds[F("maxseg")] = strip.getMaxSegments();
//leds[F("actseg")] = strip.getActiveSegmentsNum();
//leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config
#ifndef WLED_DISABLE_2D
if (strip.isMatrix) {
JsonObject matrix = leds.createNestedObject("matrix");
matrix["w"] = Segment::maxWidth;
matrix["h"] = Segment::maxHeight;
}
#endif
uint8_t totalLC = 0;
JsonArray lcarr = leds.createNestedArray(F("seglc"));
size_t nSegs = strip.getSegmentsNum();
for (size_t s = 0; s < nSegs; s++) {
if (!strip.getSegment(s).isActive()) continue;
uint8_t lc = strip.getSegment(s).getLightCapabilities();
totalLC |= lc;
lcarr.add(lc);
}
leds["lc"] = totalLC;
2021-11-25 22:05:16 +01:00
leds[F("rgbw")] = strip.hasRGBWBus(); // deprecated, use info.leds.lc
leds[F("wv")] = totalLC & 0x02; // deprecated, true if white slider should be displayed for any segment
leds["cct"] = totalLC & 0x04; // deprecated, use info.leds.lc
2022-08-14 13:05:59 +02:00
#ifdef WLED_DEBUG
JsonArray i2c = root.createNestedArray(F("i2c"));
i2c.add(i2c_sda);
i2c.add(i2c_scl);
JsonArray spi = root.createNestedArray(F("spi"));
spi.add(spi_mosi);
spi.add(spi_sclk);
spi.add(spi_miso);
2022-08-14 13:05:59 +02:00
#endif
2021-11-25 22:05:16 +01:00
root[F("str")] = syncToggleReceive;
root[F("name")] = serverDescription;
root[F("udpport")] = udpPort;
root["live"] = (bool)realtimeMode;
2022-03-28 22:36:58 +02:00
root[F("liveseg")] = useMainSegmentOnly ? strip.getMainSegmentId() : -1; // if using main segment only for live
2021-11-25 22:05:16 +01:00
switch (realtimeMode) {
case REALTIME_MODE_INACTIVE: root["lm"] = ""; break;
case REALTIME_MODE_GENERIC: root["lm"] = ""; break;
case REALTIME_MODE_UDP: root["lm"] = F("UDP"); break;
case REALTIME_MODE_HYPERION: root["lm"] = F("Hyperion"); break;
case REALTIME_MODE_E131: root["lm"] = F("E1.31"); break;
case REALTIME_MODE_ADALIGHT: root["lm"] = F("USB Adalight/TPM2"); break;
case REALTIME_MODE_ARTNET: root["lm"] = F("Art-Net"); break;
case REALTIME_MODE_TPM2NET: root["lm"] = F("tpm2.net"); break;
case REALTIME_MODE_DDP: root["lm"] = F("DDP"); break;
}
if (realtimeIP[0] == 0)
{
root[F("lip")] = "";
} else {
root[F("lip")] = realtimeIP.toString();
}
#ifdef WLED_ENABLE_WEBSOCKETS
root[F("ws")] = ws.count();
#else
root[F("ws")] = -1;
#endif
root[F("fxcount")] = strip.getModeCount();
root[F("palcount")] = strip.getPaletteCount();
2022-11-25 17:33:29 +01:00
root[F("cpalcount")] = strip.customPalettes.size(); //number of custom palettes
2021-11-25 22:05:16 +01:00
2022-02-10 23:14:48 +01:00
JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {
if ((ledMaps>>i) & 0x00000001U) {
JsonObject ledmaps0 = ledmaps.createNestedObject();
ledmaps0["id"] = i;
#ifndef ESP8266
2023-02-14 20:25:26 +01:00
if (i && ledmapNames[i-1]) ledmaps0["n"] = ledmapNames[i-1];
#endif
}
2022-02-10 23:14:48 +01:00
}
2021-11-25 22:05:16 +01:00
JsonObject wifi_info = root.createNestedObject("wifi");
wifi_info[F("bssid")] = WiFi.BSSIDstr();
int qrssi = WiFi.RSSI();
wifi_info[F("rssi")] = qrssi;
wifi_info[F("signal")] = getSignalQuality(qrssi);
wifi_info[F("channel")] = WiFi.channel();
JsonObject fs_info = root.createNestedObject("fs");
fs_info["u"] = fsBytesUsed / 1000;
fs_info["t"] = fsBytesTotal / 1000;
fs_info[F("pmt")] = presetsModifiedTime;
root[F("ndc")] = nodeListEnabled ? (int)Nodes.size() : -1;
2023-01-06 09:24:29 +01:00
2021-11-25 22:05:16 +01:00
#ifdef ARDUINO_ARCH_ESP32
#ifdef WLED_DEBUG
wifi_info[F("txPower")] = (int) WiFi.getTxPower();
wifi_info[F("sleep")] = (bool) WiFi.getSleep();
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
root[F("arch")] = "esp32";
#else
root[F("arch")] = ESP.getChipModel();
#endif
2021-11-25 22:05:16 +01:00
root[F("core")] = ESP.getSdkVersion();
//root[F("maxalloc")] = ESP.getMaxAllocHeap();
#ifdef WLED_DEBUG
root[F("resetReason0")] = (int)rtc_get_reset_reason(0);
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
#endif
root[F("lwip")] = 0; //deprecated
#else
root[F("arch")] = "esp8266";
root[F("core")] = ESP.getCoreVersion();
//root[F("maxalloc")] = ESP.getMaxFreeBlockSize();
#ifdef WLED_DEBUG
root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason;
#endif
root[F("lwip")] = LWIP_VERSION_MAJOR;
#endif
root[F("freeheap")] = ESP.getFreeHeap();
#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
2021-11-25 22:05:16 +01:00
if (psramFound()) root[F("psram")] = ESP.getFreePsram();
#endif
root[F("uptime")] = millis()/1000 + rolloverMillis*4294967;
2023-08-29 23:44:08 +02:00
char time[32];
getTimeString(time);
root[F("time")] = time;
2021-11-25 22:05:16 +01:00
usermods.addToJsonInfo(root);
uint16_t os = 0;
2021-11-25 22:05:16 +01:00
#ifdef WLED_DEBUG
os = 0x80;
#ifdef WLED_DEBUG_HOST
os |= 0x0100;
if (!netDebugEnabled) os &= ~0x0080;
#endif
2021-11-25 22:05:16 +01:00
#endif
#ifndef WLED_DISABLE_ALEXA
os += 0x40;
#endif
//os += 0x20; // indicated now removed Blynk support, may be reused to indicate another build-time option
#ifdef USERMOD_CRONIXIE
2021-11-25 22:05:16 +01:00
os += 0x10;
#endif
#ifndef WLED_DISABLE_FILESYSTEM
os += 0x08;
#endif
#ifndef WLED_DISABLE_HUESYNC
os += 0x04;
#endif
#ifdef WLED_ENABLE_ADALIGHT
os += 0x02;
#endif
#ifndef WLED_DISABLE_OTA
os += 0x01;
#endif
root[F("opt")] = os;
root[F("brand")] = "WLED";
root[F("product")] = F("FOSS");
root["mac"] = escapedMac;
char s[16] = "";
if (Network.isConnected())
{
IPAddress localIP = Network.localIP();
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
}
root["ip"] = s;
}
void setPaletteColors(JsonArray json, CRGBPalette16 palette)
{
for (int i = 0; i < 16; i++) {
JsonArray colors = json.createNestedArray();
CRGB color = palette[i];
2022-01-02 14:27:24 +01:00
colors.add(i<<4);
2021-11-25 22:05:16 +01:00
colors.add(color.red);
colors.add(color.green);
colors.add(color.blue);
}
}
void setPaletteColors(JsonArray json, byte* tcp)
{
TRGBGradientPaletteEntryUnion* ent = (TRGBGradientPaletteEntryUnion*)(tcp);
TRGBGradientPaletteEntryUnion u;
// Count entries
uint16_t count = 0;
do {
u = *(ent + count);
count++;
} while ( u.index != 255);
u = *ent;
int indexstart = 0;
while( indexstart < 255) {
indexstart = u.index;
JsonArray colors = json.createNestedArray();
colors.add(u.index);
colors.add(u.r);
colors.add(u.g);
colors.add(u.b);
ent++;
u = *ent;
}
}
void serializePalettes(JsonObject root, int page)
2021-11-25 22:05:16 +01:00
{
2022-07-28 23:19:58 +02:00
byte tcp[72];
2021-11-25 22:05:16 +01:00
#ifdef ESP8266
int itemPerPage = 5;
#else
int itemPerPage = 8;
#endif
int palettesCount = strip.getPaletteCount();
2022-07-28 23:19:58 +02:00
int customPalettes = strip.customPalettes.size();
2021-11-25 22:05:16 +01:00
2022-07-28 23:19:58 +02:00
int maxPage = (palettesCount + customPalettes -1) / itemPerPage;
2021-11-25 22:05:16 +01:00
if (page > maxPage) page = maxPage;
int start = itemPerPage * page;
int end = start + itemPerPage;
2022-07-28 23:19:58 +02:00
if (end > palettesCount + customPalettes) end = palettesCount + customPalettes;
2021-11-25 22:05:16 +01:00
2022-07-28 23:19:58 +02:00
root[F("m")] = maxPage; // inform caller how many pages there are
2021-11-25 22:05:16 +01:00
JsonObject palettes = root.createNestedObject("p");
for (int i = start; i < end; i++) {
2022-07-28 23:19:58 +02:00
JsonArray curPalette = palettes.createNestedArray(String(i>=palettesCount ? 255 - i + palettesCount : i));
2021-11-25 22:05:16 +01:00
switch (i) {
case 0: //default palette
2023-01-06 09:24:29 +01:00
setPaletteColors(curPalette, PartyColors_p);
2021-11-25 22:05:16 +01:00
break;
case 1: //random
curPalette.add("r");
curPalette.add("r");
curPalette.add("r");
curPalette.add("r");
break;
case 2: //primary color only
curPalette.add("c1");
break;
case 3: //primary + secondary
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c2");
curPalette.add("c2");
break;
case 4: //primary + secondary + tertiary
curPalette.add("c3");
curPalette.add("c2");
curPalette.add("c1");
break;
2022-02-28 23:32:24 +01:00
case 5: //primary + secondary (+tert if not off), more distinct
2021-11-25 22:05:16 +01:00
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c1");
2022-02-28 23:32:24 +01:00
break;
2021-11-25 22:05:16 +01:00
case 6: //Party colors
setPaletteColors(curPalette, PartyColors_p);
break;
case 7: //Cloud colors
setPaletteColors(curPalette, CloudColors_p);
break;
case 8: //Lava colors
setPaletteColors(curPalette, LavaColors_p);
break;
case 9: //Ocean colors
setPaletteColors(curPalette, OceanColors_p);
break;
case 10: //Forest colors
setPaletteColors(curPalette, ForestColors_p);
break;
case 11: //Rainbow colors
setPaletteColors(curPalette, RainbowColors_p);
break;
case 12: //Rainbow stripe colors
setPaletteColors(curPalette, RainbowStripeColors_p);
break;
default:
2022-02-28 23:32:24 +01:00
{
2022-07-28 23:19:58 +02:00
if (i>=palettesCount) {
setPaletteColors(curPalette, strip.customPalettes[i - palettesCount]);
} else {
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - 13])), 72);
setPaletteColors(curPalette, tcp);
}
2022-02-28 23:32:24 +01:00
}
2021-11-25 22:05:16 +01:00
break;
}
}
}
void serializeNetworks(JsonObject root)
{
JsonArray networks = root.createNestedArray(F("networks"));
int16_t status = WiFi.scanComplete();
switch (status) {
case WIFI_SCAN_FAILED:
WiFi.scanNetworks(true);
return;
case WIFI_SCAN_RUNNING:
return;
}
for (int i = 0; i < status; i++) {
JsonObject node = networks.createNestedObject();
node["ssid"] = WiFi.SSID(i);
node["rssi"] = WiFi.RSSI(i);
node["bssid"] = WiFi.BSSIDstr(i);
node["channel"] = WiFi.channel(i);
node["enc"] = WiFi.encryptionType(i);
}
WiFi.scanDelete();
if (WiFi.scanComplete() == WIFI_SCAN_FAILED) {
WiFi.scanNetworks(true);
}
}
2021-11-25 22:05:16 +01:00
void serializeNodes(JsonObject root)
{
JsonArray nodes = root.createNestedArray("nodes");
for (NodesMap::iterator it = Nodes.begin(); it != Nodes.end(); ++it)
{
if (it->second.ip[0] != 0)
{
JsonObject node = nodes.createNestedObject();
node[F("name")] = it->second.nodeName;
node["type"] = it->second.nodeType;
node["ip"] = it->second.ip.toString();
node[F("age")] = it->second.age;
node[F("vid")] = it->second.build;
}
}
}
// deserializes mode data string into JsonArray
2021-11-25 22:05:16 +01:00
void serializeModeData(JsonArray fxdata)
{
2023-08-17 16:24:16 +02:00
char lineBuffer[256];
for (size_t i = 0; i < strip.getModeCount(); i++) {
strncpy_P(lineBuffer, strip.getModeData(i), sizeof(lineBuffer)/sizeof(char)-1);
lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
if (lineBuffer[0] != 0) {
char* dataPtr = strchr(lineBuffer,'@');
if (dataPtr) fxdata.add(dataPtr+1);
else fxdata.add("");
}
}
2021-11-25 22:05:16 +01:00
}
// deserializes mode names string into JsonArray
// also removes effect data extensions (@...) from deserialised names
2023-08-17 16:24:16 +02:00
void serializeModeNames(JsonArray arr)
{
char lineBuffer[256];
for (size_t i = 0; i < strip.getModeCount(); i++) {
strncpy_P(lineBuffer, strip.getModeData(i), sizeof(lineBuffer)/sizeof(char)-1);
lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
if (lineBuffer[0] != 0) {
char* dataPtr = strchr(lineBuffer,'@');
if (dataPtr) *dataPtr = 0; // terminate mode data after name
arr.add(lineBuffer);
}
}
2021-11-25 22:05:16 +01:00
}
void serveJson(AsyncWebServerRequest* request)
{
byte subJson = 0;
const String& url = request->url();
if (url.indexOf("state") > 0) subJson = JSON_PATH_STATE;
else if (url.indexOf("info") > 0) subJson = JSON_PATH_INFO;
else if (url.indexOf("si") > 0) subJson = JSON_PATH_STATE_INFO;
else if (url.indexOf("nodes") > 0) subJson = JSON_PATH_NODES;
else if (url.indexOf("eff") > 0) subJson = JSON_PATH_EFFECTS;
else if (url.indexOf("palx") > 0) subJson = JSON_PATH_PALETTES;
else if (url.indexOf("fxda") > 0) subJson = JSON_PATH_FXDATA;
else if (url.indexOf("net") > 0) subJson = JSON_PATH_NETWORKS;
#ifdef WLED_ENABLE_JSONLIVE
2021-11-25 22:05:16 +01:00
else if (url.indexOf("live") > 0) {
serveLiveLeds(request);
return;
}
#endif
2021-11-25 22:05:16 +01:00
else if (url.indexOf("pal") > 0) {
request->send_P(200, "application/json", JSON_palette_names);
return;
}
else if (url.indexOf("cfg") > 0 && handleFileRead(request, "/cfg.json")) {
return;
}
else if (url.length() > 6) { //not just /json
request->send(501, "application/json", F("{\"error\":\"Not implemented\"}"));
2021-11-25 22:05:16 +01:00
return;
}
if (!requestJSONBufferLock(17)) {
request->send(503, "application/json", F("{\"error\":3}"));
return;
}
AsyncJsonResponse *response = new AsyncJsonResponse(&doc, subJson==JSON_PATH_FXDATA || subJson==JSON_PATH_EFFECTS); // will clear and convert JsonDocument into JsonArray if necessary
2021-11-25 22:05:16 +01:00
JsonVariant lDoc = response->getRoot();
switch (subJson)
{
case JSON_PATH_STATE:
2021-11-25 22:05:16 +01:00
serializeState(lDoc); break;
case JSON_PATH_INFO:
2021-11-25 22:05:16 +01:00
serializeInfo(lDoc); break;
case JSON_PATH_NODES:
2021-11-25 22:05:16 +01:00
serializeNodes(lDoc); break;
case JSON_PATH_PALETTES:
serializePalettes(lDoc, request->hasParam("page") ? request->getParam("page")->value().toInt() : 0); break;
case JSON_PATH_EFFECTS:
serializeModeNames(lDoc); break;
case JSON_PATH_FXDATA:
serializeModeData(lDoc); break;
case JSON_PATH_NETWORKS:
serializeNetworks(lDoc); break;
2021-11-25 22:05:16 +01:00
default: //all
JsonObject state = lDoc.createNestedObject("state");
serializeState(state);
JsonObject info = lDoc.createNestedObject("info");
serializeInfo(info);
if (subJson != JSON_PATH_STATE_INFO)
2021-11-25 22:05:16 +01:00
{
JsonArray effects = lDoc.createNestedArray(F("effects"));
2022-05-24 16:43:21 +02:00
serializeModeNames(effects); // remove WLED-SR extensions from effect names
2021-11-25 22:05:16 +01:00
lDoc[F("palettes")] = serialized((const __FlashStringHelper*)JSON_palette_names);
}
//lDoc["m"] = lDoc.memoryUsage(); // JSON buffer usage, for remote debugging
2021-11-25 22:05:16 +01:00
}
DEBUG_PRINTF("JSON buffer size: %u for request: %d\n", lDoc.memoryUsage(), subJson);
#ifdef WLED_DEBUG
size_t len =
#endif
response->setLength();
DEBUG_PRINT(F("JSON content length: ")); DEBUG_PRINTLN(len);
2021-11-25 22:05:16 +01:00
request->send(response);
releaseJSONBufferLock();
}
#ifdef WLED_ENABLE_JSONLIVE
2021-11-25 22:05:16 +01:00
#define MAX_LIVE_LEDS 180
bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient)
{
#ifdef WLED_ENABLE_WEBSOCKETS
AsyncWebSocketClient * wsc = nullptr;
if (!request) { //not HTTP, use Websockets
wsc = ws.client(wsClient);
if (!wsc || wsc->queueLength() > 0) return false; //only send if queue free
}
#endif
uint16_t used = strip.getLengthTotal();
uint16_t n = (used -1) /MAX_LIVE_LEDS +1; //only serve every n'th LED if count over MAX_LIVE_LEDS
char buffer[2000];
strcpy_P(buffer, PSTR("{\"leds\":["));
obuf = buffer;
olen = 9;
for (size_t i= 0; i < used; i += n)
2021-11-25 22:05:16 +01:00
{
uint32_t c = strip.getPixelColor(i);
2023-07-06 22:48:13 +02:00
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
uint8_t w = W(c);
r = scale8(qadd8(w, r), strip.getBrightness()); //R, add white channel to RGB channels as a simple RGBW -> RGB map
g = scale8(qadd8(w, g), strip.getBrightness()); //G
b = scale8(qadd8(w, b), strip.getBrightness()); //B
olen += sprintf(obuf + olen, "\"%06X\",", RGBW32(r,g,b,0));
2021-11-25 22:05:16 +01:00
}
olen -= 1;
oappend((const char*)F("],\"n\":"));
oappendi(n);
oappend("}");
if (request) {
request->send(200, "application/json", buffer);
}
#ifdef WLED_ENABLE_WEBSOCKETS
else {
wsc->text(obuf, olen);
}
#endif
return true;
}
#endif