diff --git a/.gitignore b/.gitignore index d59e422e..1940e377 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ node_modules .idea .direnv -wled-update.sh \ No newline at end of file +wled-update.sh +esp01-update.sh \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f81e5cb0..e2ef83e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ ### 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 - Added CCT and white balance correction support (PR #2285) diff --git a/package-lock.json b/package-lock.json index 0d381c7c..892cb480 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.13.1-bl5", + "version": "0.13.1-bl6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index eda9a00d..e5967ccf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.13.1-bl5", + "version": "0.13.1-bl6", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index d45f52be..0ac1432b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,7 +12,7 @@ ; default_envs = travis_esp8266, travis_esp32 # Release binaries -default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # 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 @@ -20,6 +20,7 @@ default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth # Single binaries (uncomment your board) ; default_envs = elekstube_ips ; default_envs = nodemcuv2 +; default_envs = esp8266_2m ; default_envs = esp01_1m_full ; default_envs = esp07 ; default_envs = d1_mini @@ -53,14 +54,14 @@ extra_configs = arduino_core_2_6_3 = espressif8266@2.3.3 arduino_core_2_7_4 = espressif8266@2.6.2 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 arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage # 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 platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 platformio/toolchain-xtensa @ ~2.40802.200502 @@ -105,6 +106,7 @@ build_flags = -DBEARSSL_SSL_BASIC -D CORE_DEBUG_LEVEL=0 -D NDEBUG + -Dregister= #build_flags for the IRremoteESP8266 library (enabled decoders have to appear here) -D _IR_ENABLE_DEFAULT_=false -D DECODE_HASH=true @@ -242,6 +244,13 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 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] board = esp01_1m platform = ${common.platform_wled_default} diff --git a/tools/cdata.js b/tools/cdata.js index 7266e861..7c50d0a2 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -113,7 +113,7 @@ function writeHtmlGzipped(sourceFile, resultFile, page) { console.info("Reading " + sourceFile); new inliner(sourceFile, function (error, html) { console.info("Inlined " + html.length + " characters"); - html = filter(html.replace("",""), "html-minify-ui"); + html = filter(html, "html-minify-ui"); console.info("Minified to " + html.length + " characters"); 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; } else if (s.method == "gzip") { 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 result = hexdump(zip.toString('hex'), true); const chunk = ` @@ -251,10 +252,11 @@ writeChunks( { file: "style.css", name: "PAGE_settingsCss", - prepend: "=====()=====", - method: "plaintext", + method: "gzip", filter: "css-minify", + mangle: (str) => + str + .replace("%%","%") }, { file: "settings.htm", @@ -263,140 +265,102 @@ writeChunks( append: ")=====", method: "plaintext", filter: "html-minify", - mangle: (str) => - str - .replace("%", "%%") - .replace(/User Interface\<\/button\>\<\/form\>/gms, "User Interface\<\/button\>\<\/form\>%DMXMENU%"), }, { file: "settings_wifi.htm", name: "PAGE_settings_wifi", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", mangle: (str) => str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") .replace( /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), + "" + ) }, { file: "settings_leds.htm", name: "PAGE_settings_leds", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", mangle: (str) => str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") .replace( /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), + "" + ) }, { file: "settings_dmx.htm", name: "PAGE_settings_dmx", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => { - const nocss = str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") + mangle: (str) => + str .replace( /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ); - return ` -#ifdef WLED_ENABLE_DMX -${nocss} -#else -const char PAGE_settings_dmx[] PROGMEM = R"=====()====="; -#endif -`; - }, + "" + ) }, { file: "settings_ui.htm", name: "PAGE_settings_ui", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", mangle: (str) => str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") .replace( /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), + "" + ) }, { file: "settings_sync.htm", name: "PAGE_settings_sync", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", mangle: (str) => str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"), + .replace( + /function GetV().*\<\/script\>/gms, + "" + ) }, { file: "settings_time.htm", name: "PAGE_settings_time", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", mangle: (str) => str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"), + .replace( + /function GetV().*\<\/script\>/gms, + "" + ) }, { file: "settings_sec.htm", name: "PAGE_settings_sec", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", mangle: (str) => str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") .replace( /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), + "" + ) }, { file: "settings_um.htm", name: "PAGE_settings_um", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", mangle: (str) => str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") .replace( /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), + "" + ) } ], "wled00/html_settings.h" @@ -408,9 +372,7 @@ writeChunks( { file: "usermod.htm", name: "PAGE_usermod", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", mangle: (str) => str.replace(/fetch\("http\:\/\/.*\/win/gms, 'fetch("/win'), @@ -442,41 +404,31 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; { file: "update.htm", name: "PAGE_update", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", }, { file: "welcome.htm", name: "PAGE_welcome", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", }, { file: "liveview.htm", name: "PAGE_liveview", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", }, { file: "liveviewws.htm", name: "PAGE_liveviewws", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", }, { file: "404.htm", name: "PAGE_404", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", }, { diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 46958886..f792256c 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -118,7 +118,8 @@ private: */ 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; if (switchOn) { if (m_onPreset) { @@ -188,10 +189,12 @@ private: if (sensorPinState == HIGH) { offTimerStart = 0; if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); + else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); publishMqtt("on"); - } else /*if (bri != 0)*/ { + } else { // start switch off timer offTimerStart = millis(); + if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); } return true; } @@ -203,14 +206,13 @@ private: */ bool handleOffTimer() { - if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) - { - if (enabled == true) - { + if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) { + offTimerStart = 0; + if (enabled == true) { if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false); + else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); publishMqtt("off"); } - offTimerStart = 0; return true; } return false; diff --git a/usermods/PWM_fan/usermod_PWM_fan.h b/usermods/PWM_fan/usermod_PWM_fan.h index 82aa917b..5cd79ef2 100644 --- a/usermods/PWM_fan/usermod_PWM_fan.h +++ b/usermods/PWM_fan/usermod_PWM_fan.h @@ -148,7 +148,7 @@ class PWMFanUsermod : public Usermod { int pwmStep = ((100 - minPWMValuePct) * newPWMvalue) / (7*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.")); } else if (difftemp <= 0.0) { // Temperature is below target temperature. Run fan at minimum speed. diff --git a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h index 3b785fad..6a7e9f5a 100644 --- a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h +++ b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h @@ -123,6 +123,11 @@ public: } } + uint16_t getLastLDRValue() + { + return lastLDRValue; + } + void addToJsonInfo(JsonObject &root) { JsonObject user = root[F("u")]; diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 371ef5f7..580e3ed3 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -37,13 +37,12 @@ class UsermodTemperature : public Usermod { // used to determine when we can read the sensors temperature // we have to wait at least 93.75 ms after requestTemperatures() is called unsigned long lastTemperaturesRequest; - float temperature = -100.f; // default to -100, DS18B20 only goes down to -50C - bool errorReading = false; + float temperature = -127.0f; // default to -127, DS18B20 only goes down to -50C // indicates requestTemperatures has been called but the sensor measurement is not complete bool waitingForConversion = false; // flag set at startup if DS18B20 sensor not found, avoids trying to keep getting // temperature if flashed to a board without a sensor attached - bool sensorFound = false; + byte sensorFound; bool enabled = true; @@ -55,27 +54,47 @@ class UsermodTemperature : public Usermod { //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 float readDallas() { - byte i; - byte data[2]; + byte data[9]; int16_t result; // raw data from sensor - if (!oneWire->reset()) return -127.0f; // send reset command and fail fast - oneWire->skip(); // skip ROM - oneWire->write(0xBE); // read (temperature) from EEPROM - for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature - for (i=2; i < 8; i++) oneWire->read(); // read unused bytes - 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 - oneWire->reset(); - oneWire->skip(); // skip ROM - oneWire->write(0x44,parasite); // request new temperature reading (without parasite power) - return (float)result + ((data[0]&0x0008) ? 0.5f : 0.0f); + float retVal = -127.0f; + if (oneWire->reset()) { // if reset() fails there are no OneWire devices + oneWire->skip(); // skip ROM + oneWire->write(0xBE); // read (temperature) from EEPROM + oneWire->read_bytes(data, 9); // first 2 bytes contain temperature + #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 + if (data[1] & 0x80) result |= 0xF000; // fix negative value + retVal = float(result) + ((data[0] & 0x08) ? 0.5f : 0.0f); + break; + } + } + for (byte i=1; i<9; i++) data[0] &= data[i]; + return data[0]==0xFF ? -127.0f : retVal; } 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(); waitingForConversion = true; - DEBUG_PRINTLN(F("Requested temperature.")); } void readTemperature() { @@ -110,6 +129,8 @@ class UsermodTemperature : public Usermod { case 0x3B: // DS1825 case 0x42: // DS28EA00 DEBUG_PRINTLN(F("Sensor found.")); + sensorFound = deviceAddress[0]; + DEBUG_PRINTF("0x%02X\n", sensorFound); return true; } } @@ -121,16 +142,15 @@ class UsermodTemperature : public Usermod { void setup() { int retries = 10; + sensorFound = 0; if (enabled) { // config says we are enabled DEBUG_PRINTLN(F("Allocating temperature pin...")); // pin retrieved from cfg.json (readFromConfig()) prior to running setup() if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { oneWire = new OneWire(temperaturePin); - if (!oneWire->reset()) { - sensorFound = false; // resetting 1-Wire bus yielded an error - } else { - while ((sensorFound=findSensor()) && retries--) { + if (oneWire->reset()) { + while (!findSensor() && retries--) { delay(25); // try to find sensor } } @@ -139,7 +159,6 @@ class UsermodTemperature : public Usermod { DEBUG_PRINTLN(F("Temperature pin allocation failed.")); } temperaturePin = -1; // allocation failed - sensorFound = false; } } 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? - 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(); if (WLED_MQTT_CONNECTED) { char subuf[64]; strcpy(subuf, mqttDeviceTopic); - if (-100 <= temperature) { + if (temperature > -100.0f) { // dont publish super low temperature as the graph will get messed up // the DallasTemperature library returns -127C or -196.6F when problem // reading the sensor 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")); - mqtt->publish(subuf, 0, false, String((float)temperature * 1.8f + 32).c_str()); + mqtt->publish(subuf, 0, false, String(getTemperatureF()).c_str()); } else { // publish something else to indicate status? } @@ -209,7 +228,7 @@ class UsermodTemperature : public Usermod { JsonArray temp = user.createNestedArray(FPSTR(_name)); - if (temperature <= -100.0 || errorReading) { + if (temperature <= -100.0f) { temp.add(0); temp.add(F(" Sensor Error!")); return; diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index 9a92d4e2..70719f9e 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -45,6 +45,9 @@ class MultiRelay : public Usermod { // status of initialisation bool initDone = false; + uint16_t periodicBroadcastSec = 60; + unsigned long lastBroadcast = 0; + // strings to reduce flash memory usage (used more than twice) static const char _name[]; static const char _enabled[]; @@ -53,7 +56,7 @@ class MultiRelay : public Usermod { static const char _activeHigh[]; static const char _external[]; static const char _button[]; - + static const char _broadcast[]; void publishMqtt(const char* state, int relay) { //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 */ void handleOffTimer() { + unsigned long now = millis(); bool activeRelays = false; for (uint8_t i=0; i 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); _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; } 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)) { _relay[i].pin = -1; // allocation failed } else { - if (!_relay[i].external) _relay[i].state = offMode; + if (!_relay[i].external) _relay[i].state = !offMode; switchRelay(i, _relay[i].state); _relay[i].active = false; } @@ -477,6 +484,7 @@ class MultiRelay : public Usermod { JsonObject top = root.createNestedObject(FPSTR(_name)); top[FPSTR(_enabled)] = enabled; + top[FPSTR(_broadcast)] = periodicBroadcastSec; for (uint8_t i=0; i=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) { if (!_relay[i].external) { - switchRelay(i, offMode); + _relay[i].state = !offMode; + switchRelay(i, _relay[i].state); + _oldMode = offMode; } } else { _relay[i].pin = -1; @@ -549,7 +561,7 @@ class MultiRelay : public Usermod { DEBUG_PRINTLN(F(" config (re)loaded.")); } // 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::_external[] PROGMEM = "external"; const char MultiRelay::_button[] PROGMEM = "button"; +const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec"; diff --git a/usermods/quinled-an-penta/quinled-an-penta.h b/usermods/quinled-an-penta/quinled-an-penta.h index 10777e36..5153ee58 100644 --- a/usermods/quinled-an-penta/quinled-an-penta.h +++ b/usermods/quinled-an-penta/quinled-an-penta.h @@ -61,10 +61,10 @@ class QuinLEDAnPentaUsermod : public Usermod float shtLastKnownHumidity = 0; // 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 oledSpiData = 16; - int8_t oledSpiCs = 0; + int8_t oledSpiCs = 27; int8_t oledSpiDc = 32; int8_t oledSpiRst = 33; int8_t shtSda = 1; @@ -75,7 +75,7 @@ class QuinLEDAnPentaUsermod : public Usermod { for(int8_t i = 0; i <= 4; i++) { - if(anPentaPins[i] == pin) + if(anPentaLEDPins[i] == pin) return true; } return false; @@ -313,7 +313,7 @@ class QuinLEDAnPentaUsermod : public Usermod byte drawnLines = 0; for (int8_t app = 0; app <= 4; app++) { for (int8_t clp = 0; clp <= 4; clp++) { - if (anPentaPins[app] == currentLedPins[clp]) { + if (anPentaLEDPins[app] == currentLedPins[clp]) { char charCurrentLedcReads[17]; sprintf(charCurrentLedcReads, "LED %d:", app+1); if (oledUseProgressBars) { diff --git a/usermods/quinled-an-penta/readme.md b/usermods/quinled-an-penta/readme.md index c292736c..4a4c0290 100644 --- a/usermods/quinled-an-penta/readme.md +++ b/usermods/quinled-an-penta/readme.md @@ -1,12 +1,12 @@ # 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 * "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 ## 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): ``` @@ -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. 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? -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. + ## 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: * 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 * Default: Disabled * 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 * Default: Disabled * 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 * Default: Disabled ## Change log +2021-12 +* Adjusted IO layout to match An-Penta v1r1 2021-10 -* First implementation. \ No newline at end of file +* First implementation. + +## Credits +ezcGman | Andy: Find me on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG \ No newline at end of file diff --git a/usermods/seven_segment_display_reloaded/readme.md b/usermods/seven_segment_display_reloaded/readme.md new file mode 100644 index 00000000..09479754 --- /dev/null +++ b/usermods/seven_segment_display_reloaded/readme.md @@ -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 \ No newline at end of file diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h new file mode 100644 index 00000000..c4a64886 --- /dev/null +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -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(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((*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"; diff --git a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h index e0d283a6..c5251cba 100644 --- a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h +++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h @@ -78,6 +78,14 @@ #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 // if SLEEP_MODE_ENABLED. #define SCREEN_TIMEOUT_MS 60*1000 // 1 min @@ -123,11 +131,11 @@ class FourLineDisplayUsermod : public Usermod { #ifndef FLD_SPI_DEFAULT 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) - DisplayType type = SSD1306; // display type #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 - DisplayType type = SSD1306_SPI; // display type + uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz) #endif + DisplayType type = FLD_TYPE; // display type bool flip = false; // flip display 180° uint8_t contrast = 10; // screen contrast uint8_t lineHeight = 1; // 1 row or 2 rows @@ -244,7 +252,7 @@ class FourLineDisplayUsermod : public Usermod { initDone = true; 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(); setFlipMode(flip); setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 @@ -691,7 +699,10 @@ class FourLineDisplayUsermod : public Usermod { screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; sleepMode = top[FPSTR(_sleepMode)] | sleepMode; clockMode = top[FPSTR(_clockMode)] | clockMode; - ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency + 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 DEBUG_PRINT(FPSTR(_name)); if (!initDone) { diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 3dcb5af6..14045a49 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -25,6 +25,10 @@ //The SCL and SDA pins are defined here. #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 #define FLD_PIN_SCL 22 #endif @@ -47,6 +51,10 @@ #define FLD_PIN_RESET 26 #endif #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 #define FLD_PIN_SCL 5 #endif @@ -70,15 +78,20 @@ #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 // if SLEEP_MODE_ENABLED. #define SCREEN_TIMEOUT_MS 60*1000 // 1 min -#define TIME_INDENT 0 -#define DATE_INDENT 2 - // 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 #define LINE_BUFFER_SIZE 16+1 @@ -229,18 +242,17 @@ class FourLineDisplayUsermod : public Usermod { private: bool initDone = false; - unsigned long lastTime = 0; // HW interface & configuration U8X8 *u8x8 = nullptr; // pointer to U8X8 display object #ifndef FLD_SPI_DEFAULT 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) - DisplayType type = SSD1306_64; // display type #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 - DisplayType type = SSD1306_SPI; // display type + uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz) #endif + DisplayType type = FLD_TYPE; // display type bool flip = false; // flip display 180° uint8_t contrast = 10; // screen contrast 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 bool sleepMode = true; // allow screen sleep? 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. bool needRedraw = true; @@ -262,6 +276,7 @@ class FourLineDisplayUsermod : public Usermod { uint8_t knownMode = 0; uint8_t knownPalette = 0; uint8_t knownMinute = 99; + uint8_t knownHour = 99; byte brightness100; byte fxspeed100; byte fxintensity100; @@ -270,21 +285,24 @@ class FourLineDisplayUsermod : public Usermod { bool powerON = true; bool displayTurnedOff = false; - unsigned long lastUpdate = 0; + unsigned long nextUpdate = 0; unsigned long lastRedraw = 0; unsigned long overlayUntil = 0; + // Set to 2 or 3 to mark lines 2 or 3. Other values ignored. byte markLineNum = 0; byte markColNum = 0; // strings to reduce flash memory usage (used more than twice) static const char _name[]; + static const char _enabled[]; static const char _contrast[]; static const char _refreshRate[]; static const char _screenTimeOut[]; static const char _flip[]; static const char _sleepMode[]; static const char _clockMode[]; + static const char _showSeconds[]; static const char _busClkFrequency[]; // 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 // network here 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) { - 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; } + 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 }}; + if (!pinManager.allocateMultiplePins(pins, 5, po)) { type=NONE; return; } } else { - PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true} }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } + isHW = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); + PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; + 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.")); switch (type) { case SSD1306: - #ifdef ESP8266 - if (!(ioPin[0]==5 && ioPin[1]==4)) - 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 + if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA lineHeight = 1; break; case SH1106: - #ifdef ESP8266 - if (!(ioPin[0]==5 && ioPin[1]==4)) - 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 + if (!isHW) u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA lineHeight = 2; break; case SSD1306_64: - #ifdef ESP8266 - if (!(ioPin[0]==5 && ioPin[1]==4)) - 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 + if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA lineHeight = 2; break; case SSD1305: - #ifdef ESP8266 - if (!(ioPin[0]==5 && ioPin[1]==4)) - 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 + if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA lineHeight = 1; break; case SSD1305_64: - #ifdef ESP8266 - if (!(ioPin[0]==5 && ioPin[1]==4)) - 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 + if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA lineHeight = 2; break; case SSD1306_SPI: - if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated - 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 + if (!isHW) 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 lineHeight = 1; break; case SSD1306_SPI64: - if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated - 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 + if (!isHW) 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 lineHeight = 2; break; default: u8x8 = nullptr; } + if (nullptr == u8x8) { 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; return; } initDone = true; 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(); setFlipMode(flip); setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 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 // 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. */ void loop() { - if (displayTurnedOff && millis() - lastUpdate < 1000) { - return; - }else if (millis() - lastUpdate < refreshRate){ - return;} + if (!enabled || strip.isUpdating()) return; + unsigned long now = millis(); + if (now < nextUpdate) return; + nextUpdate = now + ((clockMode && showSeconds) ? 1000 : refreshRate); redraw(false); - lastUpdate = millis(); } /** * Wrappers for screen drawing */ void setFlipMode(uint8_t mode) { - if (type==NONE) return; + if (type == NONE || !enabled) return; u8x8->setFlipMode(mode); } void setContrast(uint8_t contrast) { - if (type==NONE) return; + if (type == NONE || !enabled) return; u8x8->setContrast(contrast); } 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); if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); else u8x8->drawString(col, row, 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->draw2x2String(col, row, string); } 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); if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); else u8x8->drawGlyph(col, row, glyph); } uint8_t getCols() { - if (type==NONE) return 0; + if (type==NONE || !enabled) return 0; return u8x8->getCols(); } void clear() { - if (type==NONE) return; + if (type == NONE || !enabled) return; u8x8->clear(); } void setPowerSave(uint8_t save) { - if (type==NONE) return; + if (type == NONE || !enabled) return; u8x8->setPowerSave(save); } @@ -451,79 +457,83 @@ class FourLineDisplayUsermod : public Usermod { } //function to update lastredraw - void updateRedrawTime(){ - lastRedraw = millis(); - } + void updateRedrawTime() { + lastRedraw = millis(); + } /** * Redraw the screen (but only if things have changed * or if forceRedraw). */ void redraw(bool forceRedraw) { - if (type==NONE) return; - if (overlayUntil > 0) { - if (millis() >= overlayUntil) { - // Time to display the overlay has elapsed. - overlayUntil = 0; - forceRedraw = true; - } else { - // We are still displaying the overlay - // Don't redraw. - return; - } + unsigned long now = millis(); + + if (type == NONE || !enabled) return; + if (overlayUntil > 0) { + if (now >= overlayUntil) { + // Time to display the overlay has elapsed. + overlayUntil = 0; + forceRedraw = true; + } else { + // We are still displaying the overlay + // Don't redraw. + return; } + } - // Check if values which are shown on display changed from the last time. if (forceRedraw) { + knownHour = 99; needRedraw = true; + clear(); } else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon powerON = !powerON; drawStatusIcons(); lastRedraw = millis(); + return; } else if (knownnightlight != nightlightActive) { //trigger moon icon knownnightlight = nightlightActive; drawStatusIcons(); - if (knownnightlight) overlay(" Timer On", 1000, 6); + if (knownnightlight) overlay(PSTR(" Timer On"), 3000, 6); lastRedraw = millis(); - }else if (wificonnected != interfacesInited){ //trigger wifi icon + return; + } else if (wificonnected != interfacesInited) { //trigger wifi icon wificonnected = interfacesInited; drawStatusIcons(); lastRedraw = millis(); + return; } else if (knownMode != effectCurrent) { knownMode = effectCurrent; - if(displayTurnedOff)needRedraw = true; + if (displayTurnedOff) needRedraw = true; else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); } else if (knownPalette != effectPalette) { knownPalette = effectPalette; - if(displayTurnedOff)needRedraw = true; + if (displayTurnedOff) needRedraw = true; else showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 2); } else if (knownBrightness != bri) { - if(displayTurnedOff && nightlightActive){needRedraw = false; knownBrightness = bri;} - else if(displayTurnedOff)needRedraw = true; + if (displayTurnedOff && nightlightActive) { needRedraw = false; knownBrightness = bri; } + else if (displayTurnedOff) needRedraw = true; else updateBrightness(); } else if (knownEffectSpeed != effectSpeed) { - if(displayTurnedOff)needRedraw = true; + if (displayTurnedOff) needRedraw = true; else updateSpeed(); } else if (knownEffectIntensity != effectIntensity) { - if(displayTurnedOff)needRedraw = true; + if (displayTurnedOff) needRedraw = true; else updateIntensity(); } - if (!needRedraw) { // Nothing to 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() // and turn it back on if it changed. + clear(); sleepOrClock(true); } else if (displayTurnedOff && clockMode) { showTime(); } return; - } else { - clear(); } needRedraw = false; @@ -532,11 +542,10 @@ class FourLineDisplayUsermod : public Usermod { if (displayTurnedOff) { // Turn the display back on sleepOrClock(false); + clear(); } // Update last known values. - knownSsid = apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : - knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); knownBrightness = bri; knownMode = effectCurrent; knownPalette = effectPalette; @@ -563,57 +572,59 @@ class FourLineDisplayUsermod : public Usermod { showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info } - void updateBrightness(){ + void updateBrightness() { knownBrightness = bri; - if(overlayUntil == 0){ - brightness100 = (((float)(bri)/255)*100); - char lineBuffer[4]; - sprintf_P(lineBuffer, PSTR("%-3d"), brightness100); - drawString(1, lineHeight, lineBuffer); - lastRedraw = millis();} + if (overlayUntil == 0) { + brightness100 = ((uint16_t)bri*100)/255; + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), brightness100); + drawString(1, lineHeight, lineBuffer); + lastRedraw = millis(); + } } - void updateSpeed(){ + void updateSpeed() { knownEffectSpeed = effectSpeed; - if(overlayUntil == 0){ - fxspeed100 = (((float)(effectSpeed)/255)*100); - char lineBuffer[4]; - sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100); - drawString(5, lineHeight, lineBuffer); - lastRedraw = millis();} + if (overlayUntil == 0) { + fxspeed100 = ((uint16_t)effectSpeed*100)/255; + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100); + drawString(5, lineHeight, lineBuffer); + lastRedraw = millis(); + } } - void updateIntensity(){ + void updateIntensity() { knownEffectIntensity = effectIntensity; - if(overlayUntil == 0){ - fxintensity100 = (((float)(effectIntensity)/255)*100); - char lineBuffer[4]; - sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100); - drawString(9, lineHeight, lineBuffer); - lastRedraw = millis();} + if (overlayUntil == 0) { + fxintensity100 = ((uint16_t)effectIntensity*100)/255; + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100); + drawString(9, lineHeight, lineBuffer); + lastRedraw = millis(); + } } - void draw2x2GlyphIcons(){ - if(lineHeight == 2){ + void draw2x2GlyphIcons() { + if (lineHeight == 2) { drawGlyph(1, 0, 1, u8x8_font_benji_custom_icons_2x2, true);//brightness icon drawGlyph(5, 0, 2, u8x8_font_benji_custom_icons_2x2, true);//speed 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, 3*lineHeight, 5, u8x8_font_benji_custom_icons_2x2, true);//effect icon - } - else{ - 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(10, 0, 78, u8x8_font_open_iconic_thing_1x1);//intensity icon - drawGlyph(15, 2*lineHeight, 4, u8x8_font_benji_custom_icons_1x1);//palette icon - drawGlyph(15, 3*lineHeight, 70, u8x8_font_open_iconic_thing_1x1);//effect icon + } else { + 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(10, 0, 78, u8x8_font_open_iconic_thing_1x1); //intensity icon + drawGlyph(15, 2*lineHeight, 4, u8x8_font_benji_custom_icons_1x1); //palette icon + drawGlyph(15, 3*lineHeight, 70, u8x8_font_open_iconic_thing_1x1); //effect icon } } - void drawStatusIcons(){ + void drawStatusIcons() { drawGlyph(14, 0, 80 + (wificonnected?0:1), u8x8_font_open_iconic_embedded_1x1, true); // wifi icon drawGlyph(15, 0, 78 + (bri > 0 ? 0 : 3), u8x8_font_open_iconic_embedded_1x1, true); // power icon - drawGlyph(13, 0, 66 + (nightlightActive?0:4), u8x8_font_open_iconic_weather_1x1, true); // moon icon for nighlight mode + drawGlyph(13, 0, 66 + (nightlightActive?0:4), u8x8_font_open_iconic_weather_1x1, true); // moon icon for nighlight mode } /** @@ -627,94 +638,65 @@ class FourLineDisplayUsermod : public Usermod { } //Draw the arrow for the current setting beiong changed - void drawArrow(){ - if(markColNum != 255 && markLineNum !=255)drawGlyph(markColNum, markLineNum*lineHeight, 69, u8x8_font_open_iconic_play_1x1); + void drawArrow() { + if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 69, u8x8_font_open_iconic_play_1x1); } //Display the current effect or palette (desiredEntry) // on the appropriate line (row). void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { + char lineBuffer[LINE_BUFFER_SIZE]; knownMode = effectCurrent; knownPalette = effectPalette; - if(overlayUntil == 0){ - char lineBuffer[MAX_JSON_CHARS]; + if (overlayUntil == 0) { + // Find the mode name in JSON + uint8_t printedChars = extractModeName(inputEffPal, qstring, lineBuffer, LINE_BUFFER_SIZE-1); + + if (lineHeight == 2) { // use this code for 8 line display 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 - for (size_t i = 0; i < strlen_P(qstring); i++) { //find and get the full text for printing - 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(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; - drawString(1, row*lineHeight, lineBuffer); - lastRedraw = millis(); - }else{ // for long names divide the text into 2 lines and print them small - for (uint8_t i = 0; i < printedChars; i++){ - switch (lineBuffer[i]){ - case ' ': - if(i > 4 && !spaceHit) { - spaceHit = true; - break;} - if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i]; - if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; - break; - default: - if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i]; - if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; + 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; + drawString(1, row*lineHeight, lineBuffer); + } 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++) { + switch (lineBuffer[i]) { + case ' ': + if (i > 4 && !spaceHit) { + spaceHit = true; break; } - } - for (; smallChars1 < (MAX_MODE_LINE_SPACE-1); smallChars1++) smallBuffer1[smallChars1]=' '; - smallBuffer1[smallChars1] = 0; - drawString(1, row*lineHeight, smallBuffer1, true); - for (; smallChars2 < (MAX_MODE_LINE_SPACE-1); smallChars2++) smallBuffer2[smallChars2]=' '; - smallBuffer2[smallChars2] = 0; - drawString(1, row*lineHeight+1, smallBuffer2, true); - lastRedraw = millis(); + if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; + else smallBuffer1[smallChars1++] = lineBuffer[i]; + break; + default: + if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; + else smallBuffer1[smallChars1++] = lineBuffer[i]; + break; + } } + for (; smallChars1 < (MAX_MODE_LINE_SPACE-1); smallChars1++) smallBuffer1[smallChars1]=' '; + smallBuffer1[smallChars1] = 0; + drawString(1, row*lineHeight, smallBuffer1, true); + for (; smallChars2 < (MAX_MODE_LINE_SPACE-1); smallChars2++) smallBuffer2[smallChars2]=' '; + smallBuffer2[smallChars2] = 0; + drawString(1, row*lineHeight+1, smallBuffer2, true); } - else{ // use this code for 4 ling displays - if (printedChars > MAX_MODE_LINE_SPACE) printedChars = MAX_MODE_LINE_SPACE; - for (uint8_t i = 0; i < printedChars; i++){ - smallBuffer3[smallChars3++] = lineBuffer[i]; - } - - for (; smallChars3 < (MAX_MODE_LINE_SPACE); smallChars3++) smallBuffer3[smallChars3]=' '; - smallBuffer3[smallChars3] = 0; - drawString(1, row*lineHeight, smallBuffer3, true); - lastRedraw = millis(); - } - } + } 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; + for (uint8_t i = 0; i < printedChars; i++) smallBuffer3[smallChars3++] = lineBuffer[i]; + for (; smallChars3 < (MAX_MODE_LINE_SPACE); smallChars3++) smallBuffer3[smallChars3]=' '; + smallBuffer3[smallChars3] = 0; + drawString(1, row*lineHeight, smallBuffer3, true); + } + lastRedraw = millis(); + } } /** @@ -724,8 +706,10 @@ class FourLineDisplayUsermod : public Usermod { * to wake up the screen. */ bool wakeDisplay() { - //knownHour = 99; + if (type == NONE || !enabled) return false; + knownHour = 99; if (displayTurnedOff) { + clear(); // Turn the display back on sleepOrClock(false); redraw(true); @@ -740,44 +724,59 @@ class FourLineDisplayUsermod : public Usermod { * Clears the screen and prints. */ void overlay(const char* line1, long showHowLong, byte glyphType) { - if (displayTurnedOff) { - // Turn the display back on - sleepOrClock(false); - } + if (displayTurnedOff) { + // Turn the display back on + sleepOrClock(false); + } - // Print the overlay - clear(); - if (glyphType > 0){ - 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); - } - if (line1) drawString(0, 3*lineHeight, line1); - overlayUntil = millis() + showHowLong; + // Print the overlay + clear(); + if (glyphType > 0) { + 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); + if (line1) drawString(0, 3*lineHeight, line1); + } else { + if (line1) drawString(0, 2*(lineHeight-1), line1); + } + overlayUntil = millis() + showHowLong; } void networkOverlay(const char* line1, long showHowLong) { - if (displayTurnedOff) { - // Turn the display back on - sleepOrClock(false); - } + if (displayTurnedOff) { + // Turn the display back on + sleepOrClock(false); + } // Print the overlay - clear(); + clear(); // 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 - String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); // - drawString(0, lineHeight, ssidString.c_str()); + String line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); + if (line.length() < getCols()) center(line, getCols()); + drawString(0, lineHeight, line.c_str()); // Print `~` char to indicate that SSID is longer, than our display - if (knownSsid.length() > getCols()) { - drawString(getCols() - 1, 0, "~"); - } - // Third row with IP and Psssword in AP Mode - drawString(0, lineHeight*2, (knownIp.toString()).c_str()); - if (apActive) { - String appassword = apPass; - drawString(0, lineHeight*3, appassword.c_str()); - } - overlayUntil = millis() + showHowLong; + if (knownSsid.length() > getCols()) { + drawString(getCols() - 1, 0, "~"); + } + // Third row with IP and Password in AP Mode + line = knownIp.toString(); + center(line, getCols()); + drawString(0, lineHeight*2, line.c_str()); + if (apActive) { + line = apPass; + 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; } @@ -787,13 +786,12 @@ class FourLineDisplayUsermod : public Usermod { void sleepOrClock(bool enabled) { if (enabled) { if (clockMode) { - clear(); - knownMinute = 99; + knownMinute = knownHour = 99; showTime(); - }else setPowerSave(1); + } else + setPowerSave(1); displayTurnedOff = true; - } - else { + } else { setPowerSave(0); displayTurnedOff = false; } @@ -805,28 +803,45 @@ class FourLineDisplayUsermod : public Usermod { * the useAMPM configuration. */ void showTime() { - if(knownMinute != minute(localTime)){ //only redraw clock if it has changed + if (type == NONE || !enabled || !displayTurnedOff) return; + char lineBuffer[LINE_BUFFER_SIZE]; + static byte lastSecond; + byte secondCurrent = second(localTime); + byte minuteCurrent = minute(localTime); + byte hourCurrent = hour(localTime); - //updateLocalTime(); - byte AmPmHour = hour(localTime); - boolean isitAM = true; - if (useAMPM) { - if (AmPmHour > 11) AmPmHour -= 12; - if (AmPmHour == 0) AmPmHour = 12; - if (hour(localTime) > 11) isitAM = false; - } - clear(); - drawStatusIcons(); //icons power, wifi, timer, etc + if (knownMinute != minuteCurrent) { //only redraw clock if it has changed + //updateLocalTime(); + byte AmPmHour = hourCurrent; + boolean isitAM = true; + if (useAMPM) { + if (AmPmHour > 11) { AmPmHour -= 12; isitAM = false; } + if (AmPmHour == 0) { AmPmHour = 12; } + } - 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 + drawStatusIcons(); //icons power, wifi, timer, etc - sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hour(localTime)), minute(localTime)); - draw2x2String(TIME_INDENT+2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds + if (knownHour != hourCurrent) { + // only update date when hour changes + sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); + draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day + } + + sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent); + 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 - 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) { JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; JsonArray io_pin = top.createNestedArray("pin"); 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["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(_contrast)] = contrast; - top[FPSTR(_refreshRate)] = refreshRate/10; + top[FPSTR(_refreshRate)] = refreshRate/1000; top[FPSTR(_screenTimeOut)] = screenTimeout/1000; top[FPSTR(_sleepMode)] = (bool) sleepMode; top[FPSTR(_clockMode)] = (bool) clockMode; + top[FPSTR(_showSeconds)] = (bool) showSeconds; top[FPSTR(_busClkFrequency)] = ioFrequency/1000; DEBUG_PRINTLN(F("4 Line Display config saved.")); } @@ -907,15 +925,20 @@ class FourLineDisplayUsermod : public Usermod { return false; } + enabled = top[FPSTR(_enabled)] | enabled; newType = top["type"] | newType; for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i]; flip = top[FPSTR(_flip)] | flip; 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; sleepMode = top[FPSTR(_sleepMode)] | sleepMode; clockMode = top[FPSTR(_clockMode)] | clockMode; - ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency + 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 DEBUG_PRINT(FPSTR(_name)); if (!initDone) { @@ -930,10 +953,10 @@ class FourLineDisplayUsermod : public Usermod { for (byte i=0; i<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } if (pinsChanged || type!=newType) { if (type != NONE) delete u8x8; - for (byte i=0; i<5; i++) { - if (ioPin[i]>=0) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay); - ioPin[i] = newPin[i]; - } + PinOwner po = 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 + 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 type = NONE; return true; @@ -941,13 +964,14 @@ class FourLineDisplayUsermod : public Usermod { setup(); 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); setFlipMode(flip); + knownHour = 99; if (needsRedraw && !wakeDisplay()) redraw(true); } // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !(top[_busClkFrequency]).isNull(); + return !top[FPSTR(_showSeconds)].isNull(); } /* @@ -960,11 +984,13 @@ class FourLineDisplayUsermod : public Usermod { }; // strings to reduce flash memory usage (used more than twice) -const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay"; -const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast"; -const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate0.01Sec"; -const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec"; -const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip"; -const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode"; -const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode"; +const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay"; +const char FourLineDisplayUsermod::_enabled[] PROGMEM = "enabled"; +const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast"; +const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRateSec"; +const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec"; +const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip"; +const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode"; +const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode"; +const char FourLineDisplayUsermod::_showSeconds[] PROGMEM = "showSeconds"; const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz"; diff --git a/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h index 2be7ce84..092206bb 100644 --- a/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h +++ b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h @@ -162,7 +162,6 @@ public: break; } } - re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); } @@ -189,6 +188,7 @@ public: 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; @@ -200,18 +200,14 @@ public: case '[': break; case ']': - complete = true; + if (!insideQuotes) complete = true; break; case ',': - modeIndex++; + if (!insideQuotes) modeIndex++; default: - if (!insideQuotes) { - break; - } - } - if (complete) { - break; + if (!insideQuotes) break; } + if (complete) break; } return modeStrings; } diff --git a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h index bf909bdc..19f97a2d 100644 --- a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h +++ b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h @@ -97,6 +97,7 @@ public: */ void setup() { + DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { // BUG: configuring this usermod with conflicting pins @@ -443,14 +444,11 @@ public: DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); return false; } - int8_t newDTpin = pinA; - int8_t newCLKpin = pinB; - int8_t newSWpin = pinC; + int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; + int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; + int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; 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)); if (!initDone) { diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 625af0af..aa1f82b9 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -19,17 +19,20 @@ // Change between modes by pressing a button. // // Dependencies -// * This usermod REQURES the ModeSortUsermod // * This Usermod works best coupled with // FourLineDisplayUsermod. // -// If FourLineDisplayUsermod is used the folowing options are also inabled +// If FourLineDisplayUsermod is used the folowing options are also enabled // // * main color // * saturation of main color // * 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 #define ENCODER_DT_PIN 18 #endif @@ -49,13 +52,73 @@ #define LAST_UI_STATE 4 #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 { private: - int fadeAmount = 5; // Amount to change every step (brightness) + int fadeAmount = 5; // Amount to change every step (brightness) unsigned long currentTime; unsigned long loopTime; - unsigned long buttonHoldTIme; + unsigned long buttonHoldTime; int8_t pinA = ENCODER_DT_PIN; // DT from encoder int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder int8_t pinC = ENCODER_SW_PIN; // SW from encoder @@ -63,8 +126,8 @@ private: unsigned char button_state = HIGH; unsigned char prev_button_state = HIGH; bool networkShown = false; - uint16_t currentHue1 = 6425; // default reboot color - byte currentSat1 = 255; + uint16_t currentHue1 = 16; // default boot color + byte currentSat1 = 255; #ifdef USERMOD_FOUR_LINE_DISPLAY FourLineDisplayUsermod *display; @@ -72,7 +135,16 @@ private: void* display = nullptr; #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; + + // 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; unsigned char Enc_A; @@ -95,13 +167,90 @@ private: static const char _CLK_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: /* - * setup() is called once at boot. WiFi is not yet connected at this point. - * You can use it to initialize variables, sensors or similar. - */ + * 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() { + DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { // BUG: configuring this usermod with conflicting pins @@ -120,9 +269,7 @@ public: currentTime = millis(); loopTime = currentTime; - ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT); - modes_alpha_indexes = modeSortUsermod->getModesAlphaIndexes(); - palettes_alpha_indexes = modeSortUsermod->getPalettesAlphaIndexes(); + if (!initDone) sortModesAndPalettes(); #ifdef USERMOD_FOUR_LINE_DISPLAY // This Usermod uses FourLineDisplayUsermod for the best experience. @@ -140,24 +287,24 @@ public: } /* - * connected() is called every time the WiFi is (re)connected - * Use it to initialize network interfaces - */ + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ void connected() { //Serial.println("Connected to WiFi!"); } /* - * loop() is called continuously. Here you can check for events, read sensors, etc. - * - * Tips: - * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. - * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. - * - * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. - * Instead, use a timer check as shown here. - */ + * loop() is called continuously. Here you can check for events, read sensors, etc. + * + * Tips: + * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. + * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. + * + * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. + * Instead, use a timer check as shown here. + */ void loop() { currentTime = millis(); // get the current elapsed time @@ -167,19 +314,19 @@ public: // is not yet initialized when setup is called. if (!currentEffectAndPaletteInitialized) { - findCurrentEffectAndPalette();} + findCurrentEffectAndPalette(); + } - if(modes_alpha_indexes[effectCurrentIndex] != effectCurrent - || palettes_alpha_indexes[effectPaletteIndex] != effectPalette){ + if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) { currentEffectAndPaletteInitialized = false; - } + } if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz { button_state = digitalRead(pinC); 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; @@ -190,25 +337,25 @@ public: if (display != nullptr) { switch(newState) { case 0: - changedState = changeState(" Brightness", 1, 0, 1); + changedState = changeState(PSTR("Brightness"), 1, 0, 1); break; case 1: - changedState = changeState(" Speed", 1, 4, 2); + changedState = changeState(PSTR("Speed"), 1, 4, 2); break; case 2: - changedState = changeState(" Intensity", 1 ,8, 3); + changedState = changeState(PSTR("Intensity"), 1 ,8, 3); break; case 3: - changedState = changeState(" Color Palette", 2, 0, 4); + changedState = changeState(PSTR("Color Palette"), 2, 0, 4); break; case 4: - changedState = changeState(" Effect", 3, 0, 5); + changedState = changeState(PSTR("Effect"), 3, 0, 5); break; case 5: - changedState = changeState(" Main Color", 255, 255, 7); + changedState = changeState(PSTR("Main Color"), 255, 255, 7); break; case 6: - changedState = changeState(" Saturation", 255, 255, 8); + changedState = changeState(PSTR("Saturation"), 255, 255, 8); break; } } @@ -220,11 +367,15 @@ public: { prev_button_state = button_state; 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_B = digitalRead(pinB); @@ -288,9 +439,9 @@ public: } } - void displayNetworkInfo(){ + void displayNetworkInfo() { #ifdef USERMOD_FOUR_LINE_DISPLAY - display->networkOverlay(" NETWORK INFO", 15000); + display->networkOverlay(PSTR("NETWORK INFO"), 10000); networkShown = true; #endif } @@ -313,17 +464,20 @@ public: } boolean changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display != nullptr) { - if (display->wakeDisplay()) { - // Throw away wake up input - return false; - } - display->overlay(stateName, 750, glyph); - display->setMarkLine(markedLine, markedCol); - } - #endif - return true; + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display != nullptr) { + if (display->wakeDisplay()) { + // Throw away wake up input + return false; + } + String line = stateName; + //line.trim(); + display->center(line, display->getCols()); + display->overlay(line.c_str(), 750, glyph); + display->setMarkLine(markedLine, markedCol); + } + #endif + return true; } void lampUdated() { @@ -335,158 +489,158 @@ public: } void changeBrightness(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } - #endif - if (increase) bri = (bri + fadeAmount <= 255) ? (bri + fadeAmount) : 255; - else bri = (bri - fadeAmount >= 0) ? (bri - fadeAmount) : 0; - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->updateBrightness(); - #endif + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + if (increase) bri = (bri + fadeAmount <= 255) ? (bri + fadeAmount) : 255; + else bri = (bri - fadeAmount >= 0) ? (bri - fadeAmount) : 0; + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateBrightness(); + #endif } void changeEffect(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } - #endif - if (increase) effectCurrentIndex = (effectCurrentIndex + 1 >= strip.getModeCount()) ? 0 : (effectCurrentIndex + 1); - else effectCurrentIndex = (effectCurrentIndex - 1 < 0) ? (strip.getModeCount() - 1) : (effectCurrentIndex - 1); - effectCurrent = modes_alpha_indexes[effectCurrentIndex]; - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); - #endif + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0); + effectCurrent = modes_alpha_indexes[effectCurrentIndex]; + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); + #endif } void changeEffectSpeed(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } - #endif - if (increase) effectSpeed = (effectSpeed + fadeAmount <= 255) ? (effectSpeed + fadeAmount) : 255; - else effectSpeed = (effectSpeed - fadeAmount >= 0) ? (effectSpeed - fadeAmount) : 0; - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->updateSpeed(); - #endif + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0); + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateSpeed(); + #endif } void changeEffectIntensity(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } - #endif - if (increase) effectIntensity = (effectIntensity + fadeAmount <= 255) ? (effectIntensity + fadeAmount) : 255; - else effectIntensity = (effectIntensity - fadeAmount >= 0) ? (effectIntensity - fadeAmount) : 0; - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->updateIntensity(); - #endif + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0); + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateIntensity(); + #endif } void changePalette(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } - #endif - if (increase) effectPaletteIndex = (effectPaletteIndex + 1 >= strip.getPaletteCount()) ? 0 : (effectPaletteIndex + 1); - else effectPaletteIndex = (effectPaletteIndex - 1 < 0) ? (strip.getPaletteCount() - 1) : (effectPaletteIndex - 1); - effectPalette = palettes_alpha_indexes[effectPaletteIndex]; - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); - #endif + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + effectPaletteIndex = max(min((increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()-1), 0); + effectPalette = palettes_alpha_indexes[effectPaletteIndex]; + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); + #endif } void changeHue(bool increase){ - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } - #endif - - if(increase) currentHue1 += 321; - else currentHue1 -= 321; - colorHStoRGB(currentHue1, currentSat1, col); - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->updateRedrawTime(); - #endif + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + if (increase) { if (currentHue1<256) currentHue1 += 4; else currentHue1 = 0; } + else { if (currentHue1>3) currentHue1 -= 4; else currentHue1 = 256; } + colorHStoRGB(currentHue1*255, currentSat1, col); + strip.applyToAllSelected = true; + strip.setColor(0, colorFromRgbw(col)); + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateRedrawTime(); + #endif } void changeSat(bool increase){ - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } - #endif - - if(increase) currentSat1 = (currentSat1 + 5 <= 255 ? (currentSat1 + 5) : 255); - else currentSat1 = (currentSat1 - 5 >= 0 ? (currentSat1 - 5) : 0); - colorHStoRGB(currentHue1, currentSat1, col); - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->updateRedrawTime(); - #endif - + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0); + colorHStoRGB(currentHue1*256, currentSat1, col); + strip.applyToAllSelected = true; + strip.setColor(0, colorFromRgbw(col)); + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateRedrawTime(); + #endif } /* - * 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 - */ + * 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) - { - int reading = 20; - //this code adds "u":{"Light":[20," lux"]} to the info object - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - JsonArray lightArr = user.createNestedArray("Light"); //name - lightArr.add(reading); //value - lightArr.add(" lux"); //unit - } - */ + void addToJsonInfo(JsonObject& root) + { + int reading = 20; + //this code adds "u":{"Light":[20," lux"]} to the info object + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + JsonArray lightArr = user.createNestedArray("Light"); //name + lightArr.add(reading); //value + lightArr.add(" lux"); //unit + } + */ /* - * 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 - */ + * 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) { //root["user0"] = userVar0; } + */ /* - * 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 - */ + * 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) { //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!")); } + */ /** * addToConfig() (called from set.cpp) stores persistent properties to cfg.json @@ -514,14 +668,11 @@ public: DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); return false; } - int8_t newDTpin = pinA; - int8_t newCLKpin = pinB; - int8_t newSWpin = pinC; + int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; + int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; + int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; 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)); if (!initDone) { diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a2b6a959..bad85e5a 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1216,12 +1216,13 @@ uint16_t WS2812FX::mode_loading(void) { //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 uint32_t it = now / map(SEGMENT.speed, 0, 255, delay<<4, delay); uint16_t offset = it % SEGLEN; + uint16_t width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip if (!width) width = 1; for (uint16_t i = 0; i < width; i++) { 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 uint16_t WS2812FX::mode_police() { fill(SEGCOLOR(1)); - return police_base(RED, BLUE, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip -} - - -//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 + return police_base(RED, BLUE); } @@ -1262,7 +1248,142 @@ uint16_t WS2812FX::mode_two_dots() fill(SEGCOLOR(2)); 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(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(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() @@ -3783,18 +3904,24 @@ uint16_t WS2812FX::mode_washing_machine(void) { Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e */ 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 uint32_t* pixels = reinterpret_cast(SEGENV.data); 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); - setPixelColor(i, pixels[i]); shift += 3; } + uint16_t offset = 0; + for (int i = 0; i < SEGLEN; i++) { + setPixelColor(i, pixels[offset++]); + if (offset > pixelLen) offset = 0; + } + return FRAMETIME; } diff --git a/wled00/FX.h b/wled00/FX.h index f43e9765..7ea54fe9 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -48,7 +48,8 @@ /* Not used in all effects yet */ #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 insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ @@ -71,7 +72,7 @@ #define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS) #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 SEGMENT _segments[_segment_index] @@ -161,14 +162,14 @@ #define FX_MODE_COMET 41 #define FX_MODE_FIREWORKS 42 #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_GRADIENT 46 #define FX_MODE_LOADING 47 #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_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_HALLOWEEN 53 // candidate for removal #define FX_MODE_TRICOLOR_CHASE 54 @@ -550,9 +551,9 @@ class WS2812FX { _mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient; _mode[FX_MODE_LOADING] = &WS2812FX::mode_loading; _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_AREAS] = &WS2812FX::mode_two_areas; + _mode[FX_MODE_FAIRYTWINKLE] = &WS2812FX::mode_fairytwinkle; _mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual; _mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween; _mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase; @@ -648,12 +649,14 @@ class WS2812FX { calcGammaTable(float), 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), + restartRuntime(), resetSegments(), makeAutoSegments(), fixInvalidSegments(), setPixelColor(uint16_t n, uint32_t c), setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0), show(void), + setTargetFps(uint8_t fps), setPixelSegment(uint8_t n), deserializeMap(uint8_t n=0); @@ -684,6 +687,7 @@ class WS2812FX { getActiveSegmentsNum(void), //getFirstSelectedSegment(void), getMainSegmentId(void), + getTargetFps(void), gamma8(uint8_t), gamma8_cal(uint8_t, float), sin_gap(uint16_t), @@ -773,9 +777,9 @@ class WS2812FX { mode_gradient(void), mode_loading(void), mode_police(void), - mode_police_all(void), + mode_fairy(void), mode_two_dots(void), - mode_two_areas(void), + mode_fairytwinkle(void), mode_running_dual(void), mode_bicolor_chase(void), mode_tricolor_chase(void), @@ -855,6 +859,8 @@ class WS2812FX { uint16_t _usedSegmentData = 0; uint16_t _transitionDur = 750; + uint8_t _targetFps = 42; + uint16_t _frametime = (1000/42); uint16_t _cumulativeFps = 2; bool @@ -878,7 +884,7 @@ class WS2812FX { chase(uint32_t, uint32_t, uint32_t, bool), gradient_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), tricolor_chase(uint32_t, uint32_t), twinklefox_base(bool), @@ -921,6 +927,11 @@ class WS2812FX { 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 // Technical notes // =============== @@ -936,7 +947,7 @@ class WS2812FX { // - 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 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. // 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", "Gradient", "Loading", -"Police@!,Width;;", -"Police All@!,Width;;", +"Police@!,Width;;0", +"Fairy", "Two Dots@!,Dot size;1,2,Bg;!", -"Two Areas@!,Size;1,2,Bg;!", +"Fairy Twinkle", "Running Dual", "Halloween", -"Chase 3@!,Size;1,2,3;", -"Tri Wipe@!,Width;1,2,3;", +"Chase 3@!,Size;1,2,3;0", +"Tri Wipe@!,Width;1,2,3;0", "Tri Fade", "Lightning", "ICU", @@ -1008,9 +1019,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Stream 2", "Oscillate", "Pride 2015", -"Juggle@!,Trail;!,!,;!", +"Juggle@!=16,Trail=240;!,!,;!", "Palette@!,;;!", -"Fire 2012@Spark rate,Decay;;!", +"Fire 2012@Spark rate=120,Decay=64;;!", "Colorwaves", "Bpm", "Fill Noise", @@ -1027,12 +1038,12 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Twinklefox", "Twinklecat", "Halloween Eyes", -"Solid Pattern@Fg size,Bg size;Fg,Bg,;", -"Solid Pattern Tri@,Size;1,2,3;", +"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,Flicker intensity;!,!,;", +"Candle@Flicker rate=96,Flicker intensity=224;!,!,;0", "Fireworks Starburst", "Fireworks 1D@Gravity,Firing side;!,!,;!", "Bouncing Balls@Gravity,# of balls;!,!,;!", @@ -1046,9 +1057,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Ripple Rainbow", "Heartbeat", "Pacifica", -"Candle Multi@Flicker rate,Flicker intensity;!,!,;", -"Solid Glitter@,!;!,,;", -"Sunrise@Time [min],;;", +"Candle Multi@Flicker rate=96,Flicker intensity=224;!,!,;0", +"Solid Glitter@,!;!,,;0", +"Sunrise@Time [min]=60,;;0", "Phased", "Twinkleup@!,Intensity;!,!,;!", "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", "Candy2" ])====="; - +*/ #endif diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 62abc787..4fe494ad 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -165,12 +165,12 @@ void WS2812FX::service() { _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)); } //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(); /* reverse just an individual segment */ @@ -187,7 +187,7 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) { 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 uint16_t realIndex = realPixelIndex(i); @@ -350,6 +350,16 @@ uint16_t WS2812FX::getFps() { 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. */ @@ -442,14 +452,14 @@ void WS2812FX::setBrightness(uint8_t b) { if (gammaCorrectBri) b = gamma8(b); if (_brightness == b) return; _brightness = b; - _segment_index = 0; if (_brightness == 0) { //unfreeze all segments on power off for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { _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) { @@ -606,8 +616,14 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, _segment_runtimes[n].reset(); } +void WS2812FX::restartRuntime() { + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { + _segment_runtimes[i].reset(); + } +} + 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; memset(_segments, 0, sizeof(_segments)); //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) +//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) { 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; - _virtualSegmentLength = SEGMENT.length(); + _virtualSegmentLength = SEGMENT.virtualLength(); } 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 } } @@ -735,20 +772,20 @@ void WS2812FX::setTransition(uint16_t t) 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++) { - _segment_index = i; - SEGMENT.setOption(SEG_OPTION_TRANSITIONAL, t); + _segments[i].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 */ -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; uint16_t blendmax = b16 ? 0xFFFF : 0xFF; 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; 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 > 255) return 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); } -CRGB WS2812FX::col_to_crgb(uint32_t color) +CRGB IRAM_ATTR WS2812FX::col_to_crgb(uint32_t color) { CRGB fastled_col; 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) * @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) { uint32_t color = SEGCOLOR(mcol); @@ -1094,11 +1131,7 @@ void WS2812FX::deserializeMap(uint8_t n) { return; } - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else - if (!requestJSONBufferLock(5)) return; - #endif + if (!requestJSONBufferLock(7)) return; DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); @@ -1181,4 +1214,159 @@ WS2812FX* WS2812FX::instance = nullptr; //Bus static member definition, would belong in bus_manager.cpp int16_t Bus::_cct = -1; uint8_t Bus::_cctBlend = 0; -uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL; \ No newline at end of file +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() 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" +])====="; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index dd36d724..4cf56c79 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -91,7 +91,7 @@ class Bus { virtual void setBrightness(uint8_t b) {} virtual void cleanup() {} 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 uint8_t getColorOrder() { return COL_ORDER_RGB; } virtual uint8_t skippedLeds() { return 0; } @@ -220,7 +220,7 @@ class BusDigital : public Bus { return _colorOrder; } - inline uint16_t getLength() { + uint16_t getLength() { return _len - _skip; } @@ -288,7 +288,7 @@ class BusPwm : public Bus { if (!pinManager.allocatePin(currentPin, true, PinOwner::BusPwm)) { deallocatePins(); return; } - _pins[i] = currentPin; // store only after allocatePin() succeeds + _pins[i] = currentPin; //store only after allocatePin() succeeds #ifdef ESP8266 pinMode(_pins[i], OUTPUT); #else @@ -318,16 +318,21 @@ class BusPwm : public Bus { 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; - if (cct < _cctBlend) ww = 255; - else ww = ((255-cct) * 255) / (255 - _cctBlend); + #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; + else ww = ((255-cct) * 255) / (255 - _cctBlend); if ((255-cct) < _cctBlend) cw = 255; else cw = (cct * 255) / (255 - _cctBlend); ww = (w * ww) / 255; //brightness scaling cw = (w * cw) / 255; + #endif switch (_type) { case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation @@ -391,7 +396,7 @@ class BusPwm : public Bus { private: 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 uint8_t _ledcStart = 255; #endif @@ -448,7 +453,7 @@ class BusNetwork : public Bus { void setPixelColor(uint16_t pix, uint32_t c) { 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 uint16_t offset = pix * _UDPchannels; _data[offset] = R(c); diff --git a/wled00/button.cpp b/wled00/button.cpp index aef18c77..96cdfed5 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -20,7 +20,7 @@ void shortPressAction(uint8_t b) default: ++effectCurrent %= strip.getModeCount(); colorUpdated(CALL_MODE_BUTTON); break; } } else { - applyPreset(macroButton[b], CALL_MODE_BUTTON); + applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET); } // 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 } } else { - applyPreset(macroLongPress[b], CALL_MODE_BUTTON); + applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET); } // publish MQTT message @@ -58,7 +58,7 @@ void doublePressAction(uint8_t b) default: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break; } } else { - applyPreset(macroDoublePress[b], CALL_MODE_BUTTON); + applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET); } // 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 (!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 if (!bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);} } } 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 if (bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);} } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 62ae75ed..7b557cd6 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -86,8 +86,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | Bus::getAutoWhiteMode()); CJSON(correctWB, hw_led["cct"]); CJSON(cctFromRgb, hw_led[F("cr")]); - CJSON(strip.cctBlending, hw_led[F("cb")]); - Bus::setCCTBlend(strip.cctBlending); + CJSON(strip.cctBlending, hw_led[F("cb")]); + 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()); JsonArray ins = hw_led["ins"]; @@ -242,8 +244,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(receiveNotificationColor, if_sync_recv["col"]); CJSON(receiveNotificationEffects, if_sync_recv["fx"]); CJSON(receiveGroups, if_sync_recv["grp"]); + CJSON(receiveSegmentOptions, if_sync_recv["seg"]); //! 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"]; prev = notifyDirectDefault; @@ -378,7 +381,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { int act = timer["en"] | actPrev; if (act) timerWeekday[it]++; } - + if (it<8) { + CJSON(timerMonth[it], timer[F("mon")]); + CJSON(timerDay[it], timer[F("day")]); + } it++; } @@ -429,11 +435,7 @@ void deserializeConfigFromFS() { return; } - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else if (!requestJSONBufferLock(1)) return; - #endif DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); @@ -457,11 +459,7 @@ void serializeConfig() { DEBUG_PRINTLN(F("Writing settings to /cfg.json...")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else if (!requestJSONBufferLock(2)) return; - #endif JsonArray rev = doc.createNestedArray("rev"); rev.add(1); //major settings revision @@ -543,7 +541,8 @@ void serializeConfig() { hw_led["cct"] = correctWB; hw_led[F("cr")] = cctFromRgb; hw_led[F("cb")] = strip.cctBlending; - hw_led[F("rgbwm")] = Bus::getAutoWhiteMode(); + hw_led["fps"] = strip.getTargetFps(); + hw_led[F("rgbwm")] = Bus::getAutoWhiteMode(); JsonArray hw_led_ins = hw_led.createNestedArray("ins"); @@ -632,6 +631,7 @@ void serializeConfig() { if_sync_recv["col"] = receiveNotificationColor; if_sync_recv["fx"] = receiveNotificationEffects; if_sync_recv["grp"] = receiveGroups; + if_sync_recv["seg"] = receiveSegmentOptions; JsonObject if_sync_send = if_sync.createNestedObject("send"); if_sync_send[F("dir")] = notifyDirect; @@ -744,6 +744,10 @@ void serializeConfig() { timers_ins0["min"] = timerMinutes[i]; timers_ins0["macro"] = timerMacro[i]; 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"); @@ -780,11 +784,7 @@ void serializeConfig() { bool deserializeConfigSec() { DEBUG_PRINTLN(F("Reading settings from /wsec.json...")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else if (!requestJSONBufferLock(3)) return false; - #endif bool success = readObjectFromFile("/wsec.json", nullptr, &doc); if (!success) { @@ -829,11 +829,7 @@ bool deserializeConfigSec() { void serializeConfigSec() { DEBUG_PRINTLN(F("Writing settings to /wsec.json...")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else if (!requestJSONBufferLock(4)) return; - #endif JsonObject nw = doc.createNestedObject("nw"); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 5fea9cb5..a513f33a 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -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 high = maxf(rgb[0],maxf(rgb[1],rgb[2])); 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); } */ diff --git a/wled00/const.h b/wled00/const.h index d338e930..92891396 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -64,6 +64,7 @@ #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_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 #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -74,7 +75,7 @@ //Notifier callMode #define CALL_MODE_INIT 0 //no updates on init, can be used to disable updates #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_NIGHTLIGHT 4 #define CALL_MODE_NO_NOTIFY 5 @@ -84,6 +85,7 @@ #define CALL_MODE_BLYNK 9 #define CALL_MODE_ALEXA 10 #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 #define RGBW_MODE_MANUAL_ONLY 0 //No automatic white channel calculation. Manual white channel slider @@ -301,6 +303,8 @@ #define JSON_BUFFER_SIZE 20480 #endif +#define MIN_HEAP_SIZE (MAX_LED_MEMORY+2048) + // Maximum size of node map (list of other WLED instances) #ifdef ESP8266 #define WLED_MAX_NODES 24 diff --git a/wled00/data/index.css b/wled00/data/index.css index 16fc1bbb..be57d843 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -212,8 +212,9 @@ button { transform: rotate(0deg); transition: transform 0.3s; position: absolute; - top: 8px; - right: 8px; + top: 0; + right: 0; + padding: 8px; } .exp { diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 0cae084a..f77f5af7 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -7,12 +7,47 @@ WLED - + - - - +
Loading WLED UI...
diff --git a/wled00/data/index.js b/wled00/data/index.js index d4c82a64..d520469f 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -8,24 +8,23 @@ var expanded = [false]; var powered = [true]; var nlDur = 60, nlTar = 0; var nlMode = false; -var selectedFx = 0; +var selectedFx = 0, prevFx = -1; var selectedPal = 0; var sliderControl = ""; //WLEDSR: used by togglePcMode var csel = 0; -var currentPreset = -1; +var currentPreset = -1, prevPS = -1; var lastUpdate = 0; var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0; var pcMode = false, pcModeA = false, lastw = 0; var tr = 7; var d = document; -const ranges = RangeTouch.setup('input[type="range"]', {}); var palettesData; var fxdata = []; var pJson = {}, eJson = {}, lJson = {}; var pN = "", pI = 0, pNum = 0; var pmt = 1, pmtLS = 0, pmtLast = 0; var lastinfo = {}; -var ws; +var ws, cpick, ranges; var cfg = { theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}}, 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"] ]; -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 sCol(na, col) {d.documentElement.style.setProperty(na, col);} function gId(c) {return d.getElementById(c);} @@ -221,7 +209,7 @@ function onLoad() .catch(function (error) { console.log("holidays.json does not contain array of holidays. Defaults loaded."); }) - .finally(function(){ + .finally(()=>{ loadBg(cfg.theme.bg.url); }); } else @@ -232,10 +220,6 @@ function onLoad() for (var i = 0; i < cd.length; i++) cd[i].style.backgroundColor = "rgb(0, 0, 0)"; selectSlot(0); updateTablinks(0); - cpick.on("input:end", function() { - setColor(1); - }); - cpick.on("color:change", updatePSliders); pmtLS = localStorage.getItem('wledPmt'); // Load initial data @@ -244,7 +228,6 @@ function onLoad() loadFX(()=>{ loadFXData(); loadPresets(()=>{ - //if (isObj(lastinfo) && isEmpty(lastinfo)) loadInfo(requestJson); // if not filled by WS requestJson(); }); }); @@ -288,7 +271,7 @@ function showToast(text, error = false) x.classList.add(error ? "error":"show"); clearTimeout(timeout); x.style.animation = 'none'; - timeout = setTimeout(function(){ x.classList.remove("show"); }, 2900); + timeout = setTimeout(()=>{ x.classList.remove("show"); }, 2900); if (error) console.log(text); } @@ -303,7 +286,7 @@ function showErrorToast() 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) { @@ -515,7 +498,7 @@ function loadFXData(callback = null) clearErrorToast(); fxdata = json||[]; // add default value for Solid - fxdata.shift(); + fxdata.shift() fxdata.unshift("@;!;"); }) .catch(function (error) { @@ -564,9 +547,9 @@ function populatePresets(fromls) cn += `
`; if (cfg.comp.pid) cn += `
${i}
`; cn += `
${isPlaylist(i)?"":""}${pName(i)}
- -
-
`; + +
+`; pNum++; } @@ -602,41 +585,7 @@ function parseInfo() { pmt = li.fs.pmt; 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) { var cn=""; @@ -762,7 +711,7 @@ function populateSegments(s) for (var i = 0; i <= lSeg; i++) { updateLen(i); 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 (!noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value)>16) & 0xFF; - // g = (selColors[e]>> 8) & 0xFF; - // b = (selColors[e] ) & 0xFF; - //} + r = selColors[e][0]; + g = selColors[e][1]; + b = selColors[e][2]; } } if (index === false) { @@ -1105,8 +1048,10 @@ function updateSelectedFx() var selectedEffect = parent.querySelector(`.lstI[data-id="${selectedFx}"]`); if (selectedEffect) { 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. - setSliderAndColorControl(selectedFx); + setSliderAndColorControl(selectedFx, (fx || ps)); } } @@ -1172,6 +1117,7 @@ function readState(s,command=false) nlTar = s.nl.tbri; nlFade = s.nl.fade; syncSend = s.udpn.send; + prevPS = currentPreset; if (s.pl<0) currentPreset = s.ps; else currentPreset = s.pl; @@ -1235,6 +1181,7 @@ function readState(s,command=false) showToast('Error ' + s.error + ": " + errstr, true); } + prevFx = selectedFx; selectedPal = i.pal; selectedFx = i.fx; 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 -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() 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; var topPosition = 0; @@ -1252,15 +1219,25 @@ function setSliderAndColorControl(idx/*, extra*/) var slOnOff = (extras.length==0 || extras[0]=='')?[]:extras[0].split(","); var coOnOff = (extras.length<2 || extras[1]=='')?[]:extras[1].split(","); var paOnOff = (extras.length<3 || extras[2]=='')?[]:extras[2].split(","); + var obj = {"seg":{}}; // 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; ii && slOnOff[i] != "")) { 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]; else if (i==0) label.innerHTML = "Effect speed"; else if (i==1) label.innerHTML = "Effect intensity"; @@ -1320,33 +1297,37 @@ function setSliderAndColorControl(idx/*, extra*/) hide = false; } else { 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; // set palette on/off var palw = gId("palw"); // wrapper var pall = gId("pall"); // list // 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"; + 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]; else pall.innerHTML = ' Color palette'; } else { // disable label and slider 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; @@ -1362,7 +1343,7 @@ function requestJson(command=null) var useWs = (ws && ws.readyState === WebSocket.OPEN); var type = command ? 'post':'get'; 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); var t = gId('tt'); if (t.validity.valid && command.transition==null) { @@ -1376,6 +1357,8 @@ function requestJson(command=null) if (useWs) { ws.send(req?req:'{"v":true}'); return; + } else if (command && command.ps) { //refresh UI if we don't use WS (async loading of presets) + setTimeout(requestJson,200); } fetch(url, { @@ -1752,7 +1735,8 @@ function rptSeg(s) if (stop == 0) {return;} var rev = gId(`seg${s}rev`).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`)) { var grp = parseInt(gId(`seg${s}grp`).value); var spc = parseInt(gId(`seg${s}spc`).value); @@ -1850,7 +1834,7 @@ function setPalette(paletteId = null) if (paletteId === null) { paletteId = parseInt(d.querySelector('#pallist input[name="palette"]:checked').value); } 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'); if (selElement) { @@ -2106,7 +2090,7 @@ function setBalance(b) } 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); function openGH() { window.open("https://github.com/Aircoookie/WLED/wiki"); } @@ -2147,9 +2131,9 @@ function loadPalettesData(callback = null) var lsPalData = localStorage.getItem(lsKey); if (lsPalData) { try { - lsPalData = JSON.parse(lsPalData); - if (lsPalData && lsPalData.vid == lastinfo.vid) { - palettesData = lsPalData.p; + var d = JSON.parse(lsPalData); + if (d && d.vid == d.vid) { + palettesData = d.p; if (callback) callback(); // redrawPalPrev() return; } @@ -2157,7 +2141,7 @@ function loadPalettesData(callback = null) } palettesData = {}; - getPalettesData(0, function() { + getPalettesData(0, ()=>{ localStorage.setItem(lsKey, JSON.stringify({ p: palettesData, vid: lastinfo.vid @@ -2182,10 +2166,10 @@ function getPalettesData(page, callback) }) .then(json => { 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(); }) - .catch(function(error) { + .catch((error)=>{ showToast(error, true); console.log(error); }); @@ -2252,7 +2236,9 @@ function expand(i,a=false) */ expanded[i] = !expanded[i]; 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"); else gId(i<100?'segutil':'putil').classList.add(i<100?"staybot":"staytop"); diff --git a/wled00/data/settings.htm b/wled00/data/settings.htm index ef52c38b..1d0969de 100644 --- a/wled00/data/settings.htm +++ b/wled00/data/settings.htm @@ -1,6 +1,8 @@ - + + + WLED Settings @@ -31,6 +33,7 @@
+%DMXMENU%
diff --git a/wled00/data/settings_dmx.htm b/wled00/data/settings_dmx.htm index 645baa87..b2b54e20 100644 --- a/wled00/data/settings_dmx.htm +++ b/wled00/data/settings_dmx.htm @@ -1,5 +1,10 @@ -DMX Settings + + + + + + DMX Settings - +
diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 860ecfba..8446ed34 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -3,22 +3,15 @@ + LED Settings - + @@ -50,8 +56,9 @@ UDP Port: + - + @@ -84,7 +91,8 @@ UDP Port:
Sync groups
Sync groups 1 2 3

-Receive: Brightness, Color, and Effects
+Receive: Brightness, Color, and Effects
+Segment options
Send notifications on direct change:
Send notifications on button press or IR:
Send Alexa notifications:
@@ -120,45 +128,45 @@ DMX mode:
-E1.31 info
+E1.31 info
Timeout: ms
Force max brightness:
Disable realtime gamma correction:
Realtime LED offset:

Alexa Voice Assistant

Emulate Alexa device:
-Alexa invocation name: +Alexa invocation name:

Blynk

Blynk, MQTT and Hue sync all connect to external hosts!
This may impact the responsiveness of the ESP8266.

For best results, only use one of these services at a time.
(alternatively, connect a second ESP to them and use the UDP sync)

-Host: +Host: Port:
Device Auth token:
-Clear the token field to disable. Setup info +Clear the token field to disable. Setup info

MQTT

Enable MQTT:
-Broker: +Broker: Port:
The MQTT credentials are sent over an unsecured connection.
Never use the MQTT password for another service!

-Username:
+Username:
Password:
-Client ID:
-Device Topic:
-Group Topic:
+Client ID:
+Device Topic:
+Group Topic:
Publish on button press:
-Reboot required to apply changes. MQTT info +Reboot required to apply changes. MQTT info

Philips Hue

You can find the bridge IP and the light number in the 'About' section of the hue app.
Poll Hue light every ms:
Then, receive On/Off, Brightness, and Color
Hue Bridge IP:
- . - . - . -
+ . + . + . +
Press the pushlink button on the bridge, after that save this page!
(when first connecting)
Hue status: Disabled in this build
diff --git a/wled00/data/settings_time.htm b/wled00/data/settings_time.htm index ca692371..d28005a9 100644 --- a/wled00/data/settings_time.htm +++ b/wled00/data/settings_time.htm @@ -2,13 +2,14 @@ + Time Settings - + + @@ -107,7 +165,7 @@

Time setup

Get time from NTP server:
-
+
Use 24h format:
Time zone:
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
- Latitude (N): - Longitude (E): + Latitude:
+ Longitude:

Clock

Clock Overlay: @@ -160,8 +218,8 @@ Countdown Mode:
Countdown Goal:
- Year: 20 Month: Day:
- Hour: Minute: Second:
+ Date: 20--
+ Time: ::

Macro presets

Macros have moved!
Presets now also can be used as macros to save both JSON and HTTP API commands.
@@ -183,11 +241,12 @@ - Analog Button setup + Analog Button setup

Time-controlled presets

-
- -

+
+
+
+
diff --git a/wled00/data/settings_ui.htm b/wled00/data/settings_ui.htm index 364307d1..581fcf7e 100644 --- a/wled00/data/settings_ui.htm +++ b/wled00/data/settings_ui.htm @@ -3,6 +3,7 @@ + UI Settings - + diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index b0ab93e0..a14e0e33 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -3,22 +3,17 @@ + WiFi Settings - +
@@ -28,7 +23,7 @@

WiFi setup

Connect to existing network

- Network name (SSID, empty to not connect):

+ Network name (SSID, empty to not connect):

Network password:

Static IP (leave at 0.0.0.0 for DHCP):
. @@ -46,10 +41,10 @@ .
mDNS address (leave empty for no mDNS):
- http:// .local
+ http:// .local
Client IP: Not connected

Configure Access Point

- AP SSID (leave empty for no AP):

+ AP SSID (leave empty for no AP):

Hide AP name:
AP password (leave empty for open):

Access Point WiFi channel:
diff --git a/wled00/data/simple.js b/wled00/data/simple.js index 0c484474..79c56cc7 100644 --- a/wled00/data/simple.js +++ b/wled00/data/simple.js @@ -759,15 +759,9 @@ function genPalPrevCss(id) } else { if (selColors) { let e = element[1] - 1; - //if (Array.isArray(selColors[e])) { - r = selColors[e][0]; - g = selColors[e][1]; - b = selColors[e][2]; - //} else { - // r = (selColors[e]>>16) & 0xFF; - // g = (selColors[e]>> 8) & 0xFF; - // b = (selColors[e] ) & 0xFF; - //} + r = selColors[e][0]; + g = selColors[e][1]; + b = selColors[e][2]; } } if (index === false) { @@ -931,18 +925,10 @@ function readState(s,command=false) for (let e = cd.length-1; e >= 0; e--) { var r,g,b,w; - //if (Array.isArray(i.col[e])) { - r = i.col[e][0]; - g = i.col[e][1]; - b = i.col[e][2]; - 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; - //} + r = i.col[e][0]; + g = i.col[e][1]; + b = i.col[e][2]; + if (isRgbw) w = i.col[e][3]; cd[e].style.backgroundColor = "rgb(" + r + "," + g + "," + b + ")"; if (isRgbw) whites[e] = parseInt(w); selectSlot(csel); @@ -993,7 +979,7 @@ function requestJson(command=null) var useWs = (ws && ws.readyState === WebSocket.OPEN); var type = command ? 'post':'get'; 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); var t = gId('tt'); if (t.validity.valid && command.transition==null) { @@ -1007,6 +993,8 @@ function requestJson(command=null) if (useWs) { ws.send(req?req:'{"v":true}'); return; + } else if (command && command.ps) { //refresh UI if we don't use WS (async loading of presets) + setTimeout(requestJson,200); } fetch(url, { diff --git a/wled00/data/style.css b/wled00/data/style.css index 32f9ef3f..fde9f8ba 100644 --- a/wled00/data/style.css +++ b/wled00/data/style.css @@ -1,9 +1,13 @@ +html { + touch-action: manipulation; +} body { font-family: Verdana, sans-serif; + font-size: 1rem; text-align: center; background: #222; color: #fff; - line-height: 200%%; /* %% because of AsyncWebServer */ + line-height: 200%; margin: 0; } hr { @@ -27,6 +31,13 @@ button, .btn { cursor: pointer; text-decoration: none; } +button.sml { + padding: 8px; + border-radius: 20px; + font-size: 15px; + min-width: 40px; + margin: 0 0 0 10px; +} .toprow { top: 0; position: sticky; @@ -50,12 +61,13 @@ input { input:disabled { color: #888; } -input[type="text"] { - font-size: medium; +input[type="text"], +input[type="number"], +select { + font-size: medium; } input[type="number"] { width: 4em; - font-size: medium; margin: 2px; } input[type="number"].xxl { @@ -76,27 +88,29 @@ input[type="number"].s { input[type="number"].xs { width: 40px; } -select { - margin: 2px; - font-size: medium; -} input[type="checkbox"] { transform: scale(1.5); margin-right: 10px; } select { + margin: 2px; background: #333; color: #fff; font-family: Verdana, sans-serif; border: 0.5ch solid #333; } +tr { + line-height: 100%; +} td { padding: 2px; } .d5 { - width: 4.5em !important; + width: 4rem !important; +} +.exp { + transform: rotate(90deg); } - #toast { opacity: 0; background-color: #444; @@ -109,9 +123,9 @@ td { position: fixed; text-align: center; z-index: 5; - transform: translateX(-50%%); /* %% because of AsyncWebServer */ - max-width: 90%%; /* %% because of AsyncWebServer */ - left: 50%%; /* %% because of AsyncWebServer */ + transform: translateX(-50%); + max-width: 90%; + left: 50%; } #toast.show { @@ -125,3 +139,29 @@ td { background-color: #b21; 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; + } +} \ No newline at end of file diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index ca63b4fa..ccc86789 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -194,6 +194,7 @@ int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0); void handlePlaylist(); //presets.cpp +void handlePresets(); bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE); inline bool applyTemporaryPreset() {return applyPreset(255);}; 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 dmxProcessor(const String& var); void serveSettings(AsyncWebServerRequest* request, bool post = false); +void serveSettingsJS(AsyncWebServerRequest* request); //ws.cpp void handleWs(); diff --git a/wled00/html_other.h b/wled00/html_other.h index 2f1c06d7..fbb975d4 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -6,7 +6,15 @@ */ // Autogenerated from wled00/data/usermod.htm, do not edit!! -const char PAGE_usermod[] PROGMEM = R"=====(No usermod custom web page set.)====="; +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!! @@ -35,70 +43,308 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; #endif // Autogenerated from wled00/data/update.htm, do not edit!! -const char PAGE_update[] PROGMEM = R"=====( -WLED Update

WLED Software Update

-Installed version: 0.13.1-bl5
Download the latest binary: -


Updating...
-Please do not close or refresh the page :)
)====="; +const uint16_t PAGE_update_length = 733; +const uint8_t PAGE_update[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x85, 0x54, 0xef, 0x6b, 0xdc, 0x46, + 0x10, 0xfd, 0xae, 0xbf, 0x62, 0xb3, 0xa6, 0x60, 0x43, 0x4e, 0xb2, 0xef, 0x20, 0x14, 0x9d, 0xa4, + 0x50, 0xd7, 0xfe, 0x10, 0x28, 0xc4, 0x90, 0xa4, 0xa5, 0x94, 0x12, 0x56, 0xda, 0x91, 0x34, 0xdc, + 0x6a, 0x57, 0xd9, 0x1d, 0xdd, 0xf9, 0x7a, 0xf8, 0x7f, 0xef, 0x68, 0x65, 0x3b, 0x4e, 0x43, 0xe8, + 0x17, 0xb1, 0x3f, 0xe6, 0xbd, 0x99, 0x79, 0xf3, 0x56, 0xc5, 0xab, 0x9b, 0xf7, 0xbf, 0x7e, 0xfc, + 0xf3, 0xee, 0x56, 0xf4, 0x34, 0x98, 0xaa, 0x78, 0xfc, 0x82, 0xd2, 0x55, 0x31, 0x00, 0x29, 0xd1, + 0x38, 0x4b, 0x60, 0xa9, 0x94, 0x07, 0xd4, 0xd4, 0x97, 0x1a, 0xf6, 0xd8, 0xc0, 0x2a, 0x6e, 0xa4, + 0xb0, 0x6a, 0x80, 0x52, 0xee, 0x11, 0x0e, 0xa3, 0xf3, 0x24, 0xab, 0xa4, 0x20, 0x24, 0x03, 0xd5, + 0x1f, 0xbf, 0xdd, 0xde, 0x88, 0x4f, 0xa3, 0x56, 0x04, 0x45, 0xb6, 0x1c, 0x15, 0xa1, 0xf1, 0x38, + 0x52, 0x95, 0xb4, 0x93, 0x6d, 0x08, 0x9d, 0x15, 0xd7, 0xe7, 0x17, 0xa7, 0x03, 0x5a, 0xed, 0x0e, + 0x69, 0x8f, 0x81, 0x9c, 0x3f, 0xa6, 0xb5, 0x6a, 0x76, 0xe7, 0x17, 0x0f, 0xcf, 0x21, 0x9f, 0x38, + 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!! -const char PAGE_welcome[] PROGMEM = R"=====(Welcome! -

Welcome to WLED!

-Thank you for installing my application!

Next steps:

-Connect the module to your local WiFi here!

-Just trying this out in AP mode?

-)====="; +const uint16_t PAGE_welcome_length = 1528; +const uint8_t PAGE_welcome[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x56, 0x5b, 0x93, 0xaa, 0x3a, + 0x16, 0x7e, 0xef, 0x5f, 0xc1, 0x76, 0xea, 0xd4, 0x79, 0x70, 0x77, 0x73, 0x13, 0x51, 0xdb, 0xee, + 0x19, 0xc5, 0x4b, 0x7b, 0x03, 0x6f, 0x78, 0x7b, 0x0b, 0x10, 0x20, 0x08, 0x04, 0x93, 0x80, 0x97, + 0xae, 0xfe, 0xef, 0x13, 0x74, 0xf7, 0xd4, 0x3e, 0x75, 0x1e, 0xa6, 0x4e, 0x2c, 0x21, 0xf9, 0x92, + 0xb5, 0xd6, 0xb7, 0x92, 0xb8, 0x3e, 0xdb, 0x3f, 0x7a, 0x96, 0xb1, 0xde, 0xcf, 0xfb, 0x42, 0xc8, + 0x92, 0xf8, 0xbd, 0xfd, 0xeb, 0x09, 0x81, 0xf7, 0xde, 0x4e, 0x20, 0x03, 0x82, 0x1b, 0x02, 0x42, + 0x21, 0x7b, 0xab, 0xe4, 0xcc, 0x7f, 0x6e, 0x54, 0x7e, 0xa1, 0x4f, 0x2e, 0x4e, 0x19, 0x4c, 0x39, + 0x7c, 0x46, 0x1e, 0x0b, 0xdf, 0x3c, 0x58, 0x20, 0x17, 0x3e, 0xdf, 0x07, 0x15, 0x21, 0x05, 0x09, + 0x7c, 0xab, 0x14, 0x08, 0x9e, 0x33, 0x4c, 0xd8, 0xb7, 0xcd, 0x03, 0x65, 0x21, 0x4c, 0xe0, 0xb3, + 0x8b, 0x63, 0x4c, 0x2a, 0xbf, 0xb9, 0xf9, 0x97, 0x72, 0x6f, 0x7c, 0x2d, 0x43, 0x2c, 0x86, 0xef, + 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!! -const char PAGE_liveview[] PROGMEM = R"=====( -WLED Live Preview
)====="; +const uint16_t PAGE_liveview_length = 547; +const uint8_t PAGE_liveview[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x53, 0x4d, 0x6f, 0xdb, 0x30, + 0x0c, 0xbd, 0xe7, 0x57, 0x78, 0x2a, 0x5a, 0x48, 0x88, 0x63, 0x3b, 0xc5, 0xba, 0x8f, 0xf8, 0xe3, + 0xb0, 0xb5, 0x87, 0x02, 0x05, 0xd6, 0x43, 0x81, 0x61, 0x18, 0x76, 0x50, 0x24, 0xc6, 0xd6, 0x2a, + 0x4b, 0x81, 0x4c, 0xb9, 0x08, 0xd2, 0xfc, 0xf7, 0xc9, 0x76, 0xd2, 0x62, 0xc0, 0x30, 0xa0, 0x3e, + 0xc8, 0x94, 0xf8, 0xf4, 0x48, 0x3e, 0x52, 0xc5, 0xbb, 0xeb, 0x6f, 0x5f, 0x1f, 0x7e, 0xdc, 0xdf, + 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!! -const char PAGE_liveviewws[] PROGMEM = R"=====( -WLED Live Preview
)====="; +const uint16_t PAGE_liveviewws_length = 683; +const uint8_t PAGE_liveviewws[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x75, 0x54, 0xeb, 0x6f, 0xd3, 0x30, + 0x10, 0xff, 0xde, 0xbf, 0x22, 0x78, 0xa2, 0x8b, 0xd5, 0x34, 0x6d, 0x91, 0x78, 0xe5, 0x51, 0x04, + 0xac, 0x48, 0x4c, 0x13, 0x9b, 0x34, 0xd0, 0x84, 0x10, 0x1f, 0xdc, 0xf8, 0x9a, 0x58, 0x4b, 0xec, + 0x62, 0x5f, 0x1a, 0x55, 0x55, 0xfe, 0x77, 0xce, 0x4d, 0x37, 0x1e, 0x82, 0x7c, 0xc8, 0xc3, 0xbe, + 0xbb, 0xdf, 0xe3, 0xce, 0xc9, 0x9e, 0x5c, 0x5c, 0xbf, 0xff, 0xfc, 0xf5, 0x66, 0x15, 0x54, 0xd8, + 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!! -const char PAGE_404[] PROGMEM = R"=====(Not found -

404 Not Found

Akemi does not know where you are headed...

- -)====="; +const uint16_t PAGE_404_length = 868; +const uint8_t PAGE_404[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x65, 0x54, 0x5b, 0x73, 0xaa, 0x3a, + 0x14, 0x7e, 0xef, 0xaf, 0xe0, 0x78, 0xe6, 0xcc, 0x7e, 0x68, 0x2d, 0xa8, 0xd8, 0x2a, 0xa2, 0x33, + 0x01, 0x51, 0xec, 0xc5, 0x7a, 0xa3, 0xd6, 0xbe, 0x05, 0x12, 0x21, 0x15, 0x08, 0x4d, 0x82, 0x62, + 0x3b, 0xfd, 0xef, 0x3b, 0x40, 0xf7, 0x9c, 0xce, 0xec, 0x35, 0x03, 0x2b, 0xf9, 0x56, 0xd6, 0x7d, + 0x25, 0xe6, 0x3f, 0xe3, 0x27, 0x7b, 0xb3, 0x5b, 0x38, 0x4a, 0x24, 0x92, 0x78, 0x64, 0x7e, 0xff, + 0x31, 0x44, 0x23, 0x33, 0xc1, 0x02, 0x2a, 0x41, 0x04, 0x19, 0xc7, 0x62, 0xd8, 0xc8, 0xc5, 0xbe, + 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!! diff --git a/wled00/html_settings.h b/wled00/html_settings.h index bc3c5d46..82fce57f 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -6,18 +6,71 @@ */ // Autogenerated from wled00/data/style.css, do not edit!! -const char PAGE_settingsCss[] PROGMEM = R"=====()====="; +const uint16_t PAGE_settingsCss_length = 788; +const uint8_t PAGE_settingsCss[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xad, 0x55, 0xed, 0x6e, 0xa3, 0x30, + 0x10, 0x7c, 0x15, 0x4e, 0x51, 0xa5, 0x54, 0x0a, 0x08, 0x08, 0xd0, 0xd4, 0xe8, 0xa4, 0xd3, 0x3d, + 0xc4, 0xe9, 0xa4, 0x53, 0x7f, 0x18, 0xbc, 0x04, 0x2b, 0xc6, 0x46, 0xb6, 0x69, 0x49, 0x11, 0xef, + 0x7e, 0x36, 0x1f, 0x17, 0x92, 0x70, 0xed, 0x9f, 0x2a, 0x4a, 0x84, 0xbd, 0x9b, 0xf5, 0xcc, 0x8e, + 0x67, 0x29, 0x75, 0xc5, 0x3a, 0x2d, 0x9a, 0xbc, 0x74, 0x71, 0xae, 0xa9, 0xe0, 0xa8, 0xc2, 0x9c, + 0xd6, 0x0d, 0xc3, 0x76, 0xd1, 0x67, 0x82, 0x9c, 0xbb, 0x42, 0x70, 0xed, 0x16, 0xb8, 0xa2, 0xec, + 0x8c, 0x7e, 0x81, 0x24, 0x98, 0xe3, 0x9d, 0xc2, 0x5c, 0xb9, 0x0a, 0x24, 0x2d, 0xd2, 0x21, 0xac, + 0xe8, 0x3b, 0xa0, 0x40, 0x42, 0x95, 0x6a, 0x68, 0xb5, 0x8b, 0x19, 0x3d, 0x72, 0x94, 0x03, 0xd7, + 0x20, 0xd3, 0x0c, 0xe7, 0xa7, 0xa3, 0x14, 0x0d, 0x27, 0x68, 0x13, 0x86, 0x61, 0x9a, 0x0b, 0x26, + 0x24, 0xda, 0x14, 0x45, 0x91, 0x32, 0xca, 0xc1, 0x2d, 0x81, 0x1e, 0x4b, 0x8d, 0x42, 0xdf, 0x7f, + 0x48, 0x2b, 0x2c, 0x8f, 0x94, 0x23, 0xbf, 0x2f, 0x65, 0x97, 0x09, 0x49, 0x40, 0xba, 0x53, 0x7a, + 0x92, 0x24, 0x3d, 0xde, 0x61, 0x54, 0x8a, 0x57, 0x90, 0xdd, 0xb4, 0x19, 0x1e, 0x8a, 0xf1, 0x40, + 0x02, 0xb9, 0x90, 0x03, 0x66, 0xc4, 0x05, 0x87, 0xde, 0xcb, 0x34, 0xdf, 0x65, 0x8d, 0xd6, 0x82, + 0x77, 0xcb, 0xf3, 0xf7, 0xfb, 0xfd, 0xf2, 0xfc, 0x4f, 0xa8, 0x8d, 0x08, 0x90, 0xb7, 0xcf, 0x4b, + 0x47, 0x09, 0x46, 0x89, 0x33, 0x14, 0x98, 0x80, 0x49, 0x4c, 0x68, 0xa3, 0x50, 0x18, 0xd5, 0x6d, + 0x4a, 0xa8, 0xaa, 0x19, 0x3e, 0x23, 0xca, 0x07, 0x4a, 0x19, 0x13, 0xf9, 0x69, 0xd1, 0x99, 0xd0, + 0x37, 0x39, 0x13, 0xb7, 0x20, 0xac, 0x5b, 0xe7, 0x30, 0x7e, 0xd3, 0x1a, 0x13, 0x42, 0xf9, 0x11, + 0xd9, 0xb5, 0x0d, 0xa4, 0x15, 0xe5, 0xee, 0x1b, 0x25, 0xba, 0x44, 0x91, 0x8d, 0xe7, 0x8d, 0x54, + 0x06, 0x6c, 0x2d, 0xe8, 0xd0, 0xca, 0x55, 0xae, 0x23, 0x4d, 0x4f, 0x19, 0x25, 0x17, 0xe5, 0x6e, + 0x51, 0x5a, 0x04, 0x0b, 0xa9, 0xe2, 0xeb, 0xb3, 0x16, 0xf8, 0x7c, 0xc7, 0x7e, 0x02, 0xb3, 0xd3, + 0x7b, 0x5a, 0xd4, 0x52, 0xbc, 0x99, 0x0b, 0x52, 0x23, 0x3f, 0xad, 0x85, 0xa2, 0xc3, 0xa9, 0x4a, + 0xd3, 0xfc, 0x74, 0x5e, 0xe8, 0x3a, 0x6b, 0x64, 0xd5, 0x7d, 0x77, 0x29, 0x27, 0xd0, 0xa2, 0xa0, + 0xf7, 0x18, 0x3f, 0x4d, 0x22, 0x1a, 0x41, 0xbd, 0x12, 0x58, 0xfd, 0xb3, 0x5b, 0x5c, 0x0f, 0x06, + 0x85, 0xbe, 0x14, 0xc5, 0x99, 0xe9, 0x70, 0xa3, 0x21, 0x1d, 0x11, 0x25, 0xf6, 0x7c, 0xca, 0xeb, + 0x46, 0x7f, 0x81, 0x7e, 0xf1, 0x95, 0x7e, 0x63, 0x59, 0x64, 0x24, 0xc3, 0x19, 0x03, 0x32, 0xdf, + 0xa5, 0xc3, 0xe1, 0x30, 0x46, 0xfe, 0xe8, 0x73, 0x0d, 0xdf, 0x79, 0x53, 0x65, 0x20, 0x5f, 0x76, + 0x8b, 0x2d, 0x8b, 0xfd, 0x65, 0xa7, 0x80, 0x41, 0xae, 0xbb, 0x4b, 0x2f, 0x2b, 0x30, 0x1d, 0xae, + 0x56, 0xfe, 0xdb, 0x4d, 0xcd, 0x35, 0xae, 0x98, 0x7a, 0x1b, 0xce, 0xa4, 0xae, 0xf2, 0xbc, 0xb6, + 0x65, 0x53, 0x6e, 0xe0, 0xfb, 0xff, 0x49, 0x99, 0x33, 0x0e, 0xf1, 0x7a, 0xc2, 0x1c, 0x4f, 0xa2, + 0xf5, 0x78, 0x35, 0xc5, 0xe3, 0x64, 0x3d, 0xae, 0xba, 0xcb, 0xb5, 0x5b, 0x05, 0xf0, 0x2f, 0xe1, + 0x06, 0x61, 0x5e, 0x42, 0x7e, 0xca, 0x44, 0xfb, 0xd2, 0x69, 0x69, 0x5a, 0x5f, 0x08, 0x59, 0x21, + 0x95, 0x63, 0x06, 0xdb, 0xc0, 0x8b, 0x1f, 0x27, 0xe6, 0xae, 0x1c, 0x3c, 0x3e, 0xdc, 0xaa, 0xa9, + 0x81, 0x97, 0x96, 0xa4, 0x5f, 0xae, 0xb0, 0x96, 0xdd, 0x72, 0xb4, 0x98, 0xae, 0x3e, 0xf4, 0x9a, + 0xfc, 0xb3, 0x87, 0x95, 0xc1, 0x23, 0xf1, 0x4c, 0xc8, 0x8c, 0xad, 0x6f, 0xb4, 0xaa, 0x85, 0xd4, + 0x98, 0xeb, 0xde, 0x83, 0xb6, 0x5e, 0x50, 0x91, 0x42, 0x63, 0x0d, 0xdb, 0x67, 0x9f, 0xc0, 0xf1, + 0xb1, 0xdf, 0x68, 0x81, 0x95, 0xee, 0x44, 0x8d, 0x73, 0xaa, 0xcf, 0xc6, 0x14, 0xf7, 0x26, 0x88, + 0xa2, 0xe8, 0xc6, 0x7b, 0xf1, 0xe0, 0x46, 0x63, 0xd2, 0x6a, 0x50, 0xe7, 0x8e, 0xde, 0xe8, 0xc8, + 0xa7, 0xc5, 0x38, 0x08, 0x12, 0xbb, 0x18, 0x4d, 0xef, 0xc2, 0xab, 0x99, 0xa3, 0x6a, 0xf0, 0xfa, + 0xc5, 0x2e, 0x05, 0x6d, 0x81, 0xac, 0x0c, 0xdb, 0xd9, 0x7e, 0x71, 0x7a, 0xa1, 0x30, 0x3c, 0x99, + 0x89, 0x0e, 0xbf, 0xb7, 0x6e, 0xec, 0x3f, 0x58, 0x4d, 0xda, 0xc9, 0xf8, 0xcf, 0x66, 0xe6, 0x5a, + 0x1f, 0x22, 0xb3, 0x3f, 0x91, 0xf3, 0x54, 0x69, 0x2c, 0x3f, 0x33, 0x0c, 0xd6, 0x6c, 0x9e, 0x44, + 0xa9, 0x79, 0x4b, 0x54, 0xe3, 0x0c, 0x2a, 0x30, 0x01, 0xca, 0x1d, 0x2f, 0x56, 0xbb, 0xcb, 0xa3, + 0x13, 0xda, 0x1f, 0x69, 0xa0, 0x4b, 0x05, 0x73, 0x61, 0x90, 0x52, 0xc8, 0x0f, 0x2b, 0x67, 0x61, + 0xb0, 0x5a, 0xb9, 0xff, 0x61, 0x4d, 0x86, 0x1d, 0x95, 0x4b, 0x00, 0xee, 0x60, 0x4e, 0x9c, 0xed, + 0x85, 0xc4, 0x53, 0x62, 0x7a, 0xf7, 0xd8, 0x2d, 0xee, 0x24, 0x54, 0x98, 0xb2, 0x2b, 0xef, 0x16, + 0x94, 0xc1, 0xd5, 0xc6, 0x8a, 0xbf, 0x6b, 0xac, 0xd4, 0x9b, 0x51, 0xee, 0xc6, 0xf4, 0xec, 0x7e, + 0x08, 0x2c, 0xdc, 0x6f, 0xa5, 0xea, 0x3f, 0xc6, 0x17, 0x1d, 0xfc, 0x1b, 0x7c, 0x77, 0xa6, 0xf3, + 0x3f, 0x31, 0xdd, 0xfe, 0x66, 0x72, 0x0c, 0x74, 0xa6, 0x58, 0x68, 0xdf, 0x40, 0xfd, 0xc6, 0xbc, + 0xf1, 0x94, 0x33, 0x79, 0x6b, 0x1a, 0x26, 0x91, 0x0d, 0xf4, 0x7f, 0x01, 0xa1, 0x0b, 0x67, 0x1b, + 0xe4, 0x07, 0x00, 0x00 +}; // Autogenerated from wled00/data/settings.htm, do not edit!! -const char PAGE_settings[] PROGMEM = R"=====(WLED Settings -
%DMXMENU%
)====="; // Autogenerated from wled00/data/settings_wifi.htm, do not edit!! -const char PAGE_settings_wifi[] PROGMEM = R"=====(WiFi Settings")); return String(buf); } - +*/ #ifdef WLED_ENABLE_DMX - if (var == "DMXMENU") { return String(F("
")); } - #endif - if (var == "SCSS") return String(FPSTR(PAGE_settingsCss)); + //if (var == "SCSS") return String(FPSTR(PAGE_settingsCss)); 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) { byte subPage = 0; const String& url = request->url(); 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("ui") > 0) subPage = 3; else if (url.indexOf("sync") > 0) subPage = 4; @@ -502,17 +536,24 @@ void serveSettings(AsyncWebServerRequest* request, bool post) optionType = subPage; + AsyncWebServerResponse *response; switch (subPage) { - case 1: request->send_P(200, "text/html", PAGE_settings_wifi, settingsProcessor); break; - case 2: request->send_P(200, "text/html", PAGE_settings_leds, settingsProcessor); break; - case 3: request->send_P(200, "text/html", PAGE_settings_ui , settingsProcessor); break; - case 4: request->send_P(200, "text/html", PAGE_settings_sync, settingsProcessor); break; - case 5: request->send_P(200, "text/html", PAGE_settings_time, settingsProcessor); break; - case 6: request->send_P(200, "text/html", PAGE_settings_sec , settingsProcessor); break; - case 7: request->send_P(200, "text/html", PAGE_settings_dmx , settingsProcessor); break; - case 8: request->send_P(200, "text/html", PAGE_settings_um , settingsProcessor); break; - case 255: request->send_P(200, "text/html", PAGE_welcome); break; - default: request->send_P(200, "text/html", PAGE_settings , settingsProcessor); + case 1: response = request->beginResponse_P(200, "text/html", PAGE_settings_wifi, PAGE_settings_wifi_length); break; + case 2: response = request->beginResponse_P(200, "text/html", PAGE_settings_leds, PAGE_settings_leds_length); break; + case 3: response = request->beginResponse_P(200, "text/html", PAGE_settings_ui, PAGE_settings_ui_length); break; + case 4: response = request->beginResponse_P(200, "text/html", PAGE_settings_sync, PAGE_settings_sync_length); break; + case 5: response = request->beginResponse_P(200, "text/html", PAGE_settings_time, PAGE_settings_time_length); break; + case 6: response = request->beginResponse_P(200, "text/html", PAGE_settings_sec, PAGE_settings_sec_length); break; + case 7: response = request->beginResponse_P(200, "text/html", PAGE_settings_dmx, PAGE_settings_dmx_length); break; + case 8: response = request->beginResponse_P(200, "text/html", PAGE_settings_um, PAGE_settings_um_length); break; + case 253: response = request->beginResponse_P(200, "text/css", PAGE_settingsCss, PAGE_settingsCss_length); break; + 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); } diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 451c15cb..79b67656 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -37,11 +37,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp bool verboseResponse = false; { //scope JsonDocument so it releases its buffer DEBUG_PRINTLN(F("WS JSON receive buffer requested.")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else - if (!requestJSONBufferLock(15)) return; - #endif + if (!requestJSONBufferLock(11)) return; DeserializationError error = deserializeJson(doc, data, len); JsonObject root = doc.as(); @@ -49,13 +45,6 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp releaseJSONBufferLock(); return; } - /* - #ifdef WLED_DEBUG - DEBUG_PRINT(F("Incoming WS: ")); - serializeJson(root,Serial); - DEBUG_PRINTLN(); - #endif - */ if (root["v"] && root.size() == 1) { //if the received value is just "{"v":true}", send only to this client verboseResponse = true; @@ -63,15 +52,13 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp { wsLiveClientId = root["lv"] ? client->id() : 0; } else { - //fileDoc = &doc; // used for applying presets (presets.cpp) verboseResponse = deserializeState(root); - //fileDoc = nullptr; if (!interfaceUpdateCallMode) { //special case, only on playlist load, avoid sending twice in rapid succession if (millis() - lastInterfaceUpdate > 1700) verboseResponse = false; } } - releaseJSONBufferLock(); + releaseJSONBufferLock(); // will clean fileDoc } //update if it takes longer than 300ms until next "broadcast" if (verboseResponse && (millis() - lastInterfaceUpdate < 1700 || !interfaceUpdateCallMode)) sendDataWs(client); @@ -114,11 +101,7 @@ void sendDataWs(AsyncWebSocketClient * client) { //scope JsonDocument so it releases its buffer DEBUG_PRINTLN(F("WS JSON send buffer requested.")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else - if (!requestJSONBufferLock(16)) return; - #endif + if (!requestJSONBufferLock(12)) return; JsonObject state = doc.createNestedObject("state"); serializeState(state); @@ -126,18 +109,14 @@ void sendDataWs(AsyncWebSocketClient * client) serializeInfo(info); DEBUG_PRINTF("JSON buffer size: %u for WS request.\n", doc.memoryUsage()); size_t len = measureJson(doc); - buffer = ws.makeBuffer(len); - if (!buffer) { + size_t heap1 = ESP.getFreeHeap(); + buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes + size_t heap2 = ESP.getFreeHeap(); + if (!buffer || heap1-heap2get(), len +1); releaseJSONBufferLock(); } @@ -155,10 +134,13 @@ void handleWs() { if (millis() - wsLastLiveTime > WS_LIVE_INTERVAL) { + #ifdef ESP8266 + ws.cleanupClients(2); + #else ws.cleanupClients(); + #endif bool success = true; - if (wsLiveClientId) - success = serveLiveLeds(nullptr, wsLiveClientId); + if (wsLiveClientId) success = serveLiveLeds(nullptr, wsLiveClientId); wsLastLiveTime = millis(); if (!success) wsLastLiveTime -= 20; //try again in 20ms if failed due to non-empty WS queue } diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 5c75a15a..79beaf77 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -249,21 +249,18 @@ void getSettingsJS(byte subPage, char* dest) { char nS[8]; + // Pin reservations will become unnecessary when settings pages will read cfg.json directly // add reserved and usermod pins as d.um_p array oappend(SET_F("d.um_p=[6,7,8,9,10,11")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(2048); // 2k is enough for usermods - #else - if (!requestJSONBufferLock(17)) return; - #endif - - JsonObject mods = doc.createNestedObject(F("um")); - usermods.addToConfig(mods); - if (!mods.isNull()) fillUMPins(mods); - - releaseJSONBufferLock(); + if (requestJSONBufferLock(6)) { + // if we can't allocate JSON buffer ignore usermod pins + JsonObject mods = doc.createNestedObject(F("um")); + usermods.addToConfig(mods); + if (!mods.isNull()) fillUMPins(mods); + releaseJSONBufferLock(); + } #ifdef WLED_ENABLE_DMX 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("CR"),cctFromRgb); sappend('v',SET_F("CB"),strip.cctBlending); + sappend('v',SET_F("FR"),strip.getTargetFps()); sappend('v',SET_F("AW"),Bus::getAutoWhiteMode()); 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("RC"),receiveNotificationColor); sappend('c',SET_F("RX"),receiveNotificationEffects); + sappend('c',SET_F("SO"),receiveSegmentOptions); sappend('c',SET_F("SD"),notifyDirectDefault); sappend('c',SET_F("SB"),notifyButton); 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] = 'T'; sappend('v',k,timerMacro[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()); oappend(";"); } - - oappend(SET_F("}")); }