diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 867d7d46d2..f77dc4de4a 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -31,14 +31,15 @@
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
- "ms-python.python"
+ "ms-python.python",
+ "platformio.platformio-ide"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
- // "postCreateCommand": "pip3 install --user -r requirements.txt",
+ "postCreateCommand": "npm install",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml
new file mode 100644
index 0000000000..4158dd7ac7
--- /dev/null
+++ b/.github/workflows/wled-ci.yml
@@ -0,0 +1,31 @@
+name: PlatformIO CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Cache pip
+ uses: actions/cache@v2
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+ restore-keys: |
+ ${{ runner.os }}-pip-
+ - name: Cache PlatformIO
+ uses: actions/cache@v2
+ with:
+ path: ~/.platformio
+ key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ - name: Install PlatformIO
+ run: |
+ python -m pip install --upgrade pip
+ pip install --upgrade platformio
+ - name: Run PlatformIO
+ run: pio run
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a95dd5ac22..7c0dd1e5b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,9 +7,10 @@
/wled00/Release
/wled00/extLibs
/platformio_override.ini
-/wled00/my_config.h
+/wled00/my_config.h
/build_output
.DS_Store
.gitignore
.clang-format
node_modules
+.idea
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 16b4f2c1d5..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-# Continuous Integration (CI) is the practice, in software
-# engineering, of merging all developer working copies with a shared mainline
-# several times a day < https://docs.platformio.org/page/ci/index.html >
-#
-# Documentation:
-#
-# * Travis CI Embedded Builds with PlatformIO
-# < https://docs.travis-ci.com/user/integration/platformio/ >
-#
-# * PlatformIO integration with Travis CI
-# < https://docs.platformio.org/page/ci/travis.html >
-#
-# * User Guide for `platformio ci` command
-# < https://docs.platformio.org/page/userguide/cmd_ci.html >
-#
-#
-# Please choose one of the following templates (proposed below) and uncomment
-# it (remove "# " before each line) or use own configuration according to the
-# Travis CI documentation (see above).
-#
-# * Test the Travis config here:
-# < https://config.travis-ci.com/explore >
-#
-
-language: python
-python:
- # - "2.7"
- - "3.5"
-os: linux
-cache:
- bundler: true
- ccache: true
- directories:
- - "~/.platformio"
- - "~/.buildcache"
-env:
- - PLATFORMIO_CI_SRC=wled00
-install:
- - pip install -U platformio
- - platformio update
-script:
- # - platformio ci --project-conf=./platformio.ini
- - platformio run
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000000..2ee772ce16
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,42 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Build: HTML and binary",
+ "dependsOn": [
+ "Build: HTML only",
+ "Build: binary only"
+ ],
+ "dependsOrder": "sequence",
+ "problemMatcher": [
+ "$platformio",
+ ],
+ },
+ {
+ "type": "PlatformIO",
+ "label": "Build: binary only",
+ "task": "Build",
+ "group": {
+ "kind": "build",
+ "isDefault": true,
+ },
+ "problemMatcher": [
+ "$platformio"
+ ],
+ "presentation": {
+ "panel": "shared"
+ }
+ },
+ {
+ "type": "npm",
+ "script": "build",
+ "group": "build",
+ "problemMatcher": [],
+ "label": "Build: HTML only",
+ "detail": "npm run build",
+ "presentation": {
+ "panel": "shared"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 91619a8993..a56fe813e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,201 @@
## WLED changelog
+### WLED release 0.12.0
+
+#### Build 2104020
+
+- Allow clearing button/IR/relay pin on platforms that don't support negative numbers
+- Removed AUX pin
+- Hid some easter eggs, only to be found at easter
+
+### Development versions between 0.11.1 and 0.12.0 releases
+
+#### Build 2103310
+
+- Version bump to 0.12.0 "Hikari"
+- Fixed LED settings submission in iOS app
+
+#### Build 2103300
+
+- Version bump to 0.12.0-b5 "Hikari"
+- Update to core espressif32@3.2
+- Fixed IR pin not configurable
+
+#### Build 2103290
+
+- Version bump to 0.12.0-b4 "Hikari"
+- Experimental use of espressif32@3.1.1
+- Fixed RGBW mode disabled after LED settings saved
+- Fixed infrared support not compiled in if IRPIN is not defined
+
+#### Build 2103230
+
+- Fixed current estimation
+
+#### Build 2103220
+
+- Version bump to 0.12.0-b2 "Hikari"
+- Worked around an issue causing a critical decrease in framerate (wled.cpp l.240 block)
+- Bump to Espalexa v2.7.0, fixing discovery
+
+#### Build 2103210
+
+- Version bump to 0.12.0-b1 "Hikari"
+- More colors visible on Palette preview
+- Fixed chevron icon not included
+- Fixed color order override
+- Cleanup
+
+#### Build 2103200
+
+- Version bump to 0.12.0-b0 "Hikari"
+- Added palette preview and search (PR #1637)
+- Added Reverse checkbox for PWM busses - reverses logic level for on
+- Fixed various problems with the Playlist feature (PR #1724)
+- Replaced "Layer" icon with "i" icon for Info button
+- Chunchun effect more fitting for various segment lengths (PR #1804)
+- Removed global reverse (in favor of individual bus reverse)
+- Removed some unused icons from UI icon font
+
+#### Build 2103130
+
+- Added options for Auto Node discovery
+- Optimized strings (no string both F() and raw)
+
+#### Build 2103090
+
+- Added Auto Node discovery (PR #1683)
+- Added tooltips to quick color selectors for accessibility
+
+#### Build 2103060
+
+- Auto start field population in bus config
+
+#### Build 2103050
+
+- Fixed incorrect over-memory indication in LED settings on ESP32
+
+#### Build 2103041
+
+- Added destructor for BusPwm (fixes #1789)
+
+#### Build 2103040
+
+- Fixed relay mode inverted when upgrading from 0.11.0
+- Fixed no more than 2 pins per bus configurable in UI
+- Changed to non-linear IR brightness steps (PR #1742)
+- Fixed various warnings (PR #1744)
+- Added UDP DNRGBW Mode (PR #1704)
+- Added dynamic LED mapping with ledmap.json file (PR #1738)
+- Added support for QuinLED-ESP32-Ethernet board
+- Added support for WESP32 ethernet board (PR #1764)
+- Added Caching for main UI (PR #1704)
+- Added Tetrix mode (PR #1729)
+- Added memory check on Bus creation
+
+#### Build 2102050
+
+- Version bump to 0.12.0-a0 "Hikari"
+- Added FPS indication in info
+- Bumped max outputs from 7 to 10 busses for ESP32
+
+#### Build 2101310
+
+- First alpha configurable multipin
+
+#### Build 2101130
+
+- Added color transitions for all segments and slots and for segment brightness
+- Fixed bug that prevented setting a boot preset higher than 25
+
+#### Build 2101040
+
+- Replaced Red & Blue effect with Aurora effect (PR #1589)
+- Fixed HTTP changing segments uncommanded (#1618)
+- Updated copyright year and contributor page link
+
+#### Build 2012311
+
+- Fixed Countdown mode
+
+#### Build 2012310
+
+- (Hopefully actually) fixed display of usermod values in info screen
+
+#### Build 2012240
+
+- Fixed display of usermod values in info screen
+- 4 more effects now use FRAMETIME
+- Remove unsupported environments from platformio.ini
+
+#### Build 2012210
+
+- Split index.htm in separate CSS + JS files (PR #1542)
+- Minify UI HTML, saving >1.5kB flash
+- Fixed JShint warnings
+
+#### Build 2012180
+
+- Boot brightness 0 will now use the brightness from preset
+- Add iOS scrolling momentum (from PR #1528)
+
+### WLED release 0.11.1
+
+#### Build 2012180
+
+- Release of WLED 0.11.1 "Mirai"
+- Fixed AP hide not saving (fixes #1520)
+- Fixed MQTT password re-transmitted to HTML
+- Hide Update buttons while uploading, accept .bin
+- Make sure AP password is at least 8 characters long
+
+### Development versions after 0.11.0 release
+
+#### Build 2012160
+
+- Bump Espalexa to 2.5.0, fixing discovery (PR Espalexa/#152, originally PR #1497)
+
+#### Build 2012150
+
+- Added Blends FX (PR #1491)
+- Fixed an issue that made it impossible to deactivate timed presets
+
+#### Build 2012140
+
+- Added Preset ID quick display option (PR #1462)
+- Fixed LEDs not turning on when using gamma correct brightness and LEDPIN 2 (default)
+- Fixed notifier applying main segment to selected segments on notification with FX/Col disabled
+
+#### Build 2012130
+
+- Fixed RGBW mode not saved between reboots (fixes #1457)
+- Added brightness scaling in palette function for default (PR #1484)
+
+#### Build 2012101
+
+- Fixed preset cycle default duration rounded down to nearest 10sec interval (#1458)
+- Enabled E1.31/DDP/Art-Net in AP mode
+
+#### Build 2012100
+
+- Fixed multi-segment preset cycle
+- Fixed EEPROM (pre-0.11 settings) not cleared on factory reset
+- Fixed an issue with intermittent crashes on FX change (PR #1465)
+- Added function to know if strip is updating (PR #1466)
+- Fixed using colorwheel sliding the UI (PR #1459)
+- Fixed analog clock settings not saving (PR #1448)
+- Added Temperature palette (PR #1430)
+- Added Candy cane FX (PR #1445)
+
+#### Build 2012020
+
+- UDP `parsePacket()` with sync disabled (#1390)
+- Added Multi RGBW DMX mode (PR #1383)
+
+#### Build 2012010
+
+- Fixed compilation for analog (PWM) LEDs
+
### WLED version 0.11.0
#### Build 2011290
@@ -63,7 +259,7 @@
#### Build 2011153
- Fixed an ESP32 end-of-file issue
-- Fixed useRGBW not read from cfg.json
+- Fixed strip.isRgbw not read from cfg.json
#### Build 2011152
diff --git a/images/Readme.md b/images/Readme.md
new file mode 100644
index 0000000000..738a84f64c
--- /dev/null
+++ b/images/Readme.md
@@ -0,0 +1,5 @@
+### Additional Logos
+
+Additional awesome logos for WLED can be found here [Aircoookie/Akemi](https://github.com/Aircoookie/Akemi).
+
+
diff --git a/package-lock.json b/package-lock.json
index 7462b5d8a8..c6b006a935 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "wled",
- "version": "0.10.2",
+ "version": "0.11.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -958,9 +958,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
- "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"inliner": {
"version": "1.13.1",
diff --git a/package.json b/package.json
index 5d9ac9e4b9..5b75307b27 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "wled",
- "version": "0.11.0",
+ "version": "0.12.0",
"description": "Tools for WLED project",
"main": "tools/cdata.js",
"directories": {
diff --git a/pio/gzip-firmware.py b/pio-scripts/gzip-firmware.py
similarity index 100%
rename from pio/gzip-firmware.py
rename to pio-scripts/gzip-firmware.py
diff --git a/pio/name-firmware.py b/pio-scripts/name-firmware.py
similarity index 100%
rename from pio/name-firmware.py
rename to pio-scripts/name-firmware.py
diff --git a/pio/obj-dump.py b/pio-scripts/obj-dump.py
similarity index 100%
rename from pio/obj-dump.py
rename to pio-scripts/obj-dump.py
diff --git a/pio/strip-floats.py b/pio-scripts/strip-floats.py
similarity index 100%
rename from pio/strip-floats.py
rename to pio-scripts/strip-floats.py
diff --git a/pio/user_config_copy.py b/pio-scripts/user_config_copy.py
similarity index 100%
rename from pio/user_config_copy.py
rename to pio-scripts/user_config_copy.py
diff --git a/platformio.ini b/platformio.ini
index c3cf2fc4ea..ea5972ea4e 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -9,15 +9,13 @@
# ------------------------------------------------------------------------------
# Travis CI binaries (comment this out with a ';' when building for your own board)
-default_envs = travis_esp8266, travis_esp32
+;default_envs = travis_esp8266, travis_esp32
# Release binaries
-; default_envs = nodemcuv2, esp01_1m_full, esp32dev, custom_WS2801, custom_APA102, custom_LEDPIN_16, custom_LEDPIN_4, custom_LEDPIN_3, custom32_LEDPIN_16, custom32_APA102
+default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
# Single binaries (uncomment your board)
; default_envs = nodemcuv2
-; default_envs = esp01
-; default_envs = esp01_1m_ota
; default_envs = esp01_1m_full
; default_envs = esp07
; default_envs = d1_mini
@@ -32,12 +30,12 @@ default_envs = travis_esp8266, travis_esp32
; default_envs = d1_mini_5CH_Shojo_PCB
; default_envs = wemos_shield_esp32
; default_envs = m5atom
-; default_envs = esp32_poe
+; default_envs = esp32_eth
src_dir = ./wled00
data_dir = ./wled00/data
build_cache_dir = ~/.buildcache
-extra_configs =
+extra_configs =
platformio_override.ini
[common]
@@ -59,6 +57,9 @@ arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/
platform_wled_default = ${common.arduino_core_2_7_4}
# 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
+ platformio/tool-esptool @ ~1.413.0
+ platformio/tool-esptoolpy @ ~1.30000.0
# ------------------------------------------------------------------------------
# FLAGS: DEBUG
@@ -66,7 +67,7 @@ platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
# ------------------------------------------------------------------------------
debug_flags = -D DEBUG=1 -D WLED_DEBUG -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM
#if needed (for memleaks etc) also add; -DDEBUG_ESP_OOM -include "umm_malloc/umm_malloc_cfg.h"
-#-DDEBUG_ESP_CORE is not working right now
+#-DDEBUG_ESP_CORE is not working right now
# ------------------------------------------------------------------------------
# FLAGS: ldscript (available ldscripts at https://github.com/esp8266/Arduino/tree/master/tools/sdk/ld)
@@ -94,34 +95,22 @@ debug_flags = -D DEBUG=1 -D WLED_DEBUG -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT
# This reduces the OTA size with ~45KB, so it's especially useful on low memory boards (512k/1m).
# ------------------------------------------------------------------------------
build_flags =
- -Wno-switch
- -Wno-deprecated-declarations
- -Wno-write-strings
- -Wno-unused-variable
- -Wno-unused-value
- -Wno-sign-compare
- -Wno-unused-but-set-variable
- -Wno-return-type
- -Wno-sequence-point
- -Wno-narrowing
- -Wno-reorder
- -DMQTT_MAX_PACKET_SIZE=1024
- -DSECURE_CLIENT=SECURE_CLIENT_BEARSSL
+ -DMQTT_MAX_PACKET_SIZE=1024
+ -DSECURE_CLIENT=SECURE_CLIENT_BEARSSL
-DBEARSSL_SSL_BASIC
-D CORE_DEBUG_LEVEL=0
-D NDEBUG
- #build_flags for the IRremoteESP8266 library (enabled decoders have to appear here)
- -D _IR_ENABLE_DEFAULT_=false
- -D DECODE_HASH=true
+ #build_flags for the IRremoteESP8266 library (enabled decoders have to appear here)
+ -D _IR_ENABLE_DEFAULT_=false
+ -D DECODE_HASH=true
-D DECODE_NEC=true
- -D DECODE_SONY=true
+ -D DECODE_SONY=true
-D DECODE_SAMSUNG=true
-D DECODE_LG=true
-DWLED_USE_MY_CONFIG
+ ; -D USERMOD_SENSORSTOMQTT
build_unflags =
- -Wall
- -Wdeprecated-declarations
# enables all features for travis CI
build_flags_all_features =
@@ -136,15 +125,13 @@ build_flags_all_features =
build_flags_esp8266 = ${common.build_flags} ${esp8266.build_flags}
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
[esp8266]
-build_flags =
+build_flags =
-DESP8266
-DFP_IN_IROM
; NONOSDK22x_190703 = 2.2.2-dev(38a443e)
@@ -154,20 +141,20 @@ build_flags =
; lwIP 1.4 - Higher Bandwidth (Aircoookie has)
-DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
; VTABLES in Flash
- -DVTABLES_IN_FLASH
+ -DVTABLES_IN_FLASH
; restrict to minimal mime-types
- -DMIMETYPE_MINIMAL
+ -DMIMETYPE_MINIMAL
[esp32]
-build_flags = -w -g
+build_flags = -g
-DARDUINO_ARCH_ESP32
-DCONFIG_LITTLEFS_FOR_IDF_3_2
[scripts_defaults]
-extra_scripts = pio/name-firmware.py
- pio/gzip-firmware.py
- pio/strip-floats.py
- pio/user_config_copy.py
+extra_scripts = pio-scripts/name-firmware.py
+ pio-scripts/gzip-firmware.py
+ pio-scripts/strip-floats.py
+ pio-scripts/user_config_copy.py
# ------------------------------------------------------------------------------
# COMMON SETTINGS:
@@ -176,7 +163,10 @@ extra_scripts = pio/name-firmware.py
framework = arduino
board_build.flash_mode = dout
monitor_speed = 115200
+# slow upload speed (comment this out with a ';' when building for development use)
upload_speed = 115200
+# fast upload speed (remove ';' when building for development use)
+; upload_speed = 921600
# ------------------------------------------------------------------------------
# LIBRARIES: required dependencies
@@ -187,15 +177,15 @@ upload_speed = 115200
# ------------------------------------------------------------------------------
lib_compat_mode = strict
lib_deps =
- FastLED@3.3.2
- NeoPixelBus@2.6.0
- ESPAsyncTCP@1.2.0
+ fastled/FastLED @ 3.3.2
+ NeoPixelBus @ 2.6.0
+ ESPAsyncTCP @ 1.2.0
ESPAsyncUDP
- AsyncTCP@1.0.3
- IRremoteESP8266@2.7.3
+ AsyncTCP @ 1.0.3
+ 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
+ https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.2
+ #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
@@ -204,6 +194,10 @@ lib_deps =
#milesburton/DallasTemperature@^3.9.0
#For BME280 sensor uncomment following
#BME280@~3.0.0
+ ; adafruit/Adafruit BMP280 Library @ 2.1.0
+ ; adafruit/Adafruit CCS811 Library @ 1.0.4
+ ; adafruit/Adafruit Si7021 Library @ 1.4.0
+
lib_ignore =
AsyncTCP
@@ -221,26 +215,6 @@ board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
-# Unsupported environment due to insufficient flash
-[env:esp01]
-board = esp01
-platform = ${common.platform_wled_default}
-platform_packages = ${common.platform_packages}
-board_build.ldscript = ${common.ldscript_512k}
-build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_DISABLE_ALEXA -D WLED_DISABLE_BLYNK
- -D WLED_DISABLE_CRONIXIE -D WLED_DISABLE_HUESYNC -D WLED_DISABLE_INFRARED -D WLED_DISABLE_MQTT -D WLED_DISABLE_WEBSOCKETS
-
-# Unsupported environment due to insufficient flash
-[env:esp01_1m_ota]
-board = esp01_1m
-platform = ${common.platform_wled_default}
-platform_packages = ${common.platform_packages}
-board_build.ldscript = ${common.ldscript_1m0m}
-build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_ALEXA -D WLED_DISABLE_BLYNK -D WLED_DISABLE_CRONIXIE
- -D WLED_DISABLE_HUESYNC -D WLED_DISABLE_INFRARED -D WLED_DISABLE_MQTT -D WLED_DISABLE_WEBSOCKETS
-
[env:esp01_1m_full]
board = esp01_1m
platform = ${common.platform_wled_default}
@@ -255,7 +229,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp8266}
+build_flags = ${common.build_flags_esp8266}
[env:d1_mini]
board = d1_mini
@@ -264,7 +238,7 @@ platform_packages = ${common.platform_packages}
upload_speed = 921600
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp8266}
+build_flags = ${common.build_flags_esp8266}
monitor_filters = esp8266_exception_decoder
[env:heltec_wifi_kit_8]
@@ -285,19 +259,19 @@ build_flags = ${common.build_flags_esp8266} -D LEDPIN=1 -D WLED_DISABLE_INFRARED
[env:esp32dev]
board = esp32dev
-platform = espressif32@2.0
+platform = espressif32@3.2
build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp32}
+build_flags = ${common.build_flags_esp32}
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
-[env:esp32_poe]
+[env:esp32_eth]
board = esp32-poe
-platform = espressif32@2.0
+platform = espressif32@3.2
upload_speed = 921600
build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp32} -D RLYPIN=-1 -D WLED_USE_ETHERNET
+build_flags = ${common.build_flags_esp32} -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
@@ -324,7 +298,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS -D WLED_USE_H801 -D WLED_ENABLE_5CH_LEDS
+build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS -D WLED_USE_H801 -D WLED_ENABLE_5CH_LEDS
[env:d1_mini_5CH_Shojo_PCB]
board = d1_mini
@@ -332,7 +306,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp8266} -D WLED_USE_ANALOG_LEDS -D WLED_USE_SHOJO_PCB -D WLED_ENABLE_5CH_LEDS
+build_flags = ${common.build_flags_esp8266} -D WLED_USE_ANALOG_LEDS -D WLED_USE_SHOJO_PCB -D WLED_ENABLE_5CH_LEDS
# ------------------------------------------------------------------------------
# DEVELOPMENT BOARDS
@@ -356,7 +330,15 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp8266}
+build_flags = ${common.build_flags_esp8266}
+
+[env:anavi_miracle_controller]
+board = d1_mini
+platform = ${common.platform_wled_default}
+platform_packages = ${common.platform_packages}
+board_build.ldscript = ${common.ldscript_4m1m}
+build_unflags = ${common.build_unflags}
+build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2
# ------------------------------------------------------------------------------
# custom board configurations
@@ -376,7 +358,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags_esp8266} -D LEDPIN=16
+build_flags = ${common.build_flags_esp8266} -D LEDPIN=16
[env:custom_LEDPIN_3]
@@ -405,7 +387,7 @@ build_flags = ${common.build_flags_esp8266} -D USE_WS2801
[env:custom32_LEDPIN_16]
board = esp32dev
-platform = espressif32@2.0
+platform = espressif32@3.2
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D LEDPIN=16 -D RLYPIN=19
lib_ignore =
@@ -414,7 +396,7 @@ lib_ignore =
[env:custom32_APA102]
board = esp32dev
-platform = espressif32@2.0
+platform = espressif32@3.2
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D USE_APA102
lib_ignore =
@@ -423,7 +405,7 @@ lib_ignore =
[env:custom32_TOUCHPIN_T0]
board = esp32dev
-platform = espressif32@2.0
+platform = espressif32@3.2
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D TOUCHPIN=T0
lib_ignore =
@@ -432,7 +414,7 @@ lib_ignore =
[env:wemos_shield_esp32]
board = esp32dev
-platform = espressif32@2.0
+platform = espressif32@3.2
upload_port = /dev/cu.SLAB_USBtoUART
monitor_port = /dev/cu.SLAB_USBtoUART
upload_speed = 460800
@@ -446,10 +428,10 @@ lib_ignore =
board = esp32dev
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39
-lib_ignore =
+lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
-platform = espressif32@2.0
+platform = espressif32@3.2
[env:sp501e]
board = esp_wroom_02
diff --git a/platformio_override.ini.example b/platformio_override.ini.sample
similarity index 89%
rename from platformio_override.ini.example
rename to platformio_override.ini.sample
index d486dff019..c9dab54872 100644
--- a/platformio_override.ini.example
+++ b/platformio_override.ini.sample
@@ -11,20 +11,20 @@ default_envs = WLED_tasmota_1M
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}
; *********************************************************************
; *** Use custom settings from file my_config.h
-DWLED_USE_MY_CONFIG
; *********************************************************************
-; -D WLED_DISABLE_OTA
-; -D WLED_DISABLE_ALEXA
+; -D WLED_DISABLE_OTA
+; -D WLED_DISABLE_ALEXA
; -D WLED_DISABLE_BLYNK
-; -D WLED_DISABLE_CRONIXIE
-; -D WLED_DISABLE_HUESYNC
+; -D WLED_DISABLE_CRONIXIE
+; -D WLED_DISABLE_HUESYNC
; -D WLED_DISABLE_INFRARED
-; -D WLED_DISABLE_WEBSOCKETS
+; -D WLED_DISABLE_WEBSOCKETS
; PIN defines - uncomment and change, if needed:
; -D LEDPIN=2
; -D BTNPIN=0
diff --git a/readme.md b/readme.md
index 49a0064da7..15a6ef2d81 100644
--- a/readme.md
+++ b/readme.md
@@ -1,5 +1,6 @@
# HyperSerialWLED
Fork of the WLED project where the Adalight USB serial protocol @115200 speed is replaced with the AWA protocol with data integrity check at @2000000 speed for use with [HyperHDR](https://github.com/awawa-dev/HyperHDR). Work for ESP8266 (WLED platform: d1_mini) and ESP32 (WLED platform: esp32dev).
+Now it's possible to use HyperSerialWLED with any LED strip/pinout supported by the WLED.
1 For installation and configuration of WLED please refer to the WLED project: [link](https://github.com/Aircoookie/WLED)
2 For configuration of HyperHDR please refer to the base project of AWA protocol: [link](https://github.com/awawa-dev/HyperSerialEsp8266)
@@ -11,6 +12,22 @@ Fork of the WLED project where the Adalight USB serial protocol @115200 speed is
WLED is receiving data from the USB serial port at @2000000 baud:
+## ⚙️ Features of WLED 0.12
+- WS2812FX library integrated for over 100 special effects
+- FastLED noise effects and 50 palettes
+- Modern UI with color, effect and segment controls
+- Segments to set different effects and colors to parts of the LEDs
+- Settings page - configuration over network
+- Access Point and station mode - automatic failsafe AP
+- Up to 10 LED outputs per instance
+- Support for RGBW strips
+- 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
# Disclaimer
You use it on your own risk. As per the MIT license, I assume no liability for any damage to you or any other person or equipment.
diff --git a/tools/cdata.js b/tools/cdata.js
index aa473deb11..201193f6f0 100644
--- a/tools/cdata.js
+++ b/tools/cdata.js
@@ -69,6 +69,8 @@ function writeHtmlGzipped(sourceFile, resultFile) {
console.info("Reading " + sourceFile);
new inliner(sourceFile, function (error, html) {
console.info("Inlined " + html.length + " characters");
+ html = filter(html, "html-minify-ui");
+ console.info("Minified to " + html.length + " characters");
if (error) {
console.warn(error);
@@ -123,6 +125,16 @@ function filter(str, type) {
continueOnParseError: false,
removeComments: true,
});
+ } else if (type == "html-minify-ui") {
+ return MinifyHTML(str, {
+ collapseWhitespace: true,
+ conservativeCollapse: true,
+ maxLineLength: 80,
+ minifyCSS: true,
+ minifyJS: true,
+ continueOnParseError: false,
+ removeComments: true,
+ });
} else {
console.warn("Unknown filter: " + type);
return str;
@@ -132,7 +144,7 @@ function filter(str, type) {
function specToChunk(srcDir, s) {
if (s.method == "plaintext") {
const buf = fs.readFileSync(srcDir + "/" + s.file);
- const str = buf.toString("ascii");
+ const str = buf.toString("utf-8");
const chunk = `
// Autogenerated from ${srcDir}/${s.file}, do not edit!!
const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter(str, s.filter)}${
@@ -386,6 +398,14 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
method: "plaintext",
filter: "html-minify",
},
+ {
+ file: "liveviewws.htm",
+ name: "PAGE_liveviewws",
+ prepend: "=====(",
+ append: ")=====",
+ method: "plaintext",
+ filter: "html-minify",
+ },
{
file: "404.htm",
name: "PAGE_404",
diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h
new file mode 100644
index 0000000000..9717589da3
--- /dev/null
+++ b/usermods/Animated_Staircase/Animated_Staircase.h
@@ -0,0 +1,427 @@
+/*
+ * Usermod for detecting people entering/leaving a staircase and switching the
+ * staircase on/off.
+ *
+ * Edit the Animated_Staircase_config.h file to compile this usermod for your
+ * specific configuration.
+ *
+ * See the accompanying README.md file for more info.
+ */
+#pragma once
+#include "wled.h"
+#include "Animated_Staircase_config.h"
+#define USERMOD_ID_ANIMATED_STAIRCASE 1011
+
+/* Initial configuration (available in API and stored in flash) */
+bool enabled = true; // Enable this usermod
+unsigned long segment_delay_ms = 150; // Time between switching each segment
+unsigned long on_time_ms = 5 * 1000; // The time for the light to stay on
+#ifndef TOP_PIR_PIN
+unsigned int topMaxTimeUs = 1749; // default echo timout, top
+#endif
+#ifndef BOTTOM_PIR_PIN
+unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom
+#endif
+
+// Time between checking of the sensors
+const int scanDelay = 50;
+
+class Animated_Staircase : public Usermod {
+ private:
+ // Lights on or off.
+ // Flipping this will start a transition.
+ bool on = false;
+
+ // Swipe direction for current transition
+#define SWIPE_UP true
+#define SWIPE_DOWN false
+ bool swipe = SWIPE_UP;
+
+ // Indicates which Sensor was seen last (to determine
+ // the direction when swiping off)
+#define LOWER false
+#define UPPER true
+ bool lastSensor = LOWER;
+
+ // Time of the last transition action
+ unsigned long lastTime = 0;
+
+ // Time of the last sensor check
+ unsigned long lastScanTime = 0;
+
+ // Last time the lights were switched on or off
+ unsigned long lastSwitchTime = 0;
+
+ // segment id between onIndex and offIndex are on.
+ // controll the swipe by setting/moving these indices around.
+ // onIndex must be less than or equal to offIndex
+ byte onIndex = 0;
+ byte offIndex = 0;
+
+ // The maximum number of configured segments.
+ // Dynamically updated based on user configuration.
+ byte maxSegmentId = 1;
+ byte mainSegmentId = 0;
+
+ bool saveState = false;
+
+ // These values are used by the API to read the
+ // last sensor state, or trigger a sensor
+ // through the API
+ bool topSensorRead = false;
+ bool topSensorWrite = false;
+ bool bottomSensorRead = false;
+ bool bottomSensorWrite = false;
+
+ void updateSegments() {
+ mainSegmentId = strip.getMainSegmentId();
+ WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
+ WS2812FX::Segment* segments = strip.getSegments();
+ for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
+ if (!segments->isActive()) {
+ maxSegmentId = i - 1;
+ break;
+ }
+
+ if (i >= onIndex && i < offIndex) {
+ segments->setOption(SEG_OPTION_ON, 1, 1);
+
+ // We may need to copy mode and colors from segment 0 to make sure
+ // changes are propagated even when the config is changed during a wipe
+ // segments->mode = mainsegment.mode;
+ // segments->colors[0] = mainsegment.colors[0];
+ } else {
+ segments->setOption(SEG_OPTION_ON, 0, 1);
+ }
+ // Always mark segments as "transitional", we are animating the staircase
+ segments->setOption(SEG_OPTION_TRANSITIONAL, 1, 1);
+ }
+ colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
+ }
+
+ /*
+ * Detects if an object is within ultrasound range.
+ * signalPin: The pin where the pulse is sent
+ * echoPin: The pin where the echo is received
+ * maxTimeUs: Detection timeout in microseconds. If an echo is
+ * received within this time, an object is detected
+ * and the function will return true.
+ *
+ * The speed of sound is 343 meters per second at 20 degress Celcius.
+ * Since the sound has to travel back and forth, the detection
+ * distance for the sensor in cm is (0.0343 * maxTimeUs) / 2.
+ *
+ * For practical reasons, here are some useful distances:
+ *
+ * Distance = maxtime
+ * 5 cm = 292 uS
+ * 10 cm = 583 uS
+ * 20 cm = 1166 uS
+ * 30 cm = 1749 uS
+ * 50 cm = 2915 uS
+ * 100 cm = 5831 uS
+ */
+ bool ultrasoundRead(uint8_t signalPin,
+ uint8_t echoPin,
+ unsigned int maxTimeUs) {
+ digitalWrite(signalPin, HIGH);
+ delayMicroseconds(10);
+ digitalWrite(signalPin, LOW);
+ return pulseIn(echoPin, HIGH, maxTimeUs) > 0;
+ }
+
+ void checkSensors() {
+ if ((millis() - lastScanTime) > scanDelay) {
+ lastScanTime = millis();
+
+#ifdef BOTTOM_PIR_PIN
+ bottomSensorRead = bottomSensorWrite || (digitalRead(BOTTOM_PIR_PIN) == HIGH);
+#else
+ bottomSensorRead = bottomSensorWrite || ultrasoundRead(BOTTOM_TRIGGER_PIN, BOTTOM_ECHO_PIN, bottomMaxTimeUs);
+#endif
+
+#ifdef TOP_PIR_PIN
+ topSensorRead = topSensorWrite || (digitalRead(TOP_PIR_PIN) == HIGH);
+#else
+ topSensorRead = topSensorWrite || ultrasoundRead(TOP_TRIGGER_PIN, TOP_ECHO_PIN, topMaxTimeUs);
+#endif
+
+ // Values read, reset the flags for next API call
+ topSensorWrite = false;
+ bottomSensorWrite = false;
+
+ if (topSensorRead != bottomSensorRead) {
+ lastSwitchTime = millis();
+
+ if (on) {
+ lastSensor = topSensorRead;
+ } else {
+ // If the bottom sensor triggered, we need to swipe up, ON
+ swipe = bottomSensorRead;
+
+ if (swipe) {
+ Serial.println("ON -> Swipe up.");
+ } else {
+ Serial.println("ON -> Swipe down.");
+ }
+
+ if (onIndex == offIndex) {
+ // Position the indices for a correct on-swipe
+ if (swipe == SWIPE_UP) {
+ onIndex = mainSegmentId;
+ } else {
+ onIndex = maxSegmentId+1;
+ }
+ offIndex = onIndex;
+ }
+ on = true;
+ }
+ }
+ }
+ }
+
+ void autoPowerOff() {
+ if (on && ((millis() - lastSwitchTime) > on_time_ms)) {
+ // Swipe OFF in the direction of the last sensor detection
+ swipe = lastSensor;
+ on = false;
+
+ if (swipe) {
+ Serial.println("OFF -> Swipe up.");
+ } else {
+ Serial.println("OFF -> Swipe down.");
+ }
+ }
+ }
+
+ void updateSwipe() {
+ if ((millis() - lastTime) > segment_delay_ms) {
+ lastTime = millis();
+
+ byte oldOnIndex = onIndex;
+ byte oldOffIndex = offIndex;
+
+ if (on) {
+ // Turn on all segments
+ onIndex = MAX(mainSegmentId, onIndex - 1);
+ offIndex = MIN(maxSegmentId + 1, offIndex + 1);
+ } else {
+ if (swipe == SWIPE_UP) {
+ onIndex = MIN(offIndex, onIndex + 1);
+ } else {
+ offIndex = MAX(onIndex, offIndex - 1);
+ }
+ }
+
+ updateSegments();
+ }
+ }
+
+ void writeSettingsToJson(JsonObject& root) {
+ JsonObject staircase = root["staircase"];
+ if (staircase.isNull()) {
+ staircase = root.createNestedObject("staircase");
+ }
+ staircase["enabled"] = enabled;
+ staircase["segment-delay-ms"] = segment_delay_ms;
+ staircase["on-time-s"] = on_time_ms / 1000;
+
+#ifdef TOP_TRIGGER_PIN
+ staircase["top-echo-us"] = topMaxTimeUs;
+#endif
+#ifdef BOTTOM_TRIGGER_PIN
+ staircase["bottom-echo-us"] = bottomMaxTimeUs;
+#endif
+ }
+
+ void writeSensorsToJson(JsonObject& root) {
+ JsonObject staircase = root["staircase"];
+ if (staircase.isNull()) {
+ staircase = root.createNestedObject("staircase");
+ }
+ staircase["top-sensor"] = topSensorRead;
+ staircase["bottom-sensor"] = bottomSensorRead;
+ }
+
+ bool readSettingsFromJson(JsonObject& root) {
+ JsonObject staircase = root["staircase"];
+ bool changed = false;
+
+ bool shouldEnable = staircase["enabled"] | enabled;
+ if (shouldEnable != enabled) {
+ enable(shouldEnable);
+ changed = true;
+ }
+
+ unsigned long c_segment_delay_ms = staircase["segment-delay-ms"] | segment_delay_ms;
+ if (c_segment_delay_ms != segment_delay_ms) {
+ segment_delay_ms = c_segment_delay_ms;
+ changed = true;
+ }
+
+ unsigned long c_on_time_ms = (staircase["on-time-s"] | (on_time_ms / 1000)) * 1000;
+ if (c_on_time_ms != on_time_ms) {
+ on_time_ms = c_on_time_ms;
+ changed = true;
+ }
+
+#ifdef TOP_TRIGGER_PIN
+ unsigned int c_topMaxTimeUs = staircase["top-echo-us"] | topMaxTimeUs;
+ if (c_topMaxTimeUs != topMaxTimeUs) {
+ topMaxTimeUs = c_topMaxTimeUs;
+ changed = true;
+ }
+#endif
+#ifdef BOTTOM_TRIGGER_PIN
+ unsigned int c_bottomMaxTimeUs = staircase["bottom-echo-us"] | bottomMaxTimeUs;
+ if (c_bottomMaxTimeUs != bottomMaxTimeUs) {
+ bottomMaxTimeUs = c_bottomMaxTimeUs;
+ changed = true;
+ }
+#endif
+
+ return changed;
+ }
+
+ void readSensorsFromJson(JsonObject& root) {
+ JsonObject staircase = root["staircase"];
+ bottomSensorWrite = bottomSensorRead || (staircase["bottom-sensor"].as());
+ topSensorWrite = topSensorRead || (staircase["top-sensor"].as());
+ }
+
+ void enable(bool enable) {
+ if (enable) {
+ Serial.println("Animated Staircase enabled.");
+ Serial.print("Delay between steps: ");
+ Serial.print(segment_delay_ms, DEC);
+ Serial.print(" milliseconds.\nStairs switch off after: ");
+ Serial.print(on_time_ms / 1000, DEC);
+ Serial.println(" seconds.");
+
+#ifdef BOTTOM_PIR_PIN
+ pinMode(BOTTOM_PIR_PIN, INPUT);
+#else
+ pinMode(BOTTOM_TRIGGER_PIN, OUTPUT);
+ pinMode(BOTTOM_ECHO_PIN, INPUT);
+#endif
+
+#ifdef TOP_PIR_PIN
+ pinMode(TOP_PIR_PIN, INPUT);
+#else
+ pinMode(TOP_TRIGGER_PIN, OUTPUT);
+ pinMode(TOP_ECHO_PIN, INPUT);
+#endif
+ } else {
+ // Restore segment options
+ WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId);
+ WS2812FX::Segment* segments = strip.getSegments();
+ for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
+ if (!segments->isActive()) {
+ maxSegmentId = i - 1;
+ break;
+ }
+ segments->setOption(SEG_OPTION_ON, 1, 1);
+ }
+ colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
+ Serial.println("Animated Staircase disabled.");
+ }
+ enabled = enable;
+ }
+
+ public:
+ void setup() { enable(enabled); }
+
+ void loop() {
+ // Write changed settings from to flash (see readFromJsonState())
+ if (saveState) {
+ serializeConfig();
+ saveState = false;
+ }
+
+ if (!enabled) {
+ return;
+ }
+
+ checkSensors();
+ autoPowerOff();
+ updateSwipe();
+
+ }
+
+ uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; }
+
+ /*
+ * Shows configuration settings to the json API. This object looks like:
+ *
+ * "staircase" : {
+ * "enabled" : true
+ * "segment-delay-ms" : 150,
+ * "on-time-s" : 5
+ * }
+ *
+ */
+ void addToJsonState(JsonObject& root) {
+ writeSettingsToJson(root);
+ writeSensorsToJson(root);
+ Serial.println("Staircase config exposed in API.");
+ }
+
+ /*
+ * Reads configuration settings from the json API.
+ * See void addToJsonState(JsonObject& root)
+ */
+ void readFromJsonState(JsonObject& root) {
+ // The call to serializeConfig() must be done in the main loop,
+ // so we set a flag to signal the main loop to save state.
+ saveState = readSettingsFromJson(root);
+ readSensorsFromJson(root);
+ Serial.println("Staircase config read from API.");
+ }
+
+ /*
+ * Writes the configuration to internal flash memory.
+ */
+ void addToConfig(JsonObject& root) {
+ writeSettingsToJson(root);
+ Serial.println("Staircase config saved.");
+ }
+
+ /*
+ * Reads the configuration to internal flash memory before setup() is called.
+ */
+ void readFromConfig(JsonObject& root) {
+ readSettingsFromJson(root);
+ Serial.println("Staircase config loaded.");
+ }
+
+ /*
+ * Shows the delay between steps and power-off time in the "info"
+ * tab of the web-UI.
+ */
+ void addToJsonInfo(JsonObject& root) {
+ JsonObject staircase = root["u"];
+ if (staircase.isNull()) {
+ staircase = root.createNestedObject("u");
+ }
+
+ if (enabled) {
+ JsonArray usermodEnabled =
+ staircase.createNestedArray("Staircase enabled"); // name
+ usermodEnabled.add("yes"); // value
+
+ JsonArray segmentDelay =
+ staircase.createNestedArray("Delay between stairs"); // name
+ segmentDelay.add(segment_delay_ms); // value
+ segmentDelay.add(" milliseconds"); // unit
+
+ JsonArray onTime =
+ staircase.createNestedArray("Power-off stairs after"); // name
+ onTime.add(on_time_ms / 1000); // value
+ onTime.add(" seconds"); // unit
+ } else {
+ JsonArray usermodEnabled =
+ staircase.createNestedArray("Staircase enabled"); // name
+ usermodEnabled.add("no"); // value
+ }
+ }
+};
\ No newline at end of file
diff --git a/usermods/Animated_Staircase/Animated_Staircase_config.h b/usermods/Animated_Staircase/Animated_Staircase_config.h
new file mode 100644
index 0000000000..9805074771
--- /dev/null
+++ b/usermods/Animated_Staircase/Animated_Staircase_config.h
@@ -0,0 +1,21 @@
+/*
+ * Animated_Staircase compiletime confguration.
+ *
+ * Please see README.md on how to change this file.
+ */
+
+// Please change the pin numbering below to match your board.
+#define TOP_PIR_PIN D5
+#define BOTTOM_PIR_PIN D6
+
+// Or uncumment and a pir and use an ultrasound HC-SR04 sensor,
+// see README.md for details
+#ifndef TOP_PIR_PIN
+#define TOP_TRIGGER_PIN D2
+#define TOP_ECHO_PIN D3
+#endif
+
+#ifndef BOTTOM_PIR_PIN
+#define BOTTOM_TRIGGER_PIN D4
+#define BOTTOM_ECHO_PIN D5
+#endif
\ No newline at end of file
diff --git a/usermods/Animated_Staircase/README.md b/usermods/Animated_Staircase/README.md
new file mode 100644
index 0000000000..6e84b5444f
--- /dev/null
+++ b/usermods/Animated_Staircase/README.md
@@ -0,0 +1,203 @@
+# Usermod Animated Staircase
+This usermod makes your staircase look cool by switching it on with an animation. It uses
+PIR or ultrasonic sensors at the top and bottom of your stairs to:
+
+- Light up the steps in your walking direction, leading the way.
+- Switch off the steps after you, in the direction of the last detected movement.
+- Always switch on when one of the sensors detects movement, even if an effect
+ is still running. It can therewith handle multiple people on the stairs gracefully.
+
+The Animated Staircase can be controlled by the WLED API. Change settings such as
+speed, on/off time and distance settings by sending an HTTP request, see below.
+
+## WLED integration
+To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://github.com/Aircoookie/WLED/wiki/Compiling-WLED).
+
+Before compiling, you have to make the following modifications:
+
+Edit `usermods_list.cpp`:
+1. Open `wled00/usermods_list.cpp`
+2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file
+3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function.
+
+Edit `Animated_Staircase_config.h`:
+1. Open `usermods/Animated_Staircase/Animated_Staircase_config.h`
+2. To use PIR sensors, change these lines to match your setup:
+ Using D7 and D6 pin notation as used on several boards:
+
+ ```cpp
+ #define TOP_PIR_PIN D7
+ #define BOTTOM_PIR_PIN D6
+ ```
+
+ Or using GPIO numbering for pins 25 and 26:
+ ```cpp
+ #define TOP_PIR_PIN 26
+ #define BOTTOM_PIR_PIN 25
+ ```
+
+ To use Ultrasonic HC-SR04 sensors instead of (one of the) PIR sensors,
+ uncomment one of the PIR sensor lines and adjust the pin numbers for the
+ connected Ultrasonic sensor. In the example below we use an Ultrasonic
+ sensor at the bottom of the stairs:
+
+ ```cpp
+ #define TOP_PIR_PIN 32
+ //#define BOTTOM_PIR_PIN D6 /* This PIR sensor is disabled */
+
+ #ifndef TOP_PIR_PIN
+ #define TOP_SIGNAL_PIN D2
+ #define TOP_ECHO_PIN D3
+ #endif
+
+ #ifndef BOTTOM_PIR_PIN /* If the bottom PIR is disabled, */
+ #define BOTTOM_SIGNAL_PIN 25 /* This Ultrasonic sensor is used */
+ #define BOTTOM_ECHO_PIN 26
+ #endif
+ ```
+
+After these modifications, compile and upload your WLED binary to your board
+and check the WLED info page to see if this usermod is enabled.
+
+## Hardware installation
+1. Stick the LED strip under each step of the stairs.
+2. Connect the ESP8266 pin D4 or ESP32 pin D2 to the first LED data pin at the bottom step
+ of your stairs.
+3. Connect the data-out pin at the end of each strip per step to the data-in pin on the
+ other end of the next step, creating one large virtual LED strip.
+4. Mount sensors of choice at the bottom and top of the stairs and connect them to the ESP.
+5. To make sure all LEDs get enough power and have your staircase lighted evenly, power each
+ step from one side, using at least AWG14 or 2.5mm^2 cable. Don't connect them serial as you
+ do for the datacable!
+
+You _may_ need to use 10k pull-down resistors on the selected PIR pins, depending on the sensor.
+
+## WLED configuration
+1. In the WLED UI, confgure a segment for each step. The lowest step of the stairs is the
+ lowest segment id.
+2. Save your segments into a preset.
+3. Ideally, add the preset in the config > LED setup menu to the "apply
+ preset **n** at boot" setting.
+
+## Changing behavior through API
+The Staircase settings can be changed through the WLED JSON api.
+
+**NOTE:** We are using [curl](https://curl.se/) to send HTTP POSTs to the WLED API.
+If you're using Windows and want to use the curl commands, replace the `\` with a `^`
+or remove them and put everything on one line.
+
+
+| Setting | Description | Default |
+|------------------|---------------------------------------------------------------|---------|
+| enabled | Enable or disable the usermod | true |
+| segment-delay-ms | Delay (milliseconds) between switching on/off each step | 150 |
+| on-time-s | Time (seconds) the stairs stay lit after last detection | 5 |
+| bottom-echo-us | Detection range of ultrasonic sensor | 1749 |
+| bottomsensor | Manually trigger a down to up animation via API | false |
+| topsensor | Manually trigger an up to down animation via API | false |
+
+
+To read the current settings, open a browser to `http://xxx.xxx.xxx.xxx/json/state` (use your WLED
+device IP address). The device will respond with a json object containing all WLED settings.
+The staircase settings and sensor states are inside the WLED status element:
+
+```json
+{
+ "state": {
+ "staircase": {
+ "enabled": true,
+ "segment-delay-ms": 150,
+ "on-time-s": 5,
+ "bottomsensor": false,
+ "topsensor": false
+ },
+}
+```
+
+### Enable/disable the usermod
+By disabling the usermod you will be able to keep the LED's on, independent from the sensor
+activity. This enables to play with the lights without the usermod switching them on or off.
+
+To disable the usermod:
+
+```bash
+curl -X POST -H "Content-Type: application/json" \
+ -d {"staircase":{"enabled":false}} \
+ xxx.xxx.xxx.xxx/json/state
+```
+
+To enable the usermod again, use `"enabled":true`.
+
+### Changing animation parameters
+To change the delay between the steps to (for example) 100 milliseconds and the on-time to
+10 seconds:
+
+```bash
+curl -X POST -H "Content-Type: application/json" \
+ -d '{"staircase":{"segment-delay-ms":100,"on-time-s":10}}' \
+ xxx.xxx.xxx.xxx/json/state
+```
+
+### Changing detection range of the ultrasonic HC-SR04 sensor
+When an ultrasonic sensor is enabled in `Animated_Staircase_config.h`, you'll see a
+`bottom-echo-us` setting appear in the json api:
+
+```json
+{
+ "state": {
+ "staircase": {
+ "enabled": true,
+ "segment-delay-ms": 150,
+ "on-time-s": 5,
+ "bottom-echo-us": 1749
+ },
+}
+```
+
+If the HC-SR04 sensor detects an echo within 1749 microseconds (corresponding to ~30 cm
+detection range from the sensor), it will trigger switching on the staircase. This setting
+can be changed through the API with an HTTP POST:
+
+```bash
+curl -X POST -H "Content-Type: application/json" \
+ -d '{"staircase":{"bottom-echo-us":1166}}' \
+ xxx.xxx.xxx.xxx/json/state
+```
+
+Calculating the detection range can be performed as follows: The speed of sound is 343m/s at 20
+degrees Centigrade. Since the sound has to travel back and forth, the detection range for the
+sensor in cm is (0.0343 * maxTimeUs) / 2. To get you started, please find delays and distances below:
+
+| Distance | Detection time |
+|---------:|----------------:|
+| 5 cm | 292 uS |
+| 10 cm | 583 uS |
+| 20 cm | 1166 uS |
+| 30 cm | 1749 uS |
+| 50 cm | 2915 uS |
+| 100 cm | 5831 uS |
+
+**Please note:** that using an HC-SR04 sensor, particularly when detecting echos at longer
+distances creates delays in the WLED software, and _might_ introduce timing hickups in your animations or
+a less responsive web interface. It is therefore advised to keep the detection time as short as possible.
+
+### Animation triggering through the API
+Instead of stairs activation by one of the sensors, you can also trigger the animation through
+the API. To simulate triggering the bottom sensor, use:
+
+```bash
+curl -X POST -H "Content-Type: application/json" \
+ -d '{"staircase":{"bottomsensor":true}}' \
+ xxx.xxx.xxx.xxx/json/state
+```
+
+Likewise, to trigger the top sensor, use:
+
+```bash
+curl -X POST -H "Content-Type: application/json" \
+ -d '{"staircase":{"topsensor":true}}' \
+ xxx.xxx.xxx.xxx/json/state
+```
+
+Have fun with this usermod.
+www.rolfje.com
diff --git a/usermods/Artemis_reciever/readme.md b/usermods/Artemis_reciever/readme.md
new file mode 100644
index 0000000000..11b949085b
--- /dev/null
+++ b/usermods/Artemis_reciever/readme.md
@@ -0,0 +1,5 @@
+Usermod to allow WLED to receive via UDP port from RGB.NET (and therefore add as a device to be controlled within artemis on PC)
+
+This is only a very simple code to support a single led strip, it does not support the full function of the RGB.NET sketch for esp8266 only what is needed to be used with Artemis. It will show as a ws281x device in artemis when you provide the correct hostname or ip. Artemis queries the number of LEDs via the web interface (/config) but communication to set the LEDs is all done via the UDP interface.
+
+To install, copy the usermod.cpp file to wled00 folder and recompile
\ No newline at end of file
diff --git a/usermods/Artemis_reciever/usermod.cpp b/usermods/Artemis_reciever/usermod.cpp
new file mode 100644
index 0000000000..2273685255
--- /dev/null
+++ b/usermods/Artemis_reciever/usermod.cpp
@@ -0,0 +1,93 @@
+/*
+ * RGB.NET (artemis) receiver
+ *
+ * This works via the UDP, http is not supported apart from reporting LED count
+ *
+ *
+ */
+#include "wled.h"
+#include
+
+WiFiUDP UDP;
+const unsigned int RGBNET_localUdpPort = 1872; // local port to listen on
+unsigned char RGBNET_packet[770];
+long lastTime = 0;
+int delayMs = 10;
+bool isRGBNETUDPEnabled;
+
+void RGBNET_readValues() {
+
+ int RGBNET_packetSize = UDP.parsePacket();
+ if (RGBNET_packetSize) {
+ // receive incoming UDP packets
+ int sequenceNumber = UDP.read();
+ int channel = UDP.read();
+
+ //channel data is not used we only supports one channel
+ int len = UDP.read(RGBNET_packet, ledCount*3);
+ if(len==0){
+ return;
+ }
+
+ for (int i = 0; i < len; i=i+3) {
+ strip.setPixelColor(i/3, RGBNET_packet[i], RGBNET_packet[i+1], RGBNET_packet[i+2], 0);
+ }
+ //strip.show();
+ }
+}
+
+//update LED strip
+void RGBNET_show() {
+ strip.show();
+ lastTime = millis();
+}
+
+//This function provides a json with info on the number of LEDs connected
+// it is needed by artemis to know how many LEDs to display on the surface
+void handleConfig(AsyncWebServerRequest *request)
+{
+ String config = (String)"{\
+ \"channels\": [\
+ {\
+ \"channel\": 1,\
+ \"leds\": " + ledCount + "\
+ },\
+ {\
+ \"channel\": 2,\
+ \"leds\": " + "0" + "\
+ },\
+ {\
+ \"channel\": 3,\
+ \"leds\": " + "0" + "\
+ },\
+ {\
+ \"channel\": 4,\
+ \"leds\": " + "0" + "\
+ }\
+ ]\
+}";
+ request->send(200, "application/json", config);
+}
+
+
+void userSetup()
+{
+ server.on("/config", HTTP_GET, [](AsyncWebServerRequest *request){
+ handleConfig(request);
+ });
+}
+
+void userConnected()
+{
+ // new wifi, who dis?
+ UDP.begin(RGBNET_localUdpPort);
+ isRGBNETUDPEnabled = true;
+}
+
+void userLoop()
+{
+ RGBNET_readValues();
+ if (millis()-lastTime > delayMs) {
+ RGBNET_show();
+ }
+}
\ No newline at end of file
diff --git a/usermods/BME280_v2/README.md b/usermods/BME280_v2/README.md
new file mode 100644
index 0000000000..216ca63000
--- /dev/null
+++ b/usermods/BME280_v2/README.md
@@ -0,0 +1,40 @@
+Hello! I have written a v2 usermod for the BME280/BMP280 sensor based on the [existing v1 usermod](https://github.com/Aircoookie/WLED/blob/master/usermods/Wemos_D1_mini%2BWemos32_mini_shield/usermod_bme280.cpp). It is not just a refactor, there are many changes which I made to fit my use case, and I hope they will fit the use cases of others as well! Most notably, this usermod is *just* for the BME280 and does not control a display like in the v1 usermod designed for the WeMos shield.
+
+- Requires libraries `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280)) and `Wire`. Please add these under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
+- Data is published over MQTT so make sure you've enabled the MQTT sync interface.
+- This usermod also writes to serial (GPIO1 on ESP8266). Please make sure nothing else listening on the serial TX pin of your board will get confused by log messages!
+
+To enable, compile with `USERMOD_BME280` defined (i.e. `platformio_override.ini`)
+```ini
+build_flags =
+ ${common.build_flags_esp8266}
+ -D USERMOD_BME280
+```
+or define `USERMOD_BME280` in `my_config.h`
+```c++
+#define USERMOD_BME280
+```
+
+Changes include:
+- Adjustable measure intervals
+ - Temperature and pressure have separate intervals due to pressure not frequently changing at any constant altitude
+- Adjustment of number of decimal places in published sensor values
+ - Separate adjustment for temperature, humidity and pressure values
+ - Values are rounded to the specified number of decimal places
+- Pressure measured in units of hPa instead of Pa
+- Calculation of heat index (apparent temperature) and dew point
+ - These, along with humidity measurements, are disabled if the sensor is a BMP280
+- 16x oversampling of sensor during measurement
+- Values are only published if they are different from the previous value
+- Values are published on startup (continually until the MQTT broker acknowledges a successful publication)
+
+Adjustments are made through preprocessor definitions at the start of the class definition.
+
+MQTT topics are as follows:
+Measurement type | MQTT topic
+--- | ---
+Temperature | `/temperature`
+Humidity | `/humidity`
+Pressure | `/pressure`
+Heat index | `/heat_index`
+Dew point | `/dew_point`
\ No newline at end of file
diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h
new file mode 100644
index 0000000000..80a31a4f06
--- /dev/null
+++ b/usermods/BME280_v2/usermod_bme280.h
@@ -0,0 +1,212 @@
+#pragma once
+
+#include "wled.h"
+#include
+#include
+#include // BME280 sensor
+#include // BME280 extended measurements
+
+class UsermodBME280 : public Usermod
+{
+private:
+// User-defined configuration
+#define Celsius // Show temperature mesaurement in Celcius. Comment out for Fahrenheit
+#define TemperatureDecimals 1 // Number of decimal places in published temperaure values
+#define HumidityDecimals 0 // Number of decimal places in published humidity values
+#define PressureDecimals 2 // Number of decimal places in published pressure values
+#define TemperatureInterval 5 // Interval to measure temperature (and humidity, dew point if available) in seconds
+#define PressureInterval 300 // Interval to measure pressure in seconds
+
+// Sanity checks
+#if !defined(TemperatureDecimals) || TemperatureDecimals < 0
+ #define TemperatureDecimals 0
+#endif
+#if !defined(HumidityDecimals) || HumidityDecimals < 0
+ #define HumidityDecimals 0
+#endif
+#if !defined(PressureDecimals) || PressureDecimals < 0
+ #define PressureDecimals 0
+#endif
+#if !defined(TemperatureInterval) || TemperatureInterval < 0
+ #define TemperatureInterval 1
+#endif
+#if !defined(PressureInterval) || PressureInterval < 0
+ #define PressureInterval TemperatureInterval
+#endif
+
+#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
+ uint8_t SCL_PIN = 22;
+ uint8_t SDA_PIN = 21;
+#else // ESP8266 boards
+ uint8_t SCL_PIN = 5;
+ uint8_t SDA_PIN = 4;
+ //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
+#endif
+
+ // BME280 sensor settings
+ BME280I2C::Settings settings{
+ BME280::OSR_X16, // Temperature oversampling x16
+ BME280::OSR_X16, // Humidity oversampling x16
+ BME280::OSR_X16, // Pressure oversampling x16
+ // Defaults
+ BME280::Mode_Forced,
+ BME280::StandbyTime_1000ms,
+ BME280::Filter_Off,
+ BME280::SpiEnable_False,
+ BME280I2C::I2CAddr_0x76 // I2C address. I2C specific. Default 0x76
+ };
+
+ BME280I2C bme{settings};
+
+ uint8_t SensorType;
+
+ // Measurement timers
+ long timer;
+ long lastTemperatureMeasure = 0;
+ long lastPressureMeasure = 0;
+
+ // Current sensor values
+ float SensorTemperature;
+ float SensorHumidity;
+ float SensorHeatIndex;
+ float SensorDewPoint;
+ float SensorPressure;
+ // Track previous sensor values
+ float lastTemperature;
+ float lastHumidity;
+ float lastHeatIndex;
+ float lastDewPoint;
+ float lastPressure;
+
+ // Store packet IDs of MQTT publications
+ uint16_t mqttTemperaturePub = 0;
+ uint16_t mqttPressurePub = 0;
+
+ void UpdateBME280Data(int SensorType)
+ {
+ float _temperature, _humidity, _pressure;
+ #ifdef Celsius
+ BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
+ EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
+ #else
+ BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit);
+ EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Fahrenheit);
+ #endif
+ BME280::PresUnit presUnit(BME280::PresUnit_hPa);
+
+ bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
+
+ SensorTemperature = _temperature;
+ SensorHumidity = _humidity;
+ SensorPressure = _pressure;
+ if (SensorType == 1)
+ {
+ SensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
+ SensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
+ }
+ }
+
+public:
+ void setup()
+ {
+ Wire.begin(SDA_PIN, SCL_PIN);
+
+ if (!bme.begin())
+ {
+ SensorType = 0;
+ Serial.println("Could not find BME280I2C sensor!");
+ }
+ else
+ {
+ switch (bme.chipModel())
+ {
+ case BME280::ChipModel_BME280:
+ SensorType = 1;
+ Serial.println("Found BME280 sensor! Success.");
+ break;
+ case BME280::ChipModel_BMP280:
+ SensorType = 2;
+ Serial.println("Found BMP280 sensor! No Humidity available.");
+ break;
+ default:
+ SensorType = 0;
+ Serial.println("Found UNKNOWN sensor! Error!");
+ }
+ }
+ }
+
+ void loop()
+ {
+ // BME280 sensor MQTT publishing
+ // Check if sensor present and MQTT Connected, otherwise it will crash the MCU
+ if (SensorType != 0 && mqtt != nullptr)
+ {
+ // Timer to fetch new temperature, humidity and pressure data at intervals
+ timer = millis();
+
+ if (timer - lastTemperatureMeasure >= TemperatureInterval * 1000 || mqttTemperaturePub == 0)
+ {
+ lastTemperatureMeasure = timer;
+
+ UpdateBME280Data(SensorType);
+
+ float Temperature = roundf(SensorTemperature * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
+ float Humidity, HeatIndex, DewPoint;
+
+ // If temperature has changed since last measure, create string populated with device topic
+ // from the UI and values read from sensor, then publish to broker
+ if (Temperature != lastTemperature)
+ {
+ String topic = String(mqttDeviceTopic) + "/temperature";
+ mqttTemperaturePub = mqtt->publish(topic.c_str(), 0, false, String(Temperature, TemperatureDecimals).c_str());
+ }
+
+ lastTemperature = Temperature; // Update last sensor temperature for next loop
+
+ if (SensorType == 1) // Only if sensor is a BME280
+ {
+ Humidity = roundf(SensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals);
+ HeatIndex = roundf(SensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
+ DewPoint = roundf(SensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
+
+ if (Humidity != lastHumidity)
+ {
+ String topic = String(mqttDeviceTopic) + "/humidity";
+ mqtt->publish(topic.c_str(), 0, false, String(Humidity, HumidityDecimals).c_str());
+ }
+
+ if (HeatIndex != lastHeatIndex)
+ {
+ String topic = String(mqttDeviceTopic) + "/heat_index";
+ mqtt->publish(topic.c_str(), 0, false, String(HeatIndex, TemperatureDecimals).c_str());
+ }
+
+ if (DewPoint != lastDewPoint)
+ {
+ String topic = String(mqttDeviceTopic) + "/dew_point";
+ mqtt->publish(topic.c_str(), 0, false, String(DewPoint, TemperatureDecimals).c_str());
+ }
+
+ lastHumidity = Humidity;
+ lastHeatIndex = HeatIndex;
+ lastDewPoint = DewPoint;
+ }
+ }
+
+ if (timer - lastPressureMeasure >= PressureInterval * 1000 || mqttPressurePub == 0)
+ {
+ lastPressureMeasure = timer;
+
+ float Pressure = roundf(SensorPressure * pow(10, PressureDecimals)) / pow(10, PressureDecimals);
+
+ if (Pressure != lastPressure)
+ {
+ String topic = String(mqttDeviceTopic) + "/pressure";
+ mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(Pressure, PressureDecimals).c_str());
+ }
+
+ lastPressure = Pressure;
+ }
+ }
+ }
+};
\ No newline at end of file
diff --git a/usermods/DHT/platformio_override.ini b/usermods/DHT/platformio_override.ini
new file mode 100644
index 0000000000..1771fd17a3
--- /dev/null
+++ b/usermods/DHT/platformio_override.ini
@@ -0,0 +1,22 @@
+; Options
+; -------
+; USERMOD_DHT - define this to have this user mod included wled00\usermods_list.cpp
+; USERMOD_DHT_DHTTYPE - DHT model: 11, 21, 22 for DHT11, DHT21, or DHT22, defaults to 22/DHT22
+; USERMOD_DHT_PIN - pin to which DTH is connected, defaults to Q2 pin on QuinLed Dig-Uno's board
+; USERMOD_DHT_CELSIUS - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported
+; USERMOD_DHT_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds
+; USERMOD_DHT_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 90 seconds
+; USERMOD_DHT_STATS - For debug, report delay stats
+
+[env:d1_mini_usermod_dht_C]
+extends = env:d1_mini
+build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS
+lib_deps = ${env.lib_deps}
+ https://github.com/alwynallan/DHT_nonblocking
+
+[env:custom32_LEDPIN_16_usermod_dht_C]
+extends = env:custom32_LEDPIN_16
+build_flags = ${env:custom32_LEDPIN_16.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS -D USERMOD_DHT_STATS
+lib_deps = ${env.lib_deps}
+ https://github.com/alwynallan/DHT_nonblocking
+
diff --git a/usermods/DHT/readme.md b/usermods/DHT/readme.md
new file mode 100644
index 0000000000..5a123d4bde
--- /dev/null
+++ b/usermods/DHT/readme.md
@@ -0,0 +1,41 @@
+# DHT Temperature/Humidity sensor usermod
+
+This usermod will read from an attached DHT22 or DHT11 humidity and temperature sensor.
+The sensor readings are displayed in the Info section of the web UI.
+
+If sensor is not detected after a while (10 update intervals), this usermod will be disabled.
+
+## Installation
+
+Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`.
+
+### Define Your Options
+
+* `USERMOD_DHT` - define this to have this user mod included wled00\usermods_list.cpp
+* `USERMOD_DHT_DHTTYPE` - DHT model: 11, 21, 22 for DHT11, DHT21, or DHT22, defaults to 22/DHT22
+* `USERMOD_DHT_PIN` - pin to which DTH is connected, defaults to Q2 pin on QuinLed Dig-Uno's board
+* `USERMOD_DHT_CELSIUS` - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported
+* `USERMOD_DHT_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60 seconds
+* `USERMOD_DHT_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 90 seconds
+* `USERMOD_DHT_STATS` - For debug, report delay stats
+
+## Project link
+
+* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link
+
+### PlatformIO requirements
+
+If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:d1_mini_usermod_dht_C`. If not, you can add the libraries and dependencies into `platformio.ini` as you see fit.
+
+
+## Change Log
+
+2020-02-04
+* Change default QuinLed pin to Q2
+* Instead of trying to keep updates at constant cadence, space readings out by measurement interval; hope this helps to avoid occasional bursts of readings with errors
+* Add some more (optional) stats
+2020-02-03
+* Due to poor readouts on ESP32 with previous DHT library, rewrote to use https://github.com/alwynallan/DHT_nonblocking
+* The new library serializes/delays up to 5ms for the sensor readout
+2020-02-02
+* Created
diff --git a/usermods/DHT/usermod_dht.h b/usermods/DHT/usermod_dht.h
new file mode 100644
index 0000000000..9f46734f83
--- /dev/null
+++ b/usermods/DHT/usermod_dht.h
@@ -0,0 +1,216 @@
+#pragma once
+
+#include "wled.h"
+
+#include
+
+// USERMOD_DHT_DHTTYPE:
+// 11 // DHT 11
+// 21 // DHT 21
+// 22 // DHT 22 (AM2302), AM2321 *** default
+#ifndef USERMOD_DHT_DHTTYPE
+#define USERMOD_DHT_DHTTYPE 22
+#endif
+
+#if USERMOD_DHT_DHTTYPE == 11
+#define DHTTYPE DHT_TYPE_11
+#elif USERMOD_DHT_DHTTYPE == 21
+#define DHTTYPE DHT_TYPE_21
+#elif USERMOD_DHT_DHTTYPE == 22
+#define DHTTYPE DHT_TYPE_22
+#endif
+
+// Connect pin 1 (on the left) of the sensor to +5V
+// NOTE: If using a board with 3.3V logic like an Arduino Due connect pin 1
+// to 3.3V instead of 5V!
+// Connect pin 2 of the sensor to whatever your DHTPIN is
+// NOTE: Pin defaults below are for QuinLed Dig-Uno's Q2 on the board
+// Connect pin 4 (on the right) of the sensor to GROUND
+// NOTE: If using a bare sensor (AM*), Connect a 10K resistor from pin 2
+// (data) to pin 1 (power) of the sensor. DHT* boards have the pullup already
+
+#ifdef USERMOD_DHT_PIN
+#define DHTPIN USERMOD_DHT_PIN
+#else
+#ifdef ARDUINO_ARCH_ESP32
+#define DHTPIN 21
+#else //ESP8266 boards
+#define DHTPIN 4
+#endif
+#endif
+
+// the frequency to check sensor, 1 minute
+#ifndef USERMOD_DHT_MEASUREMENT_INTERVAL
+#define USERMOD_DHT_MEASUREMENT_INTERVAL 60000
+#endif
+
+// how many seconds after boot to take first measurement, 90 seconds
+// 90 gives enough time to OTA update firmware if this crashses
+#ifndef USERMOD_DHT_FIRST_MEASUREMENT_AT
+#define USERMOD_DHT_FIRST_MEASUREMENT_AT 90000
+#endif
+
+// from COOLDOWN_TIME in dht_nonblocking.cpp
+#define DHT_TIMEOUT_TIME 10000
+
+DHT_nonblocking dht_sensor(DHTPIN, DHTTYPE);
+
+class UsermodDHT : public Usermod {
+ private:
+ unsigned long nextReadTime = 0;
+ unsigned long lastReadTime = 0;
+ float humidity, temperature = 0;
+ bool initializing = true;
+ bool disabled = false;
+ #ifdef USERMOD_DHT_STATS
+ unsigned long nextResetStatsTime = 0;
+ uint16_t updates = 0;
+ uint16_t clean_updates = 0;
+ uint16_t errors = 0;
+ unsigned long maxDelay = 0;
+ unsigned long currentIteration = 0;
+ unsigned long maxIteration = 0;
+ #endif
+
+ public:
+ void setup() {
+ nextReadTime = millis() + USERMOD_DHT_FIRST_MEASUREMENT_AT;
+ lastReadTime = millis();
+ #ifdef USERMOD_DHT_STATS
+ nextResetStatsTime = millis() + 60*60*1000;
+ #endif
+ }
+
+ void loop() {
+ if (disabled) {
+ return;
+ }
+ if (millis() < nextReadTime) {
+ return;
+ }
+
+ #ifdef USERMOD_DHT_STATS
+ if (millis() >= nextResetStatsTime) {
+ nextResetStatsTime += 60*60*1000;
+ errors = 0;
+ updates = 0;
+ clean_updates = 0;
+ }
+ unsigned long dcalc = millis();
+ if (currentIteration == 0) {
+ currentIteration = millis();
+ }
+ #endif
+
+ float tempC;
+ if (dht_sensor.measure(&tempC, &humidity)) {
+ #ifdef USERMOD_DHT_CELSIUS
+ temperature = tempC;
+ #else
+ temperature = tempC * 9 / 5 + 32;
+ #endif
+
+ nextReadTime = millis() + USERMOD_DHT_MEASUREMENT_INTERVAL;
+ lastReadTime = millis();
+ initializing = false;
+
+ #ifdef USERMOD_DHT_STATS
+ unsigned long icalc = millis() - currentIteration;
+ if (icalc > maxIteration) {
+ maxIteration = icalc;
+ }
+ if (icalc > DHT_TIMEOUT_TIME) {
+ errors += icalc/DHT_TIMEOUT_TIME;
+ } else {
+ clean_updates += 1;
+ }
+ updates += 1;
+ currentIteration = 0;
+
+ #endif
+ }
+
+ #ifdef USERMOD_DHT_STATS
+ dcalc = millis() - dcalc;
+ if (dcalc > maxDelay) {
+ maxDelay = dcalc;
+ }
+ #endif
+
+ if (((millis() - lastReadTime) > 10*USERMOD_DHT_MEASUREMENT_INTERVAL)) {
+ disabled = true;
+ }
+ }
+
+ void addToJsonInfo(JsonObject& root) {
+ if (disabled) {
+ return;
+ }
+ JsonObject user = root["u"];
+ if (user.isNull()) user = root.createNestedObject("u");
+
+ JsonArray temp = user.createNestedArray("Temperature");
+ JsonArray hum = user.createNestedArray("Humidity");
+
+ #ifdef USERMOD_DHT_STATS
+ JsonArray next = user.createNestedArray("next");
+ if (nextReadTime >= millis()) {
+ next.add((nextReadTime - millis()) / 1000);
+ next.add(" sec until read");
+ } else {
+ next.add((millis() - nextReadTime) / 1000);
+ next.add(" sec active reading");
+ }
+
+ JsonArray last = user.createNestedArray("last");
+ last.add((millis() - lastReadTime) / 60000);
+ last.add(" min since read");
+
+ JsonArray err = user.createNestedArray("errors");
+ err.add(errors);
+ err.add(" Errors");
+
+ JsonArray upd = user.createNestedArray("updates");
+ upd.add(updates);
+ upd.add(" Updates");
+
+ JsonArray cupd = user.createNestedArray("cleanUpdates");
+ cupd.add(clean_updates);
+ cupd.add(" Updates");
+
+ JsonArray iter = user.createNestedArray("maxIter");
+ iter.add(maxIteration);
+ iter.add(" ms");
+
+ JsonArray delay = user.createNestedArray("maxDelay");
+ delay.add(maxDelay);
+ delay.add(" ms");
+ #endif
+
+ if (initializing) {
+ // if we haven't read the sensor yet, let the user know
+ // that we are still waiting for the first measurement
+ temp.add((nextReadTime - millis()) / 1000);
+ temp.add(" sec until read");
+ hum.add((nextReadTime - millis()) / 1000);
+ hum.add(" sec until read");
+ return;
+ }
+
+ hum.add(humidity);
+ hum.add("%");
+
+ temp.add(temperature);
+ #ifdef USERMOD_DHT_CELSIUS
+ temp.add("°C");
+ #else
+ temp.add("°F");
+ #endif
+ }
+
+ uint16_t getId()
+ {
+ return USERMOD_ID_DHT;
+ }
+
+};
diff --git a/usermods/Fix_unreachable_netservices_v2/readme.md b/usermods/Fix_unreachable_netservices_v2/readme.md
index f7d2aed6d9..f7b721dd88 100644
--- a/usermods/Fix_unreachable_netservices_v2/readme.md
+++ b/usermods/Fix_unreachable_netservices_v2/readme.md
@@ -1,17 +1,32 @@
# Fix unreachable net services V2
+**Attention: This usermod compiles only for ESP8266**
+
This usermod-v2 modification performs a ping request to the local IP address every 60 seconds. By this procedure the net services of WLED remains accessible in some problematic WLAN environments.
The modification works with static or DHCP IP address configuration.
-**Webinterface**: The number of pings and reconnects is displayed on the info page in the web interface.
-
_Story:_
Unfortunately, with all ESP projects where a web server or other network services are running, I have the problem that after some time the web server is no longer accessible. Now I found out that the connection is at least reestablished when a ping request is executed by the device.
With this modification, in the worst case, the network functions are not available for 60 seconds until the next ping request.
+## Webinterface
+
+The number of pings and reconnects is displayed on the info page in the web interface.
+The ping delay can be changed. Changes persist after a reboot.
+
+## JSON API
+
+The usermod supports the following state changes:
+
+| JSON key | Value range | Description |
+|-------------|------------------|---------------------------------|
+| PingDelayMs | 5000 to 18000000 | Deactivdate/activate the sensor |
+
+ Changes also persist after a reboot.
+
## Installation
1. Copy the file `usermod_Fix_unreachable_netservices.h` to the `wled00` directory.
diff --git a/usermods/Fix_unreachable_netservices_v2/usermod_Fix_unreachable_netservices.h b/usermods/Fix_unreachable_netservices_v2/usermod_Fix_unreachable_netservices.h
index 8ffc821edd..cb2f1b0c3e 100644
--- a/usermods/Fix_unreachable_netservices_v2/usermod_Fix_unreachable_netservices.h
+++ b/usermods/Fix_unreachable_netservices_v2/usermod_Fix_unreachable_netservices.h
@@ -1,6 +1,14 @@
#pragma once
#include "wled.h"
+#if defined(ESP32)
+#warning "Usermod FixUnreachableNetServices works only with ESP8266 builds"
+class FixUnreachableNetServices : public Usermod
+{
+};
+#endif
+
+#if defined(ESP8266)
#include
/*
@@ -23,116 +31,138 @@
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
*/
-class FixUnreachableNetServices : public Usermod {
- private:
- //Private class members. You can declare variables and functions only accessible to your usermod here
- unsigned long m_lastTime = 0;
-
- // desclare required variables
- const unsigned int PingDelayMs = 60000;
- unsigned long m_connectedWiFi = 0;
- ping_option m_pingOpt;
- unsigned int m_pingCount = 0;
-
- 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() {
- //Serial.println("Hello from my usermod!");
- }
-
-
- /*
- * connected() is called every time the WiFi is (re)connected
- * Use it to initialize network interfaces
- */
- void connected() {
- //Serial.println("Connected to WiFi!");
-
- ++m_connectedWiFi;
-
- // initialize ping_options structure
- memset(&m_pingOpt, 0, sizeof(struct ping_option));
- m_pingOpt.count = 1;
- m_pingOpt.ip = WiFi.localIP();
-
- }
-
-
- /*
- * 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() {
- if (m_connectedWiFi > 0 && millis()-m_lastTime > PingDelayMs)
- {
- ping_start(&m_pingOpt);
- m_lastTime = millis();
- ++m_pingCount;
- }
- }
-
-
- /*
- * 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)
+class FixUnreachableNetServices : public Usermod
+{
+private:
+ //Private class members. You can declare variables and functions only accessible to your usermod here
+ unsigned long m_lastTime = 0;
+
+ // declare required variables
+ unsigned long m_pingDelayMs = 60000;
+ unsigned long m_connectedWiFi = 0;
+ ping_option m_pingOpt;
+ unsigned int m_pingCount = 0;
+ bool m_updateConfig = false;
+
+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()
+ {
+ //Serial.println("Hello from my usermod!");
+ }
+
+ /**
+ * connected() is called every time the WiFi is (re)connected
+ * Use it to initialize network interfaces
+ */
+ void connected()
+ {
+ //Serial.println("Connected to WiFi!");
+
+ ++m_connectedWiFi;
+
+ // initialize ping_options structure
+ memset(&m_pingOpt, 0, sizeof(struct ping_option));
+ m_pingOpt.count = 1;
+ m_pingOpt.ip = WiFi.localIP();
+ }
+
+ /**
+ * loop
+ */
+ void loop()
+ {
+ if (m_connectedWiFi > 0 && millis() - m_lastTime > m_pingDelayMs)
{
- //this code adds "u":{"⚡ Ping fix pings": m_pingCount} to the info object
- JsonObject user = root["u"];
- if (user.isNull()) user = root.createNestedObject("u");
-
- JsonArray infoArr = user.createNestedArray("⚡ Ping fix pings"); //name
- infoArr.add(m_pingCount); //value
-
- //this code adds "u":{"⚡ Reconnects": m_connectedWiFi - 1} to the info object
- infoArr = user.createNestedArray("⚡ Reconnects"); //name
- infoArr.add(m_connectedWiFi - 1); //value
+ ping_start(&m_pingOpt);
+ m_lastTime = millis();
+ ++m_pingCount;
}
-
-
- /*
- * 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)
+ if (m_updateConfig)
{
- //root["user0"] = userVar0;
+ serializeConfig();
+ m_updateConfig = false;
}
-
-
- /*
- * 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)
+ }
+
+ /**
+ * 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)
+ {
+ //this code adds "u":{"⚡ Ping fix pings": m_pingCount} to the info object
+ JsonObject user = root["u"];
+ if (user.isNull())
+ user = root.createNestedObject("u");
+
+ String uiDomString = "⚡ Ping fix pings\
+Delay sec";
+
+ JsonArray infoArr = user.createNestedArray(uiDomString); //name
+ infoArr.add(m_pingCount); //value
+
+ //this code adds "u":{"⚡ Reconnects": m_connectedWiFi - 1} to the info object
+ infoArr = user.createNestedArray("⚡ Reconnects"); //name
+ infoArr.add(m_connectedWiFi - 1); //value
+ }
+
+ /**
+ * 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["PingDelay"] = (m_pingDelayMs/1000);
+ }
+
+ /**
+ * 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)
+ {
+ if (root["PingDelay"] != nullptr)
{
- //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!"));
+ m_pingDelayMs = (1000 * max(1UL, min(300UL, root["PingDelay"].as())));
+ m_updateConfig = 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_FIXNETSERVICES;
- }
-
- //More methods can be added in the future, this example will then be extended.
- //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class!
+ }
+
+ /**
+ * provide the changeable values
+ */
+ void addToConfig(JsonObject &root)
+ {
+ JsonObject top = root.createNestedObject("FixUnreachableNetServices");
+ top["PingDelayMs"] = m_pingDelayMs;
+ }
+
+ /**
+ * restore the changeable values
+ */
+ void readFromConfig(JsonObject &root)
+ {
+ JsonObject top = root["FixUnreachableNetServices"];
+ m_pingDelayMs = top["PingDelayMs"] | m_pingDelayMs;
+ m_pingDelayMs = max(5000UL, min(18000000UL, m_pingDelayMs));
+ }
+
+ /**
+ * 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_FIXNETSERVICES;
+ }
};
+#endif
diff --git a/usermods/Fix_unreachable_webserver/readme.md b/usermods/Fix_unreachable_webserver/readme.md
deleted file mode 100644
index 5ed17b87c7..0000000000
--- a/usermods/Fix_unreachable_webserver/readme.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Fix unreachable Webserver
-
-This modification performs a ping request to the local IP address every 60 seconds. By this procedure the web server remains accessible in some problematic WLAN environments.
-
-The modification works with static or DHCP IP address configuration
-
-_Story:_
-
-Unfortunately, with all ESP projects where a web server or other network services are running, I have the problem that after some time the web server is no longer accessible. Now I found out that the connection is at least reestablished when a ping request is executed by the device.
-
-With this modification, in the worst case, the network functions are not available for 60 seconds until the next ping request.
-
-## Installation
-
-Copy and replace the file `usermod.cpp` in wled00 directory.
-
-
diff --git a/usermods/Fix_unreachable_webserver/usermod.cpp b/usermods/Fix_unreachable_webserver/usermod.cpp
deleted file mode 100644
index f1957da26b..0000000000
--- a/usermods/Fix_unreachable_webserver/usermod.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#include "wled.h"
-/*
- * This file allows you to add own functionality to WLED more easily
- * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
- * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h)
- * bytes 2400+ are currently ununsed, but might be used for future wled features
- */
-
-#include
-
-const int PingDelayMs = 60000;
-long lastCheckTime = 0;
-bool connectedWiFi = false;
-ping_option pingOpt;
-
-//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
-
-//gets called once at boot. Do all initialization that doesn't depend on network here
-void userSetup()
-{
-
-}
-
-
-//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
-void userConnected()
-{
- connectedWiFi = true;
- // initialize ping_options structure
- memset(&pingOpt, 0, sizeof(struct ping_option));
- pingOpt.count = 1;
- pingOpt.ip = WiFi.localIP();
-}
-
-//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
-void userLoop()
-{
- if (connectedWiFi && millis()-lastCheckTime > PingDelayMs)
- {
- ping_start(&pingOpt);
- lastCheckTime = millis();
- }
-}
diff --git a/usermods/PIR_sensor_switch/readme.md b/usermods/PIR_sensor_switch/readme.md
index 79556db7f8..3d00b50542 100644
--- a/usermods/PIR_sensor_switch/readme.md
+++ b/usermods/PIR_sensor_switch/readme.md
@@ -11,19 +11,21 @@ The LED strip is switched [using a relay](https://github.com/Aircoookie/WLED/wik
The info page in the web interface shows the items below
-- the state of the sensor. By clicking on the state the sensor can be deactivated/activated.
-**I recommend to deactivate the sensor before installing an OTA update**.
+- the state of the sensor. By clicking on the state the sensor can be deactivated/activated. Changes persist after a reboot.
+**I recommend to deactivate the sensor before an OTA update and activate it again afterwards**.
- the remaining time of the off timer.
## JSON API
-The usermod supports the following state changes:
+The usermod supports the following state changes:
| JSON key | Value range | Description |
|------------|-------------|---------------------------------|
| PIRenabled | bool | Deactivdate/activate the sensor |
| PIRoffSec | 60 to 43200 | Off timer seconds |
+ Changes also persist after a reboot.
+
## Sensor connection
My setup uses an HC-SR501 sensor, a HC-SR505 should also work.
@@ -55,7 +57,7 @@ Example **usermods_list.cpp**:
//#include "usermod_v2_example.h"
//#include "usermod_temperature.h"
//#include "usermod_v2_empty.h"
-#include "usermod_PIR_sensor_switch.h"
+#include "usermod_PIR_sensor_switch.h"
void registerUsermods()
{
@@ -72,26 +74,36 @@ void registerUsermods()
}
```
-## Usermod installation (advanced mode)
+## API to enable/disable the PIR sensor from outside. For example from another usermod.
-In this mode IR sensor will disable PIR when light ON by remote controller and enable PIR when light OFF.
+The class provides the static method `PIRsensorSwitch* PIRsensorSwitch::GetInstance()` to get a pointer to the usermod object.
-1. Copy the file `usermod_PIR_sensor_switch.h` to the `wled00` directory.
-2. Register the usermod by adding `#include "usermod_PIR_sensor_switch.h"` in the top and `registerUsermod(new PIRsensorSwitch());` in the bottom of `usermods_list.cpp`.
-3. Add to the line 237, on `wled.h` in the `wled00` directory:
-
- `WLED_GLOBAL bool m_PIRenabled _INIT(true); // enable PIR sensor`
-
-4. On `ir.cpp` in the `wled00` directory, add to the IR controller's mapping (beyond line 200):
-
-- To the off button:
- `m_PIRenabled = true;`
-
-- To the on button:
- `m_PIRenabled = false;`
-
-5. Edit line 40, on `usermod_PIR_sensor_switch.h` in the `wled00` directory:
-
- `\\bool m_PIRenabled = true;`
+To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available.
+
+### There are two options to get access to the usermod instance:
+
+1. Include `usermod_PIR_sensor_switch.h` **before** you include the other usermod in `usermods_list.cpp'
+
+or
+
+2. Use `#include "usermod_PIR_sensor_switch.h"` at the top of the `usermod.h` where you need it.
+
+**Example usermod.h :**
+```cpp
+#include "wled.h"
+
+#include "usermod_PIR_sensor_switch.h"
+
+class MyUsermod : public Usermod {
+ //...
+
+ void togglePIRSensor() {
+ if (PIRsensorSwitch::GetInstance() != nullptr) {
+ PIRsensorSwitch::GetInstance()->EnablePIRsensor(!PIRsensorSwitch::GetInstance()->PIRsensorEnabled());
+ }
+ }
+ //...
+};
+```
Have fun - @gegu
diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h
index e87147a1a0..421528bfe6 100644
--- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h
+++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h
@@ -24,228 +24,343 @@
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
*/
-class PIRsensorSwitch : public Usermod {
- private:
- // PIR sensor pin
- const uint8_t PIRsensorPin = 13; // D7 on D1 mini
- // notification mode for colorUpdated()
- const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE
- // delay before switch off after the sensor state goes LOW
- uint32_t m_switchOffDelay = 600000;
- // off timer start time
- uint32_t m_offTimerStart = 0;
- // current PIR sensor pin state
- byte m_PIRsensorPinState = LOW;
- // PIR sensor enabled - ISR attached
- bool m_PIRenabled = true;
-
- /*
- * return or change if new PIR sensor state is available
- */
- static volatile bool newPIRsensorState(bool changeState = false, bool newState = false) {
- static volatile bool s_PIRsensorState = false;
- if (changeState) {
- s_PIRsensorState = newState;
- }
- return s_PIRsensorState;
- }
+class PIRsensorSwitch : public Usermod
+{
+public:
+ /**
+ * constructor
+ */
+ PIRsensorSwitch()
+ {
+ // set static instance pointer
+ PIRsensorSwitchInstance(this);
+ }
+ /**
+ * desctructor
+ */
+ ~PIRsensorSwitch()
+ {
+ PIRsensorSwitchInstance(nullptr, true);
+ ;
+ }
- /*
- * PIR sensor state has changed
- */
- static void IRAM_ATTR ISR_PIRstateChange() {
- newPIRsensorState(true, true);
- }
+ /**
+ * return the instance pointer of the class
+ */
+ static PIRsensorSwitch *GetInstance() { return PIRsensorSwitchInstance(); }
- /*
- * switch strip on/off
- */
- void switchStrip(bool switchOn) {
- if (switchOn && bri == 0) {
- bri = briLast;
- colorUpdated(NotifyUpdateMode);
- }
- else if (!switchOn && bri != 0) {
- briLast = bri;
- bri = 0;
- colorUpdated(NotifyUpdateMode);
- }
+ /**
+ * Enable/Disable the PIR sensor
+ */
+ void EnablePIRsensor(bool enable) { m_PIRenabled = enable; }
+ /**
+ * Get PIR sensor enabled/disabled state
+ */
+ bool PIRsensorEnabled() { return m_PIRenabled; }
+
+private:
+ // PIR sensor pin
+ const uint8_t PIRsensorPin = 13; // D7 on D1 mini
+ // notification mode for colorUpdated()
+ const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE
+ // delay before switch off after the sensor state goes LOW
+ uint32_t m_switchOffDelay = 600000;
+ // off timer start time
+ uint32_t m_offTimerStart = 0;
+ // current PIR sensor pin state
+ byte m_PIRsensorPinState = LOW;
+ // PIR sensor enabled - ISR attached
+ bool m_PIRenabled = true;
+ // state if serializeConfig() should be called
+ bool m_updateConfig = false;
+
+ /**
+ * return or change if new PIR sensor state is available
+ */
+ static volatile bool newPIRsensorState(bool changeState = false, bool newState = false);
+
+ /**
+ * PIR sensor state has changed
+ */
+ static void IRAM_ATTR ISR_PIRstateChange();
+
+ /**
+ * Set/get instance pointer
+ */
+ static PIRsensorSwitch *PIRsensorSwitchInstance(PIRsensorSwitch *pInstance = nullptr, bool bRemoveInstance = false);
+
+ /**
+ * switch strip on/off
+ */
+ void switchStrip(bool switchOn)
+ {
+ if (switchOn && bri == 0)
+ {
+ bri = briLast;
+ colorUpdated(NotifyUpdateMode);
}
+ else if (!switchOn && bri != 0)
+ {
+ briLast = bri;
+ bri = 0;
+ colorUpdated(NotifyUpdateMode);
+ }
+ }
- /*
- * Read and update PIR sensor state.
- * Initilize/reset switch off timer
- */
- bool updatePIRsensorState() {
- if (newPIRsensorState()) {
- m_PIRsensorPinState = digitalRead(PIRsensorPin);
-
- if (m_PIRsensorPinState == HIGH) {
- m_offTimerStart = 0;
- switchStrip(true);
- }
- else if (bri != 0) {
- // start switch off timer
- m_offTimerStart = millis();
- }
- newPIRsensorState(true, false);
- return true;
+ /**
+ * Read and update PIR sensor state.
+ * Initilize/reset switch off timer
+ */
+ bool updatePIRsensorState()
+ {
+ if (newPIRsensorState())
+ {
+ m_PIRsensorPinState = digitalRead(PIRsensorPin);
+
+ if (m_PIRsensorPinState == HIGH)
+ {
+ m_offTimerStart = 0;
+ switchStrip(true);
+ }
+ else if (bri != 0)
+ {
+ // start switch off timer
+ m_offTimerStart = millis();
}
- return false;
+ newPIRsensorState(true, false);
+ return true;
}
+ return false;
+ }
- /*
- * switch off the strip if the delay has elapsed
- */
- bool handleOffTimer() {
- if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay) {
- if (m_PIRenabled == true){
- switchStrip(false);
- }
- m_offTimerStart = 0;
- return true;
+ /**
+ * switch off the strip if the delay has elapsed
+ */
+ bool handleOffTimer()
+ {
+ if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay)
+ {
+ if (m_PIRenabled == true)
+ {
+ switchStrip(false);
}
- return false;
+ m_offTimerStart = 0;
+ return true;
}
+ return false;
+ }
- public:
- //Functions called by WLED
+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() {
- // PIR Sensor mode INPUT_PULLUP
- pinMode(PIRsensorPin, INPUT_PULLUP);
+ /**
+ * 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()
+ {
+ // PIR Sensor mode INPUT_PULLUP
+ pinMode(PIRsensorPin, INPUT_PULLUP);
+ if (m_PIRenabled)
+ {
// assign interrupt function and set CHANGE mode
attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
}
+ }
+ /**
+ * connected() is called every time the WiFi is (re)connected
+ * Use it to initialize network interfaces
+ */
+ void connected()
+ {
+ }
- /*
- * connected() is called every time the WiFi is (re)connected
- * Use it to initialize network interfaces
- */
- void connected() {
-
+ /**
+ * loop() is called continuously. Here you can check for events, read sensors, etc.
+ */
+ void loop()
+ {
+ if (!updatePIRsensorState())
+ {
+ handleOffTimer();
+ if (m_updateConfig)
+ {
+ serializeConfig();
+ m_updateConfig = false;
+ }
}
+ }
+ /**
+ * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
+ *
+ * Add PIR sensor state and switch off timer duration to jsoninfo
+ */
+ void addToJsonInfo(JsonObject &root)
+ {
+ //this code adds "u":{"⏲ PIR sensor state":uiDomString} to the info object
+ // the value contains a button to toggle the sensor enabled/disabled
+ JsonObject user = root["u"];
+ if (user.isNull())
+ user = root.createNestedObject("u");
- /*
- * loop() is called continuously. Here you can check for events, read sensors, etc.
- */
- void loop() {
- if (!updatePIRsensorState()) {
- handleOffTimer();
- }
+ JsonArray infoArr = user.createNestedArray("⏲ PIR sensor state"); //name
+ String uiDomString = "";
+ infoArr.add(uiDomString); //value
- /*
- * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
- *
- * Add PIR sensor state and switch off timer duration to jsoninfo
- */
- void addToJsonInfo(JsonObject& root)
+ //this code adds "u":{"⏲ switch off timer":uiDomString} to the info object
+ uiDomString = "⏲ switch off timer\
+after min";
+ infoArr = user.createNestedArray(uiDomString); //name
+
+ // off timer
+ if (m_offTimerStart > 0)
{
- //this code adds "u":{"⏲ PIR sensor state":uiDomString} to the info object
- // the value contains a button to toggle the sensor enabled/disabled
- JsonObject user = root["u"];
- if (user.isNull()) user = root.createNestedObject("u");
-
- JsonArray infoArr = user.createNestedArray("⏲ PIR sensor state"); //name
- String uiDomString = "";
- infoArr.add(uiDomString); //value
-
- //this code adds "u":{"⏲ switch off timer":uiDomString} to the info object
- infoArr = user.createNestedArray("⏲ switch off timer"); //name
-
- // off timer
- if (m_offTimerStart > 0) {
- uiDomString = "";
- unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
- if (offSeconds >= 3600) {
- uiDomString += (offSeconds / 3600);
- uiDomString += " hours ";
- offSeconds %= 3600;
- }
- if (offSeconds >= 60) {
- uiDomString += (offSeconds / 60);
- offSeconds %= 60;
- } else if (uiDomString.length() > 0){
- uiDomString += 0;
- }
- if (uiDomString.length() > 0){
- uiDomString += " min ";
- }
- uiDomString += (offSeconds);
- infoArr.add(uiDomString + " sec");
- } else {
- infoArr.add("inactive");
+ if (uiDomString.length() > 0)
+ {
+ uiDomString += " min ";
}
+ uiDomString += (offSeconds);
+ infoArr.add(uiDomString + " sec");
+ }
+ else
+ {
+ infoArr.add("inactive");
}
+ }
+ /**
+ * 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
+ * Add "PIRenabled" to json state. This can be used to disable/enable the sensor.
+ * Add "PIRoffSec" to json state. This can be used to adjust milliseconds.
+ */
+ void addToJsonState(JsonObject &root)
+ {
+ root["PIRenabled"] = m_PIRenabled;
+ root["PIRoffSec"] = (m_switchOffDelay / 1000);
+ }
- /*
- * 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
- * Add "PIRenabled" to json state. This can be used to disable/enable the sensor.
- * Add "PIRoffSec" to json state. This can be used to adjust milliseconds .
- */
- void addToJsonState(JsonObject& root)
+ /**
+ * 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
+ * Read "PIRenabled" from json state and switch enable/disable the PIR sensor.
+ * Read "PIRoffSec" from json state and adjust milliseconds.
+ */
+ void readFromJsonState(JsonObject &root)
+ {
+ if (root["PIRoffSec"] != nullptr)
{
- root["PIRenabled"] = m_PIRenabled;
- root["PIRoffSec"] = (m_switchOffDelay / 1000);
+ m_switchOffDelay = (1000 * max(60UL, min(43200UL, root["PIRoffSec"].as())));
+ m_updateConfig = true;
}
-
- /*
- * 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
- * Read "PIRenabled" from json state and switch enable/disable the PIR sensor.
- * Read "PIRoffSec" from json state and adjust milliseconds .
- */
- void readFromJsonState(JsonObject& root)
+ if (root["PIRenabled"] != nullptr)
{
- if (root["PIRoffSec"] != nullptr) {
- m_switchOffDelay = (1000 * max(60UL, min(43200UL, root["PIRoffSec"].as())));
+ if (root["PIRenabled"] && !m_PIRenabled)
+ {
+ attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
+ newPIRsensorState(true, true);
}
-
- if (root["PIRenabled"] != nullptr) {
- if (root["PIRenabled"] && !m_PIRenabled) {
- attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE);
- newPIRsensorState(true, true);
- }
- else if(m_PIRenabled) {
- detachInterrupt(PIRsensorPin);
- }
- m_PIRenabled = root["PIRenabled"];
+ else if (m_PIRenabled)
+ {
+ detachInterrupt(PIRsensorPin);
}
+ m_PIRenabled = root["PIRenabled"];
+ m_updateConfig = 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_PIRSWITCH;
- }
+ }
+
+ /**
+ * provide the changeable values
+ */
+ void addToConfig(JsonObject &root)
+ {
+ JsonObject top = root.createNestedObject("PIRsensorSwitch");
+ top["PIRenabled"] = m_PIRenabled;
+ top["PIRoffSec"] = m_switchOffDelay;
+ }
+
+ /**
+ * restore the changeable values
+ */
+ void readFromConfig(JsonObject &root)
+ {
+ JsonObject top = root["PIRsensorSwitch"];
+ m_PIRenabled = (top["PIRenabled"] != nullptr ? top["PIRenabled"] : true);
+ m_switchOffDelay = top["PIRoffSec"] | m_switchOffDelay;
+ }
- //More methods can be added in the future, this example will then be extended.
- //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class!
+ /**
+ * 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_PIRSWITCH;
+ }
};
+
+//////////////////////////////////////////////////////
+// PIRsensorSwitch static method implementations
+
+volatile bool PIRsensorSwitch::newPIRsensorState(bool changeState, bool newState)
+{
+ static volatile bool s_PIRsensorState = false;
+ if (changeState)
+ {
+ s_PIRsensorState = newState;
+ }
+ return s_PIRsensorState;
+}
+
+void IRAM_ATTR PIRsensorSwitch::ISR_PIRstateChange()
+{
+ newPIRsensorState(true, true);
+}
+
+PIRsensorSwitch *PIRsensorSwitch::PIRsensorSwitchInstance(PIRsensorSwitch *pInstance, bool bRemoveInstance)
+{
+ static PIRsensorSwitch *s_pPIRsensorSwitch = nullptr;
+ if (pInstance != nullptr || bRemoveInstance)
+ {
+ s_pPIRsensorSwitch = pInstance;
+ }
+ return s_pPIRsensorSwitch;
+}
diff --git a/usermods/TTGO-T-Display/README.md b/usermods/TTGO-T-Display/README.md
index 5674c466e3..872beeb8cf 100644
--- a/usermods/TTGO-T-Display/README.md
+++ b/usermods/TTGO-T-Display/README.md
@@ -3,14 +3,24 @@ This usermod allows use of the TTGO T-Display ESP32 module with integrated 240x1
for controlling WLED and showing the following information:
* Current SSID
* IP address if obtained
- * in AP mode and turned off lightning AP password is shown
+ * If connected to a network, current brightness % is shown
+ * in AP mode AP IP and password are shown
* Current effect
* Current palette
+* Estimated current in mA is shown (NOTE: for this to be a reasonable value, the correct LED type must be specified in the LED Prefs section)
+
+Button pin is mapped to the onboard button next to the side actuated reset button of the TTGO T-Display board.
+
+I have designed a 3D printed case around this board and an ["ElectroCookie"](https://amzn.to/2WCNeeA) project board, a [level shifter](https://amzn.to/3hbKu18), a [buck regulator](https://amzn.to/3mLMy0W), and a DC [power jack](https://amzn.to/3phj9NZ). I use 12V WS2815 LED strips for my projects, and power them with 12V power supplies, so the regulator drops the voltage to the 5V level I need to power the ESP module and the level shifter. If there is any interest in this case, which elevates the board and display on some custom extended headers to make place the screen at the top of the enclosure (with accessible buttons), let me know, and I could post the STL files. It is a bit tricky to get the height correct, so I also designed a one-time use 3D printed solder fixture to set the board in the right location and at the correct height for the housing. (It is one-time use because it has to be cut off after soldering to be able to remove it). I didn't think the effort to make it in multiple pieces was worthwhile.
Usermod based on a rework of the ssd1306_i2c_oled_u8g2 usermod from the WLED repo.
## Hardware

+
+
+
+
## Github reference for TTGO-Tdisplay
@@ -20,7 +30,11 @@ Usermod based on a rework of the ssd1306_i2c_oled_u8g2 usermod from the WLED rep
Functionality checked with:
* TTGO T-Display
* PlatformIO
-* Group of 4 individual Neopixels from Adafruit, and a full string of 68 LEDs.
+* Group of 4 individual Neopixels from Adafruit, and a several full strings of 12v WS2815 LEDs.
+* The hardware design shown above should be limited to shorter strings. For larger strings, I use a different setup with a dedicated 12v power supply and power them directly off the supply (in addition to dropping the 12v supply down to 5v with a buck regulator for the ESP module and level shifter).
+
+## Setup Needed:
+* As with all usermods, copy the usermod.cpp file from the TTGO-T-Display usermod folder to the wled00 folder (replacing the default usermod.cpp file).
## Platformio Requirements
### Platformio.ini changes
diff --git a/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure1a.png b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure1a.png
new file mode 100644
index 0000000000..5c2c2bef4b
Binary files /dev/null and b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure1a.png differ
diff --git a/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure2a.png b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure2a.png
new file mode 100644
index 0000000000..ac76ade44b
Binary files /dev/null and b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure2a.png differ
diff --git a/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure3a.png b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure3a.png
new file mode 100644
index 0000000000..21c416f725
Binary files /dev/null and b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure3a.png differ
diff --git a/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure4a.png b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure4a.png
new file mode 100644
index 0000000000..a098fc23d1
Binary files /dev/null and b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure4a.png differ
diff --git a/usermods/TTGO-T-Display/usermod.cpp b/usermods/TTGO-T-Display/usermod.cpp
index a4bb28c83d..75e90b1ebe 100644
--- a/usermods/TTGO-T-Display/usermod.cpp
+++ b/usermods/TTGO-T-Display/usermod.cpp
@@ -56,7 +56,7 @@ void userSetup() {
tft.setTextColor(TFT_WHITE);
tft.setCursor(1, 10);
tft.setTextDatum(MC_DATUM);
- tft.setTextSize(2);
+ tft.setTextSize(3);
tft.print("Loading...");
if (TFT_BL > 0) { // TFT_BL has been set in the TFT_eSPI library in the User Setup file TTGO_T_Display.h
@@ -142,22 +142,41 @@ void userLoop() {
tft.fillScreen(TFT_BLACK);
tft.setTextSize(2);
// First row with Wifi name
- tft.setCursor(1, 10);
+ tft.setCursor(1, 1);
tft.print(knownSsid.substring(0, tftcharwidth > 1 ? tftcharwidth - 1 : 0));
// Print `~` char to indicate that SSID is longer, than our dicplay
if (knownSsid.length() > tftcharwidth)
tft.print("~");
- // Second row with IP or Psssword
- tft.setCursor(1, 40);
- // Print password in AP mode and if led is OFF.
- if (apActive && bri == 0)
+ // Second row with AP IP and Password or IP
+ tft.setTextSize(2);
+ tft.setCursor(1, 24);
+ // Print AP IP and password in AP mode or knownIP if AP not active.
+ // if (apActive && bri == 0)
+ // tft.print(apPass);
+ // else
+ // tft.print(knownIp);
+
+ if (apActive) {
+ tft.print("AP IP: ");
+ tft.print(knownIp);
+ tft.setCursor(1,46);
+ tft.print("AP Pass:");
tft.print(apPass);
- else
+ }
+ else {
+ tft.print("IP: ");
tft.print(knownIp);
+ tft.setCursor(1,46);
+ //tft.print("Signal Strength: ");
+ //tft.print(i.wifi.signal);
+ tft.print("Brightness: ");
+ tft.print(((float(bri)/255)*100));
+ tft.print("%");
+ }
// Third row with mode name
- tft.setCursor(1, 70);
+ tft.setCursor(1, 68);
uint8_t qComma = 0;
bool insideQuotes = false;
uint8_t printedChars = 0;
@@ -184,7 +203,7 @@ void userLoop() {
break;
}
// Fourth row with palette name
- tft.setCursor(1, 100);
+ tft.setCursor(1, 90);
qComma = 0;
insideQuotes = false;
printedChars = 0;
@@ -210,5 +229,10 @@ void userLoop() {
if ((qComma > knownPalette) || (printedChars > tftcharwidth - 1))
break;
}
-
-}
\ No newline at end of file
+ // Fifth row with estimated mA usage
+ tft.setCursor(1, 112);
+ // Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate).
+ tft.print(strip.currentMilliamps);
+ tft.print("mA (estimated)");
+
+}
diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md
index 5e26ba6969..5b7f5b9585 100644
--- a/usermods/Temperature/readme.md
+++ b/usermods/Temperature/readme.md
@@ -39,7 +39,7 @@ default_envs = d1_mini
...
[common]
...
-lib_deps_external =
+lib_deps =
...
#For use SSD1306 OLED display uncomment following
U8g2@~2.27.3
@@ -55,4 +55,4 @@ lib_deps_external =
* Changed to use async, non-blocking implementation
* Do not report low temperatures that indicate an error to mqtt
* Disable plugin if temperature sensor not detected
-* Report the number of seconds until the first read in the info screen instead of sensor error
\ No newline at end of file
+* Report the number of seconds until the first read in the info screen instead of sensor error
diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h
index d72be934b5..1ce0322e8c 100644
--- a/usermods/Temperature/usermod_temperature.h
+++ b/usermods/Temperature/usermod_temperature.h
@@ -5,11 +5,13 @@
#include //DS18B20
//Pin defaults for QuinLed Dig-Uno
+#ifndef TEMPERATURE_PIN
#ifdef ARDUINO_ARCH_ESP32
#define TEMPERATURE_PIN 18
#else //ESP8266 boards
#define TEMPERATURE_PIN 14
#endif
+#endif
// the frequency to check temperature, 1 minute
#ifndef USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL
@@ -58,6 +60,7 @@ class UsermodTemperature : public Usermod {
}
void getTemperature() {
+ if (strip.isUpdating()) return;
#ifdef USERMOD_DALLASTEMPERATURE_CELSIUS
temperature = sensor.getTempC(sensorDeviceAddress);
#else
@@ -80,30 +83,28 @@ class UsermodTemperature : public Usermod {
disabled = !sensor.getAddress(sensorDeviceAddress, 0);
if (!disabled) {
- DEBUG_PRINTLN("Dallas Temperature found");
+ DEBUG_PRINTLN(F("Dallas Temperature found"));
// set the resolution for this specific device
sensor.setResolution(sensorDeviceAddress, 9, true);
// do not block waiting for reading
- sensor.setWaitForConversion(false);
+ sensor.setWaitForConversion(false);
+ // allocate pin & prevent other use
+ if (!pinManager.allocatePin(TEMPERATURE_PIN,false))
+ disabled = true;
} else {
- DEBUG_PRINTLN("Dallas Temperature not found");
+ DEBUG_PRINTLN(F("Dallas Temperature not found"));
}
}
void loop() {
- if (disabled) {
- return;
- }
+ if (disabled || strip.isUpdating()) return;
unsigned long now = millis();
// check to see if we are due for taking a measurement
// lastMeasurement will not be updated until the conversion
// is complete the the reading is finished
- if (now - lastMeasurement < USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL)
- {
- return;
- }
+ if (now - lastMeasurement < USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL) return;
// we are due for a measurement, if we are not already waiting
// for a conversion to complete, then make a new request for temps
@@ -125,7 +126,7 @@ class UsermodTemperature : public Usermod {
// 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(subuf, "/temperature");
+ strcat_P(subuf, PSTR("/temperature"));
mqtt->publish(subuf, 0, true, String(temperature).c_str());
} else {
// publish something else to indicate status?
@@ -136,34 +137,32 @@ class UsermodTemperature : public Usermod {
void addToJsonInfo(JsonObject& root) {
// dont add temperature to info if we are disabled
- if (disabled) {
- return;
- }
+ if (disabled) return;
- JsonObject user = root["u"];
- if (user.isNull()) user = root.createNestedObject("u");
+ JsonObject user = root[F("u")];
+ if (user.isNull()) user = root.createNestedObject(F("u"));
- JsonArray temp = user.createNestedArray("Temperature");
+ JsonArray temp = user.createNestedArray(F("Temperature"));
if (!getTemperatureComplete) {
// if we haven't read the sensor yet, let the user know
// that we are still waiting for the first measurement
temp.add((USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - millis()) / 1000);
- temp.add(" sec until read");
+ temp.add(F(" sec until read"));
return;
}
if (temperature <= -100) {
temp.add(0);
- temp.add(" Sensor Error!");
+ temp.add(F(" Sensor Error!"));
return;
}
temp.add(temperature);
#ifdef USERMOD_DALLASTEMPERATURE_CELSIUS
- temp.add("°C");
+ temp.add(F("°C"));
#else
- temp.add("°F");
+ temp.add(F("°F"));
#endif
}
diff --git a/usermods/buzzer/usermod_v2_buzzer.h b/usermods/buzzer/usermod_v2_buzzer.h
new file mode 100644
index 0000000000..ebd8dcb15e
--- /dev/null
+++ b/usermods/buzzer/usermod_v2_buzzer.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include "wled.h"
+#include "Arduino.h"
+
+#include
+
+#define USERMOD_ID_BUZZER 900
+#ifndef USERMOD_BUZZER_PIN
+#define USERMOD_BUZZER_PIN GPIO_NUM_32
+#endif
+
+/*
+ * Usermods allow you to add own functionality to WLED more easily
+ * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
+ *
+ * Using a usermod:
+ * 1. Copy the usermod into the sketch folder (same folder as wled00.ino)
+ * 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
+ */
+
+class BuzzerUsermod : public Usermod {
+ private:
+ unsigned long lastTime_ = 0;
+ unsigned long delay_ = 0;
+ std::deque> sequence_ {};
+ 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.
+ */
+ void setup() {
+ // Setup the pin, and default to LOW
+ pinMode(USERMOD_BUZZER_PIN, OUTPUT);
+ digitalWrite(USERMOD_BUZZER_PIN, LOW);
+
+ // Beep on startup
+ sequence_.push_back({ HIGH, 50 });
+ sequence_.push_back({ LOW, 0 });
+ }
+
+
+ /*
+ * connected() is called every time the WiFi is (re)connected
+ * Use it to initialize network interfaces
+ */
+ void connected() {
+ // Double beep on WiFi
+ sequence_.push_back({ LOW, 100 });
+ sequence_.push_back({ HIGH, 50 });
+ sequence_.push_back({ LOW, 30 });
+ sequence_.push_back({ HIGH, 50 });
+ sequence_.push_back({ LOW, 0 });
+ }
+
+ /*
+ * loop() is called continuously. Here you can check for events, read sensors, etc.
+ */
+ void loop() {
+ if (sequence_.size() < 1) return; // Wait until there is a sequence
+ if (millis() - lastTime_ <= delay_) return; // Wait until delay has elapsed
+
+ auto event = sequence_.front();
+ sequence_.pop_front();
+
+ digitalWrite(USERMOD_BUZZER_PIN, event.first);
+ delay_ = event.second;
+
+ lastTime_ = millis();
+ }
+
+
+ /*
+ * 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_BUZZER;
+ }
+};
\ No newline at end of file
diff --git a/usermods/esp32_multistrip/NpbWrapper.h b/usermods/esp32_multistrip/NpbWrapper.h
index b93242cf23..84cf8ac0d0 100644
--- a/usermods/esp32_multistrip/NpbWrapper.h
+++ b/usermods/esp32_multistrip/NpbWrapper.h
@@ -41,6 +41,7 @@
#endif
#include
+#include "const.h"
const uint8_t numStrips = NUM_STRIPS; // max 8 strips allowed on esp32
const uint16_t pixelCounts[numStrips] = {PIXEL_COUNTS}; // number of pixels on each strip
@@ -148,7 +149,6 @@ class NeoPixelWrapper
void Show()
{
- byte b;
switch (_type)
{
case NeoPixelType_Grb:
@@ -190,7 +190,52 @@ class NeoPixelWrapper
}
}
- void SetPixelColor(uint16_t indexPixel, RgbwColor color)
+ bool CanShow()
+ {
+ bool canShow = true;
+ switch (_type)
+ {
+ case NeoPixelType_Grb:
+ {
+ for (uint8_t idx = 0; idx < numStrips; idx++)
+ {
+ switch (idx)
+ {
+ case 0: canShow &= pGrb0->CanShow(); break;
+ case 1: canShow &= pGrb1->CanShow(); break;
+ case 2: canShow &= pGrb2->CanShow(); break;
+ case 3: canShow &= pGrb3->CanShow(); break;
+ case 4: canShow &= pGrb4->CanShow(); break;
+ case 5: canShow &= pGrb5->CanShow(); break;
+ case 6: canShow &= pGrb6->CanShow(); break;
+ case 7: canShow &= pGrb7->CanShow(); break;
+ }
+ }
+ break;
+ }
+ case NeoPixelType_Grbw:
+ {
+ for (uint8_t idx = 0; idx < numStrips; idx++)
+ {
+ switch (idx)
+ {
+ case 0: canShow &= pGrbw0->CanShow(); break;
+ case 1: canShow &= pGrbw1->CanShow(); break;
+ case 2: canShow &= pGrbw2->CanShow(); break;
+ case 3: canShow &= pGrbw3->CanShow(); break;
+ case 4: canShow &= pGrbw4->CanShow(); break;
+ case 5: canShow &= pGrbw5->CanShow(); break;
+ case 6: canShow &= pGrbw6->CanShow(); break;
+ case 7: canShow &= pGrbw7->CanShow(); break;
+ }
+ }
+ break;
+ }
+ }
+ return canShow;
+ }
+
+ void SetPixelColorRaw(uint16_t indexPixel, RgbwColor c)
{
// figure out which strip this pixel index is on
uint8_t stripIdx = 0;
@@ -211,17 +256,17 @@ class NeoPixelWrapper
{
case NeoPixelType_Grb:
{
- RgbColor c = RgbColor(color.R, color.G, color.B);
+ RgbColor rgb = RgbColor(c.R, c.G, c.B);
switch (stripIdx)
{
- case 0: pGrb0->SetPixelColor(indexPixel, c); break;
- case 1: pGrb1->SetPixelColor(indexPixel, c); break;
- case 2: pGrb2->SetPixelColor(indexPixel, c); break;
- case 3: pGrb3->SetPixelColor(indexPixel, c); break;
- case 4: pGrb4->SetPixelColor(indexPixel, c); break;
- case 5: pGrb5->SetPixelColor(indexPixel, c); break;
- case 6: pGrb6->SetPixelColor(indexPixel, c); break;
- case 7: pGrb7->SetPixelColor(indexPixel, c); break;
+ case 0: pGrb0->SetPixelColor(indexPixel, rgb); break;
+ case 1: pGrb1->SetPixelColor(indexPixel, rgb); break;
+ case 2: pGrb2->SetPixelColor(indexPixel, rgb); break;
+ case 3: pGrb3->SetPixelColor(indexPixel, rgb); break;
+ case 4: pGrb4->SetPixelColor(indexPixel, rgb); break;
+ case 5: pGrb5->SetPixelColor(indexPixel, rgb); break;
+ case 6: pGrb6->SetPixelColor(indexPixel, rgb); break;
+ case 7: pGrb7->SetPixelColor(indexPixel, rgb); break;
}
break;
}
@@ -229,20 +274,48 @@ class NeoPixelWrapper
{
switch (stripIdx)
{
- case 0: pGrbw0->SetPixelColor(indexPixel, color); break;
- case 1: pGrbw1->SetPixelColor(indexPixel, color); break;
- case 2: pGrbw2->SetPixelColor(indexPixel, color); break;
- case 3: pGrbw3->SetPixelColor(indexPixel, color); break;
- case 4: pGrbw4->SetPixelColor(indexPixel, color); break;
- case 5: pGrbw5->SetPixelColor(indexPixel, color); break;
- case 6: pGrbw6->SetPixelColor(indexPixel, color); break;
- case 7: pGrbw7->SetPixelColor(indexPixel, color); break;
+ case 0: pGrbw0->SetPixelColor(indexPixel, c); break;
+ case 1: pGrbw1->SetPixelColor(indexPixel, c); break;
+ case 2: pGrbw2->SetPixelColor(indexPixel, c); break;
+ case 3: pGrbw3->SetPixelColor(indexPixel, c); break;
+ case 4: pGrbw4->SetPixelColor(indexPixel, c); break;
+ case 5: pGrbw5->SetPixelColor(indexPixel, c); break;
+ case 6: pGrbw6->SetPixelColor(indexPixel, c); break;
+ case 7: pGrbw7->SetPixelColor(indexPixel, c); break;
}
break;
}
}
}
+ void SetPixelColor(uint16_t indexPixel, RgbwColor c)
+ {
+ /*
+ Set pixel color with necessary color order conversion.
+ */
+
+ RgbwColor col;
+
+ uint8_t co = _colorOrder;
+ #ifdef COLOR_ORDER_OVERRIDE
+ if (indexPixel >= COO_MIN && indexPixel < COO_MAX) co = COO_ORDER;
+ #endif
+
+ //reorder channels to selected order
+ switch (co)
+ {
+ case 0: col.G = c.G; col.R = c.R; col.B = c.B; break; //0 = GRB, default
+ case 1: col.G = c.R; col.R = c.G; col.B = c.B; break; //1 = RGB, common for WS2811
+ case 2: col.G = c.B; col.R = c.R; col.B = c.G; break; //2 = BRG
+ case 3: col.G = c.R; col.R = c.B; col.B = c.G; break; //3 = RBG
+ case 4: col.G = c.B; col.R = c.G; col.B = c.R; break; //4 = BGR
+ default: col.G = c.G; col.R = c.B; col.B = c.R; break; //5 = GBR
+ }
+ col.W = c.W;
+
+ SetPixelColorRaw(indexPixel, col);
+ }
+
void SetBrightness(byte b)
{
switch (_type)
@@ -286,9 +359,17 @@ class NeoPixelWrapper
}
}
- // NOTE: Due to feature differences, some support RGBW but the method name
- // here needs to be unique, thus GetPixeColorRgbw
- RgbwColor GetPixelColorRgbw(uint16_t indexPixel) const
+ void SetColorOrder(byte colorOrder)
+ {
+ _colorOrder = colorOrder;
+ }
+
+ uint8_t GetColorOrder()
+ {
+ return _colorOrder;
+ }
+
+ RgbwColor GetPixelColorRaw(uint16_t indexPixel) const
{
// figure out which strip this pixel index is on
uint8_t stripIdx = 0;
@@ -339,8 +420,35 @@ class NeoPixelWrapper
return 0;
}
+ // NOTE: Due to feature differences, some support RGBW but the method name
+ // here needs to be unique, thus GetPixeColorRgbw
+ uint32_t GetPixelColorRgbw(uint16_t indexPixel) const
+ {
+ RgbwColor col = GetPixelColorRaw(indexPixel);
+ uint8_t co = _colorOrder;
+ #ifdef COLOR_ORDER_OVERRIDE
+ if (indexPixel >= COO_MIN && indexPixel < COO_MAX) co = COO_ORDER;
+ #endif
+
+ switch (co)
+ {
+ // W G R B
+ case 0: return ((col.W << 24) | (col.G << 8) | (col.R << 16) | (col.B)); //0 = GRB, default
+ case 1: return ((col.W << 24) | (col.R << 8) | (col.G << 16) | (col.B)); //1 = RGB, common for WS2811
+ case 2: return ((col.W << 24) | (col.B << 8) | (col.R << 16) | (col.G)); //2 = BRG
+ case 3: return ((col.W << 24) | (col.B << 8) | (col.G << 16) | (col.R)); //3 = RBG
+ case 4: return ((col.W << 24) | (col.R << 8) | (col.B << 16) | (col.G)); //4 = BGR
+ case 5: return ((col.W << 24) | (col.G << 8) | (col.B << 16) | (col.R)); //5 = GBR
+ }
+
+ return 0;
+
+ }
+
+
private:
NeoPixelType _type;
+ byte _colorOrder = 0;
uint16_t pixelStripStartIdx[numStrips];
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/README.md b/usermods/quinled_digquad_preassembled_unofficial_v0.1/README.md
new file mode 100644
index 0000000000..39ae4edd11
--- /dev/null
+++ b/usermods/quinled_digquad_preassembled_unofficial_v0.1/README.md
@@ -0,0 +1,37 @@
+# QuinLED-Dig-Quad Preassembled Unofficial Build
+
+This usermod targets the [Preassembled QuinLED-Dig-Quad](https://quinled.info/pre-assembled-quinled-dig-quad/). Tested on board revision v1r6b,
+and includes the following features:
+
+ * **Multi-channel Support** - enabling use of LED1, LED2, LED3, LED4 pins to work using segments
+ * **Temperature Sensor Support** - pulls readings from the built-in temperature sensor and adds the reading to the *Info* page in the UI
+
+## Background
+
+As a starting point, you should check out this awesome video from Quindor: [How to compile WLED yourself](https://quinled.info/2020/12/22/livestream-wled-compile/). The usermod you are reading now just provides some shortcuts for parts of what were covered in that video.
+
+## Build Firmware with Multi-channel and Temp Support
+
+1. Copy the `platformio_override.ini` file to the project's root directory
+1. If using VS Code with the PlatformIO plugin like in the video, you will now see this new project task listed in the PLATFORMIO panel at the bottom as `env:QL-DigQuad-Pre-v0.1` (you probably need to hit the refresh button)
+
+
+
+1. Edit this file from the root directory as needed:
+
+
+
+ * `PIXEL_COUNTS` may need to be adjusted for your set-up. E.g. I have lots of LEDs in Channel 1, but that's probably unusual for most
+ * `DATA_PINS` may need to be changed to "16,3,1,26" instead of "16,1,3,26" apparently depending on the board revision or some such
+
+1. Build the mod (e.g. click `Build` from the project task circled above) and update your firmware using the `QL-DigQuad-Pre-v0.1` file, e.g. using _Manual OTA_ from the Config menu. Based on the video and my own experience, you might need to build twice 🤷♂️.
+
+## Observing Temperature
+
+Hopefully you can now see the Temperature listed in the Info page. If not, use Chrome Developer Tools to find the current temperature
+
+1. Open the Developer Tools Console
+2. Enter `lastinfo.u.Temperature` to view the Temperature array
+
+
+
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/json-temp.png b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/json-temp.png
new file mode 100644
index 0000000000..66e5011233
Binary files /dev/null and b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/json-temp.png differ
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/params.png b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/params.png
new file mode 100644
index 0000000000..64233f86bf
Binary files /dev/null and b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/params.png differ
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/pio-screenshot.png b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/pio-screenshot.png
new file mode 100644
index 0000000000..e178ed1601
Binary files /dev/null and b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/pio-screenshot.png differ
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/platformio_override.ini b/usermods/quinled_digquad_preassembled_unofficial_v0.1/platformio_override.ini
new file mode 100644
index 0000000000..6f416668fa
--- /dev/null
+++ b/usermods/quinled_digquad_preassembled_unofficial_v0.1/platformio_override.ini
@@ -0,0 +1,16 @@
+; QuinLED-Dig-Quad Preassembled Unofficial
+
+[env:QL-DigQuad-Pre-v0.1]
+extends = env:esp32dev
+build_flags = ${common.build_flags_esp32}
+ -D ESP32_MULTISTRIP
+ -D NUM_STRIPS=4
+ -D PIXEL_COUNTS="600, 300, 300, 300"
+ -D DATA_PINS="16,1,3,26"
+ -D RLYPIN=19
+ -D BTNPIN=17
+ -D USERMOD_DALLASTEMPERATURE
+ -D USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL=10000
+lib_deps = ${env.lib_deps}
+ milesburton/DallasTemperature@^3.9.0
+ OneWire@~2.3.5
\ No newline at end of file
diff --git a/usermods/sensors_to_mqtt/readme.md b/usermods/sensors_to_mqtt/readme.md
new file mode 100644
index 0000000000..7f2d64071a
--- /dev/null
+++ b/usermods/sensors_to_mqtt/readme.md
@@ -0,0 +1,87 @@
+# Sensors To Home Assistant (or mqtt)
+
+This usermod will publish values of the BMP280, CCS811 and Si7021 sensors to Home Assistant via MQTT.
+
+Its using home assistant automatic device discovery feature.
+
+The use of Home Assistant is not mandatory; it will publish the sensor values via MQTT just fine without it.
+
+Its resusing the mqtt connection set in the WLED web user interface.
+
+## Maintainer
+
+twitter.com/mpronk89
+
+## Features
+
+- Reads BMP280, CCS811 and Si7021 senors
+- Publishes via MQTT, configured via webui of wled
+- Announces device in Home Assistant for easy setup
+- Efficient energy usage
+- Updates every 60 seconds
+
+## Example mqtt topics:
+
+`$mqttDeviceTopic` is set in webui of WLED!
+
+```
+temperature: $mqttDeviceTopic/temperature
+pressure: $mqttDeviceTopic/pressure
+humidity: $mqttDeviceTopic/humidity
+tvoc: $mqttDeviceTopic/tvoc
+eCO2: $mqttDeviceTopic/eco2
+IAQ: $mqttDeviceTopic/iaq
+```
+
+# Installation
+
+## Hardware
+
+### Requirements
+
+1. BMP280/CCS811/Si7021 sensor. E.g. https://aliexpress.com/item/32979998543.html
+2. A microcontroller which can talk i2c, e.g. esp32
+
+### installation
+
+Attach the sensor to the i2c interface.
+
+Default PINs esp32:
+
+```
+SCL_PIN = 22;
+SDA_PIN = 21;
+```
+
+Default PINs ESP8266:
+
+```
+SCL_PIN = 5;
+SDA_PIN = 4;
+```
+
+## Enable in WLED
+
+1. Copy `usermod_v2_SensorsToMqtt.h` into the `wled00` directory.
+2. Add to `build_flags` in platformio.ini:
+
+```
+ -D USERMOD_SENSORSTOMQTT
+```
+
+3. And add to `lib_deps` in platformio.ini:
+
+```
+ adafruit/Adafruit BMP280 Library @ 2.1.0
+ adafruit/Adafruit CCS811 Library @ 1.0.4
+ adafruit/Adafruit Si7021 Library @ 1.4.0
+```
+
+The #ifdefs in `usermods_list.cpp` should do the rest :)
+
+# Credits
+
+- Aircoookie for making WLED
+- Other usermod creators for example code
+- Bouke_Regnerus for https://community.home-assistant.io/t/example-indoor-air-quality-text-sensor-using-ccs811-sensor/125854
+- You, for reading this
diff --git a/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h
new file mode 100644
index 0000000000..dd7aedc1f0
--- /dev/null
+++ b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h
@@ -0,0 +1,284 @@
+#pragma once
+
+#include "wled.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+Adafruit_BMP280 bmp;
+Adafruit_Si7021 si7021;
+Adafruit_CCS811 ccs811;
+
+#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards
+uint8_t SCL_PIN = 22;
+uint8_t SDA_PIN = 21;
+#else //ESP8266 boards
+uint8_t SCL_PIN = 5;
+uint8_t SDA_PIN = 4;
+#endif
+
+class UserMod_SensorsToMQTT : public Usermod
+{
+private:
+ bool initialized = false;
+ bool mqttInitialized = false;
+ float SensorPressure = 0;
+ float SensorTemperature = 0;
+ float SensorHumidity = 0;
+ char *SensorIaq = "Unknown";
+ String mqttTemperatureTopic = "";
+ String mqttHumidityTopic = "";
+ String mqttPressureTopic = "";
+ String mqttTvocTopic = "";
+ String mqttEco2Topic = "";
+ String mqttIaqTopic = "";
+ unsigned int SensorTvoc = 0;
+ unsigned int SensorEco2 = 0;
+ unsigned long nextMeasure = 0;
+
+ void _initialize()
+ {
+ initialized = bmp.begin(BMP280_ADDRESS_ALT);
+ bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */
+ Adafruit_BMP280::SAMPLING_X16, /* Temp. oversampling */
+ Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */
+ Adafruit_BMP280::FILTER_X16, /* Filtering. */
+ Adafruit_BMP280::STANDBY_MS_2000); /* Refresh values every 20 seconds */
+
+ initialized &= si7021.begin();
+ initialized &= ccs811.begin();
+ ccs811.setDriveMode(CCS811_DRIVE_MODE_10SEC); /* Refresh values every 10s */
+ Serial.print(initialized);
+ }
+
+ void _mqttInitialize()
+ {
+ mqttTemperatureTopic = String(mqttDeviceTopic) + "/temperature";
+ mqttPressureTopic = String(mqttDeviceTopic) + "/pressure";
+ mqttHumidityTopic = String(mqttDeviceTopic) + "/humidity";
+ mqttTvocTopic = String(mqttDeviceTopic) + "/tvoc";
+ mqttEco2Topic = String(mqttDeviceTopic) + "/eco2";
+ mqttIaqTopic = String(mqttDeviceTopic) + "/iaq";
+
+ String t = String("homeassistant/sensor/") + mqttClientID + "/temperature/config";
+
+ _createMqttSensor("temperature", mqttTemperatureTopic, "temperature", "°C");
+ _createMqttSensor("pressure", mqttPressureTopic, "pressure", "hPa");
+ _createMqttSensor("humidity", mqttHumidityTopic, "humidity", "%");
+ _createMqttSensor("tvoc", mqttTvocTopic, "", "ppb");
+ _createMqttSensor("eco2", mqttEco2Topic, "", "ppm");
+ _createMqttSensor("iaq", mqttIaqTopic, "", "");
+ }
+
+ void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
+ {
+ String t = String("homeassistant/sensor/") + mqttClientID + "/" + name + "/config";
+
+ StaticJsonDocument<300> doc;
+
+ doc["name"] = name;
+ doc["state_topic"] = topic;
+ doc["unique_id"] = String(mqttClientID) + name;
+ if (unitOfMeasurement != "")
+ doc["unit_of_measurement"] = unitOfMeasurement;
+ if (deviceClass != "")
+ doc["device_class"] = deviceClass;
+ doc["expire_after"] = 1800;
+
+ JsonObject device = doc.createNestedObject("device"); // attach the sensor to the same device
+ device["identifiers"] = String("wled-sensor-") + mqttClientID;
+ device["manufacturer"] = "Aircoookie";
+ device["model"] = "WLED";
+ device["sw_version"] = VERSION;
+ device["name"] = mqttClientID;
+
+ String temp;
+ serializeJson(doc, temp);
+ Serial.println(t);
+ Serial.println(temp);
+
+ mqtt->publish(t.c_str(), 0, true, temp.c_str());
+ }
+
+ void _updateSensorData()
+ {
+ SensorTemperature = bmp.readTemperature();
+ SensorHumidity = si7021.readHumidity();
+ SensorPressure = (bmp.readPressure() / 100.0F);
+ ccs811.setEnvironmentalData(SensorHumidity, SensorTemperature);
+ ccs811.readData();
+ SensorTvoc = ccs811.getTVOC();
+ SensorEco2 = ccs811.geteCO2();
+ SensorIaq = _getIaqIndex(SensorHumidity, SensorTvoc, SensorEco2);
+
+ Serial.printf("%f c, %f humidity, %f hPA, %u tvoc, %u Eco2, %s iaq\n",
+ SensorTemperature, SensorHumidity, SensorPressure,
+ SensorTvoc, SensorEco2, SensorIaq);
+ }
+
+ /**
+ * Credits: Bouke_Regnerus @ https://community.home-assistant.io/t/example-indoor-air-quality-text-sensor-using-ccs811-sensor/125854
+ */
+ char *_getIaqIndex(float humidity, int tvoc, int eco2)
+ {
+ int iaq_index = 0;
+
+ /*
+ * Transform indoor humidity values to IAQ points according to Indoor Air Quality UK:
+ * http://www.iaquk.org.uk/
+ */
+ if (humidity < 10 or humidity > 90)
+ {
+ iaq_index += 1;
+ }
+ else if (humidity < 20 or humidity > 80)
+ {
+ iaq_index += 2;
+ }
+ else if (humidity < 30 or humidity > 70)
+ {
+ iaq_index += 3;
+ }
+ else if (humidity < 40 or humidity > 60)
+ {
+ iaq_index += 4;
+ }
+ else if (humidity >= 40 and humidity <= 60)
+ {
+ iaq_index += 5;
+ }
+
+ /*
+ * Transform eCO2 values to IAQ points according to Indoor Air Quality UK:
+ * http://www.iaquk.org.uk/
+ */
+ if (eco2 <= 600)
+ {
+ iaq_index += 5;
+ }
+ else if (eco2 <= 800)
+ {
+ iaq_index += 4;
+ }
+ else if (eco2 <= 1500)
+ {
+ iaq_index += 3;
+ }
+ else if (eco2 <= 1800)
+ {
+ iaq_index += 2;
+ }
+ else if (eco2 > 1800)
+ {
+ iaq_index += 1;
+ }
+
+ /*
+ * Transform TVOC values to IAQ points according to German environmental guidelines:
+ * https://www.repcomsrl.com/wp-content/uploads/2017/06/Environmental_Sensing_VOC_Product_Brochure_EN.pdf
+ */
+ if (tvoc <= 65)
+ {
+ iaq_index += 5;
+ }
+ else if (tvoc <= 220)
+ {
+ iaq_index += 4;
+ }
+ else if (tvoc <= 660)
+ {
+ iaq_index += 3;
+ }
+ else if (tvoc <= 2200)
+ {
+ iaq_index += 2;
+ }
+ else if (tvoc > 2200)
+ {
+ iaq_index += 1;
+ }
+
+ if (iaq_index <= 6)
+ {
+ return "Unhealty";
+ }
+ else if (iaq_index <= 9)
+ {
+ return "Poor";
+ }
+ else if (iaq_index <= 12)
+ {
+ return "Moderate";
+ }
+ else if (iaq_index <= 14)
+ {
+ return "Good";
+ }
+ else if (iaq_index > 14)
+ {
+ return "Excellent";
+ }
+ }
+
+public:
+ void setup()
+ {
+ Serial.println("Starting!");
+ Wire.begin(SDA_PIN, SCL_PIN);
+ Serial.println("Initializing sensors.. ");
+ _initialize();
+ }
+
+ // gets called every time WiFi is (re-)connected.
+ void connected()
+ {
+ nextMeasure = millis() + 5000; // Schedule next measure in 5 seconds
+ }
+
+ void loop()
+ {
+ unsigned long tempTimer = millis();
+
+ if (tempTimer > nextMeasure)
+ {
+ nextMeasure = tempTimer + 60000; // Schedule next measure in 60 seconds
+
+ if (!initialized)
+ {
+ Serial.println("Error! Sensors not initialized in loop()!");
+ _initialize();
+ return; // lets try again next loop
+ }
+
+ if (mqtt != nullptr && mqtt->connected())
+ {
+ if (!mqttInitialized)
+ {
+ _mqttInitialize();
+ mqttInitialized = true;
+ }
+
+ // Update sensor data
+ _updateSensorData();
+
+ // Create string populated with user defined device topic from the UI,
+ // and the read temperature, humidity and pressure.
+ // Then publish to MQTT server.
+ mqtt->publish(mqttTemperatureTopic.c_str(), 0, true, String(SensorTemperature).c_str());
+ mqtt->publish(mqttPressureTopic.c_str(), 0, true, String(SensorPressure).c_str());
+ mqtt->publish(mqttHumidityTopic.c_str(), 0, true, String(SensorHumidity).c_str());
+ mqtt->publish(mqttTvocTopic.c_str(), 0, true, String(SensorTvoc).c_str());
+ mqtt->publish(mqttEco2Topic.c_str(), 0, true, String(SensorEco2).c_str());
+ mqtt->publish(mqttIaqTopic.c_str(), 0, true, String(SensorIaq).c_str());
+ }
+ else
+ {
+ Serial.println("Missing MQTT connection. Not publishing data");
+ mqttInitialized = false;
+ }
+ }
+ }
+};
diff --git a/usermods/usermod_v2_auto_save/readme.md b/usermods/usermod_v2_auto_save/readme.md
new file mode 100644
index 0000000000..5c835c60ef
--- /dev/null
+++ b/usermods/usermod_v2_auto_save/readme.md
@@ -0,0 +1,45 @@
+# Auto Save
+
+v2 Usermod to automatically save settings
+to preset number AUTOSAVE_PRESET_NUM after a change to any of
+
+* brightness
+* effect speed
+* effect intensity
+* mode (effect)
+* palette
+
+but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle"
+period in case there are other changes (any change will
+extend the "settle" window).
+
+It will additionally load preset AUTOSAVE_PRESET_NUM at startup.
+during the first `loop()`. Reasoning below.
+
+AutoSaveUsermod is standalone, but if FourLineDisplayUsermod is installed, it will notify the user of the saved changes.
+
+Note: I don't love that WLED doesn't respect the brightness of the preset being auto loaded, so the AutoSaveUsermod will set the AUTOSAVE_PRESET_NUM preset in the first loop, so brightness IS honored. This means WLED will effectively ignore Default brightness and Apply N preset at boot when the AutoSaveUsermod is installed.
+
+## Installation
+
+Copy and update the example `platformio_override.ini.sample`
+from the Rotary Encoder UI usermode folder to the root directory of your particular build.
+This file should be placed in the same directory as `platformio.ini`.
+
+### Define Your Options
+
+* `USERMOD_AUTO_SAVE` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp
+* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details)
+* `AUTOSAVE_SETTLE_MS` - Minimum time to wave before auto saving, defaults to 10000 (10s)
+* `AUTOSAVE_PRESET_NUM` - Preset number to auto-save to, auto-load at startup from, defaults to 99
+
+### PlatformIO requirements
+
+No special requirements.
+
+Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`.
+
+## Change Log
+
+2021-02
+* First public release
diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h
new file mode 100644
index 0000000000..bd7ea6d817
--- /dev/null
+++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h
@@ -0,0 +1,192 @@
+#pragma once
+
+#include "wled.h"
+
+//
+// v2 Usermod to automatically save settings
+// to preset number AUTOSAVE_PRESET_NUM after a change to any of
+//
+// * brightness
+// * effect speed
+// * effect intensity
+// * mode (effect)
+// * palette
+//
+// but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle"
+// period in case there are other changes (any change will
+// extend the "settle" window).
+//
+// It will additionally load preset AUTOSAVE_PRESET_NUM at startup.
+// during the first `loop()`. Reasoning below.
+//
+// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod
+// is installed, it will notify the user of the saved changes.
+//
+// Note: I don't love that WLED doesn't respect the brightness
+// of the preset being auto loaded, so the AutoSaveUsermod
+// will set the AUTOSAVE_PRESET_NUM preset in the first loop,
+// so brightness IS honored. This means WLED will effectively
+// ignore Default brightness and Apply N preset at boot when
+// the AutoSaveUsermod is installed.
+
+//How long to wait after settings change to auto-save
+#ifndef AUTOSAVE_SETTLE_MS
+#define AUTOSAVE_SETTLE_MS 10*1000
+#endif
+
+//Preset number to save to
+#ifndef AUTOSAVE_PRESET_NUM
+#define AUTOSAVE_PRESET_NUM 99
+#endif
+
+// "Auto save MM-DD HH:MM:SS"
+#define PRESET_NAME_BUFFER_SIZE 25
+
+class AutoSaveUsermod : public Usermod {
+ private:
+ // If we've detected the need to auto save, this will
+ // be non zero.
+ unsigned long autoSaveAfter = 0;
+
+ char presetNameBuffer[PRESET_NAME_BUFFER_SIZE];
+
+ bool firstLoop = true;
+
+ uint8_t knownBrightness = 0;
+ uint8_t knownEffectSpeed = 0;
+ uint8_t knownEffectIntensity = 0;
+ uint8_t knownMode = 0;
+ uint8_t knownPalette = 0;
+
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ FourLineDisplayUsermod* display;
+#endif
+
+ public:
+ // gets called once at boot. Do all initialization that doesn't depend on
+ // network here
+ void setup() {
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ // This Usermod has enhanced funcionality if
+ // FourLineDisplayUsermod is available.
+ display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
+#endif
+ }
+
+ // gets called every time WiFi is (re-)connected. Initialize own network
+ // interfaces here
+ void connected() {}
+
+ /**
+ * Da loop.
+ */
+ void loop() {
+ unsigned long now = millis();
+ uint8_t currentMode = strip.getMode();
+ uint8_t currentPalette = strip.getSegment(0).palette;
+ if (firstLoop) {
+ firstLoop = false;
+ applyPreset(AUTOSAVE_PRESET_NUM);
+ knownBrightness = bri;
+ knownEffectSpeed = effectSpeed;
+ knownEffectIntensity = effectIntensity;
+ knownMode = currentMode;
+ knownPalette = currentPalette;
+ return;
+ }
+
+ unsigned long wouldAutoSaveAfter = now + AUTOSAVE_SETTLE_MS;
+ if (knownBrightness != bri) {
+ knownBrightness = bri;
+ autoSaveAfter = wouldAutoSaveAfter;
+ } else if (knownEffectSpeed != effectSpeed) {
+ knownEffectSpeed = effectSpeed;
+ autoSaveAfter = wouldAutoSaveAfter;
+ } else if (knownEffectIntensity != effectIntensity) {
+ knownEffectIntensity = effectIntensity;
+ autoSaveAfter = wouldAutoSaveAfter;
+ } else if (knownMode != currentMode) {
+ knownMode = currentMode;
+ autoSaveAfter = wouldAutoSaveAfter;
+ } else if (knownPalette != currentPalette) {
+ knownPalette = currentPalette;
+ autoSaveAfter = wouldAutoSaveAfter;
+ }
+
+ if (autoSaveAfter && now > autoSaveAfter) {
+ autoSaveAfter = 0;
+ // Time to auto save. You may have some flickry?
+ saveSettings();
+ displayOverlay();
+ }
+ }
+
+ void saveSettings() {
+ updateLocalTime();
+ sprintf(presetNameBuffer,
+ "Auto save %02d-%02d %02d:%02d:%02d",
+ month(localTime), day(localTime),
+ hour(localTime), minute(localTime), second(localTime));
+ savePreset(AUTOSAVE_PRESET_NUM, true, presetNameBuffer);
+ }
+
+ void displayOverlay() {
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ if (display != nullptr) {
+ display->wakeDisplay();
+ display->overlay("Settings", "Auto Saved", 1500);
+ }
+#endif
+ }
+
+ /*
+ * 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) {
+ }
+
+ /*
+ * 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) {
+ }
+
+ /*
+ * 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) {
+ }
+
+ /*
+ * 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) {
+ }
+
+ /*
+ * 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_AUTO_SAVE;
+ }
+
+};
\ No newline at end of file
diff --git a/usermods/usermod_v2_four_line_display/readme.md b/usermods/usermod_v2_four_line_display/readme.md
new file mode 100644
index 0000000000..3198b2be50
--- /dev/null
+++ b/usermods/usermod_v2_four_line_display/readme.md
@@ -0,0 +1,39 @@
+# Rotary Encoder UI Usermod
+
+First, thanks to the authors of the ssd11306_i2c_oled_u8g2 mod.
+
+This usermod provides a four line display using either
+128x32 or 128x64 OLED displays.
+It's can operate independently, but starts to provide
+a relatively complete on-device UI when paired with the
+Rotary Encoder UI usermod. I strongly encourage you to use
+them together.
+
+[See the pair of usermods in action](https://www.youtube.com/watch?v=tITQY80rIOA)
+
+## Installation
+
+Copy and update the example `platformio_override.ini.sample`
+from the Rotary Encoder UI usermode folder to the root directory of your particular build.
+This file should be placed in the same directory as `platformio.ini`.
+
+### Define Your Options
+
+* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells Rotary Encoder usermod, if installed, that the display is available
+* `FLD_PIN_SCL` - The display SCL pin, defaults to 5
+* `FLD_PIN_SDA` - The display SDA pin, defaults to 4
+* `FLIP_MODE` - Set to 0 or 1
+* `LINE_HEIGHT` - Set to 1 or 2
+
+There are other `#define` values in the Usermod that might be of interest.
+
+### PlatformIO requirements
+
+This usermod requires the `U8g2` and `Wire` libraries. See the
+`platformio_override.ini.sample` found in the Rotary Encoder
+UI usermod folder for how to include these using `platformio_override.ini`.
+
+## Change Log
+
+2021-02
+* First public release
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
new file mode 100644
index 0000000000..0b59af5517
--- /dev/null
+++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h
@@ -0,0 +1,526 @@
+#pragma once
+
+#include "wled.h"
+#include // from https://github.com/olikraus/u8g2/
+
+//
+// Insired by the v1 usermod: ssd1306_i2c_oled_u8g2
+//
+// v2 usermod for using 128x32 or 128x64 i2c
+// OLED displays to provide a four line display
+// for WLED.
+//
+// Dependencies
+// * This usermod REQURES the ModeSortUsermod
+// * This Usermod works best, by far, when coupled
+// with RotaryEncoderUIUsermod.
+//
+// Make sure to enable NTP and set your time zone in WLED Config | Time.
+//
+// REQUIREMENT: You must add the following requirements to
+// REQUIREMENT: "lib_deps" within platformio.ini / platformio_override.ini
+// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine)
+// REQUIREMENT: * Wire
+//
+
+//The SCL and SDA pins are defined here.
+#ifndef FLD_PIN_SCL
+#define FLD_PIN_SCL 5
+#endif
+
+#ifndef FLD_PIN_SDA
+#define FLD_PIN_SDA 4
+#endif
+
+// U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(
+// U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA);
+U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8(
+ U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA);
+
+// Screen upside down? Change to 0 or 1
+#ifndef FLIP_MODE
+#define FLIP_MODE 0
+#endif
+
+// LINE_HEIGHT 1 is single height, for 128x32 displays.
+// LINE_HEIGHT 2 makes the 128x64 screen display at double height.
+#ifndef LINE_HEIGHT
+#define LINE_HEIGHT 2
+#endif
+
+// If you aren't also including RotaryEncoderUIUsermod
+// you probably want to set both
+// SLEEP_MODE_ENABLED false
+// CLOCK_MODE_ENABLED false
+// as you will never be able wake the display / disable the clock.
+#ifdef USERMOD_ROTARY_ENCODER_UI
+#ifndef SLEEP_MODE_ENABLED
+#define SLEEP_MODE_ENABLED true
+#endif
+#ifndef CLOCK_MODE_ENABLED
+#define CLOCK_MODE_ENABLED true
+#endif
+#else
+#define SLEEP_MODE_ENABLED false
+#define CLOCK_MODE_ENABLED false
+#endif
+
+// When to time out to the clock or blank the screen
+// if SLEEP_MODE_ENABLED.
+#define SCREEN_TIMEOUT_MS 15*1000
+
+#define TIME_INDENT 0
+#define DATE_INDENT 2
+
+// Minimum time between redrawing screen in ms
+#define USER_LOOP_REFRESH_RATE_MS 1000
+
+#if LINE_HEIGHT == 2
+#define DRAW_STRING draw1x2String
+#define DRAW_GLYPH draw1x2Glyph
+#define DRAW_BIG_STRING draw2x2String
+#else
+#define DRAW_STRING drawString
+#define DRAW_GLYPH drawGlyph
+#define DRAW_BIG_STRING draw2x2String
+#endif
+
+// Extra char (+1) for null
+#define LINE_BUFFER_SIZE 16+1
+#define FLD_LINE_3_BRIGHTNESS 0
+#define FLD_LINE_3_EFFECT_SPEED 1
+#define FLD_LINE_3_EFFECT_INTENSITY 2
+#define FLD_LINE_3_PALETTE 3
+
+#if LINE_HEIGHT == 2
+#define TIME_LINE 1
+#else
+#define TIME_LINE 0
+#endif
+
+class FourLineDisplayUsermod : public Usermod {
+ private:
+ unsigned long lastTime = 0;
+
+ // needRedraw marks if redraw is required to prevent often redrawing.
+ bool needRedraw = true;
+
+ // Next variables hold the previous known values to determine if redraw is
+ // required.
+ String knownSsid = "";
+ IPAddress knownIp;
+ uint8_t knownBrightness = 0;
+ uint8_t knownEffectSpeed = 0;
+ uint8_t knownEffectIntensity = 0;
+ uint8_t knownMode = 0;
+ uint8_t knownPalette = 0;
+ uint8_t knownMinute = 99;
+ uint8_t knownHour = 99;
+
+ bool displayTurnedOff = false;
+ long lastUpdate = 0;
+ long lastRedraw = 0;
+ long overlayUntil = 0;
+ byte lineThreeType = FLD_LINE_3_BRIGHTNESS;
+ // Set to 2 or 3 to mark lines 2 or 3. Other values ignored.
+ byte markLineNum = 0;
+
+ char lineBuffer[LINE_BUFFER_SIZE];
+
+ char **modes_qstrings = nullptr;
+ char **palettes_qstrings = nullptr;
+
+ // If display does not work or looks corrupted check the
+ // constructor reference:
+ // https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
+ // or check the gallery:
+ // https://github.com/olikraus/u8g2/wiki/gallery
+ public:
+
+ // gets called once at boot. Do all initialization that doesn't depend on
+ // network here
+ void setup() {
+ u8x8.begin();
+ u8x8.setFlipMode(FLIP_MODE);
+ u8x8.setPowerSave(0);
+ u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
+ u8x8.setFont(u8x8_font_chroma48medium8_r);
+ u8x8.DRAW_STRING(0, 0*LINE_HEIGHT, "Loading...");
+
+ ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT);
+ modes_qstrings = modeSortUsermod->getModesQStrings();
+ palettes_qstrings = modeSortUsermod->getPalettesQStrings();
+ }
+
+ // gets called every time WiFi is (re-)connected. Initialize own network
+ // interfaces here
+ void connected() {}
+
+ /**
+ * Da loop.
+ */
+ void loop() {
+ if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) {
+ return;
+ }
+ lastUpdate = millis();
+
+ redraw(false);
+ }
+
+ /**
+ * Redraw the screen (but only if things have changed
+ * or if forceRedraw).
+ */
+ void redraw(bool forceRedraw) {
+ 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;
+ }
+ }
+
+ // Check if values which are shown on display changed from the last time.
+ if (forceRedraw) {
+ needRedraw = true;
+ } else if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) {
+ needRedraw = true;
+ } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) {
+ needRedraw = true;
+ } else if (knownBrightness != bri) {
+ needRedraw = true;
+ } else if (knownEffectSpeed != effectSpeed) {
+ needRedraw = true;
+ } else if (knownEffectIntensity != effectIntensity) {
+ needRedraw = true;
+ } else if (knownMode != strip.getMode()) {
+ needRedraw = true;
+ } else if (knownPalette != strip.getSegment(0).palette) {
+ needRedraw = true;
+ }
+
+ if (!needRedraw) {
+ // Nothing to change.
+ // Turn off display after 3 minutes with no change.
+ if(SLEEP_MODE_ENABLED && !displayTurnedOff &&
+ (millis() - lastRedraw > SCREEN_TIMEOUT_MS)) {
+ // We will still check if there is a change in redraw()
+ // and turn it back on if it changed.
+ sleepOrClock(true);
+ }
+ else if (displayTurnedOff && CLOCK_MODE_ENABLED) {
+ showTime();
+ }
+ return;
+ }
+ needRedraw = false;
+ lastRedraw = millis();
+
+ if (displayTurnedOff)
+ {
+ // Turn the display back on
+ sleepOrClock(false);
+ }
+
+ // Update last known values.
+ #if defined(ESP8266)
+ knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
+ #else
+ knownSsid = WiFi.SSID();
+ #endif
+ knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
+ knownBrightness = bri;
+ knownMode = strip.getMode();
+ knownPalette = strip.getSegment(0).palette;
+ knownEffectSpeed = effectSpeed;
+ knownEffectIntensity = effectIntensity;
+
+ // Do the actual drawing
+ u8x8.clear();
+ u8x8.setFont(u8x8_font_chroma48medium8_r);
+
+ // First row with Wifi name
+ String ssidString = knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0);
+ u8x8.DRAW_STRING(1, 0*LINE_HEIGHT, ssidString.c_str());
+ // Print `~` char to indicate that SSID is longer, than owr dicplay
+ if (knownSsid.length() > u8x8.getCols()) {
+ u8x8.DRAW_STRING(u8x8.getCols() - 1, 0*LINE_HEIGHT, "~");
+ }
+
+ // Second row with IP or Psssword
+ // Print password in AP mode and if led is OFF.
+ if (apActive && bri == 0) {
+ u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, apPass);
+ }
+ else {
+ String ipString = knownIp.toString();
+ u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, ipString.c_str());
+ }
+
+ // Third row with mode name
+ showCurrentEffectOrPalette(modes_qstrings[knownMode], 2);
+
+ switch(lineThreeType) {
+ case FLD_LINE_3_BRIGHTNESS:
+ sprintf(lineBuffer, "Brightness %d", bri);
+ u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
+ break;
+ case FLD_LINE_3_EFFECT_SPEED:
+ sprintf(lineBuffer, "FX Speed %d", effectSpeed);
+ u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
+ break;
+ case FLD_LINE_3_EFFECT_INTENSITY:
+ sprintf(lineBuffer, "FX Intense %d", effectIntensity);
+ u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
+ break;
+ case FLD_LINE_3_PALETTE:
+ showCurrentEffectOrPalette(palettes_qstrings[knownPalette], 3);
+ break;
+ }
+
+ u8x8.setFont(u8x8_font_open_iconic_arrow_1x1);
+ u8x8.DRAW_GLYPH(0, markLineNum*LINE_HEIGHT, 66); // arrow icon
+
+ u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
+ u8x8.DRAW_GLYPH(0, 0*LINE_HEIGHT, 80); // wifi icon
+ u8x8.DRAW_GLYPH(0, 1*LINE_HEIGHT, 68); // home icon
+ }
+
+ /**
+ * Display the current effect or palette (desiredEntry)
+ * on the appropriate line (row).
+ *
+ * TODO: Should we cache the current effect and
+ * TODO: palette name? This seems expensive.
+ */
+ void showCurrentEffectOrPalette(char *qstring, uint8_t row) {
+ uint8_t printedChars = 1;
+ char singleJsonSymbol;
+ int i = 0;
+ while (true) {
+ singleJsonSymbol = pgm_read_byte_near(qstring + i);
+ if (singleJsonSymbol == '"' || singleJsonSymbol == '\0' ) {
+ break;
+ }
+ u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol);
+ printedChars++;
+ if ( (printedChars > u8x8.getCols() - 2)) {
+ break;
+ }
+ i++;
+ }
+ }
+
+ /**
+ * If there screen is off or in clock is displayed,
+ * this will return true. This allows us to throw away
+ * the first input from the rotary encoder but
+ * to wake up the screen.
+ */
+ bool wakeDisplay() {
+ if (displayTurnedOff) {
+ // Turn the display back on
+ sleepOrClock(false);
+ redraw(true);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Allows you to show up to two lines as overlay for a
+ * period of time.
+ * Clears the screen and prints on the middle two lines.
+ */
+ void overlay(const char* line1, const char *line2, long showHowLong) {
+ if (displayTurnedOff) {
+ // Turn the display back on
+ sleepOrClock(false);
+ }
+
+ // Print the overlay
+ u8x8.clear();
+ u8x8.setFont(u8x8_font_chroma48medium8_r);
+ if (line1) {
+ u8x8.DRAW_STRING(0, 1*LINE_HEIGHT, line1);
+ }
+ if (line2) {
+ u8x8.DRAW_STRING(0, 2*LINE_HEIGHT, line2);
+ }
+ overlayUntil = millis() + showHowLong;
+ }
+
+ /**
+ * Specify what data should be defined on line 3
+ * (the last line).
+ */
+ void setLineThreeType(byte newLineThreeType) {
+ if (newLineThreeType == FLD_LINE_3_BRIGHTNESS ||
+ newLineThreeType == FLD_LINE_3_EFFECT_SPEED ||
+ newLineThreeType == FLD_LINE_3_EFFECT_INTENSITY ||
+ newLineThreeType == FLD_LINE_3_PALETTE) {
+ lineThreeType = newLineThreeType;
+ }
+ else {
+ // Unknown value.
+ lineThreeType = FLD_LINE_3_BRIGHTNESS;
+ }
+ }
+
+ /**
+ * Line 2 or 3 (last two lines) can be marked with an
+ * arrow in the first column. Pass 2 or 3 to this to
+ * specify which line to mark with an arrow.
+ * Any other values are ignored.
+ */
+ void setMarkLine(byte newMarkLineNum) {
+ if (newMarkLineNum == 2 || newMarkLineNum == 3) {
+ markLineNum = newMarkLineNum;
+ }
+ else {
+ markLineNum = 0;
+ }
+ }
+
+ /*
+ * 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
+ }
+ */
+
+ /**
+ * Enable sleep (turn the display off) or clock mode.
+ */
+ void sleepOrClock(bool enabled) {
+ if (enabled) {
+ if (CLOCK_MODE_ENABLED) {
+ showTime();
+ }
+ else {
+ u8x8.setPowerSave(1);
+ }
+ displayTurnedOff = true;
+ }
+ else {
+ if (!CLOCK_MODE_ENABLED) {
+ u8x8.setPowerSave(0);
+ }
+ displayTurnedOff = false;
+ }
+ }
+
+ /**
+ * Display the current date and time in large characters
+ * on the middle rows. Based 24 or 12 hour depending on
+ * the useAMPM configuration.
+ */
+ void showTime() {
+ updateLocalTime();
+ byte minuteCurrent = minute(localTime);
+ byte hourCurrent = hour(localTime);
+ if (knownMinute == minuteCurrent && knownHour == hourCurrent) {
+ // Time hasn't changed.
+ return;
+ }
+ knownMinute = minuteCurrent;
+ knownHour = hourCurrent;
+
+ u8x8.clear();
+ u8x8.setFont(u8x8_font_chroma48medium8_r);
+
+ int currentMonth = month(localTime);
+ sprintf(lineBuffer, "%s %d", monthShortStr(currentMonth), day(localTime));
+ u8x8.DRAW_BIG_STRING(DATE_INDENT, TIME_LINE*LINE_HEIGHT, lineBuffer);
+
+ byte showHour = hourCurrent;
+ boolean isAM = false;
+ if (useAMPM) {
+ if (showHour == 0) {
+ showHour = 12;
+ isAM = true;
+ }
+ else if (showHour > 12) {
+ showHour -= 12;
+ isAM = false;
+ }
+ else {
+ isAM = true;
+ }
+ }
+
+ sprintf(lineBuffer, "%02d:%02d %s", showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : "");
+ // For time, we always use LINE_HEIGHT of 2 since
+ // we are printing it big.
+ u8x8.DRAW_BIG_STRING(TIME_INDENT + (useAMPM ? 0 : 2), (TIME_LINE + 1) * 2, lineBuffer);
+ }
+
+ /*
+ * 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) {
+ }
+
+ /*
+ * 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) {
+ }
+
+ /*
+ * 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) {
+ }
+
+ /*
+ * 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) {
+ }
+
+ /*
+ * 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_FOUR_LINE_DISP;
+ }
+
+};
\ No newline at end of file
diff --git a/usermods/usermod_v2_mode_sort/readme.md b/usermods/usermod_v2_mode_sort/readme.md
new file mode 100644
index 0000000000..b4fe90e73e
--- /dev/null
+++ b/usermods/usermod_v2_mode_sort/readme.md
@@ -0,0 +1,33 @@
+# Mode Sort
+
+v2 usermod that provides data about modes and
+palettes to other usermods. Notably it provides:
+* A direct method for a mode or palette name
+* Ability to retrieve mode and palette names in
+ alphabetical order
+
+```char **getModesQStrings()```
+
+Provides an array of char* (pointers) to the names of the
+palettes within JSON_mode_names, in the same order as
+JSON_mode_names. These strings end in double quote (")
+(or \0 if there is a problem).
+
+```byte *getModesAlphaIndexes()```
+
+An array of byte designating the indexes of names of the
+modes in alphabetical order. "Solid" will always remain
+at the front of the list.
+
+```char **getPalettesQStrings()```
+
+Provides an array of char* (pointers) to the names of the
+palettes within JSON_palette_names, in the same order as
+JSON_palette_names. These strings end in double quote (")
+(or \0 if there is a problem).
+
+```byte *getPalettesAlphaIndexes()```
+
+An array of byte designating the indexes of names of the
+palettes in alphabetical order. "Default" and those
+starting with "(" will always remain at the front of the list.
diff --git a/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h
new file mode 100644
index 0000000000..2be7ce84c0
--- /dev/null
+++ b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h
@@ -0,0 +1,248 @@
+#pragma once
+
+#include "wled.h"
+
+//
+// v2 usermod that provides data about modes and
+// palettes to other usermods. Notably it provides:
+// * A direct method for a mode or palette name
+// * Ability to retrieve mode and palette names in
+// alphabetical order
+//
+// char **getModesQStrings()
+// Provides an array of char* (pointers) to the names of the
+// palettes within JSON_mode_names, in the same order as
+// JSON_mode_names. These strings end in double quote (")
+// (or \0 if there is a problem).
+//
+// byte *getModesAlphaIndexes()
+// An array of byte designating the indexes of names of the
+// modes in alphabetical order. "Solid" will always remain
+// at the front of the list.
+//
+// char **getPalettesQStrings()
+// Provides an array of char* (pointers) to the names of the
+// palettes within JSON_palette_names, in the same order as
+// JSON_palette_names. These strings end in double quote (")
+// (or \0 if there is a problem).
+//
+// byte *getPalettesAlphaIndexes()
+// An array of byte designating the indexes of names of the
+// palettes in alphabetical order. "Default" and those
+// starting with "(" will always remain at the front of the list.
+//
+
+// Number of modes at the start of the list to not sort
+#define MODE_SORT_SKIP_COUNT 1
+
+// Which list is being sorted
+char **listBeingSorted = nullptr;
+
+/**
+ * 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.
+ */
+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 ModeSortUsermod : public Usermod {
+private:
+
+ // 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;
+
+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.
+ */
+ void setup() {
+ // Sort the modes and palettes on startup
+ // as they are guarantted to change.
+ sortModesAndPalettes();
+ }
+
+ char **getModesQStrings() {
+ return modes_qstrings;
+ }
+
+ byte *getModesAlphaIndexes() {
+ return modes_alpha_indexes;
+ }
+
+ char **getPalettesQStrings() {
+ return palettes_qstrings;
+ }
+
+ byte *getPalettesAlphaIndexes() {
+ return palettes_alpha_indexes;
+ }
+
+ /**
+ * This Usermod doesn't have anything for loop.
+ */
+ void loop() {}
+
+ /**
+ * 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());
+
+ int skipPaletteCount = 1;
+ while (true) {
+ // How many palette names start with '*' and should not be sorted?
+ // (Also skipping the first one, 'Default').
+ if (pgm_read_byte_near(palettes_qstrings[skipPaletteCount]) == '*') {
+ skipPaletteCount++;
+ }
+ else {
+ break;
+ }
+ }
+
+ 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);
+ switch (singleJsonSymbol) {
+ case '"':
+ insideQuotes = !insideQuotes;
+ if (insideQuotes) {
+ // We have a new mode or palette
+ modeStrings[modeIndex] = (char *)(json + i + 1);
+ }
+ break;
+ case '[':
+ break;
+ case ']':
+ complete = true;
+ break;
+ case ',':
+ 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;
+ }
+
+ /*
+ * 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) {}
+
+ /*
+ * 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) {}
+
+ /*
+ * 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_MODE_SORT;
+ }
+};
diff --git a/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample b/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample
new file mode 100644
index 0000000000..cc39d65cf9
--- /dev/null
+++ b/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample
@@ -0,0 +1,48 @@
+[platformio]
+default_envs = d1_mini
+; default_envs = esp32dev
+
+[env:esp32dev]
+board = esp32dev
+platform = espressif32@3.2
+build_unflags = ${common.build_unflags}
+build_flags =
+ ${common.build_flags_esp32}
+ -D USERMOD_MODE_SORT
+ -D USERMOD_FOUR_LINE_DISLAY -D FLD_PIN_SCL=22 -D FLD_PIN_SDA=21
+ -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19
+ -D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1
+ -D LEDPIN=16 -D BTNPIN=13
+upload_speed = 460800
+lib_ignore =
+ ESPAsyncTCP
+ ESPAsyncUDP
+
+[env:d1_mini]
+board = d1_mini
+platform = ${common.platform_wled_default}
+platform_packages = ${common.platform_packages}
+upload_speed = 460800
+board_build.ldscript = ${common.ldscript_4m1m}
+build_unflags = ${common.build_unflags}
+build_flags =
+ ${common.build_flags_esp8266}
+ -D USERMOD_MODE_SORT
+ -D USERMOD_FOUR_LINE_DISLAY -D FLD_PIN_SCL=5 -D FLD_PIN_SDA=4
+ -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=12 -D ENCODER_CLK_PIN=14 -D ENCODER_SW_PIN=13
+ -D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1
+ -D LEDPIN=3 -D BTNPIN=0
+monitor_filters = esp8266_exception_decoder
+
+[env]
+lib_deps =
+ fastled/FastLED @ 3.3.2
+ NeoPixelBus @ 2.6.0
+ ESPAsyncTCP @ 1.2.0
+ ESPAsyncUDP
+ AsyncTCP @ 1.0.3
+ IRremoteESP8266 @ 2.7.3
+ https://github.com/lorol/LITTLEFS.git
+ https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.0
+ U8g2@~2.27.2
+ Wire
diff --git a/usermods/usermod_v2_rotary_encoder_ui/readme.md b/usermods/usermod_v2_rotary_encoder_ui/readme.md
new file mode 100644
index 0000000000..4477590b58
--- /dev/null
+++ b/usermods/usermod_v2_rotary_encoder_ui/readme.md
@@ -0,0 +1,33 @@
+# Rotary Encoder UI Usermod
+
+First, thanks to the authors of other Rotary Encoder usermods.
+
+This usermod starts to provide a relatively complete on-device
+UI when paired with the Four Line Display usermod. I strongly
+encourage you to try them together.
+
+[See the pair of usermods in action](https://www.youtube.com/watch?v=tITQY80rIOA)
+
+## Installation
+
+Copy and update the example `platformio_override.ini.sample` to the root directory of your particular build.
+This file should be placed in the same directory as `platformio.ini`.
+
+### Define Your Options
+
+* `USERMOD_ROTARY_ENCODER_UI` - define this to have this user mod included wled00\usermods_list.cpp
+* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details)
+* `ENCODER_DT_PIN` - The encoders DT pin, defaults to 12
+* `ENCODER_CLK_PIN` - The encoders CLK pin, defaults to 14
+* `ENCODER_SW_PIN` - The encoders SW pin, defaults to 13
+
+### PlatformIO requirements
+
+No special requirements.
+
+Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`.
+
+## Change Log
+
+2021-02
+* First public release
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
new file mode 100644
index 0000000000..6dc2a1be3f
--- /dev/null
+++ b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h
@@ -0,0 +1,401 @@
+#pragma once
+
+#include "wled.h"
+
+//
+// Inspired by the v1 usermods
+// * rotary_encoder_change_brightness
+// * rotary_encoder_change_effect
+//
+// v2 usermod that provides a rotary encoder-based UI.
+//
+// This usermod allows you to control:
+//
+// * Brightness
+// * Selected Effect
+// * Effect Speed
+// * Effect Intensity
+// * Palette
+//
+// Change between modes by pressing a button.
+//
+// Dependencies
+// * This usermod REQURES the ModeSortUsermod
+// * This Usermod works best coupled with
+// FourLineDisplayUsermod.
+//
+
+#ifndef ENCODER_DT_PIN
+#define ENCODER_DT_PIN 12
+#endif
+
+#ifndef ENCODER_CLK_PIN
+#define ENCODER_CLK_PIN 14
+#endif
+
+#ifndef ENCODER_SW_PIN
+#define ENCODER_SW_PIN 13
+#endif
+
+#ifndef USERMOD_FOUR_LINE_DISLAY
+// These constants won't be defined if we aren't using FourLineDisplay.
+#define FLD_LINE_3_BRIGHTNESS 0
+#define FLD_LINE_3_EFFECT_SPEED 0
+#define FLD_LINE_3_EFFECT_INTENSITY 0
+#define FLD_LINE_3_PALETTE 0
+#endif
+
+
+// The last UI state
+#define LAST_UI_STATE 4
+
+
+class RotaryEncoderUIUsermod : public Usermod {
+private:
+ int fadeAmount = 10; // Amount to change every step (brightness)
+ unsigned long currentTime;
+ unsigned long loopTime;
+ const int pinA = ENCODER_DT_PIN; // DT from encoder
+ const int pinB = ENCODER_CLK_PIN; // CLK from encoder
+ const int pinC = ENCODER_SW_PIN; // SW from encoder
+ unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed
+ unsigned char button_state = HIGH;
+ unsigned char prev_button_state = HIGH;
+
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ FourLineDisplayUsermod *display;
+#else
+ void* display = nullptr;
+#endif
+
+ byte *modes_alpha_indexes = nullptr;
+ byte *palettes_alpha_indexes = nullptr;
+
+ unsigned char Enc_A;
+ unsigned char Enc_B;
+ unsigned char Enc_A_prev = 0;
+
+ bool currentEffectAndPaleeteInitialized = false;
+ uint8_t effectCurrentIndex = 0;
+ uint8_t effectPaletteIndex = 0;
+
+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.
+ */
+ void setup()
+ {
+ pinMode(pinA, INPUT_PULLUP);
+ pinMode(pinB, INPUT_PULLUP);
+ pinMode(pinC, INPUT_PULLUP);
+ currentTime = millis();
+ loopTime = currentTime;
+
+ ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT);
+ modes_alpha_indexes = modeSortUsermod->getModesAlphaIndexes();
+ palettes_alpha_indexes = modeSortUsermod->getPalettesAlphaIndexes();
+
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ // This Usermod uses FourLineDisplayUsermod for the best experience.
+ // But it's optional. But you want it.
+ display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
+ if (display != nullptr) {
+ display->setLineThreeType(FLD_LINE_3_BRIGHTNESS);
+ display->setMarkLine(3);
+ }
+#endif
+ }
+
+ /*
+ * 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.
+ */
+ void loop()
+ {
+ currentTime = millis(); // get the current elapsed time
+
+ // Initialize effectCurrentIndex and effectPaletteIndex to
+ // current state. We do it here as (at least) effectCurrent
+ // is not yet initialized when setup is called.
+ if (!currentEffectAndPaleeteInitialized) {
+ findCurrentEffectAndPalette();
+ }
+
+ if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
+ {
+ button_state = digitalRead(pinC);
+ if (prev_button_state != button_state)
+ {
+ if (button_state == LOW)
+ {
+ prev_button_state = button_state;
+
+ char newState = select_state + 1;
+ if (newState > LAST_UI_STATE) newState = 0;
+
+ bool changedState = true;
+ if (display != nullptr) {
+ switch(newState) {
+ case 0:
+ changedState = changeState("Brightness", FLD_LINE_3_BRIGHTNESS, 3);
+ break;
+ case 1:
+ changedState = changeState("Select FX", FLD_LINE_3_EFFECT_SPEED, 2);
+ break;
+ case 2:
+ changedState = changeState("FX Speed", FLD_LINE_3_EFFECT_SPEED, 3);
+ break;
+ case 3:
+ changedState = changeState("FX Intensity", FLD_LINE_3_EFFECT_INTENSITY, 3);
+ break;
+ case 4:
+ changedState = changeState("Palette", FLD_LINE_3_PALETTE, 3);
+ break;
+ }
+ }
+ if (changedState) {
+ select_state = newState;
+ }
+ }
+ else
+ {
+ prev_button_state = button_state;
+ }
+ }
+ int Enc_A = digitalRead(pinA); // Read encoder pins
+ int Enc_B = digitalRead(pinB);
+ if ((!Enc_A) && (Enc_A_prev))
+ { // A has gone from high to low
+ if (Enc_B == HIGH)
+ { // B is high so clockwise
+ switch(select_state) {
+ case 0:
+ changeBrightness(true);
+ break;
+ case 1:
+ changeEffect(true);
+ break;
+ case 2:
+ changeEffectSpeed(true);
+ break;
+ case 3:
+ changeEffectIntensity(true);
+ break;
+ case 4:
+ changePalette(true);
+ break;
+ }
+ }
+ else if (Enc_B == LOW)
+ { // B is low so counter-clockwise
+ switch(select_state) {
+ case 0:
+ changeBrightness(false);
+ break;
+ case 1:
+ changeEffect(false);
+ break;
+ case 2:
+ changeEffectSpeed(false);
+ break;
+ case 3:
+ changeEffectIntensity(false);
+ break;
+ case 4:
+ changePalette(false);
+ break;
+ }
+ }
+ }
+ Enc_A_prev = Enc_A; // Store value of A for next time
+ loopTime = currentTime; // Updates loopTime
+ }
+ }
+
+ void findCurrentEffectAndPalette() {
+ currentEffectAndPaleeteInitialized = true;
+ for (uint8_t i = 0; i < strip.getModeCount(); i++) {
+ byte value = modes_alpha_indexes[i];
+ if (modes_alpha_indexes[i] == effectCurrent) {
+ effectCurrentIndex = i;
+ break;
+ }
+ }
+
+ for (uint8_t i = 0; i < strip.getPaletteCount(); i++) {
+ byte value = palettes_alpha_indexes[i];
+ if (palettes_alpha_indexes[i] == strip.getSegment(0).palette) {
+ effectPaletteIndex = i;
+ break;
+ }
+ }
+ }
+
+ boolean changeState(const char *stateName, byte lineThreeMode, byte markedLine) {
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ if (display != nullptr) {
+ if (display->wakeDisplay()) {
+ // Throw away wake up input
+ return false;
+ }
+ display->overlay("Mode change", stateName, 1500);
+ display->setLineThreeType(lineThreeMode);
+ display->setMarkLine(markedLine);
+ }
+ #endif
+ return true;
+ }
+
+ void lampUdated() {
+ bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette);
+
+ //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
+ // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
+ colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
+ updateInterfaces(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
+ }
+
+ void changeBrightness(bool increase) {
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ 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();
+ }
+
+ void changeEffect(bool increase) {
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ 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();
+ }
+
+ void changeEffectSpeed(bool increase) {
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ 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();
+ }
+
+ void changeEffectIntensity(bool increase) {
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ 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();
+ }
+
+ void changePalette(bool increase) {
+#ifdef USERMOD_FOUR_LINE_DISLAY
+ 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();
+ }
+
+ /*
+ * 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
+ }
+ */
+
+ /*
+ * 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
+ */
+ 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!"));
+ }
+
+ /*
+ * 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_ROTARY_ENC_UI;
+ }
+};
diff --git a/wled00/FX.cpp b/wled00/FX.cpp
index cc96987a5a..308b864c97 100644
--- a/wled00/FX.cpp
+++ b/wled00/FX.cpp
@@ -25,11 +25,11 @@
*/
#include "FX.h"
+#include "tv_colors.h"
#define IBN 5100
#define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3)
-
/*
* No blinking. Just plain old static light.
*/
@@ -233,9 +233,9 @@ uint16_t WS2812FX::mode_random_color(void) {
/*
* Lights every LED in a random color. Changes all LED at the same time
-// * to new random colors.
+ * to new random colors.
*/
-uint16_t WS2812FX::mode_dynamic(void) {
+uint16_t WS2812FX::dynamic(boolean smooth=false) {
if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed
if(SEGENV.call == 0) {
@@ -252,12 +252,31 @@ uint16_t WS2812FX::mode_dynamic(void) {
SEGENV.step = it;
}
- for (uint16_t i = 0; i < SEGLEN; i++) {
- setPixelColor(i, color_wheel(SEGENV.data[i]));
- }
+ if (smooth) {
+ for (uint16_t i = 0; i < SEGLEN; i++) {
+ blendPixelColor(i, color_wheel(SEGENV.data[i]),16);
+ }
+ } else {
+ for (uint16_t i = 0; i < SEGLEN; i++) {
+ setPixelColor(i, color_wheel(SEGENV.data[i]));
+ }
+ }
return FRAMETIME;
}
+/*
+ * Original effect "Dynamic"
+ */
+uint16_t WS2812FX::mode_dynamic(void) {
+ return dynamic(false);
+}
+
+/*
+ * effect "Dynamic" with smoth color-fading
+ */
+uint16_t WS2812FX::mode_dynamic_smooth(void) {
+ return dynamic(true);
+ }
/*
* Does the "standby-breathing" of well known i-Devices.
@@ -526,7 +545,7 @@ uint16_t WS2812FX::dissolve(uint32_t color) {
}
}
- if (SEGENV.call > (255 - SEGMENT.speed) + 15)
+ if (SEGENV.call > (255 - SEGMENT.speed) + 15U)
{
SEGENV.aux0 = !SEGENV.aux0;
SEGENV.call = 0;
@@ -574,7 +593,7 @@ uint16_t WS2812FX::mode_sparkle(void) {
/*
- * Lights all LEDs in the color. Flashes single white pixels randomly.
+ * Lights all LEDs in the color. Flashes single col 1 pixels randomly. (List name: Sparkle Dark)
* Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/
*/
uint16_t WS2812FX::mode_flash_sparkle(void) {
@@ -582,12 +601,14 @@ uint16_t WS2812FX::mode_flash_sparkle(void) {
setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0));
}
- if(random8(5) == 0) {
- SEGENV.aux0 = random16(SEGLEN); // aux0 stores the random led index
- setPixelColor(SEGENV.aux0, SEGCOLOR(1));
- return 20;
- }
- return 20 + (uint16_t)(255-SEGMENT.speed);
+ if (now - SEGENV.aux0 > SEGENV.step) {
+ if(random8((255-SEGMENT.intensity) >> 4) == 0) {
+ setPixelColor(random16(SEGLEN), SEGCOLOR(1)); //flash
+ }
+ SEGENV.step = now;
+ SEGENV.aux0 = 255-SEGMENT.speed;
+ }
+ return FRAMETIME;
}
@@ -600,13 +621,16 @@ uint16_t WS2812FX::mode_hyper_sparkle(void) {
setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0));
}
- if(random8(5) < 2) {
- for(uint16_t i = 0; i < MAX(1, SEGLEN/3); i++) {
- setPixelColor(random16(SEGLEN), SEGCOLOR(1));
+ if (now - SEGENV.aux0 > SEGENV.step) {
+ if(random8((255-SEGMENT.intensity) >> 4) == 0) {
+ for(uint16_t i = 0; i < MAX(1, SEGLEN/3); i++) {
+ setPixelColor(random16(SEGLEN), SEGCOLOR(1));
+ }
}
- return 20;
+ SEGENV.step = now;
+ SEGENV.aux0 = 255-SEGMENT.speed;
}
- return 20 + (uint16_t)(255-SEGMENT.speed);
+ return FRAMETIME;
}
@@ -617,22 +641,25 @@ uint16_t WS2812FX::mode_multi_strobe(void) {
for(uint16_t i = 0; i < SEGLEN; i++) {
setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1));
}
- //blink(SEGCOLOR(0), SEGCOLOR(1), true, true);
- uint16_t delay = 50 + 20*(uint16_t)(255-SEGMENT.speed);
- uint16_t count = 2 * ((SEGMENT.speed / 10) + 1);
- if(SEGENV.step < count) {
- if((SEGENV.step & 1) == 0) {
- for(uint16_t i = 0; i < SEGLEN; i++) {
- setPixelColor(i, SEGCOLOR(0));
- }
- delay = 20;
+ SEGENV.aux0 = 50 + 20*(uint16_t)(255-SEGMENT.speed);
+ uint16_t count = 2 * ((SEGMENT.intensity / 10) + 1);
+ if(SEGENV.aux1 < count) {
+ if((SEGENV.aux1 & 1) == 0) {
+ fill(SEGCOLOR(0));
+ SEGENV.aux0 = 15;
} else {
- delay = 50;
+ SEGENV.aux0 = 50;
}
}
- SEGENV.step = (SEGENV.step + 1) % (count + 1);
- return delay;
+
+ if (now - SEGENV.aux0 > SEGENV.step) {
+ SEGENV.aux1++;
+ if (SEGENV.aux1 > count) SEGENV.aux1 = 0;
+ SEGENV.step = now;
+ }
+
+ return FRAMETIME;
}
/*
@@ -974,23 +1001,13 @@ uint16_t WS2812FX::mode_running_color(void) {
return running(SEGCOLOR(0), SEGCOLOR(1));
}
-
-/*
- * Alternating red/blue pixels running.
- */
-uint16_t WS2812FX::mode_running_red_blue(void) {
- return running(RED, BLUE);
-}
-
-
/*
- * Alternating red/green pixels running.
+ * Alternating red/white pixels running.
*/
-uint16_t WS2812FX::mode_merry_christmas(void) {
- return running(RED, GREEN);
+uint16_t WS2812FX::mode_candy_cane(void) {
+ return running(RED, WHITE);
}
-
/*
* Alternating orange/purple pixels running.
*/
@@ -1017,7 +1034,7 @@ uint16_t WS2812FX::mode_running_random(void) {
}
SEGENV.step++;
- if (SEGENV.step > ((255-SEGMENT.intensity) >> 4))
+ if (SEGENV.step > (uint8_t)((255-SEGMENT.intensity) >> 4))
{
SEGENV.step = 0;
}
@@ -1240,7 +1257,7 @@ uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, bool all)
for (uint16_t i = idexB; i < idexR; i++) setPixelColor(i, color2);
}
} else { //regular dot-only mode
- uint8_t size = 1 + SEGMENT.intensity >> 3;
+ uint8_t size = 1 + (SEGMENT.intensity >> 3);
if (size > SEGLEN/2) size = 1+ SEGLEN/2;
for (uint8_t i=0; i <= size; i++) {
setPixelColor(idexR+i, color1);
@@ -1551,9 +1568,9 @@ uint16_t WS2812FX::mode_oscillate(void)
if (SEGENV.call == 0)
{
- oscillators[0] = {SEGLEN/4, SEGLEN/8, 1, 1};
- oscillators[1] = {SEGLEN/4*3, SEGLEN/8, 1, 2};
- oscillators[2] = {SEGLEN/4*2, SEGLEN/8, -1, 1};
+ oscillators[0] = {(int16_t)(SEGLEN/4), (int8_t)(SEGLEN/8), 1, 1};
+ oscillators[1] = {(int16_t)(SEGLEN/4*3), (int8_t)(SEGLEN/8), 1, 2};
+ oscillators[2] = {(int16_t)(SEGLEN/4*2), (int8_t)(SEGLEN/8), -1, 1};
}
uint32_t cycleTime = 20 + (2 * (uint32_t)(255 - SEGMENT.speed));
@@ -1594,40 +1611,42 @@ uint16_t WS2812FX::mode_oscillate(void)
uint16_t WS2812FX::mode_lightning(void)
{
uint16_t ledstart = random16(SEGLEN); // Determine starting location of flash
- uint16_t ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1)
+ uint16_t ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1)
uint8_t bri = 255/random8(1, 3);
- if (SEGENV.step == 0)
+ if (SEGENV.aux1 == 0) //init, leader flash
{
- SEGENV.aux0 = random8(3, 3 + SEGMENT.intensity/20); //number of flashes
- bri = 52;
- SEGENV.aux1 = 1;
+ SEGENV.aux1 = random8(4, 4 + SEGMENT.intensity/20); //number of flashes
+ SEGENV.aux1 *= 2;
+
+ bri = 52; //leader has lower brightness
+ SEGENV.aux0 = 200; //200ms delay after leader
}
fill(SEGCOLOR(1));
- if (SEGENV.aux1) {
+ if (SEGENV.aux1 > 3 && !(SEGENV.aux1 & 0x01)) { //flash on even number >2
for (int i = ledstart; i < ledstart + ledlen; i++)
{
- if (SEGMENT.palette == 0)
- {
- setPixelColor(i,bri,bri,bri,bri);
- } else {
- setPixelColor(i,color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, bri));
- }
+ setPixelColor(i,color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, bri));
}
- SEGENV.aux1 = 0;
- SEGENV.step++;
- return random8(4, 10); // each flash only lasts 4-10 milliseconds
- }
-
- SEGENV.aux1 = 1;
- if (SEGENV.step == 1) return (200); // longer delay until next flash after the leader
+ SEGENV.aux1--;
- if (SEGENV.step <= SEGENV.aux0) return (50 + random8(100)); // shorter delay between strokes
+ SEGENV.step = millis();
+ //return random8(4, 10); // each flash only lasts one frame/every 24ms... originally 4-10 milliseconds
+ } else {
+ if (millis() - SEGENV.step > SEGENV.aux0) {
+ SEGENV.aux1--;
+ if (SEGENV.aux1 < 2) SEGENV.aux1 = 0;
- SEGENV.step = 0;
- return (random8(255 - SEGMENT.speed) * 100); // delay between strikes
+ SEGENV.aux0 = (50 + random8(100)); //delay between flashes
+ if (SEGENV.aux1 == 2) {
+ SEGENV.aux0 = (random8(255 - SEGMENT.speed) * 100); // delay between strikes
+ }
+ SEGENV.step = millis();
+ }
+ }
+ return FRAMETIME;
}
@@ -1754,19 +1773,22 @@ uint16_t WS2812FX::mode_fire_2012()
if (it != SEGENV.step)
{
+ uint8_t ignition = max(7,SEGLEN/10); // ignition area: 10% of segment length or minimum 7 pixels
+
// Step 1. Cool down every cell a little
for (uint16_t i = 0; i < SEGLEN; i++) {
- SEGENV.data[i] = qsub8(heat[i], random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2));
+ uint8_t temp = qsub8(heat[i], random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2));
+ heat[i] = (temp==0 && i 1; k--) {
- heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
+ heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2
}
// Step 3. Randomly ignite new 'sparks' of heat near the bottom
if (random8() <= SEGMENT.intensity) {
- uint8_t y = random8(7);
+ uint8_t y = random8(ignition);
if (y < SEGLEN) heat[y] = qadd8(heat[y], random8(160,255));
}
SEGENV.step = it;
@@ -1897,7 +1919,6 @@ uint16_t WS2812FX::mode_noise16_2()
for (uint16_t i = 0; i < SEGLEN; i++) {
uint16_t shift_x = SEGENV.step >> 6; // x as a function of time
- uint16_t shift_y = SEGENV.step/42;
uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field
@@ -1961,7 +1982,7 @@ uint16_t WS2812FX::mode_colortwinkle()
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
CRGB fastled_col, prev;
- fract8 fadeUpAmount = 8 + (SEGMENT.speed/4), fadeDownAmount = 5 + (SEGMENT.speed/7);
+ fract8 fadeUpAmount = _brightness>28 ? 8 + (SEGMENT.speed>>2) : 68-_brightness, fadeDownAmount = _brightness>28 ? 8 + (SEGMENT.speed>>3) : 68-_brightness;
for (uint16_t i = 0; i < SEGLEN; i++) {
fastled_col = col_to_crgb(getPixelColor(i));
prev = fastled_col;
@@ -3114,6 +3135,59 @@ uint16_t WS2812FX::mode_drip(void)
}
+/*
+ * Tetris or Stacking (falling bricks) Effect
+ * by Blaz Kristan (https://github.com/blazoncek, https://blaz.at/home)
+ */
+typedef struct Tetris {
+ float pos;
+ float speed;
+ uint32_t col;
+} tetris;
+
+uint16_t WS2812FX::mode_tetrix(void) {
+
+ uint16_t dataSize = sizeof(tetris);
+ if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
+ Tetris* drop = reinterpret_cast(SEGENV.data);
+
+ // initialize dropping on first call or segment full
+ if (SEGENV.call == 0 || SEGENV.aux1 >= SEGLEN) {
+ SEGENV.aux1 = 0; // reset brick stack size
+ SEGENV.step = 0;
+ fill(SEGCOLOR(1));
+ return 250; // short wait
+ }
+
+ if (SEGENV.step == 0) { //init
+ drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>3)+1 : random8(6,40)); // set speed
+ drop->pos = SEGLEN-1; // start at end of segment
+ drop->col = color_from_palette(random8(0,15)<<4,false,false,0); // limit color choices so there is enough HUE gap
+ SEGENV.step = 1; // drop state (0 init, 1 forming, 2 falling)
+ SEGENV.aux0 = (SEGMENT.intensity ? (SEGMENT.intensity>>5)+1 : random8(1,5)) * (1+(SEGLEN>>6)); // size of brick
+ }
+
+ if (SEGENV.step == 1) { // forming
+ if (random8()>>6) { // random drop
+ SEGENV.step = 2; // fall
+ }
+ }
+
+ if (SEGENV.step > 1) { // falling
+ if (drop->pos > SEGENV.aux1) { // fall until top of stack
+ drop->pos -= drop->speed; // may add gravity as: speed += gravity
+ if (int(drop->pos) < SEGENV.aux1) drop->pos = SEGENV.aux1;
+ for (uint16_t i=int(drop->pos); ipos)+SEGENV.aux0 ? drop->col : SEGCOLOR(1));
+ } else { // we hit bottom
+ SEGENV.step = 0; // go back to init
+ SEGENV.aux1 += SEGENV.aux0; // increase the stack size
+ if (SEGENV.aux1 >= SEGLEN) return 1000; // wait for a second
+ }
+ }
+ return FRAMETIME;
+}
+
+
/*
/ Plasma Effect
/ adapted from https://github.com/atuline/FastLED-Demos/blob/master/plasma/plasma.ino
@@ -3123,8 +3197,8 @@ uint16_t WS2812FX::mode_plasma(void) {
uint8_t thatPhase = beatsin8(7,-64,64);
for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set color & brightness based on a wave as follows:
- uint8_t colorIndex = cubicwave8((i*(1+ 3*(SEGMENT.speed >> 5)))+(thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change.
- + cos8((i*(1+ 2*(SEGMENT.speed >> 5)))+(thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish.
+ uint8_t colorIndex = cubicwave8(((i*(1+ 3*(SEGMENT.speed >> 5)))+(thisPhase)) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change.
+ + cos8(((i*(1+ 2*(SEGMENT.speed >> 5)))+(thatPhase)) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish.
uint8_t thisBright = qsub8(colorIndex, beatsin8(6,0, (255 - SEGMENT.intensity)|0x01 ));
CRGB color = ColorFromPalette(currentPalette, colorIndex, thisBright, LINEARBLEND);
setPixelColor(i, color.red, color.green, color.blue);
@@ -3553,15 +3627,15 @@ uint16_t WS2812FX::mode_chunchun(void)
{
fill(SEGCOLOR(1));
uint16_t counter = now*(6 + (SEGMENT.speed >> 4));
- uint16_t numBirds = SEGLEN >> 2;
- uint16_t span = SEGMENT.intensity << 8;
+ uint16_t numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment
+ uint16_t span = (SEGMENT.intensity << 8) / numBirds;
for (uint16_t i = 0; i < numBirds; i++)
{
- counter -= span/numBirds;
- int megumin = sin16(counter) + 0x8000;
+ counter -= span;
+ uint16_t megumin = sin16(counter) + 0x8000;
uint32_t bird = (megumin * SEGLEN) >> 16;
- uint32_t c = color_from_palette((i * 255)/ numBirds, false, true, 0);
+ uint32_t c = color_from_palette((i * 255)/ numBirds, false, false, 0); // no palette wrapping
setPixelColor(bird, c);
}
return FRAMETIME;
@@ -3727,3 +3801,267 @@ uint16_t WS2812FX::mode_washing_machine(void) {
return FRAMETIME;
}
+
+/*
+ Blends random colors across palette
+ Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e
+*/
+uint16_t WS2812FX::mode_blends(void) {
+ uint16_t dataSize = sizeof(uint32_t) * SEGLEN;
+ 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;
+
+ for (int i = 0; i < SEGLEN; 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;
+ }
+
+ return FRAMETIME;
+}
+
+#ifndef WLED_DISABLE_FX_HIGH_FLASH_USE
+typedef struct TvSim {
+ uint32_t totalTime = 0;
+ uint32_t fadeTime = 0;
+ uint32_t startTime = 0;
+ uint32_t elapsed = 0;
+ uint32_t pixelNum = 0;
+ uint16_t pr = 0; // Prev R, G, B
+ uint16_t pg = 0;
+ uint16_t pb = 0;
+} tvSim;
+
+#define numTVPixels (sizeof(tv_colors) / 2) // 2 bytes per Pixel (5/6/5)
+#endif
+
+/*
+ TV Simulator
+ Modified and adapted to WLED by Def3nder, based on "Fake TV Light for Engineers" by Phillip Burgess https://learn.adafruit.com/fake-tv-light-for-engineers/arduino-sketch
+*/
+uint16_t WS2812FX::mode_tv_simulator(void) {
+ #ifdef WLED_DISABLE_FX_HIGH_FLASH_USE
+ return mode_static();
+ #else
+ uint16_t nr, ng, nb, r, g, b, i;
+ uint8_t hi, lo, r8, g8, b8;
+
+ if (!SEGENV.allocateData(sizeof(tvSim))) return mode_static(); //allocation failed
+ TvSim* tvSimulator = reinterpret_cast(SEGENV.data);
+
+ // initialize start of the TV-Colors
+ if (SEGENV.call == 0) {
+ tvSimulator->pixelNum = ((uint8_t)random(18)) * numTVPixels / 18; // Begin at random movie (18 in total)
+ }
+
+ // Read next 16-bit (5/6/5) color
+ hi = pgm_read_byte(&tv_colors[tvSimulator->pixelNum * 2 ]);
+ lo = pgm_read_byte(&tv_colors[tvSimulator->pixelNum * 2 + 1]);
+
+ // Expand to 24-bit (8/8/8)
+ r8 = (hi & 0xF8) | (hi >> 5);
+ g8 = ((hi << 5) & 0xff) | ((lo & 0xE0) >> 3) | ((hi & 0x06) >> 1);
+ b8 = ((lo << 3) & 0xff) | ((lo & 0x1F) >> 2);
+
+ // Apply gamma correction, further expand to 16/16/16
+ nr = (uint8_t)gamma8(r8) * 257; // New R/G/B
+ ng = (uint8_t)gamma8(g8) * 257;
+ nb = (uint8_t)gamma8(b8) * 257;
+
+ if (SEGENV.aux0 == 0) { // initialize next iteration
+ SEGENV.aux0 = 1;
+
+ // increase color-index for next loop
+ tvSimulator->pixelNum++;
+ if (tvSimulator->pixelNum >= numTVPixels) tvSimulator->pixelNum = 0;
+
+ // randomize total duration and fade duration for the actual color
+ tvSimulator->totalTime = random(250, 2500); // Semi-random pixel-to-pixel time
+ tvSimulator->fadeTime = random(0, tvSimulator->totalTime); // Pixel-to-pixel transition time
+ if (random(10) < 3) tvSimulator->fadeTime = 0; // Force scene cut 30% of time
+
+ tvSimulator->startTime = millis();
+ } // end of initialization
+
+ // how much time is elapsed ?
+ tvSimulator->elapsed = millis() - tvSimulator->startTime;
+
+ // fade from prev volor to next color
+ if (tvSimulator->elapsed < tvSimulator->fadeTime) {
+ r = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pr, nr);
+ g = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pg, ng);
+ b = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pb, nb);
+ } else { // Avoid divide-by-zero in map()
+ r = nr;
+ g = ng;
+ b = nb;
+ }
+
+ // set strip color
+ for (i = 0; i < SEGLEN; i++) {
+ setPixelColor(i, r >> 8, g >> 8, b >> 8); // Quantize to 8-bit
+ }
+
+ // if total duration has passed, remember last color and restart the loop
+ if ( tvSimulator->elapsed >= tvSimulator->totalTime) {
+ tvSimulator->pr = nr; // Prev RGB = new RGB
+ tvSimulator->pg = ng;
+ tvSimulator->pb = nb;
+ SEGENV.aux0 = 0;
+ }
+
+ return FRAMETIME;
+ #endif
+}
+
+/*
+ Aurora effect
+*/
+
+//CONFIG
+#define BACKLIGHT 5
+#define W_MAX_COUNT 20 //Number of simultaneous waves
+#define W_MAX_SPEED 6 //Higher number, higher speed
+#define W_WIDTH_FACTOR 6 //Higher number, smaller waves
+
+class AuroraWave {
+ private:
+ uint16_t ttl;
+ CRGB basecolor;
+ float basealpha;
+ uint16_t age;
+ uint16_t width;
+ float center;
+ bool goingleft;
+ float speed_factor;
+ bool alive = true;
+
+ public:
+ void init(uint32_t segment_length, CRGB color) {
+ ttl = random(500, 1501);
+ basecolor = color;
+ basealpha = random(60, 101) / (float)100;
+ age = 0;
+ width = random(segment_length / 20, segment_length / W_WIDTH_FACTOR); //half of width to make math easier
+ if (!width) width = 1;
+ center = random(101) / (float)100 * segment_length;
+ goingleft = random(0, 2) == 0;
+ speed_factor = (random(10, 31) / (float)100 * W_MAX_SPEED / 255);
+ alive = true;
+ }
+
+ CRGB getColorForLED(int ledIndex) {
+ if(ledIndex < center - width || ledIndex > center + width) return 0; //Position out of range of this wave
+
+ CRGB rgb;
+
+ //Offset of this led from center of wave
+ //The further away from the center, the dimmer the LED
+ float offset = ledIndex - center;
+ if (offset < 0) offset = -offset;
+ float offsetFactor = offset / width;
+
+ //The age of the wave determines it brightness.
+ //At half its maximum age it will be the brightest.
+ float ageFactor = 0.1;
+ if((float)age / ttl < 0.5) {
+ ageFactor = (float)age / (ttl / 2);
+ } else {
+ ageFactor = (float)(ttl - age) / ((float)ttl * 0.5);
+ }
+
+ //Calculate color based on above factors and basealpha value
+ float factor = (1 - offsetFactor) * ageFactor * basealpha;
+ rgb.r = basecolor.r * factor;
+ rgb.g = basecolor.g * factor;
+ rgb.b = basecolor.b * factor;
+
+ return rgb;
+ };
+
+ //Change position and age of wave
+ //Determine if its sill "alive"
+ void update(uint32_t segment_length, uint32_t speed) {
+ if(goingleft) {
+ center -= speed_factor * speed;
+ } else {
+ center += speed_factor * speed;
+ }
+
+ age++;
+
+ if(age > ttl) {
+ alive = false;
+ } else {
+ if(goingleft) {
+ if(center + width < 0) {
+ alive = false;
+ }
+ } else {
+ if(center - width > segment_length) {
+ alive = false;
+ }
+ }
+ }
+ };
+
+ bool stillAlive() {
+ return alive;
+ };
+};
+
+uint16_t WS2812FX::mode_aurora(void) {
+ //aux1 = Wavecount
+ //aux2 = Intensity in last loop
+
+ AuroraWave* waves;
+
+ if(SEGENV.aux0 != SEGMENT.intensity || SEGENV.call == 0) {
+ //Intensity slider changed or first call
+ SEGENV.aux1 = ((float)SEGMENT.intensity / 255) * W_MAX_COUNT;
+ SEGENV.aux0 = SEGMENT.intensity;
+
+ if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) {
+ return mode_static(); //allocation failed
+ }
+
+ waves = reinterpret_cast(SEGENV.data);
+
+ for(int i = 0; i < SEGENV.aux1; i++) {
+ waves[i].init(SEGLEN, col_to_crgb(color_from_palette(random8(), false, false, random(0, 3))));
+ }
+ } else {
+ waves = reinterpret_cast(SEGENV.data);
+ }
+
+ for(int i = 0; i < SEGENV.aux1; i++) {
+ //Update values of wave
+ waves[i].update(SEGLEN, SEGMENT.speed);
+
+ if(!(waves[i].stillAlive())) {
+ //If a wave dies, reinitialize it starts over.
+ waves[i].init(SEGLEN, col_to_crgb(color_from_palette(random8(), false, false, random(0, 3))));
+ }
+ }
+
+ //Loop through LEDs to determine color
+ for(int i = 0; i < SEGLEN; i++) {
+ CRGB mixedRgb = CRGB(BACKLIGHT, BACKLIGHT, BACKLIGHT);
+
+ //For each LED we must check each wave if it is "active" at this position.
+ //If there are multiple waves active on a LED we multiply their values.
+ for(int j = 0; j < SEGENV.aux1; j++) {
+ CRGB rgb = waves[j].getColorForLED(i);
+
+ if(rgb != CRGB(0)) {
+ mixedRgb += rgb;
+ }
+ }
+
+ setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2], BACKLIGHT);
+ }
+
+ return FRAMETIME;
+}
\ No newline at end of file
diff --git a/wled00/FX.h b/wled00/FX.h
index 6e0b464353..2cd4b688d1 100644
--- a/wled00/FX.h
+++ b/wled00/FX.h
@@ -24,15 +24,11 @@
Modified for WLED
*/
+#include "wled.h"
+
#ifndef WS2812FX_h
#define WS2812FX_h
-#ifdef ESP32_MULTISTRIP
- #include "../usermods/esp32_multistrip/NpbWrapper.h"
-#else
- #include "NpbWrapper.h"
-#endif
-
#include "const.h"
#define FASTLED_INTERNAL //remove annoying pragma messages
@@ -52,6 +48,9 @@
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif
+/* Disable effects with high flash memory usage (currently TV simulator) - saves 18.5kB */
+//#define WLED_DISABLE_FX_HIGH_FLASH_USE
+
/* Not used in all effects yet */
#define WLED_FPS 42
#define FRAMETIME (1000/WLED_FPS)
@@ -59,16 +58,15 @@
/* 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 12
+ #define MAX_NUM_SEGMENTS 12
+ /* How many color transitions can run at once */
+ #define MAX_NUM_TRANSITIONS 8
+ /* How much data bytes all segments combined may allocate */
+ #define MAX_SEGMENT_DATA 2048
#else
- #define MAX_NUM_SEGMENTS 16
-#endif
-
-/* How much data bytes all segments combined may allocate */
-#ifdef ESP8266
-#define MAX_SEGMENT_DATA 2048
-#else
-#define MAX_SEGMENT_DATA 8192
+ #define MAX_NUM_SEGMENTS 16
+ #define MAX_NUM_TRANSITIONS 16
+ #define MAX_SEGMENT_DATA 8192
#endif
#define LED_SKIP_AMOUNT 1
@@ -76,7 +74,7 @@
#define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT _segments[_segment_index]
-#define SEGCOLOR(x) gamma32(_segments[_segment_index].colors[x])
+#define SEGCOLOR(x) _colors_t[x]
#define SEGENV _segment_runtimes[_segment_index]
#define SEGLEN _virtualSegmentLength
#define SEGACT SEGMENT.stop
@@ -116,7 +114,7 @@
#define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE )
#define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED )
-#define MODE_COUNT 114
+#define MODE_COUNT 118
#define FX_MODE_STATIC 0
#define FX_MODE_BLINK 1
@@ -156,13 +154,13 @@
#define FX_MODE_TRAFFIC_LIGHT 35
#define FX_MODE_COLOR_SWEEP_RANDOM 36
#define FX_MODE_RUNNING_COLOR 37
-#define FX_MODE_RUNNING_RED_BLUE 38
+#define FX_MODE_AURORA 38
#define FX_MODE_RUNNING_RANDOM 39
#define FX_MODE_LARSON_SCANNER 40
#define FX_MODE_COMET 41
#define FX_MODE_FIREWORKS 42
#define FX_MODE_RAIN 43
-#define FX_MODE_MERRY_CHRISTMAS 44
+#define FX_MODE_TETRIX 44
#define FX_MODE_FIRE_FLICKER 45
#define FX_MODE_GRADIENT 46
#define FX_MODE_LOADING 47
@@ -232,12 +230,19 @@
#define FX_MODE_CHUNCHUN 111
#define FX_MODE_DANCING_SHADOWS 112
#define FX_MODE_WASHING_MACHINE 113
+#define FX_MODE_CANDY_CANE 114
+#define FX_MODE_BLENDS 115
+#define FX_MODE_TV_SIMULATOR 116
+#define FX_MODE_DYNAMIC_SMOOTH 117
+
class WS2812FX {
typedef uint16_t (WS2812FX::*mode_ptr)(void);
// pre show callback
typedef void (*show_callback) (void);
+
+ static WS2812FX* instance;
// segment parameters
public:
@@ -252,14 +257,40 @@ class WS2812FX {
uint8_t grouping, spacing;
uint8_t opacity;
uint32_t colors[NUM_COLORS];
- void setOption(uint8_t n, bool val)
+ bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed
+ if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false;
+ if (c == colors[slot]) return false;
+ ColorTransition::startTransition(opacity, colors[slot], instance->_transitionDur, segn, slot);
+ colors[slot] = c; return true;
+ }
+ void setOpacity(uint8_t o, uint8_t segn) {
+ if (segn >= MAX_NUM_SEGMENTS) return;
+ if (opacity == o) return;
+ ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
+ opacity = o;
+ }
+ /*uint8_t actualOpacity() { //respects On/Off state
+ if (!getOption(SEG_OPTION_ON)) return 0;
+ return opacity;
+ }*/
+ void setOption(uint8_t n, bool val, uint8_t segn = 255)
{
+ //bool prevOn = false;
+ //if (n == SEG_OPTION_ON) prevOn = getOption(SEG_OPTION_ON);
if (val) {
options |= 0x01 << n;
} else
{
options &= ~(0x01 << n);
}
+ //transitions on segment on/off don't work correctly at this point
+ /*if (n == SEG_OPTION_ON && segn < MAX_NUM_SEGMENTS && getOption(SEG_OPTION_ON) != prevOn) {
+ if (getOption(SEG_OPTION_ON)) {
+ ColorTransition::startTransition(0, colors[0], instance->_transitionDur, segn, 0);
+ } else {
+ ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
+ }
+ }*/
}
bool getOption(uint8_t n)
{
@@ -302,10 +333,10 @@ class WS2812FX {
bool allocateData(uint16_t len){
if (data && _dataLen == len) return true; //already allocated
deallocateData();
- if (WS2812FX::_usedSegmentData + len > MAX_SEGMENT_DATA) return false; //not enough memory
+ if (WS2812FX::instance->_usedSegmentData + len > MAX_SEGMENT_DATA) return false; //not enough memory
data = new (std::nothrow) byte[len];
if (!data) return false; //allocation failed
- WS2812FX::_usedSegmentData += len;
+ WS2812FX::instance->_usedSegmentData += len;
_dataLen = len;
memset(data, 0, len);
return true;
@@ -313,15 +344,116 @@ class WS2812FX {
void deallocateData(){
delete[] data;
data = nullptr;
- WS2812FX::_usedSegmentData -= _dataLen;
+ WS2812FX::instance->_usedSegmentData -= _dataLen;
_dataLen = 0;
}
- void reset(){next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; deallocateData();}
+
+ /**
+ * If reset of this segment was request, clears runtime
+ * settings of this segment.
+ * Must not be called while an effect mode function is running
+ * because it could access the data buffer and this method
+ * may free that data buffer.
+ */
+ void resetIfRequired() {
+ if (_requiresReset) {
+ next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
+ deallocateData();
+ _requiresReset = false;
+ }
+ }
+
+ /**
+ * Flags that before the next effect is calculated,
+ * the internal segment state should be reset.
+ * Call resetIfRequired before calling the next effect function.
+ */
+ void reset() { _requiresReset = true; }
private:
uint16_t _dataLen = 0;
+ bool _requiresReset = false;
} segment_runtime;
+ typedef struct ColorTransition { // 12 bytes
+ uint32_t colorOld = 0;
+ uint32_t transitionStart;
+ uint16_t transitionDur;
+ uint8_t segment = 0xFF; //lower 6 bits: the segment this transition is for (255 indicates transition not in use/available) upper 2 bits: color channel
+ uint8_t briOld = 0;
+ static void startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot) {
+ if (segn >= MAX_NUM_SEGMENTS || slot >= NUM_COLORS || dur == 0) return;
+ if (instance->_brightness == 0) return; //do not need transitions if master bri is off
+ uint8_t tIndex = 0xFF; //none found
+ uint16_t tProgression = 0;
+ uint8_t s = segn + (slot << 6); //merge slot and segment into one byte
+
+ for (uint8_t i = 0; i < MAX_NUM_TRANSITIONS; i++) {
+ uint8_t tSeg = instance->transitions[i].segment;
+ //see if this segment + color already has a running transition
+ if (tSeg == s) {
+ tIndex = i; break;
+ }
+ if (tSeg == 0xFF) { //free transition
+ tIndex = i; tProgression = 0xFFFF;
+ }
+ }
+
+ if (tIndex == 0xFF) { //no slot found yet
+ for (uint8_t i = 0; i < MAX_NUM_TRANSITIONS; i++) {
+ //find most progressed transition to overwrite
+ uint16_t prog = instance->transitions[i].progress();
+ if (prog > tProgression) {
+ tIndex = i; tProgression = prog;
+ }
+ }
+ }
+
+ ColorTransition& t = instance->transitions[tIndex];
+ if (t.segment == s) //this is an active transition on the same segment+color
+ {
+ t.briOld = t.currentBri();
+ t.colorOld = t.currentColor(oldCol);
+ } else {
+ t.briOld = oldBri;
+ t.colorOld = oldCol;
+ uint8_t prevSeg = t.segment & 0x3F;
+ if (prevSeg < MAX_NUM_SEGMENTS) instance->_segments[prevSeg].setOption(SEG_OPTION_TRANSITIONAL, false);
+ }
+ t.transitionDur = dur;
+ t.transitionStart = millis();
+ t.segment = s;
+ instance->_segments[segn].setOption(SEG_OPTION_TRANSITIONAL, true);
+ //refresh immediately, required for Solid mode
+ if (instance->_segment_runtimes[segn].next_time > t.transitionStart + 22) instance->_segment_runtimes[segn].next_time = t.transitionStart;
+ }
+ uint16_t progress(bool allowEnd = false) { //transition progression between 0-65535
+ uint32_t timeNow = millis();
+ if (timeNow - transitionStart > transitionDur) {
+ if (allowEnd) {
+ uint8_t segn = segment & 0x3F;
+ if (segn < MAX_NUM_SEGMENTS) instance->_segments[segn].setOption(SEG_OPTION_TRANSITIONAL, false);
+ segment = 0xFF;
+ }
+ return 0xFFFF;
+ }
+ uint32_t elapsed = timeNow - transitionStart;
+ uint32_t prog = elapsed * 0xFFFF / transitionDur;
+ return (prog > 0xFFFF) ? 0xFFFF : prog;
+ }
+ uint32_t currentColor(uint32_t colorNew) {
+ return instance->color_blend(colorOld, colorNew, progress(true), true);
+ }
+ uint8_t currentBri() {
+ uint8_t segn = segment & 0x3F;
+ if (segn >= MAX_NUM_SEGMENTS) return 0;
+ uint8_t briNew = instance->_segments[segn].opacity;
+ uint32_t prog = progress() + 1;
+ return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16;
+ }
+ } color_transition;
+
WS2812FX() {
+ WS2812FX::instance = this;
//assign each member of the _mode[] array to its respective function reference
_mode[FX_MODE_STATIC] = &WS2812FX::mode_static;
_mode[FX_MODE_BLINK] = &WS2812FX::mode_blink;
@@ -359,13 +491,13 @@ class WS2812FX {
_mode[FX_MODE_TRAFFIC_LIGHT] = &WS2812FX::mode_traffic_light;
_mode[FX_MODE_COLOR_SWEEP_RANDOM] = &WS2812FX::mode_color_sweep_random;
_mode[FX_MODE_RUNNING_COLOR] = &WS2812FX::mode_running_color;
- _mode[FX_MODE_RUNNING_RED_BLUE] = &WS2812FX::mode_running_red_blue;
+ _mode[FX_MODE_AURORA] = &WS2812FX::mode_aurora;
_mode[FX_MODE_RUNNING_RANDOM] = &WS2812FX::mode_running_random;
_mode[FX_MODE_LARSON_SCANNER] = &WS2812FX::mode_larson_scanner;
_mode[FX_MODE_COMET] = &WS2812FX::mode_comet;
_mode[FX_MODE_FIREWORKS] = &WS2812FX::mode_fireworks;
_mode[FX_MODE_RAIN] = &WS2812FX::mode_rain;
- _mode[FX_MODE_MERRY_CHRISTMAS] = &WS2812FX::mode_merry_christmas;
+ _mode[FX_MODE_TETRIX] = &WS2812FX::mode_tetrix;
_mode[FX_MODE_FIRE_FLICKER] = &WS2812FX::mode_fire_flicker;
_mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient;
_mode[FX_MODE_LOADING] = &WS2812FX::mode_loading;
@@ -437,6 +569,10 @@ class WS2812FX {
_mode[FX_MODE_CHUNCHUN] = &WS2812FX::mode_chunchun;
_mode[FX_MODE_DANCING_SHADOWS] = &WS2812FX::mode_dancing_shadows;
_mode[FX_MODE_WASHING_MACHINE] = &WS2812FX::mode_washing_machine;
+ _mode[FX_MODE_CANDY_CANE] = &WS2812FX::mode_candy_cane;
+ _mode[FX_MODE_BLENDS] = &WS2812FX::mode_blends;
+ _mode[FX_MODE_TV_SIMULATOR] = &WS2812FX::mode_tv_simulator;
+ _mode[FX_MODE_DYNAMIC_SMOOTH] = &WS2812FX::mode_dynamic_smooth;
_brightness = DEFAULT_BRIGHTNESS;
currentPalette = CRGBPalette16(CRGB::Black);
@@ -444,12 +580,11 @@ class WS2812FX {
ablMilliampsMax = 850;
currentMilliamps = 0;
timebase = 0;
- bus = new NeoPixelWrapper();
resetSegments();
}
void
- init(bool supportWhite, uint16_t countPixels, bool skipFirst),
+ finalizeInit(uint16_t countPixels, bool skipFirst),
service(void),
blur(uint8_t),
fill(uint32_t),
@@ -460,6 +595,7 @@ class WS2812FX {
setBrightness(uint8_t b),
setRange(uint16_t i, uint16_t i2, uint32_t col),
setShowCallback(show_callback cb),
+ setTransition(uint16_t t),
setTransitionMode(bool t),
calcGammaTable(float),
trigger(void),
@@ -468,17 +604,18 @@ class WS2812FX {
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),
- setRgbwPwm(void),
setColorOrder(uint8_t co),
setPixelSegment(uint8_t n);
bool
- reverseMode = false, //is the entire LED strip reversed?
+ isRgbw = false,
gammaCorrectBri = false,
gammaCorrectCol = true,
applyToAllSelected = true,
segmentsAreIdentical(Segment* a, Segment* b),
- setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p);
+ setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p),
+ // return true if the strip is being sent pixel updates
+ isUpdating(void);
uint8_t
mainSegment = 0,
@@ -486,6 +623,8 @@ class WS2812FX {
paletteFade = 0,
paletteBlend = 0,
milliampsPerLed = 55,
+// getStripType(uint8_t strip=0),
+// setStripType(uint8_t type, uint8_t strip=0),
getBrightness(void),
getMode(void),
getSpeed(void),
@@ -500,19 +639,27 @@ class WS2812FX {
get_random_wheel_index(uint8_t);
int8_t
+// setStripPin(uint8_t strip, int8_t pin),
+// getStripPin(uint8_t strip=0),
+// setStripPinClk(uint8_t strip, int8_t pin),
+// getStripPinClk(uint8_t strip=0),
tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec);
uint16_t
ablMilliampsMax,
currentMilliamps,
- triwave16(uint16_t);
+// setStripLen(uint8_t strip, uint16_t len),
+// getStripLen(uint8_t strip=0),
+ triwave16(uint16_t),
+ getFps();
uint32_t
now,
timebase,
color_wheel(uint8_t),
color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255),
- color_blend(uint32_t,uint32_t,uint8_t),
+ color_blend(uint32_t,uint32_t,uint16_t,bool b16=false),
+ currentColor(uint32_t colorNew, uint8_t tNr),
gamma32(uint32_t),
getLastShow(void),
getPixelColor(uint16_t),
@@ -567,13 +714,13 @@ class WS2812FX {
mode_colorful(void),
mode_traffic_light(void),
mode_running_color(void),
- mode_running_red_blue(void),
+ mode_aurora(void),
mode_running_random(void),
mode_larson_scanner(void),
mode_comet(void),
mode_fireworks(void),
mode_rain(void),
- mode_merry_christmas(void),
+ mode_tetrix(void),
mode_halloween(void),
mode_fire_flicker(void),
mode_gradient(void),
@@ -643,11 +790,13 @@ class WS2812FX {
mode_flow(void),
mode_chunchun(void),
mode_dancing_shadows(void),
- mode_washing_machine(void);
+ mode_washing_machine(void),
+ mode_candy_cane(void),
+ mode_blends(void),
+ mode_tv_simulator(void),
+ mode_dynamic_smooth(void);
private:
- NeoPixelWrapper *bus;
-
uint32_t crgb_to_col(CRGB fastled);
CRGB col_to_crgb(uint32_t);
CRGBPalette16 currentPalette;
@@ -656,14 +805,15 @@ class WS2812FX {
uint16_t _length, _lengthRaw, _virtualSegmentLength;
uint16_t _rand16seed;
uint8_t _brightness;
- static uint16_t _usedSegmentData;
+ uint16_t _usedSegmentData = 0;
+ uint16_t _transitionDur = 750;
+
+ uint16_t _cumulativeFps = 2;
void load_gradient_palette(uint8_t);
void handle_palette(void);
bool
- shouldStartBus = false,
- _useRgbw = false,
_skipFirstMode,
_triggered;
@@ -676,6 +826,7 @@ class WS2812FX {
blink(uint32_t, uint32_t, bool strobe, bool),
candle(bool),
color_wipe(bool, bool),
+ dynamic(bool),
scan(bool),
theater_chase(uint32_t, uint32_t, bool),
running_base(bool),
@@ -695,16 +846,19 @@ class WS2812FX {
CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat);
CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff);
- void blendPixelColor(uint16_t n, uint32_t color, uint8_t blend);
+ void
+ blendPixelColor(uint16_t n, uint32_t color, uint8_t blend),
+ startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot),
+ deserializeMap(void);
+
+ uint16_t* customMappingTable = nullptr;
+ uint16_t customMappingSize = 0;
uint32_t _lastPaletteChange = 0;
uint32_t _lastShow = 0;
-
- #ifdef WLED_USE_ANALOG_LEDS
- uint32_t _analogLastShow = 0;
- RgbwColor _analogLastColor = 0;
- uint8_t _analogLastBri = 0;
- #endif
+
+ uint32_t _colors_t[3];
+ uint8_t _bri_t;
uint8_t _segment_index = 0;
uint8_t _segment_index_palette_last = 99;
@@ -715,7 +869,12 @@ class WS2812FX {
segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 28 bytes per element
friend class Segment_runtime;
- uint16_t realPixelIndex(uint16_t i);
+ ColorTransition transitions[MAX_NUM_TRANSITIONS]; //12 bytes per element
+ friend class ColorTransition;
+
+ uint16_t
+ realPixelIndex(uint16_t i),
+ transitionProgress(uint8_t tNr);
};
//10 names per line
@@ -723,15 +882,15 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow",
"Scan","Scan Dual","Fade","Theater","Theater Rainbow","Running","Saw","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","Running 2","Red & Blue","Stream",
-"Scanner","Lighthouse","Fireworks","Rain","Merry Christmas","Fire Flicker","Gradient","Loading","Police","Police All",
+"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Aurora","Stream",
+"Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Police All",
"Two Dots","Two Areas","Circus","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet",
"Scanner Dual","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise",
"Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple",
"Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst",
"Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow",
"Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise",
-"Flow","Chunchun","Dancing Shadows","Washing Machine"
+"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth"
])=====";
@@ -741,7 +900,7 @@ const char JSON_palette_names[] PROGMEM = R"=====([
"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"
+"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2"
])=====";
#endif
diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp
index ddb49f6f04..58343c0890 100644
--- a/wled00/FX_fcn.cpp
+++ b/wled00/FX_fcn.cpp
@@ -27,44 +27,59 @@
#include "FX.h"
#include "palettes.h"
-//enable custom per-LED mapping. This can allow for better effects on matrices or special displays
-//#define WLED_CUSTOM_LED_MAPPING
+/*
+ Custom per-LED mapping has moved!
+
+ Create a file "ledmap.json" using the edit page.
-#ifdef WLED_CUSTOM_LED_MAPPING
-//this is just an example (30 LEDs). It will first set all even, then all uneven LEDs.
-const uint16_t customMappingTable[] = {
+ this is just an example (30 LEDs). It will first set all even, then all uneven LEDs.
+ {"map":[
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28,
- 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29};
+ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]}
-//another example. Switches direction every 5 LEDs.
-/*const uint16_t customMappingTable[] = {
+ another example. Switches direction every 5 LEDs.
+ {"map":[
0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14,
- 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25};*/
-
-const uint16_t customMappingSize = sizeof(customMappingTable)/sizeof(uint16_t); //30 in example
-#endif
+ 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]
+*/
-void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst)
+//do not call this method from system context (network callback)
+void WS2812FX::finalizeInit(uint16_t countPixels, bool skipFirst)
{
- if (supportWhite == _useRgbw && countPixels == _length && _skipFirstMode == skipFirst) return;
RESET_RUNTIME;
- _useRgbw = supportWhite;
_length = countPixels;
_skipFirstMode = skipFirst;
- uint8_t ty = 1;
- if (supportWhite) ty = 2;
_lengthRaw = _length;
if (_skipFirstMode) {
_lengthRaw += LED_SKIP_AMOUNT;
}
- bus->Begin((NeoPixelType)ty, _lengthRaw);
+ //if busses failed to load, add default (FS issue...)
+ if (busses.getNumBusses() == 0) {
+ uint8_t defPin[] = {LEDPIN};
+ BusConfig defCfg = BusConfig(TYPE_WS2812_RGB, defPin, 0, _lengthRaw, COL_ORDER_GRB);
+ busses.add(defCfg);
+ }
+ deserializeMap();
+
+ //make segment 0 cover the entire strip
_segments[0].start = 0;
_segments[0].stop = _length;
setBrightness(_brightness);
+
+ #ifdef ESP8266
+ for (uint8_t i = 0; i < busses.getNumBusses(); i++) {
+ Bus* b = busses.getBus(i);
+ if ((!IS_DIGITAL(b->getType()) || IS_2PIN(b->getType()))) continue;
+ uint8_t pins[5];
+ b->getPins(pins);
+ BusDigital* bd = static_cast(b);
+ if (pins[0] == 3) bd->reinit();
+ }
+ #endif
}
void WS2812FX::service() {
@@ -76,23 +91,36 @@ void WS2812FX::service() {
for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++)
{
_segment_index = i;
- if (SEGMENT.isActive())
+
+ // reset the segment runtime data if needed, called before isActive to ensure deleted
+ // segment's buffers are cleared
+ SEGENV.resetIfRequired();
+
+ if (!SEGMENT.isActive()) continue;
+
+ if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary
{
- if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary
- {
- if (SEGMENT.grouping == 0) SEGMENT.grouping = 1; //sanity check
- doShow = true;
- uint16_t delay = FRAMETIME;
-
- if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen
- _virtualSegmentLength = SEGMENT.virtualLength();
- handle_palette();
- delay = (this->*_mode[SEGMENT.mode])(); //effect function
- if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++;
+ if (SEGMENT.grouping == 0) SEGMENT.grouping = 1; //sanity check
+ doShow = true;
+ uint16_t delay = FRAMETIME;
+
+ if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen
+ _virtualSegmentLength = SEGMENT.virtualLength();
+ _bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2];
+ if (!IS_SEGMENT_ON) _bri_t = 0;
+ for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) {
+ if ((transitions[t].segment & 0x3F) != i) continue;
+ uint8_t slot = transitions[t].segment >> 6;
+ if (slot == 0) _bri_t = transitions[t].currentBri();
+ _colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]);
}
-
- SEGENV.next_time = nowUp + delay;
+ for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]);
+ handle_palette();
+ delay = (this->*_mode[SEGMENT.mode])(); //effect function
+ if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++;
}
+
+ SEGENV.next_time = nowUp + delay;
}
}
_virtualSegmentLength = 0;
@@ -111,8 +139,6 @@ void WS2812FX::setPixelColor(uint16_t n, uint32_t c) {
setPixelColor(n, r, g, b, w);
}
-#define REV(i) (_length - 1 - (i))
-
//used to map from segment index to physical pixel, taking into account grouping, offsets, reverse and mirroring
uint16_t WS2812FX::realPixelIndex(uint16_t i) {
int16_t iGroup = i * SEGMENT.groupLength();
@@ -128,8 +154,6 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) {
}
realIndex += SEGMENT.start;
- /* Reverse the whole string */
- if (reverseMode) realIndex = REV(realIndex);
return realIndex;
}
@@ -137,7 +161,7 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) {
void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
{
//auto calculate white channel value if enabled
- if (_useRgbw) {
+ if (isRgbw) {
if (rgbwMode == RGBW_MODE_AUTO_BRIGHTER || (w == 0 && (rgbwMode == RGBW_MODE_DUAL || rgbwMode == RGBW_MODE_LEGACY)))
{
//white value is set to lowest RGB channel
@@ -150,57 +174,43 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
}
}
- RgbwColor col;
- col.R = r; col.G = g; col.B = b; col.W = w;
-
uint16_t skip = _skipFirstMode ? LED_SKIP_AMOUNT : 0;
if (SEGLEN) {//from segment
- //color_blend(getpixel, col, SEGMENT.opacity); (pseudocode for future blending of segments)
- if (IS_SEGMENT_ON)
- {
- if (SEGMENT.opacity < 255) {
- col.R = scale8(col.R, SEGMENT.opacity);
- col.G = scale8(col.G, SEGMENT.opacity);
- col.B = scale8(col.B, SEGMENT.opacity);
- col.W = scale8(col.W, SEGMENT.opacity);
- }
- } else {
- col = BLACK;
+ //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
+ if (_bri_t < 255) {
+ r = scale8(r, _bri_t);
+ g = scale8(g, _bri_t);
+ b = scale8(b, _bri_t);
+ w = scale8(w, _bri_t);
}
+ uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
/* Set all the pixels in the group, ensuring _skipFirstMode is honored */
- bool reversed = reverseMode ^ IS_REVERSE;
+ bool reversed = IS_REVERSE;
uint16_t realIndex = realPixelIndex(i);
for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
int16_t indexSet = realIndex + (reversed ? -j : j);
- int16_t indexSetRev = indexSet;
- if (reverseMode) indexSetRev = REV(indexSet);
- #ifdef WLED_CUSTOM_LED_MAPPING
if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet];
- #endif
- if (indexSetRev >= SEGMENT.start && indexSetRev < SEGMENT.stop) {
- bus->SetPixelColor(indexSet + skip, col);
+ if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
+ busses.setPixelColor(indexSet + skip, col);
if (IS_MIRROR) { //set the corresponding mirrored pixel
- if (reverseMode) {
- bus->SetPixelColor(REV(SEGMENT.start) - indexSet + skip + REV(SEGMENT.stop) + 1, col);
- } else {
- bus->SetPixelColor(SEGMENT.stop - indexSet + skip + SEGMENT.start - 1, col);
- }
+ uint16_t indexMir = SEGMENT.stop - indexSet + SEGMENT.start - 1;
+ if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir];
+ busses.setPixelColor(indexMir + skip, col);
}
}
}
} else { //live data, etc.
- if (reverseMode) i = REV(i);
- #ifdef WLED_CUSTOM_LED_MAPPING
if (i < customMappingSize) i = customMappingTable[i];
- #endif
- bus->SetPixelColor(i + skip, col);
+
+ uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
+ busses.setPixelColor(i + skip, col);
}
if (skip && i == 0) {
for (uint16_t j = 0; j < skip; j++) {
- bus->SetPixelColor(j, RgbwColor(0, 0, 0, 0));
+ busses.setPixelColor(j, BLACK);
}
}
}
@@ -218,8 +228,11 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
void WS2812FX::show(void) {
- if (_callback) _callback();
-
+
+ // avoid race condition, caputre _callback value
+ show_callback callback = _callback;
+ if (callback) callback();
+
//power limit calculation
//each LED can draw up 195075 "power units" (approx. 53mA)
//one PU is the power it takes to have 1 channel 1 step brighter per brightness step
@@ -248,21 +261,22 @@ void WS2812FX::show(void) {
for (uint16_t i = 0; i < _length; i++) //sum up the usage of each LED
{
- RgbwColor c = bus->GetPixelColorRaw(i);
+ uint32_t c = busses.getPixelColor(i);
+ byte r = c >> 16, g = c >> 8, b = c, w = c >> 24;
if(useWackyWS2815PowerModel)
{
// ignore white component on WS2815 power calculation
- powerSum += (MAX(MAX(c.R,c.G),c.B)) * 3;
+ powerSum += (MAX(MAX(r,g),b)) * 3;
}
else
{
- powerSum += (c.R + c.G + c.B + c.W);
+ powerSum += (r + g + b + w);
}
}
- if (_useRgbw) //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
+ if (isRgbw) //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
{
powerSum *= 3;
powerSum = powerSum >> 2; //same as /= 4
@@ -277,24 +291,52 @@ void WS2812FX::show(void) {
uint16_t scaleI = scale * 255;
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
uint8_t newBri = scale8(_brightness, scaleB);
- bus->SetBrightness(newBri);
+ busses.setBrightness(newBri);
currentMilliamps = (powerSum0 * newBri) / puPerMilliamp;
} else
{
currentMilliamps = powerSum / puPerMilliamp;
- bus->SetBrightness(_brightness);
+ busses.setBrightness(_brightness);
}
currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate
currentMilliamps += _length; //add standby power back to estimate
} else {
currentMilliamps = 0;
- bus->SetBrightness(_brightness);
+ busses.setBrightness(_brightness);
}
- bus->Show();
- _lastShow = millis();
+ // some buses send asynchronously and this method will return before
+ // all of the data has been sent.
+ // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
+ busses.show();
+ unsigned long now = millis();
+ unsigned long diff = now - _lastShow;
+ uint16_t fpsCurr = 200;
+ if (diff > 0) fpsCurr = 1000 / diff;
+ _cumulativeFps = (3 * _cumulativeFps + fpsCurr) >> 2;
+ _lastShow = now;
+}
+
+/**
+ * Returns a true value if any of the strips are still being updated.
+ * On some hardware (ESP32), strip updates are done asynchronously.
+ */
+bool WS2812FX::isUpdating() {
+ return !busses.canAllShow();
}
+/**
+ * Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough.
+ * Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accurary varies
+ */
+uint16_t WS2812FX::getFps() {
+ if (millis() - _lastShow > 2000) return 0;
+ return _cumulativeFps +1;
+}
+
+/**
+ * Forces the next frame to be computed on all active segments.
+ */
void WS2812FX::trigger() {
_triggered = true;
}
@@ -321,11 +363,10 @@ uint8_t WS2812FX::getPaletteCount()
return 13 + GRADIENT_PALETTE_COUNT;
}
-//TODO transitions
+//TODO effect transitions
bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t p) {
- uint8_t mainSeg = getMainSegmentId();
Segment& seg = _segments[getMainSegmentId()];
uint8_t modePrev = seg.mode, speedPrev = seg.speed, intensityPrev = seg.intensity, palettePrev = seg.palette;
@@ -368,36 +409,29 @@ void WS2812FX::setColor(uint8_t slot, uint32_t c) {
if (applyToAllSelected) {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
- if (_segments[i].isSelected()) _segments[i].colors[slot] = c;
+ if (_segments[i].isSelected()) {
+ _segments[i].setColor(slot, c, i);
+ applied = true;
+ }
}
}
if (!applyToAllSelected || !applied) {
- _segments[getMainSegmentId()].colors[slot] = c;
+ uint8_t mainseg = getMainSegmentId();
+ _segments[mainseg].setColor(slot, c, mainseg);
}
}
void WS2812FX::setBrightness(uint8_t b) {
+ if (gammaCorrectBri) b = gamma8(b);
if (_brightness == b) return;
- _brightness = (gammaCorrectBri) ? gamma8(b) : b;
+ _brightness = b;
_segment_index = 0;
- if (b == 0) { //unfreeze all segments on power off
+ if (_brightness == 0) { //unfreeze all segments on power off
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
_segments[i].setOption(SEG_OPTION_FREEZE, false);
}
- #if LEDPIN == LED_BUILTIN
- if (!shouldStartBus)
- shouldStartBus = true;
- #endif
- } else {
- #if LEDPIN == LED_BUILTIN
- if (shouldStartBus) {
- shouldStartBus = false;
- const uint8_t ty = _useRgbw ? 2 : 1;
- bus->Begin((NeoPixelType)ty, _lengthRaw);
- }
- #endif
}
if (SEGENV.next_time > millis() + 22 && millis() - _lastShow > MIN_SHOW_DELAY) show();//apply brightness change immediately if no refresh soon
}
@@ -449,15 +483,13 @@ uint32_t WS2812FX::getPixelColor(uint16_t i)
{
i = realPixelIndex(i);
- #ifdef WLED_CUSTOM_LED_MAPPING
if (i < customMappingSize) i = customMappingTable[i];
- #endif
if (_skipFirstMode) i += LED_SKIP_AMOUNT;
if (i >= _lengthRaw) return 0;
- return bus->GetPixelColorRgbw(i);
+ return busses.getPixelColor(i);
}
WS2812FX::Segment& WS2812FX::getSegment(uint8_t id) {
@@ -477,12 +509,13 @@ uint32_t WS2812FX::getLastShow(void) {
return _lastShow;
}
+//TODO these need to be on a per-strip basis
uint8_t WS2812FX::getColorOrder(void) {
- return bus->GetColorOrder();
+ return COL_ORDER_GRB;
}
void WS2812FX::setColorOrder(uint8_t co) {
- bus->SetColorOrder(co);
+ //bus->SetColorOrder(co);
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) {
@@ -576,6 +609,11 @@ void WS2812FX::setShowCallback(show_callback cb)
_callback = cb;
}
+void WS2812FX::setTransition(uint16_t t)
+{
+ _transitionDur = t;
+}
+
void WS2812FX::setTransitionMode(bool t)
{
unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
@@ -591,24 +629,26 @@ void WS2812FX::setTransitionMode(bool t)
/*
* color blend function
*/
-uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
+uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) {
if(blend == 0) return color1;
- if(blend == 255) return color2;
+ uint16_t blendmax = b16 ? 0xFFFF : 0xFF;
+ if(blend == blendmax) return color2;
+ uint8_t shift = b16 ? 16 : 8;
- uint32_t w1 = (color1 >> 24) & 0xff;
- uint32_t r1 = (color1 >> 16) & 0xff;
- uint32_t g1 = (color1 >> 8) & 0xff;
- uint32_t b1 = color1 & 0xff;
+ uint32_t w1 = (color1 >> 24) & 0xFF;
+ uint32_t r1 = (color1 >> 16) & 0xFF;
+ uint32_t g1 = (color1 >> 8) & 0xFF;
+ uint32_t b1 = color1 & 0xFF;
- uint32_t w2 = (color2 >> 24) & 0xff;
- uint32_t r2 = (color2 >> 16) & 0xff;
- uint32_t g2 = (color2 >> 8) & 0xff;
- uint32_t b2 = color2 & 0xff;
+ uint32_t w2 = (color2 >> 24) & 0xFF;
+ uint32_t r2 = (color2 >> 16) & 0xFF;
+ uint32_t g2 = (color2 >> 8) & 0xFF;
+ uint32_t b2 = color2 & 0xFF;
- uint32_t w3 = ((w2 * blend) + (w1 * (255 - blend))) >> 8;
- uint32_t r3 = ((r2 * blend) + (r1 * (255 - blend))) >> 8;
- uint32_t g3 = ((g2 * blend) + (g1 * (255 - blend))) >> 8;
- uint32_t b3 = ((b2 * blend) + (b1 * (255 - blend))) >> 8;
+ uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift;
+ uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift;
+ uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift;
+ uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift;
return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3));
}
@@ -890,13 +930,24 @@ void WS2812FX::handle_palette(void)
*/
uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri)
{
- if (SEGMENT.palette == 0 && mcol < 3) return SEGCOLOR(mcol); //WS2812FX default
+ if (SEGMENT.palette == 0 && mcol < 3) {
+ uint32_t color = SEGCOLOR(mcol);
+ if (pbri != 255) {
+ CRGB crgb_color = col_to_crgb(color);
+ crgb_color.nscale8_video(pbri);
+ return crgb_to_col(crgb_color);
+ } else {
+ return color;
+ }
+ }
+
uint8_t paletteIndex = i;
if (mapping) paletteIndex = (i*255)/(SEGLEN -1);
if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGB fastled_col;
fastled_col = ColorFromPalette( currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND);
- return fastled_col.r*65536 + fastled_col.g*256 + fastled_col.b;
+
+ return crgb_to_col(fastled_col);
}
//@returns `true` if color, mode, speed, intensity and palette match
@@ -916,44 +967,31 @@ bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b)
return true;
}
-#ifdef WLED_USE_ANALOG_LEDS
-void WS2812FX::setRgbwPwm(void) {
- uint32_t nowUp = millis(); // Be aware, millis() rolls over every 49 days
- if (nowUp - _analogLastShow < MIN_SHOW_DELAY) return;
- _analogLastShow = nowUp;
+//load custom mapping table from JSON file
+void WS2812FX::deserializeMap(void) {
+ if (!WLED_FS.exists("/ledmap.json")) return;
+ DynamicJsonDocument doc(JSON_BUFFER_SIZE); // full sized buffer for larger maps
- RgbwColor c;
- uint32_t col = bus->GetPixelColorRgbw(0);
- c.R = col >> 16; c.G = col >> 8; c.B = col; c.W = col >> 24;
+ DEBUG_PRINTLN(F("Reading LED map from /ledmap.json..."));
- byte b = getBrightness();
- if (color == _analogLastColor && b == _analogLastBri) return;
-
- // check color values for Warm / Cold white mix (for RGBW) // EsplanexaDevice.cpp
- #ifdef WLED_USE_5CH_LEDS
- if (c.R == 255 && c.G == 255 && c.B == 255 && c.W == 255) {
- bus->SetRgbwPwm(0, 0, 0, 0, c.W * b / 255);
- } else if (c.R == 127 && c.G == 127 && c.B == 127 && c.W == 255) {
- bus->SetRgbwPwm(0, 0, 0, c.W * b / 512, c.W * b / 255);
- } else if (c.R == 0 && c.G == 0 && c.B == 0 && c.W == 255) {
- bus->SetRgbwPwm(0, 0, 0, c.W * b / 255, 0);
- } else if (c.R == 130 && c.G == 90 && c.B == 0 && c.W == 255) {
- bus->SetRgbwPwm(0, 0, 0, c.W * b / 255, c.W * b / 512);
- } else if (c.R == 255 && c.G == 153 && c.B == 0 && c.W == 255) {
- bus->SetRgbwPwm(0, 0, 0, c.W * b / 255, 0);
- } else { // not only white colors
- bus->SetRgbwPwm(c.R * b / 255, c.G * b / 255, c.B * b / 255, c.W * b / 255);
+ if (!readObjectFromFile("/ledmap.json", nullptr, &doc)) return; //if file does not exist just exit
+
+ if (customMappingTable != nullptr) {
+ delete[] customMappingTable;
+ customMappingTable = nullptr;
+ customMappingSize = 0;
+ }
+
+ JsonArray map = doc[F("map")];
+ if (!map.isNull() && map.size()) { // not an empty map
+ customMappingSize = map.size();
+ customMappingTable = new uint16_t[customMappingSize];
+ for (uint16_t i=0; iSetRgbwPwm(c.R * b / 255, c.G * b / 255, c.B * b / 255, c.W * b / 255);
- #endif
- _analogLastColor = color;
- _analogLastBri = b;
+ }
}
-#else
-void WS2812FX::setRgbwPwm() {}
-#endif
//gamma 2.8 lookup table used for color correction
byte gammaT[] = {
@@ -1004,4 +1042,4 @@ uint32_t WS2812FX::gamma32(uint32_t color)
return ((w << 24) | (r << 16) | (g << 8) | (b));
}
-uint16_t WS2812FX::_usedSegmentData = 0;
+WS2812FX* WS2812FX::instance = nullptr;
\ No newline at end of file
diff --git a/wled00/NodeStruct.h b/wled00/NodeStruct.h
new file mode 100644
index 0000000000..a0fd2f6340
--- /dev/null
+++ b/wled00/NodeStruct.h
@@ -0,0 +1,34 @@
+#ifndef WLED_NODESTRUCT_H
+#define WLED_NODESTRUCT_H
+
+/*********************************************************************************************\
+* NodeStruct from the ESP Easy project (https://github.com/letscontrolit/ESPEasy)
+\*********************************************************************************************/
+
+#include