Files
WLED/wled00/cfg.cpp
Frank Möhle f830ea498c Clean up global variables namespace, save a few 100 bytes of flash (#5368)
* reduce scope of some variables to "static"

these are not used anywhere else. Making them static avoid name conflicts, cleans up the global scope and in some cases allows for better  optimization by the compiler.

* remove unused reference ``tz``from analog clock usermod

* side-catch: remove two "local var shadows global var" warnings

* reduce scope of functions declared globally, but not used anywhere else
Safe to make static
* declared in fcn_declare.h, only used locally in one file
* not declared in fcn_declare.h, only used locally

* HUB75 small optimization
make bit array functions "static inline"
-> better for optimization, saves some bytes because the compiler does not need to preserve a non-inline function copy for external references.

* a few more static functions
as suggested by the rabbit.
2026-02-11 22:24:06 +01:00

1371 lines
50 KiB
C++

#include "wled.h"
#include "wled_ethernet.h"
/*
* Serializes and parses the cfg.json and wsec.json settings files, stored in internal FS.
* The structure of the JSON is not to be considered an official API and may change without notice.
*/
#ifndef PIXEL_COUNTS
#define PIXEL_COUNTS DEFAULT_LED_COUNT
#endif
#ifndef DATA_PINS
#define DATA_PINS DEFAULT_LED_PIN
#endif
#ifndef LED_TYPES
#define LED_TYPES DEFAULT_LED_TYPE
#endif
#ifndef DEFAULT_LED_COLOR_ORDER
#define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB
#endif
static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) {
return (count > 0) ? (Bus::getNumberOfPins(*current) + sumPinsRequired(current+1,count-1)) : 0;
}
static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) {
// Pins provided < pins required -> always invalid
// Pins provided = pins required -> always valid
// Pins provided > pins required -> valid if excess pins are a product of last type pins since it will be repeated
return (sumPinsRequired(types, numTypes) > numPins) ? false :
(numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0;
}
//simple macro for ArduinoJSON's or syntax
#define CJSON(a,b) a = b | a
static inline void getStringFromJson(char* dest, const char* src, size_t len) {
if (src != nullptr) strlcpy(dest, src, len);
}
bool deserializeConfig(JsonObject doc, bool fromFS) {
bool needsSave = false;
//int rev_major = doc["rev"][0]; // 1
//int rev_minor = doc["rev"][1]; // 0
//long vid = doc[F("vid")]; // 2010020
JsonObject id = doc["id"];
getStringFromJson(cmDNS, id[F("mdns")], 33);
getStringFromJson(serverDescription, id[F("name")], 33);
#ifndef WLED_DISABLE_ALEXA
getStringFromJson(alexaInvocationName, id[F("inv")], 33);
#endif
CJSON(simplifiedUI, id[F("sui")]);
JsonObject nw = doc["nw"];
#ifndef WLED_DISABLE_ESPNOW
CJSON(enableESPNow, nw[F("espnow")]);
linked_remotes.clear();
JsonVariant lrem = nw[F("linked_remote")];
if (!lrem.isNull()) {
if (lrem.is<JsonArray>()) {
for (size_t i = 0; i < lrem.size(); i++) {
std::array<char, 13> entry{};
getStringFromJson(entry.data(), lrem[i], 13);
entry[12] = '\0';
linked_remotes.emplace_back(entry);
}
}
else { // legacy support for single MAC address in config
std::array<char, 13> entry{};
getStringFromJson(entry.data(), lrem, 13);
entry[12] = '\0';
linked_remotes.emplace_back(entry);
}
}
#endif
size_t n = 0;
JsonArray nw_ins = nw["ins"];
if (!nw_ins.isNull()) {
// as password are stored separately in wsec.json when reading configuration vector resize happens there, but for dynamic config we need to resize if necessary
if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing
for (JsonObject wifi : nw_ins) {
JsonArray ip = wifi["ip"];
JsonArray gw = wifi["gw"];
JsonArray sn = wifi["sn"];
char ssid[33] = "";
char pass[65] = "";
char bssid[13] = "";
IPAddress nIP = (uint32_t)0U, nGW = (uint32_t)0U, nSN = (uint32_t)0x00FFFFFF; // little endian
getStringFromJson(ssid, wifi[F("ssid")], 33);
getStringFromJson(pass, wifi["psk"], 65); // password is not normally present but if it is, use it
getStringFromJson(bssid, wifi[F("bssid")], 13);
for (size_t i = 0; i < 4; i++) {
CJSON(nIP[i], ip[i]);
CJSON(nGW[i], gw[i]);
CJSON(nSN[i], sn[i]);
}
if (strlen(ssid) > 0) strlcpy(multiWiFi[n].clientSSID, ssid, 33); // this will keep old SSID intact if not present in JSON
if (strlen(pass) > 0) strlcpy(multiWiFi[n].clientPass, pass, 65); // this will keep old password intact if not present in JSON
if (strlen(bssid) > 0) fillStr2MAC(multiWiFi[n].bssid, bssid);
multiWiFi[n].staticIP = nIP;
multiWiFi[n].staticGW = nGW;
multiWiFi[n].staticSN = nSN;
#ifdef WLED_ENABLE_WPA_ENTERPRISE
byte encType = WIFI_ENCRYPTION_TYPE_PSK;
char anonIdent[65] = "";
char ident[65] = "";
CJSON(encType, wifi[F("enc_type")]);
getStringFromJson(anonIdent, wifi["e_anon_ident"], 65);
getStringFromJson(ident, wifi["e_ident"], 65);
multiWiFi[n].encryptionType = encType;
strlcpy(multiWiFi[n].enterpriseAnonIdentity, anonIdent, 65);
strlcpy(multiWiFi[n].enterpriseIdentity, ident, 65);
#endif
if (++n >= WLED_MAX_WIFI_COUNT) break;
}
}
JsonArray dns = nw[F("dns")];
if (!dns.isNull()) {
for (size_t i = 0; i < 4; i++) {
CJSON(dnsAddress[i], dns[i]);
}
}
// https://github.com/wled/WLED/issues/5247
#ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc[F("eth")];
CJSON(ethernetType, ethernet["type"]);
// NOTE: Ethernet configuration takes priority over other use of pins
initEthernet();
#endif
JsonObject ap = doc["ap"];
getStringFromJson(apSSID, ap[F("ssid")], 33);
getStringFromJson(apPass, ap["psk"] , 65); //normally not present due to security
//int ap_pskl = ap[F("pskl")];
CJSON(apChannel, ap[F("chan")]);
if (apChannel > 13 || apChannel < 1) apChannel = 1;
CJSON(apHide, ap[F("hide")]);
if (apHide > 1) apHide = 1;
CJSON(apBehavior, ap[F("behav")]);
/*
JsonArray ap_ip = ap["ip"];
for (unsigned i = 0; i < 4; i++) {
apIP[i] = ap_ip;
}
*/
JsonObject wifi = doc[F("wifi")];
noWifiSleep = !(wifi[F("sleep")] | !noWifiSleep); // inverted
//noWifiSleep = !noWifiSleep;
CJSON(force802_3g, wifi[F("phy")]); //force phy mode g?
#ifdef ARDUINO_ARCH_ESP32
CJSON(txPower, wifi[F("txpwr")]);
txPower = min(max((int)txPower, (int)WIFI_POWER_2dBm), (int)WIFI_POWER_19_5dBm);
#endif
JsonObject hw = doc[F("hw")];
// initialize LED pins and lengths prior to other HW (except for ethernet)
JsonObject hw_led = hw["led"];
uint16_t total = hw_led[F("total")] | strip.getLengthTotal();
uint16_t ablMilliampsMax = hw_led[F("maxpwr")] | BusManager::ablMilliampsMax();
BusManager::setMilliampsMax(ablMilliampsMax);
Bus::setGlobalAWMode(hw_led[F("rgbwm")] | AW_GLOBAL_DISABLED);
CJSON(strip.correctWB, hw_led["cct"]);
CJSON(strip.cctFromRgb, hw_led[F("cr")]);
CJSON(cctICused, hw_led[F("ic")]);
uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend();
Bus::setCCTBlend(cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
CJSON(useParallelI2S, hw_led[F("prl")]);
#endif
#ifndef WLED_DISABLE_2D
// 2D Matrix Settings
JsonObject matrix = hw_led[F("matrix")];
if (!matrix.isNull()) {
strip.isMatrix = true;
unsigned numPanels = matrix[F("mpc")] | 1;
numPanels = constrain(numPanels, 1, WLED_MAX_PANELS);
strip.panel.clear();
JsonArray panels = matrix[F("panels")];
unsigned s = 0;
if (!panels.isNull()) {
strip.panel.reserve(numPanels); // pre-allocate default 8x8 panels
for (JsonObject pnl : panels) {
WS2812FX::Panel p;
CJSON(p.bottomStart, pnl["b"]);
CJSON(p.rightStart, pnl["r"]);
CJSON(p.vertical, pnl["v"]);
CJSON(p.serpentine, pnl["s"]);
CJSON(p.xOffset, pnl["x"]);
CJSON(p.yOffset, pnl["y"]);
CJSON(p.height, pnl["h"]);
CJSON(p.width, pnl["w"]);
strip.panel.push_back(p);
if (++s >= numPanels) break; // max panels reached
}
}
strip.panel.shrink_to_fit(); // release unused memory (just in case)
// cannot call strip.deserializeLedmap()/strip.setUpMatrix() here due to already locked JSON buffer
//if (!fromFS) doInit2D = true; // if called at boot (fromFS==true), WLED::beginStrip() will take care of setting up matrix
}
#endif
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize());
JsonArray ins = hw_led["ins"];
if (!ins.isNull()) {
int s = 0; // bus iterator
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES) break; // only counts physical buses
uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255};
JsonArray pinArr = elm["pin"];
if (pinArr.size() == 0) continue;
//pins[0] = pinArr[0];
unsigned i = 0;
for (int p : pinArr) {
pins[i++] = p;
if (i>4) break;
}
uint16_t length = elm["len"] | 1;
uint8_t colorOrder = (int)elm[F("order")]; // contains white channel swap option in upper nibble
uint8_t skipFirst = elm[F("skip")];
uint16_t start = elm["start"] | 0;
if (length==0 || start + length > MAX_LEDS) continue; // zero length or we reached max. number of LEDs, just stop
uint8_t ledType = elm["type"] | TYPE_WS2812_RGB;
bool reversed = elm["rev"];
bool refresh = elm["ref"] | false;
uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM
uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY;
uint8_t maPerLed = elm[F("ledma")] | LED_MILLIAMPS_DEFAULT;
uint16_t maMax = elm[F("maxpwr")] | (ablMilliampsMax * length) / total; // rough (incorrect?) per strip ABL calculation when no config exists
// To disable brightness limiter we either set output max current to 0 or single LED current to 0 (we choose output max current)
if (Bus::isPWM(ledType) || Bus::isOnOff(ledType) || Bus::isVirtual(ledType)) { // analog and virtual
maPerLed = 0;
maMax = 0;
}
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
String host = elm[F("text")] | String();
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, host);
doInitBusses = true; // finalization done in beginStrip()
if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want
}
} else if (fromFS) {
//if busses failed to load, add default (fresh install, FS issue, ...)
BusManager::removeAll();
busConfigs.clear();
DEBUG_PRINTLN(F("No busses, init default"));
constexpr unsigned defDataTypes[] = {LED_TYPES};
constexpr unsigned defDataPins[] = {DATA_PINS};
constexpr unsigned defCounts[] = {PIXEL_COUNTS};
constexpr unsigned defNumTypes = (sizeof(defDataTypes) / sizeof(defDataTypes[0]));
constexpr unsigned defNumPins = (sizeof(defDataPins) / sizeof(defDataPins[0]));
constexpr unsigned defNumCounts = (sizeof(defCounts) / sizeof(defCounts[0]));
static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins),
"The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES");
unsigned pinsIndex = 0;
for (unsigned i = 0; i < WLED_MAX_BUSSES; i++) {
uint8_t defPin[OUTPUT_MAX_PINS];
// if we have less types than requested outputs and they do not align, use last known type to set current type
unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1];
unsigned busPins = Bus::getNumberOfPins(dataType);
// if we need more pins than available all outputs have been configured
if (pinsIndex + busPins > defNumPins) break;
// Assign all pins first so we can check for conflicts on this bus
for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j];
for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) {
bool validPin = true;
// When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware
// i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc.
// Pin should not be already allocated, read/only or defined for current bus
while (PinManager::isPinAllocated(defPin[j]) || !PinManager::isPinOk(defPin[j],true)) {
if (validPin) {
DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output."));
defPin[j] = 1; // start with GPIO1 and work upwards
validPin = false;
} else if (defPin[j] < WLED_NUM_PINS) {
defPin[j]++;
} else {
DEBUG_PRINTLN(F("No available pins left! Can't configure output."));
break;
}
// is the newly assigned pin already defined or used previously?
// try next in line until there are no clashes or we run out of pins
bool clash;
do {
clash = false;
// check for conflicts on current bus
for (const auto &pin : defPin) {
if (&pin != &defPin[j] && pin == defPin[j]) {
clash = true;
break;
}
}
// We already have a clash on current bus, no point checking next buses
if (!clash) {
// check for conflicts in defined pins
for (const auto &pin : defDataPins) {
if (pin == defPin[j]) {
clash = true;
break;
}
}
}
if (clash) defPin[j]++;
if (defPin[j] >= WLED_NUM_PINS) break;
} while (clash);
}
}
pinsIndex += busPins;
// if we have less counts than pins and they do not align, use last known count to set current count
unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
unsigned start = 0;
// analog always has length 1
if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1;
busConfigs.emplace_back(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0);
doInitBusses = true; // finalization done in beginStrip()
}
}
if (hw_led["rev"] && BusManager::getNumBusses()) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus
// read color order map configuration
JsonArray hw_com = hw[F("com")];
if (!hw_com.isNull()) {
BusManager::getColorOrderMap().reserve(std::min(hw_com.size(), (size_t)WLED_MAX_COLOR_ORDER_MAPPINGS));
for (JsonObject entry : hw_com) {
uint16_t start = entry["start"] | 0;
uint16_t len = entry["len"] | 0;
uint8_t colorOrder = (int)entry[F("order")];
if (!BusManager::getColorOrderMap().add(start, len, colorOrder)) break;
}
}
// read multiple button configuration
JsonObject btn_obj = hw["btn"];
CJSON(touchThreshold, btn_obj[F("tt")]);
bool pull = btn_obj[F("pull")] | (!disablePullUp); // if true, pullup is enabled
disablePullUp = !pull;
JsonArray hw_btn_ins = btn_obj["ins"];
if (!hw_btn_ins.isNull()) {
// deallocate existing button pins
for (const auto &button : buttons) PinManager::deallocatePin(button.pin, PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button
buttons.clear(); // clear existing buttons
unsigned s = 0;
for (JsonObject btn : hw_btn_ins) {
uint8_t type = btn["type"] | BTN_TYPE_NONE;
int8_t pin = btn["pin"][0] | -1;
if (pin > -1 && PinManager::allocatePin(pin, false, PinOwner::Button)) {
#ifdef ARDUINO_ARCH_ESP32
// ESP32 only: check that analog button pin is a valid ADC gpio
if ((type == BTN_TYPE_ANALOG) || (type == BTN_TYPE_ANALOG_INVERTED)) {
if (digitalPinToAnalogChannel(pin) < 0) {
// not an ADC analog pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), pin, s);
PinManager::deallocatePin(pin, PinOwner::Button);
pin = -1;
continue;
} else {
analogReadResolution(12); // see #4040
}
} else if ((type == BTN_TYPE_TOUCH || type == BTN_TYPE_TOUCH_SWITCH)) {
if (digitalPinToTouchChannel(pin) < 0) {
// not a touch pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), pin, s);
PinManager::deallocatePin(pin, PinOwner::Button);
pin = -1;
continue;
}
//if touch pin, enable the touch interrupt on ESP32 S2 & S3
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so
else touchAttachInterrupt(pin, touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000)
#endif
} else
#endif
{
// regular buttons and switches
if (disablePullUp) {
pinMode(pin, INPUT);
} else {
#ifdef ESP32
pinMode(pin, type==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(pin, INPUT_PULLUP);
#endif
}
}
JsonArray hw_btn_ins_0_macros = btn["macros"];
uint8_t press = hw_btn_ins_0_macros[0] | 0;
uint8_t longPress = hw_btn_ins_0_macros[1] | 0;
uint8_t doublePress = hw_btn_ins_0_macros[2] | 0;
buttons.emplace_back(pin, type, press, longPress, doublePress); // add button to vector
}
if (++s >= WLED_MAX_BUTTONS) break; // max buttons reached
}
} else if (fromFS) {
// new install/missing configuration (button 0 has defaults)
// relies upon only being called once with fromFS == true, which is currently true.
constexpr uint8_t defTypes[] = {BTNTYPE};
constexpr int8_t defPins[] = {BTNPIN};
constexpr unsigned numTypes = (sizeof(defTypes) / sizeof(defTypes[0]));
constexpr unsigned numPins = (sizeof(defPins) / sizeof(defPins[0]));
// check if the number of pins and types are valid; count of pins must be greater than or equal to types
static_assert(numTypes <= numPins, "The default button pins defined in BTNPIN do not match the button types defined in BTNTYPE");
uint8_t type = BTN_TYPE_NONE;
buttons.clear(); // clear existing buttons (just in case)
for (size_t s = 0; s < WLED_MAX_BUTTONS && s < numPins; s++) {
type = defTypes[s < numTypes ? s : numTypes - 1]; // use last known type to set current type if types less than pins
if (type == BTN_TYPE_NONE || defPins[s] < 0 || !PinManager::allocatePin(defPins[s], false, PinOwner::Button)) {
if (buttons.size() == 0) buttons.emplace_back(-1, BTN_TYPE_NONE); // add disabled button to vector (so we have at least one button defined)
continue; // pin not available or invalid, skip configuring this GPIO
}
if (disablePullUp) {
pinMode(defPins[s], INPUT);
} else {
#ifdef ESP32
pinMode(defPins[s], type==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(defPins[s], INPUT_PULLUP);
#endif
}
buttons.emplace_back(defPins[s], type); // add button to vector
}
}
CJSON(buttonPublishMqtt, btn_obj["mqtt"]);
#ifndef WLED_DISABLE_INFRARED
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
if (hw_ir_pin > -2) {
PinManager::deallocatePin(irPin, PinOwner::IR);
if (PinManager::allocatePin(hw_ir_pin, false, PinOwner::IR)) {
irPin = hw_ir_pin;
} else {
irPin = -1;
}
}
CJSON(irEnabled, hw["ir"]["type"]);
#endif
CJSON(irApplyToAllSelected, hw["ir"]["sel"]);
JsonObject relay = hw[F("relay")];
rlyOpenDrain = relay[F("odrain")] | rlyOpenDrain;
int hw_relay_pin = relay["pin"] | -2;
if (hw_relay_pin > -2) {
PinManager::deallocatePin(rlyPin, PinOwner::Relay);
if (PinManager::allocatePin(hw_relay_pin,true, PinOwner::Relay)) {
rlyPin = hw_relay_pin;
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
} else {
rlyPin = -1;
}
}
if (relay.containsKey("rev")) {
rlyMde = !relay["rev"];
}
CJSON(serialBaud, hw[F("baud")]);
if (serialBaud < 96 || serialBaud > 15000) serialBaud = 1152;
updateBaudRate(serialBaud *100);
JsonArray hw_if_i2c = hw[F("if")][F("i2c-pin")];
CJSON(i2c_sda, hw_if_i2c[0]);
CJSON(i2c_scl, hw_if_i2c[1]);
PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } };
if (i2c_scl >= 0 && i2c_sda >= 0 && PinManager::allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) {
#ifdef ESP32
if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initialised (Wire.begin() called prior)
else Wire.begin();
#else
Wire.begin(i2c_sda, i2c_scl);
#endif
} else {
i2c_sda = -1;
i2c_scl = -1;
}
JsonArray hw_if_spi = hw[F("if")][F("spi-pin")];
CJSON(spi_mosi, hw_if_spi[0]);
CJSON(spi_sclk, hw_if_spi[1]);
CJSON(spi_miso, hw_if_spi[2]);
PinManagerPinType spi[3] = { { spi_mosi, true }, { spi_miso, true }, { spi_sclk, true } };
if (spi_mosi >= 0 && spi_sclk >= 0 && PinManager::allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) {
#ifdef ESP32
SPI.begin(spi_sclk, spi_miso, spi_mosi); // SPI global uses VSPI on ESP32 and FSPI on C3, S3
#else
SPI.begin();
#endif
} else {
spi_mosi = -1;
spi_miso = -1;
spi_sclk = -1;
}
//int hw_status_pin = hw[F("status")]["pin"]; // -1
JsonObject light = doc[F("light")];
CJSON(briMultiplier, light[F("scale-bri")]);
CJSON(paletteBlend, light[F("pal-mode")]);
CJSON(strip.autoSegments, light[F("aseg")]);
CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.2
float light_gc_bri = light["gc"]["bri"] | 1.0f; // default to 1.0 (false)
float light_gc_col = light["gc"]["col"] | gammaCorrectVal; // default to gammaCorrectVal (true)
if (light_gc_bri != 1.0f) gammaCorrectBri = true;
else gammaCorrectBri = false;
if (light_gc_col != 1.0f) gammaCorrectCol = true;
else gammaCorrectCol = false;
if (gammaCorrectVal < 0.1f || gammaCorrectVal > 3) {
gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false;
gammaCorrectCol = false;
}
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up tables
JsonObject light_tr = light["tr"];
int tdd = light_tr["dur"] | -1;
if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100;
strip.setTransition(transitionDelayDefault);
CJSON(randomPaletteChangeTime, light_tr[F("rpc")]);
CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]);
JsonObject light_nl = light["nl"];
CJSON(nightlightMode, light_nl["mode"]);
byte prev = nightlightDelayMinsDefault;
CJSON(nightlightDelayMinsDefault, light_nl["dur"]);
if (nightlightDelayMinsDefault != prev) nightlightDelayMins = nightlightDelayMinsDefault;
CJSON(nightlightTargetBri, light_nl[F("tbri")]);
CJSON(macroNl, light_nl["macro"]);
JsonObject def = doc["def"];
CJSON(bootPreset, def["ps"]);
CJSON(turnOnAtBoot, def["on"]); // true
CJSON(briS, def["bri"]); // 128
JsonObject interfaces = doc["if"];
JsonObject if_sync = interfaces["sync"];
CJSON(udpPort, if_sync[F("port0")]); // 21324
CJSON(udpPort2, if_sync[F("port1")]); // 65506
#ifndef WLED_DISABLE_ESPNOW
CJSON(useESPNowSync, if_sync[F("espnow")]);
#endif
JsonObject if_sync_recv = if_sync[F("recv")];
CJSON(receiveNotificationBrightness, if_sync_recv["bri"]);
CJSON(receiveNotificationColor, if_sync_recv["col"]);
CJSON(receiveNotificationEffects, if_sync_recv["fx"]);
CJSON(receiveNotificationPalette, if_sync_recv["pal"]);
CJSON(receiveGroups, if_sync_recv["grp"]);
CJSON(receiveSegmentOptions, if_sync_recv["seg"]);
CJSON(receiveSegmentBounds, if_sync_recv["sb"]);
JsonObject if_sync_send = if_sync[F("send")];
CJSON(sendNotifications, if_sync_send["en"]);
sendNotificationsRT = sendNotifications;
CJSON(notifyDirect, if_sync_send[F("dir")]);
CJSON(notifyButton, if_sync_send["btn"]);
CJSON(notifyAlexa, if_sync_send["va"]);
CJSON(notifyHue, if_sync_send["hue"]);
CJSON(syncGroups, if_sync_send["grp"]);
if (if_sync_send[F("twice")]) udpNumRetries = 1; // import setting from 0.13 and earlier
CJSON(udpNumRetries, if_sync_send["ret"]);
JsonObject if_nodes = interfaces["nodes"];
CJSON(nodeListEnabled, if_nodes[F("list")]);
CJSON(nodeBroadcastEnabled, if_nodes[F("bcast")]);
JsonObject if_live = interfaces["live"];
CJSON(receiveDirect, if_live["en"]); // UDP/Hyperion realtime
CJSON(useMainSegmentOnly, if_live[F("mso")]);
CJSON(realtimeRespectLedMaps, if_live[F("rlm")]);
CJSON(e131Port, if_live["port"]); // 5568
if (e131Port == DDP_DEFAULT_PORT) e131Port = E131_DEFAULT_PORT; // prevent double DDP port allocation
CJSON(e131Multicast, if_live[F("mc")]);
JsonObject if_live_dmx = if_live["dmx"];
CJSON(e131Universe, if_live_dmx[F("uni")]);
CJSON(e131SkipOutOfSequence, if_live_dmx[F("seqskip")]);
CJSON(DMXAddress, if_live_dmx[F("addr")]);
if (!DMXAddress || DMXAddress > 510) DMXAddress = 1;
CJSON(DMXSegmentSpacing, if_live_dmx[F("dss")]);
if (DMXSegmentSpacing > 150) DMXSegmentSpacing = 0;
CJSON(e131Priority, if_live_dmx[F("e131prio")]);
if (e131Priority > 200) e131Priority = 200;
CJSON(DMXMode, if_live_dmx["mode"]);
tdd = if_live[F("timeout")] | -1;
if (tdd >= 0) realtimeTimeoutMs = tdd * 100;
#ifdef WLED_ENABLE_DMX_INPUT
CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]);
CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]);
CJSON(dmxInputEnablePin, if_live_dmx[F("inputEnablePin")]);
CJSON(dmxInputPort, if_live_dmx[F("dmxInputPort")]);
#endif
CJSON(arlsForceMaxBri, if_live[F("maxbri")]);
CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false
CJSON(arlsOffset, if_live[F("offset")]); // 0
#ifndef WLED_DISABLE_ALEXA
CJSON(alexaEnabled, interfaces["va"][F("alexa")]); // false
CJSON(macroAlexaOn, interfaces["va"]["macros"][0]);
CJSON(macroAlexaOff, interfaces["va"]["macros"][1]);
CJSON(alexaNumPresets, interfaces["va"]["p"]);
#endif
#ifndef WLED_DISABLE_MQTT
JsonObject if_mqtt = interfaces["mqtt"];
CJSON(mqttEnabled, if_mqtt["en"]);
getStringFromJson(mqttServer, if_mqtt[F("broker")], MQTT_MAX_SERVER_LEN+1);
CJSON(mqttPort, if_mqtt["port"]); // 1883
getStringFromJson(mqttUser, if_mqtt[F("user")], 41);
getStringFromJson(mqttPass, if_mqtt["psk"], 65); //normally not present due to security
getStringFromJson(mqttClientID, if_mqtt[F("cid")], 41);
getStringFromJson(mqttDeviceTopic, if_mqtt[F("topics")][F("device")], MQTT_MAX_TOPIC_LEN+1); // "wled/test"
getStringFromJson(mqttGroupTopic, if_mqtt[F("topics")][F("group")], MQTT_MAX_TOPIC_LEN+1); // ""
CJSON(retainMqttMsg, if_mqtt[F("rtn")]);
#endif
#ifndef WLED_DISABLE_HUESYNC
JsonObject if_hue = interfaces["hue"];
CJSON(huePollingEnabled, if_hue["en"]);
CJSON(huePollLightId, if_hue["id"]);
tdd = if_hue[F("iv")] | -1;
if (tdd >= 2) huePollIntervalMs = tdd * 100;
JsonObject if_hue_recv = if_hue["recv"];
CJSON(hueApplyOnOff, if_hue_recv["on"]);
CJSON(hueApplyBri, if_hue_recv["bri"]);
CJSON(hueApplyColor, if_hue_recv["col"]);
JsonArray if_hue_ip = if_hue["ip"];
for (unsigned i = 0; i < 4; i++)
CJSON(hueIP[i], if_hue_ip[i]);
#endif
JsonObject if_ntp = interfaces[F("ntp")];
CJSON(ntpEnabled, if_ntp["en"]);
getStringFromJson(ntpServerName, if_ntp[F("host")], 33); // "1.wled.pool.ntp.org"
CJSON(currentTimezone, if_ntp[F("tz")]);
CJSON(utcOffsetSecs, if_ntp[F("offset")]);
CJSON(useAMPM, if_ntp[F("ampm")]);
CJSON(longitude, if_ntp[F("ln")]);
CJSON(latitude, if_ntp[F("lt")]);
JsonObject ol = doc[F("ol")];
CJSON(overlayCurrent ,ol[F("clock")]); // 0
CJSON(countdownMode, ol[F("cntdwn")]);
CJSON(overlayMin, ol["min"]);
CJSON(overlayMax, ol[F("max")]);
CJSON(analogClock12pixel, ol[F("o12pix")]);
CJSON(analogClock5MinuteMarks, ol[F("o5m")]);
CJSON(analogClockSecondsTrail, ol[F("osec")]);
CJSON(analogClockSolidBlack, ol[F("osb")]);
//timed macro rules
JsonObject tm = doc[F("timers")];
JsonObject cntdwn = tm[F("cntdwn")];
JsonArray cntdwn_goal = cntdwn[F("goal")];
CJSON(countdownYear, cntdwn_goal[0]);
CJSON(countdownMonth, cntdwn_goal[1]);
CJSON(countdownDay, cntdwn_goal[2]);
CJSON(countdownHour, cntdwn_goal[3]);
CJSON(countdownMin, cntdwn_goal[4]);
CJSON(countdownSec, cntdwn_goal[5]);
CJSON(macroCountdown, cntdwn["macro"]);
setCountdown();
JsonArray timers = tm["ins"];
uint8_t it = 0;
for (JsonObject timer : timers) {
if (it > 9) break;
if (it<8 && timer[F("hour")]==255) it=8; // hour==255 -> sunrise/sunset
CJSON(timerHours[it], timer[F("hour")]);
CJSON(timerMinutes[it], timer["min"]);
CJSON(timerMacro[it], timer["macro"]);
byte dowPrev = timerWeekday[it];
//note: act is currently only 0 or 1.
//the reason we are not using bool is that the on-disk type in 0.11.0 was already int
int actPrev = timerWeekday[it] & 0x01;
CJSON(timerWeekday[it], timer[F("dow")]);
if (timerWeekday[it] != dowPrev) { //present in JSON
timerWeekday[it] <<= 1; //add active bit
int act = timer["en"] | actPrev;
if (act) timerWeekday[it]++;
}
if (it<8) {
JsonObject start = timer["start"];
byte startm = start["mon"];
if (startm) timerMonth[it] = (startm << 4);
CJSON(timerDay[it], start["day"]);
JsonObject end = timer["end"];
CJSON(timerDayEnd[it], end["day"]);
byte endm = end["mon"];
if (startm) timerMonth[it] += endm & 0x0F;
if (!(timerMonth[it] & 0x0F)) timerMonth[it] += 12; //default end month to 12
}
it++;
}
JsonObject ota = doc["ota"];
const char* pwd = ota["psk"]; //normally not present due to security
bool pwdCorrect = !otaLock; //always allow access if ota not locked
if (pwd != nullptr && strncmp(otaPass, pwd, 33) == 0) pwdCorrect = true;
if (pwdCorrect) { //only accept these values from cfg.json if ota is unlocked (else from wsec.json)
CJSON(otaLock, ota[F("lock")]);
CJSON(wifiLock, ota[F("lock-wifi")]);
#ifndef WLED_DISABLE_OTA
CJSON(aOtaEnabled, ota[F("aota")]);
#endif
getStringFromJson(otaPass, pwd, 33); //normally not present due to security
CJSON(otaSameSubnet, ota[F("same-subnet")]);
}
#ifdef WLED_ENABLE_DMX
JsonObject dmx = doc["dmx"];
CJSON(DMXChannels, dmx[F("chan")]);
CJSON(DMXGap,dmx[F("gap")]);
CJSON(DMXStart, dmx["start"]);
CJSON(DMXStartLED,dmx[F("start-led")]);
JsonArray dmx_fixmap = dmx[F("fixmap")];
for (int i = 0; i < dmx_fixmap.size(); i++) {
if (i > 14) break;
CJSON(DMXFixtureMap[i],dmx_fixmap[i]);
}
CJSON(e131ProxyUniverse, dmx[F("e131proxy")]);
#endif
DEBUG_PRINTLN(F("Starting usermod config."));
JsonObject usermods_settings = doc["um"];
if (!usermods_settings.isNull()) {
needsSave = !UsermodManager::readFromConfig(usermods_settings);
}
if (fromFS) return needsSave;
// if from /json/cfg
doReboot = doc[F("rb")] | doReboot;
if (doInitBusses) return false; // no save needed, will do after bus init in wled.cpp loop
return (doc["sv"] | true);
}
static const char s_cfg_json[] PROGMEM = "/cfg.json";
bool backupConfig() {
return backupFile(s_cfg_json);
}
bool restoreConfig() {
return restoreFile(s_cfg_json);
}
bool verifyConfig() {
return validateJsonFile(s_cfg_json);
}
bool configBackupExists() {
return checkBackupExists(s_cfg_json);
}
// rename config file and reboot
// if the cfg file doesn't exist, such as after a reset, do nothing
void resetConfig() {
if (WLED_FS.exists(s_cfg_json)) {
DEBUG_PRINTLN(F("Reset config"));
char backupname[32];
snprintf_P(backupname, sizeof(backupname), PSTR("/rst.%s"), &s_cfg_json[1]);
WLED_FS.rename(s_cfg_json, backupname);
doReboot = true;
}
}
bool deserializeConfigFromFS() {
[[maybe_unused]] bool success = deserializeConfigSec();
if (!requestJSONBufferLock(JSON_LOCK_CFG_DES)) return false;
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
success = readObjectFromFile(s_cfg_json, nullptr, pDoc);
// NOTE: This routine deserializes *and* applies the configuration
// Therefore, must also initialize ethernet from this function
JsonObject root = pDoc->as<JsonObject>();
bool needsSave = deserializeConfig(root, true);
releaseJSONBufferLock();
return needsSave;
}
void serializeConfigToFS() {
serializeConfigSec();
backupConfig(); // backup before writing new config
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
if (!requestJSONBufferLock(JSON_LOCK_CFG_SER)) return;
JsonObject root = pDoc->to<JsonObject>();
serializeConfig(root);
File f = WLED_FS.open(FPSTR(s_cfg_json), "w");
if (f) serializeJson(root, f);
f.close();
releaseJSONBufferLock();
configNeedsWrite = false;
}
void serializeConfig(JsonObject root) {
JsonArray rev = root.createNestedArray("rev");
rev.add(1); //major settings revision
rev.add(0); //minor settings revision
root[F("vid")] = VERSION;
JsonObject id = root.createNestedObject("id");
id[F("mdns")] = cmDNS;
id[F("name")] = serverDescription;
#ifndef WLED_DISABLE_ALEXA
id[F("inv")] = alexaInvocationName;
#endif
id[F("sui")] = simplifiedUI;
JsonObject nw = root.createNestedObject("nw");
#ifndef WLED_DISABLE_ESPNOW
nw[F("espnow")] = enableESPNow;
JsonArray lrem = nw.createNestedArray(F("linked_remote"));
for (size_t i = 0; i < linked_remotes.size(); i++) {
lrem.add(linked_remotes[i].data());
}
#endif
JsonArray nw_ins = nw.createNestedArray("ins");
for (size_t n = 0; n < multiWiFi.size(); n++) {
JsonObject wifi = nw_ins.createNestedObject();
wifi[F("ssid")] = multiWiFi[n].clientSSID;
wifi[F("pskl")] = strlen(multiWiFi[n].clientPass);
char bssid[13];
fillMAC2Str(bssid, multiWiFi[n].bssid);
wifi[F("bssid")] = bssid;
JsonArray wifi_ip = wifi.createNestedArray("ip");
JsonArray wifi_gw = wifi.createNestedArray("gw");
JsonArray wifi_sn = wifi.createNestedArray("sn");
for (size_t i = 0; i < 4; i++) {
wifi_ip.add(multiWiFi[n].staticIP[i]);
wifi_gw.add(multiWiFi[n].staticGW[i]);
wifi_sn.add(multiWiFi[n].staticSN[i]);
}
#ifdef WLED_ENABLE_WPA_ENTERPRISE
wifi[F("enc_type")] = multiWiFi[n].encryptionType;
if (multiWiFi[n].encryptionType == WIFI_ENCRYPTION_TYPE_ENTERPRISE) {
wifi[F("e_anon_ident")] = multiWiFi[n].enterpriseAnonIdentity;
wifi[F("e_ident")] = multiWiFi[n].enterpriseIdentity;
}
#endif
}
JsonArray dns = nw.createNestedArray(F("dns"));
for (size_t i = 0; i < 4; i++) {
dns.add(dnsAddress[i]);
}
JsonObject ap = root.createNestedObject("ap");
ap[F("ssid")] = apSSID;
ap[F("pskl")] = strlen(apPass);
ap[F("chan")] = apChannel;
ap[F("hide")] = apHide;
ap[F("behav")] = apBehavior;
JsonArray ap_ip = ap.createNestedArray("ip");
ap_ip.add(4);
ap_ip.add(3);
ap_ip.add(2);
ap_ip.add(1);
JsonObject wifi = root.createNestedObject(F("wifi"));
wifi[F("sleep")] = !noWifiSleep;
wifi[F("phy")] = force802_3g;
#ifdef ARDUINO_ARCH_ESP32
wifi[F("txpwr")] = txPower;
#endif
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
JsonObject ethernet = root.createNestedObject("eth");
ethernet["type"] = ethernetType;
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
JsonArray pins = ethernet.createNestedArray("pin");
for (unsigned p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) pins.add(esp32_nonconfigurable_ethernet_pins[p].pin);
if (ethernetBoards[ethernetType].eth_power>=0) pins.add(ethernetBoards[ethernetType].eth_power);
if (ethernetBoards[ethernetType].eth_mdc>=0) pins.add(ethernetBoards[ethernetType].eth_mdc);
if (ethernetBoards[ethernetType].eth_mdio>=0) pins.add(ethernetBoards[ethernetType].eth_mdio);
switch (ethernetBoards[ethernetType].eth_clk_mode) {
case ETH_CLOCK_GPIO0_IN:
case ETH_CLOCK_GPIO0_OUT:
pins.add(0);
break;
case ETH_CLOCK_GPIO16_OUT:
pins.add(16);
break;
case ETH_CLOCK_GPIO17_OUT:
pins.add(17);
break;
}
}
#endif
JsonObject hw = root.createNestedObject(F("hw"));
JsonObject hw_led = hw.createNestedObject("led");
hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL
hw_led[F("maxpwr")] = BusManager::ablMilliampsMax();
// hw_led[F("ledma")] = 0; // no longer used
hw_led["cct"] = strip.correctWB;
hw_led[F("cr")] = strip.cctFromRgb;
hw_led[F("ic")] = cctICused;
hw_led[F("cb")] = Bus::getCCTBlend();
hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
hw_led[F("prl")] = BusManager::hasParallelOutput();
#endif
#ifndef WLED_DISABLE_2D
// 2D Matrix Settings
if (strip.isMatrix) {
JsonObject matrix = hw_led.createNestedObject(F("matrix"));
matrix[F("mpc")] = strip.panel.size();
JsonArray panels = matrix.createNestedArray(F("panels"));
for (size_t i = 0; i < strip.panel.size(); i++) {
JsonObject pnl = panels.createNestedObject();
pnl["b"] = strip.panel[i].bottomStart;
pnl["r"] = strip.panel[i].rightStart;
pnl["v"] = strip.panel[i].vertical;
pnl["s"] = strip.panel[i].serpentine;
pnl["x"] = strip.panel[i].xOffset;
pnl["y"] = strip.panel[i].yOffset;
pnl["h"] = strip.panel[i].height;
pnl["w"] = strip.panel[i].width;
}
}
#endif
JsonArray hw_led_ins = hw_led.createNestedArray("ins");
for (size_t s = 0; s < BusManager::getNumBusses(); s++) {
DEBUG_PRINTF_P(PSTR("Cfg: Saving bus #%u\n"), s);
const Bus *bus = BusManager::getBus(s);
if (!bus) break; // Memory corruption, iterator invalid
DEBUG_PRINTF_P(PSTR(" (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"),
(int)bus->getStart(), (int)(bus->getStart()+bus->getLength()),
(int)(bus->getType() & 0x7F),
(int)bus->getColorOrder(),
(int)bus->isReversed(),
(int)bus->skippedLeds(),
(int)bus->getAutoWhiteMode(),
(int)bus->getFrequency(),
(int)bus->getLEDCurrent(), (int)bus->getMaxCurrent()
);
JsonObject ins = hw_led_ins.createNestedObject();
ins["start"] = bus->getStart();
ins["len"] = bus->getLength();
JsonArray ins_pin = ins.createNestedArray("pin");
uint8_t pins[5];
uint8_t nPins = bus->getPins(pins);
for (int i = 0; i < nPins; i++) ins_pin.add(pins[i]);
ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->isReversed();
ins[F("skip")] = bus->skippedLeds();
ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbwm")] = bus->getAutoWhiteMode();
ins[F("freq")] = bus->getFrequency();
ins[F("maxpwr")] = bus->getMaxCurrent();
ins[F("ledma")] = bus->getLEDCurrent();
ins[F("text")] = bus->getCustomText();
}
JsonArray hw_com = hw.createNestedArray(F("com"));
const ColorOrderMap& com = BusManager::getColorOrderMap();
for (size_t s = 0; s < com.count(); s++) {
const ColorOrderMapEntry *entry = com.get(s);
if (!entry || !entry->len) break;
JsonObject co = hw_com.createNestedObject();
co["start"] = entry->start;
co["len"] = entry->len;
co[F("order")] = entry->colorOrder;
}
// button(s)
JsonObject hw_btn = hw.createNestedObject("btn");
hw_btn["max"] = WLED_MAX_BUTTONS; // just information about max number of buttons (not actually used)
hw_btn[F("pull")] = !disablePullUp;
JsonArray hw_btn_ins = hw_btn.createNestedArray("ins");
// configuration for all buttons
for (const auto &button : buttons) {
JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject();
hw_btn_ins_0["type"] = button.type;
JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin");
hw_btn_ins_0_pin.add(button.pin);
JsonArray hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros");
hw_btn_ins_0_macros.add(button.macroButton);
hw_btn_ins_0_macros.add(button.macroLongPress);
hw_btn_ins_0_macros.add(button.macroDoublePress);
}
hw_btn[F("tt")] = touchThreshold;
hw_btn["mqtt"] = buttonPublishMqtt;
JsonObject hw_ir = hw.createNestedObject("ir");
#ifndef WLED_DISABLE_INFRARED
hw_ir["pin"] = irPin;
hw_ir["type"] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
#endif
hw_ir["sel"] = irApplyToAllSelected;
JsonObject hw_relay = hw.createNestedObject(F("relay"));
hw_relay["pin"] = rlyPin;
hw_relay["rev"] = !rlyMde;
hw_relay[F("odrain")] = rlyOpenDrain;
hw[F("baud")] = serialBaud;
JsonObject hw_if = hw.createNestedObject(F("if"));
JsonArray hw_if_i2c = hw_if.createNestedArray("i2c-pin");
hw_if_i2c.add(i2c_sda);
hw_if_i2c.add(i2c_scl);
JsonArray hw_if_spi = hw_if.createNestedArray("spi-pin");
hw_if_spi.add(spi_mosi);
hw_if_spi.add(spi_sclk);
hw_if_spi.add(spi_miso);
//JsonObject hw_status = hw.createNestedObject("status");
//hw_status["pin"] = -1;
JsonObject light = root.createNestedObject(F("light"));
light[F("scale-bri")] = briMultiplier;
light[F("pal-mode")] = paletteBlend;
light[F("aseg")] = strip.autoSegments;
JsonObject light_gc = light.createNestedObject("gc");
light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility
light_gc["col"] = (gammaCorrectCol) ? gammaCorrectVal : 1.0f; // keep compatibility
light_gc["val"] = gammaCorrectVal;
JsonObject light_tr = light.createNestedObject("tr");
light_tr["dur"] = transitionDelayDefault / 100;
light_tr[F("rpc")] = randomPaletteChangeTime;
light_tr[F("hrp")] = useHarmonicRandomPalette;
JsonObject light_nl = light.createNestedObject("nl");
light_nl["mode"] = nightlightMode;
light_nl["dur"] = nightlightDelayMinsDefault;
light_nl[F("tbri")] = nightlightTargetBri;
light_nl["macro"] = macroNl;
JsonObject def = root.createNestedObject("def");
def["ps"] = bootPreset;
def["on"] = turnOnAtBoot;
def["bri"] = briS;
JsonObject interfaces = root.createNestedObject("if");
JsonObject if_sync = interfaces.createNestedObject("sync");
if_sync[F("port0")] = udpPort;
if_sync[F("port1")] = udpPort2;
#ifndef WLED_DISABLE_ESPNOW
if_sync[F("espnow")] = useESPNowSync;
#endif
JsonObject if_sync_recv = if_sync.createNestedObject(F("recv"));
if_sync_recv["bri"] = receiveNotificationBrightness;
if_sync_recv["col"] = receiveNotificationColor;
if_sync_recv["fx"] = receiveNotificationEffects;
if_sync_recv["pal"] = receiveNotificationPalette;
if_sync_recv["grp"] = receiveGroups;
if_sync_recv["seg"] = receiveSegmentOptions;
if_sync_recv["sb"] = receiveSegmentBounds;
JsonObject if_sync_send = if_sync.createNestedObject(F("send"));
if_sync_send["en"] = sendNotifications;
if_sync_send[F("dir")] = notifyDirect;
if_sync_send["btn"] = notifyButton;
if_sync_send["va"] = notifyAlexa;
if_sync_send["hue"] = notifyHue;
if_sync_send["grp"] = syncGroups;
if_sync_send["ret"] = udpNumRetries;
JsonObject if_nodes = interfaces.createNestedObject("nodes");
if_nodes[F("list")] = nodeListEnabled;
if_nodes[F("bcast")] = nodeBroadcastEnabled;
JsonObject if_live = interfaces.createNestedObject("live");
if_live["en"] = receiveDirect; // UDP/Hyperion realtime
if_live[F("mso")] = useMainSegmentOnly;
if_live[F("rlm")] = realtimeRespectLedMaps;
if_live["port"] = e131Port;
if_live[F("mc")] = e131Multicast;
JsonObject if_live_dmx = if_live.createNestedObject("dmx");
if_live_dmx[F("uni")] = e131Universe;
if_live_dmx[F("seqskip")] = e131SkipOutOfSequence;
if_live_dmx[F("e131prio")] = e131Priority;
if_live_dmx[F("addr")] = DMXAddress;
if_live_dmx[F("dss")] = DMXSegmentSpacing;
if_live_dmx["mode"] = DMXMode;
#ifdef WLED_ENABLE_DMX_INPUT
if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin;
if_live_dmx[F("inputTxPin")] = dmxInputReceivePin;
if_live_dmx[F("inputEnablePin")] = dmxInputEnablePin;
if_live_dmx[F("dmxInputPort")] = dmxInputPort;
#endif
if_live[F("timeout")] = realtimeTimeoutMs / 100;
if_live[F("maxbri")] = arlsForceMaxBri;
if_live[F("no-gc")] = arlsDisableGammaCorrection;
if_live[F("offset")] = arlsOffset;
#ifndef WLED_DISABLE_ALEXA
JsonObject if_va = interfaces.createNestedObject("va");
if_va[F("alexa")] = alexaEnabled;
JsonArray if_va_macros = if_va.createNestedArray("macros");
if_va_macros.add(macroAlexaOn);
if_va_macros.add(macroAlexaOff);
if_va["p"] = alexaNumPresets;
#endif
#ifndef WLED_DISABLE_MQTT
JsonObject if_mqtt = interfaces.createNestedObject("mqtt");
if_mqtt["en"] = mqttEnabled;
if_mqtt[F("broker")] = mqttServer;
if_mqtt["port"] = mqttPort;
if_mqtt[F("user")] = mqttUser;
if_mqtt[F("pskl")] = strlen(mqttPass);
if_mqtt[F("cid")] = mqttClientID;
if_mqtt[F("rtn")] = retainMqttMsg;
JsonObject if_mqtt_topics = if_mqtt.createNestedObject(F("topics"));
if_mqtt_topics[F("device")] = mqttDeviceTopic;
if_mqtt_topics[F("group")] = mqttGroupTopic;
#endif
#ifndef WLED_DISABLE_HUESYNC
JsonObject if_hue = interfaces.createNestedObject("hue");
if_hue["en"] = huePollingEnabled;
if_hue["id"] = huePollLightId;
if_hue[F("iv")] = huePollIntervalMs / 100;
JsonObject if_hue_recv = if_hue.createNestedObject(F("recv"));
if_hue_recv["on"] = hueApplyOnOff;
if_hue_recv["bri"] = hueApplyBri;
if_hue_recv["col"] = hueApplyColor;
JsonArray if_hue_ip = if_hue.createNestedArray("ip");
for (unsigned i = 0; i < 4; i++) {
if_hue_ip.add(hueIP[i]);
}
#endif
JsonObject if_ntp = interfaces.createNestedObject("ntp");
if_ntp["en"] = ntpEnabled;
if_ntp[F("host")] = ntpServerName;
if_ntp[F("tz")] = currentTimezone;
if_ntp[F("offset")] = utcOffsetSecs;
if_ntp[F("ampm")] = useAMPM;
if_ntp[F("ln")] = longitude;
if_ntp[F("lt")] = latitude;
JsonObject ol = root.createNestedObject("ol");
ol[F("clock")] = overlayCurrent;
ol[F("cntdwn")] = countdownMode;
ol["min"] = overlayMin;
ol[F("max")] = overlayMax;
ol[F("o12pix")] = analogClock12pixel;
ol[F("o5m")] = analogClock5MinuteMarks;
ol[F("osec")] = analogClockSecondsTrail;
ol[F("osb")] = analogClockSolidBlack;
JsonObject timers = root.createNestedObject(F("timers"));
JsonObject cntdwn = timers.createNestedObject(F("cntdwn"));
JsonArray goal = cntdwn.createNestedArray(F("goal"));
goal.add(countdownYear); goal.add(countdownMonth); goal.add(countdownDay);
goal.add(countdownHour); goal.add(countdownMin); goal.add(countdownSec);
cntdwn["macro"] = macroCountdown;
JsonArray timers_ins = timers.createNestedArray("ins");
for (unsigned i = 0; i < 10; i++) {
if (timerMacro[i] == 0 && timerHours[i] == 0 && timerMinutes[i] == 0) continue; // sunrise/sunset get saved always (timerHours=255)
JsonObject timers_ins0 = timers_ins.createNestedObject();
timers_ins0["en"] = (timerWeekday[i] & 0x01);
timers_ins0[F("hour")] = timerHours[i];
timers_ins0["min"] = timerMinutes[i];
timers_ins0["macro"] = timerMacro[i];
timers_ins0[F("dow")] = timerWeekday[i] >> 1;
if (i<8) {
JsonObject start = timers_ins0.createNestedObject("start");
start["mon"] = (timerMonth[i] >> 4) & 0xF;
start["day"] = timerDay[i];
JsonObject end = timers_ins0.createNestedObject("end");
end["mon"] = timerMonth[i] & 0xF;
end["day"] = timerDayEnd[i];
}
}
JsonObject ota = root.createNestedObject("ota");
ota[F("lock")] = otaLock;
ota[F("lock-wifi")] = wifiLock;
ota[F("pskl")] = strlen(otaPass);
#ifndef WLED_DISABLE_OTA
ota[F("aota")] = aOtaEnabled;
#endif
ota[F("same-subnet")] = otaSameSubnet;
#ifdef WLED_ENABLE_DMX
JsonObject dmx = root.createNestedObject("dmx");
dmx[F("chan")] = DMXChannels;
dmx[F("gap")] = DMXGap;
dmx["start"] = DMXStart;
dmx[F("start-led")] = DMXStartLED;
JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap"));
for (unsigned i = 0; i < 15; i++) {
dmx_fixmap.add(DMXFixtureMap[i]);
}
dmx[F("e131proxy")] = e131ProxyUniverse;
#endif
JsonObject usermods_settings = root.createNestedObject("um");
UsermodManager::addToConfig(usermods_settings);
}
static const char s_wsec_json[] PROGMEM = "/wsec.json";
//settings in /wsec.json, not accessible via webserver, for passwords and tokens
bool deserializeConfigSec() {
DEBUG_PRINTLN(F("Reading settings from /wsec.json..."));
if (!requestJSONBufferLock(JSON_LOCK_CFG_SEC_DES)) return false;
bool success = readObjectFromFile(s_wsec_json, nullptr, pDoc);
if (!success) {
releaseJSONBufferLock();
return false;
}
JsonObject root = pDoc->as<JsonObject>();
size_t n = 0;
JsonArray nw_ins = root["nw"]["ins"];
if (!nw_ins.isNull()) {
if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing
for (JsonObject wifi : nw_ins) {
char pw[65] = "";
getStringFromJson(pw, wifi["psk"], 65);
strlcpy(multiWiFi[n].clientPass, pw, 65);
if (++n >= WLED_MAX_WIFI_COUNT) break;
}
}
JsonObject ap = root["ap"];
getStringFromJson(apPass, ap["psk"] , 65);
[[maybe_unused]] JsonObject interfaces = root["if"];
#ifndef WLED_DISABLE_MQTT
JsonObject if_mqtt = interfaces["mqtt"];
getStringFromJson(mqttPass, if_mqtt["psk"], 65);
#endif
#ifndef WLED_DISABLE_HUESYNC
getStringFromJson(hueApiKey, interfaces["hue"][F("key")], 47);
#endif
getStringFromJson(settingsPIN, root["pin"], 5);
correctPIN = !strlen(settingsPIN);
JsonObject ota = root["ota"];
getStringFromJson(otaPass, ota[F("pwd")], 33);
CJSON(otaLock, ota[F("lock")]);
CJSON(wifiLock, ota[F("lock-wifi")]);
#ifndef WLED_DISABLE_OTA
CJSON(aOtaEnabled, ota[F("aota")]);
#endif
releaseJSONBufferLock();
return true;
}
void serializeConfigSec() {
DEBUG_PRINTLN(F("Writing settings to /wsec.json..."));
if (!requestJSONBufferLock(JSON_LOCK_CFG_SEC_SER)) return;
JsonObject root = pDoc->to<JsonObject>();
JsonObject nw = root.createNestedObject("nw");
JsonArray nw_ins = nw.createNestedArray("ins");
for (size_t i = 0; i < multiWiFi.size(); i++) {
JsonObject wifi = nw_ins.createNestedObject();
wifi[F("psk")] = multiWiFi[i].clientPass;
}
JsonObject ap = root.createNestedObject("ap");
ap["psk"] = apPass;
[[maybe_unused]] JsonObject interfaces = root.createNestedObject("if");
#ifndef WLED_DISABLE_MQTT
JsonObject if_mqtt = interfaces.createNestedObject("mqtt");
if_mqtt["psk"] = mqttPass;
#endif
#ifndef WLED_DISABLE_HUESYNC
JsonObject if_hue = interfaces.createNestedObject("hue");
if_hue[F("key")] = hueApiKey;
#endif
root["pin"] = settingsPIN;
JsonObject ota = root.createNestedObject("ota");
ota[F("pwd")] = otaPass;
ota[F("lock")] = otaLock;
ota[F("lock-wifi")] = wifiLock;
#ifndef WLED_DISABLE_OTA
ota[F("aota")] = aOtaEnabled;
#endif
File f = WLED_FS.open(FPSTR(s_wsec_json), "w");
if (f) serializeJson(root, f);
f.close();
releaseJSONBufferLock();
}