Merge branch 'dev' into sensor-info

Conflicts:
	usermods/Temperature/usermod_temperature.h
This commit is contained in:
Blaz Kristan 2022-01-01 12:57:36 +01:00
commit fe1e5aeebf
66 changed files with 6661 additions and 4663 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ node_modules
.idea .idea
.direnv .direnv
wled-update.sh wled-update.sh
esp01-update.sh

View File

@ -2,6 +2,23 @@
### Builds after release 0.12.0 ### Builds after release 0.12.0
#### Build 2112080
- Version bump to 0.13.0-b6 "Toki"
- Added "ESP02" (ESP8266 with 2M of flash) to PIO/release binaries
#### Build 2112070
- Added new effect "Fairy", replacing "Police All"
- Added new effect "Fairytwinkle", replacing "Two Areas"
- Static single JSON buffer (performance and stability improvement) (PR #2336)
#### Build 2112030
- Fixed ESP32 crash on Colortwinkles brightness change
- Fixed setting picker to black resetting hue and saturation
- Fixed auto white mode not saved to config
#### Build 2111300 #### Build 2111300
- Added CCT and white balance correction support (PR #2285) - Added CCT and white balance correction support (PR #2285)

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "wled", "name": "wled",
"version": "0.13.1-bl5", "version": "0.13.1-bl6",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "wled", "name": "wled",
"version": "0.13.1-bl5", "version": "0.13.1-bl6",
"description": "Tools for WLED project", "description": "Tools for WLED project",
"main": "tools/cdata.js", "main": "tools/cdata.js",
"directories": { "directories": {

View File

@ -12,7 +12,7 @@
; default_envs = travis_esp8266, travis_esp32 ; default_envs = travis_esp8266, travis_esp32
# Release binaries # Release binaries
default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth
# Build everything # Build everything
; default_envs = esp32dev, esp8285_4CH_MagicHome, esp8285_4CH_H801, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_5CH_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips ; default_envs = esp32dev, esp8285_4CH_MagicHome, esp8285_4CH_H801, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_5CH_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
@ -20,6 +20,7 @@ default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
# Single binaries (uncomment your board) # Single binaries (uncomment your board)
; default_envs = elekstube_ips ; default_envs = elekstube_ips
; default_envs = nodemcuv2 ; default_envs = nodemcuv2
; default_envs = esp8266_2m
; default_envs = esp01_1m_full ; default_envs = esp01_1m_full
; default_envs = esp07 ; default_envs = esp07
; default_envs = d1_mini ; default_envs = d1_mini
@ -53,14 +54,14 @@ extra_configs =
arduino_core_2_6_3 = espressif8266@2.3.3 arduino_core_2_6_3 = espressif8266@2.3.3
arduino_core_2_7_4 = espressif8266@2.6.2 arduino_core_2_7_4 = espressif8266@2.6.2
arduino_core_3_0_0 = espressif8266@3.0.0 arduino_core_3_0_0 = espressif8266@3.0.0
;arduino_core_3_2_0 = espressif8266@3.2.0 arduino_core_3_2_0 = espressif8266@3.2.0
# Development platforms # Development platforms
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage
# Platform to use for ESP8266 # Platform to use for ESP8266
platform_wled_default = ${common.arduino_core_2_7_4} platform_wled_default = ${common.arduino_core_3_2_0}
# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization # We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization
platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
platformio/toolchain-xtensa @ ~2.40802.200502 platformio/toolchain-xtensa @ ~2.40802.200502
@ -105,6 +106,7 @@ build_flags =
-DBEARSSL_SSL_BASIC -DBEARSSL_SSL_BASIC
-D CORE_DEBUG_LEVEL=0 -D CORE_DEBUG_LEVEL=0
-D NDEBUG -D NDEBUG
-Dregister=
#build_flags for the IRremoteESP8266 library (enabled decoders have to appear here) #build_flags for the IRremoteESP8266 library (enabled decoders have to appear here)
-D _IR_ENABLE_DEFAULT_=false -D _IR_ENABLE_DEFAULT_=false
-D DECODE_HASH=true -D DECODE_HASH=true
@ -242,6 +244,13 @@ build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full] [env:esp01_1m_full]
board = esp01_1m board = esp01_1m
platform = ${common.platform_wled_default} platform = ${common.platform_wled_default}

View File

@ -113,7 +113,7 @@ function writeHtmlGzipped(sourceFile, resultFile, page) {
console.info("Reading " + sourceFile); console.info("Reading " + sourceFile);
new inliner(sourceFile, function (error, html) { new inliner(sourceFile, function (error, html) {
console.info("Inlined " + html.length + " characters"); console.info("Inlined " + html.length + " characters");
html = filter(html.replace("</head>","<script src=\"iro.js\"></script><script src=\"rangetouch.js\"></script></head>"), "html-minify-ui"); html = filter(html, "html-minify-ui");
console.info("Minified to " + html.length + " characters"); console.info("Minified to " + html.length + " characters");
if (error) { if (error) {
@ -164,7 +164,8 @@ const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter(str, s.filter)}${
return s.mangle ? s.mangle(chunk) : chunk; return s.mangle ? s.mangle(chunk) : chunk;
} else if (s.method == "gzip") { } else if (s.method == "gzip") {
const buf = fs.readFileSync(srcDir + "/" + s.file); const buf = fs.readFileSync(srcDir + "/" + s.file);
const str = buf.toString('utf-8'); var str = buf.toString('utf-8');
if (s.mangle) str = s.mangle(str);
const zip = zlib.gzipSync(filter(str, s.filter), { level: zlib.constants.Z_BEST_COMPRESSION }); const zip = zlib.gzipSync(filter(str, s.filter), { level: zlib.constants.Z_BEST_COMPRESSION });
const result = hexdump(zip.toString('hex'), true); const result = hexdump(zip.toString('hex'), true);
const chunk = ` const chunk = `
@ -251,10 +252,11 @@ writeChunks(
{ {
file: "style.css", file: "style.css",
name: "PAGE_settingsCss", name: "PAGE_settingsCss",
prepend: "=====(<style>", method: "gzip",
append: "</style>)=====",
method: "plaintext",
filter: "css-minify", filter: "css-minify",
mangle: (str) =>
str
.replace("%%","%")
}, },
{ {
file: "settings.htm", file: "settings.htm",
@ -263,140 +265,102 @@ writeChunks(
append: ")=====", append: ")=====",
method: "plaintext", method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) =>
str
.replace("%", "%%")
.replace(/User Interface\<\/button\>\<\/form\>/gms, "User Interface\<\/button\>\<\/form\>%DMXMENU%"),
}, },
{ {
file: "settings_wifi.htm", file: "settings_wifi.htm",
name: "PAGE_settings_wifi", name: "PAGE_settings_wifi",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) => mangle: (str) =>
str str
.replace(/\<link rel="stylesheet".*\>/gms, "")
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
.replace( .replace(
/function GetV().*\<\/script\>/gms, /function GetV().*\<\/script\>/gms,
"function GetV() {var d=document;\n" "</script><script src=\"settings.js?p=1\"></script>"
), )
}, },
{ {
file: "settings_leds.htm", file: "settings_leds.htm",
name: "PAGE_settings_leds", name: "PAGE_settings_leds",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) => mangle: (str) =>
str str
.replace(/\<link rel="stylesheet".*\>/gms, "")
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
.replace( .replace(
/function GetV().*\<\/script\>/gms, /function GetV().*\<\/script\>/gms,
"function GetV() {var d=document;\n" "</script><script src=\"settings.js?p=2\"></script>"
), )
}, },
{ {
file: "settings_dmx.htm", file: "settings_dmx.htm",
name: "PAGE_settings_dmx", name: "PAGE_settings_dmx",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) => { mangle: (str) =>
const nocss = str str
.replace(/\<link rel="stylesheet".*\>/gms, "")
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
.replace( .replace(
/function GetV().*\<\/script\>/gms, /function GetV().*\<\/script\>/gms,
"function GetV() {var d=document;\n" "</script><script src=\"settings.js?p=7\"></script>"
); )
return `
#ifdef WLED_ENABLE_DMX
${nocss}
#else
const char PAGE_settings_dmx[] PROGMEM = R"=====()=====";
#endif
`;
},
}, },
{ {
file: "settings_ui.htm", file: "settings_ui.htm",
name: "PAGE_settings_ui", name: "PAGE_settings_ui",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) => mangle: (str) =>
str str
.replace(/\<link rel="stylesheet".*\>/gms, "")
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
.replace( .replace(
/function GetV().*\<\/script\>/gms, /function GetV().*\<\/script\>/gms,
"function GetV() {var d=document;\n" "</script><script src=\"settings.js?p=3\"></script>"
), )
}, },
{ {
file: "settings_sync.htm", file: "settings_sync.htm",
name: "PAGE_settings_sync", name: "PAGE_settings_sync",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) => mangle: (str) =>
str str
.replace(/\<link rel="stylesheet".*\>/gms, "") .replace(
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%") /function GetV().*\<\/script\>/gms,
.replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"), "</script><script src=\"settings.js?p=4\"></script>"
)
}, },
{ {
file: "settings_time.htm", file: "settings_time.htm",
name: "PAGE_settings_time", name: "PAGE_settings_time",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) => mangle: (str) =>
str str
.replace(/\<link rel="stylesheet".*\>/gms, "") .replace(
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%") /function GetV().*\<\/script\>/gms,
.replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"), "</script><script src=\"settings.js?p=5\"></script>"
)
}, },
{ {
file: "settings_sec.htm", file: "settings_sec.htm",
name: "PAGE_settings_sec", name: "PAGE_settings_sec",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) => mangle: (str) =>
str str
.replace(/\<link rel="stylesheet".*\>/gms, "")
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
.replace( .replace(
/function GetV().*\<\/script\>/gms, /function GetV().*\<\/script\>/gms,
"function GetV() {var d=document;\n" "</script><script src=\"settings.js?p=6\"></script>"
), )
}, },
{ {
file: "settings_um.htm", file: "settings_um.htm",
name: "PAGE_settings_um", name: "PAGE_settings_um",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) => mangle: (str) =>
str str
.replace(/\<link rel="stylesheet".*\>/gms, "")
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
.replace( .replace(
/function GetV().*\<\/script\>/gms, /function GetV().*\<\/script\>/gms,
"function GetV() {var d=document;\n" "</script><script src=\"settings.js?p=8\"></script>"
), )
} }
], ],
"wled00/html_settings.h" "wled00/html_settings.h"
@ -408,9 +372,7 @@ writeChunks(
{ {
file: "usermod.htm", file: "usermod.htm",
name: "PAGE_usermod", name: "PAGE_usermod",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
mangle: (str) => mangle: (str) =>
str.replace(/fetch\("http\:\/\/.*\/win/gms, 'fetch("/win'), str.replace(/fetch\("http\:\/\/.*\/win/gms, 'fetch("/win'),
@ -442,41 +404,31 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
{ {
file: "update.htm", file: "update.htm",
name: "PAGE_update", name: "PAGE_update",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
}, },
{ {
file: "welcome.htm", file: "welcome.htm",
name: "PAGE_welcome", name: "PAGE_welcome",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
}, },
{ {
file: "liveview.htm", file: "liveview.htm",
name: "PAGE_liveview", name: "PAGE_liveview",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
}, },
{ {
file: "liveviewws.htm", file: "liveviewws.htm",
name: "PAGE_liveviewws", name: "PAGE_liveviewws",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
}, },
{ {
file: "404.htm", file: "404.htm",
name: "PAGE_404", name: "PAGE_404",
prepend: "=====(", method: "gzip",
append: ")=====",
method: "plaintext",
filter: "html-minify", filter: "html-minify",
}, },
{ {

View File

@ -118,7 +118,8 @@ private:
*/ */
void switchStrip(bool switchOn) void switchStrip(bool switchOn)
{ {
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing
if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing
PIRtriggered = switchOn; PIRtriggered = switchOn;
if (switchOn) { if (switchOn) {
if (m_onPreset) { if (m_onPreset) {
@ -188,10 +189,12 @@ private:
if (sensorPinState == HIGH) { if (sensorPinState == HIGH) {
offTimerStart = 0; offTimerStart = 0;
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
publishMqtt("on"); publishMqtt("on");
} else /*if (bri != 0)*/ { } else {
// start switch off timer // start switch off timer
offTimerStart = millis(); offTimerStart = millis();
if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
} }
return true; return true;
} }
@ -203,14 +206,13 @@ private:
*/ */
bool handleOffTimer() bool handleOffTimer()
{ {
if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) {
{ offTimerStart = 0;
if (enabled == true) if (enabled == true) {
{
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false); if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
publishMqtt("off"); publishMqtt("off");
} }
offTimerStart = 0;
return true; return true;
} }
return false; return false;

View File

@ -148,7 +148,7 @@ class PWMFanUsermod : public Usermod {
int pwmStep = ((100 - minPWMValuePct) * newPWMvalue) / (7*100); int pwmStep = ((100 - minPWMValuePct) * newPWMvalue) / (7*100);
int pwmMinimumValue = (minPWMValuePct * newPWMvalue) / 100; int pwmMinimumValue = (minPWMValuePct * newPWMvalue) / 100;
if ((temp == NAN) || (temp <= 0.0)) { if ((temp == NAN) || (temp <= -100.0)) {
DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255.")); DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255."));
} else if (difftemp <= 0.0) { } else if (difftemp <= 0.0) {
// Temperature is below target temperature. Run fan at minimum speed. // Temperature is below target temperature. Run fan at minimum speed.

View File

@ -123,6 +123,11 @@ public:
} }
} }
uint16_t getLastLDRValue()
{
return lastLDRValue;
}
void addToJsonInfo(JsonObject &root) void addToJsonInfo(JsonObject &root)
{ {
JsonObject user = root[F("u")]; JsonObject user = root[F("u")];

View File

@ -37,13 +37,12 @@ class UsermodTemperature : public Usermod {
// used to determine when we can read the sensors temperature // used to determine when we can read the sensors temperature
// we have to wait at least 93.75 ms after requestTemperatures() is called // we have to wait at least 93.75 ms after requestTemperatures() is called
unsigned long lastTemperaturesRequest; unsigned long lastTemperaturesRequest;
float temperature = -100.f; // default to -100, DS18B20 only goes down to -50C float temperature = -127.0f; // default to -127, DS18B20 only goes down to -50C
bool errorReading = false;
// indicates requestTemperatures has been called but the sensor measurement is not complete // indicates requestTemperatures has been called but the sensor measurement is not complete
bool waitingForConversion = false; bool waitingForConversion = false;
// flag set at startup if DS18B20 sensor not found, avoids trying to keep getting // flag set at startup if DS18B20 sensor not found, avoids trying to keep getting
// temperature if flashed to a board without a sensor attached // temperature if flashed to a board without a sensor attached
bool sensorFound = false; byte sensorFound;
bool enabled = true; bool enabled = true;
@ -55,27 +54,47 @@ class UsermodTemperature : public Usermod {
//Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013
float readDallas() { float readDallas() {
byte i; byte data[9];
byte data[2];
int16_t result; // raw data from sensor int16_t result; // raw data from sensor
if (!oneWire->reset()) return -127.0f; // send reset command and fail fast float retVal = -127.0f;
if (oneWire->reset()) { // if reset() fails there are no OneWire devices
oneWire->skip(); // skip ROM oneWire->skip(); // skip ROM
oneWire->write(0xBE); // read (temperature) from EEPROM oneWire->write(0xBE); // read (temperature) from EEPROM
for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature oneWire->read_bytes(data, 9); // first 2 bytes contain temperature
for (i=2; i < 8; i++) oneWire->read(); // read unused bytes #ifdef WLED_DEBUG
if (OneWire::crc8(data,8) != data[8]) {
DEBUG_PRINTLN(F("CRC error reading temperature."));
for (byte i=0; i < 9; i++) DEBUG_PRINTF("0x%02X ", data[i]);
DEBUG_PRINT(F(" => "));
DEBUG_PRINTF("0x%02X\n", OneWire::crc8(data,8));
}
#endif
switch(sensorFound) {
case 0x10: // DS18S20 has 9-bit precision
result = (data[1] << 8) | data[0];
retVal = float(result) * 0.5f;
break;
case 0x22: // DS18B20
case 0x28: // DS1822
case 0x3B: // DS1825
case 0x42: // DS28EA00
result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning
if (data[1]&0x80) result |= 0xFF00; // fix negative value if (data[1] & 0x80) result |= 0xF000; // fix negative value
oneWire->reset(); retVal = float(result) + ((data[0] & 0x08) ? 0.5f : 0.0f);
oneWire->skip(); // skip ROM break;
oneWire->write(0x44,parasite); // request new temperature reading (without parasite power) }
return (float)result + ((data[0]&0x0008) ? 0.5f : 0.0f); }
for (byte i=1; i<9; i++) data[0] &= data[i];
return data[0]==0xFF ? -127.0f : retVal;
} }
void requestTemperatures() { void requestTemperatures() {
readDallas(); DEBUG_PRINTLN(F("Requesting temperature."));
oneWire->reset();
oneWire->skip(); // skip ROM
oneWire->write(0x44,parasite); // request new temperature reading (TODO: parasite would need special handling)
lastTemperaturesRequest = millis(); lastTemperaturesRequest = millis();
waitingForConversion = true; waitingForConversion = true;
DEBUG_PRINTLN(F("Requested temperature."));
} }
void readTemperature() { void readTemperature() {
@ -110,6 +129,8 @@ class UsermodTemperature : public Usermod {
case 0x3B: // DS1825 case 0x3B: // DS1825
case 0x42: // DS28EA00 case 0x42: // DS28EA00
DEBUG_PRINTLN(F("Sensor found.")); DEBUG_PRINTLN(F("Sensor found."));
sensorFound = deviceAddress[0];
DEBUG_PRINTF("0x%02X\n", sensorFound);
return true; return true;
} }
} }
@ -121,16 +142,15 @@ class UsermodTemperature : public Usermod {
void setup() { void setup() {
int retries = 10; int retries = 10;
sensorFound = 0;
if (enabled) { if (enabled) {
// config says we are enabled // config says we are enabled
DEBUG_PRINTLN(F("Allocating temperature pin...")); DEBUG_PRINTLN(F("Allocating temperature pin..."));
// pin retrieved from cfg.json (readFromConfig()) prior to running setup() // pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) {
oneWire = new OneWire(temperaturePin); oneWire = new OneWire(temperaturePin);
if (!oneWire->reset()) { if (oneWire->reset()) {
sensorFound = false; // resetting 1-Wire bus yielded an error while (!findSensor() && retries--) {
} else {
while ((sensorFound=findSensor()) && retries--) {
delay(25); // try to find sensor delay(25); // try to find sensor
} }
} }
@ -139,7 +159,6 @@ class UsermodTemperature : public Usermod {
DEBUG_PRINTLN(F("Temperature pin allocation failed.")); DEBUG_PRINTLN(F("Temperature pin allocation failed."));
} }
temperaturePin = -1; // allocation failed temperaturePin = -1; // allocation failed
sensorFound = false;
} }
} }
lastMeasurement = millis() - readingInterval + 10000; lastMeasurement = millis() - readingInterval + 10000;
@ -164,20 +183,20 @@ class UsermodTemperature : public Usermod {
} }
// we were waiting for a conversion to complete, have we waited log enough? // we were waiting for a conversion to complete, have we waited log enough?
if (now - lastTemperaturesRequest >= 100 /* 93.75ms per the datasheet but can be up to 750ms */) { if (now - lastTemperaturesRequest >= 750 /* 93.75ms per the datasheet but can be up to 750ms */) {
readTemperature(); readTemperature();
if (WLED_MQTT_CONNECTED) { if (WLED_MQTT_CONNECTED) {
char subuf[64]; char subuf[64];
strcpy(subuf, mqttDeviceTopic); strcpy(subuf, mqttDeviceTopic);
if (-100 <= temperature) { if (temperature > -100.0f) {
// dont publish super low temperature as the graph will get messed up // dont publish super low temperature as the graph will get messed up
// the DallasTemperature library returns -127C or -196.6F when problem // the DallasTemperature library returns -127C or -196.6F when problem
// reading the sensor // reading the sensor
strcat_P(subuf, PSTR("/temperature")); strcat_P(subuf, PSTR("/temperature"));
mqtt->publish(subuf, 0, false, String(temperature).c_str()); mqtt->publish(subuf, 0, false, String(getTemperatureC()).c_str());
strcat_P(subuf, PSTR("_f")); strcat_P(subuf, PSTR("_f"));
mqtt->publish(subuf, 0, false, String((float)temperature * 1.8f + 32).c_str()); mqtt->publish(subuf, 0, false, String(getTemperatureF()).c_str());
} else { } else {
// publish something else to indicate status? // publish something else to indicate status?
} }
@ -209,7 +228,7 @@ class UsermodTemperature : public Usermod {
JsonArray temp = user.createNestedArray(FPSTR(_name)); JsonArray temp = user.createNestedArray(FPSTR(_name));
if (temperature <= -100.0 || errorReading) { if (temperature <= -100.0f) {
temp.add(0); temp.add(0);
temp.add(F(" Sensor Error!")); temp.add(F(" Sensor Error!"));
return; return;

View File

@ -45,6 +45,9 @@ class MultiRelay : public Usermod {
// status of initialisation // status of initialisation
bool initDone = false; bool initDone = false;
uint16_t periodicBroadcastSec = 60;
unsigned long lastBroadcast = 0;
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
static const char _name[]; static const char _name[];
static const char _enabled[]; static const char _enabled[];
@ -53,7 +56,7 @@ class MultiRelay : public Usermod {
static const char _activeHigh[]; static const char _activeHigh[];
static const char _external[]; static const char _external[];
static const char _button[]; static const char _button[];
static const char _broadcast[];
void publishMqtt(const char* state, int relay) { void publishMqtt(const char* state, int relay) {
//Check if MQTT Connected, otherwise it will crash the 8266 //Check if MQTT Connected, otherwise it will crash the 8266
@ -68,15 +71,19 @@ class MultiRelay : public Usermod {
* switch off the strip if the delay has elapsed * switch off the strip if the delay has elapsed
*/ */
void handleOffTimer() { void handleOffTimer() {
unsigned long now = millis();
bool activeRelays = false; bool activeRelays = false;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].active && _switchTimerStart > 0 && millis() - _switchTimerStart > (_relay[i].delay*1000)) { if (_relay[i].active && _switchTimerStart > 0 && now - _switchTimerStart > (_relay[i].delay*1000)) {
if (!_relay[i].external) toggleRelay(i); if (!_relay[i].external) toggleRelay(i);
_relay[i].active = false; _relay[i].active = false;
} else if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) {
if (_relay[i].pin>=0) publishMqtt(_relay[i].state ? "on" : "off", i);
} }
activeRelays = activeRelays || _relay[i].active; activeRelays = activeRelays || _relay[i].active;
} }
if (!activeRelays) _switchTimerStart = 0; if (!activeRelays) _switchTimerStart = 0;
if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) lastBroadcast = now;
} }
/** /**
@ -266,7 +273,7 @@ class MultiRelay : public Usermod {
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) { if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
_relay[i].pin = -1; // allocation failed _relay[i].pin = -1; // allocation failed
} else { } else {
if (!_relay[i].external) _relay[i].state = offMode; if (!_relay[i].external) _relay[i].state = !offMode;
switchRelay(i, _relay[i].state); switchRelay(i, _relay[i].state);
_relay[i].active = false; _relay[i].active = false;
} }
@ -477,6 +484,7 @@ class MultiRelay : public Usermod {
JsonObject top = root.createNestedObject(FPSTR(_name)); JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled; top[FPSTR(_enabled)] = enabled;
top[FPSTR(_broadcast)] = periodicBroadcastSec;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
String parName = FPSTR(_relay_str); parName += '-'; parName += i; String parName = FPSTR(_relay_str); parName += '-'; parName += i;
JsonObject relay = top.createNestedObject(parName); JsonObject relay = top.createNestedObject(parName);
@ -506,6 +514,8 @@ class MultiRelay : public Usermod {
} }
enabled = top[FPSTR(_enabled)] | enabled; enabled = top[FPSTR(_enabled)] | enabled;
periodicBroadcastSec = top[FPSTR(_broadcast)] | periodicBroadcastSec;
periodicBroadcastSec = min(900,max(0,(int)periodicBroadcastSec));
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
String parName = FPSTR(_relay_str); parName += '-'; parName += i; String parName = FPSTR(_relay_str); parName += '-'; parName += i;
@ -539,7 +549,9 @@ class MultiRelay : public Usermod {
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) { if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
if (!_relay[i].external) { if (!_relay[i].external) {
switchRelay(i, offMode); _relay[i].state = !offMode;
switchRelay(i, _relay[i].state);
_oldMode = offMode;
} }
} else { } else {
_relay[i].pin = -1; _relay[i].pin = -1;
@ -549,7 +561,7 @@ class MultiRelay : public Usermod {
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[F("relay-0")][FPSTR(_button)].isNull(); return !top[FPSTR(_broadcast)].isNull();
} }
/** /**
@ -570,3 +582,4 @@ const char MultiRelay::_delay_str[] PROGMEM = "delay-s";
const char MultiRelay::_activeHigh[] PROGMEM = "active-high"; const char MultiRelay::_activeHigh[] PROGMEM = "active-high";
const char MultiRelay::_external[] PROGMEM = "external"; const char MultiRelay::_external[] PROGMEM = "external";
const char MultiRelay::_button[] PROGMEM = "button"; const char MultiRelay::_button[] PROGMEM = "button";
const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec";

View File

@ -61,10 +61,10 @@ class QuinLEDAnPentaUsermod : public Usermod
float shtLastKnownHumidity = 0; float shtLastKnownHumidity = 0;
// Pin/IO vars // Pin/IO vars
const int8_t anPentaPins[5] = {14, 13, 12, 4, 2}; const int8_t anPentaLEDPins[5] = {14, 13, 12, 4, 2};
int8_t oledSpiClk = 15; int8_t oledSpiClk = 15;
int8_t oledSpiData = 16; int8_t oledSpiData = 16;
int8_t oledSpiCs = 0; int8_t oledSpiCs = 27;
int8_t oledSpiDc = 32; int8_t oledSpiDc = 32;
int8_t oledSpiRst = 33; int8_t oledSpiRst = 33;
int8_t shtSda = 1; int8_t shtSda = 1;
@ -75,7 +75,7 @@ class QuinLEDAnPentaUsermod : public Usermod
{ {
for(int8_t i = 0; i <= 4; i++) for(int8_t i = 0; i <= 4; i++)
{ {
if(anPentaPins[i] == pin) if(anPentaLEDPins[i] == pin)
return true; return true;
} }
return false; return false;
@ -313,7 +313,7 @@ class QuinLEDAnPentaUsermod : public Usermod
byte drawnLines = 0; byte drawnLines = 0;
for (int8_t app = 0; app <= 4; app++) { for (int8_t app = 0; app <= 4; app++) {
for (int8_t clp = 0; clp <= 4; clp++) { for (int8_t clp = 0; clp <= 4; clp++) {
if (anPentaPins[app] == currentLedPins[clp]) { if (anPentaLEDPins[app] == currentLedPins[clp]) {
char charCurrentLedcReads[17]; char charCurrentLedcReads[17];
sprintf(charCurrentLedcReads, "LED %d:", app+1); sprintf(charCurrentLedcReads, "LED %d:", app+1);
if (oledUseProgressBars) { if (oledUseProgressBars) {

View File

@ -1,12 +1,12 @@
# QuinLED-An-Penta # QuinLED-An-Penta
The (un)official usermod to get the best out of the QuinLED-An-Penta, like using the OLED and the SHT30 temperature/humidity sensor. The (un)official usermod to get the best out of the QuinLED-An-Penta (https://quinled.info/quinled-an-penta/), like using the OLED and the SHT30 temperature/humidity sensor.
## Requirements ## Requirements
* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2 * "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2
* "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85 * "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85
## Usermod installation ## Usermod installation
Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one and add the buildflag `-D QUINLED_AN_PENTA`. Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one, add the buildflag `-D QUINLED_AN_PENTA` and the below library dependencies.
ESP32 (**without** ethernet): ESP32 (**without** ethernet):
``` ```
@ -33,14 +33,19 @@ This mod has been optimized for an SSD1306 driven 128x64 OLED. Using a smaller O
I highly recommend using these "two color monochromatic OLEDs", which have the first 16 pixels in a different color than the other 48, e.g. a yellow/blue OLED. I highly recommend using these "two color monochromatic OLEDs", which have the first 16 pixels in a different color than the other 48, e.g. a yellow/blue OLED.
Also note, you need to have an **SPI** driven OLED, **not i2c**! Also note, you need to have an **SPI** driven OLED, **not i2c**!
### Limitations combined with Ethernet
The initial development of this mod had been done with a beta version of the QuinLED-An-Penta, which had a different IO layout for the OLED: The CS pin used to be IO_0, but has been changed to IO27 with the first v1 public release. Unfortunately, IO27 is used by the Ethernet boards, so WLED will not let you enable the OLED screen, if you're using it with Ethernet. This unfortunately makes the development I've done to support/show Ethernet information void, as it cannot be used.
However (and I've not tried this, as I don't own a v1 board): You can try to modify this mod and try to use IO27 for the OLED and share it with the Ethernet board. It is "just" the chip select pin, so there is a chance that both can coexist and use the same IO. You need to skip WLEDs PinManager for the CS pin, so WLED will not block using it. If you don't know how this works: Leave it. If you know what I'm talking about: Try it and please let me know on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
### My OLED flickers after some time, what should I do? ### My OLED flickers after some time, what should I do?
That's a tricky one: During development I saw that the OLED sometimes starts to "bug out" / flicker and won't work anymore. This seems to be caused by the high PWM interference the board produces. It seems to loose it's settings and then doesn't know how to draw anymore. Turns out the only way to fix this is to call the libraries `begin()` method again which will re-initialize the display. That's a tricky one: During development I saw that the OLED sometimes starts to "bug out" / flicker and won't work anymore. This seems to be caused by the high PWM interference the board produces. It seems to loose its settings and then doesn't know how to draw anymore. Turns out the only way to fix this is to call the libraries `begin()` method again which will re-initialize the display.
If you're facing this issue, you can enable a setting I've added which will call the `begin()` roughly every 60 seconds between a page change. This will make the page change take ~500ms, but will fix the display. If you're facing this issue, you can enable a setting I've added which will call the `begin()` roughly every 60 seconds between a page change. This will make the page change take ~500ms, but will fix the display.
## Configuration ## Configuration
Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D QUINLED_AN_PENTA`, you will see the config for it there: Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D QUINLED_AN_PENTA`, you will see the config for it there:
* Enable-OLED: * Enable-OLED:
* What it does: Enabled the optional SPI driven OLED that can be mounted to the 7-pin female header * What it does: Enables the optional SPI driven OLED that can be mounted to the 7-pin female header. Won't work with Ethernet, read above.
* Possible values: Enabled/Disabled * Possible values: Enabled/Disabled
* Default: Disabled * Default: Disabled
* OLED-Use-Progress-Bars: * OLED-Use-Progress-Bars:
@ -60,10 +65,15 @@ Navigate to the "Config" and then to the "Usermods" section. If you compiled WLE
* Possible values: Enabled/Disabled * Possible values: Enabled/Disabled
* Default: Disabled * Default: Disabled
* Enable-SHT30-Temp-Humidity-Sensor: * Enable-SHT30-Temp-Humidity-Sensor:
* What it does: Enabled the onboard SHT30 temperature and humidity sensor * What it does: Enables the onboard SHT30 temperature and humidity sensor
* Possible values: Enabled/Disabled * Possible values: Enabled/Disabled
* Default: Disabled * Default: Disabled
## Change log ## Change log
2021-12
* Adjusted IO layout to match An-Penta v1r1
2021-10 2021-10
* First implementation. * First implementation.
## Credits
ezcGman | Andy: Find me on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG

View File

@ -0,0 +1,129 @@
# Seven Segment Display Reloaded
Usermod that uses the overlay feature to create a configurable seven segment display.
Optimized for maximum configurability and use with seven segment clocks by parallyze (https://www.instructables.com/member/parallyze/instructables/)
Very loosely based on the existing usermod "seven segment display".
## Installation
Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SSDR` in `my_config.h`.
For the auto brightness option, the usermod SN_Photoresistor has to be installed as well. See SN_Photoresistor/readme.md for instructions.
## Settings
All settings can be controlled the usermod setting page.
Part of the settings can be controlled through MQTT with a raw payload or through a json request to /json/state.
### enabled
Enables/disables this overlay usermod
### inverted
Enables the inverted mode in which the background should be enabled and the digits should be black (leds off)
### Colon-blinking
Enables the blinking colon(s) if they are defined
### enable-auto-brightness
Enables the auto brightness feature. Can be only used with the usermod SN_Photoresistor installed.
### auto-brightness-min / auto-brightness-max
The lux value calculated from usermod SN_Photoresistor will be mapped to the values defined here.
The mapping is 0 - 1000 lux will be mapped to auto-brightness-min - auto-brightness-max
The mA current protection of WLED will override the calculated value if it is too high.
### Display-Mask
Defines the type of the time/date display.
For example "H:m" (default)
- H - 00-23 hours
- h - 01-12 hours
- k - 01-24 hours
- m - 00-59 minutes
- s - 00-59 seconds
- d - 01-31 day of month
- M - 01-12 month
- y - 21 last two positions of year
- Y - 2021 year
- : for a colon
### LED-Numbers
- LED-Numbers-Hours
- LED-Numbers-Minutes
- LED-Numbers-Seconds
- LED-Numbers-Colons
- LED-Numbers-Day
- LED-Numbers-Month
- LED-Numbers-Year
See following example for usage.
## Example
Example for Leds definition
```
< A >
/\ /\
F B
\/ \/
< G >
/\ /\
E C
\/ \/
< D >
```
Leds or Range of Leds are seperated by a comma ","
Segments are seperated by a semicolon ";" and are read as A;B;C;D;E;F;G
Digits are seperated by colon ":" -> A;B;C;D;E;F;G:A;B;C;D;E;F;G
Ranges are defined as lower to higher (lower first)
For example, an clock definition for the following clock (https://www.instructables.com/Lazy-7-Quick-Build-Edition/) is
- hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
- minute "37-38;39-40;42-43;44,31;32-33;35-36;34,41:21-22;23-24;26-27;28,15;16-17;19-20;18,25"
or
- hour "6,7;8,9;11,12;13,0;1,2;4,5;3,10:52,53;54,55;57,58;59,46;47,48;50,51;49,56"
- minute "15,28;16,17;19,20;21,22;23,24;26,27;18,25:31,44;32,33;35,36;37,38;39,40;42,43;34,41"
depending on the orientation.
# The example detailed:
hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
there are two digits seperated by ":"
- 59,46;47-48;50-51;52-53;54-55;57-58;49,56
- 0,13;1-2;4-5;6-7;8-9;11-12;3,10
In the first digit,
the **segment A** consists of the leds number **59 and 46**., **segment B** consists of the leds number **47, 48** and so on
The second digit starts again with **segment A** and leds **0 and 13**, **segment B** consists of the leds number **1 and 2** and so on
### first digit of the hour
- Segment A: 59, 46
- Segment B: 47, 48
- Segment C: 50, 51
- Segment D: 52, 53
- Segment E: 54, 55
- Segment F: 57, 58
- Segment G: 49, 56
### second digit of the hour
- Segment A: 0, 13
- Segment B: 1, 2
- Segment C: 4, 5
- Segment D: 6, 7
- Segment E: 8, 9
- Segment F: 11, 12
- Segment G: 3, 10

View File

@ -0,0 +1,544 @@
#pragma once
#include "wled.h"
class UsermodSSDR : public Usermod {
//#define REFRESHTIME 497
private:
//Runtime variables.
unsigned long umSSDRLastRefresh = 0;
unsigned long umSSDRResfreshTime = 3000;
bool umSSDRDisplayTime = false;
bool umSSDRInverted = false;
bool umSSDRColonblink = true;
bool umSSDREnableLDR = false;
String umSSDRHours = "";
String umSSDRMinutes = "";
String umSSDRSeconds = "";
String umSSDRColons = "";
String umSSDRDays = "";
String umSSDRMonths = "";
String umSSDRYears = "";
uint16_t umSSDRLength = 0;
uint16_t umSSDRBrightnessMin = 0;
uint16_t umSSDRBrightnessMax = 255;
bool* umSSDRMask = 0;
/*// H - 00-23 hours
// h - 01-12 hours
// k - 01-24 hours
// m - 00-59 minutes
// s - 00-59 seconds
// d - 01-31 day of month
// M - 01-12 month
// y - 21 last two positions of year
// Y - 2021 year
// : for a colon
*/
String umSSDRDisplayMask = "H:m"; //This should reflect physical equipment.
/* Segment order, seen from the front:
< A >
/\ /\
F B
\/ \/
< G >
/\ /\
E C
\/ \/
< D >
*/
uint8_t umSSDRNumbers[11][7] = {
// A B C D E F G
{ 1, 1, 1, 1, 1, 1, 0 }, // 0
{ 0, 1, 1, 0, 0, 0, 0 }, // 1
{ 1, 1, 0, 1, 1, 0, 1 }, // 2
{ 1, 1, 1, 1, 0, 0, 1 }, // 3
{ 0, 1, 1, 0, 0, 1, 1 }, // 4
{ 1, 0, 1, 1, 0, 1, 1 }, // 5
{ 1, 0, 1, 1, 1, 1, 1 }, // 6
{ 1, 1, 1, 0, 0, 0, 0 }, // 7
{ 1, 1, 1, 1, 1, 1, 1 }, // 8
{ 1, 1, 1, 1, 0, 1, 1 } // 9
};
//String to reduce flash memory usage
static const char _str_name[];
static const char _str_ldrEnabled[];
static const char _str_timeEnabled[];
static const char _str_inverted[];
static const char _str_colonblink[];
static const char _str_displayMask[];
static const char _str_hours[];
static const char _str_minutes[];
static const char _str_seconds[];
static const char _str_colons[];
static const char _str_days[];
static const char _str_months[];
static const char _str_years[];
static const char _str_minBrightness[];
static const char _str_maxBrightness[];
#ifdef USERMOD_ID_SN_PHOTORESISTOR
Usermod_SN_Photoresistor *ptr;
#else
void* ptr = nullptr;
#endif
void _overlaySevenSegmentDraw() {
int displayMaskLen = static_cast<int>(umSSDRDisplayMask.length());
bool colonsDone = false;
_setAllFalse();
for (int index = 0; index < displayMaskLen; index++) {
int timeVar = 0;
switch (umSSDRDisplayMask[index]) {
case 'h':
timeVar = hourFormat12(localTime);
_showElements(&umSSDRHours, timeVar, 0);
break;
case 'H':
timeVar = hour(localTime);
_showElements(&umSSDRHours, timeVar, 0);
break;
case 'k':
timeVar = hour(localTime) + 1;
_showElements(&umSSDRHours, timeVar, 0);
break;
case 'm':
timeVar = minute(localTime);
_showElements(&umSSDRMinutes, timeVar, 0);
break;
case 's':
timeVar = second(localTime);
_showElements(&umSSDRSeconds, timeVar, 0);
break;
case 'd':
timeVar = day(localTime);
_showElements(&umSSDRDays, timeVar, 0);
break;
case 'M':
timeVar = month(localTime);
_showElements(&umSSDRMonths, timeVar, 0);
break;
case 'y':
timeVar = second(localTime);
_showElements(&umSSDRYears, timeVar, 0);
break;
case 'Y':
timeVar = year(localTime);
_showElements(&umSSDRYears, timeVar, 0);
break;
case ':':
if (!colonsDone) { // only call _setColons once as all colons are printed when the first colon is found
_setColons();
colonsDone = true;
}
break;
}
}
_setMaskToLeds();
}
void _setColons() {
if ( umSSDRColonblink ) {
if ( second(localTime) % 2 == 0 ) {
_showElements(&umSSDRColons, 0, 1);
}
} else {
_showElements(&umSSDRColons, 0, 1);
}
}
void _showElements(String *map, int timevar, bool isColon) {
if (!(*map).equals("") && !(*map) == NULL) {
int length = String(timevar).length();
bool addZero = false;
if (length == 1) {
length = 2;
addZero = true;
}
int timeArr[length];
if(addZero) {
timeArr[1] = 0;
timeArr[0] = timevar;
} else {
int count = 0;
while (timevar) {
timeArr[count] = timevar%10;
timevar /= 10;
count++;
};
}
int colonsLen = static_cast<int>((*map).length());
int count = 0;
int countSegments = 0;
int countDigit = 0;
bool range = false;
int lastSeenLedNr = 0;
for (int index = 0; index < colonsLen; index++) {
switch ((*map)[index]) {
case '-':
lastSeenLedNr = _checkForNumber(count, index, map);
count = 0;
range = true;
break;
case ':':
_setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
count = 0;
range = false;
countDigit++;
countSegments = 0;
break;
case ';':
_setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
count = 0;
range = false;
countSegments++;
break;
case ',':
_setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
count = 0;
range = false;
break;
default:
count++;
break;
}
}
_setLeds(_checkForNumber(count, colonsLen, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
}
}
void _setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon) {
if ((colon && umSSDRColonblink) || umSSDRNumbers[number][countSegments]) {
if (range) {
for(int i = lastSeenLedNr; i <= lednr; i++) {
umSSDRMask[i] = true;
}
} else {
umSSDRMask[lednr] = true;
}
}
}
void _setMaskToLeds() {
for(int i = 0; i <= umSSDRLength; i++) {
if ((!umSSDRInverted && !umSSDRMask[i]) || (umSSDRInverted && umSSDRMask[i])) {
strip.setPixelColor(i, 0x000000);
}
}
}
void _setAllFalse() {
for(int i = 0; i <= umSSDRLength; i++) {
umSSDRMask[i] = false;
}
}
int _checkForNumber(int count, int index, String *map) {
String number = (*map).substring(index - count, index);
return number.toInt();
}
void _publishMQTTint_P(const char *subTopic, int value)
{
if(mqtt == NULL) return;
char buffer[64];
char valBuffer[12];
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic);
sprintf_P(valBuffer, PSTR("%d"), value);
mqtt->publish(buffer, 2, true, valBuffer);
}
void _publishMQTTstr_P(const char *subTopic, String Value)
{
if(mqtt == NULL) return;
char buffer[64];
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic);
mqtt->publish(buffer, 2, true, Value.c_str(), Value.length());
}
bool _cmpIntSetting_P(char *topic, char *payload, const char *setting, void *value)
{
if (strcmp_P(topic, setting) == 0)
{
*((int *)value) = strtol(payload, NULL, 10);
_publishMQTTint_P(setting, *((int *)value));
return true;
}
return false;
}
bool _handleSetting(char *topic, char *payload) {
if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &umSSDRDisplayTime)) {
return true;
}
if (_cmpIntSetting_P(topic, payload, _str_ldrEnabled, &umSSDREnableLDR)) {
return true;
}
if (_cmpIntSetting_P(topic, payload, _str_inverted, &umSSDRInverted)) {
return true;
}
if (_cmpIntSetting_P(topic, payload, _str_colonblink, &umSSDRColonblink)) {
return true;
}
if (strcmp_P(topic, _str_displayMask) == 0) {
umSSDRDisplayMask = String(payload);
_publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask);
return true;
}
return false;
}
void _updateMQTT()
{
_publishMQTTint_P(_str_timeEnabled, umSSDRDisplayTime);
_publishMQTTint_P(_str_ldrEnabled, umSSDREnableLDR);
_publishMQTTint_P(_str_inverted, umSSDRInverted);
_publishMQTTint_P(_str_colonblink, umSSDRColonblink);
_publishMQTTstr_P(_str_hours, umSSDRHours);
_publishMQTTstr_P(_str_minutes, umSSDRMinutes);
_publishMQTTstr_P(_str_seconds, umSSDRSeconds);
_publishMQTTstr_P(_str_colons, umSSDRColons);
_publishMQTTstr_P(_str_days, umSSDRDays);
_publishMQTTstr_P(_str_months, umSSDRMonths);
_publishMQTTstr_P(_str_years, umSSDRYears);
_publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask);
_publishMQTTint_P(_str_minBrightness, umSSDRBrightnessMin);
_publishMQTTint_P(_str_maxBrightness, umSSDRBrightnessMax);
}
void _addJSONObject(JsonObject& root) {
JsonObject ssdrObj = root[FPSTR(_str_name)];
if (ssdrObj.isNull()) {
ssdrObj = root.createNestedObject(FPSTR(_str_name));
}
ssdrObj[FPSTR(_str_timeEnabled)] = umSSDRDisplayTime;
ssdrObj[FPSTR(_str_ldrEnabled)] = umSSDREnableLDR;
ssdrObj[FPSTR(_str_inverted)] = umSSDRInverted;
ssdrObj[FPSTR(_str_colonblink)] = umSSDRColonblink;
ssdrObj[FPSTR(_str_displayMask)] = umSSDRDisplayMask;
ssdrObj[FPSTR(_str_hours)] = umSSDRHours;
ssdrObj[FPSTR(_str_minutes)] = umSSDRMinutes;
ssdrObj[FPSTR(_str_seconds)] = umSSDRSeconds;
ssdrObj[FPSTR(_str_colons)] = umSSDRColons;
ssdrObj[FPSTR(_str_days)] = umSSDRDays;
ssdrObj[FPSTR(_str_months)] = umSSDRMonths;
ssdrObj[FPSTR(_str_years)] = umSSDRYears;
ssdrObj[FPSTR(_str_minBrightness)] = umSSDRBrightnessMin;
ssdrObj[FPSTR(_str_maxBrightness)] = umSSDRBrightnessMax;
}
public:
//Functions called by WLED
/*
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup() {
umSSDRLength = strip.getLengthTotal();
if (umSSDRMask != 0) {
umSSDRMask = (bool*) realloc(umSSDRMask, umSSDRLength * sizeof(bool));
} else {
umSSDRMask = (bool*) malloc(umSSDRLength * sizeof(bool));
}
_setAllFalse();
#ifdef USERMOD_ID_SN_PHOTORESISTOR
ptr = (Usermod_SN_Photoresistor*) usermods.lookup(USERMOD_ID_SN_PHOTORESISTOR);
#endif
DEBUG_PRINTLN(F("Setup done"));
}
/*
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop() {
if (!umSSDRDisplayTime || strip.isUpdating()) {
return;
}
#ifdef USERMOD_ID_SN_PHOTORESISTOR
if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) {
if (ptr != nullptr) {
uint16_t lux = ptr->getLastLDRValue();
uint16_t brightness = map(lux, 0, 1000, umSSDRBrightnessMin, umSSDRBrightnessMax);
if (bri != brightness) {
bri = brightness;
colorUpdated(1);
}
}
umSSDRLastRefresh = millis();
}
#endif
}
void handleOverlayDraw() {
if (umSSDRDisplayTime) {
_overlaySevenSegmentDraw();
}
}
/*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor
*/
void addToJsonInfo(JsonObject& root) {
JsonObject user = root[F("u")];
if (user.isNull()) {
user = root.createNestedObject(F("u"));
}
JsonArray enabled = user.createNestedArray("Time enabled");
enabled.add(umSSDRDisplayTime);
JsonArray invert = user.createNestedArray("Time inverted");
invert.add(umSSDRInverted);
JsonArray blink = user.createNestedArray("Blinking colon");
blink.add(umSSDRColonblink);
JsonArray ldrEnable = user.createNestedArray("Auto Brightness enabled");
ldrEnable.add(umSSDREnableLDR);
}
/*
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void addToJsonState(JsonObject& root) {
JsonObject user = root[F("u")];
if (user.isNull()) {
user = root.createNestedObject(F("u"));
}
_addJSONObject(user);
}
/*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject& root) {
JsonObject user = root[F("u")];
if (!user.isNull()) {
JsonObject ssdrObj = user[FPSTR(_str_name)];
umSSDRDisplayTime = ssdrObj[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime;
umSSDREnableLDR = ssdrObj[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR;
umSSDRInverted = ssdrObj[FPSTR(_str_inverted)] | umSSDRInverted;
umSSDRColonblink = ssdrObj[FPSTR(_str_colonblink)] | umSSDRColonblink;
umSSDRDisplayMask = ssdrObj[FPSTR(_str_displayMask)] | umSSDRDisplayMask;
}
}
void onMqttConnect(bool sessionPresent) {
char subBuffer[48];
if (mqttDeviceTopic[0] != 0)
{
_updateMQTT();
//subscribe for sevenseg messages on the device topic
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttDeviceTopic, _str_name);
mqtt->subscribe(subBuffer, 2);
}
if (mqttGroupTopic[0] != 0)
{
//subcribe for sevenseg messages on the group topic
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_name);
mqtt->subscribe(subBuffer, 2);
}
}
bool onMqttMessage(char *topic, char *payload) {
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
size_t topicPrefixLen = strlen_P(PSTR("/wledSS/"));
if (strncmp_P(topic, PSTR("/wledSS/"), topicPrefixLen) == 0) {
topic += topicPrefixLen;
} else {
return false;
}
//We only care if the topic ends with /set
size_t topicLen = strlen(topic);
if (topicLen > 4 &&
topic[topicLen - 4] == '/' &&
topic[topicLen - 3] == 's' &&
topic[topicLen - 2] == 'e' &&
topic[topicLen - 1] == 't')
{
//Trim /set and handle it
topic[topicLen - 4] = '\0';
_handleSetting(topic, payload);
}
return true;
}
void addToConfig(JsonObject &root) {
_addJSONObject(root);
}
bool readFromConfig(JsonObject &root) {
JsonObject top = root[FPSTR(_str_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_str_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
umSSDRDisplayTime = (top[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime);
umSSDREnableLDR = (top[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR);
umSSDRInverted = (top[FPSTR(_str_inverted)] | umSSDRInverted);
umSSDRColonblink = (top[FPSTR(_str_colonblink)] | umSSDRColonblink);
umSSDRDisplayMask = top[FPSTR(_str_displayMask)] | umSSDRDisplayMask;
umSSDRHours = top[FPSTR(_str_hours)] | umSSDRHours;
umSSDRMinutes = top[FPSTR(_str_minutes)] | umSSDRMinutes;
umSSDRSeconds = top[FPSTR(_str_seconds)] | umSSDRSeconds;
umSSDRColons = top[FPSTR(_str_colons)] | umSSDRColons;
umSSDRDays = top[FPSTR(_str_days)] | umSSDRDays;
umSSDRMonths = top[FPSTR(_str_months)] | umSSDRMonths;
umSSDRYears = top[FPSTR(_str_years)] | umSSDRYears;
umSSDRBrightnessMin = top[FPSTR(_str_minBrightness)] | umSSDRBrightnessMin;
umSSDRBrightnessMax = top[FPSTR(_str_maxBrightness)] | umSSDRBrightnessMax;
DEBUG_PRINT(FPSTR(_str_name));
DEBUG_PRINTLN(F(" config (re)loaded."));
return true;
}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() {
return USERMOD_ID_SSDR;
}
};
const char UsermodSSDR::_str_name[] PROGMEM = "UsermodSSDR";
const char UsermodSSDR::_str_timeEnabled[] PROGMEM = "enabled";
const char UsermodSSDR::_str_inverted[] PROGMEM = "inverted";
const char UsermodSSDR::_str_colonblink[] PROGMEM = "Colon-blinking";
const char UsermodSSDR::_str_displayMask[] PROGMEM = "Display-Mask";
const char UsermodSSDR::_str_hours[] PROGMEM = "LED-Numbers-Hours";
const char UsermodSSDR::_str_minutes[] PROGMEM = "LED-Numbers-Minutes";
const char UsermodSSDR::_str_seconds[] PROGMEM = "LED-Numbers-Seconds";
const char UsermodSSDR::_str_colons[] PROGMEM = "LED-Numbers-Colons";
const char UsermodSSDR::_str_days[] PROGMEM = "LED-Numbers-Day";
const char UsermodSSDR::_str_months[] PROGMEM = "LED-Numbers-Month";
const char UsermodSSDR::_str_years[] PROGMEM = "LED-Numbers-Year";
const char UsermodSSDR::_str_ldrEnabled[] PROGMEM = "enable-auto-brightness";
const char UsermodSSDR::_str_minBrightness[] PROGMEM = "auto-brightness-min";
const char UsermodSSDR::_str_maxBrightness[] PROGMEM = "auto-brightness-max";

View File

@ -78,6 +78,14 @@
#endif #endif
#endif #endif
#ifndef FLD_TYPE
#ifndef FLD_SPI_DEFAULT
#define FLD_TYPE SSD1306
#else
#define FLD_TYPE SSD1306_SPI
#endif
#endif
// When to time out to the clock or blank the screen // When to time out to the clock or blank the screen
// if SLEEP_MODE_ENABLED. // if SLEEP_MODE_ENABLED.
#define SCREEN_TIMEOUT_MS 60*1000 // 1 min #define SCREEN_TIMEOUT_MS 60*1000 // 1 min
@ -123,11 +131,11 @@ class FourLineDisplayUsermod : public Usermod {
#ifndef FLD_SPI_DEFAULT #ifndef FLD_SPI_DEFAULT
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000) uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
DisplayType type = SSD1306; // display type
#else #else
int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST
DisplayType type = SSD1306_SPI; // display type uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz)
#endif #endif
DisplayType type = FLD_TYPE; // display type
bool flip = false; // flip display 180° bool flip = false; // flip display 180°
uint8_t contrast = 10; // screen contrast uint8_t contrast = 10; // screen contrast
uint8_t lineHeight = 1; // 1 row or 2 rows uint8_t lineHeight = 1; // 1 row or 2 rows
@ -244,7 +252,7 @@ class FourLineDisplayUsermod : public Usermod {
initDone = true; initDone = true;
DEBUG_PRINTLN(F("Starting display.")); DEBUG_PRINTLN(F("Starting display."));
if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too /*if (!(type == SSD1306_SPI || type == SSD1306_SPI64))*/ u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin(); u8x8->begin();
setFlipMode(flip); setFlipMode(flip);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
@ -691,6 +699,9 @@ class FourLineDisplayUsermod : public Usermod {
screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000;
sleepMode = top[FPSTR(_sleepMode)] | sleepMode; sleepMode = top[FPSTR(_sleepMode)] | sleepMode;
clockMode = top[FPSTR(_clockMode)] | clockMode; clockMode = top[FPSTR(_clockMode)] | clockMode;
if (newType == SSD1306_SPI || newType == SSD1306_SPI64)
ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
else
ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));

View File

@ -25,6 +25,10 @@
//The SCL and SDA pins are defined here. //The SCL and SDA pins are defined here.
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
#define HW_PIN_SCL 22
#define HW_PIN_SDA 21
#define HW_PIN_CLOCKSPI 18
#define HW_PIN_DATASPI 23
#ifndef FLD_PIN_SCL #ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 22 #define FLD_PIN_SCL 22
#endif #endif
@ -47,6 +51,10 @@
#define FLD_PIN_RESET 26 #define FLD_PIN_RESET 26
#endif #endif
#else #else
#define HW_PIN_SCL 5
#define HW_PIN_SDA 4
#define HW_PIN_CLOCKSPI 14
#define HW_PIN_DATASPI 13
#ifndef FLD_PIN_SCL #ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 5 #define FLD_PIN_SCL 5
#endif #endif
@ -70,15 +78,20 @@
#endif #endif
#endif #endif
#ifndef FLD_TYPE
#ifndef FLD_SPI_DEFAULT
#define FLD_TYPE SSD1306
#else
#define FLD_TYPE SSD1306_SPI
#endif
#endif
// When to time out to the clock or blank the screen // When to time out to the clock or blank the screen
// if SLEEP_MODE_ENABLED. // if SLEEP_MODE_ENABLED.
#define SCREEN_TIMEOUT_MS 60*1000 // 1 min #define SCREEN_TIMEOUT_MS 60*1000 // 1 min
#define TIME_INDENT 0
#define DATE_INDENT 2
// Minimum time between redrawing screen in ms // Minimum time between redrawing screen in ms
#define USER_LOOP_REFRESH_RATE_MS 100 #define USER_LOOP_REFRESH_RATE_MS 1000
// Extra char (+1) for null // Extra char (+1) for null
#define LINE_BUFFER_SIZE 16+1 #define LINE_BUFFER_SIZE 16+1
@ -229,18 +242,17 @@ class FourLineDisplayUsermod : public Usermod {
private: private:
bool initDone = false; bool initDone = false;
unsigned long lastTime = 0;
// HW interface & configuration // HW interface & configuration
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
#ifndef FLD_SPI_DEFAULT #ifndef FLD_SPI_DEFAULT
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000) uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
DisplayType type = SSD1306_64; // display type
#else #else
int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST
DisplayType type = SSD1306_SPI; // display type uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz)
#endif #endif
DisplayType type = FLD_TYPE; // display type
bool flip = false; // flip display 180° bool flip = false; // flip display 180°
uint8_t contrast = 10; // screen contrast uint8_t contrast = 10; // screen contrast
uint8_t lineHeight = 1; // 1 row or 2 rows uint8_t lineHeight = 1; // 1 row or 2 rows
@ -248,6 +260,8 @@ class FourLineDisplayUsermod : public Usermod {
uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms
bool sleepMode = true; // allow screen sleep? bool sleepMode = true; // allow screen sleep?
bool clockMode = false; // display clock bool clockMode = false; // display clock
bool showSeconds = true; // display clock with seconds
bool enabled = true;
// needRedraw marks if redraw is required to prevent often redrawing. // needRedraw marks if redraw is required to prevent often redrawing.
bool needRedraw = true; bool needRedraw = true;
@ -262,6 +276,7 @@ class FourLineDisplayUsermod : public Usermod {
uint8_t knownMode = 0; uint8_t knownMode = 0;
uint8_t knownPalette = 0; uint8_t knownPalette = 0;
uint8_t knownMinute = 99; uint8_t knownMinute = 99;
uint8_t knownHour = 99;
byte brightness100; byte brightness100;
byte fxspeed100; byte fxspeed100;
byte fxintensity100; byte fxintensity100;
@ -270,21 +285,24 @@ class FourLineDisplayUsermod : public Usermod {
bool powerON = true; bool powerON = true;
bool displayTurnedOff = false; bool displayTurnedOff = false;
unsigned long lastUpdate = 0; unsigned long nextUpdate = 0;
unsigned long lastRedraw = 0; unsigned long lastRedraw = 0;
unsigned long overlayUntil = 0; unsigned long overlayUntil = 0;
// Set to 2 or 3 to mark lines 2 or 3. Other values ignored. // Set to 2 or 3 to mark lines 2 or 3. Other values ignored.
byte markLineNum = 0; byte markLineNum = 0;
byte markColNum = 0; byte markColNum = 0;
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
static const char _name[]; static const char _name[];
static const char _enabled[];
static const char _contrast[]; static const char _contrast[];
static const char _refreshRate[]; static const char _refreshRate[];
static const char _screenTimeOut[]; static const char _screenTimeOut[];
static const char _flip[]; static const char _flip[];
static const char _sleepMode[]; static const char _sleepMode[];
static const char _clockMode[]; static const char _clockMode[];
static const char _showSeconds[];
static const char _busClkFrequency[]; static const char _busClkFrequency[];
// If display does not work or looks corrupted check the // If display does not work or looks corrupted check the
@ -298,149 +316,137 @@ class FourLineDisplayUsermod : public Usermod {
// gets called once at boot. Do all initialization that doesn't depend on // gets called once at boot. Do all initialization that doesn't depend on
// network here // network here
void setup() { void setup() {
if (type == NONE) return; if (type == NONE || !enabled) return;
bool isHW;
PinOwner po = PinOwner::UM_FourLineDisplay;
if (type == SSD1306_SPI || type == SSD1306_SPI64) { if (type == SSD1306_SPI || type == SSD1306_SPI64) {
isHW = (ioPin[0]==HW_PIN_CLOCKSPI && ioPin[1]==HW_PIN_DATASPI);
PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true }}; PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true }};
if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } if (!pinManager.allocateMultiplePins(pins, 5, po)) { type=NONE; return; }
} else { } else {
isHW = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA);
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; }
} }
DEBUG_PRINTLN(F("Allocating display.")); DEBUG_PRINTLN(F("Allocating display."));
switch (type) { switch (type) {
case SSD1306: case SSD1306:
#ifdef ESP8266 if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4)) else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 1; lineHeight = 1;
break; break;
case SH1106: case SH1106:
#ifdef ESP8266 if (!isHW) u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4)) else u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 2; lineHeight = 2;
break; break;
case SSD1306_64: case SSD1306_64:
#ifdef ESP8266 if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4)) else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 2; lineHeight = 2;
break; break;
case SSD1305: case SSD1305:
#ifdef ESP8266 if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4)) else u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 1; lineHeight = 1;
break; break;
case SSD1305_64: case SSD1305_64:
#ifdef ESP8266 if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4)) else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 2; lineHeight = 2;
break; break;
case SSD1306_SPI: case SSD1306_SPI:
if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
else
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
lineHeight = 1; lineHeight = 1;
break; break;
case SSD1306_SPI64: case SSD1306_SPI64:
if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
else
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
lineHeight = 2; lineHeight = 2;
break; break;
default: default:
u8x8 = nullptr; u8x8 = nullptr;
} }
if (nullptr == u8x8) { if (nullptr == u8x8) {
DEBUG_PRINTLN(F("Display init failed.")); DEBUG_PRINTLN(F("Display init failed."));
for (byte i=0; i<5 && ioPin[i]>=0; i++) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay); pinManager.deallocateMultiplePins((const uint8_t*)ioPin, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po);
type = NONE; type = NONE;
return; return;
} }
initDone = true; initDone = true;
DEBUG_PRINTLN(F("Starting display.")); DEBUG_PRINTLN(F("Starting display."));
if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too /*if (!(type == SSD1306_SPI || type == SSD1306_SPI64))*/ u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin(); u8x8->begin();
setFlipMode(flip); setFlipMode(flip);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
setPowerSave(0); setPowerSave(0);
drawString(0, 0, "Loading..."); //drawString(0, 0, "Loading...");
overlay(PSTR("Loading..."),3000,0);
} }
// gets called every time WiFi is (re-)connected. Initialize own network // gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here // interfaces here
void connected() {} void connected() {
knownSsid = apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() :
knownIp = apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
networkOverlay(PSTR("NETWORK INFO"),7000);
}
/** /**
* Da loop. * Da loop.
*/ */
void loop() { void loop() {
if (displayTurnedOff && millis() - lastUpdate < 1000) { if (!enabled || strip.isUpdating()) return;
return; unsigned long now = millis();
}else if (millis() - lastUpdate < refreshRate){ if (now < nextUpdate) return;
return;} nextUpdate = now + ((clockMode && showSeconds) ? 1000 : refreshRate);
redraw(false); redraw(false);
lastUpdate = millis();
} }
/** /**
* Wrappers for screen drawing * Wrappers for screen drawing
*/ */
void setFlipMode(uint8_t mode) { void setFlipMode(uint8_t mode) {
if (type==NONE) return; if (type == NONE || !enabled) return;
u8x8->setFlipMode(mode); u8x8->setFlipMode(mode);
} }
void setContrast(uint8_t contrast) { void setContrast(uint8_t contrast) {
if (type==NONE) return; if (type == NONE || !enabled) return;
u8x8->setContrast(contrast); u8x8->setContrast(contrast);
} }
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
if (type==NONE) return; if (type == NONE || !enabled) return;
u8x8->setFont(u8x8_font_chroma48medium8_r); u8x8->setFont(u8x8_font_chroma48medium8_r);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string);
else u8x8->drawString(col, row, string); else u8x8->drawString(col, row, string);
} }
void draw2x2String(uint8_t col, uint8_t row, const char *string) { void draw2x2String(uint8_t col, uint8_t row, const char *string) {
if (type==NONE) return; if (type == NONE || !enabled) return;
u8x8->setFont(u8x8_font_chroma48medium8_r); u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->draw2x2String(col, row, string); u8x8->draw2x2String(col, row, string);
} }
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) { void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) {
if (type==NONE) return; if (type == NONE || !enabled) return;
u8x8->setFont(font); u8x8->setFont(font);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph);
else u8x8->drawGlyph(col, row, glyph); else u8x8->drawGlyph(col, row, glyph);
} }
uint8_t getCols() { uint8_t getCols() {
if (type==NONE) return 0; if (type==NONE || !enabled) return 0;
return u8x8->getCols(); return u8x8->getCols();
} }
void clear() { void clear() {
if (type==NONE) return; if (type == NONE || !enabled) return;
u8x8->clear(); u8x8->clear();
} }
void setPowerSave(uint8_t save) { void setPowerSave(uint8_t save) {
if (type==NONE) return; if (type == NONE || !enabled) return;
u8x8->setPowerSave(save); u8x8->setPowerSave(save);
} }
@ -460,9 +466,11 @@ class FourLineDisplayUsermod : public Usermod {
* or if forceRedraw). * or if forceRedraw).
*/ */
void redraw(bool forceRedraw) { void redraw(bool forceRedraw) {
if (type==NONE) return; unsigned long now = millis();
if (type == NONE || !enabled) return;
if (overlayUntil > 0) { if (overlayUntil > 0) {
if (millis() >= overlayUntil) { if (now >= overlayUntil) {
// Time to display the overlay has elapsed. // Time to display the overlay has elapsed.
overlayUntil = 0; overlayUntil = 0;
forceRedraw = true; forceRedraw = true;
@ -473,23 +481,27 @@ class FourLineDisplayUsermod : public Usermod {
} }
} }
// Check if values which are shown on display changed from the last time. // Check if values which are shown on display changed from the last time.
if (forceRedraw) { if (forceRedraw) {
knownHour = 99;
needRedraw = true; needRedraw = true;
clear();
} else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon } else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon
powerON = !powerON; powerON = !powerON;
drawStatusIcons(); drawStatusIcons();
lastRedraw = millis(); lastRedraw = millis();
return;
} else if (knownnightlight != nightlightActive) { //trigger moon icon } else if (knownnightlight != nightlightActive) { //trigger moon icon
knownnightlight = nightlightActive; knownnightlight = nightlightActive;
drawStatusIcons(); drawStatusIcons();
if (knownnightlight) overlay(" Timer On", 1000, 6); if (knownnightlight) overlay(PSTR(" Timer On"), 3000, 6);
lastRedraw = millis(); lastRedraw = millis();
return;
} else if (wificonnected != interfacesInited) { //trigger wifi icon } else if (wificonnected != interfacesInited) { //trigger wifi icon
wificonnected = interfacesInited; wificonnected = interfacesInited;
drawStatusIcons(); drawStatusIcons();
lastRedraw = millis(); lastRedraw = millis();
return;
} else if (knownMode != effectCurrent) { } else if (knownMode != effectCurrent) {
knownMode = effectCurrent; knownMode = effectCurrent;
if (displayTurnedOff) needRedraw = true; if (displayTurnedOff) needRedraw = true;
@ -510,20 +522,18 @@ class FourLineDisplayUsermod : public Usermod {
else updateIntensity(); else updateIntensity();
} }
if (!needRedraw) { if (!needRedraw) {
// Nothing to change. // Nothing to change.
// Turn off display after 1 minutes with no change. // Turn off display after 1 minutes with no change.
if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) { if (sleepMode && !displayTurnedOff && (now - lastRedraw > screenTimeout)) {
// We will still check if there is a change in redraw() // We will still check if there is a change in redraw()
// and turn it back on if it changed. // and turn it back on if it changed.
clear();
sleepOrClock(true); sleepOrClock(true);
} else if (displayTurnedOff && clockMode) { } else if (displayTurnedOff && clockMode) {
showTime(); showTime();
} }
return; return;
} else {
clear();
} }
needRedraw = false; needRedraw = false;
@ -532,11 +542,10 @@ class FourLineDisplayUsermod : public Usermod {
if (displayTurnedOff) { if (displayTurnedOff) {
// Turn the display back on // Turn the display back on
sleepOrClock(false); sleepOrClock(false);
clear();
} }
// Update last known values. // Update last known values.
knownSsid = apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() :
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
knownBrightness = bri; knownBrightness = bri;
knownMode = effectCurrent; knownMode = effectCurrent;
knownPalette = effectPalette; knownPalette = effectPalette;
@ -566,31 +575,34 @@ class FourLineDisplayUsermod : public Usermod {
void updateBrightness() { void updateBrightness() {
knownBrightness = bri; knownBrightness = bri;
if (overlayUntil == 0) { if (overlayUntil == 0) {
brightness100 = (((float)(bri)/255)*100); brightness100 = ((uint16_t)bri*100)/255;
char lineBuffer[4]; char lineBuffer[4];
sprintf_P(lineBuffer, PSTR("%-3d"), brightness100); sprintf_P(lineBuffer, PSTR("%-3d"), brightness100);
drawString(1, lineHeight, lineBuffer); drawString(1, lineHeight, lineBuffer);
lastRedraw = millis();} lastRedraw = millis();
}
} }
void updateSpeed() { void updateSpeed() {
knownEffectSpeed = effectSpeed; knownEffectSpeed = effectSpeed;
if (overlayUntil == 0) { if (overlayUntil == 0) {
fxspeed100 = (((float)(effectSpeed)/255)*100); fxspeed100 = ((uint16_t)effectSpeed*100)/255;
char lineBuffer[4]; char lineBuffer[4];
sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100); sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100);
drawString(5, lineHeight, lineBuffer); drawString(5, lineHeight, lineBuffer);
lastRedraw = millis();} lastRedraw = millis();
}
} }
void updateIntensity() { void updateIntensity() {
knownEffectIntensity = effectIntensity; knownEffectIntensity = effectIntensity;
if (overlayUntil == 0) { if (overlayUntil == 0) {
fxintensity100 = (((float)(effectIntensity)/255)*100); fxintensity100 = ((uint16_t)effectIntensity*100)/255;
char lineBuffer[4]; char lineBuffer[4];
sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100); sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100);
drawString(9, lineHeight, lineBuffer); drawString(9, lineHeight, lineBuffer);
lastRedraw = millis();} lastRedraw = millis();
}
} }
void draw2x2GlyphIcons() { void draw2x2GlyphIcons() {
@ -600,8 +612,7 @@ class FourLineDisplayUsermod : public Usermod {
drawGlyph(9, 0, 3, u8x8_font_benji_custom_icons_2x2, true);//intensity icon drawGlyph(9, 0, 3, u8x8_font_benji_custom_icons_2x2, true);//intensity icon
drawGlyph(14, 2*lineHeight, 4, u8x8_font_benji_custom_icons_2x2, true);//palette icon drawGlyph(14, 2*lineHeight, 4, u8x8_font_benji_custom_icons_2x2, true);//palette icon
drawGlyph(14, 3*lineHeight, 5, u8x8_font_benji_custom_icons_2x2, true);//effect icon drawGlyph(14, 3*lineHeight, 5, u8x8_font_benji_custom_icons_2x2, true);//effect icon
} } else {
else{
drawGlyph(2, 0, 69, u8x8_font_open_iconic_weather_1x1); //brightness icon drawGlyph(2, 0, 69, u8x8_font_open_iconic_weather_1x1); //brightness icon
drawGlyph(6, 0, 72, u8x8_font_open_iconic_play_1x1); //speed icon drawGlyph(6, 0, 72, u8x8_font_open_iconic_play_1x1); //speed icon
drawGlyph(10, 0, 78, u8x8_font_open_iconic_thing_1x1); //intensity icon drawGlyph(10, 0, 78, u8x8_font_open_iconic_thing_1x1); //intensity icon
@ -634,63 +645,37 @@ class FourLineDisplayUsermod : public Usermod {
//Display the current effect or palette (desiredEntry) //Display the current effect or palette (desiredEntry)
// on the appropriate line (row). // on the appropriate line (row).
void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) {
char lineBuffer[LINE_BUFFER_SIZE];
knownMode = effectCurrent; knownMode = effectCurrent;
knownPalette = effectPalette; knownPalette = effectPalette;
if (overlayUntil == 0) { if (overlayUntil == 0) {
char lineBuffer[MAX_JSON_CHARS];
char smallBuffer1[MAX_MODE_LINE_SPACE];
char smallBuffer2[MAX_MODE_LINE_SPACE];
char smallBuffer3[MAX_MODE_LINE_SPACE+1];
uint8_t qComma = 0;
bool insideQuotes = false;
bool spaceHit = false;
uint8_t printedChars = 0;
uint8_t smallChars1 = 0;
uint8_t smallChars2 = 0;
uint8_t smallChars3 = 0;
uint8_t totalCount = 0;
char singleJsonSymbol;
// Find the mode name in JSON // Find the mode name in JSON
for (size_t i = 0; i < strlen_P(qstring); i++) { //find and get the full text for printing uint8_t printedChars = extractModeName(inputEffPal, qstring, lineBuffer, LINE_BUFFER_SIZE-1);
singleJsonSymbol = pgm_read_byte_near(qstring + i);
if (singleJsonSymbol == '\0') break;
switch (singleJsonSymbol) {
case '"':
insideQuotes = !insideQuotes;
break;
case '[':
case ']':
break;
case ',':
qComma++;
default:
if (!insideQuotes || (qComma != inputEffPal)) break;
lineBuffer[printedChars++] = singleJsonSymbol;
totalCount++;
}
if ((qComma > inputEffPal)) break;
}
if (lineHeight == 2) { // use this code for 8 line display if (lineHeight == 2) { // use this code for 8 line display
if(printedChars < (MAX_MODE_LINE_SPACE)){ // use big font if the text fits char smallBuffer1[MAX_MODE_LINE_SPACE];
for (;printedChars < (MAX_MODE_LINE_SPACE-1); printedChars++) {lineBuffer[printedChars]=' '; } char smallBuffer2[MAX_MODE_LINE_SPACE];
uint8_t smallChars1 = 0;
uint8_t smallChars2 = 0;
if (printedChars < MAX_MODE_LINE_SPACE) { // use big font if the text fits
for (;printedChars < (MAX_MODE_LINE_SPACE-1); printedChars++) lineBuffer[printedChars]=' ';
lineBuffer[printedChars] = 0; lineBuffer[printedChars] = 0;
drawString(1, row*lineHeight, lineBuffer); drawString(1, row*lineHeight, lineBuffer);
lastRedraw = millis();
} else { // for long names divide the text into 2 lines and print them small } else { // for long names divide the text into 2 lines and print them small
bool spaceHit = false;
for (uint8_t i = 0; i < printedChars; i++) { for (uint8_t i = 0; i < printedChars; i++) {
switch (lineBuffer[i]) { switch (lineBuffer[i]) {
case ' ': case ' ':
if (i > 4 && !spaceHit) { if (i > 4 && !spaceHit) {
spaceHit = true; spaceHit = true;
break;} break;
if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i]; }
if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i];
else smallBuffer1[smallChars1++] = lineBuffer[i];
break; break;
default: default:
if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i];
if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i];
else smallBuffer1[smallChars1++] = lineBuffer[i];
break; break;
} }
} }
@ -700,20 +685,17 @@ class FourLineDisplayUsermod : public Usermod {
for (; smallChars2 < (MAX_MODE_LINE_SPACE-1); smallChars2++) smallBuffer2[smallChars2]=' '; for (; smallChars2 < (MAX_MODE_LINE_SPACE-1); smallChars2++) smallBuffer2[smallChars2]=' ';
smallBuffer2[smallChars2] = 0; smallBuffer2[smallChars2] = 0;
drawString(1, row*lineHeight+1, smallBuffer2, true); drawString(1, row*lineHeight+1, smallBuffer2, true);
lastRedraw = millis();
} }
} } else { // use this code for 4 ling displays
else{ // use this code for 4 ling displays char smallBuffer3[MAX_MODE_LINE_SPACE+1];
uint8_t smallChars3 = 0;
if (printedChars > MAX_MODE_LINE_SPACE) printedChars = MAX_MODE_LINE_SPACE; if (printedChars > MAX_MODE_LINE_SPACE) printedChars = MAX_MODE_LINE_SPACE;
for (uint8_t i = 0; i < printedChars; i++){ for (uint8_t i = 0; i < printedChars; i++) smallBuffer3[smallChars3++] = lineBuffer[i];
smallBuffer3[smallChars3++] = lineBuffer[i];
}
for (; smallChars3 < (MAX_MODE_LINE_SPACE); smallChars3++) smallBuffer3[smallChars3]=' '; for (; smallChars3 < (MAX_MODE_LINE_SPACE); smallChars3++) smallBuffer3[smallChars3]=' ';
smallBuffer3[smallChars3] = 0; smallBuffer3[smallChars3] = 0;
drawString(1, row*lineHeight, smallBuffer3, true); drawString(1, row*lineHeight, smallBuffer3, true);
lastRedraw = millis();
} }
lastRedraw = millis();
} }
} }
@ -724,8 +706,10 @@ class FourLineDisplayUsermod : public Usermod {
* to wake up the screen. * to wake up the screen.
*/ */
bool wakeDisplay() { bool wakeDisplay() {
//knownHour = 99; if (type == NONE || !enabled) return false;
knownHour = 99;
if (displayTurnedOff) { if (displayTurnedOff) {
clear();
// Turn the display back on // Turn the display back on
sleepOrClock(false); sleepOrClock(false);
redraw(true); redraw(true);
@ -750,8 +734,10 @@ class FourLineDisplayUsermod : public Usermod {
if (glyphType > 0) { if (glyphType > 0) {
if (lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_font_benji_custom_icons_6x6, true); if (lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_font_benji_custom_icons_6x6, true);
else drawGlyph(7, lineHeight, glyphType, u8x8_font_benji_custom_icons_2x2, true); else drawGlyph(7, lineHeight, glyphType, u8x8_font_benji_custom_icons_2x2, true);
}
if (line1) drawString(0, 3*lineHeight, line1); if (line1) drawString(0, 3*lineHeight, line1);
} else {
if (line1) drawString(0, 2*(lineHeight-1), line1);
}
overlayUntil = millis() + showHowLong; overlayUntil = millis() + showHowLong;
} }
@ -763,19 +749,32 @@ class FourLineDisplayUsermod : public Usermod {
// Print the overlay // Print the overlay
clear(); clear();
// First row string // First row string
if (line1) drawString(0, 0, line1); if (line1) {
String l1 = line1;
l1.trim();
center(l1, getCols());
drawString(0, 0, l1.c_str());
}
// Second row with Wifi name // Second row with Wifi name
String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); // String line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0);
drawString(0, lineHeight, ssidString.c_str()); if (line.length() < getCols()) center(line, getCols());
drawString(0, lineHeight, line.c_str());
// Print `~` char to indicate that SSID is longer, than our display // Print `~` char to indicate that SSID is longer, than our display
if (knownSsid.length() > getCols()) { if (knownSsid.length() > getCols()) {
drawString(getCols() - 1, 0, "~"); drawString(getCols() - 1, 0, "~");
} }
// Third row with IP and Psssword in AP Mode // Third row with IP and Password in AP Mode
drawString(0, lineHeight*2, (knownIp.toString()).c_str()); line = knownIp.toString();
center(line, getCols());
drawString(0, lineHeight*2, line.c_str());
if (apActive) { if (apActive) {
String appassword = apPass; line = apPass;
drawString(0, lineHeight*3, appassword.c_str()); center(line, getCols());
drawString(0, lineHeight*3, line.c_str());
} else if (strcmp(serverDescription, PSTR("WLED")) != 0) {
line = serverDescription;
center(line, getCols());
drawString(0, lineHeight*3, line.c_str());
} }
overlayUntil = millis() + showHowLong; overlayUntil = millis() + showHowLong;
} }
@ -787,13 +786,12 @@ class FourLineDisplayUsermod : public Usermod {
void sleepOrClock(bool enabled) { void sleepOrClock(bool enabled) {
if (enabled) { if (enabled) {
if (clockMode) { if (clockMode) {
clear(); knownMinute = knownHour = 99;
knownMinute = 99;
showTime(); showTime();
}else setPowerSave(1); } else
setPowerSave(1);
displayTurnedOff = true; displayTurnedOff = true;
} } else {
else {
setPowerSave(0); setPowerSave(0);
displayTurnedOff = false; displayTurnedOff = false;
} }
@ -805,28 +803,45 @@ class FourLineDisplayUsermod : public Usermod {
* the useAMPM configuration. * the useAMPM configuration.
*/ */
void showTime() { void showTime() {
if(knownMinute != minute(localTime)){ //only redraw clock if it has changed if (type == NONE || !enabled || !displayTurnedOff) return;
char lineBuffer[LINE_BUFFER_SIZE];
char lineBuffer[LINE_BUFFER_SIZE];
static byte lastSecond;
byte secondCurrent = second(localTime);
byte minuteCurrent = minute(localTime);
byte hourCurrent = hour(localTime);
if (knownMinute != minuteCurrent) { //only redraw clock if it has changed
//updateLocalTime(); //updateLocalTime();
byte AmPmHour = hour(localTime); byte AmPmHour = hourCurrent;
boolean isitAM = true; boolean isitAM = true;
if (useAMPM) { if (useAMPM) {
if (AmPmHour > 11) AmPmHour -= 12; if (AmPmHour > 11) { AmPmHour -= 12; isitAM = false; }
if (AmPmHour == 0) AmPmHour = 12; if (AmPmHour == 0) { AmPmHour = 12; }
if (hour(localTime) > 11) isitAM = false;
} }
clear();
drawStatusIcons(); //icons power, wifi, timer, etc drawStatusIcons(); //icons power, wifi, timer, etc
if (knownHour != hourCurrent) {
// only update date when hour changes
sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime));
draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day
}
sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hour(localTime)), minute(localTime)); sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent);
draw2x2String(TIME_INDENT+2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds
if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time
knownMinute = minute(localTime); knownMinute = minuteCurrent;
knownHour = hourCurrent;
} else {
if (secondCurrent == lastSecond) return;
}
if (showSeconds && !useAMPM) {
lastSecond = secondCurrent;
draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":");
sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent);
drawString(12 + (lineHeight%2), lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line
} }
} }
@ -873,16 +888,19 @@ class FourLineDisplayUsermod : public Usermod {
*/ */
void addToConfig(JsonObject& root) { void addToConfig(JsonObject& root) {
JsonObject top = root.createNestedObject(FPSTR(_name)); JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
JsonArray io_pin = top.createNestedArray("pin"); JsonArray io_pin = top.createNestedArray("pin");
for (byte i=0; i<5; i++) io_pin.add(ioPin[i]); for (byte i=0; i<5; i++) io_pin.add(ioPin[i]);
top["help4PinTypes"] = F("Clk,Data,CS,DC,RST"); // help for Settings page top["help4Pins"] = F("Clk,Data,CS,DC,RST"); // help for Settings page
top["type"] = type; top["type"] = type;
top["help4Type"] = F("1=SSD1306,2=SH1106,3=SSD1306_128x64,4=SSD1305,5=SSD1305_128x64,6=SSD1306_SPI,7=SSD1306_SPI_128x64"); // help for Settings page
top[FPSTR(_flip)] = (bool) flip; top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_contrast)] = contrast; top[FPSTR(_contrast)] = contrast;
top[FPSTR(_refreshRate)] = refreshRate/10; top[FPSTR(_refreshRate)] = refreshRate/1000;
top[FPSTR(_screenTimeOut)] = screenTimeout/1000; top[FPSTR(_screenTimeOut)] = screenTimeout/1000;
top[FPSTR(_sleepMode)] = (bool) sleepMode; top[FPSTR(_sleepMode)] = (bool) sleepMode;
top[FPSTR(_clockMode)] = (bool) clockMode; top[FPSTR(_clockMode)] = (bool) clockMode;
top[FPSTR(_showSeconds)] = (bool) showSeconds;
top[FPSTR(_busClkFrequency)] = ioFrequency/1000; top[FPSTR(_busClkFrequency)] = ioFrequency/1000;
DEBUG_PRINTLN(F("4 Line Display config saved.")); DEBUG_PRINTLN(F("4 Line Display config saved."));
} }
@ -907,14 +925,19 @@ class FourLineDisplayUsermod : public Usermod {
return false; return false;
} }
enabled = top[FPSTR(_enabled)] | enabled;
newType = top["type"] | newType; newType = top["type"] | newType;
for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i]; for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i];
flip = top[FPSTR(_flip)] | flip; flip = top[FPSTR(_flip)] | flip;
contrast = top[FPSTR(_contrast)] | contrast; contrast = top[FPSTR(_contrast)] | contrast;
refreshRate = (top[FPSTR(_refreshRate)] | refreshRate/10) * 10; refreshRate = (top[FPSTR(_refreshRate)] | refreshRate/1000) * 1000;
screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000;
sleepMode = top[FPSTR(_sleepMode)] | sleepMode; sleepMode = top[FPSTR(_sleepMode)] | sleepMode;
clockMode = top[FPSTR(_clockMode)] | clockMode; clockMode = top[FPSTR(_clockMode)] | clockMode;
showSeconds = top[FPSTR(_showSeconds)] | showSeconds;
if (newType == SSD1306_SPI || newType == SSD1306_SPI64)
ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
else
ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
@ -930,10 +953,10 @@ class FourLineDisplayUsermod : public Usermod {
for (byte i=0; i<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } for (byte i=0; i<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; }
if (pinsChanged || type!=newType) { if (pinsChanged || type!=newType) {
if (type != NONE) delete u8x8; if (type != NONE) delete u8x8;
for (byte i=0; i<5; i++) { PinOwner po = PinOwner::UM_FourLineDisplay;
if (ioPin[i]>=0) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay); if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
ioPin[i] = newPin[i]; pinManager.deallocateMultiplePins((const uint8_t *)ioPin, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po);
} for (byte i=0; i<5; i++) ioPin[i] = newPin[i];
if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1 if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1
type = NONE; type = NONE;
return true; return true;
@ -941,13 +964,14 @@ class FourLineDisplayUsermod : public Usermod {
setup(); setup();
needsRedraw |= true; needsRedraw |= true;
} }
if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too /*if (!(type == SSD1306_SPI || type == SSD1306_SPI64))*/ u8x8->setBusClock(ioFrequency); // can be used for SPI too
setContrast(contrast); setContrast(contrast);
setFlipMode(flip); setFlipMode(flip);
knownHour = 99;
if (needsRedraw && !wakeDisplay()) redraw(true); if (needsRedraw && !wakeDisplay()) redraw(true);
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !(top[_busClkFrequency]).isNull(); return !top[FPSTR(_showSeconds)].isNull();
} }
/* /*
@ -961,10 +985,12 @@ class FourLineDisplayUsermod : public Usermod {
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay"; const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
const char FourLineDisplayUsermod::_enabled[] PROGMEM = "enabled";
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast"; const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate0.01Sec"; const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRateSec";
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec"; const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip"; const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode"; const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode"; const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
const char FourLineDisplayUsermod::_showSeconds[] PROGMEM = "showSeconds";
const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz"; const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz";

View File

@ -162,7 +162,6 @@ public:
break; break;
} }
} }
re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount);
} }
@ -189,6 +188,7 @@ public:
bool complete = false; bool complete = false;
for (size_t i = 0; i < strlen_P(json); i++) { for (size_t i = 0; i < strlen_P(json); i++) {
singleJsonSymbol = pgm_read_byte_near(json + i); singleJsonSymbol = pgm_read_byte_near(json + i);
if (singleJsonSymbol == '\0') break;
switch (singleJsonSymbol) { switch (singleJsonSymbol) {
case '"': case '"':
insideQuotes = !insideQuotes; insideQuotes = !insideQuotes;
@ -200,18 +200,14 @@ public:
case '[': case '[':
break; break;
case ']': case ']':
complete = true; if (!insideQuotes) complete = true;
break; break;
case ',': case ',':
modeIndex++; if (!insideQuotes) modeIndex++;
default: default:
if (!insideQuotes) { if (!insideQuotes) break;
break;
}
}
if (complete) {
break;
} }
if (complete) break;
} }
return modeStrings; return modeStrings;
} }

View File

@ -97,6 +97,7 @@ public:
*/ */
void setup() void setup()
{ {
DEBUG_PRINTLN(F("Usermod Rotary Encoder init."));
PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } };
if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) {
// BUG: configuring this usermod with conflicting pins // BUG: configuring this usermod with conflicting pins
@ -443,14 +444,11 @@ public:
DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false; return false;
} }
int8_t newDTpin = pinA; int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA;
int8_t newCLKpin = pinB; int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB;
int8_t newSWpin = pinC; int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC;
enabled = top[FPSTR(_enabled)] | enabled; enabled = top[FPSTR(_enabled)] | enabled;
newDTpin = top[FPSTR(_DT_pin)] | newDTpin;
newCLKpin = top[FPSTR(_CLK_pin)] | newCLKpin;
newSWpin = top[FPSTR(_SW_pin)] | newSWpin;
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {

View File

@ -19,17 +19,20 @@
// Change between modes by pressing a button. // Change between modes by pressing a button.
// //
// Dependencies // Dependencies
// * This usermod REQURES the ModeSortUsermod
// * This Usermod works best coupled with // * This Usermod works best coupled with
// FourLineDisplayUsermod. // FourLineDisplayUsermod.
// //
// If FourLineDisplayUsermod is used the folowing options are also inabled // If FourLineDisplayUsermod is used the folowing options are also enabled
// //
// * main color // * main color
// * saturation of main color // * saturation of main color
// * display network (long press buttion) // * display network (long press buttion)
// //
#ifdef USERMOD_MODE_SORT
#error "Usermod Mode Sort is no longer required. Remove -D USERMOD_MODE_SORT from platformio.ini"
#endif
#ifndef ENCODER_DT_PIN #ifndef ENCODER_DT_PIN
#define ENCODER_DT_PIN 18 #define ENCODER_DT_PIN 18
#endif #endif
@ -49,13 +52,73 @@
#define LAST_UI_STATE 4 #define LAST_UI_STATE 4
#endif #endif
// Number of modes at the start of the list to not sort
#define MODE_SORT_SKIP_COUNT 1
// Which list is being sorted
static char **listBeingSorted;
/**
* Modes and palettes are stored as strings that
* end in a quote character. Compare two of them.
* We are comparing directly within either
* JSON_mode_names or JSON_palette_names.
*/
static int re_qstringCmp(const void *ap, const void *bp) {
char *a = listBeingSorted[*((byte *)ap)];
char *b = listBeingSorted[*((byte *)bp)];
int i = 0;
do {
char aVal = pgm_read_byte_near(a + i);
if (aVal >= 97 && aVal <= 122) {
// Lowercase
aVal -= 32;
}
char bVal = pgm_read_byte_near(b + i);
if (bVal >= 97 && bVal <= 122) {
// Lowercase
bVal -= 32;
}
// Relly we shouldn't ever get to '\0'
if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') {
// We're done. one is a substring of the other
// or something happenend and the quote didn't stop us.
if (aVal == bVal) {
// Same value, probably shouldn't happen
// with this dataset
return 0;
}
else if (aVal == '"' || aVal == '\0') {
return -1;
}
else {
return 1;
}
}
if (aVal == bVal) {
// Same characters. Move to the next.
i++;
continue;
}
// We're done
if (aVal < bVal) {
return -1;
}
else {
return 1;
}
} while (true);
// We shouldn't get here.
return 0;
}
class RotaryEncoderUIUsermod : public Usermod { class RotaryEncoderUIUsermod : public Usermod {
private: private:
int fadeAmount = 5; // Amount to change every step (brightness) int fadeAmount = 5; // Amount to change every step (brightness)
unsigned long currentTime; unsigned long currentTime;
unsigned long loopTime; unsigned long loopTime;
unsigned long buttonHoldTIme; unsigned long buttonHoldTime;
int8_t pinA = ENCODER_DT_PIN; // DT from encoder int8_t pinA = ENCODER_DT_PIN; // DT from encoder
int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder
int8_t pinC = ENCODER_SW_PIN; // SW from encoder int8_t pinC = ENCODER_SW_PIN; // SW from encoder
@ -63,7 +126,7 @@ private:
unsigned char button_state = HIGH; unsigned char button_state = HIGH;
unsigned char prev_button_state = HIGH; unsigned char prev_button_state = HIGH;
bool networkShown = false; bool networkShown = false;
uint16_t currentHue1 = 6425; // default reboot color uint16_t currentHue1 = 16; // default boot color
byte currentSat1 = 255; byte currentSat1 = 255;
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
@ -72,7 +135,16 @@ private:
void* display = nullptr; void* display = nullptr;
#endif #endif
// Pointers the start of the mode names within JSON_mode_names
char **modes_qstrings = nullptr;
// Array of mode indexes in alphabetical order.
byte *modes_alpha_indexes = nullptr; byte *modes_alpha_indexes = nullptr;
// Pointers the start of the palette names within JSON_palette_names
char **palettes_qstrings = nullptr;
// Array of palette indexes in alphabetical order.
byte *palettes_alpha_indexes = nullptr; byte *palettes_alpha_indexes = nullptr;
unsigned char Enc_A; unsigned char Enc_A;
@ -95,6 +167,82 @@ private:
static const char _CLK_pin[]; static const char _CLK_pin[];
static const char _SW_pin[]; static const char _SW_pin[];
/**
* Sort the modes and palettes to the index arrays
* modes_alpha_indexes and palettes_alpha_indexes.
*/
void sortModesAndPalettes() {
modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount());
modes_alpha_indexes = re_initIndexArray(strip.getModeCount());
re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT);
palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount());
palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount());
// How many palette names start with '*' and should not be sorted?
// (Also skipping the first one, 'Default').
int skipPaletteCount = 1;
while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount++]) == '*') ;
re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount);
}
byte *re_initIndexArray(int numModes) {
byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
for (byte i = 0; i < numModes; i++) {
indexes[i] = i;
}
return indexes;
}
/**
* Return an array of mode or palette names from the JSON string.
* They don't end in '\0', they end in '"'.
*/
char **re_findModeStrings(const char json[], int numModes) {
char **modeStrings = (char **)malloc(sizeof(char *) * numModes);
uint8_t modeIndex = 0;
bool insideQuotes = false;
// advance past the mark for markLineNum that may exist.
char singleJsonSymbol;
// Find the mode name in JSON
bool complete = false;
for (size_t i = 0; i < strlen_P(json); i++) {
singleJsonSymbol = pgm_read_byte_near(json + i);
if (singleJsonSymbol == '\0') break;
switch (singleJsonSymbol) {
case '"':
insideQuotes = !insideQuotes;
if (insideQuotes) {
// We have a new mode or palette
modeStrings[modeIndex] = (char *)(json + i + 1);
}
break;
case '[':
break;
case ']':
if (!insideQuotes) complete = true;
break;
case ',':
if (!insideQuotes) modeIndex++;
default:
if (!insideQuotes) break;
}
if (complete) break;
}
return modeStrings;
}
/**
* Sort either the modes or the palettes using quicksort.
*/
void re_sortModes(char **modeNames, byte *indexes, int count, int numSkip) {
listBeingSorted = modeNames;
qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp);
listBeingSorted = nullptr;
}
public: public:
/* /*
* setup() is called once at boot. WiFi is not yet connected at this point. * setup() is called once at boot. WiFi is not yet connected at this point.
@ -102,6 +250,7 @@ public:
*/ */
void setup() void setup()
{ {
DEBUG_PRINTLN(F("Usermod Rotary Encoder init."));
PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } };
if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) {
// BUG: configuring this usermod with conflicting pins // BUG: configuring this usermod with conflicting pins
@ -120,9 +269,7 @@ public:
currentTime = millis(); currentTime = millis();
loopTime = currentTime; loopTime = currentTime;
ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT); if (!initDone) sortModesAndPalettes();
modes_alpha_indexes = modeSortUsermod->getModesAlphaIndexes();
palettes_alpha_indexes = modeSortUsermod->getPalettesAlphaIndexes();
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
// This Usermod uses FourLineDisplayUsermod for the best experience. // This Usermod uses FourLineDisplayUsermod for the best experience.
@ -167,10 +314,10 @@ public:
// is not yet initialized when setup is called. // is not yet initialized when setup is called.
if (!currentEffectAndPaletteInitialized) { if (!currentEffectAndPaletteInitialized) {
findCurrentEffectAndPalette();} findCurrentEffectAndPalette();
}
if(modes_alpha_indexes[effectCurrentIndex] != effectCurrent if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) {
|| palettes_alpha_indexes[effectPaletteIndex] != effectPalette){
currentEffectAndPaletteInitialized = false; currentEffectAndPaletteInitialized = false;
} }
@ -179,7 +326,7 @@ public:
button_state = digitalRead(pinC); button_state = digitalRead(pinC);
if (prev_button_state != button_state) if (prev_button_state != button_state)
{ {
if (button_state == HIGH && (millis()-buttonHoldTIme < 3000)) if (button_state == HIGH && (millis()-buttonHoldTime < 3000))
{ {
prev_button_state = button_state; prev_button_state = button_state;
@ -190,25 +337,25 @@ public:
if (display != nullptr) { if (display != nullptr) {
switch(newState) { switch(newState) {
case 0: case 0:
changedState = changeState(" Brightness", 1, 0, 1); changedState = changeState(PSTR("Brightness"), 1, 0, 1);
break; break;
case 1: case 1:
changedState = changeState(" Speed", 1, 4, 2); changedState = changeState(PSTR("Speed"), 1, 4, 2);
break; break;
case 2: case 2:
changedState = changeState(" Intensity", 1 ,8, 3); changedState = changeState(PSTR("Intensity"), 1 ,8, 3);
break; break;
case 3: case 3:
changedState = changeState(" Color Palette", 2, 0, 4); changedState = changeState(PSTR("Color Palette"), 2, 0, 4);
break; break;
case 4: case 4:
changedState = changeState(" Effect", 3, 0, 5); changedState = changeState(PSTR("Effect"), 3, 0, 5);
break; break;
case 5: case 5:
changedState = changeState(" Main Color", 255, 255, 7); changedState = changeState(PSTR("Main Color"), 255, 255, 7);
break; break;
case 6: case 6:
changedState = changeState(" Saturation", 255, 255, 8); changedState = changeState(PSTR("Saturation"), 255, 255, 8);
break; break;
} }
} }
@ -220,11 +367,15 @@ public:
{ {
prev_button_state = button_state; prev_button_state = button_state;
networkShown = false; networkShown = false;
if(!prev_button_state)buttonHoldTIme = millis(); if (!prev_button_state) buttonHoldTime = millis();
} }
} }
if (!prev_button_state && (millis()-buttonHoldTIme > 3000) && !networkShown) displayNetworkInfo(); //long press for network info if (!prev_button_state && (millis()-buttonHoldTime > 3000) && !networkShown) {
displayNetworkInfo(); //long press for network info
loopTime = currentTime; // Updates loopTime
return;
}
Enc_A = digitalRead(pinA); // Read encoder pins Enc_A = digitalRead(pinA); // Read encoder pins
Enc_B = digitalRead(pinB); Enc_B = digitalRead(pinB);
@ -290,7 +441,7 @@ public:
void displayNetworkInfo() { void displayNetworkInfo() {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
display->networkOverlay(" NETWORK INFO", 15000); display->networkOverlay(PSTR("NETWORK INFO"), 10000);
networkShown = true; networkShown = true;
#endif #endif
} }
@ -319,7 +470,10 @@ public:
// Throw away wake up input // Throw away wake up input
return false; return false;
} }
display->overlay(stateName, 750, glyph); String line = stateName;
//line.trim();
display->center(line, display->getCols());
display->overlay(line.c_str(), 750, glyph);
display->setMarkLine(markedLine, markedCol); display->setMarkLine(markedLine, markedCol);
} }
#endif #endif
@ -357,8 +511,7 @@ public:
return; return;
} }
#endif #endif
if (increase) effectCurrentIndex = (effectCurrentIndex + 1 >= strip.getModeCount()) ? 0 : (effectCurrentIndex + 1); effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0);
else effectCurrentIndex = (effectCurrentIndex - 1 < 0) ? (strip.getModeCount() - 1) : (effectCurrentIndex - 1);
effectCurrent = modes_alpha_indexes[effectCurrentIndex]; effectCurrent = modes_alpha_indexes[effectCurrentIndex];
lampUdated(); lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
@ -374,8 +527,7 @@ public:
return; return;
} }
#endif #endif
if (increase) effectSpeed = (effectSpeed + fadeAmount <= 255) ? (effectSpeed + fadeAmount) : 255; effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0);
else effectSpeed = (effectSpeed - fadeAmount >= 0) ? (effectSpeed - fadeAmount) : 0;
lampUdated(); lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateSpeed(); display->updateSpeed();
@ -390,8 +542,7 @@ public:
return; return;
} }
#endif #endif
if (increase) effectIntensity = (effectIntensity + fadeAmount <= 255) ? (effectIntensity + fadeAmount) : 255; effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0);
else effectIntensity = (effectIntensity - fadeAmount >= 0) ? (effectIntensity - fadeAmount) : 0;
lampUdated(); lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateIntensity(); display->updateIntensity();
@ -406,8 +557,7 @@ public:
return; return;
} }
#endif #endif
if (increase) effectPaletteIndex = (effectPaletteIndex + 1 >= strip.getPaletteCount()) ? 0 : (effectPaletteIndex + 1); effectPaletteIndex = max(min((increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()-1), 0);
else effectPaletteIndex = (effectPaletteIndex - 1 < 0) ? (strip.getPaletteCount() - 1) : (effectPaletteIndex - 1);
effectPalette = palettes_alpha_indexes[effectPaletteIndex]; effectPalette = palettes_alpha_indexes[effectPaletteIndex];
lampUdated(); lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
@ -423,10 +573,11 @@ public:
return; return;
} }
#endif #endif
if (increase) { if (currentHue1<256) currentHue1 += 4; else currentHue1 = 0; }
if(increase) currentHue1 += 321; else { if (currentHue1>3) currentHue1 -= 4; else currentHue1 = 256; }
else currentHue1 -= 321; colorHStoRGB(currentHue1*255, currentSat1, col);
colorHStoRGB(currentHue1, currentSat1, col); strip.applyToAllSelected = true;
strip.setColor(0, colorFromRgbw(col));
lampUdated(); lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateRedrawTime(); display->updateRedrawTime();
@ -440,15 +591,14 @@ public:
return; return;
} }
#endif #endif
currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0);
if(increase) currentSat1 = (currentSat1 + 5 <= 255 ? (currentSat1 + 5) : 255); colorHStoRGB(currentHue1*256, currentSat1, col);
else currentSat1 = (currentSat1 - 5 >= 0 ? (currentSat1 - 5) : 0); strip.applyToAllSelected = true;
colorHStoRGB(currentHue1, currentSat1, col); strip.setColor(0, colorFromRgbw(col));
lampUdated(); lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateRedrawTime(); display->updateRedrawTime();
#endif #endif
} }
/* /*
@ -473,20 +623,24 @@ public:
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
/*
void addToJsonState(JsonObject &root) void addToJsonState(JsonObject &root)
{ {
//root["user0"] = userVar0; //root["user0"] = userVar0;
} }
*/
/* /*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
/*
void readFromJsonState(JsonObject &root) void readFromJsonState(JsonObject &root)
{ {
//userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
} }
*/
/** /**
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json * addToConfig() (called from set.cpp) stores persistent properties to cfg.json
@ -514,14 +668,11 @@ public:
DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false; return false;
} }
int8_t newDTpin = pinA; int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA;
int8_t newCLKpin = pinB; int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB;
int8_t newSWpin = pinC; int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC;
enabled = top[FPSTR(_enabled)] | enabled; enabled = top[FPSTR(_enabled)] | enabled;
newDTpin = top[FPSTR(_DT_pin)] | newDTpin;
newCLKpin = top[FPSTR(_CLK_pin)] | newCLKpin;
newSWpin = top[FPSTR(_SW_pin)] | newSWpin;
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {

View File

@ -1216,12 +1216,13 @@ uint16_t WS2812FX::mode_loading(void) {
//American Police Light with all LEDs Red and Blue //American Police Light with all LEDs Red and Blue
uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, uint16_t width) uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2)
{ {
uint16_t delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster uint16_t delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster
uint32_t it = now / map(SEGMENT.speed, 0, 255, delay<<4, delay); uint32_t it = now / map(SEGMENT.speed, 0, 255, delay<<4, delay);
uint16_t offset = it % SEGLEN; uint16_t offset = it % SEGLEN;
uint16_t width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip
if (!width) width = 1; if (!width) width = 1;
for (uint16_t i = 0; i < width; i++) { for (uint16_t i = 0; i < width; i++) {
uint16_t indexR = (offset + i) % SEGLEN; uint16_t indexR = (offset + i) % SEGLEN;
@ -1233,26 +1234,11 @@ uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, uint16_t width)
} }
//American Police Light with all LEDs Red and Blue
uint16_t WS2812FX::mode_police_all()
{
return police_base(RED, BLUE, (SEGLEN>>1));
}
//Police Lights Red and Blue //Police Lights Red and Blue
uint16_t WS2812FX::mode_police() uint16_t WS2812FX::mode_police()
{ {
fill(SEGCOLOR(1)); fill(SEGCOLOR(1));
return police_base(RED, BLUE, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip return police_base(RED, BLUE);
}
//Police All with custom colors
uint16_t WS2812FX::mode_two_areas()
{
fill(SEGCOLOR(2));
return police_base(SEGCOLOR(0), SEGCOLOR(1), ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip
} }
@ -1262,7 +1248,142 @@ uint16_t WS2812FX::mode_two_dots()
fill(SEGCOLOR(2)); fill(SEGCOLOR(2));
uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1); uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1);
return police_base(SEGCOLOR(0), color2, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip return police_base(SEGCOLOR(0), color2);
}
/*
* Fairy, inspired by https://www.youtube.com/watch?v=zeOw5MZWq24
*/
//4 bytes
typedef struct Flasher {
uint16_t stateStart;
uint8_t stateDur;
bool stateOn;
} flasher;
#define FLASHERS_PER_ZONE 6
#define MAX_SHIMMER 92
uint16_t WS2812FX::mode_fairy() {
//set every pixel to a 'random' color from palette (using seed so it doesn't change between frames)
uint16_t PRNG16 = 5100 + _segment_index;
for (uint16_t i = 0; i < SEGLEN; i++) {
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0));
}
//amount of flasher pixels depending on intensity (0: none, 255: every LED)
if (SEGMENT.intensity == 0) return FRAMETIME;
uint8_t flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10
uint16_t numFlashers = (SEGLEN / flasherDistance) +1;
uint16_t dataSize = sizeof(flasher) * numFlashers;
if (!SEGENV.allocateData(dataSize)) return FRAMETIME; //allocation failed
Flasher* flashers = reinterpret_cast<Flasher*>(SEGENV.data);
uint16_t now16 = now & 0xFFFF;
//Up to 11 flashers in one brightness zone, afterwards a new zone for every 6 flashers
uint16_t zones = numFlashers/FLASHERS_PER_ZONE;
if (!zones) zones = 1;
uint8_t flashersInZone = numFlashers/zones;
uint8_t flasherBri[FLASHERS_PER_ZONE*2 -1];
for (uint16_t z = 0; z < zones; z++) {
uint16_t flasherBriSum = 0;
uint16_t firstFlasher = z*flashersInZone;
if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1));
for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) {
uint16_t stateTime = now16 - flashers[f].stateStart;
//random on/off time reached, switch state
if (stateTime > flashers[f].stateDur * 10) {
flashers[f].stateOn = !flashers[f].stateOn;
if (flashers[f].stateOn) {
flashers[f].stateDur = 12 + random8(12 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms
} else {
flashers[f].stateDur = 20 + random8(6 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms
}
//flashers[f].stateDur = 51 + random8(2 + ((255 - SEGMENT.speed) >> 1));
flashers[f].stateStart = now16;
if (stateTime < 255) {
flashers[f].stateStart -= 255 -stateTime; //start early to get correct bri
flashers[f].stateDur += 26 - stateTime/10;
stateTime = 255 - stateTime;
} else {
stateTime = 0;
}
}
if (stateTime > 255) stateTime = 255; //for flasher brightness calculation, fades in first 255 ms of state
//flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? 255-gamma8((510 - stateTime) >> 1) : gamma8((510 - stateTime) >> 1);
flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? stateTime : 255 - (stateTime >> 0);
flasherBriSum += flasherBri[f - firstFlasher];
}
//dim factor, to create "shimmer" as other pixels get less voltage if a lot of flashers are on
uint8_t avgFlasherBri = flasherBriSum / flashersInZone;
uint8_t globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers
for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) {
uint8_t bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255;
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
uint16_t flasherPos = f*flasherDistance;
setPixelColor(flasherPos, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), bri));
for (uint16_t i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) {
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0, globalPeakBri));
}
}
}
return FRAMETIME;
}
/*
* Fairytwinkle. Like Colortwinkle, but starting from all lit and not relying on getPixelColor
* Warning: Uses 4 bytes of segment data per pixel
*/
uint16_t WS2812FX::mode_fairytwinkle() {
uint16_t dataSize = sizeof(flasher) * SEGLEN;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
Flasher* flashers = reinterpret_cast<Flasher*>(SEGENV.data);
uint16_t now16 = now & 0xFFFF;
uint16_t PRNG16 = 5100 + _segment_index;
uint16_t riseFallTime = 400 + (255-SEGMENT.speed)*3;
uint16_t maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1);
for (uint16_t f = 0; f < SEGLEN; f++) {
uint16_t stateTime = now16 - flashers[f].stateStart;
//random on/off time reached, switch state
if (stateTime > flashers[f].stateDur * 100) {
flashers[f].stateOn = !flashers[f].stateOn;
bool init = !flashers[f].stateDur;
if (flashers[f].stateOn) {
flashers[f].stateDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +1;
} else {
flashers[f].stateDur = riseFallTime/100 + random8(3 + ((255 - SEGMENT.speed) >> 6)) +1;
}
flashers[f].stateStart = now16;
stateTime = 0;
if (init) {
flashers[f].stateStart -= riseFallTime; //start lit
flashers[f].stateDur = riseFallTime/100 + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +5; //fire up a little quicker
stateTime = riseFallTime;
}
}
if (flashers[f].stateOn && flashers[f].stateDur > maxDur) flashers[f].stateDur = maxDur; //react more quickly on intensity change
if (stateTime > riseFallTime) stateTime = riseFallTime; //for flasher brightness calculation, fades in first 255 ms of state
uint8_t fadeprog = 255 - ((stateTime * 255) / riseFallTime);
uint8_t flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog);
uint16_t lastR = PRNG16;
uint16_t diff = 0;
while (diff < 0x4000) { //make sure colors of two adjacent LEDs differ enough
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
diff = (PRNG16 > lastR) ? PRNG16 - lastR : lastR - PRNG16;
}
setPixelColor(f, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), flasherBri));
}
return FRAMETIME;
} }
@ -1970,7 +2091,7 @@ uint16_t WS2812FX::mode_colortwinkle()
} }
} }
} }
return FRAMETIME; return FRAMETIME_FIXED;
} }
@ -2755,7 +2876,7 @@ uint16_t WS2812FX::candle(bool multi)
} }
} }
return FRAMETIME; return FRAMETIME_FIXED;
} }
uint16_t WS2812FX::mode_candle() uint16_t WS2812FX::mode_candle()
@ -3783,18 +3904,24 @@ uint16_t WS2812FX::mode_washing_machine(void) {
Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e
*/ */
uint16_t WS2812FX::mode_blends(void) { uint16_t WS2812FX::mode_blends(void) {
uint16_t dataSize = sizeof(uint32_t) * SEGLEN; // max segment length of 56 pixels on 16 segment ESP8266 uint16_t pixelLen = SEGLEN > UINT8_MAX ? UINT8_MAX : SEGLEN;
uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data); uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128);
uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8;
for (int i = 0; i < SEGLEN; i++) { for (int i = 0; i < pixelLen; i++) {
pixels[i] = color_blend(pixels[i], color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_SOLID_WRAP, 255), blendSpeed); pixels[i] = color_blend(pixels[i], color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_SOLID_WRAP, 255), blendSpeed);
setPixelColor(i, pixels[i]);
shift += 3; shift += 3;
} }
uint16_t offset = 0;
for (int i = 0; i < SEGLEN; i++) {
setPixelColor(i, pixels[offset++]);
if (offset > pixelLen) offset = 0;
}
return FRAMETIME; return FRAMETIME;
} }

View File

@ -48,7 +48,8 @@
/* Not used in all effects yet */ /* Not used in all effects yet */
#define WLED_FPS 42 #define WLED_FPS 42
#define FRAMETIME (1000/WLED_FPS) #define FRAMETIME_FIXED (1000/WLED_FPS)
#define FRAMETIME _frametime
/* each segment uses 52 bytes of SRAM memory, so if you're application fails because of /* each segment uses 52 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
@ -71,7 +72,7 @@
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS) #define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)
#define LED_SKIP_AMOUNT 1 #define LED_SKIP_AMOUNT 1
#define MIN_SHOW_DELAY 15 #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
#define NUM_COLORS 3 /* number of colors per segment */ #define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT _segments[_segment_index] #define SEGMENT _segments[_segment_index]
@ -161,14 +162,14 @@
#define FX_MODE_COMET 41 #define FX_MODE_COMET 41
#define FX_MODE_FIREWORKS 42 #define FX_MODE_FIREWORKS 42
#define FX_MODE_RAIN 43 #define FX_MODE_RAIN 43
#define FX_MODE_TETRIX 44 #define FX_MODE_TETRIX 44 //was Merry Christmas prior to 0.12.0 (use "Chase 2" with Red/Green)
#define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_FIRE_FLICKER 45
#define FX_MODE_GRADIENT 46 #define FX_MODE_GRADIENT 46
#define FX_MODE_LOADING 47 #define FX_MODE_LOADING 47
#define FX_MODE_POLICE 48 // candidate for removal (after below three) #define FX_MODE_POLICE 48 // candidate for removal (after below three)
#define FX_MODE_POLICE_ALL 49 // candidate for removal #define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity)
#define FX_MODE_TWO_DOTS 50 #define FX_MODE_TWO_DOTS 50
#define FX_MODE_TWO_AREAS 51 // candidate for removal #define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity)
#define FX_MODE_RUNNING_DUAL 52 #define FX_MODE_RUNNING_DUAL 52
#define FX_MODE_HALLOWEEN 53 // candidate for removal #define FX_MODE_HALLOWEEN 53 // candidate for removal
#define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_CHASE 54
@ -550,9 +551,9 @@ class WS2812FX {
_mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient; _mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient;
_mode[FX_MODE_LOADING] = &WS2812FX::mode_loading; _mode[FX_MODE_LOADING] = &WS2812FX::mode_loading;
_mode[FX_MODE_POLICE] = &WS2812FX::mode_police; _mode[FX_MODE_POLICE] = &WS2812FX::mode_police;
_mode[FX_MODE_POLICE_ALL] = &WS2812FX::mode_police_all; _mode[FX_MODE_FAIRY] = &WS2812FX::mode_fairy;
_mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots; _mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots;
_mode[FX_MODE_TWO_AREAS] = &WS2812FX::mode_two_areas; _mode[FX_MODE_FAIRYTWINKLE] = &WS2812FX::mode_fairytwinkle;
_mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual; _mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual;
_mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween; _mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween;
_mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase; _mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase;
@ -648,12 +649,14 @@ class WS2812FX {
calcGammaTable(float), calcGammaTable(float),
trigger(void), trigger(void),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0, uint16_t offset = UINT16_MAX), setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0, uint16_t offset = UINT16_MAX),
restartRuntime(),
resetSegments(), resetSegments(),
makeAutoSegments(), makeAutoSegments(),
fixInvalidSegments(), fixInvalidSegments(),
setPixelColor(uint16_t n, uint32_t c), setPixelColor(uint16_t n, uint32_t c),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0), setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
show(void), show(void),
setTargetFps(uint8_t fps),
setPixelSegment(uint8_t n), setPixelSegment(uint8_t n),
deserializeMap(uint8_t n=0); deserializeMap(uint8_t n=0);
@ -684,6 +687,7 @@ class WS2812FX {
getActiveSegmentsNum(void), getActiveSegmentsNum(void),
//getFirstSelectedSegment(void), //getFirstSelectedSegment(void),
getMainSegmentId(void), getMainSegmentId(void),
getTargetFps(void),
gamma8(uint8_t), gamma8(uint8_t),
gamma8_cal(uint8_t, float), gamma8_cal(uint8_t, float),
sin_gap(uint16_t), sin_gap(uint16_t),
@ -773,9 +777,9 @@ class WS2812FX {
mode_gradient(void), mode_gradient(void),
mode_loading(void), mode_loading(void),
mode_police(void), mode_police(void),
mode_police_all(void), mode_fairy(void),
mode_two_dots(void), mode_two_dots(void),
mode_two_areas(void), mode_fairytwinkle(void),
mode_running_dual(void), mode_running_dual(void),
mode_bicolor_chase(void), mode_bicolor_chase(void),
mode_tricolor_chase(void), mode_tricolor_chase(void),
@ -855,6 +859,8 @@ class WS2812FX {
uint16_t _usedSegmentData = 0; uint16_t _usedSegmentData = 0;
uint16_t _transitionDur = 750; uint16_t _transitionDur = 750;
uint8_t _targetFps = 42;
uint16_t _frametime = (1000/42);
uint16_t _cumulativeFps = 2; uint16_t _cumulativeFps = 2;
bool bool
@ -878,7 +884,7 @@ class WS2812FX {
chase(uint32_t, uint32_t, uint32_t, bool), chase(uint32_t, uint32_t, uint32_t, bool),
gradient_base(bool), gradient_base(bool),
ripple_base(bool), ripple_base(bool),
police_base(uint32_t, uint32_t, uint16_t), police_base(uint32_t, uint32_t),
running(uint32_t, uint32_t, bool theatre=false), running(uint32_t, uint32_t, bool theatre=false),
tricolor_chase(uint32_t, uint32_t), tricolor_chase(uint32_t, uint32_t),
twinklefox_base(bool), twinklefox_base(bool),
@ -921,6 +927,11 @@ class WS2812FX {
transitionProgress(uint8_t tNr); transitionProgress(uint8_t tNr);
}; };
extern const char JSON_mode_names[];
extern const char JSON_palette_names[];
// the following has been moved to FX_fcn.cpp instead
/*
// WLEDSR: extensions // WLEDSR: extensions
// Technical notes // Technical notes
// =============== // ===============
@ -936,7 +947,7 @@ class WS2812FX {
// - a ! means that the default is used. // - a ! means that the default is used.
// - For sliders: Effect speeds, Effect intensity, Custom 1, Custom 2, Custom 3 // - For sliders: Effect speeds, Effect intensity, Custom 1, Custom 2, Custom 3
// - For colors: Fx color, Background color, Custom // - For colors: Fx color, Background color, Custom
// - For palette: prompt Color palette // - For palette: prompt for color palette OR palette ID if numeric (will hide palette selection)
// //
// Note: If palette is on and no colors are specified 1,2 and 3 is shown in each color circle. // Note: If palette is on and no colors are specified 1,2 and 3 is shown in each color circle.
// If a color is specified, the 1,2 or 3 is replaced by that specification. // If a color is specified, the 1,2 or 3 is replaced by that specification.
@ -992,14 +1003,14 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Fire Flicker", "Fire Flicker",
"Gradient", "Gradient",
"Loading", "Loading",
"Police@!,Width;;", "Police@!,Width;;0",
"Police All@!,Width;;", "Fairy",
"Two Dots@!,Dot size;1,2,Bg;!", "Two Dots@!,Dot size;1,2,Bg;!",
"Two Areas@!,Size;1,2,Bg;!", "Fairy Twinkle",
"Running Dual", "Running Dual",
"Halloween", "Halloween",
"Chase 3@!,Size;1,2,3;", "Chase 3@!,Size;1,2,3;0",
"Tri Wipe@!,Width;1,2,3;", "Tri Wipe@!,Width;1,2,3;0",
"Tri Fade", "Tri Fade",
"Lightning", "Lightning",
"ICU", "ICU",
@ -1008,9 +1019,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Stream 2", "Stream 2",
"Oscillate", "Oscillate",
"Pride 2015", "Pride 2015",
"Juggle@!,Trail;!,!,;!", "Juggle@!=16,Trail=240;!,!,;!",
"Palette@!,;;!", "Palette@!,;;!",
"Fire 2012@Spark rate,Decay;;!", "Fire 2012@Spark rate=120,Decay=64;;!",
"Colorwaves", "Colorwaves",
"Bpm", "Bpm",
"Fill Noise", "Fill Noise",
@ -1027,12 +1038,12 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Twinklefox", "Twinklefox",
"Twinklecat", "Twinklecat",
"Halloween Eyes", "Halloween Eyes",
"Solid Pattern@Fg size,Bg size;Fg,Bg,;", "Solid Pattern@Fg size,Bg size;Fg,Bg,;0",
"Solid Pattern Tri@,Size;1,2,3;", "Solid Pattern Tri@,Size;1,2,3;0",
"Spots@Spread,Width;!,!,;!", "Spots@Spread,Width;!,!,;!",
"Spots Fade@Spread,Width;!,!,;!", "Spots Fade@Spread,Width;!,!,;!",
"Glitter", "Glitter",
"Candle@Flicker rate,Flicker intensity;!,!,;", "Candle@Flicker rate=96,Flicker intensity=224;!,!,;0",
"Fireworks Starburst", "Fireworks Starburst",
"Fireworks 1D@Gravity,Firing side;!,!,;!", "Fireworks 1D@Gravity,Firing side;!,!,;!",
"Bouncing Balls@Gravity,# of balls;!,!,;!", "Bouncing Balls@Gravity,# of balls;!,!,;!",
@ -1046,9 +1057,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Ripple Rainbow", "Ripple Rainbow",
"Heartbeat", "Heartbeat",
"Pacifica", "Pacifica",
"Candle Multi@Flicker rate,Flicker intensity;!,!,;", "Candle Multi@Flicker rate=96,Flicker intensity=224;!,!,;0",
"Solid Glitter@,!;!,,;", "Solid Glitter@,!;!,,;0",
"Sunrise@Time [min],;;", "Sunrise@Time [min]=60,;;0",
"Phased", "Phased",
"Twinkleup@!,Intensity;!,!,;!", "Twinkleup@!,Intensity;!,!,;!",
"Noise Pal", "Noise Pal",
@ -1075,5 +1086,5 @@ const char JSON_palette_names[] PROGMEM = R"=====([
"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", "Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
"Candy2" "Candy2"
])====="; ])=====";
*/
#endif #endif

View File

@ -165,12 +165,12 @@ void WS2812FX::service() {
_triggered = false; _triggered = false;
} }
void WS2812FX::setPixelColor(uint16_t n, uint32_t c) { void IRAM_ATTR WS2812FX::setPixelColor(uint16_t n, uint32_t c) {
setPixelColor(n, R(c), G(c), B(c), W(c)); setPixelColor(n, R(c), G(c), B(c), W(c));
} }
//used to map from segment index to physical pixel, taking into account grouping, offsets, reverse and mirroring //used to map from segment index to physical pixel, taking into account grouping, offsets, reverse and mirroring
uint16_t WS2812FX::realPixelIndex(uint16_t i) { uint16_t IRAM_ATTR WS2812FX::realPixelIndex(uint16_t i) {
int16_t iGroup = i * SEGMENT.groupLength(); int16_t iGroup = i * SEGMENT.groupLength();
/* reverse just an individual segment */ /* reverse just an individual segment */
@ -187,7 +187,7 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) {
return realIndex; return realIndex;
} }
void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) void IRAM_ATTR WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
{ {
if (SEGLEN) {//from segment if (SEGLEN) {//from segment
uint16_t realIndex = realPixelIndex(i); uint16_t realIndex = realPixelIndex(i);
@ -350,6 +350,16 @@ uint16_t WS2812FX::getFps() {
return _cumulativeFps +1; return _cumulativeFps +1;
} }
uint8_t WS2812FX::getTargetFps() {
return _targetFps;
}
void WS2812FX::setTargetFps(uint8_t fps) {
if (fps > 0 && fps <= 120) _targetFps = fps;
//_targetFps = min(max((int)fps,1),120);
_frametime = 1000 / _targetFps;
}
/** /**
* Forces the next frame to be computed on all active segments. * Forces the next frame to be computed on all active segments.
*/ */
@ -442,14 +452,14 @@ void WS2812FX::setBrightness(uint8_t b) {
if (gammaCorrectBri) b = gamma8(b); if (gammaCorrectBri) b = gamma8(b);
if (_brightness == b) return; if (_brightness == b) return;
_brightness = b; _brightness = b;
_segment_index = 0;
if (_brightness == 0) { //unfreeze all segments on power off if (_brightness == 0) { //unfreeze all segments on power off
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{ {
_segments[i].setOption(SEG_OPTION_FREEZE, false); _segments[i].setOption(SEG_OPTION_FREEZE, false);
} }
} }
if (SEGENV.next_time > millis() + 22 && millis() - _lastShow > MIN_SHOW_DELAY) show();//apply brightness change immediately if no refresh soon unsigned long t = millis();
if (_segment_runtimes[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon
} }
uint8_t WS2812FX::getMode(void) { uint8_t WS2812FX::getMode(void) {
@ -606,8 +616,14 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
_segment_runtimes[n].reset(); _segment_runtimes[n].reset();
} }
void WS2812FX::restartRuntime() {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) {
_segment_runtimes[i].reset();
}
}
void WS2812FX::resetSegments() { void WS2812FX::resetSegments() {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) if (_segments[i].name) delete _segments[i].name; for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) if (_segments[i].name) delete[] _segments[i].name;
mainSegment = 0; mainSegment = 0;
memset(_segments, 0, sizeof(_segments)); memset(_segments, 0, sizeof(_segments));
//memset(_segment_runtimes, 0, sizeof(_segment_runtimes)); //memset(_segment_runtimes, 0, sizeof(_segment_runtimes));
@ -701,14 +717,35 @@ bool WS2812FX::checkSegmentAlignment() {
} }
//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply) //After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply)
//Note: If called in an interrupt (e.g. JSON API), it must be reset with "setPixelColor(255)",
//otherwise it can lead to a crash on ESP32 because _segment_index is modified while in use by the main thread
#ifdef ARDUINO_ARCH_ESP32
uint8_t _segment_index_prev = 0;
uint16_t _virtualSegmentLength_prev = 0;
bool _ps_set = false;
#endif
void WS2812FX::setPixelSegment(uint8_t n) void WS2812FX::setPixelSegment(uint8_t n)
{ {
if (n < MAX_NUM_SEGMENTS) { if (n < MAX_NUM_SEGMENTS) {
#ifdef ARDUINO_ARCH_ESP32
if (!_ps_set) {
_segment_index_prev = _segment_index;
_virtualSegmentLength_prev = _virtualSegmentLength;
_ps_set = true;
}
#endif
_segment_index = n; _segment_index = n;
_virtualSegmentLength = SEGMENT.length(); _virtualSegmentLength = SEGMENT.virtualLength();
} else { } else {
_segment_index = 0;
_virtualSegmentLength = 0; _virtualSegmentLength = 0;
#ifdef ARDUINO_ARCH_ESP32
if (_ps_set) {
_segment_index = _segment_index_prev;
_virtualSegmentLength = _virtualSegmentLength_prev;
_ps_set = false;
}
#endif
} }
} }
@ -738,17 +775,17 @@ void WS2812FX::setTransitionMode(bool t)
unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++) for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{ {
_segment_index = i; _segments[i].setOption(SEG_OPTION_TRANSITIONAL, t);
SEGMENT.setOption(SEG_OPTION_TRANSITIONAL, t);
if (t && SEGMENT.mode == FX_MODE_STATIC && SEGENV.next_time > waitMax) SEGENV.next_time = waitMax; if (t && _segments[i].mode == FX_MODE_STATIC && _segment_runtimes[i].next_time > waitMax)
_segment_runtimes[i].next_time = waitMax;
} }
} }
/* /*
* color blend function * color blend function
*/ */
uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) { uint32_t IRAM_ATTR WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) {
if(blend == 0) return color1; if(blend == 0) return color1;
uint16_t blendmax = b16 ? 0xFFFF : 0xFF; uint16_t blendmax = b16 ? 0xFFFF : 0xFF;
if(blend == blendmax) return color2; if(blend == blendmax) return color2;
@ -851,13 +888,13 @@ void WS2812FX::blur(uint8_t blur_amount)
} }
} }
uint16_t WS2812FX::triwave16(uint16_t in) uint16_t IRAM_ATTR WS2812FX::triwave16(uint16_t in)
{ {
if (in < 0x8000) return in *2; if (in < 0x8000) return in *2;
return 0xFFFF - (in - 0x8000)*2; return 0xFFFF - (in - 0x8000)*2;
} }
uint8_t WS2812FX::sin_gap(uint16_t in) { uint8_t IRAM_ATTR WS2812FX::sin_gap(uint16_t in) {
if (in & 0x100) return 0; if (in & 0x100) return 0;
//if (in > 255) return 0; //if (in > 255) return 0;
return sin8(in + 192); //correct phase shift of sine so that it starts and stops at 0 return sin8(in + 192); //correct phase shift of sine so that it starts and stops at 0
@ -924,13 +961,13 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) {
} }
uint32_t WS2812FX::crgb_to_col(CRGB fastled) uint32_t IRAM_ATTR WS2812FX::crgb_to_col(CRGB fastled)
{ {
return RGBW32(fastled.red, fastled.green, fastled.blue, 0); return RGBW32(fastled.red, fastled.green, fastled.blue, 0);
} }
CRGB WS2812FX::col_to_crgb(uint32_t color) CRGB IRAM_ATTR WS2812FX::col_to_crgb(uint32_t color)
{ {
CRGB fastled_col; CRGB fastled_col;
fastled_col.red = R(color); fastled_col.red = R(color);
@ -1053,7 +1090,7 @@ void WS2812FX::handle_palette(void)
* @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling)
* @returns Single color from palette * @returns Single color from palette
*/ */
uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) uint32_t IRAM_ATTR WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri)
{ {
if (SEGMENT.palette == 0 && mcol < 3) { if (SEGMENT.palette == 0 && mcol < 3) {
uint32_t color = SEGCOLOR(mcol); uint32_t color = SEGCOLOR(mcol);
@ -1094,11 +1131,7 @@ void WS2812FX::deserializeMap(uint8_t n) {
return; return;
} }
#ifdef WLED_USE_DYNAMIC_JSON if (!requestJSONBufferLock(7)) return;
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(5)) return;
#endif
DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINT(F("Reading LED map from "));
DEBUG_PRINTLN(fileName); DEBUG_PRINTLN(fileName);
@ -1182,3 +1215,158 @@ WS2812FX* WS2812FX::instance = nullptr;
int16_t Bus::_cct = -1; int16_t Bus::_cct = -1;
uint8_t Bus::_cctBlend = 0; uint8_t Bus::_cctBlend = 0;
uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL; uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL;
// WLEDSR: extensions
// Technical notes
// ===============
// If an effect name is followed by an @, slider and color control is effective.
// See setSliderAndColorControl in index.js for implementation
// If not effective then:
// - For AC effects (id<128) 2 sliders and 3 colors and the palette will be shown
// - For SR effects (id>128) 5 sliders and 3 colors and the palette will be shown
// If effective (@)
// - a ; seperates slider controls (left) from color controls (middle) and palette control (right)
// - if left, middle or right is empty no controls are shown
// - a , seperates slider controls (max 5) or color controls (max 3). Palette has only one value
// - a ! means that the default is used.
// - For sliders: Effect speeds, Effect intensity, Custom 1, Custom 2, Custom 3
// - For colors: Fx color, Background color, Custom
// - For palette: prompt for color palette OR palette ID if numeric (will hide palette selection)
//
// Note: If palette is on and no colors are specified 1,2 and 3 is shown in each color circle.
// If a color is specified, the 1,2 or 3 is replaced by that specification.
// Note: Effects can override default pattern behaviour
// - FadeToBlack can override the background setting
// - Defining SEGCOL(<i>) can override a specific palette using these values (e.g. Color Gradient)
const char JSON_mode_names[] PROGMEM = R"=====([
"Solid",
"Blink@!,;!,!,;!",
"Breathe@!,;!,!;!",
"Wipe@!,!;!,!,;!",
"Wipe Random@!,;;!",
"Random Colors@!,Fade time;;!",
"Sweep@!,!;!,!,;!",
"Dynamic",
"Colorloop@!,Saturation;;!",
"Rainbow",
"Scan@!,# of dots;!,!,;!",
"Scan Dual@!,# of dots;!,!,;!",
"Fade",
"Theater@!,Gap size;!,!,;!",
"Theater Rainbow",
"Running@!,Wave width;!,!,;!",
"Saw@!,Width;!,!,;!",
"Twinkle",
"Dissolve",
"Dissolve Rnd",
"Sparkle",
"Sparkle Dark",
"Sparkle+",
"Strobe",
"Strobe Rainbow",
"Strobe Mega",
"Blink Rainbow",
"Android",
"Chase",
"Chase Random",
"Chase Rainbow",
"Chase Flash",
"Chase Flash Rnd",
"Rainbow Runner",
"Colorful",
"Traffic Light",
"Sweep Random",
"Chase 2@!,Width;!,!,;!",
"Aurora",
"Stream",
"Scanner",
"Lighthouse",
"Fireworks",
"Rain",
"Tetrix@!,Width;!,!,;!",
"Fire Flicker",
"Gradient",
"Loading",
"Police@!,Width;;0",
"Fairy",
"Two Dots@!,Dot size;1,2,Bg;!",
"Fairy Twinkle",
"Running Dual",
"Halloween",
"Chase 3@!,Size;1,2,3;0",
"Tri Wipe@!,Width;1,2,3;0",
"Tri Fade",
"Lightning",
"ICU",
"Multi Comet",
"Scanner Dual",
"Stream 2",
"Oscillate",
"Pride 2015",
"Juggle@!=16,Trail=240;!,!,;!",
"Palette@!,;;!",
"Fire 2012@Spark rate=120,Decay=64;;!",
"Colorwaves",
"Bpm",
"Fill Noise",
"Noise 1",
"Noise 2",
"Noise 3",
"Noise 4",
"Colortwinkles",
"Lake",
"Meteor@!,Trail length;!,!,;!",
"Meteor Smooth@!,Trail length;!,!,;!",
"Railway",
"Ripple",
"Twinklefox",
"Twinklecat",
"Halloween Eyes",
"Solid Pattern@Fg size,Bg size;Fg,Bg,;!=0",
"Solid Pattern Tri@,Size;1,2,3;!=0",
"Spots@Spread,Width;!,!,;!",
"Spots Fade@Spread,Width;!,!,;!",
"Glitter",
"Candle@Flicker rate=96,Flicker intensity=224;!,!,;0",
"Fireworks Starburst",
"Fireworks 1D@Gravity,Firing side;!,!,;!",
"Bouncing Balls@Gravity,# of balls;!,!,;!",
"Sinelon",
"Sinelon Dual",
"Sinelon Rainbow",
"Popcorn@",
"Drip@Gravity,# of drips;!,!;!",
"Plasma@Phase,;1,2,3;!",
"Percent@,% of fill;!,!,;!",
"Ripple Rainbow",
"Heartbeat",
"Pacifica",
"Candle Multi@Flicker rate=96,Flicker intensity=224;!,!,;0",
"Solid Glitter@,!;!,,;0",
"Sunrise@Time [min]=60,;;0",
"Phased",
"Twinkleup@!,Intensity;!,!,;!",
"Noise Pal",
"Sine",
"Phased Noise",
"Flow",
"Chunchun@!,Gap size;!,!,;!",
"Dancing Shadows",
"Washing Machine",
"Candy Cane",
"Blends",
"TV Simulator",
"Dynamic Smooth"
])=====";
const char JSON_palette_names[] PROGMEM = R"=====([
"Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean",
"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash",
"Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64",
"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn",
"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura",
"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf",
"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
"Candy2"
])=====";

View File

@ -91,7 +91,7 @@ class Bus {
virtual void setBrightness(uint8_t b) {} virtual void setBrightness(uint8_t b) {}
virtual void cleanup() {} virtual void cleanup() {}
virtual uint8_t getPins(uint8_t* pinArray) { return 0; } virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
inline uint16_t getLength() { return _len; } virtual uint16_t getLength() { return _len; }
virtual void setColorOrder() {} virtual void setColorOrder() {}
virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
virtual uint8_t skippedLeds() { return 0; } virtual uint8_t skippedLeds() { return 0; }
@ -220,7 +220,7 @@ class BusDigital : public Bus {
return _colorOrder; return _colorOrder;
} }
inline uint16_t getLength() { uint16_t getLength() {
return _len - _skip; return _len - _skip;
} }
@ -318,8 +318,12 @@ class BusPwm : public Bus {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5; cct = (approximateKelvinFromRGB(c) - 1900) >> 5;
} }
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
uint8_t ww, cw; uint8_t ww, cw;
#ifdef WLED_USE_IC_CCT
ww = w;
cw = cct;
#else
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
if (cct < _cctBlend) ww = 255; if (cct < _cctBlend) ww = 255;
else ww = ((255-cct) * 255) / (255 - _cctBlend); else ww = ((255-cct) * 255) / (255 - _cctBlend);
@ -328,6 +332,7 @@ class BusPwm : public Bus {
ww = (w * ww) / 255; //brightness scaling ww = (w * ww) / 255; //brightness scaling
cw = (w * cw) / 255; cw = (w * cw) / 255;
#endif
switch (_type) { switch (_type) {
case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
@ -391,7 +396,7 @@ class BusPwm : public Bus {
private: private:
uint8_t _pins[5] = {255, 255, 255, 255, 255}; uint8_t _pins[5] = {255, 255, 255, 255, 255};
uint8_t _data[5] = {255, 255, 255, 255, 255}; uint8_t _data[5] = {0};
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
uint8_t _ledcStart = 255; uint8_t _ledcStart = 255;
#endif #endif
@ -448,7 +453,7 @@ class BusNetwork : public Bus {
void setPixelColor(uint16_t pix, uint32_t c) { void setPixelColor(uint16_t pix, uint32_t c) {
if (!_valid || pix >= _len) return; if (!_valid || pix >= _len) return;
if (_rgbw) c = autoWhiteCalc(c); if (isRgbw()) c = autoWhiteCalc(c);
if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
uint16_t offset = pix * _UDPchannels; uint16_t offset = pix * _UDPchannels;
_data[offset] = R(c); _data[offset] = R(c);

View File

@ -20,7 +20,7 @@ void shortPressAction(uint8_t b)
default: ++effectCurrent %= strip.getModeCount(); colorUpdated(CALL_MODE_BUTTON); break; default: ++effectCurrent %= strip.getModeCount(); colorUpdated(CALL_MODE_BUTTON); break;
} }
} else { } else {
applyPreset(macroButton[b], CALL_MODE_BUTTON); applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
} }
// publish MQTT message // publish MQTT message
@ -39,7 +39,7 @@ void longPressAction(uint8_t b)
default: bri += 8; colorUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action default: bri += 8; colorUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action
} }
} else { } else {
applyPreset(macroLongPress[b], CALL_MODE_BUTTON); applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
} }
// publish MQTT message // publish MQTT message
@ -58,7 +58,7 @@ void doublePressAction(uint8_t b)
default: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break; default: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
} }
} else { } else {
applyPreset(macroDoublePress[b], CALL_MODE_BUTTON); applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET);
} }
// publish MQTT message // publish MQTT message
@ -105,12 +105,12 @@ void handleSwitch(uint8_t b)
if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
if (!buttonPressedBefore[b]) { // on -> off if (!buttonPressedBefore[b]) { // on -> off
if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON); if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
else { //turn on else { //turn on
if (!bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);} if (!bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);}
} }
} else { // off -> on } else { // off -> on
if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON); if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
else { //turn off else { //turn off
if (bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);} if (bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);}
} }

View File

@ -88,6 +88,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(cctFromRgb, hw_led[F("cr")]); CJSON(cctFromRgb, hw_led[F("cr")]);
CJSON(strip.cctBlending, hw_led[F("cb")]); CJSON(strip.cctBlending, hw_led[F("cb")]);
Bus::setCCTBlend(strip.cctBlending); Bus::setCCTBlend(strip.cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
//strip.setTargetFps(hw_led["fps"].isNull() ? WLED_FPS : hw_led["fps"].as<byte>());
JsonArray ins = hw_led["ins"]; JsonArray ins = hw_led["ins"];
@ -242,8 +244,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(receiveNotificationColor, if_sync_recv["col"]); CJSON(receiveNotificationColor, if_sync_recv["col"]);
CJSON(receiveNotificationEffects, if_sync_recv["fx"]); CJSON(receiveNotificationEffects, if_sync_recv["fx"]);
CJSON(receiveGroups, if_sync_recv["grp"]); CJSON(receiveGroups, if_sync_recv["grp"]);
CJSON(receiveSegmentOptions, if_sync_recv["seg"]);
//! following line might be a problem if called after boot //! following line might be a problem if called after boot
receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects || receiveSegmentOptions);
JsonObject if_sync_send = if_sync["send"]; JsonObject if_sync_send = if_sync["send"];
prev = notifyDirectDefault; prev = notifyDirectDefault;
@ -378,7 +381,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
int act = timer["en"] | actPrev; int act = timer["en"] | actPrev;
if (act) timerWeekday[it]++; if (act) timerWeekday[it]++;
} }
if (it<8) {
CJSON(timerMonth[it], timer[F("mon")]);
CJSON(timerDay[it], timer[F("day")]);
}
it++; it++;
} }
@ -429,11 +435,7 @@ void deserializeConfigFromFS() {
return; return;
} }
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(1)) return; if (!requestJSONBufferLock(1)) return;
#endif
DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
@ -457,11 +459,7 @@ void serializeConfig() {
DEBUG_PRINTLN(F("Writing settings to /cfg.json...")); DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(2)) return; if (!requestJSONBufferLock(2)) return;
#endif
JsonArray rev = doc.createNestedArray("rev"); JsonArray rev = doc.createNestedArray("rev");
rev.add(1); //major settings revision rev.add(1); //major settings revision
@ -543,6 +541,7 @@ void serializeConfig() {
hw_led["cct"] = correctWB; hw_led["cct"] = correctWB;
hw_led[F("cr")] = cctFromRgb; hw_led[F("cr")] = cctFromRgb;
hw_led[F("cb")] = strip.cctBlending; hw_led[F("cb")] = strip.cctBlending;
hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getAutoWhiteMode(); hw_led[F("rgbwm")] = Bus::getAutoWhiteMode();
JsonArray hw_led_ins = hw_led.createNestedArray("ins"); JsonArray hw_led_ins = hw_led.createNestedArray("ins");
@ -632,6 +631,7 @@ void serializeConfig() {
if_sync_recv["col"] = receiveNotificationColor; if_sync_recv["col"] = receiveNotificationColor;
if_sync_recv["fx"] = receiveNotificationEffects; if_sync_recv["fx"] = receiveNotificationEffects;
if_sync_recv["grp"] = receiveGroups; if_sync_recv["grp"] = receiveGroups;
if_sync_recv["seg"] = receiveSegmentOptions;
JsonObject if_sync_send = if_sync.createNestedObject("send"); JsonObject if_sync_send = if_sync.createNestedObject("send");
if_sync_send[F("dir")] = notifyDirect; if_sync_send[F("dir")] = notifyDirect;
@ -744,6 +744,10 @@ void serializeConfig() {
timers_ins0["min"] = timerMinutes[i]; timers_ins0["min"] = timerMinutes[i];
timers_ins0["macro"] = timerMacro[i]; timers_ins0["macro"] = timerMacro[i];
timers_ins0[F("dow")] = timerWeekday[i] >> 1; timers_ins0[F("dow")] = timerWeekday[i] >> 1;
if (i<8) {
timers_ins0[F("mon")] = timerMonth[i];
timers_ins0[F("day")] = timerDay[i];
}
} }
JsonObject ota = doc.createNestedObject("ota"); JsonObject ota = doc.createNestedObject("ota");
@ -780,11 +784,7 @@ void serializeConfig() {
bool deserializeConfigSec() { bool deserializeConfigSec() {
DEBUG_PRINTLN(F("Reading settings from /wsec.json...")); DEBUG_PRINTLN(F("Reading settings from /wsec.json..."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(3)) return false; if (!requestJSONBufferLock(3)) return false;
#endif
bool success = readObjectFromFile("/wsec.json", nullptr, &doc); bool success = readObjectFromFile("/wsec.json", nullptr, &doc);
if (!success) { if (!success) {
@ -829,11 +829,7 @@ bool deserializeConfigSec() {
void serializeConfigSec() { void serializeConfigSec() {
DEBUG_PRINTLN(F("Writing settings to /wsec.json...")); DEBUG_PRINTLN(F("Writing settings to /wsec.json..."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(4)) return; if (!requestJSONBufferLock(4)) return;
#endif
JsonObject nw = doc.createNestedObject("nw"); JsonObject nw = doc.createNestedObject("nw");

View File

@ -240,7 +240,7 @@ void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_M
float low = minf(rgb[0],minf(rgb[1],rgb[2])); float low = minf(rgb[0],minf(rgb[1],rgb[2]));
float high = maxf(rgb[0],maxf(rgb[1],rgb[2])); float high = maxf(rgb[0],maxf(rgb[1],rgb[2]));
if (high < 0.1f) return; if (high < 0.1f) return;
float sat = 100.0f * ((high - low) / high);; // maximum saturation is 100 (corrected from 255) float sat = 100.0f * ((high - low) / high); // maximum saturation is 100 (corrected from 255)
rgb[3] = (byte)((255.0f - sat) / 255.0f * (rgb[0] + rgb[1] + rgb[2]) / 3); rgb[3] = (byte)((255.0f - sat) / 255.0f * (rgb[0] + rgb[1] + rgb[2]) / 3);
} }
*/ */

View File

@ -64,6 +64,7 @@
#define USERMOD_ID_SEVEN_SEGMENT_DISPLAY 21 //Usermod "usermod_v2_seven_segment_display.h" #define USERMOD_ID_SEVEN_SEGMENT_DISPLAY 21 //Usermod "usermod_v2_seven_segment_display.h"
#define USERMOD_RGB_ROTARY_ENCODER 22 //Usermod "rgb-rotary-encoder.h" #define USERMOD_RGB_ROTARY_ENCODER 22 //Usermod "rgb-rotary-encoder.h"
#define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h" #define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h"
#define USERMOD_ID_SSDR 24 //Usermod "usermod_v2_seven_segment_display_reloaded.h"
//Access point behavior //Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@ -74,7 +75,7 @@
//Notifier callMode //Notifier callMode
#define CALL_MODE_INIT 0 //no updates on init, can be used to disable updates #define CALL_MODE_INIT 0 //no updates on init, can be used to disable updates
#define CALL_MODE_DIRECT_CHANGE 1 #define CALL_MODE_DIRECT_CHANGE 1
#define CALL_MODE_BUTTON 2 #define CALL_MODE_BUTTON 2 //default button actions applied to selected segments
#define CALL_MODE_NOTIFICATION 3 #define CALL_MODE_NOTIFICATION 3
#define CALL_MODE_NIGHTLIGHT 4 #define CALL_MODE_NIGHTLIGHT 4
#define CALL_MODE_NO_NOTIFY 5 #define CALL_MODE_NO_NOTIFY 5
@ -84,6 +85,7 @@
#define CALL_MODE_BLYNK 9 #define CALL_MODE_BLYNK 9
#define CALL_MODE_ALEXA 10 #define CALL_MODE_ALEXA 10
#define CALL_MODE_WS_SEND 11 //special call mode, not for notifier, updates websocket only #define CALL_MODE_WS_SEND 11 //special call mode, not for notifier, updates websocket only
#define CALL_MODE_BUTTON_PRESET 12 //button/IR JSON preset/macro
//RGB to RGBW conversion mode //RGB to RGBW conversion mode
#define RGBW_MODE_MANUAL_ONLY 0 //No automatic white channel calculation. Manual white channel slider #define RGBW_MODE_MANUAL_ONLY 0 //No automatic white channel calculation. Manual white channel slider
@ -301,6 +303,8 @@
#define JSON_BUFFER_SIZE 20480 #define JSON_BUFFER_SIZE 20480
#endif #endif
#define MIN_HEAP_SIZE (MAX_LED_MEMORY+2048)
// Maximum size of node map (list of other WLED instances) // Maximum size of node map (list of other WLED instances)
#ifdef ESP8266 #ifdef ESP8266
#define WLED_MAX_NODES 24 #define WLED_MAX_NODES 24

View File

@ -212,8 +212,9 @@ button {
transform: rotate(0deg); transform: rotate(0deg);
transition: transform 0.3s; transition: transform 0.3s;
position: absolute; position: absolute;
top: 8px; top: 0;
right: 8px; right: 0;
padding: 8px;
} }
.exp { .exp {

View File

@ -7,12 +7,47 @@
<meta content="yes" name="apple-mobile-web-app-capable"> <meta content="yes" name="apple-mobile-web-app-capable">
<link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAGACGAAAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAAE1JREFUOI1j/P//PwOxgNGeAUMxE9G6cQCKDWAhpADZ2f8PMjBS3QW08QK20KaZC2gfC9hCnqouoNgARgY7zMxAyNlUdQHlXiAlO2MDAD63EVqNHAe0AAAAAElFTkSuQmCC"/> <link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAGACGAAAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAAE1JREFUOI1j/P//PwOxgNGeAUMxE9G6cQCKDWAhpADZ2f8PMjBS3QW08QK20KaZC2gfC9hCnqouoNgARgY7zMxAyNlUdQHlXiAlO2MDAD63EVqNHAe0AAAAAElFTkSuQmCC"/>
<title>WLED</title> <title>WLED</title>
<script>function feedback(){}</script> <script>
function feedback(){}
// instead of including [script src="iro.js"][/script] and [script src="rangetouch.js"][/script]
// (which would be inlined by nodeJS inliner during minimization and compression) we need to load them dynamically
// the following is needed to load iro.js and rangetouch.js as consecutive requests to allow ESP8266
// to keep up with requests (if requests happent too fast some may not get processed)
// it will also call onLoad() after last is loaded (it was removed from [body onload="onLoad()"]).
var h = document.getElementsByTagName('head')[0];
var l = document.createElement('script');
l.type = 'application/javascript';
l.src = 'iro.js';
l.addEventListener('load', (e) => {
// after iro is loaded initialize global variable
cpick = new iro.ColorPicker("#picker", {
width: 260,
wheelLightness: false,
wheelAngle: 270,
wheelDirection: "clockwise",
layout: [{
component: iro.ui.Wheel,
options: {}
}]
});
cpick.on("input:end", () => {setColor(1);});
cpick.on("color:change", () => {updatePSliders()});
var l = document.createElement('script');
l.type = 'application/javascript';
l.src = 'rangetouch.js';
l.addEventListener('load', (e) => {
// after rangetouch is loaded initialize global variable
ranges = RangeTouch.setup('input[type="range"]', {});
onLoad(); // start processing UI
});
//h.appendChild(l); // if this fires too quickly for ESP8266 use next line
setTimeout(function(){h.appendChild(l)},50);
});
setTimeout(function(){h.appendChild(l)},50);
</script>
<link rel="stylesheet" href="index.css"> <link rel="stylesheet" href="index.css">
<script src="iro.js"></script>
<script src="rangetouch.js"></script>
</head> </head>
<body onload="onLoad()"> <body>
<div id="cv" class="overlay">Loading WLED UI...</div> <div id="cv" class="overlay">Loading WLED UI...</div>
<noscript><div class="overlay" style="opacity:1;">Sorry, WLED UI needs JavaScript!</div></noscript> <noscript><div class="overlay" style="opacity:1;">Sorry, WLED UI needs JavaScript!</div></noscript>

View File

@ -8,24 +8,23 @@ var expanded = [false];
var powered = [true]; var powered = [true];
var nlDur = 60, nlTar = 0; var nlDur = 60, nlTar = 0;
var nlMode = false; var nlMode = false;
var selectedFx = 0; var selectedFx = 0, prevFx = -1;
var selectedPal = 0; var selectedPal = 0;
var sliderControl = ""; //WLEDSR: used by togglePcMode var sliderControl = ""; //WLEDSR: used by togglePcMode
var csel = 0; var csel = 0;
var currentPreset = -1; var currentPreset = -1, prevPS = -1;
var lastUpdate = 0; var lastUpdate = 0;
var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0; var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0;
var pcMode = false, pcModeA = false, lastw = 0; var pcMode = false, pcModeA = false, lastw = 0;
var tr = 7; var tr = 7;
var d = document; var d = document;
const ranges = RangeTouch.setup('input[type="range"]', {});
var palettesData; var palettesData;
var fxdata = []; var fxdata = [];
var pJson = {}, eJson = {}, lJson = {}; var pJson = {}, eJson = {}, lJson = {};
var pN = "", pI = 0, pNum = 0; var pN = "", pI = 0, pNum = 0;
var pmt = 1, pmtLS = 0, pmtLast = 0; var pmt = 1, pmtLS = 0, pmtLast = 0;
var lastinfo = {}; var lastinfo = {};
var ws; var ws, cpick, ranges;
var cfg = { var cfg = {
theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}}, theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, comp :{colors:{picker: true, rgb: false, quick: true, hex: false},
@ -39,17 +38,6 @@ var hol = [
[2024,2,31,2,"https://aircoookie.github.io/easter.png"] [2024,2,31,2,"https://aircoookie.github.io/easter.png"]
]; ];
var cpick = new iro.ColorPicker("#picker", {
width: 260,
wheelLightness: false,
wheelAngle: 270,
wheelDirection: "clockwise",
layout: [{
component: iro.ui.Wheel,
options: {}
}]
});
function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();} function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();}
function sCol(na, col) {d.documentElement.style.setProperty(na, col);} function sCol(na, col) {d.documentElement.style.setProperty(na, col);}
function gId(c) {return d.getElementById(c);} function gId(c) {return d.getElementById(c);}
@ -221,7 +209,7 @@ function onLoad()
.catch(function (error) { .catch(function (error) {
console.log("holidays.json does not contain array of holidays. Defaults loaded."); console.log("holidays.json does not contain array of holidays. Defaults loaded.");
}) })
.finally(function(){ .finally(()=>{
loadBg(cfg.theme.bg.url); loadBg(cfg.theme.bg.url);
}); });
} else } else
@ -232,10 +220,6 @@ function onLoad()
for (var i = 0; i < cd.length; i++) cd[i].style.backgroundColor = "rgb(0, 0, 0)"; for (var i = 0; i < cd.length; i++) cd[i].style.backgroundColor = "rgb(0, 0, 0)";
selectSlot(0); selectSlot(0);
updateTablinks(0); updateTablinks(0);
cpick.on("input:end", function() {
setColor(1);
});
cpick.on("color:change", updatePSliders);
pmtLS = localStorage.getItem('wledPmt'); pmtLS = localStorage.getItem('wledPmt');
// Load initial data // Load initial data
@ -244,7 +228,6 @@ function onLoad()
loadFX(()=>{ loadFX(()=>{
loadFXData(); loadFXData();
loadPresets(()=>{ loadPresets(()=>{
//if (isObj(lastinfo) && isEmpty(lastinfo)) loadInfo(requestJson); // if not filled by WS
requestJson(); requestJson();
}); });
}); });
@ -288,7 +271,7 @@ function showToast(text, error = false)
x.classList.add(error ? "error":"show"); x.classList.add(error ? "error":"show");
clearTimeout(timeout); clearTimeout(timeout);
x.style.animation = 'none'; x.style.animation = 'none';
timeout = setTimeout(function(){ x.classList.remove("show"); }, 2900); timeout = setTimeout(()=>{ x.classList.remove("show"); }, 2900);
if (error) console.log(text); if (error) console.log(text);
} }
@ -303,7 +286,7 @@ function showErrorToast()
showToast('Connection to light failed!', true); showToast('Connection to light failed!', true);
} }
function clearErrorToast() {gId("toast").className = gId("toast").className.replace("error", "");} function clearErrorToast() {gId("toast").classList.remove("error");}
function getRuntimeStr(rt) function getRuntimeStr(rt)
{ {
@ -515,7 +498,7 @@ function loadFXData(callback = null)
clearErrorToast(); clearErrorToast();
fxdata = json||[]; fxdata = json||[];
// add default value for Solid // add default value for Solid
fxdata.shift(); fxdata.shift()
fxdata.unshift("@;!;"); fxdata.unshift("@;!;");
}) })
.catch(function (error) { .catch(function (error) {
@ -602,41 +585,7 @@ function parseInfo() {
pmt = li.fs.pmt; pmt = li.fs.pmt;
cct = li.leds.cct; cct = li.leds.cct;
} }
/*
function loadInfo(callback=null)
{
var url = (loc?`http://${locip}`:'') + '/json/info';
var useWs = (ws && ws.readyState === WebSocket.OPEN);
if (useWs) {
ws.send('{"v":true}');
return;
}
fetch(url, {
method: 'get'
})
.then(res => {
if (!res.ok) showToast('Could not load Info!', true);
return res.json();
})
.then(json => {
clearErrorToast();
lastinfo = json;
parseInfo();
showNodes();
if (isInfo) populateInfo(json);
reqsLegal = true;
if (!ws && lastinfo.ws > -1) setTimeout(makeWS,500);
})
.catch(function (error) {
showToast(error, true);
console.log(error);
})
.finally(()=>{
if (callback) callback();
updateUI();
});
}
*/
function populateInfo(i) function populateInfo(i)
{ {
var cn=""; var cn="";
@ -762,7 +711,7 @@ function populateSegments(s)
for (var i = 0; i <= lSeg; i++) { for (var i = 0; i <= lSeg; i++) {
updateLen(i); updateLen(i);
updateTrail(gId(`seg${i}bri`)); updateTrail(gId(`seg${i}bri`));
gId(`segr${lSeg}`).style.display = "none"; gId(`segr${i}`).style.display = "none";
} }
if (segCount < 2) gId(`segd${lSeg}`).style.display = "none"; if (segCount < 2) gId(`segd${lSeg}`).style.display = "none";
if (!noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value)<ledCount) gId(`segr${lSeg}`).style.display = "inline"; if (!noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value)<ledCount) gId(`segr${lSeg}`).style.display = "inline";
@ -780,7 +729,7 @@ function populateEffects()
effects.unshift({ effects.unshift({
"id": 0, "id": 0,
"name": "Solid@;!;" "name": "Solid@;!;0"
}); });
for (let i = 0; i < effects.length; i++) { for (let i = 0; i < effects.length; i++) {
@ -883,15 +832,9 @@ function genPalPrevCss(id)
} else { } else {
if (selColors) { if (selColors) {
let e = element[1] - 1; let e = element[1] - 1;
//if (Array.isArray(selColors[e])) {
r = selColors[e][0]; r = selColors[e][0];
g = selColors[e][1]; g = selColors[e][1];
b = selColors[e][2]; b = selColors[e][2];
//} else {
// r = (selColors[e]>>16) & 0xFF;
// g = (selColors[e]>> 8) & 0xFF;
// b = (selColors[e] ) & 0xFF;
//}
} }
} }
if (index === false) { if (index === false) {
@ -1105,8 +1048,10 @@ function updateSelectedFx()
var selectedEffect = parent.querySelector(`.lstI[data-id="${selectedFx}"]`); var selectedEffect = parent.querySelector(`.lstI[data-id="${selectedFx}"]`);
if (selectedEffect) { if (selectedEffect) {
selectedEffect.classList.add('selected'); selectedEffect.classList.add('selected');
var fx = (selectedFx != prevFx) && currentPreset==-1; //effect changed & preset==none
var ps = (prevPS != currentPreset) && currentPreset==-1; // preset changed & preset==none
// WLEDSR: extract the Slider and color control string from the HTML element and set it. // WLEDSR: extract the Slider and color control string from the HTML element and set it.
setSliderAndColorControl(selectedFx); setSliderAndColorControl(selectedFx, (fx || ps));
} }
} }
@ -1172,6 +1117,7 @@ function readState(s,command=false)
nlTar = s.nl.tbri; nlTar = s.nl.tbri;
nlFade = s.nl.fade; nlFade = s.nl.fade;
syncSend = s.udpn.send; syncSend = s.udpn.send;
prevPS = currentPreset;
if (s.pl<0) currentPreset = s.ps; if (s.pl<0) currentPreset = s.ps;
else currentPreset = s.pl; else currentPreset = s.pl;
@ -1235,6 +1181,7 @@ function readState(s,command=false)
showToast('Error ' + s.error + ": " + errstr, true); showToast('Error ' + s.error + ": " + errstr, true);
} }
prevFx = selectedFx;
selectedPal = i.pal; selectedPal = i.pal;
selectedFx = i.fx; selectedFx = i.fx;
redrawPalPrev(); // if any color changed (random palette did at least) redrawPalPrev(); // if any color changed (random palette did at least)
@ -1242,7 +1189,27 @@ function readState(s,command=false)
} }
// WLEDSR: control HTML elements for Slider and Color Control // WLEDSR: control HTML elements for Slider and Color Control
function setSliderAndColorControl(idx/*, extra*/) // Technical notes
// ===============
// If an effect name is followed by an @, slider and color control is effective.
// If not effective then:
// - For AC effects (id<128) 2 sliders and 3 colors and the palette will be shown
// - For SR effects (id>128) 5 sliders and 3 colors and the palette will be shown
// If effective (@)
// - a ; seperates slider controls (left) from color controls (middle) and palette control (right)
// - if left, middle or right is empty no controls are shown
// - a , seperates slider controls (max 5) or color controls (max 3). Palette has only one value
// - a ! means that the default is used.
// - For sliders: Effect speeds, Effect intensity, Custom 1, Custom 2, Custom 3
// - For colors: Fx color, Background color, Custom
// - For palette: prompt for color palette OR palette ID if numeric (will hide palette selection)
//
// Note: If palette is on and no colors are specified 1,2 and 3 is shown in each color circle.
// If a color is specified, the 1,2 or 3 is replaced by that specification.
// Note: Effects can override default pattern behaviour
// - FadeToBlack can override the background setting
// - Defining SEGCOL(<i>) can override a specific palette using these values (e.g. Color Gradient)
function setSliderAndColorControl(idx, applyDef=false)
{ {
if (!(Array.isArray(fxdata) && fxdata.length>idx)) return; if (!(Array.isArray(fxdata) && fxdata.length>idx)) return;
var topPosition = 0; var topPosition = 0;
@ -1252,15 +1219,25 @@ function setSliderAndColorControl(idx/*, extra*/)
var slOnOff = (extras.length==0 || extras[0]=='')?[]:extras[0].split(","); var slOnOff = (extras.length==0 || extras[0]=='')?[]:extras[0].split(",");
var coOnOff = (extras.length<2 || extras[1]=='')?[]:extras[1].split(","); var coOnOff = (extras.length<2 || extras[1]=='')?[]:extras[1].split(",");
var paOnOff = (extras.length<3 || extras[2]=='')?[]:extras[2].split(","); var paOnOff = (extras.length<3 || extras[2]=='')?[]:extras[2].split(",");
var obj = {"seg":{}};
// set html slider items on/off // set html slider items on/off
var nSliders = Math.floor((gId("Effects").children.length - 1) / 2); // p (label) & div for each slider + FX list var nSliders = Math.min(5,Math.floor((gId("Effects").children.length - 1) / 2)); // p (label) & div for each slider + FX list
for (let i=0; i<nSliders; i++) { for (let i=0; i<nSliders; i++) {
var slider = gId("slider" + i); var slider = gId("slider" + i);
var label = gId("sliderLabel" + i); var label = gId("sliderLabel" + i);
// if (not controlDefined and for AC speed or intensity and for SR alle sliders) or slider has a value // if (not controlDefined and for AC speed or intensity and for SR alle sliders) or slider has a value
if ((!controlDefined && i < ((idx<128)?2:nSliders)) || (slOnOff.length>i && slOnOff[i] != "")) { if ((!controlDefined && i < ((idx<128)?2:nSliders)) || (slOnOff.length>i && slOnOff[i] != "")) {
label.style.display = "block"; label.style.display = "block";
if (slOnOff.length>i && slOnOff[i].indexOf("=")>0) {
//embeded default values
var dPos = slOnOff[i].indexOf("=");
var v = Math.max(0,Math.min(255,parseInt(slOnOff[i].substr(dPos+1))));
if (i==0) { if (applyDef) gId("sliderSpeed").value = v; obj.seg.sx = v; }
else if (i==1) { if (applyDef) gId("sliderIntensity").value = v; obj.seg.ix = v; }
else { if (applyDef) gId("sliderC"+(i-1)).value = v; obj.seg["C"+(i-1)] = v}
slOnOff[i] = slOnOff[i].substring(0,dPos-1);
}
if (slOnOff.length>i && slOnOff[i]!="!") label.innerHTML = slOnOff[i]; if (slOnOff.length>i && slOnOff[i]!="!") label.innerHTML = slOnOff[i];
else if (i==0) label.innerHTML = "Effect speed"; else if (i==0) label.innerHTML = "Effect speed";
else if (i==1) label.innerHTML = "Effect intensity"; else if (i==1) label.innerHTML = "Effect intensity";
@ -1320,33 +1297,37 @@ function setSliderAndColorControl(idx/*, extra*/)
hide = false; hide = false;
} else { } else {
btn.style.display = "none"; btn.style.display = "none";
if (i>0 && csel==i) selectSlot(0);
} }
} }
/*
// perhaps too aggressive
var ccfg = cfg.comp.colors;
gId("picker").style.display = hide && ccfg.picker ? "none" : "block";
gId("vwrap").style.display = hide && ccfg.picker ? "none" : "block";
gId("kwrap").style.display = hide && ccfg.picker && cct ? "none" : "block";
gId("wwrap").style.display = hide ? "none" : "block";
gId("wbal").style.display = hide && !cct ? "none" : "block";
gId("rgbwrap").style.display = hide && ccfg.rgb ? "none" : "block";
gId("qcs-w").style.display = hide && ccfg.quick ? "none" : "block";
*/
gId("cslLabel").innerHTML = cslLabel; gId("cslLabel").innerHTML = cslLabel;
// set palette on/off // set palette on/off
var palw = gId("palw"); // wrapper var palw = gId("palw"); // wrapper
var pall = gId("pall"); // list var pall = gId("pall"); // list
// if not controlDefined or palette has a value // if not controlDefined or palette has a value
if ((!controlDefined) || (paOnOff.length>0 && paOnOff[0]!="")) { if ((!controlDefined) || (paOnOff.length>0 && paOnOff[0]!="" && isNaN(paOnOff[0]))) {
palw.style.display = "inline-block"; palw.style.display = "inline-block";
if (paOnOff.length>0 && paOnOff[0].indexOf("=")>0) {
//embeded default values
var dPos = paOnOff[0].indexOf("=");
var v = Math.max(0,Math.min(255,parseInt(paOnOff[0].substr(dPos+1))));
var p = d.querySelector(`#pallist input[name="palette"][value="${v}"]`);
if (applyDef && p) {
p.checked = true;
obj.seg.pal = v;
}
paOnOff[0] = paOnOff[0].substring(0,dPos-1);
}
if (paOnOff.length>0 && paOnOff[0] != "!") pall.innerHTML = paOnOff[0]; if (paOnOff.length>0 && paOnOff[0] != "!") pall.innerHTML = paOnOff[0];
else pall.innerHTML = '<i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i> Color palette'; else pall.innerHTML = '<i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i> Color palette';
} else { } else {
// disable label and slider // disable label and slider
palw.style.display = "none"; palw.style.display = "none";
// if numeric set as selected palette
if (paOnOff.length>0 && paOnOff[0]!="" && !isNaN(paOnOff[0]) && parseInt(paOnOff[0])!=selectedPal) obj.seg.pal = parseInt(paOnOff[0]);
} }
if (!isEmpty(obj.seg) && applyDef) requestJson(obj); //update default values (may need throttling on ESP8266)
} }
var jsonTimeout; var jsonTimeout;
@ -1362,7 +1343,7 @@ function requestJson(command=null)
var useWs = (ws && ws.readyState === WebSocket.OPEN); var useWs = (ws && ws.readyState === WebSocket.OPEN);
var type = command ? 'post':'get'; var type = command ? 'post':'get';
if (command) { if (command) {
command.v = true; // force complete /json/si API response if (useWs || !command.ps) command.v = true; // force complete /json/si API response (ps is async so no point)
command.time = Math.floor(Date.now() / 1000); command.time = Math.floor(Date.now() / 1000);
var t = gId('tt'); var t = gId('tt');
if (t.validity.valid && command.transition==null) { if (t.validity.valid && command.transition==null) {
@ -1376,6 +1357,8 @@ function requestJson(command=null)
if (useWs) { if (useWs) {
ws.send(req?req:'{"v":true}'); ws.send(req?req:'{"v":true}');
return; return;
} else if (command && command.ps) { //refresh UI if we don't use WS (async loading of presets)
setTimeout(requestJson,200);
} }
fetch(url, { fetch(url, {
@ -1752,7 +1735,8 @@ function rptSeg(s)
if (stop == 0) {return;} if (stop == 0) {return;}
var rev = gId(`seg${s}rev`).checked; var rev = gId(`seg${s}rev`).checked;
var mi = gId(`seg${s}mi`).checked; var mi = gId(`seg${s}mi`).checked;
var obj = {"seg": {"id": 0, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop}, "rev": rev, "mi": mi, "on": !powered[s], "bri": parseInt(gId(`seg${s}bri`).value)}; var sel = gId(`seg${s}sel`).checked;
var obj = {"seg": {"id": 0, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop, "rev": rev, "mi": mi, "on": !powered[s], "bri": parseInt(gId(`seg${s}bri`).value), "sel": sel}};
if (gId(`seg${s}grp`)) { if (gId(`seg${s}grp`)) {
var grp = parseInt(gId(`seg${s}grp`).value); var grp = parseInt(gId(`seg${s}grp`).value);
var spc = parseInt(gId(`seg${s}spc`).value); var spc = parseInt(gId(`seg${s}spc`).value);
@ -1850,7 +1834,7 @@ function setPalette(paletteId = null)
if (paletteId === null) { if (paletteId === null) {
paletteId = parseInt(d.querySelector('#pallist input[name="palette"]:checked').value); paletteId = parseInt(d.querySelector('#pallist input[name="palette"]:checked').value);
} else { } else {
d.querySelector(`#pallist input[name="palette"][value="${paletteId}`).checked = true; d.querySelector(`#pallist input[name="palette"][value="${paletteId}"]`).checked = true;
} }
var selElement = d.querySelector('#pallist .selected'); var selElement = d.querySelector('#pallist .selected');
if (selElement) { if (selElement) {
@ -2106,7 +2090,7 @@ function setBalance(b)
} }
var hc = 0; var hc = 0;
setInterval(function(){if (!isInfo) return; hc+=18; if (hc>300) hc=0; if (hc>200)hc=306; if (hc==144) hc+=36; if (hc==108) hc+=18; setInterval(()=>{if (!isInfo) return; hc+=18; if (hc>300) hc=0; if (hc>200)hc=306; if (hc==144) hc+=36; if (hc==108) hc+=18;
gId('heart').style.color = `hsl(${hc}, 100%, 50%)`;}, 910); gId('heart').style.color = `hsl(${hc}, 100%, 50%)`;}, 910);
function openGH() { window.open("https://github.com/Aircoookie/WLED/wiki"); } function openGH() { window.open("https://github.com/Aircoookie/WLED/wiki"); }
@ -2147,9 +2131,9 @@ function loadPalettesData(callback = null)
var lsPalData = localStorage.getItem(lsKey); var lsPalData = localStorage.getItem(lsKey);
if (lsPalData) { if (lsPalData) {
try { try {
lsPalData = JSON.parse(lsPalData); var d = JSON.parse(lsPalData);
if (lsPalData && lsPalData.vid == lastinfo.vid) { if (d && d.vid == d.vid) {
palettesData = lsPalData.p; palettesData = d.p;
if (callback) callback(); // redrawPalPrev() if (callback) callback(); // redrawPalPrev()
return; return;
} }
@ -2157,7 +2141,7 @@ function loadPalettesData(callback = null)
} }
palettesData = {}; palettesData = {};
getPalettesData(0, function() { getPalettesData(0, ()=>{
localStorage.setItem(lsKey, JSON.stringify({ localStorage.setItem(lsKey, JSON.stringify({
p: palettesData, p: palettesData,
vid: lastinfo.vid vid: lastinfo.vid
@ -2182,10 +2166,10 @@ function getPalettesData(page, callback)
}) })
.then(json => { .then(json => {
palettesData = Object.assign({}, palettesData, json.p); palettesData = Object.assign({}, palettesData, json.p);
if (page < json.m) setTimeout(function() { getPalettesData(page + 1, callback); }, 50); if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 50);
else callback(); else callback();
}) })
.catch(function(error) { .catch((error)=>{
showToast(error, true); showToast(error, true);
console.log(error); console.log(error);
}); });
@ -2252,7 +2236,9 @@ function expand(i,a=false)
*/ */
expanded[i] = !expanded[i]; expanded[i] = !expanded[i];
seg.style.display = (expanded[i]) ? "block":"none"; seg.style.display = (expanded[i]) ? "block":"none";
gId('sege' +i).style.transform = (expanded[i]) ? "rotate(180deg)":"rotate(0deg)"; //gId('sege' +i).style.transform = (expanded[i]) ? "rotate(180deg)":"rotate(0deg)";
if (expanded[i]) gId('sege' +i).classList.add("exp");
else gId('sege' +i).classList.remove("exp");
if (expanded[i]) gId(i<100?'segutil':'putil').classList.remove(i<100?"staybot":"staytop"); if (expanded[i]) gId(i<100?'segutil':'putil').classList.remove(i<100?"staybot":"staytop");
else gId(i<100?'segutil':'putil').classList.add(i<100?"staybot":"staytop"); else gId(i<100?'segutil':'putil').classList.add(i<100?"staybot":"staytop");

View File

@ -1,6 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head><meta charset="UTF-8"> <head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>WLED Settings</title> <title>WLED Settings</title>
<style> <style>
body { body {
@ -21,7 +23,7 @@
border-radius: var(--h); border-radius: var(--h);
font-size: 6vmin; font-size: 6vmin;
height: var(--h); height: var(--h);
width: calc(100% - 40px); width: calc(100%% - 40px); /* needed for AsyncWebServer parsing */
margin-top: 2vh; margin-top: 2vh;
} }
</style> </style>
@ -31,6 +33,7 @@
<form action="/settings/wifi"><button type="submit">WiFi Setup</button></form> <form action="/settings/wifi"><button type="submit">WiFi Setup</button></form>
<form action="/settings/leds"><button type="submit">LED Preferences</button></form> <form action="/settings/leds"><button type="submit">LED Preferences</button></form>
<form action="/settings/ui"><button type="submit">User Interface</button></form> <form action="/settings/ui"><button type="submit">User Interface</button></form>
%DMXMENU%<!--form action="/settings/dmx"><button type="submit">DMX Output</button></form-->
<form action="/settings/sync"><button type="submit">Sync Interfaces</button></form> <form action="/settings/sync"><button type="submit">Sync Interfaces</button></form>
<form action="/settings/time"><button type="submit">Time & Macros</button></form> <form action="/settings/time"><button type="submit">Time & Macros</button></form>
<form action="/settings/um"><button type="submit">Usermods</button></form> <form action="/settings/um"><button type="submit">Usermods</button></form>

View File

@ -1,5 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>DMX Settings</title> <html lang="en">
<head>
<meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<meta charset="utf-8">
<title>DMX Settings</title>
<script> <script>
function GCH(num) { function GCH(num) {
d=document; d=document;
@ -31,7 +36,7 @@ function mMap(){
function S(){GCH(15);GetV();mMap();}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/DMX");}function B(){window.history.back();} function S(){GCH(15);GetV();mMap();}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/DMX");}function B(){window.history.back();}
function GetV(){} function GetV(){}
</script> </script>
<style>@import url("/style.css");</style> <style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body onload="S()">
<form id="form_s" name="Sf" method="post"> <form id="form_s" name="Sf" method="post">

View File

@ -3,22 +3,15 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>LED Settings</title> <title>LED Settings</title>
<script> <script>
var d=document,laprev=55,maxB=1,maxM=4000,maxPB=4096,maxL=1333,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32 var d=document,laprev=55,maxB=1,maxM=4000,maxPB=4096,maxL=1333,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[]; var customStarts=false,startsDirty=[];
function H() function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");}
{ function B(){window.open("/settings","_self");}
window.open("https://kno.wled.ge/features/settings/#led-settings");
}
function B()
{
window.open("/settings","_self");
}
function gId(n){return d.getElementById(n);} function gId(n){return d.getElementById(n);}
function off(n){ function off(n){d.getElementsByName(n)[0].value = -1;}
d.getElementsByName(n)[0].value = -1;
}
var timeout; var timeout;
function showToast(text, error = false) function showToast(text, error = false)
{ {
@ -71,7 +64,6 @@
if (bquot > 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);} if (bquot > 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);}
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914 if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
} }
function S(){GetV();checkSi();setABL();}
function enABL() function enABL()
{ {
var en = gId('able').checked; var en = gId('able').checked;
@ -246,7 +238,7 @@
// memory usage and warnings // memory usage and warnings
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 = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none'; gId('ledwarning').style.display = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none';
gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || 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";
@ -313,7 +305,7 @@ ${i+1}:
<option value="80">DDP RGB (network)</option> <option value="80">DDP RGB (network)</option>
<!--option value="81">E1.31 RGB (network)</option--> <!--option value="81">E1.31 RGB (network)</option-->
<!--option value="82">ArtNet RGB (network)</option--> <!--option value="82">ArtNet RGB (network)</option-->
</select>&nbsp; </select><br>
<div id="co${i}" style="display:inline">Color Order: <div id="co${i}" style="display:inline">Color Order:
<select name="CO${i}"> <select name="CO${i}">
<option value="0">GRB</option> <option value="0">GRB</option>
@ -322,8 +314,7 @@ ${i+1}:
<option value="3">RBG</option> <option value="3">RBG</option>
<option value="4">BGR</option> <option value="4">BGR</option>
<option value="5">GBR</option> <option value="5">GBR</option>
</select></div> </select><br></div>
<br>
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp; <span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp;
<div id="dig${i}c" style="display:inline">Length: <input type="number" name="LC${i}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div> <div id="dig${i}c" style="display:inline">Length: <input type="number" name="LC${i}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div>
<br> <br>
@ -451,6 +442,7 @@ ${i+1}:
} }
} }
} }
function S(){GetV();checkSi();setABL();}
function GetV() function GetV()
{ {
//values injected by server while sending HTML //values injected by server while sending HTML
@ -524,8 +516,7 @@ ${i+1}:
<option value=7>9-key red</option> <option value=7>9-key red</option>
<option value=8>JSON remote</option> <option value=8>JSON remote</option>
</select><span style="cursor: pointer;" onclick="off('IR')">&nbsp;&#215;</span><br> </select><span style="cursor: pointer;" onclick="off('IR')">&nbsp;&#215;</span><br>
<div id="json" style="display:none;">JSON file: <input type="file" name="data" accept=".json"> <input type="button" value="Upload" onclick="uploadFile('/ir.json');"><br></div> <div id="json" style="display:none;">JSON file: <input type="file" name="data" accept=".json"><button type="button" class="sml" onclick="uploadFile('/ir.json')">Upload</button><br></div>
<div id="toast"></div>
<a href="https://kno.wled.ge/interfaces/infrared/" target="_blank">IR info</a><br> <a href="https://kno.wled.ge/interfaces/infrared/" target="_blank">IR info</a><br>
Relay GPIO: <input type="number" min="-1" max="33" name="RL" onchange="UI()" class="xs"> Invert <input type="checkbox" name="RM"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#215;</span><br> Relay GPIO: <input type="number" min="-1" max="33" name="RL" onchange="UI()" class="xs"> Invert <input type="checkbox" name="RM"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#215;</span><br>
<hr style="width:260px"> <hr style="width:260px">
@ -536,7 +527,7 @@ ${i+1}:
<br><br> <br><br>
Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br> Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br>
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br> Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br>
Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %% Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %
<h3>Transitions</h3> <h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br> Crossfade: <input type="checkbox" name="TF"><br>
Transition Time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br> Transition Time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
@ -563,7 +554,7 @@ ${i+1}:
</select> </select>
<br> <br>
Calculate CCT from RGB: <input type="checkbox" name="CR"> <br> Calculate CCT from RGB: <input type="checkbox" name="CR"> <br>
CCT additive blending: <input type="number" class="s" min="0" max="100" name="CB" required> %%</span> CCT additive blending: <input type="number" class="s" min="0" max="100" name="CB" required> %</span>
<h3>Advanced</h3> <h3>Advanced</h3>
Palette blending: Palette blending:
<select name="PB"> <select name="PB">
@ -572,10 +563,12 @@ ${i+1}:
<option value="2">Linear (never wrap)</option> <option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option> <option value="3">None (not recommended)</option>
</select><br> </select><br>
Target refresh rate: <input type="number" class="s" min="1" max="120" name="FR" required> FPS
<hr style="width:260px"> <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> <div id="cfg">Config template: <input type="file" name="data2" accept=".json"><button type="button" class="sml" onclick="loadCfg(d.Sf.data2)">Apply</button><br></div>
<hr> <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>
<div id="toast"></div>
</body> </body>
</html> </html>

View File

@ -2,13 +2,14 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Misc Settings</title> <title>Misc Settings</title>
<script> <script>
var d = document; var d = document;
function H() function H()
{ {
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#security-settings"); window.open("https://kno.wled.ge/features/settings/#security-settings");
} }
function B() function B()
{ {

View File

@ -1,11 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>Sync Settings</title> <html lang="en">
<head>
<meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<meta charset="utf-8">
<title>Sync Settings</title>
<script>var d=document; <script>var d=document;
function gId(s) function gId(s)
{ {
return d.getElementById(s); return d.getElementById(s);
} }
function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#sync-settings");}function B(){window.open("/settings","_self");} function H(){window.open("https://kno.wled.ge/interfaces/udp-notifier/");}
function B(){window.open("/settings","_self");}
function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.DA.value == 1) d.Sf.DA.value = 0; if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;} function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.DA.value == 1) d.Sf.DA.value = 0; if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;}
else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} } else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} }
function FC() function FC()
@ -35,7 +41,7 @@ function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 556
function S(){GetV();SetVal();} function S(){GetV();SetVal();}
function GetV(){var d=document;} function GetV(){var d=document;}
</script> </script>
<style>@import url("/style.css");</style> <style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body onload="S()">
<form id="form_s" name="Sf" method="post" onsubmit="GC()"> <form id="form_s" name="Sf" method="post" onsubmit="GC()">
@ -50,8 +56,9 @@ UDP Port: <input name="UP" type="number" min="1" max="65535" class="d5" required
<input name="GS" id="GS" type="number" style="display: none;"> <!-- hidden inputs for bitwise group checkboxes --> <input name="GS" id="GS" type="number" style="display: none;"> <!-- hidden inputs for bitwise group checkboxes -->
<input name="GR" id="GR" type="number" style="display: none;"> <input name="GR" id="GR" type="number" style="display: none;">
<table style="margin: 0 auto;"> <table style="margin: 0 auto;">
<tr><td colspan="9" style="text-align:center">Sync groups</td></tr>
<tr> <tr>
<td>Sync groups</td> <td></td>
<td>1</td> <td>1</td>
<td>2</td> <td>2</td>
<td>3</td> <td>3</td>
@ -84,7 +91,8 @@ UDP Port: <input name="UP" type="number" min="1" max="65535" class="d5" required
<td><input type="checkbox" id="R8" name="R8"></td> <td><input type="checkbox" id="R8" name="R8"></td>
</tr> </tr>
</table><br> </table><br>
Receive: <input type="checkbox" name="RB">Brightness, <input type="checkbox" name="RC">Color, and <input type="checkbox" name="RX">Effects<br> Receive: <nowrap><input type="checkbox" name="RB">Brightness,</nowrap> <nowrap><input type="checkbox" name="RC">Color,</nowrap> <nowrap>and <input type="checkbox" name="RX">Effects</nowrap><br>
<input type="checkbox" name="SO">Segment options<br>
Send notifications on direct change: <input type="checkbox" name="SD"><br> Send notifications on direct change: <input type="checkbox" name="SD"><br>
Send notifications on button press or IR: <input type="checkbox" name="SB"><br> Send notifications on button press or IR: <input type="checkbox" name="SB"><br>
Send Alexa notifications: <input type="checkbox" name="SA"><br> Send Alexa notifications: <input type="checkbox" name="SA"><br>
@ -120,45 +128,45 @@ DMX mode:
<option value=5>Dimmer + Multi RGB</option> <option value=5>Dimmer + Multi RGB</option>
<option value=6>Multi RGBW</option> <option value=6>Multi RGBW</option>
</select><br> </select><br>
<a href="https://github.com/Aircoookie/WLED/wiki/E1.31-DMX" target="_blank">E1.31 info</a><br> <a href="https://kno.wled.ge/interfaces/e1.31-dmx/" target="_blank">E1.31 info</a><br>
Timeout: <input name="ET" type="number" min="1" max="65000" required> ms<br> Timeout: <input name="ET" type="number" min="1" max="65000" required> ms<br>
Force max brightness: <input type="checkbox" name="FB"><br> Force max brightness: <input type="checkbox" name="FB"><br>
Disable realtime gamma correction: <input type="checkbox" name="RG"><br> Disable realtime gamma correction: <input type="checkbox" name="RG"><br>
Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required> Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required>
<h3>Alexa Voice Assistant</h3> <h3>Alexa Voice Assistant</h3>
Emulate Alexa device: <input type="checkbox" name="AL"><br> Emulate Alexa device: <input type="checkbox" name="AL"><br>
Alexa invocation name: <input name="AI" maxlength="32"> Alexa invocation name: <input type="text" name="AI" maxlength="32">
<h3>Blynk</h3> <h3>Blynk</h3>
<b>Blynk, MQTT and Hue sync all connect to external hosts!<br> <b>Blynk, MQTT and Hue sync all connect to external hosts!<br>
This may impact the responsiveness of the ESP8266.</b><br> This may impact the responsiveness of the ESP8266.</b><br>
For best results, only use one of these services at a time.<br> For best results, only use one of these services at a time.<br>
(alternatively, connect a second ESP to them and use the UDP sync)<br><br> (alternatively, connect a second ESP to them and use the UDP sync)<br><br>
Host: <input name="BH" maxlength="32"> Host: <input type="text" name="BH" maxlength="32">
Port: <input name="BP" type="number" min="1" max="65535" value="80" class="d5"><br> Port: <input name="BP" type="number" min="1" max="65535" value="80" class="d5"><br>
Device Auth token: <input name="BK" maxlength="33"><br> Device Auth token: <input name="BK" maxlength="33"><br>
<i>Clear the token field to disable. </i><a href="https://github.com/Aircoookie/WLED/wiki/Blynk" target="_blank">Setup info</a> <i>Clear the token field to disable. </i><a href="https://kno.wled.ge/interfaces/blynk/" target="_blank">Setup info</a>
<h3>MQTT</h3> <h3>MQTT</h3>
Enable MQTT: <input type="checkbox" name="MQ"><br> Enable MQTT: <input type="checkbox" name="MQ"><br>
Broker: <input name="MS" maxlength="32"> Broker: <input type="text" name="MS" maxlength="32">
Port: <input name="MQPORT" type="number" min="1" max="65535" class="d5"><br> Port: <input name="MQPORT" type="number" min="1" max="65535" class="d5"><br>
<b>The MQTT credentials are sent over an unsecured connection.<br> <b>The MQTT credentials are sent over an unsecured connection.<br>
Never use the MQTT password for another service!</b><br> Never use the MQTT password for another service!</b><br>
Username: <input name="MQUSER" maxlength="40"><br> Username: <input type="text" name="MQUSER" maxlength="40"><br>
Password: <input type="password" name="MQPASS" maxlength="64"><br> Password: <input type="password" name="MQPASS" maxlength="64"><br>
Client ID: <input name="MQCID" maxlength="40"><br> Client ID: <input type="text" name="MQCID" maxlength="40"><br>
Device Topic: <input name="MD" maxlength="32"><br> Device Topic: <input type="text" name="MD" maxlength="32"><br>
Group Topic: <input name="MG" maxlength="32"><br> Group Topic: <input type="text" name="MG" maxlength="32"><br>
Publish on button press: <input type="checkbox" name="BM"><br> Publish on button press: <input type="checkbox" name="BM"><br>
<i>Reboot required to apply changes. </i><a href="https://github.com/Aircoookie/WLED/wiki/MQTT" target="_blank">MQTT info</a> <i>Reboot required to apply changes. </i><a href="https://kno.wled.ge/interfaces/mqtt/" target="_blank">MQTT info</a>
<h3>Philips Hue</h3> <h3>Philips Hue</h3>
<i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br> <i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br>
Poll Hue light <input name="HL" type="number" min="1" max="99" > every <input name="HI" type="number" min="100" max="65000"> ms: <input type="checkbox" name="HP"><br> Poll Hue light <input name="HL" type="number" min="1" max="99" > every <input name="HI" type="number" min="100" max="65000"> ms: <input type="checkbox" name="HP"><br>
Then, receive <input type="checkbox" name="HO"> On/Off, <input type="checkbox" name="HB"> Brightness, and <input type="checkbox" name="HC"> Color<br> Then, receive <input type="checkbox" name="HO"> On/Off, <input type="checkbox" name="HB"> Brightness, and <input type="checkbox" name="HC"> Color<br>
Hue Bridge IP:<br> Hue Bridge IP:<br>
<input name="H0" type="number" min="0" max="255" > . <input name="H0" type="number" class="s" min="0" max="255" > .
<input name="H1" type="number" min="0" max="255" > . <input name="H1" type="number" class="s" min="0" max="255" > .
<input name="H2" type="number" min="0" max="255" > . <input name="H2" type="number" class="s" min="0" max="255" > .
<input name="H3" type="number" min="0" max="255" ><br> <input name="H3" type="number" class="s" min="0" max="255" ><br>
<b>Press the pushlink button on the bridge, after that save this page!</b><br> <b>Press the pushlink button on the bridge, after that save this page!</b><br>
(when first connecting)<br> (when first connecting)<br>
Hue status: <span class="sip"> Disabled in this build </span><hr> Hue status: <span class="sip"> Disabled in this build </span><hr>

View File

@ -2,13 +2,14 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Time Settings</title> <title>Time Settings</title>
<script> <script>
var d=document; var d=document;
function H() function H()
{ {
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#time-settings"); window.open("https://kno.wled.ge/features/settings/#time-settings");
} }
function B() function B()
{ {
@ -16,12 +17,18 @@
} }
function S() function S()
{ {
BTa();GetV();Cs();FC(); BTa();GetV();updLoc();Cs();FC();
} }
function gId(s) function gId(s)
{ {
return d.getElementById(s); return d.getElementById(s);
} }
function expand(o,i)
{
o.classList.toggle("exp");
var t = gId("WD"+i);
t.style.display = t.style.display!=="none" ? "none" : "";
}
function Cs() function Cs()
{ {
gId("cac").style.display="none"; gId("cac").style.display="none";
@ -43,61 +50,112 @@
} }
function BTa() function BTa()
{ {
var ih="<tr><th>En.</th><th>Hour</th><th>Minute</th><th>Preset</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr>"; var ih="<tr><th>En.</th><th>Hour</th><th>Minute</th><th>Preset</th><th></th></tr>";
for (i=0;i<8;i++) for (i=0;i<8;i++) {
{ ih+=`<tr><td><input name="W${i}" id="W${i}" type="hidden"><input id="W${i}0" type="checkbox"></td>
ih+="<tr><td><input name=\"W"+i+"\" id=\"W"+i+"\" type=\"hidden\"><input id=\"W"+i+"0\" type=\"checkbox\"></td><td><input name=\"H"+i+"\" class=\"xs\" type=\"number\" min=\"0\" max=\"24\"></td><td><input name=\"N"+i+"\" class=\"xs\" type=\"number\" min=\"0\" max=\"59\"></td><td><input name=\"T"+i+"\" class=\"s\" type=\"number\" min=\"0\" max=\"250\"></td>"; <td><input name="H${i}" class="xs" type="number" min="0" max="24"></td>
for (j=1;j<8;j++) ih+="<td><input id=\"W"+i+j+"\" type=\"checkbox\"></td>"; <td><input name="N${i}" class="xs" type="number" min="0" max="59"></td>
ih+="</tr>"; <td><input name="T${i}" class="s" type="number" min="0" max="250"></td>
<td><button type="button" class="sml" onclick="expand(this,${i})">&gt;</button></td></tr>`;
ih+=`<tr><td colspan=5><div id="WD${i}" style="display:none;">`;
ih+=`<input name="X${i}" id="TMX${i}" type="checkbox" onchange="tglDOW(${i},this.checked)"><label for="TMX${i}">Use DOW</label>`;
ih+=`<table id="TT${i}" style="width:100%;"><tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr><tr>`
for (j=1;j<8;j++) ih+=`<td><input id="W${i}${j}" type="checkbox"></td>`;
ih+=`</tr></table><div id="TD${i}">Date:
<select id="TMM${i}" name="M${i}" onchange="chkMon(this,${i})">
<option value="0">Every month</option>
<option value="13">Odd month</option>
<option value="14">Even month</option>
<option value="1">Jan</option>
<option value="2">Feb</option>
<option value="3">Mar</option>
<option value="4">Apr</option>
<option value="5">May</option>
<option value="6">Jun</option>
<option value="7">Jul</option>
<option value="8">Aug</option>
<option value="9">Sep</option>
<option value="10">Oct</option>
<option value="11">Nov</option>
<option value="12">Dec</option>
</select><select id="TMD${i}" name="D${i}">`;
for (j=1;j<32;j++) ih+=`<option value="${j}">${j}</option>`;
ih+="</select></div><hr></div></td></tr>";
} }
ih+="<tr><td><input name=\"W8\" id=\"W8\" type=\"hidden\"><input id=\"W80\" type=\"checkbox\"></td><td>Sunrise<input name=\"H8\" value=\"255\" type=\"hidden\"></td><td><input name=\"N8\" class=\"xs\" type=\"number\" min=\"-59\" max=\"59\"></td><td><input name=\"T8\" class=\"s\" type=\"number\" min=\"0\" max=\"250\"></td>"; ih+=`<tr><td><input name="W8" id="W8" type="hidden"><input id="W80" type="checkbox"></td>
for (j=1;j<8;j++) ih+="<td><input id=\"W8"+j+"\" type=\"checkbox\"></td>"; <td>Sunrise<input name="H8" value="255" type="hidden"></td>
ih+="</tr><tr><td><input name=\"W9\" id=\"W9\" type=\"hidden\"><input id=\"W90\" type=\"checkbox\"></td><td>Sunset<input name=\"H9\" value=\"255\" type=\"hidden\"></td><td><input name=\"N9\" class=\"xs\" type=\"number\" min=\"-59\" max=\"59\"></td><td><input name=\"T9\" class=\"s\" type=\"number\" min=\"0\" max=\"250\"></td>"; <td><input name="N8" class="xs" type="number" min="-59" max="59"></td>
for (j=1;j<8;j++) ih+="<td><input id=\"W9"+j+"\" type=\"checkbox\"></td>"; <td><input name="T8" class="s" type="number" min="0" max="250"></td>
ih+="</tr>"; <td><button type="button" class="sml" onclick="expand(this,8)">&gt;</button></td></tr><tr><td colspan=5>`;
ih+=`<div id="WD8"style="display:none;"><table style="width:100%;"><tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr><tr>`;
for (j=1;j<8;j++) ih+=`<td><input id="W8${j}" type="checkbox"></td>`;
ih+="</tr></table><hr></div></td></tr>";
ih+=`<tr><td><input name="W9" id="W9" type="hidden"><input id="W90" type="checkbox"></td>
<td>Sunrise<input name="H9" value="255" type="hidden"></td>
<td><input name="N9" class="xs" type="number" min="-59" max="59"></td>
<td><input name="T9" class="s" type="number" min="0" max="250"></td>
<td><button type="button" class="sml" onclick="expand(this,9)">&gt;</button></td></tr><tr><td colspan=5>`;
ih+=`<div id="WD9" style="display:none;"><table style="width:100%;"><tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr><tr>`;
for (j=1;j<8;j++) ih+=`<td><input id="W9${j}" type="checkbox"></td>`;
ih+="</tr></table><hr></div></td></tr>";
gId("TMT").innerHTML=ih; gId("TMT").innerHTML=ih;
} }
function tglDOW(i,b)
{
gId("TT"+i).style.display = b ? "table" : "none";
gId("TD"+i).style.display = !b ? "block" : "none";
}
function chkMon(o,i)
{
const m31 = [1,3,5,7,8,10,12,0,13,14];
if (!m31.includes(parseInt(o.value)) && parseInt(gId("TMD"+i).value)>30) gId("TMD"+i).value="30";
if (parseInt(o.value)===2 && parseInt(gId("TMD"+i).value)>28) gId("TMD"+i).value="28";
}
function FC() function FC()
{ {
for(j=0;j<8;j++) for(j=0;j<8;j++)
{ {
for(i=0;i<10;i++) gId("W"+i+j).checked=gId("W"+i).value>>j&1; for(i=0;i<10;i++) gId("W"+i+j).checked=gId("W"+i).value>>j&1;
tglDOW(j,gId('TMX'+j).checked);
} }
} }
function Wd() function Wd()
{ {
a = [0,0,0,0,0,0,0,0,0,0]; a = [0,0,0,0,0,0,0,0,0,0];
for(i=0;i<10;i++) for (i=0; i<10; i++) {
{
m=1; m=1;
for(j=0;j<8;j++) for(j=0;j<8;j++) { a[i]+=gId(("W"+i)+j).checked*m; m*=2;}
{
a[i]+=gId("W"+i+j).checked*m;m*=2;
}
gId("W"+i).value=a[i]; gId("W"+i).value=a[i];
} }
if (d.Sf.LTR.value==="S") { d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); }
if (d.Sf.LNR.value==="W") { d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); }
} }
function addRow(i,p,l,d) { function addRow(i,p,l,d) {
var t = gId("macros"); // table var t = gId("macros"); // table
var rCnt = t.rows.length; // get the number of rows. var rCnt = t.rows.length; // get the number of rows.
var tr = t.insertRow(rCnt); // table row. var tr = t.insertRow(rCnt); // table row.
var b = String.fromCharCode((i<10?48:55)+i);
var td = document.createElement('td'); // TABLE DEFINITION. var td = document.createElement('td'); // TABLE DEFINITION.
td = tr.insertCell(0); td = tr.insertCell(0);
td.innerHTML = `Button ${i}:`; td.innerHTML = `Button ${i}:`;
td = tr.insertCell(1); td = tr.insertCell(1);
td.innerHTML = `<input name="MP${i}" type="number" class="m" min="0" max="250" value="${p}" required>`; td.innerHTML = `<input name="MP${b}" type="number" class="s" min="0" max="250" value="${p}" required>`;
td = tr.insertCell(2); td = tr.insertCell(2);
td.innerHTML = `<input name="ML${i}" type="number" class="m" min="0" max="250" value="${l}" required>`; td.innerHTML = `<input name="ML${b}" type="number" class="s" min="0" max="250" value="${l}" required>`;
td = tr.insertCell(3); td = tr.insertCell(3);
td.innerHTML = `<input name="MD${i}" type="number" class="m" min="0" max="250" value="${d}" required>`; td.innerHTML = `<input name="MD${b}" type="number" class="s" min="0" max="250" value="${d}" required>`;
}
function updLoc(i) {
if (parseFloat(d.Sf.LT.value)<0) { d.Sf.LTR.value = "S"; d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); } else d.Sf.LTR.value = "N";
if (parseFloat(d.Sf.LN.value)<0) { d.Sf.LNR.value = "W"; d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); } else d.Sf.LNR.value = "E";
} }
function GetV() function GetV()
{ {
//values injected by server while sending HTML //values injected by server while sending HTML
} }
</script> </script>
<style>@import url("/style.css");</style> <!--script src="http://192.168.70.61/settings/settings.js?p=5"></script-->
<style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body onload="S()">
<form id="form_s" name="Sf" method="post" onsubmit="Wd()"> <form id="form_s" name="Sf" method="post" onsubmit="Wd()">
@ -107,7 +165,7 @@
</div> </div>
<h2>Time setup</h2> <h2>Time setup</h2>
Get time from NTP server: <input type="checkbox" name="NT"><br> Get time from NTP server: <input type="checkbox" name="NT"><br>
<input name="NS" maxlength="32"><br> <input type="text" name="NS" maxlength="32"><br>
Use 24h format: <input type="checkbox" name="CF"><br> Use 24h format: <input type="checkbox" name="CF"><br>
Time zone: Time zone:
<select name="TZ"> <select name="TZ">
@ -136,8 +194,8 @@
</select><br> </select><br>
UTC offset: <input name="UO" type="number" min="-65500" max="65500" required> seconds (max. 18 hours)<br> UTC offset: <input name="UO" type="number" min="-65500" max="65500" required> seconds (max. 18 hours)<br>
Current local time is <span class="times">unknown</span>.<br> Current local time is <span class="times">unknown</span>.<br>
Latitude (N): <input name="LT" type="number" class="xl" min="-66.6" max="66.6" step="0.01"> Latitude: <select name="LTR"><option value="N">N</option><option value="S">S</option></select><input name="LT" type="number" class="xl" min="0" max="66.6" step="0.01"><br>
Longitude (E): <input name="LN" type="number" class="xl" min="-180" max="180" step="0.01"> Longitude: <select name="LNR"><option value="E">E</option><option value="W">W</option></select><input name="LN" type="number" class="xl" min="0" max="180" step="0.01">
<div id="sun" class="times"></div> <div id="sun" class="times"></div>
<h3>Clock</h3> <h3>Clock</h3>
Clock Overlay: Clock Overlay:
@ -160,8 +218,8 @@
</div> </div>
Countdown Mode: <input type="checkbox" name="CE"><br> Countdown Mode: <input type="checkbox" name="CE"><br>
Countdown Goal:<br> Countdown Goal:<br>
Year: 20 <input name="CY" class="xs" type="number" min="0" max="99" required> Month: <input name="CI" class="xs" type="number" min="1" max="12" required> Day: <input name="CD" class="xs" type="number" min="1" max="31" required><br> Date: <nowrap>20<input name="CY" class="xs" type="number" min="0" max="99" required>-<input name="CI" class="xs" type="number" min="1" max="12" required>-<input name="CD" class="xs" type="number" min="1" max="31" required></nowrap><br>
Hour: <input name="CH" class="xs" type="number" min="0" max="23" required> Minute: <input name="CM" class="xs" type="number" min="0" max="59" required> Second: <input name="CS" class="xs" type="number" min="0" max="59" required><br> Time: <nowrap><input name="CH" class="xs" type="number" min="0" max="23" required>:<input name="CM" class="xs" type="number" min="0" max="59" required>:<input name="CS" class="xs" type="number" min="0" max="59" required></nowrap><br>
<h3>Macro presets</h3> <h3>Macro presets</h3>
<b>Macros have moved!</b><br> <b>Macros have moved!</b><br>
<i>Presets now also can be used as macros to save both JSON and HTTP API commands.<br> <i>Presets now also can be used as macros to save both JSON and HTTP API commands.<br>
@ -183,11 +241,12 @@
<tbody> <tbody>
</tbody> </tbody>
</table> </table>
<a href="https://github.com/Aircoookie/WLED/wiki/Macros#analog-button" target="_blank">Analog Button setup</a> <a href="https://kno.wled.ge/features/macros/#analog-button" target="_blank">Analog Button setup</a>
<h3>Time-controlled presets</h3> <h3>Time-controlled presets</h3>
<div style="display: inline-block"> <div style="display: inline-block">
<table id="TMT"> <table id="TMT" style="min-width:330px;"></table>
</table></div><hr> </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>

View File

@ -3,6 +3,7 @@
<head lang="en"> <head lang="en">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>UI Settings</title> <title>UI Settings</title>
<script> <script>
var d = document; var d = document;
@ -171,7 +172,7 @@
} }
function H() function H()
{ {
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#user-interface-settings"); window.open("https://kno.wled.ge/features/settings/#user-interface-settings");
} }
function B() function B()
{ {
@ -224,7 +225,7 @@
<span id="lserr" style="color:red; display:none">&#9888; Could not access local storage. Make sure it is enabled in your browser.</span><hr> <span id="lserr" style="color:red; display:none">&#9888; Could not access local storage. Make sure it is enabled in your browser.</span><hr>
</div> </div>
<h2>Web Setup</h2> <h2>Web Setup</h2>
Server description: <input name="DS" maxlength="32"><br> Server description: <input type="text" name="DS" maxlength="32"><br>
Sync button toggles both send and receive: <input type="checkbox" name="ST"><br> Sync button toggles both send and receive: <input type="checkbox" name="ST"><br>
Enable simplified UI: <input type="checkbox" name="SU"><br> Enable simplified UI: <input type="checkbox" name="SU"><br>
<i>The following UI customization settings are unique both to the WLED device and this browser.<br> <i>The following UI customization settings are unique both to the WLED device and this browser.<br>
@ -242,8 +243,8 @@
<span id="idonthateyou" style="display:none"><i>Why would you? </i>&#x1F97A;<br></span> <span id="idonthateyou" style="display:none"><i>Why would you? </i>&#x1F97A;<br></span>
<span class="l"></span>: <input type="number" min=0.0 max=1.0 step=0.01 id="theme_alpha_tab" class="agi"><br> <span class="l"></span>: <input type="number" min=0.0 max=1.0 step=0.01 id="theme_alpha_tab" class="agi"><br>
<span class="l"></span>: <input type="number" min=0.0 max=1.0 step=0.01 id="theme_alpha_bg" class="agi"><br> <span class="l"></span>: <input type="number" min=0.0 max=1.0 step=0.01 id="theme_alpha_bg" class="agi"><br>
<span class="l"></span>: <input id="theme_color_bg" maxlength="9" class="agi"><br> <span class="l"></span>: <input type="text" id="theme_color_bg" maxlength="9" class="agi"><br>
<span class="l">BG image URL</span>: <input id="theme_bg_url" class="agi" oninput="checkRandomBg()"><br> <span class="l">BG image URL</span>: <input type="text" id="theme_bg_url" class="agi" oninput="checkRandomBg()"><br>
<span class="l">Random BG image</span>: <input type="checkbox" id="theme_bg_random" class="agi cb" onchange="setRandomBg()"><br> <span class="l">Random BG image</span>: <input type="checkbox" id="theme_bg_random" class="agi cb" onchange="setRandomBg()"><br>
<input id="theme_base" class="agi" style="display:none"> <input id="theme_base" class="agi" style="display:none">
<span class="l"></span>: <input type="checkbox" id="comp_css" class="agi cb"><br> <span class="l"></span>: <input type="checkbox" id="comp_css" class="agi cb"><br>

View File

@ -3,6 +3,7 @@
<head lang="en"> <head lang="en">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>Usermod Settings</title> <title>Usermod Settings</title>
<script> <script>
var d = document; var d = document;
@ -133,7 +134,7 @@
} }
function GetV() {} function GetV() {}
</script> </script>
<style>@import url("/style.css");</style> <style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body onload="S()">

View File

@ -3,22 +3,17 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=500"> <meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>WiFi Settings</title> <title>WiFi Settings</title>
<script> <script>
function H() function H(){window.open("https://kno.wled.ge/features/settings/#wifi-settings");}
{ function B(){window.open("/settings","_self");}
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#wifi-settings");
}
function B()
{
window.open("/settings","_self");
}
function GetV() function GetV()
{ {
//values injected by server while sending HTML //values injected by server while sending HTML
} }
</script> </script>
<style>@import url("/style.css");</style> <style>@import url("style.css");</style>
</head> </head>
<body onload="GetV()"> <body onload="GetV()">
<form id="form_s" name="Sf" method="post"> <form id="form_s" name="Sf" method="post">
@ -28,7 +23,7 @@
</div> </div>
<h2>WiFi setup</h2> <h2>WiFi setup</h2>
<h3>Connect to existing network</h3> <h3>Connect to existing network</h3>
Network name (SSID, empty to not connect): <br><input name="CS" maxlength="32"><br> Network name (SSID, empty to not connect): <br><input type="text" name="CS" maxlength="32"><br>
Network password: <br> <input type="password" name="CP" maxlength="63"><br> Network password: <br> <input type="password" name="CP" maxlength="63"><br>
Static IP (leave at 0.0.0.0 for DHCP):<br> Static IP (leave at 0.0.0.0 for DHCP):<br>
<input name="I0" type="number" class="s" min="0" max="255" required> . <input name="I0" type="number" class="s" min="0" max="255" required> .
@ -46,10 +41,10 @@
<input name="S2" type="number" class="s" min="0" max="255" required> . <input name="S2" type="number" class="s" min="0" max="255" required> .
<input name="S3" type="number" class="s" min="0" max="255" required><br> <input name="S3" type="number" class="s" min="0" max="255" required><br>
mDNS address (leave empty for no mDNS):<br/> mDNS address (leave empty for no mDNS):<br/>
http:// <input name="CM" maxlength="32"> .local<br> http:// <input type="text" name="CM" maxlength="32"> .local<br>
Client IP: <span class="sip"> Not connected </span> <br> Client IP: <span class="sip"> Not connected </span> <br>
<h3>Configure Access Point</h3> <h3>Configure Access Point</h3>
AP SSID (leave empty for no AP):<br> <input name="AS" maxlength="32"><br> AP SSID (leave empty for no AP):<br> <input type="text" name="AS" maxlength="32"><br>
Hide AP name: <input type="checkbox" name="AH"><br> Hide AP name: <input type="checkbox" name="AH"><br>
AP password (leave empty for open):<br> <input type="password" name="AP" maxlength="63" pattern="(.{8,63})|()" title="Empty or min. 8 characters"><br> AP password (leave empty for open):<br> <input type="password" name="AP" maxlength="63" pattern="(.{8,63})|()" title="Empty or min. 8 characters"><br>
Access Point WiFi channel: <input name="AC" type="number" class="xs" min="1" max="13" required><br> Access Point WiFi channel: <input name="AC" type="number" class="xs" min="1" max="13" required><br>

View File

@ -759,15 +759,9 @@ function genPalPrevCss(id)
} else { } else {
if (selColors) { if (selColors) {
let e = element[1] - 1; let e = element[1] - 1;
//if (Array.isArray(selColors[e])) {
r = selColors[e][0]; r = selColors[e][0];
g = selColors[e][1]; g = selColors[e][1];
b = selColors[e][2]; b = selColors[e][2];
//} else {
// r = (selColors[e]>>16) & 0xFF;
// g = (selColors[e]>> 8) & 0xFF;
// b = (selColors[e] ) & 0xFF;
//}
} }
} }
if (index === false) { if (index === false) {
@ -931,18 +925,10 @@ function readState(s,command=false)
for (let e = cd.length-1; e >= 0; e--) for (let e = cd.length-1; e >= 0; e--)
{ {
var r,g,b,w; var r,g,b,w;
//if (Array.isArray(i.col[e])) {
r = i.col[e][0]; r = i.col[e][0];
g = i.col[e][1]; g = i.col[e][1];
b = i.col[e][2]; b = i.col[e][2];
if (isRgbw) w = i.col[e][3]; if (isRgbw) w = i.col[e][3];
//} else {
// // unsigned long RGBW (@blazoncek v2 experimental API implementation)
// r = (i.col[e]>>16) & 0xFF;
// g = (i.col[e]>> 8) & 0xFF;
// b = (i.col[e] ) & 0xFF;
// if (isRgbw) w = (i.col[e] >> 24) & 0xFF;
//}
cd[e].style.backgroundColor = "rgb(" + r + "," + g + "," + b + ")"; cd[e].style.backgroundColor = "rgb(" + r + "," + g + "," + b + ")";
if (isRgbw) whites[e] = parseInt(w); if (isRgbw) whites[e] = parseInt(w);
selectSlot(csel); selectSlot(csel);
@ -993,7 +979,7 @@ function requestJson(command=null)
var useWs = (ws && ws.readyState === WebSocket.OPEN); var useWs = (ws && ws.readyState === WebSocket.OPEN);
var type = command ? 'post':'get'; var type = command ? 'post':'get';
if (command) { if (command) {
command.v = true; // force complete /json/si API response if (useWs || !command.ps) command.v = true; // force complete /json/si API response
command.time = Math.floor(Date.now() / 1000); command.time = Math.floor(Date.now() / 1000);
var t = gId('tt'); var t = gId('tt');
if (t.validity.valid && command.transition==null) { if (t.validity.valid && command.transition==null) {
@ -1007,6 +993,8 @@ function requestJson(command=null)
if (useWs) { if (useWs) {
ws.send(req?req:'{"v":true}'); ws.send(req?req:'{"v":true}');
return; return;
} else if (command && command.ps) { //refresh UI if we don't use WS (async loading of presets)
setTimeout(requestJson,200);
} }
fetch(url, { fetch(url, {

View File

@ -1,9 +1,13 @@
html {
touch-action: manipulation;
}
body { body {
font-family: Verdana, sans-serif; font-family: Verdana, sans-serif;
font-size: 1rem;
text-align: center; text-align: center;
background: #222; background: #222;
color: #fff; color: #fff;
line-height: 200%%; /* %% because of AsyncWebServer */ line-height: 200%;
margin: 0; margin: 0;
} }
hr { hr {
@ -27,6 +31,13 @@ button, .btn {
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
} }
button.sml {
padding: 8px;
border-radius: 20px;
font-size: 15px;
min-width: 40px;
margin: 0 0 0 10px;
}
.toprow { .toprow {
top: 0; top: 0;
position: sticky; position: sticky;
@ -50,12 +61,13 @@ input {
input:disabled { input:disabled {
color: #888; color: #888;
} }
input[type="text"] { input[type="text"],
input[type="number"],
select {
font-size: medium; font-size: medium;
} }
input[type="number"] { input[type="number"] {
width: 4em; width: 4em;
font-size: medium;
margin: 2px; margin: 2px;
} }
input[type="number"].xxl { input[type="number"].xxl {
@ -76,27 +88,29 @@ input[type="number"].s {
input[type="number"].xs { input[type="number"].xs {
width: 40px; width: 40px;
} }
select {
margin: 2px;
font-size: medium;
}
input[type="checkbox"] { input[type="checkbox"] {
transform: scale(1.5); transform: scale(1.5);
margin-right: 10px; margin-right: 10px;
} }
select { select {
margin: 2px;
background: #333; background: #333;
color: #fff; color: #fff;
font-family: Verdana, sans-serif; font-family: Verdana, sans-serif;
border: 0.5ch solid #333; border: 0.5ch solid #333;
} }
tr {
line-height: 100%;
}
td { td {
padding: 2px; padding: 2px;
} }
.d5 { .d5 {
width: 4.5em !important; width: 4rem !important;
}
.exp {
transform: rotate(90deg);
} }
#toast { #toast {
opacity: 0; opacity: 0;
background-color: #444; background-color: #444;
@ -109,9 +123,9 @@ td {
position: fixed; position: fixed;
text-align: center; text-align: center;
z-index: 5; z-index: 5;
transform: translateX(-50%%); /* %% because of AsyncWebServer */ transform: translateX(-50%);
max-width: 90%%; /* %% because of AsyncWebServer */ max-width: 90%;
left: 50%%; /* %% because of AsyncWebServer */ left: 50%;
} }
#toast.show { #toast.show {
@ -125,3 +139,29 @@ td {
background-color: #b21; background-color: #b21;
animation: fadein 0.5s; animation: fadein 0.5s;
} }
@media screen and (max-width: 767px) {
input[type="text"],
input[type="file"],
input[type="number"],
input[type="email"],
input[type="tel"],
input[type="password"] {
font-size: 16px;
}
}
@media screen and (max-width: 480px) {
input[type="number"].s {
width: 40px;
}
input[type="number"].xs {
width: 32px;
}
input[type="file"] {
width: 224px;
}
#btns select {
width: 144px;
}
}

View File

@ -194,6 +194,7 @@ int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0);
void handlePlaylist(); void handlePlaylist();
//presets.cpp //presets.cpp
void handlePresets();
bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE); bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE);
inline bool applyTemporaryPreset() {return applyPreset(255);}; inline bool applyTemporaryPreset() {return applyPreset(255);};
void savePreset(byte index, bool persist = true, const char* pname = nullptr, JsonObject saveobj = JsonObject()); void savePreset(byte index, bool persist = true, const char* pname = nullptr, JsonObject saveobj = JsonObject());
@ -324,6 +325,7 @@ void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& h
String settingsProcessor(const String& var); String settingsProcessor(const String& var);
String dmxProcessor(const String& var); String dmxProcessor(const String& var);
void serveSettings(AsyncWebServerRequest* request, bool post = false); void serveSettings(AsyncWebServerRequest* request, bool post = false);
void serveSettingsJS(AsyncWebServerRequest* request);
//ws.cpp //ws.cpp
void handleWs(); void handleWs();

View File

@ -6,7 +6,15 @@
*/ */
// Autogenerated from wled00/data/usermod.htm, do not edit!! // Autogenerated from wled00/data/usermod.htm, do not edit!!
const char PAGE_usermod[] PROGMEM = R"=====(<!DOCTYPE html><html><body>No usermod custom web page set.</body></html>)====="; const uint16_t PAGE_usermod_length = 81;
const uint8_t PAGE_usermod[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xb3, 0x51, 0x74, 0xf1, 0x77, 0x0e,
0x89, 0x0c, 0x70, 0x55, 0xc8, 0x28, 0xc9, 0xcd, 0xb1, 0xb3, 0x81, 0x90, 0x49, 0xf9, 0x29, 0x95,
0x76, 0x7e, 0xf9, 0x0a, 0xa5, 0xc5, 0xa9, 0x45, 0xb9, 0xf9, 0x29, 0x0a, 0xc9, 0xa5, 0xc5, 0x25,
0xf9, 0xb9, 0x0a, 0xe5, 0xa9, 0x49, 0x0a, 0x05, 0x89, 0xe9, 0xa9, 0x0a, 0xc5, 0xa9, 0x25, 0x7a,
0x36, 0xfa, 0x60, 0x55, 0x36, 0xfa, 0x60, 0x2d, 0x00, 0x1e, 0x93, 0x65, 0xc7, 0x48, 0x00, 0x00,
0x00
};
// Autogenerated from wled00/data/msg.htm, do not edit!! // Autogenerated from wled00/data/msg.htm, do not edit!!
@ -35,70 +43,308 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
#endif #endif
// Autogenerated from wled00/data/update.htm, do not edit!! // Autogenerated from wled00/data/update.htm, do not edit!!
const char PAGE_update[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta content="width=device-width" name="viewport"> const uint16_t PAGE_update_length = 733;
<title>WLED Update</title><script> const uint8_t PAGE_update[] PROGMEM = {
function B(){window.history.back()}function U(){document.getElementById("uf").style.display="none",document.getElementById("msg").style.display="block"} 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x85, 0x54, 0xef, 0x6b, 0xdc, 0x46,
</script><style> 0x10, 0xfd, 0xae, 0xbf, 0x62, 0xb3, 0xa6, 0x60, 0x43, 0x4e, 0xb2, 0xef, 0x20, 0x14, 0x9d, 0xa4,
.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none} 0x50, 0xd7, 0xfe, 0x10, 0x28, 0xc4, 0x90, 0xa4, 0xa5, 0x94, 0x12, 0x56, 0xda, 0x91, 0x34, 0xdc,
</style></head><body><h2>WLED Software Update</h2><form method="POST" 0x6a, 0x57, 0xd9, 0x1d, 0xdd, 0xf9, 0x7a, 0xf8, 0x7f, 0xef, 0x68, 0x65, 0x3b, 0x4e, 0x43, 0xe8,
action="/update" id="uf" enctype="multipart/form-data" onsubmit="U()"> 0x17, 0xb1, 0x3f, 0xe6, 0xbd, 0x99, 0x79, 0xf3, 0x56, 0xc5, 0xab, 0x9b, 0xf7, 0xbf, 0x7e, 0xfc,
Installed version: 0.13.1-bl5<br>Download the latest binary: <a 0xf3, 0xee, 0x56, 0xf4, 0x34, 0x98, 0xaa, 0x78, 0xfc, 0x82, 0xd2, 0x55, 0x31, 0x00, 0x29, 0xd1,
href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img 0x38, 0x4b, 0x60, 0xa9, 0x94, 0x07, 0xd4, 0xd4, 0x97, 0x1a, 0xf6, 0xd8, 0xc0, 0x2a, 0x6e, 0xa4,
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"> 0xb0, 0x6a, 0x80, 0x52, 0xee, 0x11, 0x0e, 0xa3, 0xf3, 0x24, 0xab, 0xa4, 0x20, 0x24, 0x03, 0xd5,
</a><br><input type="file" class="bt" name="update" required><br><input 0x1f, 0xbf, 0xdd, 0xde, 0x88, 0x4f, 0xa3, 0x56, 0x04, 0x45, 0xb6, 0x1c, 0x15, 0xa1, 0xf1, 0x38,
type="submit" class="bt" value="Update!"><br><button type="button" class="bt" 0x52, 0x95, 0xb4, 0x93, 0x6d, 0x08, 0x9d, 0x15, 0xd7, 0xe7, 0x17, 0xa7, 0x03, 0x5a, 0xed, 0x0e,
onclick="B()">Back</button></form><div id="msg"><b>Updating...</b><br> 0x69, 0x8f, 0x81, 0x9c, 0x3f, 0xa6, 0xb5, 0x6a, 0x76, 0xe7, 0x17, 0x0f, 0xcf, 0x21, 0x9f, 0x38,
Please do not close or refresh the page :)</div></body></html>)====="; 0x44, 0xbb, 0x66, 0x1a, 0xb8, 0x82, 0xb4, 0x03, 0xba, 0x35, 0x30, 0x2f, 0xaf, 0x8f, 0xef, 0xf4,
0xb9, 0x9c, 0x5a, 0x79, 0x91, 0x06, 0x3a, 0x1a, 0x48, 0x35, 0x86, 0xd1, 0xa8, 0x63, 0x29, 0xad,
0xb3, 0x20, 0x5f, 0xff, 0x10, 0x32, 0x84, 0xee, 0x7b, 0x4c, 0x6d, 0x5c, 0xb3, 0x93, 0x0f, 0x49,
0x91, 0x3d, 0x96, 0x58, 0xc4, 0x80, 0x2a, 0x49, 0x6b, 0x3a, 0xcd, 0x15, 0x75, 0xde, 0x4d, 0x56,
0xe7, 0x67, 0x9b, 0xcd, 0x66, 0xdb, 0x38, 0xe3, 0x7c, 0x7e, 0xd6, 0xb6, 0xed, 0xb6, 0x65, 0x65,
0x56, 0xad, 0x1a, 0xd0, 0x1c, 0xf3, 0xdf, 0xc1, 0x6b, 0x65, 0xd5, 0xeb, 0xa0, 0x6c, 0x58, 0x05,
0xf0, 0xd8, 0x6e, 0x6b, 0xe7, 0x35, 0xf8, 0x3c, 0xdd, 0x34, 0xbd, 0x08, 0xce, 0xa0, 0x16, 0x91,
0xe0, 0x31, 0x6d, 0x8e, 0xd6, 0xa0, 0x85, 0x55, 0x4c, 0xbe, 0x50, 0x05, 0xfc, 0x07, 0xf2, 0xf5,
0xe5, 0x78, 0xbf, 0x1d, 0x94, 0xef, 0xd0, 0xe6, 0x3f, 0x3f, 0x2f, 0x57, 0xe4, 0xc6, 0xfc, 0x6a,
0x3d, 0xde, 0x3f, 0xa0, 0x1d, 0x27, 0xfa, 0x8b, 0x8e, 0x23, 0x94, 0x2d, 0x1a, 0xf8, 0xfb, 0xf4,
0x15, 0x7a, 0xf5, 0x86, 0xef, 0x6b, 0xa7, 0x8f, 0xa7, 0xff, 0xa9, 0x8c, 0xe0, 0x9e, 0x56, 0xca,
0x60, 0x67, 0xf3, 0x86, 0xa5, 0x01, 0xbf, 0x7d, 0xd9, 0xe5, 0x7a, 0xbd, 0x7e, 0xd9, 0x65, 0xac,
0xb2, 0x07, 0xec, 0x7a, 0xe2, 0xe2, 0x2e, 0x7f, 0x7a, 0x38, 0x63, 0x11, 0x4f, 0x4f, 0x5d, 0xcc,
0x7a, 0x47, 0xe5, 0xa2, 0x62, 0x45, 0xb6, 0x18, 0x67, 0x2e, 0x82, 0x4d, 0xb4, 0x5e, 0x7c, 0xf0,
0xc1, 0xb5, 0x74, 0x50, 0x1e, 0x9e, 0x0d, 0xc1, 0x17, 0x45, 0xeb, 0xfc, 0x20, 0xd8, 0x60, 0xbd,
0xd3, 0xa5, 0xbc, 0x7b, 0xff, 0xe1, 0xa3, 0x14, 0x89, 0x8a, 0x43, 0x2f, 0x65, 0x36, 0xc5, 0x40,
0x29, 0x90, 0xef, 0x78, 0xca, 0x02, 0xd8, 0x0e, 0x73, 0xc7, 0x72, 0x98, 0x0c, 0xe1, 0xa8, 0x3c,
0x65, 0x33, 0x7e, 0xc5, 0x51, 0x4a, 0x0a, 0x67, 0xc3, 0x54, 0x0f, 0xc8, 0x16, 0x65, 0xbb, 0xb0,
0x07, 0xdf, 0xd9, 0x40, 0xca, 0x18, 0xd0, 0x62, 0x0f, 0x3e, 0x30, 0x63, 0x2e, 0x2e, 0xd3, 0xab,
0x4d, 0x7a, 0xc5, 0x62, 0xbf, 0x29, 0x6a, 0x5f, 0xdd, 0xb8, 0x83, 0x35, 0x4e, 0x69, 0x41, 0x3d,
0x08, 0xc3, 0x99, 0x02, 0x89, 0x1a, 0xad, 0xf2, 0xc7, 0x5c, 0x14, 0x4a, 0x24, 0xbd, 0x87, 0xb6,
0x94, 0x3d, 0xd1, 0x18, 0xf2, 0x2c, 0xeb, 0x90, 0xfa, 0xa9, 0x4e, 0x1b, 0x37, 0x64, 0xbf, 0xa0,
0x6f, 0x9c, 0x73, 0x3b, 0x84, 0x6c, 0xee, 0x2c, 0xf3, 0x60, 0x40, 0x05, 0x08, 0x52, 0x10, 0x8f,
0x09, 0xb8, 0x82, 0xcf, 0xb5, 0x51, 0x76, 0x27, 0xab, 0x02, 0x87, 0x4e, 0x24, 0xc1, 0x37, 0x5f,
0x79, 0xf8, 0x24, 0x0d, 0x3d, 0x82, 0xd1, 0x21, 0x45, 0xf7, 0x48, 0xfb, 0x44, 0xf1, 0x5f, 0xea,
0x34, 0xec, 0xbb, 0xb7, 0x51, 0xd4, 0xb2, 0xe5, 0x0a, 0x57, 0xe1, 0xcb, 0xc4, 0x0a, 0xce, 0x0f,
0x2c, 0x53, 0xd5, 0xdc, 0x43, 0x11, 0x8d, 0x20, 0x16, 0x59, 0x66, 0x27, 0x48, 0xd1, 0x18, 0x15,
0x02, 0xdb, 0x99, 0x9e, 0xde, 0xe5, 0x93, 0x8c, 0x1e, 0xbe, 0x4c, 0xe8, 0x41, 0xbf, 0x04, 0x26,
0x0b, 0x72, 0x51, 0xee, 0x1b, 0xec, 0x5e, 0x99, 0x89, 0x6f, 0x96, 0x61, 0xbd, 0x92, 0x0b, 0xa8,
0x9e, 0x88, 0xf8, 0x3d, 0x2e, 0xa0, 0x65, 0xf3, 0x0d, 0x28, 0x71, 0xb6, 0x31, 0xd8, 0xec, 0x4a,
0x79, 0x3d, 0xcf, 0xe0, 0x9a, 0xfd, 0x54, 0x64, 0x4b, 0x1c, 0xbb, 0x62, 0x1e, 0x56, 0x55, 0x68,
0xdc, 0xc7, 0x89, 0xce, 0x8f, 0x90, 0x19, 0xab, 0x98, 0x01, 0x6d, 0x97, 0xa6, 0x29, 0xc7, 0xc6,
0x3c, 0xc9, 0x5d, 0x54, 0x43, 0x68, 0x27, 0xac, 0x23, 0x4e, 0xe0, 0x78, 0xe3, 0x3c, 0x77, 0xd0,
0x7a, 0x08, 0x7d, 0x1c, 0xd8, 0xa8, 0x3a, 0x10, 0xf9, 0x45, 0x91, 0x31, 0x1f, 0x73, 0x2f, 0x5e,
0xcb, 0xe2, 0x7f, 0xeb, 0x5f, 0xd6, 0x9c, 0xdd, 0xf9, 0xcd, 0x04, 0x00, 0x00
};
// Autogenerated from wled00/data/welcome.htm, do not edit!! // Autogenerated from wled00/data/welcome.htm, do not edit!!
const char PAGE_welcome[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta charset="utf-8"><meta const uint16_t PAGE_welcome_length = 1528;
content="width=device-width" name="viewport"><meta name="theme-color" const uint8_t PAGE_welcome[] PROGMEM = {
content="#222222"><title>Welcome!</title><style> 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x56, 0x5b, 0x93, 0xaa, 0x3a,
body{font-family:Verdana,Helvetica,sans-serif;text-align:center;background-color:#222;margin:0;color:#fff}button{outline:0;cursor:pointer;padding:8px;margin:10px;width:230px;text-transform:uppercase;font-family:helvetica;font-size:19px;background-color:#333;color:#fff;border:0 solid #fff;border-radius:25px}img{width:950px;max-width:82%;image-rendering:pixelated;image-rendering:crisp-edges;margin:4vh 0 0 0;animation:fi 1s}@keyframes fi{from{opacity:0}to{opacity:1}}.main{animation:fi 1.5s .7s both} 0x16, 0x7e, 0xef, 0x5f, 0xc1, 0x76, 0xea, 0xd4, 0x79, 0x70, 0x77, 0x73, 0x13, 0x51, 0xdb, 0xee,
</style></head><body><img alt="" 0x19, 0xc5, 0x4b, 0x7b, 0x03, 0x6f, 0x78, 0x7b, 0x0b, 0x10, 0x20, 0x08, 0x04, 0x93, 0x80, 0x97,
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG0AAAAfCAMAAADazLOuAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABLUExURQAAAAB81gCU/zKq///mo7sWMN8bO+ZIYtZaAP9rAP+HMsCiG+TAIOnMS0KqNU7KPnLUZOrq6v///4CAgGhoaL+/v6CgoExMTAAAAAlm4O8AAAAZdFJOU////////////////////////////////wABNAq3AAAACXBIWXMAAA7DAAAOwwHHb6hkAAACN0lEQVRIS73VjVLCMBAEYIr8CYKkrdj3f1J37zaXFCpTO+piaDgbPq9px9VQ0qyrvKj4q6m0Zr1h+M7xF1zRmnWzqV9/0d2jttGotO1uv9dUObwej5oqp7fzWVPl8n69aprzoOUUbbvdIbV3OLwitXc6vSG1d7m8I3feSEN0j2CeNbOY4MxigjOLCc4sZsTV2l1cCyy4wIILLLjAxtykltq2rbTU+qi01N5rXNO2leaFORoija2l5MM5a02ac9Ya16Sk5tgaPrUpjZub0BL6YqSxKwbH77XUUmSkJXSl8QtaMuyJhq5maL5nTKVpZC13VmtMpTFT2g4vJjTuGfMzzXftiUZnhdtgb1xofvypRon5TjNnxYN9zJo6K5ruSIzQtGuVZn0x91rKvdHBvm39E7SyZ4y06Gz8BDBFKzsXmhcwyfsGZ9VpbhoiCinaxPNmGWmWWrNU2jB0q6HvOhN1JUtCixQtp2g51ZVUXIPS2RMAD++T2nY/DrDjOMDO4wC7jmNYj3d73nrXug8Yt9uNB8xNU1cKNXWlUFNXCjV1pZhGTE83m2vWfYf/NGj4Bg1zu5JD3/MnH5ZWfLOksbmGWGjgXMN5/C2GXYGFFW9Nmtle6Xut0Gm+JsayCj8z0nhjGvYJzVf4aSzmNYsr+u7Q2JIdoX3YOQjOslmsW1jJ3120nE9gfo79hTaNdcsqVR610lvO47pllae9ReZ805zKo2a3iaY5c75pTmVCA6dJ5H7N0sr/asPwBehb7ifEhusRAAAAAElFTkSuQmCC"> 0xae, 0xfe, 0xef, 0x13, 0x74, 0xf7, 0xd4, 0x3e, 0x75, 0x1e, 0xa6, 0x4e, 0x2c, 0x21, 0xf9, 0x92,
<div class="main"><h1>Welcome to WLED!</h1><h3> 0xb5, 0xd6, 0xb7, 0x92, 0xb8, 0x3e, 0xdb, 0x3f, 0x7a, 0x96, 0xb1, 0xde, 0xcf, 0xfb, 0x42, 0xc8,
Thank you for installing my application!</h3><b>Next steps:</b><br><br> 0x92, 0xf8, 0xbd, 0xfd, 0xeb, 0x09, 0x81, 0xf7, 0xde, 0x4e, 0x20, 0x03, 0x82, 0x1b, 0x02, 0x42,
Connect the module to your local WiFi here!<br><button 0x21, 0x7b, 0xab, 0xe4, 0xcc, 0x7f, 0x6e, 0x54, 0x7e, 0xa1, 0x4f, 0x2e, 0x4e, 0x19, 0x4c, 0x39,
onclick='window.location.href="/settings/wifi"'>WiFi settings</button><br><i> 0x7c, 0x46, 0x1e, 0x0b, 0xdf, 0x3c, 0x58, 0x20, 0x17, 0x3e, 0xdf, 0x07, 0x15, 0x21, 0x05, 0x09,
Just trying this out in AP mode?</i><br><button 0x7c, 0xab, 0x14, 0x08, 0x9e, 0x33, 0x4c, 0xd8, 0xb7, 0xcd, 0x03, 0x65, 0x21, 0x4c, 0xe0, 0xb3,
onclick='window.location.href="/sliders"'>To the controls!</button><br></div> 0x8b, 0x63, 0x4c, 0x2a, 0xbf, 0xb9, 0xf9, 0x97, 0x72, 0x6f, 0x7c, 0x2d, 0x43, 0x2c, 0x86, 0xef,
</body></html>)====="; 0x5b, 0x18, 0xbb, 0x38, 0x81, 0x3f, 0xda, 0xe2, 0x63, 0xdc, 0xa6, 0xec, 0xca, 0x5f, 0x4f, 0x0e,
0xf6, 0xae, 0x9f, 0x3e, 0xb7, 0x7a, 0xf6, 0x41, 0x82, 0xe2, 0x6b, 0x6b, 0x03, 0x89, 0x07, 0x52,
0xf0, 0xf3, 0x03, 0xc6, 0x05, 0x64, 0xc8, 0x05, 0x3f, 0x29, 0x48, 0xe9, 0x33, 0x85, 0x04, 0xf9,
0xaf, 0x0c, 0x5e, 0xd8, 0x33, 0x88, 0x51, 0x90, 0xb6, 0x5c, 0x1e, 0x06, 0x92, 0x57, 0x07, 0xb8,
0xc7, 0x80, 0xe0, 0x3c, 0xf5, 0x1e, 0x1c, 0x5a, 0x65, 0xe0, 0xd7, 0x04, 0x90, 0x00, 0xa5, 0x2d,
0xe9, 0xf5, 0x17, 0xe6, 0xfb, 0xfe, 0x97, 0x93, 0x33, 0x86, 0xd3, 0x4f, 0x9c, 0xb3, 0x18, 0xa5,
0xb0, 0x9c, 0xcb, 0x09, 0xe5, 0x93, 0x19, 0x46, 0x77, 0x4f, 0x19, 0xf0, 0x3c, 0x94, 0x06, 0xad,
0x46, 0x76, 0xf9, 0xb6, 0x97, 0x25, 0xde, 0xbf, 0xef, 0x41, 0x4b, 0x51, 0xcb, 0xfe, 0x3d, 0x3e,
0x23, 0x9c, 0x90, 0x8f, 0x49, 0xd2, 0xca, 0xb3, 0x0c, 0x12, 0x17, 0x50, 0xf8, 0xfa, 0x7b, 0x06,
0xe1, 0x37, 0xf3, 0x07, 0x4a, 0xd1, 0x0d, 0xb6, 0xe4, 0x26, 0xb7, 0xfe, 0x3b, 0x57, 0x55, 0x55,
0x7f, 0xa3, 0xf8, 0xea, 0x60, 0xe2, 0x41, 0xd2, 0x92, 0x04, 0x8a, 0x63, 0xe4, 0x09, 0xbf, 0x61,
0xcf, 0x04, 0x78, 0x28, 0xa7, 0x2d, 0x45, 0xcb, 0x2e, 0x5f, 0x28, 0x09, 0x3e, 0x1f, 0xac, 0x9a,
0x9a, 0x74, 0x67, 0x7b, 0x79, 0x9c, 0x54, 0xab, 0xa1, 0xfc, 0xf1, 0x8a, 0x12, 0x10, 0xc0, 0x67,
0x02, 0x53, 0x6e, 0x56, 0xe6, 0x93, 0xa1, 0x0b, 0x8c, 0x01, 0x83, 0xde, 0xdf, 0x66, 0x5c, 0x82,
0x68, 0xf6, 0x0c, 0xbd, 0x00, 0xd2, 0xef, 0x8c, 0x6b, 0x45, 0x28, 0x48, 0xe5, 0xe7, 0x15, 0xa4,
0x7c, 0x39, 0x43, 0x38, 0x6d, 0xf9, 0x48, 0x90, 0xe9, 0xd7, 0x7f, 0x8e, 0xf0, 0xea, 0x13, 0x7e,
0xe4, 0x54, 0xf0, 0xd1, 0xa7, 0x4f, 0x70, 0xf2, 0x89, 0x33, 0xe0, 0x22, 0x76, 0x6d, 0x49, 0x5f,
0x0c, 0xff, 0x6f, 0x20, 0x7f, 0x7d, 0xbd, 0x24, 0x00, 0xa5, 0x9f, 0x7f, 0x75, 0xf0, 0xa2, 0x51,
0xe1, 0x45, 0xa7, 0x82, 0x83, 0x59, 0xf8, 0xf5, 0xd4, 0x16, 0x1f, 0xc7, 0xdf, 0x16, 0x1f, 0x37,
0xb3, 0xbc, 0x05, 0xef, 0x6d, 0x9e, 0x97, 0x00, 0x62, 0x7e, 0x79, 0xf8, 0x45, 0xa2, 0xc4, 0x7d,
0xab, 0x78, 0x80, 0x81, 0xd6, 0x9d, 0xb5, 0x98, 0xa5, 0x01, 0xdf, 0x3f, 0x0a, 0xeb, 0xb5, 0x9f,
0x68, 0xd3, 0xb5, 0x96, 0x67, 0x69, 0x32, 0x0c, 0x70, 0x87, 0x37, 0x73, 0x65, 0x87, 0x7d, 0x3b,
0xe0, 0xbd, 0xa1, 0x54, 0x8e, 0x7d, 0xa3, 0x33, 0xe3, 0xaf, 0x1e, 0xb8, 0x4d, 0xad, 0xbc, 0x04,
0x3a, 0x3b, 0x73, 0xb5, 0x94, 0x46, 0x1d, 0x42, 0x6b, 0x6e, 0x7d, 0x51, 0x02, 0xcb, 0x74, 0x61,
0xcb, 0xdd, 0x4e, 0xc7, 0xb8, 0x44, 0xe7, 0xa2, 0xb1, 0x5f, 0xd8, 0x1c, 0xeb, 0x4e, 0xed, 0xfe,
0xc5, 0x5e, 0xde, 0xe7, 0xbb, 0x0d, 0x39, 0x30, 0x6c, 0xf1, 0x36, 0x39, 0x89, 0xa2, 0x98, 0x60,
0x9d, 0x6e, 0x67, 0x66, 0xc3, 0xb1, 0xaa, 0x87, 0xd1, 0x9e, 0x1d, 0x40, 0x67, 0xde, 0x24, 0x9d,
0x79, 0xf5, 0x63, 0x46, 0x0d, 0x34, 0xac, 0xae, 0x3b, 0x23, 0x2b, 0x9d, 0xad, 0xa4, 0xc9, 0xc9,
0xb4, 0xf5, 0xc9, 0x3c, 0x9d, 0xda, 0x07, 0x8b, 0x9c, 0xea, 0x05, 0xb7, 0xac, 0x19, 0x9d, 0x60,
0x18, 0x62, 0x30, 0xad, 0x8a, 0x45, 0xdd, 0x08, 0x70, 0xff, 0x32, 0x5b, 0xdf, 0x09, 0xc5, 0x49,
0xcd, 0x6a, 0x94, 0x9d, 0x83, 0x37, 0x18, 0x5b, 0xb6, 0xf8, 0x7f, 0xda, 0xb9, 0xd3, 0x35, 0x3b,
0x27, 0xb5, 0x34, 0x30, 0x76, 0xdd, 0xd1, 0x76, 0x57, 0xe6, 0xa7, 0xf7, 0xf8, 0xc3, 0x3a, 0x9f,
0x3f, 0x3e, 0x9c, 0x7a, 0x78, 0x2c, 0xa7, 0x4c, 0x29, 0xee, 0x2f, 0x36, 0xcb, 0xd1, 0x4a, 0x57,
0x37, 0xd1, 0x66, 0x6a, 0xcc, 0xba, 0x9d, 0xfe, 0x7e, 0x44, 0x1a, 0xc6, 0x7e, 0x72, 0x24, 0x5e,
0xa4, 0xfa, 0xf2, 0x58, 0xd5, 0x6f, 0x60, 0x37, 0x30, 0xb2, 0xb5, 0x55, 0xcd, 0x10, 0xe8, 0x05,
0xce, 0xfc, 0xc4, 0x2f, 0x66, 0x73, 0xb3, 0x90, 0x4e, 0x57, 0x52, 0x4c, 0xa2, 0xda, 0xa9, 0x9e,
0x48, 0x07, 0x22, 0x87, 0xd5, 0x99, 0x7e, 0x19, 0xc8, 0xb7, 0x65, 0x92, 0x6e, 0x6f, 0xa7, 0x4d,
0x53, 0x94, 0x3c, 0x25, 0x62, 0x6c, 0x88, 0x99, 0x25, 0xe7, 0x45, 0xd3, 0xb3, 0x2d, 0xe7, 0x0c,
0x23, 0x0d, 0x9f, 0x32, 0xdd, 0xbf, 0x6d, 0x37, 0xf3, 0xb8, 0x91, 0xd6, 0x9b, 0x20, 0x23, 0x37,
0x6c, 0xd9, 0xb6, 0xe3, 0x14, 0xde, 0xc8, 0xd9, 0xa8, 0xd6, 0xf4, 0x8c, 0xd8, 0xce, 0xad, 0x17,
0xab, 0xa1, 0xec, 0xe9, 0x49, 0x63, 0xa4, 0xfa, 0x70, 0xd5, 0x37, 0xa5, 0x48, 0x31, 0xa0, 0xe9,
0x58, 0xfb, 0xda, 0xec, 0x82, 0x82, 0xc8, 0x9a, 0x1a, 0x6e, 0x8d, 0x1e, 0xe8, 0x7a, 0xa3, 0xc4,
0xb2, 0x6b, 0x5c, 0xaf, 0xb5, 0xf3, 0x68, 0x34, 0x9d, 0x4e, 0xa3, 0xce, 0x85, 0x5d, 0x8f, 0x31,
0x3b, 0x29, 0xc4, 0x59, 0xdb, 0xd5, 0x13, 0x92, 0x64, 0x53, 0x23, 0x3b, 0xd3, 0x52, 0x62, 0x08,
0x06, 0xd6, 0x12, 0xa3, 0x08, 0x28, 0xb1, 0x36, 0x9b, 0x69, 0x40, 0x52, 0x80, 0xdb, 0xdc, 0x03,
0xb9, 0xbe, 0x3a, 0x6a, 0x2c, 0x00, 0x73, 0x62, 0x67, 0xd1, 0x21, 0x77, 0xa4, 0xee, 0xb4, 0xbe,
0x3f, 0xad, 0x2e, 0x93, 0xb3, 0xf3, 0xa1, 0xeb, 0x3b, 0xdb, 0x4e, 0x56, 0xc7, 0xf1, 0x6e, 0x15,
0x37, 0x16, 0x0c, 0xcc, 0xf2, 0xeb, 0x38, 0x3c, 0x69, 0x09, 0x98, 0x6a, 0xe9, 0x7a, 0xb2, 0xc9,
0x0e, 0x86, 0xac, 0x6e, 0x12, 0x36, 0xcb, 0xd6, 0x83, 0xb5, 0x12, 0xd4, 0x8a, 0x71, 0xb4, 0xce,
0x87, 0xfe, 0xec, 0x76, 0xdb, 0xf9, 0x0c, 0xd9, 0x87, 0x34, 0xf4, 0x58, 0xe0, 0xc8, 0x17, 0xec,
0x17, 0xd7, 0x6c, 0x89, 0x53, 0x6d, 0x1d, 0x99, 0xe9, 0x65, 0x6f, 0x36, 0x6f, 0x63, 0x5c, 0x9f,
0x68, 0x24, 0x5f, 0x8d, 0x6e, 0x0b, 0x36, 0xcc, 0x37, 0x87, 0x54, 0xba, 0x34, 0x65, 0x32, 0x29,
0xbc, 0x8f, 0x6e, 0x91, 0xa8, 0xcd, 0xbe, 0xbe, 0xba, 0x1e, 0x6a, 0x57, 0xa9, 0x3e, 0xbc, 0x35,
0xba, 0xbd, 0xee, 0x60, 0x72, 0xa3, 0xbb, 0x24, 0x74, 0xcf, 0x57, 0x9f, 0x0e, 0x0f, 0xcd, 0x4d,
0xe6, 0x84, 0x18, 0x19, 0x28, 0x05, 0x97, 0xb9, 0x99, 0x0c, 0xb7, 0xc9, 0x76, 0x4b, 0x4c, 0x5b,
0x89, 0xba, 0xd2, 0xa9, 0xfe, 0x51, 0x58, 0xa1, 0x29, 0x8f, 0x6d, 0x66, 0xa0, 0xcb, 0x82, 0x65,
0x4a, 0xa0, 0xc9, 0x87, 0x8d, 0xbd, 0x1b, 0xcd, 0x57, 0xca, 0x72, 0xd6, 0xe9, 0x55, 0xab, 0x6b,
0x25, 0xdd, 0x8b, 0x3d, 0xd2, 0x8b, 0xac, 0x59, 0xcf, 0xaa, 0x9d, 0x0d, 0x3d, 0x4a, 0xcc, 0x7d,
0xa4, 0x7a, 0xba, 0x9a, 0x92, 0x5d, 0x1e, 0x34, 0xf6, 0xac, 0x99, 0x9b, 0xdd, 0xc6, 0xc5, 0xb4,
0x65, 0x77, 0x62, 0xee, 0xb6, 0xb1, 0x3d, 0x30, 0x77, 0x46, 0xb4, 0x91, 0xb3, 0x43, 0x38, 0x5c,
0xf7, 0x1b, 0x6a, 0xa2, 0x14, 0x5b, 0x7f, 0xef, 0x8b, 0xe6, 0x30, 0xaa, 0x75, 0x03, 0xf9, 0x96,
0x6b, 0xe3, 0x9e, 0x2a, 0xce, 0xd2, 0x0f, 0xed, 0xb0, 0xf5, 0xa7, 0xd6, 0x91, 0x3a, 0x9c, 0xd2,
0x30, 0x0a, 0x76, 0x33, 0x53, 0x13, 0x0d, 0x65, 0xb8, 0xdb, 0x0f, 0x07, 0x83, 0x6d, 0xd3, 0x4c,
0x78, 0xfd, 0xae, 0xef, 0x72, 0x26, 0x0d, 0x93, 0xea, 0x98, 0x82, 0xab, 0x11, 0x35, 0x6e, 0x52,
0x1a, 0x46, 0xc3, 0x62, 0x3f, 0xbe, 0x6d, 0xfc, 0x1a, 0x58, 0xdd, 0x38, 0x13, 0x4a, 0xaa, 0xb9,
0xbe, 0x50, 0xc6, 0x23, 0x0f, 0xef, 0xd4, 0xbd, 0xb5, 0x88, 0x2c, 0x1a, 0x27, 0x74, 0x2b, 0x47,
0x63, 0x55, 0x56, 0xa4, 0xb4, 0xdf, 0x0c, 0x7c, 0xac, 0x37, 0xc3, 0x35, 0x30, 0x3d, 0x97, 0x9e,
0x36, 0xcb, 0xba, 0x2c, 0xc5, 0x85, 0x55, 0xd3, 0xb3, 0x38, 0x06, 0xb0, 0xb9, 0x84, 0x87, 0x86,
0xa4, 0xdd, 0x26, 0x58, 0x01, 0x2a, 0x02, 0x7b, 0xcd, 0xd5, 0xb5, 0x6c, 0x9d, 0x6c, 0x8c, 0x4e,
0xdd, 0x1b, 0x6b, 0x1f, 0xba, 0x29, 0x51, 0x22, 0x02, 0x3a, 0x3f, 0x77, 0x61, 0xe8, 0xe8, 0xc8,
0xef, 0x87, 0x39, 0x5d, 0xde, 0x7f, 0x5a, 0xfd, 0x78, 0xb0, 0x3e, 0xae, 0xf2, 0x45, 0x62, 0x18,
0x95, 0xf7, 0xa7, 0xb6, 0x87, 0x0a, 0xc1, 0x8d, 0x01, 0xa5, 0x6f, 0x95, 0xb2, 0x28, 0x71, 0x39,
0x0a, 0xe5, 0x6f, 0x2d, 0x12, 0x18, 0x16, 0xb6, 0xd3, 0x7e, 0x8f, 0x6b, 0x12, 0x07, 0xdb, 0xa1,
0xfa, 0xfe, 0xb4, 0x0e, 0x41, 0x7a, 0x14, 0xae, 0x38, 0x17, 0x78, 0x7d, 0x17, 0x50, 0x4a, 0x19,
0x88, 0xb9, 0x5a, 0x04, 0x42, 0x72, 0x15, 0x40, 0x96, 0xc5, 0xbc, 0xa8, 0x97, 0x15, 0xad, 0xb4,
0x50, 0x79, 0xd5, 0x7a, 0x37, 0xb9, 0x1e, 0x08, 0x94, 0xc1, 0x8c, 0xb6, 0xda, 0xa2, 0xc3, 0x11,
0x72, 0xff, 0x3e, 0x19, 0x38, 0x4d, 0xa1, 0xcb, 0x04, 0x2e, 0x8e, 0x42, 0x82, 0xbd, 0x3c, 0xbe,
0x07, 0xe3, 0x7e, 0x89, 0x10, 0x63, 0x17, 0xc4, 0xc2, 0x16, 0x0d, 0x90, 0x10, 0x42, 0xc2, 0x05,
0xf1, 0x6e, 0x73, 0xd7, 0x26, 0xe1, 0x09, 0xa7, 0x2e, 0x8f, 0x71, 0x7c, 0xfb, 0xf3, 0x8c, 0x52,
0x0f, 0x9f, 0x5f, 0xca, 0xc5, 0x65, 0xc0, 0x97, 0x90, 0x40, 0xff, 0xad, 0x22, 0x72, 0xe1, 0x66,
0x9c, 0x0e, 0x15, 0xcf, 0xc8, 0x47, 0x95, 0x3f, 0xdf, 0xef, 0x6e, 0xbe, 0x41, 0xce, 0xe0, 0xee,
0xe6, 0x41, 0x03, 0xbd, 0x3f, 0x8d, 0x73, 0xca, 0x29, 0x90, 0x6b, 0x99, 0x00, 0x0b, 0x11, 0x15,
0xb8, 0xf6, 0xf1, 0xa4, 0x84, 0xce, 0xbc, 0x24, 0x05, 0xff, 0xdd, 0x16, 0xd1, 0xfb, 0x3f, 0x0b,
0xcf, 0x55, 0x09, 0x12, 0xca, 0x03, 0xaf, 0xf1, 0x3d, 0xb7, 0x52, 0xee, 0x09, 0x8e, 0xe9, 0x8f,
0xbf, 0xc6, 0x16, 0xf9, 0xb6, 0xf3, 0xcd, 0x17, 0x1f, 0x65, 0x5d, 0xbc, 0xff, 0x07, 0xf9, 0x2f,
0xec, 0xfa, 0x82, 0xd2, 0x99, 0x08, 0x00, 0x00
};
// Autogenerated from wled00/data/liveview.htm, do not edit!! // Autogenerated from wled00/data/liveview.htm, do not edit!!
const char PAGE_liveview[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta name="viewport" const uint16_t PAGE_liveview_length = 547;
content="width=device-width,initial-scale=1,minimum-scale=1"><meta const uint8_t PAGE_liveview[] PROGMEM = {
charset="utf-8"><meta name="theme-color" content="#222222"><title> 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x53, 0x4d, 0x6f, 0xdb, 0x30,
WLED Live Preview</title><style> 0x0c, 0xbd, 0xe7, 0x57, 0x78, 0x2a, 0x5a, 0x48, 0x88, 0x63, 0x3b, 0xc5, 0xba, 0x8f, 0xf8, 0xe3,
body{margin:0}#canv{background:#000;filter:brightness(175%);width:100%;height:100%;position:absolute} 0xb0, 0xb5, 0x87, 0x02, 0x05, 0xd6, 0x43, 0x81, 0x61, 0x18, 0x76, 0x50, 0x24, 0xc6, 0xd6, 0x2a,
</style></head><body><div id="canv"><script> 0x4b, 0x81, 0x4c, 0xb9, 0x08, 0xd2, 0xfc, 0xf7, 0xc9, 0x76, 0xd2, 0x62, 0xc0, 0x30, 0xa0, 0x3e,
update();var tmout=null;function update(){if(document.hidden)return clearTimeout(tmout),void(tmout=setTimeout(update,250));fetch("/json/live").then(t=>(t.ok||(clearTimeout(tmout),tmout=setTimeout(update,2500)),t.json())).then(t=>{var e="linear-gradient(90deg,",u=t.leds.length;for(i=0;i<u;i++){var o=t.leds[i];o.length>6&&(o=o.substring(2)),e+="#"+o,i<u-1&&(e+=",")}e+=")",document.getElementById("canv").style.background=e,clearTimeout(tmout),tmout=setTimeout(update,40)}).catch((function(t){clearTimeout(tmout),tmout=setTimeout(update,2500)}))} 0xc8, 0x94, 0xf8, 0xf4, 0x48, 0x3e, 0x52, 0xc5, 0xbb, 0xeb, 0x6f, 0x5f, 0x1f, 0x7e, 0xdc, 0xdf,
</script></body></html>)====="; 0x44, 0x0d, 0xb6, 0xba, 0x2a, 0x8e, 0x2b, 0x70, 0x59, 0x15, 0x2d, 0x20, 0x8f, 0x0c, 0x6f, 0xa1,
0x24, 0xbd, 0x82, 0xa7, 0xad, 0x75, 0x48, 0xa2, 0x99, 0xb0, 0x06, 0xc1, 0x60, 0x49, 0x9e, 0x94,
0xc4, 0xa6, 0x94, 0xd0, 0x2b, 0x01, 0x8b, 0x71, 0x13, 0x2b, 0xa3, 0x50, 0x71, 0xbd, 0xe8, 0x04,
0xd7, 0x50, 0x2e, 0xe3, 0x36, 0x1c, 0xb4, 0xbe, 0x3d, 0xed, 0xc9, 0x91, 0x73, 0x26, 0x1a, 0xee,
0x3a, 0x08, 0x1c, 0x1e, 0x37, 0x8b, 0x4f, 0xe4, 0xaf, 0x50, 0xd8, 0x40, 0x0b, 0x0b, 0x61, 0xb5,
0x75, 0x24, 0x7a, 0x09, 0x76, 0x76, 0x39, 0x7e, 0x01, 0x8a, 0x0a, 0x35, 0x54, 0xb3, 0xef, 0x77,
0x37, 0xd7, 0xd1, 0x9d, 0xea, 0x21, 0xba, 0x77, 0x30, 0xa4, 0x57, 0xa4, 0x93, 0xa7, 0xe8, 0x70,
0x37, 0x00, 0xd6, 0x56, 0xee, 0xf6, 0x2d, 0x77, 0xb5, 0x32, 0xab, 0xec, 0x70, 0x26, 0xb8, 0xe9,
0xf7, 0x6b, 0x2e, 0x1e, 0x6b, 0x67, 0xbd, 0x91, 0xab, 0xb3, 0x2c, 0xcb, 0xf2, 0x8d, 0xd2, 0x08,
0x6e, 0xb5, 0x76, 0xaa, 0x6e, 0xd0, 0x40, 0xd7, 0xd1, 0xe5, 0xc7, 0xab, 0x73, 0x96, 0x8f, 0xd5,
0xac, 0x96, 0x59, 0x76, 0x9e, 0x37, 0x30, 0xf8, 0x26, 0x7b, 0x6b, 0xbb, 0x50, 0x9f, 0x35, 0x2b,
0xbe, 0xee, 0xac, 0xf6, 0x08, 0x87, 0x59, 0x91, 0x4e, 0xe1, 0x8a, 0x74, 0xd2, 0x6c, 0x88, 0x5a,
0x15, 0x52, 0xf5, 0x91, 0x92, 0x25, 0x19, 0x82, 0x86, 0x94, 0x3b, 0xe1, 0xd4, 0x16, 0xab, 0x99,
0xdf, 0x4a, 0x8e, 0x40, 0x59, 0xde, 0x73, 0x17, 0x61, 0x6b, 0x3d, 0x96, 0xc6, 0x6b, 0x9d, 0x6f,
0xbc, 0x11, 0x03, 0x6f, 0x74, 0x02, 0xec, 0xd5, 0x86, 0x4a, 0x2b, 0x7c, 0x1b, 0x6a, 0x4f, 0x1a,
0x25, 0x25, 0x18, 0xe6, 0x00, 0xbd, 0x33, 0x91, 0xd0, 0xc0, 0xdd, 0x83, 0x6a, 0x21, 0xdc, 0xa6,
0x23, 0x07, 0x8b, 0x7b, 0xab, 0xe4, 0x64, 0x97, 0x41, 0xd5, 0x93, 0x73, 0x22, 0x8b, 0x2f, 0xaf,
0x32, 0xc6, 0xf2, 0x0d, 0xa0, 0x68, 0x28, 0x49, 0x7f, 0x77, 0xd6, 0xa4, 0x3a, 0xc8, 0x46, 0x58,
0x12, 0x94, 0x36, 0x14, 0xcb, 0x8a, 0x62, 0x62, 0x1f, 0x9f, 0x9f, 0xe9, 0xbf, 0xa8, 0xff, 0xc3,
0x1a, 0x68, 0x63, 0x4c, 0x06, 0x42, 0xca, 0xd8, 0x2b, 0xdb, 0x7e, 0x28, 0x2e, 0xf4, 0x51, 0x2b,
0x13, 0xe8, 0x16, 0xb5, 0xe3, 0x52, 0x85, 0x32, 0xe8, 0xe7, 0x4c, 0x42, 0x1d, 0x93, 0xd8, 0x97,
0x98, 0x68, 0x90, 0x5d, 0x58, 0x4c, 0x8d, 0x4d, 0xbe, 0xb1, 0x8e, 0xaa, 0x32, 0xcb, 0x55, 0xe1,
0x73, 0x35, 0x9f, 0xb3, 0xf1, 0xbe, 0x3d, 0x82, 0x7e, 0xaa, 0x5f, 0xb9, 0x3d, 0x22, 0xab, 0x0f,
0x17, 0x17, 0xd4, 0x96, 0x36, 0xe9, 0xfc, 0xba, 0x43, 0xa7, 0x4c, 0x4d, 0x2f, 0x43, 0x0a, 0x30,
0x0f, 0xd3, 0x41, 0xe6, 0x36, 0x0e, 0x04, 0x8b, 0x65, 0x80, 0x0c, 0x07, 0x31, 0x61, 0x87, 0xe1,
0xcf, 0x48, 0xfc, 0xa2, 0x63, 0x0d, 0x78, 0xa3, 0x61, 0x30, 0xbf, 0xec, 0x6e, 0x25, 0x9d, 0x9a,
0xc3, 0x92, 0xb1, 0x7f, 0xc9, 0xeb, 0x64, 0x94, 0x10, 0xbf, 0x45, 0x88, 0xf7, 0x19, 0x3b, 0xb0,
0x44, 0xf0, 0x41, 0x5e, 0x7a, 0x6a, 0x24, 0x45, 0xb6, 0x7f, 0xb3, 0x9a, 0x07, 0xc6, 0xc6, 0x79,
0x9a, 0x86, 0xa5, 0x48, 0xa7, 0x51, 0x4a, 0xc7, 0x17, 0xf9, 0x07, 0xe3, 0xed, 0x60, 0x85, 0xa7,
0x03, 0x00, 0x00
};
// Autogenerated from wled00/data/liveviewws.htm, do not edit!! // Autogenerated from wled00/data/liveviewws.htm, do not edit!!
const char PAGE_liveviewws[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta name="viewport" const uint16_t PAGE_liveviewws_length = 683;
content="width=device-width,initial-scale=1,minimum-scale=1"><meta const uint8_t PAGE_liveviewws[] PROGMEM = {
charset="utf-8"><meta name="theme-color" content="#222222"><title> 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x75, 0x54, 0xeb, 0x6f, 0xd3, 0x30,
WLED Live Preview</title><style> 0x10, 0xff, 0xde, 0xbf, 0x22, 0x78, 0xa2, 0x8b, 0xd5, 0x34, 0x6d, 0x91, 0x78, 0xe5, 0x51, 0x04,
body{margin:0}#canv{background:#000;filter:brightness(175%);width:100%;height:100%;position:absolute} 0xac, 0x48, 0x4c, 0x13, 0x9b, 0x34, 0xd0, 0x84, 0x10, 0x1f, 0xdc, 0xf8, 0x9a, 0x58, 0x4b, 0xec,
</style></head><body><div id="canv"><script> 0x62, 0x5f, 0x1a, 0x55, 0x55, 0xfe, 0x77, 0xce, 0x4d, 0x37, 0x1e, 0x82, 0x7c, 0xc8, 0xc3, 0xbe,
function updatePreview(e){var n="linear-gradient(90deg,",o=e.length;for(i=0;i<o;i++){var t=e[i];t.length>6&&(t=t.substring(2)),n+="#"+t,i<o-1&&(n+=",")}n+=")",document.getElementById("canv").style.background=n}function getLiveJson(e){try{var n=JSON.parse(e.data);n&&n.leds&&requestAnimationFrame((function(){updatePreview(n.leds)}))}catch(e){console.error("Live-Preview ws error:",e)}}var ws=top.window.ws;ws&&ws.readyState===WebSocket.OPEN?(console.info("Use top WS for peek"),ws.send("{'lv':true}")):(console.info("Peek ws opening"),(ws=new WebSocket("ws://"+document.location.host+"/ws")).onopen=function(){console.info("Peek WS opened"),ws.send("{'lv':true}")}),ws.addEventListener("message",getLiveJson) 0xbb, 0xdf, 0xe3, 0xce, 0xc9, 0x9e, 0x5c, 0x5c, 0xbf, 0xff, 0xfc, 0xf5, 0x66, 0x15, 0x54, 0xd8,
</script></body></html>)====="; 0xd4, 0xcb, 0xec, 0x74, 0x07, 0x21, 0x97, 0x59, 0x03, 0x28, 0x02, 0x2d, 0x1a, 0xc8, 0xd9, 0x4e,
0x41, 0xb7, 0x35, 0x16, 0x59, 0x30, 0x2a, 0x8c, 0x46, 0xd0, 0x98, 0xb3, 0x4e, 0x49, 0xac, 0x72,
0x09, 0x3b, 0x55, 0xc0, 0xf4, 0xf8, 0x11, 0x29, 0xad, 0x50, 0x89, 0x7a, 0xea, 0x0a, 0x51, 0x43,
0xbe, 0x88, 0x1a, 0x5a, 0x68, 0xda, 0xe6, 0xe1, 0x9b, 0x9d, 0x6a, 0x8e, 0x8a, 0x4a, 0x58, 0x07,
0x54, 0xa3, 0xc5, 0xcd, 0xf4, 0x15, 0xfb, 0x03, 0x0a, 0x2b, 0x68, 0x60, 0x5a, 0x98, 0xda, 0x58,
0x16, 0x3c, 0x82, 0x9d, 0x3d, 0x3b, 0x5e, 0x14, 0x8a, 0x0a, 0x6b, 0x58, 0x8e, 0xee, 0xae, 0x56,
0x17, 0xc1, 0x95, 0xda, 0x41, 0x70, 0x63, 0xc1, 0xd3, 0xcb, 0x66, 0xc3, 0x4e, 0xe6, 0x70, 0xef,
0x03, 0xd6, 0x46, 0xee, 0x0f, 0x8d, 0xb0, 0xa5, 0xd2, 0xc9, 0xbc, 0x3f, 0x2b, 0x84, 0xde, 0x1d,
0xd6, 0xa2, 0xb8, 0x2f, 0xad, 0x69, 0xb5, 0x4c, 0xce, 0xe6, 0xf3, 0x79, 0xba, 0x51, 0x35, 0x82,
0x4d, 0xd6, 0x56, 0x95, 0x15, 0x6a, 0x70, 0x2e, 0x5c, 0xbc, 0x7c, 0xfe, 0x94, 0xa7, 0x47, 0x35,
0xc9, 0x62, 0x3e, 0x7f, 0x9a, 0x56, 0xe0, 0xf7, 0x86, 0xf7, 0xad, 0x71, 0xa4, 0xcf, 0xe8, 0x44,
0xac, 0x9d, 0xa9, 0x5b, 0x84, 0x7e, 0x94, 0xcd, 0x06, 0xb8, 0x6c, 0x36, 0x78, 0xe6, 0x51, 0x97,
0x99, 0x54, 0xbb, 0x40, 0xc9, 0x9c, 0x79, 0x50, 0xa2, 0xec, 0x0a, 0xab, 0xb6, 0xb8, 0x1c, 0x6d,
0x5a, 0x5d, 0xf8, 0xfc, 0xa0, 0xdd, 0x4a, 0x81, 0x70, 0x22, 0x1e, 0x02, 0x3f, 0xec, 0x84, 0x0d,
0x74, 0xce, 0x6a, 0xa5, 0x41, 0xd8, 0x69, 0x69, 0x85, 0x54, 0x24, 0x3b, 0x7c, 0x3d, 0x97, 0x50,
0x46, 0x2c, 0x32, 0x39, 0xc4, 0x35, 0xe8, 0x12, 0xab, 0x74, 0x63, 0x6c, 0xa8, 0xf2, 0x79, 0xaa,
0x32, 0x93, 0xaa, 0xc9, 0x64, 0x48, 0xc5, 0x1c, 0xbe, 0xa9, 0xef, 0x29, 0x9e, 0x82, 0x96, 0x2f,
0xc6, 0xe3, 0x10, 0x73, 0x8c, 0x5d, 0xbb, 0x76, 0x68, 0x95, 0x2e, 0xc3, 0x67, 0x9c, 0x47, 0x7a,
0x42, 0x3e, 0xb2, 0x09, 0x46, 0x94, 0x3b, 0x5d, 0x50, 0x88, 0x5f, 0x88, 0x18, 0xef, 0xfd, 0x93,
0xb3, 0x48, 0x9a, 0xa2, 0x6d, 0x08, 0x36, 0x2e, 0x01, 0x57, 0x35, 0xf8, 0xd7, 0x77, 0xfb, 0x8f,
0x32, 0x1c, 0x64, 0xf0, 0xf8, 0xa8, 0x34, 0xfe, 0xe5, 0x61, 0xae, 0xfb, 0x47, 0x45, 0x94, 0xe2,
0x9b, 0x71, 0xe9, 0x8c, 0xf6, 0x7a, 0xd0, 0xee, 0x4f, 0x9a, 0x2e, 0x6f, 0xaf, 0x3f, 0xc5, 0x5b,
0xdf, 0xee, 0x10, 0x62, 0x52, 0x2d, 0x78, 0xaa, 0xc7, 0x63, 0x4d, 0x4c, 0xa5, 0x1b, 0x8f, 0x2d,
0xfc, 0x68, 0xc1, 0xe1, 0x5b, 0x1a, 0x13, 0xe1, 0xeb, 0x7c, 0xb0, 0x34, 0x03, 0x61, 0xf8, 0x50,
0x36, 0xe4, 0x87, 0x3f, 0xad, 0x1a, 0xf2, 0x78, 0xcf, 0x79, 0x5f, 0x08, 0x2c, 0x2a, 0x8f, 0x45,
0x33, 0x42, 0xdd, 0x80, 0x18, 0xac, 0x25, 0x6f, 0x98, 0xa7, 0x31, 0x3d, 0xc5, 0x07, 0x9d, 0x0b,
0x8e, 0xcb, 0x09, 0x8b, 0x80, 0xf7, 0xbd, 0xa7, 0xd4, 0xb9, 0x1c, 0xcd, 0x36, 0xee, 0x94, 0x96,
0xa6, 0x8b, 0x3b, 0x97, 0x76, 0xc4, 0xa3, 0x73, 0xb1, 0xa5, 0xfe, 0xed, 0x6f, 0x91, 0xc0, 0xf2,
0x3c, 0xbf, 0x83, 0xf5, 0xad, 0x29, 0xee, 0x01, 0xe3, 0xeb, 0x9b, 0xd5, 0xa7, 0x37, 0xe1, 0x03,
0x86, 0xd2, 0x1b, 0x13, 0xb2, 0x2f, 0x0e, 0x02, 0xaa, 0x11, 0xdc, 0xdd, 0x06, 0xd4, 0x8e, 0x60,
0x0b, 0x70, 0xcf, 0x78, 0x44, 0x35, 0x1c, 0x68, 0xb2, 0xeb, 0x70, 0x5e, 0xef, 0xce, 0x13, 0xb4,
0x2d, 0xf4, 0x8c, 0xf3, 0xe4, 0xaf, 0xe4, 0x1b, 0x8a, 0xf6, 0xbc, 0xcc, 0x16, 0x34, 0x75, 0x86,
0x12, 0x43, 0xa2, 0xa4, 0x89, 0xec, 0x23, 0x68, 0xc8, 0x3a, 0x97, 0xcc, 0x66, 0x6c, 0xf2, 0xd8,
0x91, 0xda, 0x14, 0x47, 0x7f, 0xe2, 0xca, 0x38, 0x9c, 0xb0, 0x59, 0xe7, 0xa8, 0x72, 0x6c, 0xb4,
0x2f, 0x92, 0xff, 0xe6, 0xd6, 0x3f, 0xa0, 0x88, 0xa4, 0x8f, 0x02, 0xf9, 0x5f, 0x8a, 0xfd, 0x71,
0x43, 0x48, 0xb9, 0xda, 0x11, 0xd6, 0x95, 0x72, 0x74, 0xe4, 0x80, 0x9c, 0x6c, 0xe8, 0x40, 0x88,
0x12, 0x58, 0xf4, 0x5b, 0x73, 0xb9, 0x9f, 0xf8, 0x61, 0x9c, 0xb3, 0xd9, 0x30, 0xec, 0xb3, 0xe3,
0x3f, 0xe3, 0x27, 0xdc, 0x5a, 0x16, 0x55, 0x49, 0x04, 0x00, 0x00
};
// Autogenerated from wled00/data/404.htm, do not edit!! // Autogenerated from wled00/data/404.htm, do not edit!!
const char PAGE_404[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta charset="utf-8"><meta const uint16_t PAGE_404_length = 868;
content="width=device-width" name="viewport"><meta name="theme-color" const uint8_t PAGE_404[] PROGMEM = {
content="#222222"><title>Not found</title><style> 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x65, 0x54, 0x5b, 0x73, 0xaa, 0x3a,
body{font-family:Verdana,Helvetica,sans-serif;text-align:center;background-color:#222;margin:0;color:#fff}img{width:400px;max-width:50%;image-rendering:pixelated;image-rendering:crisp-edges;margin:25px 0 -10px 0}button{outline:0;cursor:pointer;padding:8px;margin:10px;width:230px;text-transform:uppercase;font-family:helvetica;font-size:19px;background-color:#333;color:#fff;border:0 solid #fff;border-radius:25px} 0x14, 0x7e, 0xef, 0xaf, 0xe0, 0x78, 0xe6, 0xcc, 0x7e, 0x68, 0x2d, 0xa8, 0xd8, 0x2a, 0xa2, 0x33,
</style></head><body><img alt="" 0x01, 0x51, 0xec, 0xc5, 0x7a, 0xa3, 0xd6, 0xbe, 0x05, 0x12, 0x21, 0x15, 0x08, 0x4d, 0x82, 0x62,
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAbUExURQAAAAB81gCU/zKq/////9bW1oCAgGhoaAAAAGPLX8AAAAAJdFJOU///////////AFNPeBIAAAAJcEhZcwAADsAAAA7AAWrWiQkAAACdSURBVDhPxc9bDoUgEANQebP/FUuHMjBGY/B+3EYR7RH0qC/ZBc6HwCljgHO+xZIVSI2sYgHaG7EBWh8jWoxTrCBFdDJ+BD4lbIHxAcz8APAVLTsrZE4eQD5qzt3cAFTYokC4YCN9Gybgu4yAQtBFLQXHuHABA7JMeOEC/E0W5uy9gv4vo5QHK2i7yq2C8UABM4HmL+CSTXCTF1DrCX6+Gp9zB5dsAAAAAElFTkSuQmCC"> 0x3b, 0xfd, 0xef, 0x3b, 0x40, 0xf7, 0x9c, 0xce, 0xec, 0x35, 0x03, 0x2b, 0xf9, 0x56, 0xd6, 0x7d,
<h1>404 Not Found</h1><b>Akemi does not know where you are headed...</b><br><br> 0x25, 0xe6, 0x3f, 0xe3, 0x27, 0x7b, 0xb3, 0x5b, 0x38, 0x4a, 0x24, 0x92, 0x78, 0x64, 0x7e, 0xff,
<button onclick='window.location.href="/sliders"'>Back to controls</button> 0x31, 0x44, 0x23, 0x33, 0xc1, 0x02, 0x2a, 0x41, 0x04, 0x19, 0xc7, 0x62, 0xd8, 0xc8, 0xc5, 0xbe,
</body></html>)====="; 0xd9, 0x6b, 0x7c, 0xa3, 0x17, 0x01, 0x4d, 0x05, 0x4e, 0x25, 0x7c, 0x22, 0x48, 0x44, 0x43, 0x84,
0x8f, 0x24, 0xc0, 0xcd, 0x6a, 0xd3, 0x50, 0x52, 0x98, 0xe0, 0x61, 0xe3, 0x48, 0xf0, 0x29, 0xa3,
0x4c, 0xfc, 0xd1, 0xa9, 0x51, 0x11, 0xe1, 0x04, 0x37, 0x03, 0x1a, 0x53, 0xd6, 0xf8, 0x61, 0xe6,
0xdf, 0x76, 0x45, 0xf2, 0xac, 0x20, 0x22, 0xc6, 0xa3, 0x39, 0x15, 0xca, 0x9e, 0xe6, 0x29, 0x32,
0xd5, 0x1a, 0x30, 0xb9, 0x38, 0x4b, 0x76, 0xe1, 0x53, 0x74, 0xfe, 0xdc, 0x4b, 0xb5, 0xe6, 0x1e,
0x26, 0x24, 0x3e, 0x1b, 0xcf, 0x98, 0x21, 0x98, 0xc2, 0x2b, 0x17, 0xc7, 0x47, 0x2c, 0x48, 0x00,
0xaf, 0x38, 0x4c, 0x79, 0x93, 0x63, 0x46, 0xf6, 0x03, 0x81, 0x0b, 0xd1, 0x84, 0x31, 0x09, 0x53,
0x23, 0x90, 0x7e, 0x30, 0x1b, 0xf8, 0x30, 0x38, 0x84, 0xac, 0xb4, 0x5c, 0x07, 0x61, 0x94, 0x9e,
0x07, 0x09, 0x64, 0x21, 0x49, 0x0d, 0x6d, 0xf0, 0x8d, 0xed, 0xf7, 0xfb, 0x2f, 0x92, 0x84, 0x9f,
0x55, 0x42, 0x86, 0xae, 0x69, 0x59, 0x21, 0xcf, 0x14, 0x75, 0x82, 0x46, 0x57, 0xfb, 0x6f, 0x40,
0x12, 0x18, 0xe2, 0x26, 0xc3, 0x29, 0x92, 0x8e, 0xd2, 0xd0, 0xc8, 0x48, 0x81, 0x63, 0x28, 0x30,
0xfa, 0x4b, 0x12, 0x30, 0xc2, 0xb3, 0x26, 0x46, 0x21, 0xe6, 0x7f, 0xfc, 0xb4, 0xbb, 0x59, 0xa1,
0x68, 0x4a, 0xb3, 0xa5, 0x95, 0xfc, 0xcb, 0xcf, 0x85, 0xa0, 0xe9, 0x27, 0xcd, 0x45, 0x4c, 0x52,
0x5c, 0x46, 0x91, 0x33, 0x2e, 0xc3, 0xc8, 0x28, 0xa9, 0x62, 0xce, 0x20, 0x42, 0xa5, 0xa5, 0x5e,
0x15, 0x45, 0x65, 0xa1, 0xd4, 0x1c, 0xd4, 0xd1, 0xb4, 0x3b, 0xe5, 0xba, 0xca, 0x54, 0x30, 0x99,
0xfa, 0x9e, 0xb2, 0xc4, 0xc8, 0xb3, 0x0c, 0xb3, 0x00, 0x72, 0x3c, 0xf8, 0x59, 0xab, 0xe8, 0x4f,
0x8d, 0x6a, 0x94, 0x93, 0x0f, 0x6c, 0xb4, 0xfa, 0x52, 0xfb, 0xef, 0xaa, 0x74, 0x3a, 0x9d, 0x1f,
0xc5, 0x18, 0xf8, 0x94, 0xc9, 0x74, 0x0c, 0x4d, 0xe1, 0x34, 0x26, 0x48, 0xf9, 0x81, 0x35, 0x19,
0x44, 0x24, 0xe7, 0x55, 0x4e, 0x5f, 0x17, 0xa6, 0x5a, 0xf7, 0xc9, 0x54, 0xeb, 0x19, 0x2a, 0xdb,
0x35, 0x32, 0x65, 0x29, 0x15, 0x18, 0xcb, 0x36, 0xcb, 0x96, 0x73, 0x16, 0x0c, 0x1b, 0x08, 0x0a,
0x68, 0x54, 0x85, 0x52, 0xb3, 0x34, 0x94, 0xee, 0x39, 0xbe, 0xd1, 0xaf, 0xc8, 0xb3, 0xf5, 0xb4,
0x3a, 0x69, 0xf7, 0xd3, 0x90, 0x02, 0x49, 0xf3, 0xb5, 0x17, 0x39, 0x5e, 0x28, 0x57, 0x76, 0xb9,
0x05, 0xa1, 0x0d, 0x1e, 0x25, 0xb3, 0x9c, 0x6c, 0xc6, 0xa6, 0x15, 0xf2, 0x32, 0x5f, 0xaf, 0xb4,
0x19, 0x60, 0x5c, 0x0f, 0x6e, 0x96, 0x25, 0xb0, 0x4a, 0x97, 0x5e, 0xcb, 0x92, 0x0a, 0xc5, 0xdb,
0xe9, 0xd8, 0xdb, 0x2d, 0xbd, 0x12, 0xf4, 0x3d, 0xa7, 0xf0, 0x56, 0x95, 0xdc, 0xea, 0xb5, 0x42,
0xdb, 0x53, 0x3f, 0xee, 0xdf, 0xd5, 0x92, 0xfa, 0xfe, 0xb6, 0x45, 0x6d, 0x10, 0x4e, 0x23, 0x0a,
0x4b, 0xf1, 0x74, 0xf1, 0xf0, 0xd2, 0xab, 0x2c, 0xdf, 0xa1, 0xc9, 0xdd, 0x93, 0xa7, 0xfe, 0x4f,
0x60, 0x32, 0x5f, 0x60, 0x6b, 0x56, 0xc9, 0x02, 0x27, 0x7a, 0x0d, 0x4e, 0x00, 0x8c, 0x79, 0xb9,
0xbd, 0x05, 0x60, 0xcb, 0xb6, 0x64, 0x79, 0x28, 0x03, 0x45, 0x6b, 0x6f, 0x65, 0x3d, 0x8f, 0xa3,
0x45, 0x11, 0xf4, 0xfd, 0x31, 0xf5, 0x42, 0x07, 0xcc, 0x97, 0xd8, 0x5f, 0xa8, 0x13, 0x2f, 0x77,
0x1f, 0xdf, 0xac, 0xe9, 0x4e, 0xb5, 0x2e, 0x3b, 0xce, 0x6e, 0x75, 0xbb, 0x72, 0xb5, 0x77, 0x5b,
0x7d, 0xb5, 0x82, 0x1b, 0xf7, 0x64, 0xc7, 0x6f, 0xa1, 0xfb, 0x74, 0x59, 0xbc, 0xce, 0x9e, 0xd7,
0xb3, 0x36, 0xdf, 0x85, 0x2e, 0x9c, 0xde, 0x3a, 0xd6, 0x36, 0xea, 0xbd, 0x6d, 0x69, 0xb1, 0x61,
0xb6, 0x35, 0x41, 0xe3, 0xbb, 0x4b, 0x6b, 0xac, 0xc7, 0xfe, 0xcc, 0x2d, 0x40, 0xf0, 0xd1, 0x03,
0x0b, 0xf0, 0xfc, 0xb0, 0xe1, 0xec, 0xd5, 0xd1, 0xf1, 0x72, 0xdc, 0x7d, 0xff, 0x10, 0x9d, 0x00,
0x4c, 0x36, 0x3b, 0x7a, 0xb0, 0xf5, 0x9d, 0x3d, 0xef, 0x4f, 0xcf, 0x7e, 0x98, 0xeb, 0x67, 0xb0,
0x14, 0xd6, 0xe4, 0x61, 0xf9, 0xe2, 0xe6, 0x2e, 0xb0, 0xc0, 0xed, 0xdd, 0x23, 0x7e, 0x72, 0x6c,
0xd5, 0xd1, 0xb6, 0xdd, 0xfc, 0xdc, 0x0f, 0x8f, 0xfa, 0x91, 0x76, 0x97, 0xee, 0x7d, 0x9b, 0xdc,
0x9e, 0xdf, 0xdb, 0x76, 0xcf, 0x03, 0xd6, 0xa3, 0xee, 0x26, 0x0f, 0x97, 0xf6, 0x7a, 0xf3, 0x62,
0x6f, 0x26, 0xad, 0x31, 0xb3, 0x5f, 0x6e, 0x2e, 0xa7, 0x59, 0xff, 0xc3, 0xea, 0xa2, 0x2a, 0x5b,
0xe0, 0xc4, 0x93, 0xcd, 0x61, 0x9d, 0x2f, 0x13, 0xdb, 0x6e, 0x8c, 0x2e, 0xcc, 0xa8, 0x35, 0xd2,
0x35, 0x5d, 0x29, 0xef, 0xeb, 0xa4, 0xbe, 0xaf, 0x12, 0x31, 0xfd, 0x11, 0x38, 0xe0, 0x84, 0x28,
0x88, 0x62, 0xae, 0xa4, 0x52, 0x76, 0x48, 0xe9, 0x49, 0x39, 0x45, 0x98, 0x61, 0xe5, 0x4c, 0x73,
0x05, 0x4a, 0x5e, 0x0e, 0x08, 0x46, 0xd7, 0xd7, 0xd7, 0xa6, 0xea, 0x4b, 0x0d, 0x56, 0x7d, 0x17,
0x66, 0x7d, 0x19, 0x14, 0x9a, 0x06, 0x31, 0x09, 0x0e, 0xc3, 0x5f, 0x27, 0x92, 0x22, 0x7a, 0xba,
0x8e, 0x69, 0x00, 0x05, 0xa1, 0xe9, 0x75, 0xc4, 0xf0, 0x7e, 0xd8, 0x50, 0xb9, 0x1c, 0x43, 0xcc,
0x78, 0xe3, 0xd7, 0xc8, 0x92, 0xb3, 0xab, 0x08, 0xaa, 0x94, 0x6f, 0x09, 0xa3, 0x31, 0x97, 0xe6,
0x2a, 0x13, 0xd2, 0x96, 0x5a, 0x8f, 0x9f, 0x5a, 0xbd, 0x6a, 0xbf, 0x01, 0x22, 0xc8, 0xb7, 0x64,
0xeb, 0x04, 0x00, 0x00
};
// Autogenerated from wled00/data/favicon.ico, do not edit!! // Autogenerated from wled00/data/favicon.ico, do not edit!!

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -189,7 +189,7 @@ void sendImprovInfoResponse() {
out[11] = 4; //Firmware len ("WLED") out[11] = 4; //Firmware len ("WLED")
out[12] = 'W'; out[13] = 'L'; out[14] = 'E'; out[15] = 'D'; out[12] = 'W'; out[13] = 'L'; out[14] = 'E'; out[15] = 'D';
uint8_t lengthSum = 17; uint8_t lengthSum = 17;
uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.13.0-b5/%i"),VERSION); uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.13.0-b6/%i"),VERSION);
out[16] = vlen; lengthSum += vlen; out[16] = vlen; lengthSum += vlen;
uint8_t hlen = 7; uint8_t hlen = 7;
#ifdef ESP8266 #ifdef ESP8266

View File

@ -71,12 +71,10 @@ void decBrightness()
// apply preset or fallback to a effect and palette if it doesn't exist // apply preset or fallback to a effect and palette if it doesn't exist
void presetFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID) void presetFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID)
{ {
byte prevError = errorFlag; applyPreset(presetID, CALL_MODE_BUTTON_PRESET);
if (!applyPreset(presetID, CALL_MODE_BUTTON)) { //these two will be overwritten if preset exists in handlePresets()
effectCurrent = effectID; effectCurrent = effectID;
effectPalette = paletteID; effectPalette = paletteID;
errorFlag = prevError; //clear error 12 from non-existent preset
}
} }
/* /*
@ -90,7 +88,7 @@ bool decodeIRCustom(uint32_t code)
{ {
//just examples, feel free to modify or remove //just examples, feel free to modify or remove
case IRCUSTOM_ONOFF : toggleOnOff(); break; case IRCUSTOM_ONOFF : toggleOnOff(); break;
case IRCUSTOM_MACRO1 : applyPreset(1, CALL_MODE_BUTTON); break; case IRCUSTOM_MACRO1 : applyPreset(1, CALL_MODE_BUTTON_PRESET); break;
default: return false; default: return false;
} }
@ -575,12 +573,7 @@ void decodeIRJson(uint32_t code)
JsonObject fdo; JsonObject fdo;
JsonObject jsonCmdObj; JsonObject jsonCmdObj;
DEBUG_PRINTLN(F("IR JSON buffer requested.")); if (!requestJSONBufferLock(13)) return;
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(6)) return;
#endif
sprintf_P(objKey, PSTR("\"0x%lX\":"), (unsigned long)code); sprintf_P(objKey, PSTR("\"0x%lX\":"), (unsigned long)code);
@ -593,12 +586,12 @@ void decodeIRJson(uint32_t code)
lastValidCode = 0; lastValidCode = 0;
if (fdo.isNull()) { if (fdo.isNull()) {
//the received code does not exist //the received code does not exist
releaseJSONBufferLock();
if (!WLED_FS.exists("/ir.json")) errorFlag = ERR_FS_IRLOAD; //warn if IR file itself doesn't exist if (!WLED_FS.exists("/ir.json")) errorFlag = ERR_FS_IRLOAD; //warn if IR file itself doesn't exist
releaseJSONBufferLock();
return; return;
} }
cmdStr = fdo["cmd"].as<String>();; cmdStr = fdo["cmd"].as<String>();
jsonCmdObj = fdo["cmd"]; //object jsonCmdObj = fdo["cmd"]; //object
// command is JSON object // command is JSON object
@ -615,9 +608,9 @@ void decodeIRJson(uint32_t code)
lastValidCode = code; lastValidCode = code;
decBrightness(); decBrightness();
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback } else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] ? fdo["PL"] : 1; uint8_t p1 = fdo["PL"] | 1;
uint8_t p2 = fdo["FX"] ? fdo["FX"] : random8(MODE_COUNT); uint8_t p2 = fdo["FX"] | random8(MODE_COUNT);
uint8_t p3 = fdo["FP"] ? fdo["FP"] : 0; uint8_t p3 = fdo["FP"] | 0;
presetFallback(p1, p2, p3); presetFallback(p1, p2, p3);
} }
} else { } else {
@ -638,9 +631,9 @@ void decodeIRJson(uint32_t code)
} }
colorUpdated(CALL_MODE_BUTTON); colorUpdated(CALL_MODE_BUTTON);
} else if (!jsonCmdObj.isNull()) { } else if (!jsonCmdObj.isNull()) {
deserializeState(jsonCmdObj, CALL_MODE_BUTTON); // command is JSON object
deserializeState(jsonCmdObj, CALL_MODE_BUTTON_PRESET);
} }
//fileDoc = nullptr;
releaseJSONBufferLock(); releaseJSONBufferLock();
} }
@ -669,7 +662,8 @@ void handleIR()
{ {
if (results.value != 0) // only print results if anything is received ( != 0 ) if (results.value != 0) // only print results if anything is received ( != 0 )
{ {
DEBUG_PRINTF("IR recv: 0x%lX\n", (unsigned long)results.value); if (!pinManager.isPinAllocated(1)) //GPIO 1 - Serial TX pin
Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value);
} }
decodeIR(results.value); decodeIR(results.value);
irrecv->resume(); irrecv->resume();

View File

@ -67,7 +67,8 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
uint16_t grp = elem["grp"] | seg.grouping; uint16_t grp = elem["grp"] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing; uint16_t spc = elem[F("spc")] | seg.spacing;
strip.setSegment(id, start, stop, grp, spc); uint16_t of = seg.offset;
if (!(elem[F("spc")].isNull() && elem["grp"].isNull())) effectChanged = true; //send UDP
uint16_t len = 1; uint16_t len = 1;
if (stop > start) len = stop - start; if (stop > start) len = stop - start;
@ -76,9 +77,10 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
int offsetAbs = abs(offset); int offsetAbs = abs(offset);
if (offsetAbs > len - 1) offsetAbs %= len; if (offsetAbs > len - 1) offsetAbs %= len;
if (offset < 0) offsetAbs = len - offsetAbs; if (offset < 0) offsetAbs = len - offsetAbs;
seg.offset = offsetAbs; of = offsetAbs;
} }
if (stop > start && seg.offset > len -1) seg.offset = len -1; if (stop > start && of > len -1) of = len -1;
strip.setSegment(id, start, stop, grp, spc, of);
byte segbri = 0; byte segbri = 0;
if (getVal(elem["bri"], &segbri)) { if (getVal(elem["bri"], &segbri)) {
@ -157,19 +159,23 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED)); seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED));
seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR )); seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR ));
if (!(elem[F("sel")].isNull() && elem["rev"].isNull() && elem["on"].isNull() && elem[F("mi")].isNull())) effectChanged = true; //send UDP
//temporary, strip object gets updated via colorUpdated() //temporary, strip object gets updated via colorUpdated()
if (id == strip.getMainSegmentId()) { if (id == strip.getMainSegmentId()) {
byte effectPrev = effectCurrent;
if (getVal(elem["fx"], &effectCurrent, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 1-255 exact value) if (getVal(elem["fx"], &effectCurrent, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 1-255 exact value)
if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
} }
effectSpeed = elem[F("sx")] | effectSpeed; effectSpeed = elem[F("sx")] | effectSpeed;
effectIntensity = elem[F("ix")] | effectIntensity; effectIntensity = elem[F("ix")] | effectIntensity;
getVal(elem["pal"], &effectPalette, 1, strip.getPaletteCount()); getVal(elem["pal"], &effectPalette, 1, strip.getPaletteCount());
} else { //permanent } else { //permanent
byte fx = seg.mode; byte fx = seg.mode;
byte fxPrev = fx;
if (getVal(elem["fx"], &fx, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 1-255 exact value) if (getVal(elem["fx"], &fx, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 1-255 exact value)
strip.setMode(id, fx); strip.setMode(id, fx);
if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually if (!presetId && seg.mode != fxPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
} }
seg.speed = elem[F("sx")] | seg.speed; seg.speed = elem[F("sx")] | seg.speed;
seg.intensity = elem[F("ix")] | seg.intensity; seg.intensity = elem[F("ix")] | seg.intensity;
@ -233,7 +239,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
return; // seg.differs(prev); return; // seg.differs(prev);
} }
// deserializes WLED state (fileDoc points to doc object if called from web server) // deserializes WLED state (fileDoc points to doc object (root) if called from web server, MQTT, IR, preset; not from UDP)
bool deserializeState(JsonObject root, byte callMode, byte presetId) bool deserializeState(JsonObject root, byte callMode, byte presetId)
{ {
DEBUG_PRINTLN(F("Deserializing state")); DEBUG_PRINTLN(F("Deserializing state"));
@ -314,7 +320,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
byte lowestActive = 99; byte lowestActive = 99;
for (byte s = 0; s < strip.getMaxSegments(); s++) for (byte s = 0; s < strip.getMaxSegments(); s++)
{ {
WS2812FX::Segment sg = strip.getSegment(s); WS2812FX::Segment &sg = strip.getSegment(s);
if (sg.isActive()) if (sg.isActive())
{ {
if (lowestActive == 99) lowestActive = s; if (lowestActive == 99) lowestActive = s;
@ -345,11 +351,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
usermods.readFromJsonState(root); usermods.readFromJsonState(root);
int8_t ledmap = root[F("ledmap")] | -1; loadLedmap = root[F("ledmap")] | loadLedmap;
if (ledmap >= 0) {
//strip.deserializeMap(ledmap); // requires separate JSON buffer
loadLedmap = ledmap;
}
byte ps = root[F("psave")]; byte ps = root[F("psave")];
if (ps > 0) { if (ps > 0) {
@ -484,7 +486,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
JsonArray seg = root.createNestedArray("seg"); JsonArray seg = root.createNestedArray("seg");
for (byte s = 0; s < strip.getMaxSegments(); s++) for (byte s = 0; s < strip.getMaxSegments(); s++)
{ {
WS2812FX::Segment sg = strip.getSegment(s); WS2812FX::Segment &sg = strip.getSegment(s);
if (sg.isActive()) if (sg.isActive())
{ {
JsonObject seg0 = seg.createNestedObject(); JsonObject seg0 = seg.createNestedObject();
@ -524,7 +526,7 @@ void serializeInfo(JsonObject root)
} }
leds[F("pwr")] = strip.currentMilliamps; leds[F("pwr")] = strip.currentMilliamps;
leds[F("fps")] = strip.getFps(); leds["fps"] = strip.getFps();
leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0; leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0;
leds[F("maxseg")] = strip.getMaxSegments(); leds[F("maxseg")] = strip.getMaxSegments();
//leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config //leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config
@ -957,12 +959,8 @@ void serveJson(AsyncWebServerRequest* request)
return; return;
} }
#ifdef WLED_USE_DYNAMIC_JSON if (!requestJSONBufferLock(17)) return;
AsyncJsonResponse* response = new AsyncJsonResponse(JSON_BUFFER_SIZE, subJson==6);
#else
if (!requestJSONBufferLock(7)) return;
AsyncJsonResponse *response = new AsyncJsonResponse(&doc, subJson==6); AsyncJsonResponse *response = new AsyncJsonResponse(&doc, subJson==6);
#endif
JsonVariant lDoc = response->getRoot(); JsonVariant lDoc = response->getRoot();

View File

@ -76,15 +76,16 @@ bool colorChanged()
void colorUpdated(int callMode) void colorUpdated(int callMode)
{ {
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa 11: ws send only 12: button preset
if (callMode != CALL_MODE_INIT && if (callMode != CALL_MODE_INIT &&
callMode != CALL_MODE_DIRECT_CHANGE && callMode != CALL_MODE_DIRECT_CHANGE &&
callMode != CALL_MODE_NO_NOTIFY) strip.applyToAllSelected = true; //if not from JSON api, which directly sets segments callMode != CALL_MODE_NO_NOTIFY &&
callMode != CALL_MODE_BUTTON_PRESET) strip.applyToAllSelected = true; //if not from JSON api, which directly sets segments
bool someSel = false; bool someSel = false;
if (callMode == CALL_MODE_NOTIFICATION) { if (callMode == CALL_MODE_NOTIFICATION) {
someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects || receiveSegmentOptions);
} }
//Notifier: apply received FX to selected segments only if actually receiving FX //Notifier: apply received FX to selected segments only if actually receiving FX

View File

@ -91,16 +91,10 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
colorUpdated(CALL_MODE_DIRECT_CHANGE); colorUpdated(CALL_MODE_DIRECT_CHANGE);
} else if (strcmp_P(topic, PSTR("/api")) == 0) { } else if (strcmp_P(topic, PSTR("/api")) == 0) {
DEBUG_PRINTLN(F("MQTT JSON buffer requested.")); DEBUG_PRINTLN(F("MQTT JSON buffer requested."));
#ifdef WLED_USE_DYNAMIC_JSON if (!requestJSONBufferLock(15)) { delete[] payloadStr; return; }
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(8)) return;
#endif
if (payload[0] == '{') { //JSON API if (payload[0] == '{') { //JSON API
deserializeJson(doc, payloadStr); deserializeJson(doc, payloadStr);
//fileDoc = &doc; // used for applying presets (presets.cpp)
deserializeState(doc.as<JsonObject>()); deserializeState(doc.as<JsonObject>());
//fileDoc = nullptr;
} else { //HTTP API } else { //HTTP API
String apireq = "win&"; String apireq = "win&";
apireq += (char*)payloadStr; apireq += (char*)payloadStr;

View File

@ -331,11 +331,17 @@ void checkTimers()
for (uint8_t i = 0; i < 8; i++) for (uint8_t i = 0; i < 8; i++)
{ {
if (timerMacro[i] != 0 if (timerMacro[i] != 0
&& (timerWeekday[i] & 0x01) //timer is enabled
&& (timerHours[i] == hour(localTime) || timerHours[i] == 24) //if hour is set to 24, activate every hour && (timerHours[i] == hour(localTime) || timerHours[i] == 24) //if hour is set to 24, activate every hour
&& timerMinutes[i] == minute(localTime) && timerMinutes[i] == minute(localTime)
&& (timerWeekday[i] & 0x01) //timer is enabled && ( (timerDay[i] == 0 && ((timerWeekday[i] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week
&& ((timerWeekday[i] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week || (timerDay[i] > 0 && timerDay[i]==day(localTime) &&
(timerMonth[i] == 0 || timerMonth[i]==month(localTime) || (timerMonth[i]==13 && month(localTime)%2) || (timerMonth[i]==14 && !(month(localTime)%2)))
)
)
)
{ {
unloadPlaylist();
applyPreset(timerMacro[i]); applyPreset(timerMacro[i]);
} }
} }
@ -349,6 +355,7 @@ void checkTimers()
&& (timerWeekday[8] & 0x01) //timer is enabled && (timerWeekday[8] & 0x01) //timer is enabled
&& ((timerWeekday[8] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week && ((timerWeekday[8] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week
{ {
unloadPlaylist();
applyPreset(timerMacro[8]); applyPreset(timerMacro[8]);
DEBUG_PRINTF("Sunrise macro %d triggered.",timerMacro[8]); DEBUG_PRINTF("Sunrise macro %d triggered.",timerMacro[8]);
} }
@ -363,6 +370,7 @@ void checkTimers()
&& (timerWeekday[9] & 0x01) //timer is enabled && (timerWeekday[9] & 0x01) //timer is enabled
&& ((timerWeekday[9] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week && ((timerWeekday[9] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week
{ {
unloadPlaylist();
applyPreset(timerMacro[9]); applyPreset(timerMacro[9]);
DEBUG_PRINTF("Sunset macro %d triggered.",timerMacro[9]); DEBUG_PRINTF("Sunset macro %d triggered.",timerMacro[9]);
} }

View File

@ -119,7 +119,8 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
void handlePlaylist() { void handlePlaylist() {
static unsigned long presetCycledTime = 0; static unsigned long presetCycledTime = 0;
if (currentPlaylist < 0 || playlistEntries == nullptr) return; // if fileDoc is not null JSON buffer is in use so just quit
if (currentPlaylist < 0 || playlistEntries == nullptr || fileDoc != nullptr) return;
if (millis() - presetCycledTime > (100*playlistEntryDur)) { if (millis() - presetCycledTime > (100*playlistEntryDur)) {
presetCycledTime = millis(); presetCycledTime = millis();

View File

@ -4,80 +4,87 @@
* Methods to handle saving and loading presets to/from the filesystem * Methods to handle saving and loading presets to/from the filesystem
*/ */
// called from: handleSet(), deserializeState(), applyMacro(), handlePlaylist(), checkCountdown(), checkTimers(), handleNightlight(), presetFallback() #ifdef ARDUINO_ARCH_ESP32
// shortPressAction(), longPressAction(), doublePressAction(), handleSwitch(), onAlexaChange() static char *tmpRAMbuffer = nullptr;
#endif
static volatile byte presetToApply = 0;
static volatile byte callModeToApply = 0;
bool applyPreset(byte index, byte callMode) bool applyPreset(byte index, byte callMode)
{ {
if (index == 0) return false; presetToApply = index;
callModeToApply = callMode;
const char *filename = index < 255 ? "/presets.json" : "/tmp.json";
if (fileDoc) {
errorFlag = readObjectFromFileUsingId(filename, index, fileDoc) ? ERR_NONE : ERR_FS_PLOAD;
JsonObject fdo = fileDoc->as<JsonObject>();
if (fdo["ps"] == index) fdo.remove("ps"); //remove load request for same presets to prevent recursive crash
#ifdef WLED_DEBUG_FS
serializeJson(*fileDoc, Serial);
#endif
deserializeState(fdo, callMode, index);
} else {
DEBUG_PRINTLN(F("Apply preset JSON buffer requested."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(9)) return false;
#endif
errorFlag = readObjectFromFileUsingId(filename, index, &doc) ? ERR_NONE : ERR_FS_PLOAD;
JsonObject fdo = doc.as<JsonObject>();
if (fdo["ps"] == index) fdo.remove("ps");
#ifdef WLED_DEBUG_FS
serializeJson(doc, Serial);
#endif
deserializeState(fdo, callMode, index);
releaseJSONBufferLock();
}
if (!errorFlag) {
if (index < 255) currentPreset = index;
return true; return true;
} }
return false;
void handlePresets()
{
if (presetToApply == 0 || fileDoc) return; //JSON buffer allocated (apply preset in next cycle) or no preset waiting
JsonObject fdo;
const char *filename = presetToApply < 255 ? "/presets.json" : "/tmp.json";
// allocate buffer
DEBUG_PRINTLN(F("Apply preset JSON buffer requested."));
if (!requestJSONBufferLock(9)) return; // will also assign fileDoc
#ifdef ARDUINO_ARCH_ESP32
if (presetToApply==255 && tmpRAMbuffer!=nullptr) {
deserializeJson(*fileDoc,tmpRAMbuffer);
errorFlag = ERR_NONE;
} else
#endif
{
errorFlag = readObjectFromFileUsingId(filename, presetToApply, fileDoc) ? ERR_NONE : ERR_FS_PLOAD;
}
fdo = fileDoc->as<JsonObject>();
fdo.remove("ps"); //remove load request for presets to prevent recursive crash
deserializeState(fdo, callModeToApply, presetToApply);
#if defined(ARDUINO_ARCH_ESP32)
//Aircoookie recommended not to delete buffer
if (presetToApply==255 && tmpRAMbuffer!=nullptr) {
free(tmpRAMbuffer);
tmpRAMbuffer = nullptr;
}
#endif
releaseJSONBufferLock(); // will also clear fileDoc
if (!errorFlag && presetToApply < 255) currentPreset = presetToApply;
if (callModeToApply == CALL_MODE_BUTTON_PRESET) errorFlag = ERR_NONE; //ignore error on button press
presetToApply = 0; //clear request for preset
callModeToApply = 0;
} }
//called from handleSet(PS=) [network callback (fileDoc==nullptr), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)]
void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj) void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj)
{ {
if (index == 0 || (index > 250 && persist) || (index<255 && !persist)) return; if (index == 0 || (index > 250 && persist) || (index<255 && !persist)) return;
JsonObject sObj = saveobj; JsonObject sObj = saveobj;
bool bufferAllocated = false;
const char *filename = persist ? "/presets.json" : "/tmp.json"; const char *filename = persist ? "/presets.json" : "/tmp.json";
if (!fileDoc) { if (!fileDoc) {
// called from handleSet() HTTP API
DEBUG_PRINTLN(F("Save preset JSON buffer requested.")); DEBUG_PRINTLN(F("Save preset JSON buffer requested."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(10)) return; if (!requestJSONBufferLock(10)) return;
#endif sObj = fileDoc->to<JsonObject>();
bufferAllocated = true;
sObj = doc.to<JsonObject>(); }
if (pname) sObj["n"] = pname; if (pname) sObj["n"] = pname;
DEBUGFS_PRINTLN(F("Save current state"));
serializeState(sObj, true);
if (persist) currentPreset = index;
writeObjectToFileUsingId(filename, index, &doc);
releaseJSONBufferLock();
} else { //from JSON API (fileDoc != nullptr)
DEBUGFS_PRINTLN(F("Reuse recv buffer"));
sObj.remove(F("psave")); sObj.remove(F("psave"));
sObj.remove(F("v")); sObj.remove(F("v"));
if (!sObj["o"]) { if (!sObj["o"]) {
DEBUGFS_PRINTLN(F("Save current state")); DEBUGFS_PRINTLN(F("Serialize current state"));
serializeState(sObj, true, sObj["ib"], sObj["sb"]); if (sObj["ib"].isNull() && sObj["sb"].isNull()) serializeState(sObj, true);
else serializeState(sObj, true, sObj["ib"], sObj["sb"]);
if (persist) currentPreset = index; if (persist) currentPreset = index;
} }
sObj.remove("o"); sObj.remove("o");
@ -86,9 +93,29 @@ void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj)
sObj.remove(F("error")); sObj.remove(F("error"));
sObj.remove(F("time")); sObj.remove(F("time"));
#if defined(ARDUINO_ARCH_ESP32)
if (index==255) {
if (tmpRAMbuffer!=nullptr) free(tmpRAMbuffer);
size_t len = measureJson(*fileDoc) + 1;
DEBUG_PRINTLN(len);
// if possible use SPI RAM on ESP32
#ifdef WLED_USE_PSRAM
if (psramFound())
tmpRAMbuffer = (char*) ps_malloc(len);
else
#endif
tmpRAMbuffer = (char*) malloc(len);
if (tmpRAMbuffer!=nullptr) {
serializeJson(*fileDoc, tmpRAMbuffer, len);
} else {
writeObjectToFileUsingId(filename, index, fileDoc); writeObjectToFileUsingId(filename, index, fileDoc);
} }
} else
#endif
writeObjectToFileUsingId(filename, index, fileDoc);
if (persist) presetsModifiedTime = toki.second(); //unix time if (persist) presetsModifiedTime = toki.second(); //unix time
if (bufferAllocated) releaseJSONBufferLock();
updateFSInfo(); updateFSInfo();
} }

View File

@ -77,6 +77,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.cctBlending = request->arg(F("CB")).toInt(); strip.cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending); Bus::setCCTBlend(strip.cctBlending);
Bus::setAutoWhiteMode(request->arg(F("AW")).toInt()); Bus::setAutoWhiteMode(request->arg(F("AW")).toInt());
strip.setTargetFps(request->arg(F("FR")).toInt());
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
@ -201,7 +202,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
receiveNotificationBrightness = request->hasArg(F("RB")); receiveNotificationBrightness = request->hasArg(F("RB"));
receiveNotificationColor = request->hasArg(F("RC")); receiveNotificationColor = request->hasArg(F("RC"));
receiveNotificationEffects = request->hasArg(F("RX")); receiveNotificationEffects = request->hasArg(F("RX"));
receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); receiveSegmentOptions = request->hasArg(F("SO"));
receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects || receiveSegmentOptions);
notifyDirectDefault = request->hasArg(F("SD")); notifyDirectDefault = request->hasArg(F("SD"));
notifyDirect = notifyDirectDefault; notifyDirect = notifyDirectDefault;
notifyButton = request->hasArg(F("SB")); notifyButton = request->hasArg(F("SB"));
@ -290,6 +292,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
//start ntp if not already connected //start ntp if not already connected
if (ntpEnabled && WLED_CONNECTED && !ntpConnected) ntpConnected = ntpUdp.begin(ntpLocalPort); if (ntpEnabled && WLED_CONNECTED && !ntpConnected) ntpConnected = ntpUdp.begin(ntpLocalPort);
ntpLastSyncTime = 0; // force new NTP query
longitude = request->arg(F("LN")).toFloat(); longitude = request->arg(F("LN")).toFloat();
latitude = request->arg(F("LT")).toFloat(); latitude = request->arg(F("LT")).toFloat();
@ -325,9 +328,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
macroCountdown = request->arg(F("MC")).toInt(); macroCountdown = request->arg(F("MC")).toInt();
macroNl = request->arg(F("MN")).toInt(); macroNl = request->arg(F("MN")).toInt();
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) { for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
char mp[4] = "MP"; mp[2] = 48+i; mp[3] = 0; // short char mp[4] = "MP"; mp[2] = (i<10?48:55)+i; mp[3] = 0; // short
char ml[4] = "ML"; ml[2] = 48+i; ml[3] = 0; // long char ml[4] = "ML"; ml[2] = (i<10?48:55)+i; ml[3] = 0; // long
char md[4] = "MD"; md[2] = 48+i; md[3] = 0; // double char md[4] = "MD"; md[2] = (i<10?48:55)+i; md[3] = 0; // double
//if (!request->hasArg(mp)) break; //if (!request->hasArg(mp)) break;
macroButton[i] = request->arg(mp).toInt(); // these will default to 0 if not present macroButton[i] = request->arg(mp).toInt(); // these will default to 0 if not present
macroLongPress[i] = request->arg(ml).toInt(); macroLongPress[i] = request->arg(ml).toInt();
@ -335,21 +338,28 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
} }
char k[3]; k[2] = 0; char k[3]; k[2] = 0;
for (int i = 0; i<10; i++) for (int i = 0; i<10; i++) {
{
k[1] = i+48;//ascii 0,1,2,3 k[1] = i+48;//ascii 0,1,2,3
k[0] = 'H'; //timer hours k[0] = 'H'; //timer hours
timerHours[i] = request->arg(k).toInt(); timerHours[i] = request->arg(k).toInt();
k[0] = 'N'; //minutes k[0] = 'N'; //minutes
timerMinutes[i] = request->arg(k).toInt(); timerMinutes[i] = request->arg(k).toInt();
k[0] = 'T'; //macros k[0] = 'T'; //macros
timerMacro[i] = request->arg(k).toInt(); timerMacro[i] = request->arg(k).toInt();
k[0] = 'W'; //weekdays k[0] = 'W'; //weekdays
timerWeekday[i] = request->arg(k).toInt(); timerWeekday[i] = request->arg(k).toInt();
if (i<8) {
k[0] = 'X'; //DOW
if (!request->hasArg(k)) {
k[0] = 'M'; //month
timerMonth[i] = request->arg(k).toInt();
k[0] = 'D'; //day
timerDay[i] = request->arg(k).toInt();
} else {
timerMonth[i] = 0;
timerDay[i] = 0;
}
}
} }
} }
@ -417,11 +427,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
//USERMODS //USERMODS
if (subPage == 8) if (subPage == 8)
{ {
#ifdef WLED_USE_DYNAMIC_JSON if (!requestJSONBufferLock(5)) return;
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(11)) return;
#endif
JsonObject um = doc.createNestedObject("um"); JsonObject um = doc.createNestedObject("um");
@ -495,9 +501,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
} }
} }
usermods.readFromConfig(um); // force change of usermod parameters usermods.readFromConfig(um); // force change of usermod parameters
}
releaseJSONBufferLock(); releaseJSONBufferLock();
}
if (subPage != 2 && (subPage != 6 || !doReboot)) serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init) if (subPage != 2 && (subPage != 6 || !doReboot)) serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init)
if (subPage == 4) alexaInit(); if (subPage == 4) alexaInit();

View File

@ -4,7 +4,7 @@
* UDP sync notifier / Realtime / Hyperion / TPM2.NET * UDP sync notifier / Realtime / Hyperion / TPM2.NET
*/ */
#define WLEDPACKETSIZE 39 #define WLEDPACKETSIZE (40+(MAX_NUM_SEGMENTS*3))
#define UDP_IN_MAXSIZE 1472 #define UDP_IN_MAXSIZE 1472
#define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times #define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times
@ -17,6 +17,7 @@ void notify(byte callMode, bool followUp)
case CALL_MODE_INIT: return; case CALL_MODE_INIT: return;
case CALL_MODE_DIRECT_CHANGE: if (!notifyDirect) return; break; case CALL_MODE_DIRECT_CHANGE: if (!notifyDirect) return; break;
case CALL_MODE_BUTTON: if (!notifyButton) return; break; case CALL_MODE_BUTTON: if (!notifyButton) return; break;
case CALL_MODE_BUTTON_PRESET: if (!notifyButton) return; break;
case CALL_MODE_NIGHTLIGHT: if (!notifyDirect) return; break; case CALL_MODE_NIGHTLIGHT: if (!notifyDirect) return; break;
case CALL_MODE_HUE: if (!notifyHue) return; break; case CALL_MODE_HUE: if (!notifyHue) return; break;
case CALL_MODE_PRESET_CYCLE: if (!notifyDirect) return; break; case CALL_MODE_PRESET_CYCLE: if (!notifyDirect) return; break;
@ -41,8 +42,8 @@ void notify(byte callMode, bool followUp)
//0: old 1: supports white 2: supports secondary color //0: old 1: supports white 2: supports secondary color
//3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette //3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette
//6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet //6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet
//9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet //9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet 11: per segment options, variable packet length (40+MAX_NUM_SEGMENTS*3)
udpOut[11] = 10; udpOut[11] = 11;
udpOut[12] = colSec[0]; udpOut[12] = colSec[0];
udpOut[13] = colSec[1]; udpOut[13] = colSec[1];
udpOut[14] = colSec[2]; udpOut[14] = colSec[2];
@ -84,6 +85,14 @@ void notify(byte callMode, bool followUp)
udpOut[37] = strip.hasCCTBus() ? 0 : 255; //check this is 0 for the next value to be significant udpOut[37] = strip.hasCCTBus() ? 0 : 255; //check this is 0 for the next value to be significant
udpOut[38] = mainseg.cct; udpOut[38] = mainseg.cct;
udpOut[39] = strip.getMaxSegments();
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment &selseg = strip.getSegment(i);
udpOut[40+i*3] = selseg.options & 0x0F; //only take into account mirrored, selected, on, reversed
udpOut[41+i*3] = selseg.spacing;
udpOut[42+i*3] = selseg.grouping;
}
IPAddress broadcastIp; IPAddress broadcastIp;
broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP()); broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP());
@ -282,6 +291,18 @@ void handleNotifications()
if (version < 200 && (receiveNotificationEffects || !someSel)) if (version < 200 && (receiveNotificationEffects || !someSel))
{ {
if (currentPlaylist>=0) unloadPlaylist(); if (currentPlaylist>=0) unloadPlaylist();
if (version>10) {
if (receiveSegmentOptions) {
// will not sync start & stop
uint8_t srcSegs = udpIn[39];
if (srcSegs > strip.getMaxSegments()) srcSegs = strip.getMaxSegments();
for (uint8_t i = 0; i < srcSegs; i++) {
WS2812FX::Segment& selseg = strip.getSegment(i);
for (uint8_t j = 0; j<4; j++) selseg.setOption(j, (udpIn[40+i*3] >> j) & 0x01); //only take into account mirrored, selected, on, reversed
strip.setSegment(i, selseg.start, selseg.stop, udpIn[42+i*3], udpIn[41+i*3], selseg.offset); // will also properly reset segments
}
}
}
if (udpIn[8] < strip.getModeCount()) effectCurrent = udpIn[8]; if (udpIn[8] < strip.getModeCount()) effectCurrent = udpIn[8];
effectSpeed = udpIn[9]; effectSpeed = udpIn[9];
if (version > 2) effectIntensity = udpIn[16]; if (version > 2) effectIntensity = udpIn[16];
@ -441,16 +462,17 @@ void handleNotifications()
// API over UDP // API over UDP
udpIn[packetSize] = '\0'; udpIn[packetSize] = '\0';
if (!requestJSONBufferLock(18)) return;
if (udpIn[0] >= 'A' && udpIn[0] <= 'Z') { //HTTP API if (udpIn[0] >= 'A' && udpIn[0] <= 'Z') { //HTTP API
String apireq = "win&"; String apireq = "win&";
apireq += (char*)udpIn; apireq += (char*)udpIn;
handleSet(nullptr, apireq); handleSet(nullptr, apireq);
} else if (udpIn[0] == '{') { //JSON API } else if (udpIn[0] == '{') { //JSON API
DynamicJsonDocument jsonBuffer(2048); DeserializationError error = deserializeJson(doc, udpIn);
DeserializationError error = deserializeJson(jsonBuffer, udpIn); JsonObject root = doc.as<JsonObject>();
JsonObject root = jsonBuffer.as<JsonObject>();
if (!error && !root.isNull()) deserializeState(root); if (!error && !root.isNull()) deserializeState(root);
} }
releaseJSONBufferLock();
} }

View File

@ -104,6 +104,10 @@
#include "../usermods/seven_segment_display/usermod_v2_seven_segment_display.h" #include "../usermods/seven_segment_display/usermod_v2_seven_segment_display.h"
#endif #endif
#ifdef USERMOD_SSDR
#include "../usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h"
#endif
#ifdef QUINLED_AN_PENTA #ifdef QUINLED_AN_PENTA
#include "../usermods/quinled-an-penta/quinled-an-penta.h" #include "../usermods/quinled-an-penta/quinled-an-penta.h"
#endif #endif
@ -200,6 +204,10 @@ void registerUsermods()
usermods.add(new SevenSegmentDisplay()); usermods.add(new SevenSegmentDisplay());
#endif #endif
#ifdef USERMOD_SSDR
usermods.add(new UsermodSSDR());
#endif
#ifdef QUINLED_AN_PENTA #ifdef QUINLED_AN_PENTA
usermods.add(new QuinLEDAnPentaUsermod()); usermods.add(new QuinLEDAnPentaUsermod());
#endif #endif

View File

@ -114,7 +114,7 @@ void sappends(char stype, const char* key, char* val)
case 's': {//string (we can interpret val as char*) case 's': {//string (we can interpret val as char*)
String buf = val; String buf = val;
//convert "%" to "%%" to make EspAsyncWebServer happy //convert "%" to "%%" to make EspAsyncWebServer happy
buf.replace("%","%%"); //buf.replace("%","%%");
oappend("d.Sf."); oappend("d.Sf.");
oappend(key); oappend(key);
oappend(".value=\""); oappend(".value=\"");
@ -202,6 +202,7 @@ bool isAsterisksOnly(const char* str, byte maxLen)
} }
//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994
bool requestJSONBufferLock(uint8_t module) bool requestJSONBufferLock(uint8_t module)
{ {
unsigned long now = millis(); unsigned long now = millis();

View File

@ -65,12 +65,14 @@ void WLED::loop()
yield(); yield();
handleIO(); handleIO();
handleIR(); handleIR();
#ifndef WLED_DISABLE_ALEXA
handleAlexa(); handleAlexa();
#endif
yield(); yield();
if (doReboot) if (doReboot) reset();
reset();
if (doCloseFile) { if (doCloseFile) {
closeFile(); closeFile();
yield(); yield();
@ -78,21 +80,25 @@ void WLED::loop()
if (!realtimeMode || realtimeOverride) // block stuff if WARLS/Adalight is enabled if (!realtimeMode || realtimeOverride) // block stuff if WARLS/Adalight is enabled
{ {
if (apActive) if (apActive) dnsServer.processNextRequest();
dnsServer.processNextRequest();
#ifndef WLED_DISABLE_OTA #ifndef WLED_DISABLE_OTA
if (WLED_CONNECTED && aOtaEnabled) if (WLED_CONNECTED && aOtaEnabled) ArduinoOTA.handle();
ArduinoOTA.handle();
#endif #endif
handleNightlight(); handleNightlight();
handlePlaylist(); handlePlaylist();
yield(); yield();
#ifndef WLED_DISABLE_HUESYNC
handleHue(); handleHue();
#ifndef WLED_DISABLE_BLYNK yield();
handleBlynk();
#endif #endif
#ifndef WLED_DISABLE_BLYNK
handleBlynk();
yield();
#endif
handlePresets();
yield(); yield();
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
@ -120,6 +126,8 @@ void WLED::loop()
if (lastMqttReconnectAttempt > millis()) { if (lastMqttReconnectAttempt > millis()) {
rolloverMillis++; rolloverMillis++;
lastMqttReconnectAttempt = 0; lastMqttReconnectAttempt = 0;
ntpLastSyncTime = 0;
strip.restartRuntime();
} }
if (millis() - lastMqttReconnectAttempt > 30000) { if (millis() - lastMqttReconnectAttempt > 30000) {
lastMqttReconnectAttempt = millis(); lastMqttReconnectAttempt = millis();
@ -286,8 +294,10 @@ void WLED::setup()
WiFi.onEvent(WiFiEvent); WiFi.onEvent(WiFiEvent);
#endif #endif
#ifdef WLED_ENABLE_ADALIGHT // reserve GPIO3 (RX) pin for ADALight #ifdef WLED_ENABLE_ADALIGHT
if (!pinManager.isPinAllocated(3)) { //Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused
//Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused
if (!pinManager.isPinAllocated(3) && !pinManager.isPinAllocated(1)) {
Serial.println(F("Ada")); Serial.println(F("Ada"));
pinManager.allocatePin(3,false); pinManager.allocatePin(3,false);
} else { } else {
@ -624,7 +634,7 @@ void WLED::handleConnection()
// reconnect WiFi to clear stale allocations if heap gets too low // reconnect WiFi to clear stale allocations if heap gets too low
if (now - heapTime > 5000) { if (now - heapTime > 5000) {
uint32_t heap = ESP.getFreeHeap(); uint32_t heap = ESP.getFreeHeap();
if (heap < JSON_BUFFER_SIZE+512 && lastHeap < JSON_BUFFER_SIZE+512) { if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) {
DEBUG_PRINT(F("Heap too low! ")); DEBUG_PRINT(F("Heap too low! "));
DEBUG_PRINTLN(heap); DEBUG_PRINTLN(heap);
forceReconnect = true; forceReconnect = true;

View File

@ -3,12 +3,12 @@
/* /*
Main sketch, global variable declarations Main sketch, global variable declarations
@title WLED project sketch @title WLED project sketch
@version 0.13.1-bl4 @version 0.13.1-bl6
@author Christian Schwinne @author Christian Schwinne
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2112031 #define VERSION 2201011
//uncomment this if you have a "my_config.h" file you'd like to use //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG
@ -310,6 +310,7 @@ WLED_GLOBAL uint8_t receiveGroups _INIT(0x01); // sync receiv
WLED_GLOBAL bool receiveNotificationBrightness _INIT(true); // apply brightness from incoming notifications WLED_GLOBAL bool receiveNotificationBrightness _INIT(true); // apply brightness from incoming notifications
WLED_GLOBAL bool receiveNotificationColor _INIT(true); // apply color WLED_GLOBAL bool receiveNotificationColor _INIT(true); // apply color
WLED_GLOBAL bool receiveNotificationEffects _INIT(true); // apply effects setup WLED_GLOBAL bool receiveNotificationEffects _INIT(true); // apply effects setup
WLED_GLOBAL bool receiveSegmentOptions _INIT(false); // apply segment options
WLED_GLOBAL bool notifyDirect _INIT(false); // send notification if change via UI or HTTP API WLED_GLOBAL bool notifyDirect _INIT(false); // send notification if change via UI or HTTP API
WLED_GLOBAL bool notifyButton _INIT(false); // send if updated by button or infrared remote WLED_GLOBAL bool notifyButton _INIT(false); // send if updated by button or infrared remote
WLED_GLOBAL bool notifyAlexa _INIT(false); // send notification if updated via Alexa WLED_GLOBAL bool notifyAlexa _INIT(false); // send notification if updated via Alexa
@ -509,6 +510,8 @@ WLED_GLOBAL int8_t timerMinutes[] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }));
WLED_GLOBAL byte timerMacro[] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })); WLED_GLOBAL byte timerMacro[] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }));
WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 })); // weekdays to activate on WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 })); // weekdays to activate on
// bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity // bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity
WLED_GLOBAL byte timerMonth[] _INIT_N(({0,0,0,0,0,0,0,0}));
WLED_GLOBAL byte timerDay[] _INIT_N(({0,0,0,0,0,0,0,0}));
// blynk // blynk
WLED_GLOBAL bool blynkEnabled _INIT(false); WLED_GLOBAL bool blynkEnabled _INIT(false);
@ -527,7 +530,7 @@ WLED_GLOBAL byte presetCycMax _INIT(5);
// realtime // realtime
WLED_GLOBAL byte realtimeMode _INIT(REALTIME_MODE_INACTIVE); WLED_GLOBAL byte realtimeMode _INIT(REALTIME_MODE_INACTIVE);
WLED_GLOBAL byte realtimeOverride _INIT(REALTIME_OVERRIDE_NONE); WLED_GLOBAL byte realtimeOverride _INIT(REALTIME_OVERRIDE_NONE);
WLED_GLOBAL IPAddress realtimeIP _INIT_N(((0, 0, 0, 0)));; WLED_GLOBAL IPAddress realtimeIP _INIT_N(((0, 0, 0, 0)));
WLED_GLOBAL unsigned long realtimeTimeout _INIT(0); WLED_GLOBAL unsigned long realtimeTimeout _INIT(0);
WLED_GLOBAL uint8_t tpmPacketCount _INIT(0); WLED_GLOBAL uint8_t tpmPacketCount _INIT(0);
WLED_GLOBAL uint16_t tpmPayloadFrameSize _INIT(0); WLED_GLOBAL uint16_t tpmPayloadFrameSize _INIT(0);
@ -610,9 +613,7 @@ WLED_GLOBAL int8_t loadLedmap _INIT(-1);
WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager()); WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
// global ArduinoJson buffer // global ArduinoJson buffer
#ifndef WLED_USE_DYNAMIC_JSON
WLED_GLOBAL StaticJsonDocument<JSON_BUFFER_SIZE> doc; WLED_GLOBAL StaticJsonDocument<JSON_BUFFER_SIZE> doc;
#endif
WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0);
// enable additional debug output // enable additional debug output

View File

@ -382,11 +382,7 @@ void deEEP() {
DEBUG_PRINTLN(F("Preset file not found, attempting to load from EEPROM")); DEBUG_PRINTLN(F("Preset file not found, attempting to load from EEPROM"));
DEBUGFS_PRINTLN(F("Allocating saving buffer for dEEP")); DEBUGFS_PRINTLN(F("Allocating saving buffer for dEEP"));
#ifdef WLED_USE_DYNAMIC_JSON if (!requestJSONBufferLock(8)) return;
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(12)) return;
#endif
JsonObject sObj = doc.to<JsonObject>(); JsonObject sObj = doc.to<JsonObject>();
sObj.createNestedObject("0"); sObj.createNestedObject("0");

View File

@ -48,22 +48,14 @@ void handleSerial()
Serial.print("WLED"); Serial.write(' '); Serial.println(VERSION); Serial.print("WLED"); Serial.write(' '); Serial.println(VERSION);
} else if (next == '{') { //JSON API } else if (next == '{') { //JSON API
bool verboseResponse = false; bool verboseResponse = false;
DEBUG_PRINTLN(F("Serial JSON buffer requested.")); if (!requestJSONBufferLock(16)) return;
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(13)) return;
#endif
Serial.setTimeout(100); Serial.setTimeout(100);
DeserializationError error = deserializeJson(doc, Serial); DeserializationError error = deserializeJson(doc, Serial);
if (error) { if (error) {
releaseJSONBufferLock(); releaseJSONBufferLock();
return; return;
} }
//fileDoc = &doc; // used for applying presets (presets.cpp)
verboseResponse = deserializeState(doc.as<JsonObject>()); verboseResponse = deserializeState(doc.as<JsonObject>());
//fileDoc = nullptr;
//only send response if TX pin is unused for other purposes //only send response if TX pin is unused for other purposes
if (verboseResponse && !pinManager.isPinAllocated(1)) { if (verboseResponse && !pinManager.isPinAllocated(1)) {
doc.clear(); doc.clear();

View File

@ -65,11 +65,21 @@ void initServer()
#ifdef WLED_ENABLE_WEBSOCKETS #ifdef WLED_ENABLE_WEBSOCKETS
server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", PAGE_liveviewws); if (handleIfNoneMatchCacheHeader(request)) return;
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_liveviewws, PAGE_liveviewws_length);
response->addHeader(F("Content-Encoding"),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
//request->send_P(200, "text/html", PAGE_liveviewws);
}); });
#else #else
server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", PAGE_liveview); if (handleIfNoneMatchCacheHeader(request)) return;
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_liveview, PAGE_liveview_length);
response->addHeader(F("Content-Encoding"),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
//request->send_P(200, "text/html", PAGE_liveview);
}); });
#endif #endif
@ -109,13 +119,8 @@ void initServer()
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request) { AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request) {
bool verboseResponse = false; bool verboseResponse = false;
bool isConfig = false; bool isConfig = false;
{ //scope JsonDocument so it releases its buffer
DEBUG_PRINTLN(F("HTTP JSON buffer requested."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(14)) return; if (!requestJSONBufferLock(14)) return;
#endif
DeserializationError error = deserializeJson(doc, (uint8_t*)(request->_tempObject)); DeserializationError error = deserializeJson(doc, (uint8_t*)(request->_tempObject));
JsonObject root = doc.as<JsonObject>(); JsonObject root = doc.as<JsonObject>();
@ -132,14 +137,12 @@ void initServer()
serializeJson(root,Serial); serializeJson(root,Serial);
DEBUG_PRINTLN(); DEBUG_PRINTLN();
#endif #endif
//fileDoc = &doc; // used for applying presets (presets.cpp)
verboseResponse = deserializeState(root); verboseResponse = deserializeState(root);
//fileDoc = nullptr;
} else { } else {
verboseResponse = deserializeConfig(root); //use verboseResponse to determine whether cfg change should be saved immediately verboseResponse = deserializeConfig(root); //use verboseResponse to determine whether cfg change should be saved immediately
} }
releaseJSONBufferLock(); releaseJSONBufferLock();
}
if (verboseResponse) { if (verboseResponse) {
if (!isConfig) { if (!isConfig) {
serveJson(request); return; //if JSON contains "v" serveJson(request); return; //if JSON contains "v"
@ -164,7 +167,12 @@ void initServer()
}); });
server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", PAGE_usermod); if (handleIfNoneMatchCacheHeader(request)) return;
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_usermod, PAGE_usermod_length);
response->addHeader(F("Content-Encoding"),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
//request->send_P(200, "text/html", PAGE_usermod);
}); });
server.on("/url", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/url", HTTP_GET, [](AsyncWebServerRequest *request){
@ -190,7 +198,6 @@ void initServer()
}); });
server.on("/iro.js", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/iro.js", HTTP_GET, [](AsyncWebServerRequest *request){
if (handleIfNoneMatchCacheHeader(request)) return;
AsyncWebServerResponse *response = request->beginResponse_P(200, "application/javascript", iroJs, iroJs_length); AsyncWebServerResponse *response = request->beginResponse_P(200, "application/javascript", iroJs, iroJs_length);
response->addHeader(F("Content-Encoding"),"gzip"); response->addHeader(F("Content-Encoding"),"gzip");
setStaticContentCacheHeaders(response); setStaticContentCacheHeaders(response);
@ -198,7 +205,6 @@ void initServer()
}); });
server.on("/rangetouch.js", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/rangetouch.js", HTTP_GET, [](AsyncWebServerRequest *request){
if (handleIfNoneMatchCacheHeader(request)) return;
AsyncWebServerResponse *response = request->beginResponse_P(200, "application/javascript", rangetouchJs, rangetouchJs_length); AsyncWebServerResponse *response = request->beginResponse_P(200, "application/javascript", rangetouchJs, rangetouchJs_length);
response->addHeader(F("Content-Encoding"),"gzip"); response->addHeader(F("Content-Encoding"),"gzip");
setStaticContentCacheHeaders(response); setStaticContentCacheHeaders(response);
@ -221,7 +227,11 @@ void initServer()
//init ota page //init ota page
#ifndef WLED_DISABLE_OTA #ifndef WLED_DISABLE_OTA
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", PAGE_update); AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_update, PAGE_update_length);
response->addHeader(F("Content-Encoding"),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
//request->send_P(200, "text/html", PAGE_update);
}); });
server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){
@ -303,7 +313,11 @@ void initServer()
if(espalexa.handleAlexaApiCall(request)) return; if(espalexa.handleAlexaApiCall(request)) return;
#endif #endif
if(handleFileRead(request, request->url())) return; if(handleFileRead(request, request->url())) return;
request->send_P(404, "text/html", PAGE_404); AsyncWebServerResponse *response = request->beginResponse_P(404, "text/html", PAGE_404, PAGE_404_length);
response->addHeader(F("Content-Encoding"),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
//request->send_P(404, "text/html", PAGE_404);
}); });
} }
@ -408,21 +422,22 @@ void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& h
String settingsProcessor(const String& var) String settingsProcessor(const String& var)
{ {
/*
if (var == "CSS") { if (var == "CSS") {
char buf[SETTINGS_STACK_BUF_SIZE]; char buf[SETTINGS_STACK_BUF_SIZE];
buf[0] = 0; buf[0] = 0;
getSettingsJS(optionType, buf); getSettingsJS(optionType, buf);
obuf = buf;
oappend(SET_F("}</script>"));
return String(buf); return String(buf);
} }
*/
#ifdef WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX
if (var == "DMXMENU") { if (var == "DMXMENU") {
return String(F("<form action=/settings/dmx><button type=submit>DMX Output</button></form>")); return String(F("<form action=/settings/dmx><button type=submit>DMX Output</button></form>"));
} }
#endif #endif
if (var == "SCSS") return String(FPSTR(PAGE_settingsCss)); //if (var == "SCSS") return String(FPSTR(PAGE_settingsCss));
return String(); return String();
} }
@ -447,13 +462,32 @@ String dmxProcessor(const String& var)
} }
void serveSettingsJS(AsyncWebServerRequest* request)
{
char buf[SETTINGS_STACK_BUF_SIZE+37];
buf[0] = 0;
byte subPage = request->arg(F("p")).toInt();
if (!subPage || subPage>8) {
strcpy_P(buf, PSTR("alert('Settings for this request are not implemented.');"));
request->send(501, "application/javascript", buf);
return;
}
strcat_P(buf,PSTR("function GetV(){var d=document;"));
getSettingsJS(subPage, buf+strlen(buf)); // this may overflow by 35bytes!!!
strcat_P(buf,PSTR("}"));
request->send(200, "application/javascript", buf);
}
void serveSettings(AsyncWebServerRequest* request, bool post) void serveSettings(AsyncWebServerRequest* request, bool post)
{ {
byte subPage = 0; byte subPage = 0;
const String& url = request->url(); const String& url = request->url();
if (url.indexOf("sett") >= 0) if (url.indexOf("sett") >= 0)
{ {
if (url.indexOf("wifi") > 0) subPage = 1; if (url.indexOf(".js") > 0) subPage = 254;
else if (url.indexOf(".css") > 0) subPage = 253;
else if (url.indexOf("wifi") > 0) subPage = 1;
else if (url.indexOf("leds") > 0) subPage = 2; else if (url.indexOf("leds") > 0) subPage = 2;
else if (url.indexOf("ui") > 0) subPage = 3; else if (url.indexOf("ui") > 0) subPage = 3;
else if (url.indexOf("sync") > 0) subPage = 4; else if (url.indexOf("sync") > 0) subPage = 4;
@ -502,17 +536,24 @@ void serveSettings(AsyncWebServerRequest* request, bool post)
optionType = subPage; optionType = subPage;
AsyncWebServerResponse *response;
switch (subPage) switch (subPage)
{ {
case 1: request->send_P(200, "text/html", PAGE_settings_wifi, settingsProcessor); break; case 1: response = request->beginResponse_P(200, "text/html", PAGE_settings_wifi, PAGE_settings_wifi_length); break;
case 2: request->send_P(200, "text/html", PAGE_settings_leds, settingsProcessor); break; case 2: response = request->beginResponse_P(200, "text/html", PAGE_settings_leds, PAGE_settings_leds_length); break;
case 3: request->send_P(200, "text/html", PAGE_settings_ui , settingsProcessor); break; case 3: response = request->beginResponse_P(200, "text/html", PAGE_settings_ui, PAGE_settings_ui_length); break;
case 4: request->send_P(200, "text/html", PAGE_settings_sync, settingsProcessor); break; case 4: response = request->beginResponse_P(200, "text/html", PAGE_settings_sync, PAGE_settings_sync_length); break;
case 5: request->send_P(200, "text/html", PAGE_settings_time, settingsProcessor); break; case 5: response = request->beginResponse_P(200, "text/html", PAGE_settings_time, PAGE_settings_time_length); break;
case 6: request->send_P(200, "text/html", PAGE_settings_sec , settingsProcessor); break; case 6: response = request->beginResponse_P(200, "text/html", PAGE_settings_sec, PAGE_settings_sec_length); break;
case 7: request->send_P(200, "text/html", PAGE_settings_dmx , settingsProcessor); break; case 7: response = request->beginResponse_P(200, "text/html", PAGE_settings_dmx, PAGE_settings_dmx_length); break;
case 8: request->send_P(200, "text/html", PAGE_settings_um , settingsProcessor); break; case 8: response = request->beginResponse_P(200, "text/html", PAGE_settings_um, PAGE_settings_um_length); break;
case 255: request->send_P(200, "text/html", PAGE_welcome); break; case 253: response = request->beginResponse_P(200, "text/css", PAGE_settingsCss, PAGE_settingsCss_length); break;
default: request->send_P(200, "text/html", PAGE_settings , settingsProcessor); case 254: serveSettingsJS(request); return;
case 255: response = request->beginResponse_P(200, "text/html", PAGE_welcome, PAGE_welcome_length); break;
default: request->send_P(200, "text/html", PAGE_settings, settingsProcessor); return;
//default: response = request->beginResponse_P(200, "text/html", PAGE_settings, PAGE_settings_length); break;
} }
response->addHeader(F("Content-Encoding"),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
} }

View File

@ -37,11 +37,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
bool verboseResponse = false; bool verboseResponse = false;
{ //scope JsonDocument so it releases its buffer { //scope JsonDocument so it releases its buffer
DEBUG_PRINTLN(F("WS JSON receive buffer requested.")); DEBUG_PRINTLN(F("WS JSON receive buffer requested."));
#ifdef WLED_USE_DYNAMIC_JSON if (!requestJSONBufferLock(11)) return;
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(15)) return;
#endif
DeserializationError error = deserializeJson(doc, data, len); DeserializationError error = deserializeJson(doc, data, len);
JsonObject root = doc.as<JsonObject>(); JsonObject root = doc.as<JsonObject>();
@ -49,13 +45,6 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
releaseJSONBufferLock(); releaseJSONBufferLock();
return; return;
} }
/*
#ifdef WLED_DEBUG
DEBUG_PRINT(F("Incoming WS: "));
serializeJson(root,Serial);
DEBUG_PRINTLN();
#endif
*/
if (root["v"] && root.size() == 1) { if (root["v"] && root.size() == 1) {
//if the received value is just "{"v":true}", send only to this client //if the received value is just "{"v":true}", send only to this client
verboseResponse = true; verboseResponse = true;
@ -63,15 +52,13 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
{ {
wsLiveClientId = root["lv"] ? client->id() : 0; wsLiveClientId = root["lv"] ? client->id() : 0;
} else { } else {
//fileDoc = &doc; // used for applying presets (presets.cpp)
verboseResponse = deserializeState(root); verboseResponse = deserializeState(root);
//fileDoc = nullptr;
if (!interfaceUpdateCallMode) { if (!interfaceUpdateCallMode) {
//special case, only on playlist load, avoid sending twice in rapid succession //special case, only on playlist load, avoid sending twice in rapid succession
if (millis() - lastInterfaceUpdate > 1700) verboseResponse = false; if (millis() - lastInterfaceUpdate > 1700) verboseResponse = false;
} }
} }
releaseJSONBufferLock(); releaseJSONBufferLock(); // will clean fileDoc
} }
//update if it takes longer than 300ms until next "broadcast" //update if it takes longer than 300ms until next "broadcast"
if (verboseResponse && (millis() - lastInterfaceUpdate < 1700 || !interfaceUpdateCallMode)) sendDataWs(client); if (verboseResponse && (millis() - lastInterfaceUpdate < 1700 || !interfaceUpdateCallMode)) sendDataWs(client);
@ -114,11 +101,7 @@ void sendDataWs(AsyncWebSocketClient * client)
{ //scope JsonDocument so it releases its buffer { //scope JsonDocument so it releases its buffer
DEBUG_PRINTLN(F("WS JSON send buffer requested.")); DEBUG_PRINTLN(F("WS JSON send buffer requested."));
#ifdef WLED_USE_DYNAMIC_JSON if (!requestJSONBufferLock(12)) return;
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(16)) return;
#endif
JsonObject state = doc.createNestedObject("state"); JsonObject state = doc.createNestedObject("state");
serializeState(state); serializeState(state);
@ -126,18 +109,14 @@ void sendDataWs(AsyncWebSocketClient * client)
serializeInfo(info); serializeInfo(info);
DEBUG_PRINTF("JSON buffer size: %u for WS request.\n", doc.memoryUsage()); DEBUG_PRINTF("JSON buffer size: %u for WS request.\n", doc.memoryUsage());
size_t len = measureJson(doc); size_t len = measureJson(doc);
buffer = ws.makeBuffer(len); size_t heap1 = ESP.getFreeHeap();
if (!buffer) { buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes
size_t heap2 = ESP.getFreeHeap();
if (!buffer || heap1-heap2<len) {
releaseJSONBufferLock(); releaseJSONBufferLock();
ws.cleanupClients(0); // disconnect all clients to release memory
return; //out of memory return; //out of memory
} }
/*
#ifdef WLED_DEBUG
DEBUG_PRINT(F("Outgoing WS: "));
serializeJson(doc,Serial);
DEBUG_PRINTLN();
#endif
*/
serializeJson(doc, (char *)buffer->get(), len +1); serializeJson(doc, (char *)buffer->get(), len +1);
releaseJSONBufferLock(); releaseJSONBufferLock();
} }
@ -155,10 +134,13 @@ void handleWs()
{ {
if (millis() - wsLastLiveTime > WS_LIVE_INTERVAL) if (millis() - wsLastLiveTime > WS_LIVE_INTERVAL)
{ {
#ifdef ESP8266
ws.cleanupClients(2);
#else
ws.cleanupClients(); ws.cleanupClients();
#endif
bool success = true; bool success = true;
if (wsLiveClientId) if (wsLiveClientId) success = serveLiveLeds(nullptr, wsLiveClientId);
success = serveLiveLeds(nullptr, wsLiveClientId);
wsLastLiveTime = millis(); wsLastLiveTime = millis();
if (!success) wsLastLiveTime -= 20; //try again in 20ms if failed due to non-empty WS queue if (!success) wsLastLiveTime -= 20; //try again in 20ms if failed due to non-empty WS queue
} }

View File

@ -249,21 +249,18 @@ void getSettingsJS(byte subPage, char* dest)
{ {
char nS[8]; char nS[8];
// Pin reservations will become unnecessary when settings pages will read cfg.json directly // Pin reservations will become unnecessary when settings pages will read cfg.json directly
// add reserved and usermod pins as d.um_p array // add reserved and usermod pins as d.um_p array
oappend(SET_F("d.um_p=[6,7,8,9,10,11")); oappend(SET_F("d.um_p=[6,7,8,9,10,11"));
#ifdef WLED_USE_DYNAMIC_JSON if (requestJSONBufferLock(6)) {
DynamicJsonDocument doc(2048); // 2k is enough for usermods // if we can't allocate JSON buffer ignore usermod pins
#else
if (!requestJSONBufferLock(17)) return;
#endif
JsonObject mods = doc.createNestedObject(F("um")); JsonObject mods = doc.createNestedObject(F("um"));
usermods.addToConfig(mods); usermods.addToConfig(mods);
if (!mods.isNull()) fillUMPins(mods); if (!mods.isNull()) fillUMPins(mods);
releaseJSONBufferLock(); releaseJSONBufferLock();
}
#ifdef WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX
oappend(SET_F(",2")); // DMX hardcoded pin oappend(SET_F(",2")); // DMX hardcoded pin
@ -335,6 +332,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("CCT"),correctWB); sappend('c',SET_F("CCT"),correctWB);
sappend('c',SET_F("CR"),cctFromRgb); sappend('c',SET_F("CR"),cctFromRgb);
sappend('v',SET_F("CB"),strip.cctBlending); sappend('v',SET_F("CB"),strip.cctBlending);
sappend('v',SET_F("FR"),strip.getTargetFps());
sappend('v',SET_F("AW"),Bus::getAutoWhiteMode()); sappend('v',SET_F("AW"),Bus::getAutoWhiteMode());
for (uint8_t s=0; s < busses.getNumBusses(); s++) { for (uint8_t s=0; s < busses.getNumBusses(); s++) {
@ -419,6 +417,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("RB"),receiveNotificationBrightness); sappend('c',SET_F("RB"),receiveNotificationBrightness);
sappend('c',SET_F("RC"),receiveNotificationColor); sappend('c',SET_F("RC"),receiveNotificationColor);
sappend('c',SET_F("RX"),receiveNotificationEffects); sappend('c',SET_F("RX"),receiveNotificationEffects);
sappend('c',SET_F("SO"),receiveSegmentOptions);
sappend('c',SET_F("SD"),notifyDirectDefault); sappend('c',SET_F("SD"),notifyDirectDefault);
sappend('c',SET_F("SB"),notifyButton); sappend('c',SET_F("SB"),notifyButton);
sappend('c',SET_F("SH"),notifyHue); sappend('c',SET_F("SH"),notifyHue);
@ -550,6 +549,12 @@ void getSettingsJS(byte subPage, char* dest)
k[0] = 'N'; sappend('v',k,timerMinutes[i]); k[0] = 'N'; sappend('v',k,timerMinutes[i]);
k[0] = 'T'; sappend('v',k,timerMacro[i]); k[0] = 'T'; sappend('v',k,timerMacro[i]);
k[0] = 'W'; sappend('v',k,timerWeekday[i]); k[0] = 'W'; sappend('v',k,timerWeekday[i]);
if (i<8) {
k[0] = 'X'; sappend('c',k,timerDay[i]==0);
k[0] = 'M'; sappend('v',k,timerMonth[i]);
k[0] = 'D'; sappend('v',k,timerDay[i]?timerDay[i]:1);
}
} }
} }
@ -600,6 +605,4 @@ void getSettingsJS(byte subPage, char* dest)
oappendi(usermods.getModCount()); oappendi(usermods.getModCount());
oappend(";"); oappend(";");
} }
oappend(SET_F("}</script>"));
} }