diff --git a/CHANGELOG.md b/CHANGELOG.md index 4581c0ab..f3af30f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,45 @@ ### Development versions after the 0.10.2 release +#### Build 2011154 + +- Fixed RGBW saved incorrectly +- Fixed pmt caching requesting /presets.json too often +- Fixed deEEP not copying the first segment of EEPROM preset 16 + +#### Build 2011153 + +- Fixed an ESP32 end-of-file issue +- Fixed useRGBW not read from cfg.json + +#### Build 2011152 + +- Version bump to 0.11.0p "Mirai" +- Increased max. num of segments to 12 (ESP8266) / 16 (ESP32) +- Up to 250 presets stored in the `presets.json` file in filesystem +- Complete overhaul of the Presets UI tab +- Updated iro.js to v5 (fixes black color wheel) +- Added white temperature slider to color wheel +- Add JSON settings serialization/deserialization to cfg.json and wsec.json +- Added deEEP to convert the EEPROM settings and presets to files +- Playlist support - JSON only for now +- New v2 usermod methods `addToConfig()` and `readFromConfig()` (see EXAMPLE_v2 for doc) +- Added Ethernet support for ESP32 (PR #1316) +- IP addresses are now handled by the `Network` class +- New `esp32_poe` PIO environment +- Use EspAsyncWebserver Aircoookie fork v.2.0.0 (hiding wsec.json) +- Removed `WLED_DISABLE_FILESYSTEM` and `WLED_ENABLE_FS_SERVING` defines as they are now required +- Added pin manager +- UI performance improvements (no drop shadows) +- More explanatory error messages in UI +- Improved candle brightness +- Return remaining nightlight time `nl.rem` in JSON API (PR #1302) +- Added gamma calculation (yet unused) +- Added LED type definitions to const.h (yet unused) +- Added nicer 404 page +- Removed `NP` and `MS=` macro HTTP API commands +- Removed macros from Time settings + #### Build 2011120 - Added the ability for the /api MQTT topic to receive JSON API payloads diff --git a/package.json b/package.json index b0aa7ceb..4e431090 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.10.2", + "version": "0.11.0p", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index e95f27f1..070e1c1d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -138,6 +138,8 @@ build_flags_esp32 = ${common.build_flags} ${esp32.build_flags} ldscript_512k = eagle.flash.512k.ld ;for older versions change this to eagle.flash.512k0.ld ldscript_1m0m = eagle.flash.1m.ld ;for older versions change this to eagle.flash.1m0.ld +ldscript_1m128k = eagle.flash.1m128.ld +ldscript_2m512k = eagle.flash.2m512.ld ldscript_2m1m = eagle.flash.2m1m.ld ldscript_4m1m = eagle.flash.4m1m.ld @@ -185,19 +187,20 @@ lib_extra_dirs = ./wled00/src lib_compat_mode = strict lib_deps = FastLED@3.3.2 - #NeoPixelBus@2.5.7 - https://github.com/Makuna/NeoPixelBus + NeoPixelBus@2.6.0 ESPAsyncTCP@1.2.0 ESPAsyncUDP AsyncTCP@1.0.3 - https://github.com/Aircoookie/ESPAsyncWebServer IRremoteESP8266@2.7.3 + https://github.com/lorol/LITTLEFS.git + https://github.com/Aircoookie/ESPAsyncWebServer.git@~2.0.0 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For use SSD1306 OLED display uncomment following #U8g2@~2.27.2 #For Dallas sensor uncomment following 2 lines #OneWire@~2.3.5 + #milesburton/DallasTemperature@^3.9.0 #For BME280 sensor uncomment following #BME280@~3.0.0 lib_ignore = @@ -241,7 +244,7 @@ build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_ALEXA -D WLED_DISABL board = esp01_1m platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} -board_build.ldscript = ${common.ldscript_1m0m} +board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA @@ -261,6 +264,7 @@ upload_speed = 921600 board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} +monitor_filters = esp8266_exception_decoder [env:heltec_wifi_kit_8] board = d1_mini @@ -455,7 +459,7 @@ build_flags = ${common.build_flags_esp8266} ${common.debug_flags} ${common.build [env:travis_esp32] extends = env:esp32dev -build_type = debug +; build_type = debug build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} ${common.debug_flags} ${common.build_flags_all_features} diff --git a/readme.md b/readme.md index e85f98ec..866db4aa 100644 --- a/readme.md +++ b/readme.md @@ -22,19 +22,20 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control - Settings page - configuration over network - Access Point and station mode - automatic failsafe AP - Support for RGBW strips -- 16 user presets to save and load colors/effects easily, supports cycling through them. -- Macro functions to automatically execute API calls +- Up to 250 user presets to save and load colors/effects easily, supports cycling through them. +- Presets can be used to automatically execute API calls - Nightlight function (gradually dims down) - Full OTA software updatability (HTTP + ArduinoOTA), password protectable - Configurable analog clock + support for the Cronixie kit by Diamex - Configurable Auto Brightness limit for safer operation +- Filesystem-based config for easier backup of presets and settings ## 💡 Supported light control interfaces - WLED app for [Android](https://play.google.com/store/apps/details?id=com.aircoookie.WLED) and [iOS](https://apps.apple.com/us/app/wled/id1475695033) - JSON and HTTP request APIs - MQTT - Blynk IoT -- E1.31, Art-Net and TPM2.net +- E1.31, Art-Net, DDP and TPM2.net - [Hyperion](https://github.com/hyperion-project/hyperion.ng) - UDP realtime - Alexa voice control (including dimming and color) diff --git a/tools/cdata.js b/tools/cdata.js index 584dbdad..aa473deb 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -386,6 +386,14 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; method: "plaintext", filter: "html-minify", }, + { + file: "404.htm", + name: "PAGE_404", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + }, { file: "favicon.ico", name: "favicon", diff --git a/usermods/EXAMPLE_v2/usermod_v2_example.h b/usermods/EXAMPLE_v2/usermod_v2_example.h index e67ef8da..ef68e907 100644 --- a/usermods/EXAMPLE_v2/usermod_v2_example.h +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -103,7 +103,43 @@ class MyExampleUsermod : public Usermod { 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() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will also not yet add your setting to one of the settings pages automatically. + * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject("exampleUsermod"); + top["great"] = userVar0; //save this var persistently whenever settings are saved + } + + + /* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + */ + void readFromConfig(JsonObject& root) + { + JsonObject top = root["top"]; + userVar0 = top["great"] | 42; //The value right of the pipe "|" is the default value in case your setting was not present in cfg.json (e.g. first boot) + } + /* * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5ad5de2e..bac929ff 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2726,7 +2726,7 @@ uint16_t WS2812FX::candle(bool multi) //max. flicker range controlled by intensity uint8_t valrange = SEGMENT.intensity; - uint8_t rndval = valrange >> 1; + uint8_t rndval = valrange >> 1; //max 127 //step (how much to move closer to target per frame) coarsely set by speed uint8_t speedFactor = 4; @@ -2763,9 +2763,9 @@ uint16_t WS2812FX::candle(bool multi) } if (newTarget) { - s_target = random8(rndval) + random8(rndval); + s_target = random8(rndval) + random8(rndval); //between 0 and rndval*2 -2 = 252 if (s_target < (rndval >> 1)) s_target = (rndval >> 1) + random8(rndval); - uint8_t offset = (255 - valrange) >> 1; + uint8_t offset = (255 - valrange); s_target += offset; uint8_t dif = (s_target > s) ? s_target - s : s - s_target; diff --git a/wled00/FX.h b/wled00/FX.h index 75d642cf..7ec99bfd 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -54,9 +54,9 @@ /* each segment uses 52 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ #ifdef ESP8266 - #define MAX_NUM_SEGMENTS 10 + #define MAX_NUM_SEGMENTS 12 #else - #define MAX_NUM_SEGMENTS 10 + #define MAX_NUM_SEGMENTS 16 #endif /* How much data bytes all segments combined may allocate */ @@ -456,6 +456,7 @@ class WS2812FX { setRange(uint16_t i, uint16_t i2, uint32_t col), setShowCallback(show_callback cb), setTransitionMode(bool t), + calcGammaTable(float), trigger(void), setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0), resetSegments(), @@ -489,6 +490,7 @@ class WS2812FX { //getFirstSelectedSegment(void), getMainSegmentId(void), gamma8(uint8_t), + gamma8_cal(uint8_t, float), get_random_wheel_index(uint8_t); int8_t diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index ad459aba..099f77cd 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -965,8 +965,8 @@ void WS2812FX::setRgbwPwm(void) { void WS2812FX::setRgbwPwm() {} #endif -//gamma 2.4 lookup table used for color correction -const byte gammaT[] = { +//gamma 2.8 lookup table used for color correction +byte gammaT[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, @@ -984,6 +984,17 @@ const byte gammaT[] = { 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; +uint8_t WS2812FX::gamma8_cal(uint8_t b, float gamma) { + return (int)(pow((float)b / 255.0, gamma) * 255 + 0.5); +} + +void WS2812FX::calcGammaTable(float gamma) +{ + for (uint16_t i = 0; i < 256; i++) { + gammaT[i] = gamma8_cal(i, gamma); + } +} + uint8_t WS2812FX::gamma8(uint8_t b) { return gammaT[b]; diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index 05e46093..d97f0804 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -46,7 +46,7 @@ void onAlexaChange(EspalexaDevice* dev) bri = briLast; colorUpdated(NOTIFIER_CALL_MODE_ALEXA); } - } else applyMacro(macroAlexaOn); + } else applyPreset(macroAlexaOn); } else if (m == EspalexaDeviceProperty::off) { if (!macroAlexaOff) @@ -57,7 +57,7 @@ void onAlexaChange(EspalexaDevice* dev) bri = 0; colorUpdated(NOTIFIER_CALL_MODE_ALEXA); } - } else applyMacro(macroAlexaOff); + } else applyPreset(macroAlexaOff); } else if (m == EspalexaDeviceProperty::bri) { bri = espalexaDevice->getValue(); diff --git a/wled00/button.cpp b/wled00/button.cpp index 280a571a..249351c6 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -11,7 +11,7 @@ void shortPressAction() toggleOnOff(); colorUpdated(NOTIFIER_CALL_MODE_BUTTON); } else { - applyMacro(macroButton); + applyPreset(macroButton); } } @@ -41,7 +41,7 @@ void handleButton() { if (!buttonLongPressed) { - if (macroLongPress) {applyMacro(macroLongPress);} + if (macroLongPress) {applyPreset(macroLongPress);} else _setRandomColor(false,true); buttonLongPressed = true; @@ -62,7 +62,7 @@ void handleButton() else if (!buttonLongPressed) { //short press if (macroDoublePress) { - if (doublePress) applyMacro(macroDoublePress); + if (doublePress) applyPreset(macroDoublePress); else buttonWaitTime = millis(); } else shortPressAction(); } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp new file mode 100644 index 00000000..07afdf60 --- /dev/null +++ b/wled00/cfg.cpp @@ -0,0 +1,680 @@ +#include "wled.h" + +/* + * Serializes and parses the cfg.json and wsec.json settings files, stored in internal FS. + * The structure of the JSON is not to be considered an official API and may change without notice. + */ + +//simple macro for ArduinoJSON's or syntax +#define CJSON(a,b) a = b | a + +void getStringFromJson(char* dest, const char* src, size_t len) { + if (src != nullptr) strlcpy(dest, src, len); +} + +void deserializeConfig() { + bool fromeep = false; + bool success = deserializeConfigSec(); + if (!success) { //if file does not exist, try reading from EEPROM + deEEPSettings(); + fromeep = true; + } + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); + + success = readObjectFromFile("/cfg.json", nullptr, &doc); + if (!success) { //if file does not exist, try reading from EEPROM + if (!fromeep) deEEPSettings(); + return; + } + + //deserializeJson(doc, json); + + //int rev_major = doc[F("rev")][0]; // 1 + //int rev_minor = doc[F("rev")][1]; // 0 + + //long vid = doc[F("vid")]; // 2010020 + + JsonObject id = doc[F("id")]; + getStringFromJson(cmDNS, id[F("mdns")], 33); + getStringFromJson(serverDescription, id[F("name")], 33); + getStringFromJson(alexaInvocationName, id[F("inv")], 33); + + JsonObject nw_ins_0 = doc["nw"][F("ins")][0]; + getStringFromJson(clientSSID, nw_ins_0[F("ssid")], 33); + //int nw_ins_0_pskl = nw_ins_0[F("pskl")]; + //The WiFi PSK is normally not contained in the regular file for security reasons. + //If it is present however, we will use it + getStringFromJson(clientPass, nw_ins_0["psk"], 65); + + JsonArray nw_ins_0_ip = nw_ins_0[F("ip")]; + JsonArray nw_ins_0_gw = nw_ins_0[F("gw")]; + JsonArray nw_ins_0_sn = nw_ins_0[F("sn")]; + + for (byte i = 0; i < 4; i++) { + CJSON(staticIP[i], nw_ins_0_ip[i]); + CJSON(staticGateway[i], nw_ins_0_gw[i]); + CJSON(staticSubnet[i], nw_ins_0_sn[i]); + } + + JsonObject ap = doc[F("ap")]; + getStringFromJson(apSSID, ap[F("ssid")], 33); + getStringFromJson(apPass, ap["psk"] , 65); //normally not present due to security + //int ap_pskl = ap[F("pskl")]; + + CJSON(apChannel, ap[F("chan")]); + if (apChannel > 13 || apChannel < 1) apChannel = 1; + + CJSON(apHide, ap[F("hide")]); + if (apHide > 1) apHide = 1; + + CJSON(apBehavior, ap[F("behav")]); + + /* + JsonArray ap_ip = ap[F("ip")]; + for (byte i = 0; i < 4; i++) { + apIP[i] = ap_ip; + }*/ + + noWifiSleep = doc[F("wifi")][F("sleep")] | !noWifiSleep; // inverted + noWifiSleep = !noWifiSleep; + //int wifi_phy = doc[F("wifi")][F("phy")]; //force phy mode n? + + JsonObject hw = doc[F("hw")]; + + JsonObject hw_led = hw[F("led")]; + CJSON(ledCount, hw_led[F("total")]); + if (ledCount > MAX_LEDS) ledCount = MAX_LEDS; + + CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); + CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); + CJSON(strip.reverseMode, hw_led[F("rev")]); + + JsonObject hw_led_ins_0 = hw_led[F("ins")][0]; + //bool hw_led_ins_0_en = hw_led_ins_0[F("en")]; // true + //int hw_led_ins_0_start = hw_led_ins_0[F("start")]; // 0 + //int hw_led_ins_0_len = hw_led_ins_0[F("len")]; // 1200 + + //int hw_led_ins_0_pin_0 = hw_led_ins_0[F("pin")][0]; // 2 + + strip.colorOrder = hw_led_ins_0[F("order")]; + //bool hw_led_ins_0_rev = hw_led_ins_0[F("rev")]; // false + skipFirstLed = hw_led_ins_0[F("skip")]; // 0 + useRGBW = (hw_led_ins_0[F("type")] == TYPE_SK6812_RGBW); + + JsonObject hw_btn_ins_0 = hw[F("btn")][F("ins")][0]; + buttonEnabled = hw_btn_ins_0[F("en")] | buttonEnabled; + + //int hw_btn_ins_0_pin_0 = hw_btn_ins_0[F("pin")][0]; // 0 + + JsonArray hw_btn_ins_0_macros = hw_btn_ins_0[F("macros")]; + CJSON(macroButton, hw_btn_ins_0_macros[0]); + CJSON(macroLongPress,hw_btn_ins_0_macros[1]); + CJSON(macroDoublePress, hw_btn_ins_0_macros[2]); + + //int hw_btn_ins_0_type = hw_btn_ins_0[F("type")]; // 0 + + //int hw_ir_pin = hw[F("ir")][F("pin")]; // 4 + CJSON(irEnabled, hw[F("ir")][F("type")]); // 0 + + //int hw_relay_pin = hw[F("relay")][F("pin")]; // 12 + //bool hw_relay_rev = hw[F("relay")][F("rev")]; // false + + //int hw_status_pin = hw[F("status")][F("pin")]; // -1 + + JsonObject light = doc[F("light")]; + CJSON(briMultiplier, light[F("scale-bri")]); + CJSON(strip.paletteBlend, light[F("pal-mode")]); + + float light_gc_bri = light[F("gc")]["bri"]; + float light_gc_col = light[F("gc")][F("col")]; // 2.8 + if (light_gc_bri > 1.5) strip.gammaCorrectBri = true; + else if (light_gc_bri > 0.5) strip.gammaCorrectBri = false; + if (light_gc_col > 1.5) strip.gammaCorrectCol = true; + else if (light_gc_col > 0.5) strip.gammaCorrectCol = false; + + JsonObject light_tr = light[F("tr")]; + CJSON(fadeTransition, light_tr[F("mode")]); + int tdd = light_tr[F("dur")] | -1; + if (tdd >= 0) transitionDelayDefault = tdd * 100; + CJSON(strip.paletteFade, light_tr[F("pal")]); + + JsonObject light_nl = light["nl"]; + CJSON(nightlightMode, light_nl[F("mode")]); + CJSON(nightlightDelayMinsDefault, light_nl[F("dur")]); + nightlightDelayMins = nightlightDelayMinsDefault; + + CJSON(nightlightTargetBri, light_nl[F("tbri")]); + CJSON(macroNl, light_nl[F("macro")]); + + JsonObject def = doc[F("def")]; + CJSON(bootPreset, def[F("ps")]); + CJSON(turnOnAtBoot, def["on"]); // true + CJSON(briS, def["bri"]); // 128 + if (briS == 0) briS = 255; + + JsonObject def_cy = def[F("cy")]; + CJSON(presetCyclingEnabled, def_cy["on"]); + + CJSON(presetCycleMin, def_cy[F("range")][0]); + CJSON(presetCycleMax, def_cy[F("range")][1]); + + tdd = def_cy[F("dur")] | -1; + if (tdd >= 0) presetCycleTime = tdd * 100; + + JsonObject interfaces = doc["if"]; + + JsonObject if_sync = interfaces[F("sync")]; + CJSON(udpPort, if_sync[F("port0")]); // 21324 + CJSON(udpPort2, if_sync[F("port1")]); // 65506 + + JsonObject if_sync_recv = if_sync[F("recv")]; + CJSON(receiveNotificationBrightness, if_sync_recv["bri"]); + CJSON(receiveNotificationColor, if_sync_recv[F("col")]); + CJSON(receiveNotificationEffects, if_sync_recv[F("fx")]); + receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); + + JsonObject if_sync_send = if_sync[F("send")]; + CJSON(notifyDirectDefault, if_sync_send[F("dir")]); + notifyDirect = notifyDirectDefault; + CJSON(notifyButton, if_sync_send[F("btn")]); + CJSON(notifyAlexa, if_sync_send[F("va")]); + CJSON(notifyHue, if_sync_send[F("hue")]); + CJSON(notifyMacro, if_sync_send[F("macro")]); + CJSON(notifyTwice, if_sync_send[F("twice")]); + + JsonObject if_live = interfaces[F("live")]; + CJSON(receiveDirect, if_live[F("en")]); + CJSON(e131Port, if_live[F("port")]); // 5568 + CJSON(e131Multicast, if_live[F("mc")]); + + JsonObject if_live_dmx = if_live[F("dmx")]; + CJSON(e131Universe, if_live_dmx[F("uni")]); + CJSON(e131SkipOutOfSequence, if_live_dmx[F("seqskip")]); + CJSON(DMXAddress, if_live_dmx[F("addr")]); + CJSON(DMXMode, if_live_dmx[F("mode")]); + + tdd = if_live[F("timeout")] | -1; + if (tdd >= 0) realtimeTimeoutMs = tdd * 100; + CJSON(arlsForceMaxBri, if_live[F("maxbri")]); + CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false + CJSON(arlsOffset, if_live[F("offset")]); // 0 + + CJSON(alexaEnabled, interfaces[F("va")][F("alexa")]); // false + + CJSON(macroAlexaOn, interfaces[F("va")][F("macros")][0]); + CJSON(macroAlexaOff, interfaces[F("va")][F("macros")][1]); + + const char* apikey = interfaces[F("blynk")][F("token")] | "Hidden"; + tdd = strnlen(apikey, 36); + if (tdd > 20 || tdd == 0) + getStringFromJson(blynkApiKey, apikey, 36); //normally not present due to security + + JsonObject if_mqtt = interfaces[F("mqtt")]; + CJSON(mqttEnabled, if_mqtt[F("en")]); + getStringFromJson(mqttServer, if_mqtt[F("broker")], 33); + CJSON(mqttPort, if_mqtt[F("port")]); // 1883 + getStringFromJson(mqttUser, if_mqtt[F("user")], 41); + getStringFromJson(mqttPass, if_mqtt["psk"], 41); //normally not present due to security + getStringFromJson(mqttClientID, if_mqtt[F("cid")], 41); + + getStringFromJson(mqttDeviceTopic, if_mqtt[F("topics")][F("device")], 33); // "wled/test" + getStringFromJson(mqttGroupTopic, if_mqtt[F("topics")][F("group")], 33); // "" + + JsonObject if_hue = interfaces[F("hue")]; + CJSON(huePollingEnabled, if_hue[F("en")]); + CJSON(huePollLightId, if_hue[F("id")]); + tdd = if_hue[F("iv")] | -1; + if (tdd >= 2) huePollIntervalMs = tdd * 100; + + JsonObject if_hue_recv = if_hue[F("recv")]; + CJSON(hueApplyOnOff, if_hue_recv["on"]); + CJSON(hueApplyBri, if_hue_recv["bri"]); + CJSON(hueApplyColor, if_hue_recv[F("col")]); + + JsonArray if_hue_ip = if_hue[F("ip")]; + + for (byte i = 0; i < 4; i++) + CJSON(hueIP[i], if_hue_ip[i]); + + JsonObject if_ntp = interfaces[F("ntp")]; + CJSON(ntpEnabled, if_ntp[F("en")]); + getStringFromJson(ntpServerName, if_ntp[F("host")], 33); // "1.wled.pool.ntp.org" + CJSON(currentTimezone, if_ntp[F("tz")]); + CJSON(utcOffsetSecs, if_ntp[F("offset")]); + CJSON(useAMPM, if_ntp[F("ampm")]); + + JsonObject ol = doc[F("ol")]; + CJSON(overlayDefault ,ol[F("clock")]); // 0 + CJSON(countdownMode, ol[F("cntdwn")]); + overlayCurrent = overlayDefault; + + JsonArray ol_cntdwn = ol[F("cntdwn")]; //[20,12,31,23,59,59] + + //timed macro rules + JsonObject tm = doc[F("timers")]; + JsonObject cntdwn = tm[F("cntdwn")]; + JsonArray cntdwn_goal = cntdwn[F("goal")]; + CJSON(countdownYear, cntdwn_goal[0]); + CJSON(countdownMonth, cntdwn_goal[1]); + CJSON(countdownDay, cntdwn_goal[2]); + CJSON(countdownHour, cntdwn_goal[3]); + CJSON(countdownMin, cntdwn_goal[4]); + CJSON(countdownSec, cntdwn_goal[5]); + CJSON(macroCountdown, cntdwn[F("macro")]); + + JsonArray timers = tm[F("ins")]; + uint8_t it = 0; + for (JsonObject timer : timers) { + if (it > 7) break; + CJSON(timerHours[it], timer[F("hour")]); + CJSON(timerMinutes[it], timer[F("min")]); + CJSON(timerMacro[it], timer[F("macro")]); + + byte dowPrev = timerWeekday[it]; + bool actPrev = timerWeekday[it] & 0x01; + CJSON(timerWeekday[it], timer[F("dow")]); + if (timerWeekday[it] != dowPrev) { //present in JSON + timerWeekday[it] <<= 1; //add active bit + bool act = timer[F("en")] | actPrev; + if (act) timerWeekday[it]++; + } + + it++; + } + + JsonObject ota = doc["ota"]; + const char* pwd = ota["psk"]; //normally not present due to security + + bool pwdCorrect = !otaLock; //always allow access if ota not locked + if (pwd != nullptr && strncmp(otaPass, pwd, 33) == 0) pwdCorrect = true; + + if (pwdCorrect) { //only accept these values from cfg.json if ota is unlocked (else from wsec.json) + CJSON(otaLock, ota[F("lock")]); + CJSON(wifiLock, ota[F("lock-wifi")]); + CJSON(aOtaEnabled, ota[F("aota")]); + getStringFromJson(otaPass, pwd, 33); //normally not present due to security + } + + #ifdef WLED_ENABLE_DMX + JsonObject dmx = doc["dmx"]; + CJSON(DMXChannels, dmx[F("chan")]); + CJSON(DMXGap,dmx[F("gap")]); + CJSON(DMXStart, dmx[F("start")]); + CJSON(DMXStartLED,dmx[F("start-led")]); + + JsonArray dmx_fixmap = dmx.createNestedArray("fixmap"); + it = 0; + for (int i : dmx_fixmap) { + if (it > 14) break; + DMXFixtureMap[i] = i; + it++; + } + #endif + + JsonObject usermods_settings = doc["um"]; + usermods.readFromConfig(usermods_settings); +} + +void serializeConfig() { + serializeConfigSec(); + + DEBUG_PRINTLN(F("Writing settings to /cfg.json...")); + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + //{ //scope this to reduce stack size + JsonArray rev = doc.createNestedArray("rev"); + rev.add(1); //major settings revision + rev.add(0); //minor settings revision + + doc[F("vid")] = VERSION; + + JsonObject id = doc.createNestedObject("id"); + id[F("mdns")] = cmDNS; + id[F("name")] = serverDescription; + id[F("inv")] = alexaInvocationName; + + JsonObject nw = doc.createNestedObject("nw"); + + JsonArray nw_ins = nw.createNestedArray("ins"); + + JsonObject nw_ins_0 = nw_ins.createNestedObject(); + nw_ins_0[F("ssid")] = clientSSID; + nw_ins_0[F("pskl")] = strlen(clientPass); + + JsonArray nw_ins_0_ip = nw_ins_0.createNestedArray("ip"); + JsonArray nw_ins_0_gw = nw_ins_0.createNestedArray("gw"); + JsonArray nw_ins_0_sn = nw_ins_0.createNestedArray("sn"); + + for (byte i = 0; i < 4; i++) { + nw_ins_0_ip.add(staticIP[i]); + nw_ins_0_gw.add(staticGateway[i]); + nw_ins_0_sn.add(staticSubnet[i]); + } + + JsonObject ap = doc.createNestedObject("ap"); + ap[F("ssid")] = apSSID; + ap[F("pskl")] = strlen(apPass); + ap[F("chan")] = apChannel; + ap[F("behav")] = apBehavior; + + JsonArray ap_ip = ap.createNestedArray("ip"); + ap_ip.add(4); + ap_ip.add(3); + ap_ip.add(2); + ap_ip.add(1); + + JsonObject wifi = doc.createNestedObject("wifi"); + wifi[F("sleep")] = !noWifiSleep; + wifi[F("phy")] = 1; + + JsonObject hw = doc.createNestedObject("hw"); + + JsonObject hw_led = hw.createNestedObject("led"); + hw_led[F("total")] = ledCount; + hw_led[F("maxpwr")] = strip.ablMilliampsMax; + hw_led[F("ledma")] = strip.milliampsPerLed; + hw_led[F("rev")] = strip.reverseMode; + + JsonArray hw_led_ins = hw_led.createNestedArray("ins"); + + JsonObject hw_led_ins_0 = hw_led_ins.createNestedObject(); + hw_led_ins_0[F("en")] = true; + hw_led_ins_0[F("start")] = 0; + hw_led_ins_0[F("len")] = ledCount; + JsonArray hw_led_ins_0_pin = hw_led_ins_0.createNestedArray("pin"); + hw_led_ins_0_pin.add(LEDPIN); + #ifdef DATAPIN + hw_led_ins_0_pin.add(DATAPIN); + #endif + hw_led_ins_0[F("order")] = strip.colorOrder; //color order + hw_led_ins_0[F("rev")] = false; + hw_led_ins_0[F("skip")] = skipFirstLed ? 1 : 0; + + //this is very crude and temporary + byte ledType = TYPE_WS2812_RGB; + if (useRGBW) ledType = TYPE_SK6812_RGBW; + #ifdef USE_WS2801 + ledType = TYPE_WS2801; + #endif + #ifdef USE_APA102 + ledType = TYPE_APA102; + #endif + #ifdef USE_LPD8806 + ledType = TYPE_LPD8806; + #endif + #ifdef USE_P9813 + ledType = TYPE_P9813; + #endif + #ifdef USE_TM1814 + ledType = TYPE_TM1814; + #endif + + hw_led_ins_0[F("type")] = ledType; + + JsonObject hw_btn = hw.createNestedObject("btn"); + + JsonArray hw_btn_ins = hw_btn.createNestedArray("ins"); + + #if defined(BTNPIN) && BTNPIN > -1 + JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject(); + hw_btn_ins_0[F("type")] = (buttonEnabled) ? BTN_TYPE_PUSH : BTN_TYPE_NONE; + + JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin"); + hw_btn_ins_0_pin.add(BTNPIN); + + JsonArray hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros"); + hw_btn_ins_0_macros.add(macroButton); + hw_btn_ins_0_macros.add(macroLongPress); + hw_btn_ins_0_macros.add(macroDoublePress); + #endif + + #if defined(IRPIN) && IRPIN > -1 + JsonObject hw_ir = hw.createNestedObject("ir"); + hw_ir[F("pin")] = IR_PIN; + hw_ir[F("type")] = 0; + #endif + + #if defined(RLYPIN) && RLYPIN > -1 + JsonObject hw_relay = hw.createNestedObject("relay"); + hw_relay[F("pin")] = RLYPIN; + hw_relay[F("rev")] = (RLYMDE) ? false : true; + JsonObject hw_status = hw.createNestedObject("status"); + hw_status[F("pin")] = -1; + #endif + + JsonObject light = doc.createNestedObject("light"); + light[F("scale-bri")] = briMultiplier; + light[F("pal-mode")] = strip.paletteBlend; + + JsonObject light_gc = light.createNestedObject("gc"); + light_gc["bri"] = (strip.gammaCorrectBri) ? 2.8 : 1.0; + light_gc[F("col")] = (strip.gammaCorrectCol) ? 2.8 : 1.0; + + JsonObject light_tr = light.createNestedObject("tr"); + light_tr[F("mode")] = fadeTransition; + light_tr[F("dur")] = transitionDelayDefault / 100; + light_tr[F("pal")] = strip.paletteFade; + + JsonObject light_nl = light.createNestedObject("nl"); + light_nl[F("mode")] = nightlightMode; + light_nl[F("dur")] = nightlightDelayMinsDefault; + light_nl[F("tbri")] = nightlightTargetBri; + light_nl[F("macro")] = macroNl; + + JsonObject def = doc.createNestedObject("def"); + def[F("ps")] = bootPreset; + def["on"] = turnOnAtBoot; + def["bri"] = briS; + + //to be removed once preset cycles are presets + if (saveCurrPresetCycConf) { + JsonObject def_cy = def.createNestedObject("cy"); + def_cy["on"] = presetCyclingEnabled; + + JsonArray def_cy_range = def_cy.createNestedArray("range"); + def_cy_range.add(presetCycleMin); + def_cy_range.add(presetCycleMax); + def_cy[F("dur")] = presetCycleTime / 100; + } + + JsonObject interfaces = doc.createNestedObject("if"); + + JsonObject if_sync = interfaces.createNestedObject("sync"); + if_sync[F("port0")] = udpPort; + if_sync[F("port1")] = udpPort2; + + JsonObject if_sync_recv = if_sync.createNestedObject("recv"); + if_sync_recv["bri"] = receiveNotificationBrightness; + if_sync_recv[F("col")] = receiveNotificationColor; + if_sync_recv[F("fx")] = receiveNotificationEffects; + + JsonObject if_sync_send = if_sync.createNestedObject("send"); + if_sync_send[F("dir")] = notifyDirect; + if_sync_send[F("btn")] = notifyButton; + if_sync_send[F("va")] = notifyAlexa; + if_sync_send[F("hue")] = notifyHue; + if_sync_send[F("macro")] = notifyMacro; + if_sync_send[F("twice")] = notifyTwice; + + JsonObject if_live = interfaces.createNestedObject("live"); + if_live[F("en")] = receiveDirect; + if_live[F("port")] = e131Port; + if_live[F("mc")] = e131Multicast; + + JsonObject if_live_dmx = if_live.createNestedObject("dmx"); + if_live_dmx[F("uni")] = e131Universe; + if_live_dmx[F("seqskip")] = e131SkipOutOfSequence; + if_live_dmx[F("addr")] = DMXAddress; + if_live_dmx[F("mode")] = DMXMode; + if_live[F("timeout")] = realtimeTimeoutMs / 100; + if_live[F("maxbri")] = arlsForceMaxBri; + if_live[F("no-gc")] = arlsDisableGammaCorrection; + if_live[F("offset")] = arlsOffset; + + JsonObject if_va = interfaces.createNestedObject("va"); + if_va[F("alexa")] = alexaEnabled; + + JsonArray if_va_macros = if_va.createNestedArray("macros"); + if_va_macros.add(macroAlexaOn); + if_va_macros.add(macroAlexaOff); + JsonObject if_blynk = interfaces.createNestedObject("blynk"); + if_blynk[F("token")] = strlen(blynkApiKey) ? "Hidden":""; + + JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); + if_mqtt[F("en")] = mqttEnabled; + if_mqtt[F("broker")] = mqttServer; + if_mqtt[F("port")] = mqttPort; + if_mqtt[F("user")] = mqttUser; + if_mqtt[F("pskl")] = strlen(mqttPass); + if_mqtt[F("cid")] = mqttClientID; + + JsonObject if_mqtt_topics = if_mqtt.createNestedObject("topics"); + if_mqtt_topics[F("device")] = mqttDeviceTopic; + if_mqtt_topics[F("group")] = mqttGroupTopic; + + JsonObject if_hue = interfaces.createNestedObject("hue"); + if_hue[F("en")] = huePollingEnabled; + if_hue[F("id")] = huePollLightId; + if_hue[F("iv")] = huePollIntervalMs / 100; + + JsonObject if_hue_recv = if_hue.createNestedObject("recv"); + if_hue_recv["on"] = hueApplyOnOff; + if_hue_recv["bri"] = hueApplyBri; + if_hue_recv[F("col")] = hueApplyColor; + + JsonArray if_hue_ip = if_hue.createNestedArray("ip"); + for (byte i = 0; i < 4; i++) { + if_hue_ip.add(hueIP[i]); + } + + JsonObject if_ntp = interfaces.createNestedObject("ntp"); + if_ntp[F("en")] = ntpEnabled; + if_ntp[F("host")] = ntpServerName; + if_ntp[F("tz")] = currentTimezone; + if_ntp[F("offset")] = utcOffsetSecs; + if_ntp[F("ampm")] = useAMPM; + + JsonObject ol = doc.createNestedObject("ol"); + ol[F("clock")] = overlayDefault; + ol[F("cntdwn")] = countdownMode; + + JsonObject timers = doc.createNestedObject("timers"); + + JsonObject cntdwn = timers.createNestedObject("cntdwn"); + JsonArray goal = cntdwn.createNestedArray("goal"); + goal.add(countdownYear); goal.add(countdownMonth); goal.add(countdownDay); + goal.add(countdownHour); goal.add(countdownMin); goal.add(countdownSec); + cntdwn[F("macro")] = macroCountdown; + + JsonArray timers_ins = timers.createNestedArray("ins"); + + for (byte i = 0; i < 8; i++) { + if (timerMacro[i] == 0 && timerHours[i] == 0 && timerMinutes[i] == 0) continue; + JsonObject timers_ins0 = timers_ins.createNestedObject(); + timers_ins0[F("en")] = (timerWeekday[i] & 0x01); + timers_ins0[F("hour")] = timerHours[i]; + timers_ins0[F("min")] = timerMinutes[i]; + timers_ins0[F("macro")] = timerMacro[i]; + timers_ins0[F("dow")] = timerWeekday[i] >> 1; + } + + JsonObject ota = doc.createNestedObject("ota"); + ota[F("lock")] = otaLock; + ota[F("lock-wifi")] = wifiLock; + ota[F("pskl")] = strlen(otaPass); + ota[F("aota")] = aOtaEnabled; + + #ifdef WLED_ENABLE_DMX + JsonObject dmx = doc.createNestedObject("dmx"); + dmx[F("chan")] = DMXChannels; + dmx[F("gap")] = DMXGap; + dmx[F("start")] = DMXStart; + dmx[F("start-led")] = DMXStartLED; + + JsonArray dmx_fixmap = dmx.createNestedArray("fixmap"); + for (byte i = 0; i < 15; i++) + dmx_fixmap.add(DMXFixtureMap[i]); + #endif + //} + + JsonObject usermods_settings = doc.createNestedObject("um"); + usermods.addToConfig(usermods_settings); + + File f = WLED_FS.open("/cfg.json", "w"); + if (f) serializeJson(doc, f); + f.close(); +} + +//settings in /wsec.json, not accessible via webserver, for passwords and tokens +bool deserializeConfigSec() { + DEBUG_PRINTLN(F("Reading settings from /wsec.json...")); + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + bool success = readObjectFromFile("/wsec.json", nullptr, &doc); + if (!success) return false; + + JsonObject nw_ins_0 = doc["nw"][F("ins")][0]; + getStringFromJson(clientPass, nw_ins_0["psk"], 65); + + JsonObject ap = doc[F("ap")]; + getStringFromJson(apPass, ap["psk"] , 65); + + JsonObject interfaces = doc["if"]; + + const char* apikey = interfaces["blynk"][F("token")] | "Hidden"; + int tdd = strnlen(apikey, 36); + if (tdd > 20 || tdd == 0) + getStringFromJson(blynkApiKey, apikey, 36); + + JsonObject if_mqtt = interfaces[F("mqtt")]; + getStringFromJson(mqttPass, if_mqtt["psk"], 41); + + getStringFromJson(hueApiKey, interfaces[F("hue")][F("key")], 47); + + JsonObject ota = doc["ota"]; + getStringFromJson(otaPass, ota[F("pwd")], 33); + CJSON(otaLock, ota[F("lock")]); + CJSON(wifiLock, ota[F("lock-wifi")]); + CJSON(aOtaEnabled, ota[F("aota")]); + + return true; +} + +void serializeConfigSec() { + DEBUG_PRINTLN(F("Writing settings to /wsec.json...")); + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + JsonObject nw = doc.createNestedObject("nw"); + + JsonArray nw_ins = nw.createNestedArray("ins"); + + JsonObject nw_ins_0 = nw_ins.createNestedObject(); + nw_ins_0["psk"] = clientPass; + + JsonObject ap = doc.createNestedObject("ap"); + ap["psk"] = apPass; + + JsonObject interfaces = doc.createNestedObject("if"); + JsonObject if_blynk = interfaces.createNestedObject("blynk"); + if_blynk[F("token")] = blynkApiKey; + JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); + if_mqtt["psk"] = mqttPass; + JsonObject if_hue = interfaces.createNestedObject("hue"); + if_hue[F("key")] = hueApiKey; + + JsonObject ota = doc.createNestedObject("ota"); + ota[F("pwd")] = otaPass; + ota[F("lock")] = otaLock; + ota[F("lock-wifi")] = wifiLock; + ota[F("aota")] = aOtaEnabled; + + File f = WLED_FS.open("/wsec.json", "w"); + if (f) serializeJson(doc, f); + f.close(); +} \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index 1f131d76..8dc1a286 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -72,20 +72,46 @@ #define DMX_MODE_MULTIPLE_RGB 4 //every LED is addressed with its own RGB (ledCount * 3 channels) #define DMX_MODE_MULTIPLE_DRGB 5 //every LED is addressed with its own RGB and share a master dimmer (ledCount * 3 + 1 channels) -//Light capability byte (unused) +//Light capability byte (unused) 0bRRCCTTTT +//bits 0/1/2/3: specifies a type of LED driver. A single "driver" may have different chip models but must have the same protocol/behavior +//bits 4/5: specifies the class of LED driver - 0b00 (dec. 0-15) unconfigured/reserved +// - 0b01 (dec. 16-31) digital (data pin only) +// - 0b10 (dec. 32-47) analog (PWM) +// - 0b11 (dec. 48-63) digital (data + clock / SPI) +//bits 6/7 are reserved and set to 0b00 + #define TYPE_NONE 0 //light is not configured #define TYPE_RESERVED 1 //unused. Might indicate a "virtual" light -#define TYPE_WS2812_RGB 2 -#define TYPE_SK6812_RGBW 3 -#define TYPE_WS2812_WWA 4 //amber + warm + cold white -#define TYPE_WS2801 5 -#define TYPE_ANALOG_1CH 6 //single channel PWM. Uses value of brightest RGBW channel -#define TYPE_ANALOG_2CH 7 //analog WW + CW -#define TYPE_ANALOG_3CH 8 //analog RGB -#define TYPE_ANALOG_4CH 9 //analog RGBW -#define TYPE_ANALOG_5CH 10 //analog RGB + WW + CW -#define TYPE_APA102 11 -#define TYPE_LPD8806 12 +//Digital types (data pin only) (16-31) +#define TYPE_WS2812_1CH 20 //white-only chips +#define TYPE_WS2812_WWA 21 //amber + warm + cold white +#define TYPE_WS2812_RGB 22 +#define TYPE_GS8608 23 //same driver as WS2812, but will require signal 2x per second (else displays test pattern) +#define TYPE_WS2811_400KHZ 24 //half-speed WS2812 protocol, used by very old WS2811 units +#define TYPE_SK6812_RGBW 30 +//"Analog" types (PWM) (32-47) +#define TYPE_ONOFF 40 //binary output (relays etc.) +#define TYPE_ANALOG_1CH 41 //single channel PWM. Uses value of brightest RGBW channel +#define TYPE_ANALOG_2CH 42 //analog WW + CW +#define TYPE_ANALOG_3CH 43 //analog RGB +#define TYPE_ANALOG_4CH 44 //analog RGBW +#define TYPE_ANALOG_5CH 45 //analog RGB + WW + CW +//Digital types (data + clock / SPI) (48-63) +#define TYPE_WS2801 50 +#define TYPE_APA102 51 +#define TYPE_LPD8806 52 +#define TYPE_P9813 53 +#define TYPE_TM1814 54 + + +//Button type +#define BTN_TYPE_NONE 0 +#define BTN_TYPE_RESERVED 1 +#define BTN_TYPE_PUSH 2 +#define BTN_TYPE_PUSH_ACT_HIGH 3 //not implemented +#define BTN_TYPE_SWITCH 4 //not implemented +#define BTN_TYPE_SWITCH_ACT_HIGH 5 //not implemented + //Hue error codes #define HUE_ERROR_INACTIVE 0 @@ -105,14 +131,21 @@ #define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed #define SEG_OPTION_TRANSITIONAL 7 +// WLED Error modes +#define ERR_NONE 0 // All good :) +#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?) +#define ERR_JSON 9 // JSON parsing failed (input too large?) +#define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?) +#define ERR_FS_QUOTA 11 // The FS is full or the maximum file size is reached +#define ERR_FS_PLOAD 12 // It was attempted to load a preset that does not exist +#define ERR_FS_GENERAL 19 // A general unspecified filesystem error occured + //Timer mode types #define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness #define NL_MODE_FADE 1 //Fade to target brightness gradually #define NL_MODE_COLORFADE 2 //Fade to target brightness and secondary color gradually #define NL_MODE_SUN 3 //Sunrise/sunset. Target brightness is set immediately, then Sunrise effect is started. Max 60 min. -//EEPROM size -#define EEPSIZE 2560 //Maximum is 4096 #define NTP_PACKET_SIZE 48 @@ -130,6 +163,7 @@ #define ABL_MILLIAMPS_DEFAULT 850; // auto lower brightness to stay close to milliampere limit + #define TOUCH_THRESHOLD 32 // limit to recognize a touch, higher value means more sensitive // Size of buffer for API JSON object (increase for more segments) diff --git a/wled00/data/404.html b/wled00/data/404.htm similarity index 96% rename from wled00/data/404.html rename to wled00/data/404.htm index 92f87d3d..1b7c95fc 100644 --- a/wled00/data/404.html +++ b/wled00/data/404.htm @@ -35,7 +35,6 @@ color: white; border: 0px solid white; border-radius: 25px; - filter: drop-shadow(0px 0px 1px #000); } diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 8b532de8..beffff89 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -204,7 +204,6 @@ button { .tab { background-color: transparent; color: var(--c-d); - /*filter: drop-shadow(0px 0px 0px #111);*/ } .bot { @@ -254,7 +253,7 @@ button { width: calc(var(--n)*100%); height: calc(100% - var(--tp) - var(--bh)); margin-top: var(--tp); - transform: translate(calc(var(--tx, 0px) + var(--i, 0)/var(--n)*-100%)); + transform: translate(calc(var(--i, 0)/var(--n)*-100%)); overscroll-behavior: none; } @@ -324,6 +323,7 @@ button { #fxb0 { margin-bottom: 2px; + filter: drop-shadow(0 0 1px #000); } .first { @@ -511,7 +511,6 @@ input[type=range]::-moz-range-thumb { #picker { margin: 10px auto; width: 260px; - filter: drop-shadow(0px 0px 1px #000); } #rgbwrap { @@ -527,7 +526,6 @@ input[type=range]::-moz-range-thumb { color: var(--c-f); border: 0px solid white; border-radius: 25px; - filter: drop-shadow(0px 0px 1px #000); transition-duration: 0.5s; -webkit-backface-visibility: hidden; -webkit-transform:translate3d(0,0,0); @@ -548,6 +546,9 @@ input[type=range]::-moz-range-thumb { .btna-icon { margin: 0px; } +.btn-p { + width: 216px; +} #qcs-w { margin-top: 10px; @@ -600,7 +601,7 @@ input[type=number], input[type=text] { padding: 8px; margin: 6px 6px 6px 0; font-size: 19px; - transition: background 0.2s; + transition: background-color 0.2s; outline: none; width: 50px; -webkit-appearance: textfield; @@ -608,12 +609,37 @@ input[type=number], input[type=text] { appearance: textfield; } +textarea { + background: var(--c-2); + color: var(--c-f); + width: 236px; + height: 90px; + border-radius: 5px; + border: 2px solid #555; + outline: none; + resize: none; + font-size: 19px; +} + +::selection { + background: var(--c-b); +} + input[type=text] { width: 100px; border-radius: 25px; text-align: center; } +.ptxt { + width: 200px !important; + margin: 26px 0 6px 12px !important; +} + +.stxt { + width: 50px !important; +} + input[type=number]:focus, input[type=text]:focus { background: var(--c-6); } @@ -629,13 +655,22 @@ input[type=number]::-webkit-outer-spin-button { .segname { position: absolute; - top: 10px; + top: 0px; left: 50%; + padding: 9px 0; transform: translateX(-50%); white-space: nowrap; cursor: pointer; } +.pname { + width: 208px; + padding: 8px 0; + text-align: center; + overflow: hidden; + text-overflow: clip; +} + .newseg { cursor: default; } @@ -649,41 +684,28 @@ input[type=number]::-webkit-outer-spin-button { margin: 6px; } -.rect { - border-radius: 5px; -} - .psts { - background-color: var(--c-2); - color: var(--c-8); - cursor: default; -} -.stored { background-color: var(--c-3); color: var(--c-f); cursor: pointer; -} -.saving { - background-color: var(--c-3); - color: var(--c-f); - cursor: pointer; -} -.stored.saving { - background-color: var(--c-r); - color: var(--c-f); + padding: 2px 0 0 0; + height: 40px; } .cnf { - position: absolute; - top: 66px; - right: 28px; color: var(--c-f); cursor: pointer; background: var(--c-3); - padding: 43px 6px; border-radius: 5px; } +.cnf-s { + position: absolute; + top: 66px; + right: 28px; + padding: 43px 6px; +} + .pwr { color: var(--c-6); transform: translate(2px, 3px); @@ -695,7 +717,7 @@ input[type=number]::-webkit-outer-spin-button { } .half { - padding: 6px 6px; + padding: 7.5px; top: 64px; } @@ -718,7 +740,7 @@ input[type=number]::-webkit-outer-spin-button { .schkl { padding: 2px 22px 0px 35px; - margin-bottom: 0px; + margin: 0 0 0 2px; } .revchkl { @@ -742,7 +764,7 @@ input[type=number]::-webkit-outer-spin-button { height: 25px; width: 25px; background-color: var(--c-3); - border-radius: 5px; + border-radius: 10px; } .schk { @@ -813,9 +835,14 @@ input[type=number]::-webkit-outer-spin-button { background-color: var(--c-2); color: var(--c-f); border: 0px solid white; - border-radius: 5px; - filter: drop-shadow(0px 0px 1px #000); + border-radius: 20px; text-align: left; + transition: background-color 0.5s; + filter: brightness(1); +} + +.pres { + margin-bottom: 6px; } .segin { @@ -827,6 +854,19 @@ input[type=number]::-webkit-outer-spin-button { display: block; } +.c { + text-align: center; +} + +.po2 { + display: none; + margin-top: 8px; +} + +.pwarn { + color: red; +} + ::-webkit-scrollbar { width: 6px; } @@ -991,34 +1031,20 @@ input[type=number]::-webkit-outer-spin-button {
- +
-

Load from slot

- - - -
- - - -
- - - -
- - - -
-
- Slot 16 can save all segments.

+
+ +
+
+ +
+
+ Loading... +