Merge remote-tracking branch 'upstream/0_15' into fix_usermod_tetisai

This commit is contained in:
muebau 2024-05-23 16:15:35 +02:00
commit 78089107b7
80 changed files with 4818 additions and 1407 deletions

20
.github/stale.yml vendored
View File

@ -1,20 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 120
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- keep
- enhancement
- confirmed
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Hey! This issue has been open for quite some time without any new comments now.
It will be closed automatically in a week if no further activity occurs.
Thank you for using WLED!
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

30
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '0 12 * * *'
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-stale: 120
days-before-close: 7
stale-issue-label: 'stale'
stale-pr-label: 'stale'
exempt-issue-labels: 'pinned,keep,enhancement,confirmed'
exempt-pr-labels: 'pinned,keep,enhancement,confirmed'
exempt-all-milestones: true
operations-per-run: 1000
stale-issue-message: >
Hey! This issue has been open for quite some time without any new comments now.
It will be closed automatically in a week if no further activity occurs.
Thank you for using WLED! ✨
stale-pr-message: >
Hey! This pull request has been open for quite some time without any new comments now.
It will be closed automatically in a week if no further activity occurs.
Thank you for contributing to WLED! ❤️

2
.gitignore vendored
View File

@ -21,4 +21,4 @@ wled-update.sh
/wled00/my_config.h
/wled00/Release
/wled00/wled00.ino.cpp
/wled00/html_*.h
/wled00/html_*.h

View File

@ -1,5 +1,41 @@
## WLED changelog
#### Build 2405180
- Official 0.15.0-b3 release
- Merge 0.14.3 fixes
- Added Pinwheel Expand 1D->2D effect mapping mode (#3961 by @Brandon502)
- Add changeable i2c address to BME280 usermod (#3966 by @LordMike)
- Effect: Firenoise - add palette selection
- Experimental parallel I2S support for ESP32 (compile time option)
- increased outputs to 17
- increased max possible color order overrides
- use WLED_USE_PARALLEL_I2S during compile
WARNING: Do not set up more than 256 LEDs per output when using parallel I2S with NeoPixelBus less than 2.9.0
- Update Usermod: Battery (#3964 by @adamsthws)
- Update Usermod: BME280 (#3965 by @LordMike)
- TM1914 chip support (#3913)
- Ignore brightness in Peek
- Antialiased line & circle drawing functions
- Enabled some audioreactive effects for single pixel strips/segments (#3942 by @gaaat98)
- Usermod Battery: Added Support for different battery types, Optimized file structure (#3003 by @itCarl)
- Skip playlist entry API (#3946 by @freakintoddles2)
- various optimisations and bugfixes (#3987, #3978)
#### Build 2405030
- Using brightness in analog clock overlay (#3944 by @paspiz85)
- Add Webpage shortcuts (#3945 by @w00000dy)
- ArtNet Poll reply (#3892 by @askask)
- Improved brightness change via long button presses (#3933 by @gaaat98)
- Relay open drain output (#3920 by @Suxsem)
- NEW JSON API: release info (update page, `info.release`)
- update esp32 platform to arduino-esp32 v2.0.9 (#3902)
- various optimisations and bugfixes (#3952, #3922, #3878, #3926, #3919, #3904 @DedeHai)
#### Build 2404120
- v0.15.0-b3
- fix for #3896 & WS2815 current saving
- conditional compile for AA setPixelColor()
#### Build 2404100
- Internals: #3859, #3862, #3873, #3875
- Prefer I2S1 over RMT on ESP32
@ -17,6 +53,11 @@
- Fix for #3889
- BREAKING: Effect: modified KITT (Scanner) (#3763)
#### Build 2404040
- WLED 0.14.3 release
- Fix for transition 0 (#3854, #3832, #3720)
- Fix for #3855 via #3873 (by @willmmiles)
#### Build 2403280
- Individual color channel control for JSON API (fixes #3860)
- "col":[int|string|object|array, int|string|object|array, int|string|object|array]

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "wled",
"version": "0.15.0-b2",
"version": "0.15.0-b3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wled",
"version": "0.15.0-b2",
"version": "0.15.0-b3",
"license": "ISC",
"dependencies": {
"clean-css": "^5.3.3",

View File

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

View File

@ -1,9 +1,24 @@
# Little convenience script to get an object dump
# You may add "-S" to the objdump commandline (i.e. replace "-D -C " with "-d -S -C ")
# to get source code intermixed with disassembly (SLOW !)
Import('env')
def obj_dump_after_elf(source, target, env):
platform = env.PioPlatform()
board = env.BoardConfig()
mcu = board.get("build.mcu", "esp32")
print("Create firmware.asm")
env.Execute("xtensa-lx106-elf-objdump "+ "-D " + str(target[0]) + " > "+ "${PROGNAME}.asm")
if mcu == "esp8266":
env.Execute("xtensa-lx106-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
if mcu == "esp32":
env.Execute("xtensa-esp32-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
if mcu == "esp32s2":
env.Execute("xtensa-esp32s2-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
if mcu == "esp32s3":
env.Execute("xtensa-esp32s3-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
if mcu == "esp32c3":
env.Execute("riscv32-esp-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", [obj_dump_after_elf])

View File

@ -36,6 +36,8 @@ def create_release(source):
def bin_rename_copy(source, target, env):
_create_dirs()
variant = env["PIOENV"]
builddir = os.path.join(env["PROJECT_BUILD_DIR"], variant)
source_map = os.path.join(builddir, env["PROGNAME"] + ".map")
# create string with location and file names based on variant
map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant)
@ -44,7 +46,11 @@ def bin_rename_copy(source, target, env):
# copy firmware.map to map/<variant>.map
if os.path.isfile("firmware.map"):
shutil.move("firmware.map", map_file)
print("Found linker mapfile firmware.map")
shutil.copy("firmware.map", map_file)
if os.path.isfile(source_map):
print(f"Found linker mapfile {source_map}")
shutil.copy(source_map, map_file)
def bin_gzip(source, target):
# only create gzip for esp8266

View File

@ -10,7 +10,7 @@
# ------------------------------------------------------------------------------
# CI/release binaries
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32s3_4M_PSRAM_qspi, esp32_wrover
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover
src_dir = ./wled00
data_dir = ./wled00/data
@ -86,7 +86,6 @@ debug_flags = -D DEBUG=1 -D WLED_DEBUG
# This reduces the OTA size with ~45KB, so it's especially useful on low memory boards (512k/1m).
# ------------------------------------------------------------------------------
build_flags =
-Wno-attributes
-DMQTT_MAX_PACKET_SIZE=1024
-DSECURE_CLIENT=SECURE_CLIENT_BEARSSL
-DBEARSSL_SSL_BASIC
@ -104,10 +103,6 @@ build_flags =
build_unflags =
build_flags_esp8266 = ${common.build_flags} ${esp8266.build_flags}
build_flags_esp32 = ${common.build_flags} ${esp32.build_flags}
build_flags_esp32_V4= ${common.build_flags} ${esp32_idf_V4.build_flags}
ldscript_1m128k = eagle.flash.1m128.ld
ldscript_2m512k = eagle.flash.2m512.ld
ldscript_2m1m = eagle.flash.2m1m.ld
@ -120,6 +115,7 @@ extra_scripts =
post:pio-scripts/strip-floats.py
pre:pio-scripts/user_config_copy.py
pre:pio-scripts/build_ui.py
; post:pio-scripts/obj-dump.py ;; convenience script to create a disassembly dump of the firmware (hardcore debugging)
# ------------------------------------------------------------------------------
# COMMON SETTINGS:
@ -174,7 +170,7 @@ lib_deps =
# SHT85
;robtillaart/SHT85@~0.3.3
# Audioreactive usermod
;kosme/arduinoFFT @ 2.0.0
;kosme/arduinoFFT @ 2.0.1
extra_scripts = ${scripts_defaults.extra_scripts}
@ -218,14 +214,19 @@ build_flags = -g
#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
-D LOROL_LITTLEFS
; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv
big_partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, 256KB filesystem, coredump support
large_partitions = tools/WLED_ESP32_8MB.csv
extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv
lib_deps =
https://github.com/lorol/LITTLEFS.git
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
${env.lib_deps}
# additional build flags for audioreactive
AR_build_flags = -D USERMOD_AUDIOREACTIVE
AR_lib_deps = kosme/arduinoFFT @ 2.0.0
AR_lib_deps = kosme/arduinoFFT @ 2.0.1
[esp32_idf_V4]
;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
@ -233,24 +234,21 @@ AR_lib_deps = kosme/arduinoFFT @ 2.0.0
;;
;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly.
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
platform = espressif32@5.3.0
platform_packages =
platform = espressif32@ ~6.3.2
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
build_flags = -g
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
-DARDUINO_ARCH_ESP32 -DESP32
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
${env.lib_deps}
[esp32s2]
;; generic definitions for all ESP32-S2 boards
platform = espressif32@5.3.0
platform_packages =
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
platform = espressif32@ ~6.3.2
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
build_flags = -g
-DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32S2
@ -267,8 +265,8 @@ lib_deps =
[esp32c3]
;; generic definitions for all ESP32-C3 boards
platform = espressif32@5.3.0
platform_packages =
platform = espressif32@ ~6.3.2
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
build_flags = -g
-DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32C3
@ -284,8 +282,8 @@ lib_deps =
[esp32s3]
;; generic definitions for all ESP32-S3 boards
platform = espressif32@5.3.0
platform_packages =
platform = espressif32@ ~6.3.2
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
build_flags = -g
-DESP32
-DARDUINO_ARCH_ESP32
@ -296,7 +294,6 @@ build_flags = -g
-DCO
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
${env.lib_deps}
@ -312,14 +309,14 @@ 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_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D
lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder
[env:nodemcuv2_160]
extends = env:nodemcuv2
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D
[env:esp8266_2m]
board = esp_wroom_02
@ -327,13 +324,13 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02
lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m_160]
extends = env:esp8266_2m
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02_160
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160
[env:esp01_1m_full]
board = esp01_1m
@ -341,32 +338,45 @@ 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_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA
; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full_160]
extends = env:esp01_1m_full
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA
; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
[env:esp32dev]
board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
[env:esp32dev_8M]
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_8M #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags}
lib_deps = ${esp32_idf_V4.lib_deps}
${esp32.AR_lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.large_partitions}
; board_build.f_flash = 80000000L
[env:esp32dev_audioreactive]
board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags}
lib_deps = ${esp32.lib_deps}
${esp32.AR_lib_deps}
@ -381,22 +391,26 @@ platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
upload_speed = 921600
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
-D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
lib_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}
[env:esp32_wrover]
platform = ${esp32.platform}
extends = esp32_idf_V4
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
board = ttgo-t7-v14-mini32
board_build.f_flash = 80000000L
board_build.flash_mode = qio
board_build.partitions = tools/WLED_ESP32-wrover_4MB.csv
board_build.partitions = ${esp32.default_partitions}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_WROVER
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER
-DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html
-D LEDPIN=25
lib_deps = ${esp32.lib_deps}
; ${esp32.AR_build_flags}
lib_deps = ${esp32_idf_V4.lib_deps}
; ${esp32.AR_lib_deps}
[env:esp32c3dev]
extends = esp32c3
@ -404,7 +418,7 @@ platform = ${esp32c3.platform}
platform_packages = ${esp32c3.platform_packages}
framework = arduino
board = esp32-c3-devkitm-1
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
board_build.partitions = ${esp32.default_partitions}
build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3
-D WLED_WATCHDOG_TIMEOUT=0
-DLOLIN_WIFI_FIX ; seems to work much better with this
@ -414,35 +428,15 @@ upload_speed = 460800
build_unflags = ${common.build_unflags}
lib_deps = ${esp32c3.lib_deps}
[env:esp32s3dev_8MB]
;; ESP32-S3-DevKitC-1 development board, with 8MB FLASH, no PSRAM (flash_mode: qio)
board = esp32-s3-devkitc-1
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600 ; or 460800
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
;-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
${esp32.AR_build_flags}
lib_deps = ${esp32s3.lib_deps}
${esp32.AR_lib_deps}
board_build.partitions = tools/WLED_ESP32_8MB.csv
board_build.f_flash = 80000000L
board_build.flash_mode = qio
; board_build.flash_mode = dio ;; try this if you have problems at startup
monitor_filters = esp32_exception_decoder
[env:esp32s3dev_8MB_PSRAM_opi]
;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
[env:esp32s3dev_16MB_opi]
;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_PSRAM_opi
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_16MB_opi
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
@ -450,27 +444,47 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
${esp32.AR_build_flags}
lib_deps = ${esp32s3.lib_deps}
${esp32.AR_lib_deps}
board_build.partitions = tools/WLED_ESP32_8MB.csv
board_build.partitions = ${esp32.extreme_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:esp32s3_4M_PSRAM_qspi]
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
[env:esp32s3dev_8MB_opi]
;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_PSRAM_qspi
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_opi
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
${esp32.AR_build_flags}
lib_deps = ${esp32s3.lib_deps}
${esp32.AR_lib_deps}
board_build.partitions = ${esp32.large_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:esp32s3_4M_qspi]
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
-D WLED_WATCHDOG_TIMEOUT=0
${esp32.AR_build_flags}
lib_deps = ${esp32s3.lib_deps}
${esp32.AR_lib_deps}
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
@ -479,9 +493,9 @@ monitor_filters = esp32_exception_decoder
platform = ${esp32s2.platform}
platform_packages = ${esp32s2.platform_packages}
board = lolin_s2_mini
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
;board_build.flash_mode = qio
;board_build.f_flash = 80000000L
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = qio
board_build.f_flash = 80000000L
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2
-DARDUINO_USB_CDC_ON_BOOT=1

View File

@ -28,12 +28,15 @@ lib_deps = ${esp8266.lib_deps}
; robtillaart/SHT85@~0.3.3
; gmag11/QuickESPNow ;@ 0.6.2
; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library
; https://github.com/kosme/arduinoFFT#develop @ 1.9.2+sha.419d7b0 ;; used for USERMOD_AUDIOREACTIVE - using "known working" hash
; build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
; https://github.com/kosme/arduinoFFT#develop @ 2.0.1 ;; used for USERMOD_AUDIOREACTIVE
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags}
;
; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above.
;
; Set a release name that may be used to distinguish required binary for flashing
; -D WLED_RELEASE_NAME=ESP32_MULTI_USREMODS
;
; disable specific features
; -D WLED_DISABLE_OTA
; -D WLED_DISABLE_ALEXA
@ -56,6 +59,7 @@ build_flags = ${common.build_flags_esp8266}
; -D IRPIN=4
; -D RLYPIN=12
; -D RLYMDE=1
; -D RLYODRAIN=0
; -D LED_BUILTIN=2 # GPIO of built-in LED
;
; Limit max buses
@ -90,6 +94,12 @@ build_flags = ${common.build_flags_esp8266}
; -D USERMOD_AUTO_SAVE
; -D AUTOSAVE_AFTER_SEC=90
;
; Use AHT10/AHT15/AHT20 usermod
; -D USERMOD_AHT10
;
; Use INA226 usermod
; -D USERMOD_INA226
;
; Use 4 Line Display usermod with SPI display
; -D USERMOD_FOUR_LINE_DISPLAY
; -D USE_ALT_DISPlAY # mandatory
@ -179,7 +189,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.build_flags}
lib_deps = ${esp8266.lib_deps}
[env:d1_mini]
@ -189,7 +199,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.build_flags}
lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder
@ -199,7 +209,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.build_flags}
lib_deps = ${esp8266.lib_deps}
[env:h803wf]
@ -208,7 +218,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=1 -D WLED_DISABLE_INFRARED
build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
[env:esp32dev_qio80]
@ -216,7 +226,7 @@ board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_qio80 #-D WLED_DISABLE_BROWNOUT_DET
build_flags = ${common.build_flags} ${esp32.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
@ -231,7 +241,7 @@ board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_V4_qio80 #-D WLED_DISABLE_BROWNOUT_DET
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32_idf_V4.default_partitions}
@ -240,14 +250,14 @@ board_build.flash_mode = dio
[env:esp32s2_saola]
board = esp32-s2-saola-1
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip
platform_packages =
platform = ${esp32s2.platform}
platform_packages = ${esp32s2.platform_packages}
framework = arduino
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
board_build.flash_mode = qio
upload_speed = 460800
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=S2_saola
build_flags = ${common.build_flags} ${esp32s2.build_flags}
;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work
-DARDUINO_USB_CDC_ON_BOOT=1
lib_deps = ${esp32s2.lib_deps}
@ -265,7 +275,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
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA
lib_deps = ${esp8266.lib_deps}
[env:esp8285_H801]
@ -274,7 +284,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
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA
lib_deps = ${esp8266.lib_deps}
[env:d1_mini_5CH_Shojo_PCB]
@ -283,7 +293,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_SHOJO_PCB
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_USE_SHOJO_PCB
lib_deps = ${esp8266.lib_deps}
[env:d1_mini_debug]
@ -293,7 +303,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} ${common.debug_flags}
build_flags = ${common.build_flags} ${esp8266.build_flags} ${common.debug_flags}
lib_deps = ${esp8266.lib_deps}
[env:d1_mini_ota]
@ -305,7 +315,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.build_flags}
lib_deps = ${esp8266.lib_deps}
[env:anavi_miracle_controller]
@ -314,7 +324,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=12 -D IRPIN=-1 -D RLYPIN=2
build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2
lib_deps = ${esp8266.lib_deps}
[env:esp32c3dev_2MB]
@ -324,7 +334,7 @@ extends = esp32c3
platform = ${esp32c3.platform}
platform_packages = ${esp32c3.platform_packages}
board = esp32-c3-devkitm-1
build_flags = ${common.build_flags} ${esp32c3.build_flags} #-D WLED_RELEASE_NAME=ESP32-C3
build_flags = ${common.build_flags} ${esp32c3.build_flags}
-D WLED_WATCHDOG_TIMEOUT=0
-D WLED_DISABLE_OTA
; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB
@ -341,7 +351,7 @@ platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
upload_speed = 460800
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32}
build_flags = ${common.build_flags} ${esp32.build_flags}
-D LEDPIN=16
-D RLYPIN=19
-D BTNPIN=17
@ -361,7 +371,7 @@ board_build.partitions = ${esp32.default_partitions}
[env:m5atom]
board = esp32dev
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39
build_flags = ${common.build_flags} ${esp32.build_flags} -D LEDPIN=27 -D BTNPIN=39
lib_deps = ${esp32.lib_deps}
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
@ -371,14 +381,14 @@ board_build.partitions = ${esp32.default_partitions}
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=1
build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=1
lib_deps = ${esp8266.lib_deps}
[env:sp511e]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
lib_deps = ${esp8266.lib_deps}
[env:Athom_RGBCW] ;7w and 5w(GU10) bulbs
@ -387,7 +397,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5
-D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0
lib_deps = ${esp8266.lib_deps}
@ -397,7 +407,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13
-D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT
lib_deps = ${esp8266.lib_deps}
@ -407,7 +417,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=-1 -D LEDPIN=1 -D WLED_DISABLE_INFRARED
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 -D LEDPIN=1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
[env:Athom_4Pin_Controller] ; With clock and data interface
@ -416,7 +426,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=12 -D LEDPIN=1 -D WLED_DISABLE_INFRARED
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=12 -D LEDPIN=1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
[env:Athom_5Pin_Controller] ;Analog light strip controller
@ -425,7 +435,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=-1 DATA_PINS=4,12,14,13 -D WLED_DISABLE_INFRARED
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 DATA_PINS=4,12,14,13 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
[env:MY9291]
@ -434,7 +444,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_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D USERMOD_MY9291
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA -D USERMOD_MY9291
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
@ -448,7 +458,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
build_flags = ${common.build_flags} ${esp8266.build_flags}
lib_deps = ${esp8266.lib_deps}
[env:codm-controller-0_6-rev2]
@ -457,7 +467,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.build_flags}
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
@ -468,7 +478,7 @@ board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
upload_speed = 921600
build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED
-D USERMOD_RTC
-D USERMOD_ELEKSTUBE_IPS
-D LEDPIN=12

View File

@ -28,7 +28,7 @@ h11==0.14.0
# via
# uvicorn
# wsproto
idna==3.4
idna==3.7
# via
# anyio
# requests
@ -52,6 +52,8 @@ starlette==0.23.1
# via platformio
tabulate==0.9.0
# via platformio
typing-extensions==4.11.0
# via starlette
urllib3==1.26.18
# via requests
uvicorn==0.20.0

View File

@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1D0000,
app1, app, ota_1, 0x1E0000,0x1D0000,
spiffs, data, spiffs, 0x3B0000,0x40000,
coredump, data, coredump,,64K
1 # Name, Type, SubType, Offset, Size, Flags
2 nvs, data, nvs, 0x9000, 0x5000,
3 otadata, data, ota, 0xe000, 0x2000,
4 app0, app, ota_0, 0x10000, 0x1D0000,
5 app1, app, ota_1, 0x1E0000,0x1D0000,
6 spiffs, data, spiffs, 0x3B0000,0x40000,
7 coredump, data, coredump,,64K

View File

@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1A0000,
app1, app, ota_1, 0x1B0000,0x1A0000,
spiffs, data, spiffs, 0x350000,0xB0000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x1A0000
5 app1 app ota_1 0x1B0000 0x1A0000
6 spiffs data spiffs 0x350000 0xB0000

View File

@ -0,0 +1,36 @@
# Usermod AHT10
This Usermod is designed to read a `AHT10`, `AHT15` or `AHT20` sensor and output the following:
- Temperature
- Humidity
Configuration is performed via the Usermod menu. The following settings can be configured in the Usermod Menu:
- I2CAddress: The i2c address in decimal. Set it to either 56 (0x38, the default) or 57 (0x39).
- SensorType, one of:
- 0 - AHT10
- 1 - AHT15
- 2 - AHT20
- CheckInterval: Number of seconds between readings
- Decimals: Number of decimals to put in the output
Dependencies, These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
- Libraries
- `enjoyneering/AHT10@~1.1.0` (by [enjoyneering](https://registry.platformio.org/libraries/enjoyneering/AHT10))
- `Wire`
## Author
[@LordMike](https://github.com/LordMike)
# Compiling
To enable, compile with `USERMOD_AHT10` defined (e.g. in `platformio_override.ini`)
```ini
[env:aht10_example]
extends = env:esp32dev
build_flags =
${common.build_flags} ${esp32.build_flags}
-D USERMOD_AHT10
; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal
lib_deps =
${esp32.lib_deps}
enjoyneering/AHT10@~1.1.0
```

View File

@ -0,0 +1,9 @@
[env:aht10_example]
extends = env:esp32dev
build_flags =
${common.build_flags} ${esp32.build_flags}
-D USERMOD_AHT10
; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal
lib_deps =
${esp32.lib_deps}
enjoyneering/AHT10@~1.1.0

View File

@ -0,0 +1,327 @@
#pragma once
#include "wled.h"
#include <AHT10.h>
#define AHT10_SUCCESS 1
class UsermodAHT10 : public Usermod
{
private:
static const char _name[];
unsigned long _lastLoopCheck = 0;
bool _settingEnabled : 1; // Enable the usermod
bool _mqttPublish : 1; // Publish mqtt values
bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change
bool _mqttHomeAssistant : 1; // Enable Home Assistant docs
bool _initDone : 1; // Initialization is done
// Settings. Some of these are stored in a different format than they're user settings - so we don't have to convert at runtime
uint8_t _i2cAddress = AHT10_ADDRESS_0X38;
ASAIR_I2C_SENSOR _ahtType = AHT10_SENSOR;
uint16_t _checkInterval = 60000; // milliseconds, user settings is in seconds
float _decimalFactor = 100; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..)
uint8_t _lastStatus = 0;
float _lastHumidity = 0;
float _lastTemperature = 0;
#ifndef WLED_MQTT_DISABLE
float _lastHumiditySent = 0;
float _lastTemperatureSent = 0;
#endif
AHT10 *_aht = nullptr;
float truncateDecimals(float val)
{
return roundf(val * _decimalFactor) / _decimalFactor;
}
void initializeAht()
{
if (_aht != nullptr)
{
delete _aht;
}
_aht = new AHT10(_i2cAddress, _ahtType);
_lastStatus = 0;
_lastHumidity = 0;
_lastTemperature = 0;
}
~UsermodAHT10()
{
delete _aht;
_aht = nullptr;
}
#ifndef WLED_DISABLE_MQTT
void mqttInitialize()
{
// This is a generic "setup mqtt" function, So we must abort if we're not to do mqtt
if (!WLED_MQTT_CONNECTED || !_mqttPublish || !_mqttHomeAssistant)
return;
char topic[128];
snprintf_P(topic, 127, "%s/temperature", mqttDeviceTopic);
mqttCreateHassSensor(F("Temperature"), topic, F("temperature"), F("°C"));
snprintf_P(topic, 127, "%s/humidity", mqttDeviceTopic);
mqttCreateHassSensor(F("Humidity"), topic, F("humidity"), F("%"));
}
void mqttPublishIfChanged(const __FlashStringHelper *topic, float &lastState, float state, float minChange)
{
// Check if MQTT Connected, otherwise it will crash the 8266
// Only report if the change is larger than the required diff
if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || fabsf(lastState - state) > minChange))
{
char subuf[128];
snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic);
mqtt->publish(subuf, 0, false, String(state).c_str());
lastState = state;
}
}
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
void mqttCreateHassSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
{
String t = String(F("homeassistant/sensor/")) + mqttClientID + "/" + name + F("/config");
StaticJsonDocument<600> doc;
doc[F("name")] = name;
doc[F("state_topic")] = topic;
doc[F("unique_id")] = String(mqttClientID) + name;
if (unitOfMeasurement != "")
doc[F("unit_of_measurement")] = unitOfMeasurement;
if (deviceClass != "")
doc[F("device_class")] = deviceClass;
doc[F("expire_after")] = 1800;
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
device[F("name")] = serverDescription;
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
device[F("manufacturer")] = F(WLED_BRAND);
device[F("model")] = F(WLED_PRODUCT_NAME);
device[F("sw_version")] = versionString;
String temp;
serializeJson(doc, temp);
DEBUG_PRINTLN(t);
DEBUG_PRINTLN(temp);
mqtt->publish(t.c_str(), 0, true, temp.c_str());
}
#endif
public:
void setup()
{
initializeAht();
}
void loop()
{
// if usermod is disabled or called during strip updating just exit
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
if (!_settingEnabled || strip.isUpdating())
return;
// do your magic here
unsigned long currentTime = millis();
if (currentTime - _lastLoopCheck < _checkInterval)
return;
_lastLoopCheck = currentTime;
_lastStatus = _aht->readRawData();
if (_lastStatus == AHT10_ERROR)
{
// Perform softReset and retry
DEBUG_PRINTLN(F("AHTxx returned error, doing softReset"));
if (!_aht->softReset())
{
DEBUG_PRINTLN(F("softReset failed"));
return;
}
_lastStatus = _aht->readRawData();
}
if (_lastStatus == AHT10_SUCCESS)
{
float temperature = truncateDecimals(_aht->readTemperature(AHT10_USE_READ_DATA));
float humidity = truncateDecimals(_aht->readHumidity(AHT10_USE_READ_DATA));
#ifndef WLED_DISABLE_MQTT
// Push to MQTT
// We can avoid reporting if the change is insignificant. The threshold chosen is below the level of accuracy, but way above 0.01 which is the precision of the value provided.
// The AHT10/15/20 has an accuracy of 0.3C in the temperature readings
mqttPublishIfChanged(F("temperature"), _lastTemperatureSent, temperature, 0.1f);
// The AHT10/15/20 has an accuracy in the humidity sensor of 2%
mqttPublishIfChanged(F("humidity"), _lastHumiditySent, humidity, 0.5f);
#endif
// Store
_lastTemperature = temperature;
_lastHumidity = humidity;
}
}
#ifndef WLED_DISABLE_MQTT
void onMqttConnect(bool sessionPresent)
{
mqttInitialize();
}
#endif
uint16_t getId()
{
return USERMOD_ID_AHT10;
}
void addToJsonInfo(JsonObject &root) override
{
// if "u" object does not exist yet wee need to create it
JsonObject user = root["u"];
if (user.isNull())
user = root.createNestedObject("u");
#ifdef USERMOD_AHT10_DEBUG
JsonArray temp = user.createNestedArray(F("AHT last loop"));
temp.add(_lastLoopCheck);
temp = user.createNestedArray(F("AHT last status"));
temp.add(_lastStatus);
#endif
JsonArray jsonTemp = user.createNestedArray(F("Temperature"));
JsonArray jsonHumidity = user.createNestedArray(F("Humidity"));
if (_lastLoopCheck == 0)
{
// Before first run
jsonTemp.add(F("Not read yet"));
jsonHumidity.add(F("Not read yet"));
return;
}
if (_lastStatus != AHT10_SUCCESS)
{
jsonTemp.add(F("An error occurred"));
jsonHumidity.add(F("An error occurred"));
return;
}
jsonTemp.add(_lastTemperature);
jsonTemp.add(F("°C"));
jsonHumidity.add(_lastHumidity);
jsonHumidity.add(F("%"));
}
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject(FPSTR(_name));
top[F("Enabled")] = _settingEnabled;
top[F("I2CAddress")] = static_cast<uint8_t>(_i2cAddress);
top[F("SensorType")] = _ahtType;
top[F("CheckInterval")] = _checkInterval / 1000;
top[F("Decimals")] = log10f(_decimalFactor);
#ifndef WLED_DISABLE_MQTT
top[F("MqttPublish")] = _mqttPublish;
top[F("MqttPublishAlways")] = _mqttPublishAlways;
top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant;
#endif
DEBUG_PRINTLN(F("AHT10 config saved."));
}
bool readFromConfig(JsonObject &root) override
{
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
JsonObject top = root[FPSTR(_name)];
bool configComplete = !top.isNull();
if (!configComplete)
return false;
bool tmpBool = false;
configComplete &= getJsonValue(top[F("Enabled")], tmpBool);
if (configComplete)
_settingEnabled = tmpBool;
configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress);
configComplete &= getJsonValue(top[F("CheckInterval")], _checkInterval);
if (configComplete)
{
if (1 <= _checkInterval && _checkInterval <= 600)
_checkInterval *= 1000;
else
// Invalid input
_checkInterval = 60000;
}
configComplete &= getJsonValue(top[F("Decimals")], _decimalFactor);
if (configComplete)
{
if (0 <= _decimalFactor && _decimalFactor <= 5)
_decimalFactor = pow10f(_decimalFactor);
else
// Invalid input
_decimalFactor = 100;
}
uint8_t tmpAhtType;
configComplete &= getJsonValue(top[F("SensorType")], tmpAhtType);
if (configComplete)
{
if (0 <= tmpAhtType && tmpAhtType <= 2)
_ahtType = static_cast<ASAIR_I2C_SENSOR>(tmpAhtType);
else
// Invalid input
_ahtType = ASAIR_I2C_SENSOR::AHT10_SENSOR;
}
#ifndef WLED_DISABLE_MQTT
configComplete &= getJsonValue(top[F("MqttPublish")], tmpBool);
if (configComplete)
_mqttPublish = tmpBool;
configComplete &= getJsonValue(top[F("MqttPublishAlways")], tmpBool);
if (configComplete)
_mqttPublishAlways = tmpBool;
configComplete &= getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool);
if (configComplete)
_mqttHomeAssistant = tmpBool;
#endif
if (_initDone)
{
// Reloading config
initializeAht();
#ifndef WLED_DISABLE_MQTT
mqttInitialize();
#endif
}
_initDone = true;
return configComplete;
}
};
const char UsermodAHT10::_name[] PROGMEM = "AHTxx";

View File

@ -59,7 +59,7 @@ private:
bool sensorFound = false;
// Home Assistant and MQTT
String mqttLuminanceTopic = F("");
String mqttLuminanceTopic;
bool mqttInitialized = false;
bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages

View File

@ -7,6 +7,7 @@ This Usermod is designed to read a `BME280` or `BMP280` sensor and output the fo
- Dew Point (`BME280` only)
Configuration is performed via the Usermod menu. There are no parameters to set in code! The following settings can be configured in the Usermod Menu:
- The i2c address in decimal. Set it to either 118 (0x76, the default) or 119 (0x77).
- Temperature Decimals (number of decimal places to output)
- Humidity Decimals
- Pressure Decimals

View File

@ -24,6 +24,7 @@ private:
uint8_t PressureDecimals = 0; // Number of decimal places in published pressure values
uint16_t TemperatureInterval = 5; // Interval to measure temperature (and humidity, dew point if available) in seconds
uint16_t PressureInterval = 300; // Interval to measure pressure in seconds
BME280I2C::I2CAddr I2CAddress = BME280I2C::I2CAddr_0x76; // i2c address, defaults to 0x76
bool PublishAlways = false; // Publish values even when they have not changed
bool UseCelsius = true; // Use Celsius for Reporting
bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information
@ -35,20 +36,7 @@ private:
#endif
bool initDone = false;
// 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};
BME280I2C bme;
uint8_t sensorType;
@ -181,34 +169,52 @@ private:
}
}
void initializeBmeComms()
{
BME280I2C::Settings settings{
BME280::OSR_X16, // Temperature oversampling x16
BME280::OSR_X16, // Humidity oversampling x16
BME280::OSR_X16, // Pressure oversampling x16
BME280::Mode_Forced,
BME280::StandbyTime_1000ms,
BME280::Filter_Off,
BME280::SpiEnable_False,
I2CAddress
};
bme.setSettings(settings);
if (!bme.begin())
{
sensorType = 0;
DEBUG_PRINTLN(F("Could not find BME280 I2C sensor!"));
}
else
{
switch (bme.chipModel())
{
case BME280::ChipModel_BME280:
sensorType = 1;
DEBUG_PRINTLN(F("Found BME280 sensor! Success."));
break;
case BME280::ChipModel_BMP280:
sensorType = 2;
DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available."));
break;
default:
sensorType = 0;
DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!"));
}
}
}
public:
void setup()
{
if (i2c_scl<0 || i2c_sda<0) { enabled = false; sensorType = 0; return; }
if (!bme.begin())
{
sensorType = 0;
DEBUG_PRINTLN(F("Could not find BME280 I2C sensor!"));
}
else
{
switch (bme.chipModel())
{
case BME280::ChipModel_BME280:
sensorType = 1;
DEBUG_PRINTLN(F("Found BME280 sensor! Success."));
break;
case BME280::ChipModel_BMP280:
sensorType = 2;
DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available."));
break;
default:
sensorType = 0;
DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!"));
}
}
initDone=true;
initializeBmeComms();
initDone = true;
}
void loop()
@ -365,12 +371,11 @@ public:
}
else if (sensorType==2) //BMP280
{
JsonArray temperature_json = user.createNestedArray(F("Temperature"));
JsonArray pressure_json = user.createNestedArray(F("Pressure"));
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)));
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
temperature_json.add(tempScale);
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)) / powf(10, PressureDecimals));
pressure_json.add(F("hPa"));
}
else if (sensorType==1) //BME280
@ -382,9 +387,9 @@ public:
JsonArray dewpoint_json = user.createNestedArray(F("Dew Point"));
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
temperature_json.add(tempScale);
humidity_json.add(roundf(sensorHumidity * powf(10, HumidityDecimals)));
humidity_json.add(roundf(sensorHumidity * powf(10, HumidityDecimals)) / powf(10, HumidityDecimals));
humidity_json.add(F("%"));
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)) / powf(10, PressureDecimals));
pressure_json.add(F("hPa"));
heatindex_json.add(roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
heatindex_json.add(tempScale);
@ -399,6 +404,7 @@ public:
{
JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
top[F("I2CAddress")] = static_cast<uint8_t>(I2CAddress);
top[F("TemperatureDecimals")] = TemperatureDecimals;
top[F("HumidityDecimals")] = HumidityDecimals;
top[F("PressureDecimals")] = PressureDecimals;
@ -426,6 +432,10 @@ public:
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
// A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing
uint8_t tmpI2cAddress;
configComplete &= getJsonValue(top[F("I2CAddress")], tmpI2cAddress, 0x76);
I2CAddress = static_cast<BME280I2C::I2CAddr>(tmpI2cAddress);
configComplete &= getJsonValue(top[F("TemperatureDecimals")], TemperatureDecimals, 1);
configComplete &= getJsonValue(top[F("HumidityDecimals")], HumidityDecimals, 0);
configComplete &= getJsonValue(top[F("PressureDecimals")], PressureDecimals, 0);
@ -440,8 +450,23 @@ public:
// first run: reading from cfg.json
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
DEBUG_PRINTLN(F(" config (re)loaded."));
// Reset all known values
sensorType = 0;
sensorTemperature = 0;
sensorHumidity = 0;
sensorHeatIndex = 0;
sensorDewPoint = 0;
sensorPressure = 0;
lastTemperature = 0;
lastHumidity = 0;
lastHeatIndex = 0;
lastDewPoint = 0;
lastPressure = 0;
initializeBmeComms();
}
return configComplete;

Binary file not shown.

View File

@ -0,0 +1,152 @@
# Usermod BME68X
This usermod was developed for a BME680/BME68X sensor. The BME68X is not compatible with the BME280/BMP280 chip. It has its own library. The original 'BSEC Software Library' from Bosch was used to develop the code. The measured values are displayed on the WLED info page.
<p align="center"><img src="pics/pic1.png" style="width:60%;"></p>
In addition, the values are published on MQTT if this is active. The topic used for this is: 'wled/[MQTT Client ID]'. The Client ID is set in the WLED MQTT settings.
<p align="center"><img src="pics/pic2.png"></p>
If you use HomeAssistance discovery, the device tree for HomeAssistance is created. This is published under the topic 'homeassistant/sensor/[MQTT Client ID]' via MQTT.
<p align="center"><img src="pics/pic3.png"></p>
A device with the following sensors appears in HomeAssistant. Please note that MQTT must be activated in HomeAssistant.
<p align="center"><img src="pics/pic4.png" style="width:60%;"></p>
## Features
Raw sensor types
Sensor Accuracy Scale Range
--------------------------------------------------------------------------------------------------
Temperature +/- 1.0 °C/°F -40 to 85 °C
Humidity +/- 3 % 0 to 100 %
Pressure +/- 1 hPa 300 to 1100 hPa
Gas Resistance Ohm
The BSEC Library calculates the following values via the gas resistance
Sensor Accuracy Scale Range
--------------------------------------------------------------------------------------------------
IAQ value between 0 and 500
Static IAQ same as IAQ but for permanently installed devices
CO2 PPM
VOC PPM
Gas-Percentage %
In addition the usermod calculates
Sensor Accuracy Scale Range
--------------------------------------------------------------------------------------------------
Absolute humidity g/m³
Dew point °C/°F
### IAQ (Indoor Air Quality)
The IAQ is divided into the following value groups.
<p align="center"><img src="pics/pic5.png"></p>
For more detailed information, please consult the enclosed Bosch product description (BME680.pdf).
## Calibration of the device
The gas sensor of the BME68X must be calibrated. This differs from the BME280, which does not require any calibration.
There is a range of additional information for this, which the driver also provides. These values can be found in HomeAssistant under Diagnostics.
- **STABILIZATION_STATUS**: Gas sensor stabilization status [boolean] Indicates initial stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization is finished (1).
- **RUN_IN_STATUS**: Gas sensor run-in status [boolean] Indicates power-on stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization is finished (1)
Furthermore, all GAS based values have their own accuracy value. These have the following meaning:
- **Accuracy = 0** means the sensor is being stabilized (this can take a while on the first run)
- **Accuracy = 1** means that the previous measured values show too few differences and cannot be used for calibration. If the sensor is at accuracy 1 for too long, you must ensure that the ambient air is chaning. Opening the windows is fine. Or sometimes it is sufficient to breathe on the sensor for approx. 5 minutes.
- **Accuracy = 2** means the sensor is currently calibrating.
- **Accuracy = 3** means that the sensor has been successfully calibrated. Once accuracy 3 is reached, the calibration data is automatically written to the file system. This calibration data will be used again at the next start and will speed up the calibration.
The IAQ index is therefore only meaningful if IAQ Accuracy = 3. In addition to the value for IAQ, BSEC also provides us with CO2 and VOC equivalent values. When using the sensor, the calibration value should also always be read out and displayed or transmitted.
Reasonably reliable values are therefore only achieved when accuracy displays the value 3.
## Settings
The settings of the usermods are set in the usermod section of wled.
<p align="center"><img src="pics/pic6.png"></p>
The possible settings are
- **Enable:** Enables / disables the usermod
- **I2C address:** I2C address of the sensor. You can choose between 0X77 & 0X76. The default is 0x77.
- **Interval:** Specifies the interval of seconds at which the usermod should be executed. The default is every second.
- **Pub Chages Only:** If this item is active, the values are only published if they have changed since the last publication.
- **Pub Accuracy:** The Accuracy values associated with the gas values are also published.
- **Pub Calib State:** If this item is active, STABILIZATION_STATUS& RUN_IN_STATUS are also published.
- **Temp Scale:** Here you can choose between °C and °F.
- **Temp Offset:** The temperature offset is always set in °C. It must be converted for Fahrenheit.
- **HA Discovery:** If this item is active, the HomeAssistant sensor tree is created.
- **Pause While WLED Active:** If WLED has many LEDs to calculate, the computing power may no longer be sufficient to calculate the LEDs and read the sensor data. The LEDs then hang for a few microseconds, which can be seen. If this point is active, no sensor data is fetched as long as WLED is running.
- **Del Calibration Hist:** If a check mark is set here, the calibration file saved in the file system is deleted when the settings are saved.
### Sensors
Applies to all sensors. The number of decimal places is set here. If the sensor is set to -1, it will no longer be published. In addition, the IAQ values can be activated here in verbal form.
It is recommended to use the Static IAQ for the IAQ values. This is recommended by Bosch for statically placed devices.
## Output
Data is published over MQTT - make sure you've enabled the MQTT sync interface.
In addition to outputting via MQTT, you can read the values from the Info Screen on the dashboard page of the device's web interface.
Methods also exist to read the read/calculated values from other WLED modules through code.
- getTemperature(); The scale °C/°F is depended to the settings
- getHumidity();
- getPressure();
- getGasResistance();
- getAbsoluteHumidity();
- getDewPoint(); The scale °C/°F is depended to the settings
- getIaq();
- getStaticIaq();
- getCo2();
- getVoc();
- getGasPerc();
- getIaqAccuracy();
- getStaticIaqAccuracy();
- getCo2Accuracy();
- getVocAccuracy();
- getGasPercAccuracy();
- getStabStatus();
- getRunInStatus();
## Compiling
To enable, compile with `USERMOD_BME68X` defined (e.g. in `platformio_override.ini`) and add the `BSEC Software Library` to the lib_deps.
```
[env:esp32-BME680]
board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
lib_deps = ${esp32.lib_deps}
boschsensortec/BSEC Software Library @ ^1.8.1492 ; USERMOD: BME680
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32}
-D USERMOD_BME68X ; USERMOD: BME680
```
## Revision History
### Version 1.0.0
- First version of the BME68X_v user module
### Version 1.0.1
- Rebased to WELD Version 0.15
- Reworked some default settings
- A problem with the default settings has been fixed
## Known problems
- MQTT goes online at device start. Shortly afterwards it goes offline and takes quite a while until it goes online again. The problem does not come from this user module, but from the WLED core.
- If you save the settings often, WLED can get stuck.
- If many LEDS are connected to WLED, reading the sensor can cause a small but noticeable hang. The "Pause While WLED Active" option was introduced as a workaround.
<div><img src="pics/GeoGab.svg" width="20%"/> </div>
Gabriel Sieben (gsieben@geogab.net)

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="enable-background:new 0 0 595.28 127.56;"
viewBox="0 0 600 135"
y="0px"
x="0px"
id="Layer_1"
version="1.1"><metadata
id="metadata2372"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs2370"><linearGradient
osb:paint="solid"
id="linearGradient3877"><stop
id="stop3875"
offset="0"
style="stop-color:#808285;stop-opacity:1;" /></linearGradient><clipPath
id="clipPath2379"
clipPathUnits="userSpaceOnUse"><g
style="fill:#808285;fill-opacity:1;fill-rule:nonzero;stroke:#230065;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
id="use2381"><path
style="fill:#808285;fill-opacity:1;fill-rule:nonzero;stroke:#230065;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
class="st0"
d="m 588.34,94.29 c 0,13.28 -10.77,24.04 -24.04,24.04 H 31.38 C 18.1,118.33 7.34,107.57 7.34,94.29 V 32.38 C 7.34,19.1 18.1,8.34 31.38,8.34 h 532.91 c 13.28,0 24.04,10.76 24.04,24.04 V 94.29 Z M 159.44,69.66 c 0,17.15 11.68,19.07 21.89,19.07 h 14.23 c 14.6,0 23.44,-3.2 23.44,-13.87 v -0.91 h -7.84 c -0.45,7.11 -4.47,8.39 -14.32,8.39 h -10.76 c -13.68,0 -18.79,-2.74 -18.79,-13.32 V 66.65 H 219 V 60.9 C 219,44.3 208.88,40.74 194.83,40.74 h -12.59 c -11.12,0 -22.8,1.92 -22.8,18.79 z m 51.72,-9.95 h -43.87 c 0.73,-10.58 3.65,-12.59 16.05,-12.59 h 11.49 c 12.77,0 16.33,4.74 16.33,9.49 z m 18.62,10.13 c 0,15.33 10.86,18.88 25.73,18.88 h 11.22 c 14.87,0 25.72,-3.56 25.72,-18.88 V 59.62 c 0,-15.32 -10.85,-18.88 -25.72,-18.88 h -11.22 c -14.87,0 -25.73,3.56 -25.73,18.88 z m 54.82,-0.46 c 0,10.86 -6.38,12.95 -15.51,12.95 h -15.96 c -9.12,0 -15.51,-2.09 -15.51,-12.95 v -9.31 c 0,-10.85 6.39,-12.95 15.51,-12.95 h 15.96 c 9.12,0 15.51,2.1 15.51,12.95 z m 173.79,18.61 v -31.2 c 0,-13.87 -9.85,-16.06 -24.45,-16.06 h -12.41 c -14.05,0 -19.43,3.47 -19.43,11.95 v 2.28 h 7.84 v -1.64 c 0,-3.83 1.92,-6.2 11.77,-6.2 h 11.49 c 12.04,0 17.33,1.09 17.33,9.49 v 8.76 h -0.18 c -2.74,-4.47 -6.39,-5.56 -16.6,-5.56 H 421.16 C 407.48,59.81 400,61.09 400,71.67 v 3.1 c 0,8.76 3.1,13.96 14.96,13.96 h 18.79 z m -7.84,-12.5 c 0,5.84 -5.47,6.84 -19.7,6.84 h -10.4 c -10.76,0 -12.59,-2.19 -12.59,-7.39 v -1.46 c 0,-5.84 2.83,-7.3 12.95,-7.3 h 12.04 c 12.04,0 17.7,0.82 17.7,7.2 z M 73.6,66.69 h 10.88 l 5.03,0.01 h 48.67 v 2.41 c 0,9.12 -5.47,12.23 -14.41,12.23 H 88.83 c -8.58,0 -15.51,-2.55 -15.51,-14.05 v -0.35 -0.26 z m -9.16,-0.01 c 0,0.06 0,0.14 0,0.2 0.67,14.68 6.68,21.76 23.47,21.76 h 36.85 c 16.51,0 22.35,-6.39 22.35,-24.36 V 59.4 H 89.51 L 85.39,59.39 H 73.32 v -0.01 -16.9 c 0,-11.49 6.93,-14.05 15.51,-14.05 H 112 c 22.07,0 25.81,0.91 25.81,13.23 h 8.39 v -2.37 c 0,-15.23 -12.68,-18.15 -24.54,-18.15 H 87.92 c -17.88,0 -23.53,8.03 -23.53,24.72 V 59.39 L 64.38,59.4 H 44.6 c -11.86,0 -24.54,2.92 -24.54,18.15 v 2.37 5.02 2.37 c 0,15.24 12.68,18.16 24.54,18.16 h 40.79 432.59 34.03 c 17.88,0 23.54,-8.03 23.54,-24.72 V 45.88 c 0,-16.69 -5.65,-24.72 -23.54,-24.72 l -0.16,-0.02 h -223 c -17.88,0 -23.53,8.03 -23.53,24.72 v 18.06 c 0,16.69 5.65,24.72 23.53,24.72 h 36.85 c 16.51,0 22.35,-6.39 22.35,-24.36 V 59.4 h -42.78 v 7.3 h 33.84 v 2.41 c 0,9.12 -5.47,12.23 -14.41,12.23 h -34.94 c -8.58,0 -15.5,-2.55 -15.5,-14.05 V 42.48 c 0,-11.49 6.93,-14.05 15.5,-14.05 l 142.5,0.02 v 61.24 l 24.35,0.73 h 10.95 c 14.23,0 24.18,-3.56 24.18,-19.61 V 62.6 c 0,-14.87 -5.75,-20.16 -24.63,-20.16 h -11.95 -14.88 -0.17 l -0.02,-14.01 h 71.75 l -0.75,0.02 c 8.58,0 15.51,2.55 15.51,14.04 v 41.62 c 0,11.49 -6.93,14.05 -15.51,14.05 H 518.97 86.87 54.27 c -22.08,0 -25.81,-0.91 -25.81,-13.23 v -5.02 c 0,-12.31 3.74,-13.23 25.81,-13.23 h 9.84 z m 459.45,4.04 c 0,9.03 -2.83,13.32 -15.6,13.32 h -12.77 c -11.67,0 -15.42,-4.93 -15.42,-13.41 v -8.85 c 0,-11.13 6.48,-12.95 17.06,-12.95 h 10.58 c 10.76,0 16.14,2.01 16.14,12.77 v 9.12 z"
id="path3935" /><path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#230065;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
class="st0"
d="m 611.07375,-48.387188 c 0,13.28 -10.77,24.039999 -24.04,24.039999 H 54.113747 c -13.27999,0 -24.04,-10.76 -24.04,-24.039999 v -61.910002 c 0,-13.28 10.76001,-24.04 24.04,-24.04 H 587.02375 c 13.28,0 24.04,10.76 24.04,24.04 v 61.910002 z"
id="path3937" /></g></clipPath><clipPath
id="clipPath2398"
clipPathUnits="userSpaceOnUse"><g
id="use2400"
style="fill:#268298;fill-opacity:1"><g
id="g3959"
clip-path="url(#clipPath2407)"
style="fill:#268298;fill-opacity:1"><g
id="g3957"
style="fill:#268298;fill-opacity:1"><rect
style="opacity:1;fill:#268298;fill-opacity:1;stroke:#000000;stroke-opacity:1"
id="rect3955"
width="350.98587"
height="147.57361"
x="95.224861"
y="-97.290329" /></g></g></g></clipPath><clipPath
id="clipPath2407"
clipPathUnits="userSpaceOnUse"><g
id="use2409"
style="fill:#268298;fill-opacity:1"><rect
style="opacity:1;fill:#268298;fill-opacity:1;stroke:#000000;stroke-opacity:1"
id="rect3963"
width="350.98587"
height="147.57361"
x="95.224861"
y="-97.290329" /></g></clipPath></defs>
<style
id="style2363"
type="text/css">
.st0{fill:#808285;}
</style>
<path
style="opacity:1;fill:#ffffff;fill-opacity:0.9859813;fill-rule:nonzero;stroke:#5c7823;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
class="st0"
d="M 600,103.51782 C 600,116.79902 588.8778,127.56 575.17384,127.56 H 24.826163 C 11.111876,127.56 5.1635108e-7,116.79902 5.1635108e-7,103.51782 V 41.602186 C 5.1635108e-7,28.320979 11.111876,17.560001 24.826163,17.560001 H 575.16351 c 13.71429,0 24.82616,10.760978 24.82616,24.042185 v 61.915634 z"
id="path2365-0" /><path
style="fill:#808285;fill-opacity:1;fill-rule:nonzero;stroke:#230065;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
class="st0"
d="M 600,103.51782 C 600,116.79902 588.8778,127.56 575.17384,127.56 H 24.826162 C 11.111877,127.56 5.1635111e-7,116.79902 5.1635111e-7,103.51782 V 41.602187 C 5.1635111e-7,28.320977 11.111876,17.559997 24.826162,17.559997 H 575.16351 c 13.71429,0 24.82616,10.76098 24.82616,24.04219 V 103.51782 Z M 157.07401,78.885567 c 0,17.15156 12.06196,19.07173 22.60585,19.07173 h 14.69535 c 15.07745,0 24.20654,-3.20029 24.20654,-13.87125 v -0.91009 h -8.09638 c -0.46472,7.11065 -4.61618,8.39076 -14.7883,8.39076 h -11.11188 c -14.12736,0 -19.40447,-2.74024 -19.40447,-13.3212 v -2.37022 h 53.40103 v -5.75052 c 0,-16.60151 -10.45094,-20.16183 -24.96041,-20.16183 h -13.00172 c -11.48365,0 -23.54561,1.92017 -23.54561,18.79171 z m 53.41136,-9.9509 h -45.30465 c 0.75387,-10.58096 3.76936,-12.59114 16.57487,-12.59114 h 11.86575 c 13.18761,0 16.86403,4.74043 16.86403,9.49086 z m 19.22891,10.13092 c 0,15.33139 11.21515,18.88171 26.57143,18.88171 h 11.58692 c 15.35629,0 26.56111,-3.56032 26.56111,-18.88171 v -10.22093 c 0,-15.32139 -11.20482,-18.88171 -26.56111,-18.88171 h -11.58692 c -15.35628,0 -26.57143,3.56032 -26.57143,18.88171 z m 56.61275,-0.46004 c 0,10.86098 -6.58865,12.95117 -16.01722,12.95117 h -16.48193 c -9.41825,0 -16.01721,-2.09019 -16.01721,-12.95117 v -9.31085 c 0,-10.85098 6.59896,-12.95117 16.01721,-12.95117 h 16.48193 c 9.41824,0 16.01722,2.10019 16.01722,12.95117 z m 179.47332,18.61169 v -31.20283 c 0,-13.87126 -10.17212,-16.06146 -25.24957,-16.06146 h -12.81584 c -14.50946,0 -20.0654,3.47031 -20.0654,11.95108 v 2.28021 h 8.09638 v -1.64015 c 0,-3.83035 1.98279,-6.20056 12.15491,-6.20056 h 11.86575 c 12.43373,0 17.89673,1.0901 17.89673,9.49086 v 8.7608 h -0.18589 c -2.8296,-4.47041 -6.59897,-5.56051 -17.14286,-5.56051 h -13.00172 c -14.12736,0 -21.85198,1.28012 -21.85198,11.86108 v 3.10028 c 0,8.76079 3.20138,13.96126 15.44923,13.96126 h 19.40447 z m -8.09639,-12.50114 c 0,5.84053 -5.64888,6.84062 -20.34423,6.84062 h -10.74011 c -11.11187,0 -13.00172,-2.19019 -13.00172,-7.39067 v -1.46013 c 0,-5.84053 2.92255,-7.30066 13.3735,-7.30066 h 12.43373 c 12.43374,0 18.27883,0.82007 18.27883,7.20065 z m -389.27711,-8.8008 h 11.2358 l 5.194492,0.01 h 50.261618 v 2.41021 c 0,9.12083 -5.64888,12.23111 -14.88124,12.23111 H 84.154905 c -8.860585,0 -16.017212,-2.55023 -16.017212,-14.05127 v -0.35003 -0.26003 z m -9.459552,-0.01 c 0,0.06 0,0.14002 0,0.20002 0.69191,14.68133 6.898451,21.76198 24.237521,21.76198 h 38.055071 c 17.04992,0 23.0809,-6.39058 23.0809,-24.36221 v -4.88045 H 84.857142 l -4.254733,-0.01 H 68.137693 v -0.01 -16.90153 c 0,-11.49104 7.156627,-14.05129 16.017212,-14.05129 h 23.927705 c 22.79174,0 26.65405,0.91009 26.65405,13.23121 h 8.66437 v -2.37021 c 0,-15.23139 -13.09466,-18.15166 -25.34251,-18.15166 H 83.215146 c -18.464717,0 -24.299483,8.03073 -24.299483,24.72226 v 13.53122 l -0.01033,0.01 h -20.42685 c -12.247849,0 -25.342513,2.92027 -25.342513,18.15165 v 2.37022 5.02045 2.37022 c 0,15.241393 13.094664,18.161653 25.342513,18.161653 h 42.123923 446.736664 35.14286 c 18.46471,0 24.30981,-8.03073 24.30981,-24.722253 v -34.87316 c 0,-16.69153 -5.83477,-24.72226 -24.30981,-24.72226 l -0.16523,-0.02 H 332.0241 c -18.46471,0 -24.29948,8.03073 -24.29948,24.72226 v 18.06163 c 0,16.69152 5.83477,24.72225 24.29948,24.72225 h 38.05508 c 17.04991,0 23.08089,-6.39058 23.08089,-24.36221 v -4.88045 h -44.179 v 7.30067 h 34.94664 v 2.41021 c 0,9.12083 -5.64888,12.23111 -14.88123,12.23111 h -36.08262 c -8.86059,0 -16.00688,-2.55023 -16.00688,-14.05127 v -24.81225 c 0,-11.49104 7.15662,-14.05129 16.00688,-14.05129 l 147.16007,0.02 v 61.24556 l 25.14629,0.73007 h 11.30809 c 14.69536,0 24.97074,-3.56033 24.97074,-19.61178 v -8.21075 c 0,-14.87135 -5.93803,-20.16183 -25.43545,-20.16183 h -12.34079 -15.36661 -0.17556 l -0.0207,-14.01128 h 74.09639 l -0.77453,0.02 c 8.86059,0 16.01721,2.55024 16.01721,14.04128 v 41.62378 c 0,11.491053 -7.15662,14.051283 -16.01721,14.051283 H 528.36145 82.130808 48.464716 c -22.802065,0 -26.654044,-0.91008 -26.654044,-13.231213 v -5.02046 c 0,-12.31111 3.862306,-13.2312 26.654044,-13.2312 h 10.161789 z m 474.475042,4.04037 c 0,9.03082 -2.92255,13.32121 -16.11015,13.32121 h -13.18761 c -12.05164,0 -15.92427,-4.93045 -15.92427,-13.41122 v -8.8508 c 0,-11.13101 6.69191,-12.95118 17.6179,-12.95118 h 10.92599 c 11.11188,0 16.66781,2.01019 16.66781,12.77116 v 9.12083 z"
id="path2365" />
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,160 @@
#ifndef UMBBattery_h
#define UMBBattery_h
#include "battery_defaults.h"
/**
* Battery base class
* all other battery classes should inherit from this
*/
class UMBattery
{
private:
protected:
float minVoltage;
float maxVoltage;
float voltage;
int8_t level = 100;
float calibration; // offset or calibration value to fine tune the calculated voltage
float voltageMultiplier; // ratio for the voltage divider
float linearMapping(float v, float min, float max, float oMin = 0.0f, float oMax = 100.0f)
{
return (v-min) * (oMax-oMin) / (max-min) + oMin;
}
public:
UMBattery()
{
this->setVoltageMultiplier(USERMOD_BATTERY_VOLTAGE_MULTIPLIER);
this->setCalibration(USERMOD_BATTERY_CALIBRATION);
}
virtual void update(batteryConfig cfg)
{
if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage);
if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage);
if(cfg.level) this->setLevel(cfg.level);
if(cfg.calibration) this->setCalibration(cfg.calibration);
if(cfg.voltageMultiplier) this->setVoltageMultiplier(cfg.voltageMultiplier);
}
/**
* Corresponding battery curves
* calculates the level in % (0-100) with given voltage and possible voltage range
*/
virtual float mapVoltage(float v, float min, float max) = 0;
// {
// example implementation, linear mapping
// return (v-min) * 100 / (max-min);
// };
virtual void calculateAndSetLevel(float voltage) = 0;
/*
*
* Getter and Setter
*
*/
/*
* Get lowest configured battery voltage
*/
virtual float getMinVoltage()
{
return this->minVoltage;
}
/*
* Set lowest battery voltage
* can't be below 0 volt
*/
virtual void setMinVoltage(float voltage)
{
this->minVoltage = max(0.0f, voltage);
}
/*
* Get highest configured battery voltage
*/
virtual float getMaxVoltage()
{
return this->maxVoltage;
}
/*
* Set highest battery voltage
* can't be below minVoltage
*/
virtual void setMaxVoltage(float voltage)
{
this->maxVoltage = max(getMinVoltage()+.5f, voltage);
}
float getVoltage()
{
return this->voltage;
}
/**
* check if voltage is within specified voltage range, allow 10% over/under voltage
*/
void setVoltage(float voltage)
{
// this->voltage = ( (voltage < this->getMinVoltage() * 0.85f) || (voltage > this->getMaxVoltage() * 1.1f) )
// ? -1.0f
// : voltage;
this->voltage = voltage;
}
float getLevel()
{
return this->level;
}
void setLevel(float level)
{
this->level = constrain(level, 0.0f, 110.0f);
}
/*
* Get the configured calibration value
* a offset value to fine-tune the calculated voltage.
*/
virtual float getCalibration()
{
return calibration;
}
/*
* Set the voltage calibration offset value
* a offset value to fine-tune the calculated voltage.
*/
virtual void setCalibration(float offset)
{
calibration = offset;
}
/*
* Get the configured calibration value
* a value to set the voltage divider ratio
*/
virtual float getVoltageMultiplier()
{
return voltageMultiplier;
}
/*
* Set the voltage multiplier value
* a value to set the voltage divider ratio.
*/
virtual void setVoltageMultiplier(float multiplier)
{
voltageMultiplier = multiplier;
}
};
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -1,3 +1,8 @@
#ifndef UMBDefaults_h
#define UMBDefaults_h
#include "wled.h"
// pin defaults
// for the esp32 it is best to use the ADC1: GPIO32 - GPIO39
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html
@ -9,24 +14,66 @@
#endif
#endif
// The initial delay before the first battery voltage reading after power-on.
// This allows the voltage to stabilize before readings are taken, improving accuracy of initial reading.
#ifndef USERMOD_BATTERY_INITIAL_DELAY
#define USERMOD_BATTERY_INITIAL_DELAY 10000 // (milliseconds)
#endif
// the frequency to check the battery, 30 sec
#ifndef USERMOD_BATTERY_MEASUREMENT_INTERVAL
#define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000
#endif
// default for 18650 battery
// https://batterybro.com/blogs/18650-wholesale-battery-reviews/18852515-when-to-recycle-18650-batteries-and-how-to-start-a-collection-center-in-your-vape-shop
// Discharge voltage: 2.5 volt + .1 for personal safety
#ifndef USERMOD_BATTERY_MIN_VOLTAGE
#ifdef USERMOD_BATTERY_USE_LIPO
// LiPo "1S" Batteries should not be dischared below 3V !!
#define USERMOD_BATTERY_MIN_VOLTAGE 3.2f
#else
#define USERMOD_BATTERY_MIN_VOLTAGE 2.6f
#endif
/* Default Battery Type
* 0 = unkown
* 1 = Lipo
* 2 = Lion
*/
#ifndef USERMOD_BATTERY_DEFAULT_TYPE
#define USERMOD_BATTERY_DEFAULT_TYPE 0
#endif
/*
*
* Unkown 'Battery' defaults
*
*/
#ifndef USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE
// Extra save defaults
#define USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE 3.3f
#endif
#ifndef USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE
#define USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE 4.2f
#endif
//the default ratio for the voltage divider
/*
*
* Lithium polymer (Li-Po) defaults
*
*/
#ifndef USERMOD_BATTERY_LIPO_MIN_VOLTAGE
// LiPo "1S" Batteries should not be dischared below 3V !!
#define USERMOD_BATTERY_LIPO_MIN_VOLTAGE 3.2f
#endif
#ifndef USERMOD_BATTERY_LIPO_MAX_VOLTAGE
#define USERMOD_BATTERY_LIPO_MAX_VOLTAGE 4.2f
#endif
/*
*
* Lithium-ion (Li-Ion) defaults
*
*/
#ifndef USERMOD_BATTERY_LION_MIN_VOLTAGE
// default for 18650 battery
#define USERMOD_BATTERY_LION_MIN_VOLTAGE 2.6f
#endif
#ifndef USERMOD_BATTERY_LION_MAX_VOLTAGE
#define USERMOD_BATTERY_LION_MAX_VOLTAGE 4.2f
#endif
// the default ratio for the voltage divider
#ifndef USERMOD_BATTERY_VOLTAGE_MULTIPLIER
#ifdef ARDUINO_ARCH_ESP32
#define USERMOD_BATTERY_VOLTAGE_MULTIPLIER 2.0f
@ -35,13 +82,8 @@
#endif
#endif
#ifndef USERMOD_BATTERY_MAX_VOLTAGE
#define USERMOD_BATTERY_MAX_VOLTAGE 4.2f
#endif
// a common capacity for single 18650 battery cells is between 2500 and 3600 mAh
#ifndef USERMOD_BATTERY_TOTAL_CAPACITY
#define USERMOD_BATTERY_TOTAL_CAPACITY 3100
#ifndef USERMOD_BATTERY_AVERAGING_ALPHA
#define USERMOD_BATTERY_AVERAGING_ALPHA 0.1f
#endif
// offset or calibration value to fine tune the calculated voltage
@ -49,11 +91,6 @@
#define USERMOD_BATTERY_CALIBRATION 0
#endif
// calculate remaining time / the time that is left before the battery runs out of power
// #ifndef USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED
// #define USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED false
// #endif
// auto-off feature
#ifndef USERMOD_BATTERY_AUTO_OFF_ENABLED
#define USERMOD_BATTERY_AUTO_OFF_ENABLED true
@ -78,4 +115,26 @@
#ifndef USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION
#define USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION 5
#endif
// battery types
typedef enum
{
unknown=0,
lipo=1,
lion=2
} batteryType;
// used for initial configuration after boot
typedef struct bconfig_t
{
batteryType type;
float minVoltage;
float maxVoltage;
float voltage; // current voltage
int8_t level; // current level
float calibration; // offset or calibration value to fine tune the calculated voltage
float voltageMultiplier;
} batteryConfig;
#endif

View File

@ -6,105 +6,166 @@
Enables battery level monitoring of your project.
For this to work, the positive side of the (18650) battery must be connected to pin `A0` of the d1 mini/esp8266 with a 100k Ohm resistor (see [Useful Links](#useful-links)).
If you have an ESP32 board, connect the positive side of the battery to ADC1 (GPIO32 - GPIO39)
<p align="center">
<img width="500" src="assets/battery_info_screen.png">
<p align="left">
<img width="700" src="assets/battery_info_screen.png">
</p>
<br>
## ⚙️ Features
- 💯 Displays current battery voltage
- 💯 Displays current battery voltage
- 🚥 Displays battery level
- 🚫 Auto-off with configurable Threshold
- 🚫 Auto-off with configurable threshold
- 🚨 Low power indicator with many configuration possibilities
<br><br>
## 🎈 Installation
define `USERMOD_BATTERY` in `wled00/my_config.h`
| **Option 1** | **Option 2** |
|--------------|--------------|
| In `wled00/my_config.h`<br>Add the line: `#define USERMOD_BATTERY`<br><br>[Example: my_config.h](assets/installation_my_config_h.png) | In `platformio_override.ini` (or `platformio.ini`)<br>Under: `build_flags =`, add the line: `-D USERMOD_BATTERY`<br><br>[Example: platformio_override.ini](assets/installation_platformio_override_ini.png) |
### Example wiring
<br><br>
<p align="center">
<img width="300" src="assets/battery_connection_schematic_01.png">
</p>
## 🔌 Example wiring
### Define Your Options
- (see [Useful Links](#useful-links)).
<table style="width: 100%; table-layout: fixed;">
<tr>
<!-- Column for the first image -->
<td style="width: 50%; vertical-align: bottom;">
<img width="300" src="assets/battery_connection_schematic_01.png" style="display: block;">
<p><strong>ESP8266</strong><br>
With a 100k Ohm resistor, connect the positive<br>
side of the battery to pin `A0`.</p>
</td>
<!-- Column for the second image -->
<td style="width: 50%; vertical-align: bottom;">
<img width="250" src="assets/battery_connection_schematic_esp32.png" style="display: block;">
<p><strong>ESP32</strong> (+S2, S3, C3 etc...)<br>
Use a voltage divider (two resistors of equal value).<br>
Connect to ADC1 (GPIO32 - GPIO39). GPIO35 is Default.</p>
</td>
</tr>
</table>
<br><br>
## Define Your Options
| Name | Unit | Description |
| ----------------------------------------------- | ----------- |-------------------------------------------------------------------------------------- |
| `USERMOD_BATTERY` | | define this (in `my_config.h`) to have this usermod included wled00\usermods_list.cpp |
| `USERMOD_BATTERY_USE_LIPO` | | define this (in `my_config.h`) if you use LiPo rechargeables (1S) |
| `USERMOD_BATTERY_MEASUREMENT_PIN` | | defaults to A0 on ESP8266 and GPIO35 on ESP32 |
| `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | battery check interval. defaults to 30 seconds |
| `USERMOD_BATTERY_MIN_VOLTAGE` | v | minimum battery voltage. default is 2.6 (18650 battery standard) |
| `USERMOD_BATTERY_MAX_VOLTAGE` | v | maximum battery voltage. default is 4.2 (18650 battery standard) |
| `USERMOD_BATTERY_TOTAL_CAPACITY` | mAh | the capacity of all cells in parallel summed up |
| `USERMOD_BATTERY_CALIBRATION` | | offset / calibration number, fine tune the measured voltage by the microcontroller |
| `USERMOD_BATTERY` | | Define this (in `my_config.h`) to have this usermod included wled00\usermods_list.cpp |
| `USERMOD_BATTERY_MEASUREMENT_PIN` | | Defaults to A0 on ESP8266 and GPIO35 on ESP32 |
| `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | Battery check interval. defaults to 30 seconds |
| `USERMOD_BATTERY_INITIAL_DELAY` | ms | Delay before initial reading. defaults to 10 seconds to allow voltage stabilization |
| `USERMOD_BATTERY_{TYPE}_MIN_VOLTAGE` | v | Minimum battery voltage. default is 2.6 (18650 battery standard) |
| `USERMOD_BATTERY_{TYPE}_MAX_VOLTAGE` | v | Maximum battery voltage. default is 4.2 (18650 battery standard) |
| `USERMOD_BATTERY_{TYPE}_TOTAL_CAPACITY` | mAh | The capacity of all cells in parallel summed up |
| `USERMOD_BATTERY_{TYPE}_CALIBRATION` | | Offset / calibration number, fine tune the measured voltage by the microcontroller |
| Auto-Off | --- | --- |
| `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | enables auto-off |
| `USERMOD_BATTERY_AUTO_OFF_THRESHOLD` | % (0-100) | when this threshold is reached master power turns off |
| `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | Enables auto-off |
| `USERMOD_BATTERY_AUTO_OFF_THRESHOLD` | % (0-100) | When this threshold is reached master power turns off |
| Low-Power-Indicator | --- | --- |
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED` | true/false | enables low power indication |
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET` | preset id | when low power is detected then use this preset to indicate low power |
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD` | % (0-100) | when this threshold is reached low power gets indicated |
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION` | seconds | for this long the configured preset is played |
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED` | true/false | Enables low power indication |
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET` | preset id | When low power is detected then use this preset to indicate low power |
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD` | % (0-100) | When this threshold is reached low power gets indicated |
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION` | seconds | For this long the configured preset is played |
All parameters can be configured at runtime via the Usermods settings page.
<br>
**NOTICE:** Each Battery type can be pre-configured individualy (in `my_config.h`)
| Name | Alias | `my_config.h` example |
| --------------- | ------------- | ------------------------------------- |
| Lithium Polymer | lipo (Li-Po) | `USERMOD_BATTERY_lipo_MIN_VOLTAGE` |
| Lithium Ionen | lion (Li-Ion) | `USERMOD_BATTERY_lion_TOTAL_CAPACITY` |
<br><br>
## 🔧 Calibration
The calibration number is a value that is added to the final computed voltage after it has been scaled by the voltage multiplier.
It fine-tunes the voltage reading so that it more closely matches the actual battery voltage, compensating for inaccuracies inherent in the voltage divider resistors or the ESP's ADC measurements.
Set calibration either in the Usermods settings page or at compile time in `my_config.h` or `platformio_override.ini`.
It can be either a positive or negative number.
<br><br>
## ⚠️ Important
- Make sure you know your battery specifications! All batteries are **NOT** the same!
- Example:
Make sure you know your battery specifications! All batteries are **NOT** the same!
| Your battery specification table | | Options you can define |
| :-------------------------------- |:--------------- | :---------------------------- |
| Capacity | 3500mAh 12,5 Wh | |
| Minimum capacity | 3350mAh 11,9 Wh | |
Example:
| Your battery specification table | | Options you can define |
| --------------------------------- | --------------- | ----------------------------- |
| Capacity | 3500mAh 12.5Wh | |
| Minimum capacity | 3350mAh 11.9Wh | |
| Rated voltage | 3.6V - 3.7V | |
| **Charging end voltage** | **4,2V ± 0,05** | `USERMOD_BATTERY_MAX_VOLTAGE` |
| **Discharge voltage** | **2,5V** | `USERMOD_BATTERY_MIN_VOLTAGE` |
| **Charging end voltage** | **4.2V ± 0.05** | `USERMOD_BATTERY_MAX_VOLTAGE` |
| **Discharge voltage** | **2.5V** | `USERMOD_BATTERY_MIN_VOLTAGE` |
| Max. discharge current (constant) | 10A (10000mA) | |
| max. charging current | 1.7A (1700mA) | |
| ... | ... | ... |
| .. | .. | .. |
Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6V - 3.7V](https://www.akkuteile.de/lithium-ionen-akkus/18650/molicel/molicel-inr18650-m35a-3500mah-10a-lithium-ionen-akku-3-6v-3-7v_100833)
Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6V - 3.7V](https://www.akkuteile.de/lithium-ionen-akkus/18650/molicel/molicel-inr18650-m35a-3500mah-10a-lithium-ionen-akku-3-6v-3-7v_100833)
<br><br>
## 🌐 Useful Links
- https://lazyzero.de/elektronik/esp8266/wemos_d1_mini_a0/start
- https://arduinodiy.wordpress.com/2016/12/25/monitoring-lipo-battery-voltage-with-wemos-d1-minibattery-shield-and-thingspeak/
<br><br>
## 📝 Change Log
2024-05-11
- Documentation updated
2024-04-30
- Integrate factory pattern to make it easier to add other / custom battery types
- Update readme
- Improved initial reading accuracy by delaying initial measurement to allow voltage to stabilize at power-on
2023-01-04
- basic support for LiPo rechargeable batteries ( `-D USERMOD_BATTERY_USE_LIPO`)
- improved support for esp32 (read calibrated voltage)
- corrected config saving (measurement pin, and battery min/max were lost)
- various bugfixes
- Basic support for LiPo rechargeable batteries (`-D USERMOD_BATTERY_USE_LIPO`)
- Improved support for ESP32 (read calibrated voltage)
- Corrected config saving (measurement pin, and battery min/max were lost)
- Various bugfixes
2022-12-25
- added "auto-off" feature
- added "low-power-indication" feature
- added "calibration/offset" field to configuration page
- added getter and setter, so that user usermods could interact with this one
- update readme (added new options, made it markdownlint compliant)
- Added "auto-off" feature
- Added "low-power-indication" feature
- Added "calibration/offset" field to configuration page
- Added getter and setter, so that user usermods could interact with this one
- Update readme (added new options, made it markdownlint compliant)
2021-09-02
- added "Battery voltage" to info
- added circuit diagram to readme
- added MQTT support, sending battery voltage
- minor fixes
- Added "Battery voltage" to info
- Added circuit diagram to readme
- Added MQTT support, sending battery voltage
- Minor fixes
2021-08-15
- changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries
- Changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries
- Updated readme, added specification table
2021-08-10

View File

@ -0,0 +1,38 @@
#ifndef UMBLion_h
#define UMBLion_h
#include "../battery_defaults.h"
#include "../UMBattery.h"
/**
* LiOn Battery
*
*/
class LionUMBattery : public UMBattery
{
private:
public:
LionUMBattery() : UMBattery()
{
this->setMinVoltage(USERMOD_BATTERY_LION_MIN_VOLTAGE);
this->setMaxVoltage(USERMOD_BATTERY_LION_MAX_VOLTAGE);
}
float mapVoltage(float v, float min, float max) override
{
return this->linearMapping(v, min, max); // basic mapping
};
void calculateAndSetLevel(float voltage) override
{
this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage()));
};
virtual void setMaxVoltage(float voltage) override
{
this->maxVoltage = max(getMinVoltage()+1.0f, voltage);
}
};
#endif

View File

@ -0,0 +1,54 @@
#ifndef UMBLipo_h
#define UMBLipo_h
#include "../battery_defaults.h"
#include "../UMBattery.h"
/**
* LiPo Battery
*
*/
class LipoUMBattery : public UMBattery
{
private:
public:
LipoUMBattery() : UMBattery()
{
this->setMinVoltage(USERMOD_BATTERY_LIPO_MIN_VOLTAGE);
this->setMaxVoltage(USERMOD_BATTERY_LIPO_MAX_VOLTAGE);
}
/**
* LiPo batteries have a differnt discharge curve, see
* https://blog.ampow.com/lipo-voltage-chart/
*/
float mapVoltage(float v, float min, float max) override
{
float lvl = 0.0f;
lvl = this->linearMapping(v, min, max); // basic mapping
if (lvl < 40.0f)
lvl = this->linearMapping(lvl, 0, 40, 0, 12); // last 45% -> drops very quickly
else {
if (lvl < 90.0f)
lvl = this->linearMapping(lvl, 40, 90, 12, 95); // 90% ... 40% -> almost linear drop
else // level > 90%
lvl = this->linearMapping(lvl, 90, 105, 95, 100); // highest 15% -> drop slowly
}
return lvl;
};
void calculateAndSetLevel(float voltage) override
{
this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage()));
};
virtual void setMaxVoltage(float voltage) override
{
this->maxVoltage = max(getMinVoltage()+0.7f, voltage);
}
};
#endif

View File

@ -0,0 +1,39 @@
#ifndef UMBUnkown_h
#define UMBUnkown_h
#include "../battery_defaults.h"
#include "../UMBattery.h"
/**
* Unkown / Default Battery
*
*/
class UnkownUMBattery : public UMBattery
{
private:
public:
UnkownUMBattery() : UMBattery()
{
this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE);
this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE);
}
void update(batteryConfig cfg)
{
if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); else this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE);
if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); else this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE);
}
float mapVoltage(float v, float min, float max) override
{
return this->linearMapping(v, min, max); // basic mapping
};
void calculateAndSetLevel(float voltage) override
{
this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage()));
};
};
#endif

View File

@ -2,12 +2,15 @@
#include "wled.h"
#include "battery_defaults.h"
#include "UMBattery.h"
#include "types/UnkownUMBattery.h"
#include "types/LionUMBattery.h"
#include "types/LiPoUMBattery.h"
/*
* Usermod by Maximilian Mewes
* Mail: mewes.maximilian@gmx.de
* GitHub: itCarl
* Date: 25.12.2022
* E-mail: mewes.maximilian@gmx.de
* Created at: 25.12.2022
* If you have any questions, please feel free to contact me.
*/
class UsermodBattery : public Usermod
@ -15,47 +18,36 @@ class UsermodBattery : public Usermod
private:
// battery pin can be defined in my_config.h
int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN;
UMBattery* bat = new UnkownUMBattery();
batteryConfig cfg;
// Initial delay before first reading to allow voltage stabilization
unsigned long initialDelay = USERMOD_BATTERY_INITIAL_DELAY;
bool initialDelayComplete = false;
bool isFirstVoltageReading = true;
// how often to read the battery voltage
unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL;
unsigned long nextReadTime = 0;
unsigned long lastReadTime = 0;
// battery min. voltage
float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE;
// battery max. voltage
float maxBatteryVoltage = USERMOD_BATTERY_MAX_VOLTAGE;
// all battery cells summed up
unsigned int totalBatteryCapacity = USERMOD_BATTERY_TOTAL_CAPACITY;
// raw analog reading
float rawValue = 0.0f;
// calculated voltage
float voltage = maxBatteryVoltage;
// between 0 and 1, to control strength of voltage smoothing filter
float alpha = 0.05f;
// multiplier for the voltage divider that is in place between ADC pin and battery, default will be 2 but might be adapted to readout voltages over ~5v ESP32 or ~6.6v ESP8266
float voltageMultiplier = USERMOD_BATTERY_VOLTAGE_MULTIPLIER;
// mapped battery level based on voltage
int8_t batteryLevel = 100;
// offset or calibration value to fine tune the calculated voltage
float calibration = USERMOD_BATTERY_CALIBRATION;
// time left estimation feature
// bool calculateTimeLeftEnabled = USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED;
// float estimatedTimeLeft = 0.0;
float alpha = USERMOD_BATTERY_AVERAGING_ALPHA;
// auto shutdown/shutoff/master off feature
bool autoOffEnabled = USERMOD_BATTERY_AUTO_OFF_ENABLED;
int8_t autoOffThreshold = USERMOD_BATTERY_AUTO_OFF_THRESHOLD;
uint8_t autoOffThreshold = USERMOD_BATTERY_AUTO_OFF_THRESHOLD;
// low power indicator feature
bool lowPowerIndicatorEnabled = USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED;
int8_t lowPowerIndicatorPreset = USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET;
int8_t lowPowerIndicatorThreshold = USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD;
int8_t lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10;
int8_t lowPowerIndicatorDuration = USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION;
uint8_t lowPowerIndicatorPreset = USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET;
uint8_t lowPowerIndicatorThreshold = USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD;
uint8_t lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10;
uint8_t lowPowerIndicatorDuration = USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION;
bool lowPowerIndicationDone = false;
unsigned long lowPowerActivationTime = 0; // used temporary during active time
int8_t lastPreset = 0;
uint8_t lastPreset = 0;
//
bool initDone = false;
bool initializing = true;
@ -67,22 +59,17 @@ class UsermodBattery : public Usermod
static const char _preset[];
static const char _duration[];
static const char _init[];
// custom map function
// https://forum.arduino.cc/t/floating-point-using-map-function/348113/2
double mapf(double x, double in_min, double in_max, double out_min, double out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
/**
* Helper for rounding floating point values
*/
float dot2round(float x)
{
float nx = (int)(x * 100 + .5);
return (float)(nx / 100);
}
/*
/**
* Turn off all leds
*/
void turnOff()
@ -91,15 +78,15 @@ class UsermodBattery : public Usermod
stateUpdated(CALL_MODE_DIRECT_CHANGE);
}
/*
/**
* Indicate low power by activating a configured preset for a given time and then switching back to the preset that was selected previously
*/
void lowPowerIndicator()
{
if (!lowPowerIndicatorEnabled) return;
if (batteryPin < 0) return; // no measurement
if (lowPowerIndicationDone && lowPowerIndicatorReactivationThreshold <= batteryLevel) lowPowerIndicationDone = false;
if (lowPowerIndicatorThreshold <= batteryLevel) return;
if (lowPowerIndicationDone && lowPowerIndicatorReactivationThreshold <= bat->getLevel()) lowPowerIndicationDone = false;
if (lowPowerIndicatorThreshold <= bat->getLevel()) return;
if (lowPowerIndicationDone) return;
if (lowPowerActivationTime <= 1) {
lowPowerActivationTime = millis();
@ -114,26 +101,39 @@ class UsermodBattery : public Usermod
}
}
/**
* read the battery voltage in different ways depending on the architecture
*/
float readVoltage()
{
#ifdef ARDUINO_ARCH_ESP32
// use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value
return (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration;
return (analogReadMilliVolts(batteryPin) / 1000.0f) * bat->getVoltageMultiplier() + bat->getCalibration();
#else
// use analog read on esp8266 ( 0V ~ 1V no attenuation options) and divide by ADC precision 1023 and multiply by voltage multiplier and apply calibration value
return (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration;
return (analogRead(batteryPin) / 1023.0f) * bat->getVoltageMultiplier() + bat->getCalibration();
#endif
}
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()
{
// plug in the right battery type
if(cfg.type == (batteryType)lipo) {
bat = new LipoUMBattery();
} else if(cfg.type == (batteryType)lion) {
bat = new LionUMBattery();
}
// update the choosen battery type with configured values
bat->update(cfg);
#ifdef ARDUINO_ARCH_ESP32
bool success = false;
DEBUG_PRINTLN(F("Allocating battery pin..."));
@ -141,7 +141,6 @@ class UsermodBattery : public Usermod
if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) {
DEBUG_PRINTLN(F("Battery pin allocation succeeded."));
success = true;
voltage = readVoltage();
}
if (!success) {
@ -152,17 +151,17 @@ class UsermodBattery : public Usermod
}
#else //ESP8266 boards have only one analog input pin A0
pinMode(batteryPin, INPUT);
voltage = readVoltage();
#endif
nextReadTime = millis() + readingInterval;
// First voltage reading is delayed to allow voltage stabilization after powering up
nextReadTime = millis() + initialDelay;
lastReadTime = millis();
initDone = true;
}
/*
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
@ -182,6 +181,25 @@ class UsermodBattery : public Usermod
lowPowerIndicator();
// Handling the initial delay
if (!initialDelayComplete && millis() < nextReadTime)
return; // Continue to return until the initial delay is over
// Once the initial delay is over, set it as complete
if (!initialDelayComplete)
{
initialDelayComplete = true;
// Set the regular interval after initial delay
nextReadTime = millis() + readingInterval;
}
// Make the first voltage reading after the initial delay has elapsed
if (isFirstVoltageReading)
{
bat->setVoltage(readVoltage());
isFirstVoltageReading = false;
}
// check the battery level every USERMOD_BATTERY_MEASUREMENT_INTERVAL (ms)
if (millis() < nextReadTime) return;
@ -191,43 +209,17 @@ class UsermodBattery : public Usermod
if (batteryPin < 0) return; // nothing to read
initializing = false;
float rawValue = readVoltage();
rawValue = readVoltage();
// filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout
voltage = voltage + alpha * (rawValue - voltage);
// check if voltage is within specified voltage range, allow 10% over/under voltage - removed cause this just makes it hard for people to troubleshoot as the voltage in the web gui will say invalid instead of displaying a voltage
//voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage;
float filteredVoltage = bat->getVoltage() + alpha * (rawValue - bat->getVoltage());
bat->setVoltage(filteredVoltage);
// translate battery voltage into percentage
/*
the standard "map" function doesn't work
https://www.arduino.cc/reference/en/language/functions/math/map/ notes and warnings at the bottom
*/
#ifdef USERMOD_BATTERY_USE_LIPO
batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100); // basic mapping
// LiPo batteries have a differnt dischargin curve, see
// https://blog.ampow.com/lipo-voltage-chart/
if (batteryLevel < 40.0f)
batteryLevel = mapf(batteryLevel, 0, 40, 0, 12); // last 45% -> drops very quickly
else {
if (batteryLevel < 90.0f)
batteryLevel = mapf(batteryLevel, 40, 90, 12, 95); // 90% ... 40% -> almost linear drop
else // level > 90%
batteryLevel = mapf(batteryLevel, 90, 105, 95, 100); // highest 15% -> drop slowly
}
#else
batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100);
#endif
if (voltage > -1.0f) batteryLevel = constrain(batteryLevel, 0.0f, 110.0f);
// if (calculateTimeLeftEnabled) {
// float currentBatteryCapacity = totalBatteryCapacity;
// estimatedTimeLeft = (currentBatteryCapacity/strip.currentMilliamps)*60;
// }
bat->calculateAndSetLevel(filteredVoltage);
// Auto off -- Master power off
if (autoOffEnabled && (autoOffThreshold >= batteryLevel))
if (autoOffEnabled && (autoOffThreshold >= bat->getLevel()))
turnOff();
#ifndef WLED_DISABLE_MQTT
@ -236,13 +228,13 @@ class UsermodBattery : public Usermod
if (WLED_MQTT_CONNECTED) {
char buf[64]; // buffer for snprintf()
snprintf_P(buf, 63, PSTR("%s/voltage"), mqttDeviceTopic);
mqtt->publish(buf, 0, false, String(voltage).c_str());
mqtt->publish(buf, 0, false, String(bat->getVoltage()).c_str());
}
#endif
}
/*
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor
@ -262,16 +254,6 @@ class UsermodBattery : public Usermod
// info modal display names
JsonArray infoPercentage = user.createNestedArray(F("Battery level"));
JsonArray infoVoltage = user.createNestedArray(F("Battery voltage"));
// if (calculateTimeLeftEnabled)
// {
// JsonArray infoEstimatedTimeLeft = user.createNestedArray(F("Estimated time left"));
// if (initializing) {
// infoEstimatedTimeLeft.add(FPSTR(_init));
// } else {
// infoEstimatedTimeLeft.add(estimatedTimeLeft);
// infoEstimatedTimeLeft.add(F(" min"));
// }
// }
JsonArray infoNextUpdate = user.createNestedArray(F("Next update"));
infoNextUpdate.add((nextReadTime - millis()) / 1000);
@ -283,46 +265,104 @@ class UsermodBattery : public Usermod
return;
}
if (batteryLevel < 0) {
if (bat->getLevel() < 0) {
infoPercentage.add(F("invalid"));
} else {
infoPercentage.add(batteryLevel);
infoPercentage.add(bat->getLevel());
}
infoPercentage.add(F(" %"));
if (voltage < 0) {
if (bat->getVoltage() < 0) {
infoVoltage.add(F("invalid"));
} else {
infoVoltage.add(dot2round(voltage));
infoVoltage.add(dot2round(bat->getVoltage()));
}
infoVoltage.add(F(" V"));
}
void addBatteryToJsonObject(JsonObject& battery, bool forJsonState)
{
if(forJsonState) { battery[F("type")] = cfg.type; } else {battery[F("type")] = (String)cfg.type; } // has to be a String otherwise it won't get converted to a Dropdown
battery[F("min-voltage")] = bat->getMinVoltage();
battery[F("max-voltage")] = bat->getMaxVoltage();
battery[F("calibration")] = bat->getCalibration();
battery[F("voltage-multiplier")] = bat->getVoltageMultiplier();
battery[FPSTR(_readInterval)] = readingInterval;
/*
JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section
ao[FPSTR(_enabled)] = autoOffEnabled;
ao[FPSTR(_threshold)] = autoOffThreshold;
JsonObject lp = battery.createNestedObject(F("indicator")); // low power section
lp[FPSTR(_enabled)] = lowPowerIndicatorEnabled;
lp[FPSTR(_preset)] = lowPowerIndicatorPreset; // dropdown trickery (String)lowPowerIndicatorPreset;
lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold;
lp[FPSTR(_duration)] = lowPowerIndicatorDuration;
}
void getUsermodConfigFromJsonObject(JsonObject& battery)
{
getJsonValue(battery[F("type")], cfg.type);
getJsonValue(battery[F("min-voltage")], cfg.minVoltage);
getJsonValue(battery[F("max-voltage")], cfg.maxVoltage);
getJsonValue(battery[F("calibration")], cfg.calibration);
getJsonValue(battery[F("voltage-multiplier")], cfg.voltageMultiplier);
setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval);
JsonObject ao = battery[F("auto-off")];
setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled);
setAutoOffThreshold(ao[FPSTR(_threshold)] | autoOffThreshold);
JsonObject lp = battery[F("indicator")];
setLowPowerIndicatorEnabled(lp[FPSTR(_enabled)] | lowPowerIndicatorEnabled);
setLowPowerIndicatorPreset(lp[FPSTR(_preset)] | lowPowerIndicatorPreset);
setLowPowerIndicatorThreshold(lp[FPSTR(_threshold)] | lowPowerIndicatorThreshold);
lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10;
setLowPowerIndicatorDuration(lp[FPSTR(_duration)] | lowPowerIndicatorDuration);
if(initDone)
bat->update(cfg);
}
/**
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
/*
void addToJsonState(JsonObject& root)
{
JsonObject battery = root.createNestedObject(FPSTR(_name));
if (battery.isNull())
battery = root.createNestedObject(FPSTR(_name));
addBatteryToJsonObject(battery, true);
DEBUG_PRINTLN(F("Battery state exposed in JSON API."));
}
*/
/*
/**
* 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 (!initDone) return; // prevent crash on boot applyPreset()
JsonObject battery = root[FPSTR(_name)];
if (!battery.isNull()) {
getUsermodConfigFromJsonObject(battery);
DEBUG_PRINTLN(F("Battery state read from JSON API."));
}
}
*/
/*
/**
* 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().
@ -359,47 +399,41 @@ class UsermodBattery : public Usermod
*/
void addToConfig(JsonObject& root)
{
JsonObject battery = root.createNestedObject(FPSTR(_name)); // usermodname
JsonObject battery = root.createNestedObject(FPSTR(_name));
if (battery.isNull()) {
battery = root.createNestedObject(FPSTR(_name));
}
#ifdef ARDUINO_ARCH_ESP32
battery[F("pin")] = batteryPin;
#endif
// battery[F("time-left")] = calculateTimeLeftEnabled;
battery[F("min-voltage")] = minBatteryVoltage;
battery[F("max-voltage")] = maxBatteryVoltage;
battery[F("capacity")] = totalBatteryCapacity;
battery[F("calibration")] = calibration;
battery[F("voltage-multiplier")] = voltageMultiplier;
battery[FPSTR(_readInterval)] = readingInterval;
JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section
ao[FPSTR(_enabled)] = autoOffEnabled;
ao[FPSTR(_threshold)] = autoOffThreshold;
JsonObject lp = battery.createNestedObject(F("indicator")); // low power section
lp[FPSTR(_enabled)] = lowPowerIndicatorEnabled;
lp[FPSTR(_preset)] = lowPowerIndicatorPreset; // dropdown trickery (String)lowPowerIndicatorPreset;
lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold;
lp[FPSTR(_duration)] = lowPowerIndicatorDuration;
addBatteryToJsonObject(battery, false);
// read voltage in case calibration or voltage multiplier changed to see immediate effect
voltage = readVoltage();
bat->setVoltage(readVoltage());
DEBUG_PRINTLN(F("Battery config saved."));
}
void appendConfigData()
{
oappend(SET_F("addInfo('Battery:min-voltage', 1, 'v');"));
oappend(SET_F("addInfo('Battery:max-voltage', 1, 'v');"));
oappend(SET_F("addInfo('Battery:capacity', 1, 'mAh');"));
oappend(SET_F("addInfo('Battery:interval', 1, 'ms');"));
oappend(SET_F("addInfo('Battery:auto-off:threshold', 1, '%');"));
oappend(SET_F("addInfo('Battery:indicator:threshold', 1, '%');"));
oappend(SET_F("addInfo('Battery:indicator:duration', 1, 's');"));
// Total: 462 Bytes
oappend(SET_F("td=addDropdown('Battery', 'type');")); // 35 Bytes
oappend(SET_F("addOption(td, 'Unkown', '0');")); // 30 Bytes
oappend(SET_F("addOption(td, 'LiPo', '1');")); // 28 Bytes
oappend(SET_F("addOption(td, 'LiOn', '2');")); // 28 Bytes
oappend(SET_F("addInfo('Battery:type',1,'<small style=\"color:orange\">requires reboot</small>');")); // 81 Bytes
oappend(SET_F("addInfo('Battery:min-voltage', 1, 'v');")); // 40 Bytes
oappend(SET_F("addInfo('Battery:max-voltage', 1, 'v');")); // 40 Bytes
oappend(SET_F("addInfo('Battery:interval', 1, 'ms');")); // 38 Bytes
oappend(SET_F("addInfo('Battery:auto-off:threshold', 1, '%');")); // 47 Bytes
oappend(SET_F("addInfo('Battery:indicator:threshold', 1, '%');")); // 48 Bytes
oappend(SET_F("addInfo('Battery:indicator:duration', 1, 's');")); // 47 Bytes
// cannot quite get this mf to work. its exeeding some buffer limit i think
// what i wanted is a list of all presets to select one from
// this option list would exeed the oappend() buffer
// a list of all presets to select one from
// oappend(SET_F("bd=addDropdown('Battery:low-power-indicator', 'preset');"));
// the loop generates: oappend(SET_F("addOption(bd, 'preset name', preset id);"));
// for(int8_t i=1; i < 42; i++) {
@ -412,7 +446,7 @@ class UsermodBattery : public Usermod
}
/*
/**
* 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 immediately after boot, or after saving on the Usermod Settings page)
*
@ -445,25 +479,13 @@ class UsermodBattery : public Usermod
newBatteryPin = battery[F("pin")] | newBatteryPin;
#endif
// calculateTimeLeftEnabled = battery[F("time-left")] | calculateTimeLeftEnabled;
setMinBatteryVoltage(battery[F("min-voltage")] | minBatteryVoltage);
setMaxBatteryVoltage(battery[F("max-voltage")] | maxBatteryVoltage);
setTotalBatteryCapacity(battery[F("capacity")] | totalBatteryCapacity);
setCalibration(battery[F("calibration")] | calibration);
setVoltageMultiplier(battery[F("voltage-multiplier")] | voltageMultiplier);
setMinBatteryVoltage(battery[F("min-voltage")] | bat->getMinVoltage());
setMaxBatteryVoltage(battery[F("max-voltage")] | bat->getMaxVoltage());
setCalibration(battery[F("calibration")] | bat->getCalibration());
setVoltageMultiplier(battery[F("voltage-multiplier")] | bat->getVoltageMultiplier());
setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval);
JsonObject ao = battery[F("auto-off")];
setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled);
setAutoOffThreshold(ao[FPSTR(_threshold)] | autoOffThreshold);
JsonObject lp = battery[F("indicator")];
setLowPowerIndicatorEnabled(lp[FPSTR(_enabled)] | lowPowerIndicatorEnabled);
setLowPowerIndicatorPreset(lp[FPSTR(_preset)] | lowPowerIndicatorPreset); // dropdown trickery (int)lp["preset"]
setLowPowerIndicatorThreshold(lp[FPSTR(_threshold)] | lowPowerIndicatorThreshold);
lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10;
setLowPowerIndicatorDuration(lp[FPSTR(_duration)] | lowPowerIndicatorDuration);
DEBUG_PRINT(FPSTR(_name));
getUsermodConfigFromJsonObject(battery);
#ifdef ARDUINO_ARCH_ESP32
if (!initDone)
@ -491,8 +513,9 @@ class UsermodBattery : public Usermod
return !battery[FPSTR(_readInterval)].isNull();
}
/*
* Generate a preset sample for low power indication
/**
* TBD: Generate a preset sample for low power indication
* a button on the config page would be cool, currently not possible
*/
void generateExamplePreset()
{
@ -529,7 +552,7 @@ class UsermodBattery : public Usermod
*
*/
/*
/**
* 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.
*/
@ -538,13 +561,23 @@ class UsermodBattery : public Usermod
return USERMOD_ID_BATTERY;
}
/**
* get currently active battery type
*/
batteryType getBatteryType()
{
return cfg.type;
}
/**
*
*/
unsigned long getReadingInterval()
{
return readingInterval;
}
/*
/**
* minimum repetition is 3000ms (3s)
*/
void setReadingInterval(unsigned long newReadingInterval)
@ -552,105 +585,84 @@ class UsermodBattery : public Usermod
readingInterval = max((unsigned long)3000, newReadingInterval);
}
/*
/**
* Get lowest configured battery voltage
*/
float getMinBatteryVoltage()
{
return minBatteryVoltage;
return bat->getMinVoltage();
}
/*
/**
* Set lowest battery voltage
* can't be below 0 volt
*/
void setMinBatteryVoltage(float voltage)
{
minBatteryVoltage = max(0.0f, voltage);
bat->setMinVoltage(voltage);
}
/*
/**
* Get highest configured battery voltage
*/
float getMaxBatteryVoltage()
{
return maxBatteryVoltage;
return bat->getMaxVoltage();
}
/*
/**
* Set highest battery voltage
* can't be below minBatteryVoltage
*/
void setMaxBatteryVoltage(float voltage)
{
#ifdef USERMOD_BATTERY_USE_LIPO
maxBatteryVoltage = max(getMinBatteryVoltage()+0.7f, voltage);
#else
maxBatteryVoltage = max(getMinBatteryVoltage()+1.0f, voltage);
#endif
bat->setMaxVoltage(voltage);
}
/*
* Get the capacity of all cells in parralel sumed up
* unit: mAh
*/
unsigned int getTotalBatteryCapacity()
{
return totalBatteryCapacity;
}
void setTotalBatteryCapacity(unsigned int capacity)
{
totalBatteryCapacity = capacity;
}
/*
/**
* Get the calculated voltage
* formula: (adc pin value / adc precision * max voltage) + calibration
*/
float getVoltage()
{
return voltage;
return bat->getVoltage();
}
/*
/**
* Get the mapped battery level (0 - 100) based on voltage
* important: voltage can drop when a load is applied, so its only an estimate
*/
int8_t getBatteryLevel()
{
return batteryLevel;
return bat->getLevel();
}
/*
/**
* Get the configured calibration value
* a offset value to fine-tune the calculated voltage.
*/
float getCalibration()
{
return calibration;
return bat->getCalibration();
}
/*
/**
* Set the voltage calibration offset value
* a offset value to fine-tune the calculated voltage.
*/
void setCalibration(float offset)
{
calibration = offset;
bat->setCalibration(offset);
}
/*
/**
* Set the voltage multiplier value
* A multiplier that may need adjusting for different voltage divider setups
*/
void setVoltageMultiplier(float multiplier)
{
voltageMultiplier = multiplier;
bat->setVoltageMultiplier(multiplier);
}
/*
@ -659,10 +671,10 @@ class UsermodBattery : public Usermod
*/
float getVoltageMultiplier()
{
return voltageMultiplier;
return bat->getVoltageMultiplier();
}
/*
/**
* Get auto-off feature enabled status
* is auto-off enabled, true/false
*/
@ -671,7 +683,7 @@ class UsermodBattery : public Usermod
return autoOffEnabled;
}
/*
/**
* Set auto-off feature status
*/
void setAutoOffEnabled(bool enabled)
@ -679,7 +691,7 @@ class UsermodBattery : public Usermod
autoOffEnabled = enabled;
}
/*
/**
* Get auto-off threshold in percent (0-100)
*/
int8_t getAutoOffThreshold()
@ -687,7 +699,7 @@ class UsermodBattery : public Usermod
return autoOffThreshold;
}
/*
/**
* Set auto-off threshold in percent (0-100)
*/
void setAutoOffThreshold(int8_t threshold)
@ -697,8 +709,7 @@ class UsermodBattery : public Usermod
autoOffThreshold = lowPowerIndicatorEnabled /*&& autoOffEnabled*/ ? min(lowPowerIndicatorThreshold-1, (int)autoOffThreshold) : autoOffThreshold;
}
/*
/**
* Get low-power-indicator feature enabled status
* is the low-power-indicator enabled, true/false
*/
@ -707,7 +718,7 @@ class UsermodBattery : public Usermod
return lowPowerIndicatorEnabled;
}
/*
/**
* Set low-power-indicator feature status
*/
void setLowPowerIndicatorEnabled(bool enabled)
@ -715,7 +726,7 @@ class UsermodBattery : public Usermod
lowPowerIndicatorEnabled = enabled;
}
/*
/**
* Get low-power-indicator preset to activate when low power is detected
*/
int8_t getLowPowerIndicatorPreset()
@ -723,7 +734,7 @@ class UsermodBattery : public Usermod
return lowPowerIndicatorPreset;
}
/*
/**
* Set low-power-indicator preset to activate when low power is detected
*/
void setLowPowerIndicatorPreset(int8_t presetId)
@ -741,7 +752,7 @@ class UsermodBattery : public Usermod
return lowPowerIndicatorThreshold;
}
/*
/**
* Set low-power-indicator threshold in percent (0-100)
*/
void setLowPowerIndicatorThreshold(int8_t threshold)
@ -751,7 +762,7 @@ class UsermodBattery : public Usermod
lowPowerIndicatorThreshold = autoOffEnabled /*&& lowPowerIndicatorEnabled*/ ? max(autoOffThreshold+1, (int)lowPowerIndicatorThreshold) : max(5, (int)lowPowerIndicatorThreshold);
}
/*
/**
* Get low-power-indicator duration in seconds
*/
int8_t getLowPowerIndicatorDuration()
@ -759,7 +770,7 @@ class UsermodBattery : public Usermod
return lowPowerIndicatorDuration;
}
/*
/**
* Set low-power-indicator duration in seconds
*/
void setLowPowerIndicatorDuration(int8_t duration)
@ -767,9 +778,8 @@ class UsermodBattery : public Usermod
lowPowerIndicatorDuration = duration;
}
/*
* Get low-power-indicator status when the indication is done thsi returns true
/**
* Get low-power-indicator status when the indication is done this returns true
*/
bool getLowPowerIndicatorDone()
{

View File

@ -0,0 +1,77 @@
# Usermod INA226
This Usermod is designed to read values from an INA226 sensor and output the following:
- Current
- Voltage
- Power
- Shunt Voltage
- Overflow status
## Configuration
The following settings can be configured in the Usermod Menu:
- **Enabled**: Enable or disable the usermod.
- **I2CAddress**: The I2C address in decimal. Default is 64 (0x40).
- **CheckInterval**: Number of seconds between readings. This should be higher than the time it takes to make a reading, determined by the two next options.
- **INASamples**: The number of samples to configure the INA226 to use for a measurement. Higher counts provide more accuracy. See the 'Understanding Samples and Conversion Times' section for more details.
- **INAConversionTime**: The time to use on converting and preparing readings on the INA226. Higher times provide more precision. See the 'Understanding Samples and Conversion Times' section for more details.
- **Decimals**: Number of decimals in the output.
- **ShuntResistor**: Shunt resistor value in milliohms. An R100 shunt resistor should be written as "100", while R010 should be "10".
- **CurrentRange**: Expected maximum current in milliamps (e.g., 5 A = 5000 mA).
- **MqttPublish**: Enable or disable MQTT publishing.
- **MqttPublishAlways**: Publish always, regardless if there is a change.
- **MqttHomeAssistantDiscovery**: Enable Home Assistant discovery.
## Dependencies
These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
- Libraries
- `wollewald/INA226_WE@~1.2.9` (by [wollewald](https://registry.platformio.org/libraries/wollewald/INA226_WE))
- `Wire`
## Understanding Samples and Conversion Times
The INA226 uses a programmable ADC with configurable conversion times and averaging to optimize the measurement accuracy and speed. The conversion time and number of samples are determined based on the `INASamples` and `INAConversionTime` settings. The following table outlines the possible combinations:
| Conversion Time (μs) | 1 Sample | 4 Samples | 16 Samples | 64 Samples | 128 Samples | 256 Samples | 512 Samples | 1024 Samples |
|----------------------|----------|-----------|------------|------------|-------------|-------------|-------------|--------------|
| 140 | 0.28 ms | 1.12 ms | 4.48 ms | 17.92 ms | 35.84 ms | 71.68 ms | 143.36 ms | 286.72 ms |
| 204 | 0.408 ms | 1.632 ms | 6.528 ms | 26.112 ms | 52.224 ms | 104.448 ms | 208.896 ms | 417.792 ms |
| 332 | 0.664 ms | 2.656 ms | 10.624 ms | 42.496 ms | 84.992 ms | 169.984 ms | 339.968 ms | 679.936 ms |
| 588 | 1.176 ms | 4.704 ms | 18.816 ms | 75.264 ms | 150.528 ms | 301.056 ms | 602.112 ms | 1204.224 ms |
| 1100 | 2.2 ms | 8.8 ms | 35.2 ms | 140.8 ms | 281.6 ms | 563.2 ms | 1126.4 ms | 2252.8 ms |
| 2116 | 4.232 ms | 16.928 ms | 67.712 ms | 270.848 ms | 541.696 ms | 1083.392 ms | 2166.784 ms | 4333.568 ms |
| 4156 | 8.312 ms | 33.248 ms | 132.992 ms | 531.968 ms | 1063.936 ms | 2127.872 ms | 4255.744 ms | 8511.488 ms |
| 8244 | 16.488 ms| 65.952 ms | 263.808 ms | 1055.232 ms| 2110.464 ms | 4220.928 ms | 8441.856 ms | 16883.712 ms |
It is important to pick a combination that provides the needed balance between accuracy and precision while ensuring new readings within the `CheckInterval` setting. When `USERMOD_INA226_DEBUG` is defined, the info pane contains the expected time to make a reading, which can be seen in the table above.
As an example, if you want a new reading every 5 seconds (`CheckInterval`), a valid combination is `256 samples` and `4156 μs` which would provide new values every 2.1 seconds.
The picked values also slightly affect power usage. If the `CheckInterval` is set to more than 20 seconds, the INA226 is configured in `triggered` reading mode, where it only uses power as long as it's working. Then the conversion time and average samples counts determine how long the chip stays turned on every `CheckInterval` time.
### Calculating Current and Power
The INA226 calculates current by measuring the differential voltage across a shunt resistor and using the calibration register value to convert this measurement into current. Power is calculated by multiplying the current by the bus voltage.
For detailed programming information and register configurations, refer to the [INA226 datasheet](https://www.ti.com/product/INA226).
## Author
[@LordMike](https://github.com/LordMike)
## Compiling
To enable, compile with `USERMOD_INA226` defined (e.g. in `platformio_override.ini`).
```ini
[env:ina226_example]
extends = env:esp32dev
build_flags =
${common.build_flags} ${esp32.build_flags}
-D USERMOD_INA226
; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal
lib_deps =
${esp32.lib_deps}
wollewald/INA226_WE@~1.2.9
```

View File

@ -0,0 +1,9 @@
[env:ina226_example]
extends = env:esp32dev
build_flags =
${common.build_flags} ${esp32.build_flags}
-D USERMOD_INA226
; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal
lib_deps =
${esp32.lib_deps}
wollewald/INA226_WE@~1.2.9

View File

@ -0,0 +1,556 @@
#pragma once
#include "wled.h"
#include <INA226_WE.h>
#define INA226_ADDRESS 0x40 // Default I2C address for INA226
#define DEFAULT_CHECKINTERVAL 60000
#define DEFAULT_INASAMPLES 128
#define DEFAULT_INASAMPLESENUM AVERAGE_128
#define DEFAULT_INACONVERSIONTIME 1100
#define DEFAULT_INACONVERSIONTIMEENUM CONV_TIME_1100
// A packed version of all INA settings enums and their human friendly counterparts packed into a 32 bit structure
// Some values are shifted and need to be preprocessed before usage
struct InaSettingLookup
{
uint16_t avgSamples : 11; // Max 1024, which could be in 10 bits if we shifted by 1; if we somehow handle the edge case with "1"
uint8_t avgEnum : 4; // Shift by 8 to get the INA226_AVERAGES value, accepts 0x00 to 0x0F, we need 0x00 to 0x0E
uint16_t convTimeUs : 14; // We could save 2 bits by shifting this, but we won't save anything at present.
INA226_CONV_TIME convTimeEnum : 3; // Only the lowest 3 bits are defined in the conversion time enumerations
};
const InaSettingLookup _inaSettingsLookup[] = {
{1024, AVERAGE_1024 >> 8, 8244, CONV_TIME_8244},
{512, AVERAGE_512 >> 8, 4156, CONV_TIME_4156},
{256, AVERAGE_256 >> 8, 2116, CONV_TIME_2116},
{128, AVERAGE_128 >> 8, 1100, CONV_TIME_1100},
{64, AVERAGE_64 >> 8, 588, CONV_TIME_588},
{16, AVERAGE_16 >> 8, 332, CONV_TIME_332},
{4, AVERAGE_4 >> 8, 204, CONV_TIME_204},
{1, AVERAGE_1 >> 8, 140, CONV_TIME_140}};
// Note: Will update the provided arg to be the correct value
INA226_AVERAGES getAverageEnum(uint16_t &samples)
{
for (const auto &setting : _inaSettingsLookup)
{
// If a user supplies 2000 samples, we serve up the highest possible value
if (samples >= setting.avgSamples)
{
samples = setting.avgSamples;
return static_cast<INA226_AVERAGES>(setting.avgEnum << 8);
}
}
// Default value if not found
samples = DEFAULT_INASAMPLES;
return DEFAULT_INASAMPLESENUM;
}
INA226_CONV_TIME getConversionTimeEnum(uint16_t &timeUs)
{
for (const auto &setting : _inaSettingsLookup)
{
// If a user supplies 9000 μs, we serve up the highest possible value
if (timeUs >= setting.convTimeUs)
{
timeUs = setting.convTimeUs;
return setting.convTimeEnum;
}
}
// Default value if not found
timeUs = DEFAULT_INACONVERSIONTIME;
return DEFAULT_INACONVERSIONTIMEENUM;
}
class UsermodINA226 : public Usermod
{
private:
static const char _name[];
unsigned long _lastLoopCheck = 0;
unsigned long _lastTriggerTime = 0;
bool _settingEnabled : 1; // Enable the usermod
bool _mqttPublish : 1; // Publish MQTT values
bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change
bool _mqttHomeAssistant : 1; // Enable Home Assistant docs
bool _initDone : 1; // Initialization is done
bool _isTriggeredOperationMode : 1; // false = continuous, true = triggered
bool _measurementTriggered : 1; // if triggered mode, then true indicates we're waiting for measurements
uint16_t _settingInaConversionTimeUs : 12; // Conversion time, shift by 2
uint16_t _settingInaSamples : 11; // Number of samples for averaging, max 1024
uint8_t _i2cAddress;
uint16_t _checkInterval; // milliseconds, user settings is in seconds
float _decimalFactor; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..)
uint16_t _shuntResistor; // Shunt resistor value in milliohms
uint16_t _currentRange; // Expected maximum current in milliamps
uint8_t _lastStatus = 0;
float _lastCurrent = 0;
float _lastVoltage = 0;
float _lastPower = 0;
float _lastShuntVoltage = 0;
bool _lastOverflow = false;
#ifndef WLED_MQTT_DISABLE
float _lastCurrentSent = 0;
float _lastVoltageSent = 0;
float _lastPowerSent = 0;
float _lastShuntVoltageSent = 0;
bool _lastOverflowSent = false;
#endif
INA226_WE *_ina226 = nullptr;
float truncateDecimals(float val)
{
return roundf(val * _decimalFactor) / _decimalFactor;
}
void initializeINA226()
{
if (_ina226 != nullptr)
{
delete _ina226;
}
_ina226 = new INA226_WE(_i2cAddress);
if (!_ina226->init())
{
DEBUG_PRINTLN(F("INA226 initialization failed!"));
return;
}
_ina226->setCorrectionFactor(1.0);
uint16_t tmpShort = _settingInaSamples;
_ina226->setAverage(getAverageEnum(tmpShort));
tmpShort = _settingInaConversionTimeUs << 2;
_ina226->setConversionTime(getConversionTimeEnum(tmpShort));
if (_checkInterval >= 20000)
{
_isTriggeredOperationMode = true;
_ina226->setMeasureMode(TRIGGERED);
}
else
{
_isTriggeredOperationMode = false;
_ina226->setMeasureMode(CONTINUOUS);
}
_ina226->setResistorRange(static_cast<float>(_shuntResistor) / 1000.0, static_cast<float>(_currentRange) / 1000.0);
}
void fetchAndPushValues()
{
_lastStatus = _ina226->getI2cErrorCode();
if (_lastStatus != 0)
return;
float current = truncateDecimals(_ina226->getCurrent_mA() / 1000.0);
float voltage = truncateDecimals(_ina226->getBusVoltage_V());
float power = truncateDecimals(_ina226->getBusPower() / 1000.0);
float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_V());
bool overflow = _ina226->overflow;
#ifndef WLED_DISABLE_MQTT
mqttPublishIfChanged(F("current"), _lastCurrentSent, current, 0.01f);
mqttPublishIfChanged(F("voltage"), _lastVoltageSent, voltage, 0.01f);
mqttPublishIfChanged(F("power"), _lastPowerSent, power, 0.1f);
mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltage, 0.01f);
mqttPublishIfChanged(F("overflow"), _lastOverflowSent, overflow);
#endif
_lastCurrent = current;
_lastVoltage = voltage;
_lastPower = power;
_lastShuntVoltage = shuntVoltage;
_lastOverflow = overflow;
}
void handleTriggeredMode(unsigned long currentTime)
{
if (_measurementTriggered)
{
// Test if we have a measurement every 400ms
if (currentTime - _lastTriggerTime >= 400)
{
_lastTriggerTime = currentTime;
if (_ina226->isBusy())
return;
fetchAndPushValues();
_measurementTriggered = false;
}
}
else
{
if (currentTime - _lastLoopCheck >= _checkInterval)
{
// Start a measurement and use isBusy() later to determine when it is done
_ina226->startSingleMeasurementNoWait();
_lastLoopCheck = currentTime;
_lastTriggerTime = currentTime;
_measurementTriggered = true;
}
}
}
void handleContinuousMode(unsigned long currentTime)
{
if (currentTime - _lastLoopCheck >= _checkInterval)
{
_lastLoopCheck = currentTime;
fetchAndPushValues();
}
}
~UsermodINA226()
{
delete _ina226;
_ina226 = nullptr;
}
#ifndef WLED_DISABLE_MQTT
void mqttInitialize()
{
if (!WLED_MQTT_CONNECTED || !_mqttPublish || !_mqttHomeAssistant)
return;
char topic[128];
snprintf_P(topic, 127, "%s/current", mqttDeviceTopic);
mqttCreateHassSensor(F("Current"), topic, F("current"), F("A"));
snprintf_P(topic, 127, "%s/voltage", mqttDeviceTopic);
mqttCreateHassSensor(F("Voltage"), topic, F("voltage"), F("V"));
snprintf_P(topic, 127, "%s/power", mqttDeviceTopic);
mqttCreateHassSensor(F("Power"), topic, F("power"), F("W"));
snprintf_P(topic, 127, "%s/shunt_voltage", mqttDeviceTopic);
mqttCreateHassSensor(F("Shunt Voltage"), topic, F("voltage"), F("V"));
snprintf_P(topic, 127, "%s/overflow", mqttDeviceTopic);
mqttCreateHassBinarySensor(F("Overflow"), topic);
}
void mqttPublishIfChanged(const __FlashStringHelper *topic, float &lastState, float state, float minChange)
{
if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || fabsf(lastState - state) > minChange))
{
char subuf[128];
snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic);
mqtt->publish(subuf, 0, false, String(state).c_str());
lastState = state;
}
}
void mqttPublishIfChanged(const __FlashStringHelper *topic, bool &lastState, bool state)
{
if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || lastState != state))
{
char subuf[128];
snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic);
mqtt->publish(subuf, 0, false, state ? "true" : "false");
lastState = state;
}
}
void mqttCreateHassSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
{
String t = String(F("homeassistant/sensor/")) + mqttClientID + "/" + name + F("/config");
StaticJsonDocument<600> doc;
doc[F("name")] = name;
doc[F("state_topic")] = topic;
doc[F("unique_id")] = String(mqttClientID) + name;
if (unitOfMeasurement != "")
doc[F("unit_of_measurement")] = unitOfMeasurement;
if (deviceClass != "")
doc[F("device_class")] = deviceClass;
doc[F("expire_after")] = 1800;
JsonObject device = doc.createNestedObject(F("device"));
device[F("name")] = serverDescription;
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
device[F("manufacturer")] = F(WLED_BRAND);
device[F("model")] = F(WLED_PRODUCT_NAME);
device[F("sw_version")] = versionString;
String temp;
serializeJson(doc, temp);
DEBUG_PRINTLN(t);
DEBUG_PRINTLN(temp);
mqtt->publish(t.c_str(), 0, true, temp.c_str());
}
void mqttCreateHassBinarySensor(const String &name, const String &topic)
{
String t = String(F("homeassistant/binary_sensor/")) + mqttClientID + "/" + name + F("/config");
StaticJsonDocument<600> doc;
doc[F("name")] = name;
doc[F("state_topic")] = topic;
doc[F("unique_id")] = String(mqttClientID) + name;
JsonObject device = doc.createNestedObject(F("device"));
device[F("name")] = serverDescription;
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
device[F("manufacturer")] = F(WLED_BRAND);
device[F("model")] = F(WLED_PRODUCT_NAME);
device[F("sw_version")] = versionString;
String temp;
serializeJson(doc, temp);
DEBUG_PRINTLN(t);
DEBUG_PRINTLN(temp);
mqtt->publish(t.c_str(), 0, true, temp.c_str());
}
#endif
public:
UsermodINA226()
{
// Default values
_settingInaSamples = DEFAULT_INASAMPLES;
_settingInaConversionTimeUs = DEFAULT_INACONVERSIONTIME;
_i2cAddress = INA226_ADDRESS;
_checkInterval = DEFAULT_CHECKINTERVAL;
_decimalFactor = 100;
_shuntResistor = 1000;
_currentRange = 1000;
}
void setup()
{
initializeINA226();
}
void loop()
{
if (!_settingEnabled || strip.isUpdating())
return;
unsigned long currentTime = millis();
if (_isTriggeredOperationMode)
{
handleTriggeredMode(currentTime);
}
else
{
handleContinuousMode(currentTime);
}
}
#ifndef WLED_DISABLE_MQTT
void onMqttConnect(bool sessionPresent)
{
mqttInitialize();
}
#endif
uint16_t getId()
{
return USERMOD_ID_INA226;
}
void addToJsonInfo(JsonObject &root) override
{
JsonObject user = root["u"];
if (user.isNull())
user = root.createNestedObject("u");
#ifdef USERMOD_INA226_DEBUG
JsonArray temp = user.createNestedArray(F("INA226 last loop"));
temp.add(_lastLoopCheck);
temp = user.createNestedArray(F("INA226 last status"));
temp.add(_lastStatus);
temp = user.createNestedArray(F("INA226 average samples"));
temp.add(_settingInaSamples);
temp.add(F("samples"));
temp = user.createNestedArray(F("INA226 conversion time"));
temp.add(_settingInaConversionTimeUs << 2);
temp.add(F("μs"));
// INA226 uses (2 * conversion time * samples) time to take a reading.
temp = user.createNestedArray(F("INA226 expected sample time"));
uint32_t sampleTimeNeededUs = (static_cast<uint32_t>(_settingInaConversionTimeUs) << 2) * _settingInaSamples * 2;
temp.add(truncateDecimals(sampleTimeNeededUs / 1000.0));
temp.add(F("ms"));
temp = user.createNestedArray(F("INA226 mode"));
temp.add(_isTriggeredOperationMode ? F("triggered") : F("continuous"));
if (_isTriggeredOperationMode)
{
temp = user.createNestedArray(F("INA226 triggered"));
temp.add(_measurementTriggered ? F("waiting for measurement") : F(""));
}
#endif
JsonArray jsonCurrent = user.createNestedArray(F("Current"));
JsonArray jsonVoltage = user.createNestedArray(F("Voltage"));
JsonArray jsonPower = user.createNestedArray(F("Power"));
JsonArray jsonShuntVoltage = user.createNestedArray(F("Shunt Voltage"));
JsonArray jsonOverflow = user.createNestedArray(F("Overflow"));
if (_lastLoopCheck == 0)
{
jsonCurrent.add(F("Not read yet"));
jsonVoltage.add(F("Not read yet"));
jsonPower.add(F("Not read yet"));
jsonShuntVoltage.add(F("Not read yet"));
jsonOverflow.add(F("Not read yet"));
return;
}
if (_lastStatus != 0)
{
jsonCurrent.add(F("An error occurred"));
jsonVoltage.add(F("An error occurred"));
jsonPower.add(F("An error occurred"));
jsonShuntVoltage.add(F("An error occurred"));
jsonOverflow.add(F("An error occurred"));
return;
}
jsonCurrent.add(_lastCurrent);
jsonCurrent.add(F("A"));
jsonVoltage.add(_lastVoltage);
jsonVoltage.add(F("V"));
jsonPower.add(_lastPower);
jsonPower.add(F("W"));
jsonShuntVoltage.add(_lastShuntVoltage);
jsonShuntVoltage.add(F("V"));
jsonOverflow.add(_lastOverflow ? F("true") : F("false"));
}
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject(FPSTR(_name));
top[F("Enabled")] = _settingEnabled;
top[F("I2CAddress")] = static_cast<uint8_t>(_i2cAddress);
top[F("CheckInterval")] = _checkInterval / 1000;
top[F("INASamples")] = _settingInaSamples;
top[F("INAConversionTime")] = _settingInaConversionTimeUs << 2;
top[F("Decimals")] = log10f(_decimalFactor);
top[F("ShuntResistor")] = _shuntResistor;
top[F("CurrentRange")] = _currentRange;
#ifndef WLED_DISABLE_MQTT
top[F("MqttPublish")] = _mqttPublish;
top[F("MqttPublishAlways")] = _mqttPublishAlways;
top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant;
#endif
DEBUG_PRINTLN(F("INA226 config saved."));
}
bool readFromConfig(JsonObject &root) override
{
JsonObject top = root[FPSTR(_name)];
bool configComplete = !top.isNull();
if (!configComplete)
return false;
bool tmpBool;
if (getJsonValue(top[F("Enabled")], tmpBool))
_settingEnabled = tmpBool;
else
configComplete = false;
configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress);
if (getJsonValue(top[F("CheckInterval")], _checkInterval))
{
if (1 <= _checkInterval && _checkInterval <= 600)
_checkInterval *= 1000;
else
_checkInterval = DEFAULT_CHECKINTERVAL;
}
else
configComplete = false;
uint16_t tmpShort;
if (getJsonValue(top[F("INASamples")], tmpShort))
{
// The method below will fix the provided value to a valid one
getAverageEnum(tmpShort);
_settingInaSamples = tmpShort;
}
else
configComplete = false;
if (getJsonValue(top[F("INAConversionTime")], tmpShort))
{
// The method below will fix the provided value to a valid one
getConversionTimeEnum(tmpShort);
_settingInaConversionTimeUs = tmpShort >> 2;
}
else
configComplete = false;
if (getJsonValue(top[F("Decimals")], _decimalFactor))
{
if (0 <= _decimalFactor && _decimalFactor <= 5)
_decimalFactor = pow10f(_decimalFactor);
else
_decimalFactor = 100;
}
else
configComplete = false;
configComplete &= getJsonValue(top[F("ShuntResistor")], _shuntResistor);
configComplete &= getJsonValue(top[F("CurrentRange")], _currentRange);
#ifndef WLED_DISABLE_MQTT
if (getJsonValue(top[F("MqttPublish")], tmpBool))
_mqttPublish = tmpBool;
else
configComplete = false;
if (getJsonValue(top[F("MqttPublishAlways")], tmpBool))
_mqttPublishAlways = tmpBool;
else
configComplete = false;
if (getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool))
_mqttHomeAssistant = tmpBool;
else
configComplete = false;
#endif
if (_initDone)
{
initializeINA226();
#ifndef WLED_DISABLE_MQTT
mqttInitialize();
#endif
}
_initDone = true;
return configComplete;
}
};
const char UsermodINA226::_name[] PROGMEM = "INA226";

View File

@ -183,7 +183,6 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT resul
// These are the input and output vectors. Input vectors receive computed results from FFT.
static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins
static float vImag[samplesFFT] = {0.0f}; // imaginary parts
static float windowWeighingFactors[samplesFFT] = {0.0f};
// Create FFT object
// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2
@ -196,7 +195,8 @@ static float windowWeighingFactors[samplesFFT] = {0.0f};
#include <arduinoFFT.h>
static ArduinoFFT<float> FFT = ArduinoFFT<float>( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors);
/* Create FFT object with weighing factor storage */
static ArduinoFFT<float> FFT = ArduinoFFT<float>( vReal, vImag, samplesFFT, SAMPLE_RATE, true);
// Helper functions
@ -282,6 +282,7 @@ void FFTcode(void * parameter)
//FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection
FFT.compute( FFTDirection::Forward ); // Compute FFT
FFT.complexToMagnitude(); // Compute magnitudes
vReal[0] = 0; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues.
FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant
FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
@ -1121,6 +1122,11 @@ class AudioReactive : public Usermod {
delay(100); // Give that poor microphone some time to setup.
useBandPassFilter = false;
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
if ((i2sckPin == I2S_PIN_NO_CHANGE) && (i2ssdPin >= 0) && (i2swsPin >= 0) && ((dmType == 1) || (dmType == 4)) ) dmType = 5; // dummy user support: SCK == -1 --means--> PDM microphone
#endif
switch (dmType) {
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
// stub cases for not-yet-supported I2S modes on other ESP32 chips

View File

@ -71,7 +71,7 @@
* if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case.
*/
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 4))
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 6))
// espressif bug: only_left has no sound, left and right are swapped
// https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138)
// https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918)
@ -770,4 +770,4 @@ class SPH0654 : public I2SSource {
#endif
}
};
#endif
#endif

View File

@ -27,18 +27,11 @@ Currently ESP8266 is not supported, due to low speed and small RAM of this chip.
There are however plans to create a lightweight audioreactive for the 8266, with reduced features.
## Installation
### using customised _arduinoFFT_ library for use with this usermod
Add `-D USERMOD_AUDIOREACTIVE` to your PlatformIO environment `build_flags`, as well as `https://github.com/blazoncek/arduinoFFT.git` to your `lib_deps`.
If you are not using PlatformIO (which you should) try adding `#define USERMOD_AUDIOREACTIVE` to *my_config.h* and make sure you have _arduinoFFT_ library downloaded and installed.
### using latest _arduinoFFT_ library version 2.x
The latest arduinoFFT release version should be used for audioreactive.
Customised _arduinoFFT_ library for use with this usermod can be found at https://github.com/blazoncek/arduinoFFT.git
### using latest (develop) _arduinoFFT_ library
Alternatively, you can use the latest arduinoFFT development version.
ArduinoFFT `develop` library is slightly more accurate, and slightly faster than our customised library, however also needs additional 2kB RAM.
* `build_flags` = `-D USERMOD_AUDIOREACTIVE` `-D UM_AUDIOREACTIVE_USE_NEW_FFT`
* `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2`
* `build_flags` = `-D USERMOD_AUDIOREACTIVE`
* `lib_deps`= `kosme/arduinoFFT @ 2.0.1`
## Configuration

View File

@ -667,7 +667,7 @@ void MultiRelay::addToJsonInfo(JsonObject &root) {
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin<0 || !_relay[i].external) continue;
uiDomString = F("Relay "); uiDomString += i;
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
infoArr = user.createNestedArray(uiDomString); // timer value
uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({");
uiDomString += FPSTR(_name);

View File

@ -1135,10 +1135,12 @@ bool FourLineDisplayUsermod::handleButton(uint8_t b) {
return handled;
}
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#ifndef ARDUINO_RUNNING_CORE
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif
#endif
void FourLineDisplayUsermod::onUpdateBegin(bool init) {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)

View File

@ -2506,7 +2506,7 @@ uint16_t ripple_base()
uint16_t cx = rippleorigin >> 8;
uint16_t cy = rippleorigin & 0xFF;
uint8_t mag = scale8(sin8((propF>>2)), amp);
if (propI > 0) SEGMENT.draw_circle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag));
if (propI > 0) SEGMENT.drawCircle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag), true);
} else
#endif
{
@ -3018,8 +3018,12 @@ uint16_t mode_bouncing_balls(void) {
}
int pos = roundf(balls[i].height * (SEGLEN - 1));
#ifdef WLED_USE_AA_PIXELS
if (SEGLEN<32) SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color); // encode virtual strip into index
else SEGMENT.setPixelColor(balls[i].height + (stripNr+1)*10.0f, color);
#else
SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color); // encode virtual strip into index
#endif
}
}
};
@ -3191,23 +3195,30 @@ static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow@!,T
// utility function that will add random glitter to SEGMENT
void glitter_base(uint8_t intensity, uint32_t col = ULTRAWHITE) {
if (intensity > random8()) {
if (SEGMENT.is2D()) {
SEGMENT.setPixelColorXY(random16(SEGMENT.virtualWidth()),random16(SEGMENT.virtualHeight()), col);
} else {
SEGMENT.setPixelColor(random16(SEGLEN), col);
}
}
if (intensity > random8()) SEGMENT.setPixelColor(random16(SEGLEN), col);
}
//Glitter with palette background, inspired by https://gist.github.com/kriegsman/062e10f7f07ba8518af6
uint16_t mode_glitter()
{
if (!SEGMENT.check2) mode_palette(); // use "* Color 1" palette for solid background (replacing "Solid glitter")
if (!SEGMENT.check2) { // use "* Color 1" palette for solid background (replacing "Solid glitter")
unsigned counter = 0;
if (SEGMENT.speed != 0) {
counter = (strip.now * ((SEGMENT.speed >> 3) +1)) & 0xFFFF;
counter = counter >> 8;
}
bool noWrap = (strip.paletteBlend == 2 || (strip.paletteBlend == 0 && SEGMENT.speed == 0));
for (unsigned i = 0; i < SEGLEN; i++) {
unsigned colorIndex = (i * 255 / SEGLEN) - counter;
if (noWrap) colorIndex = map(colorIndex, 0, 255, 0, 240); //cut off blend at palette "end"
SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, true, 255));
}
}
glitter_base(SEGMENT.intensity, SEGCOLOR(2) ? SEGCOLOR(2) : ULTRAWHITE);
return FRAMETIME;
}
static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@!,!,,,,,Overlay;1,2,Glitter color;!;;pal=0,m12=0"; //pixels
static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@!,!,,,,,Overlay;,,Glitter color;!;;pal=0,m12=0"; //pixels
//Solid colour background with glitter (can be replaced by Glitter)
@ -5050,25 +5061,25 @@ uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline
SEGMENT.fill(BLACK);
}
uint16_t xscale = SEGMENT.intensity*4;
uint32_t yscale = SEGMENT.speed*8;
uint8_t indexx = 0;
unsigned xscale = SEGMENT.intensity*4;
unsigned yscale = SEGMENT.speed*8;
unsigned indexx = 0;
SEGPALETTE = CRGBPalette16( CRGB(0,0,0), CRGB(0,0,0), CRGB(0,0,0), CRGB(0,0,0),
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange,
CRGB::DarkOrange,CRGB::DarkOrange, CRGB::Orange, CRGB::Orange,
CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow);
CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : CRGBPalette16(CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black,
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange,
CRGB::DarkOrange,CRGB::DarkOrange, CRGB::Orange, CRGB::Orange,
CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow);
for (int j=0; j < cols; j++) {
for (int i=0; i < rows; i++) {
indexx = inoise8(j*yscale*rows/255, i*xscale+strip.now/4); // We're moving along our Perlin map.
SEGMENT.setPixelColorXY(j, i, ColorFromPalette(SEGPALETTE, min(i*(indexx)>>4, 255), i*255/cols, LINEARBLEND)); // With that value, look up the 8 bit colour palette value and assign it to the current LED.
indexx = inoise8(j*yscale*rows/255, i*xscale+strip.now/4); // We're moving along our Perlin map.
SEGMENT.setPixelColorXY(j, i, ColorFromPalette(pal, min(i*(indexx)>>4, 255U), i*255/cols, LINEARBLEND)); // With that value, look up the 8 bit colour palette value and assign it to the current LED.
} // for i
} // for j
return FRAMETIME;
} // mode_2Dfirenoise()
static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y scale;;!;2";
static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y scale,,,,Palette;;!;2;pal=66";
//////////////////////////////
@ -6052,8 +6063,8 @@ uint16_t mode_2Dfloatingblobs(void) {
}
}
uint32_t c = SEGMENT.color_from_palette(blob->color[i], false, false, 0);
if (blob->r[i] > 1.f) SEGMENT.fill_circle(blob->x[i], blob->y[i], roundf(blob->r[i]), c);
else SEGMENT.setPixelColorXY(blob->x[i], blob->y[i], c);
if (blob->r[i] > 1.f) SEGMENT.fillCircle(roundf(blob->x[i]), roundf(blob->y[i]), roundf(blob->r[i]), c);
else SEGMENT.setPixelColorXY((int)roundf(blob->x[i]), (int)roundf(blob->y[i]), c);
// move x
if (blob->x[i] + blob->r[i] >= cols - 1) blob->x[i] += (blob->sX[i] * ((cols - 1 - blob->x[i]) / blob->r[i] + 0.005f));
else if (blob->x[i] - blob->r[i] <= 0) blob->x[i] += (blob->sX[i] * (blob->x[i] / blob->r[i] + 0.005f));
@ -6639,7 +6650,6 @@ static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall,
// * JUGGLES //
//////////////////////
uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline.
if (SEGLEN == 1) return mode_static();
um_data_t *um_data;
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
// add support for no audio
@ -6651,12 +6661,13 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline.
uint16_t my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0);
for (size_t i=0; i<SEGMENT.intensity/32+1U; i++) {
// if SEGLEN equals 1, we will always set color to the first and only pixel, but the effect is still good looking
SEGMENT.setPixelColor(beatsin16(SEGMENT.speed/4+i*2,0,SEGLEN-1), color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now/4+i*2, false, PALETTE_SOLID_WRAP, 0), my_sampleAgc));
}
return FRAMETIME;
} // mode_juggles()
static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!;1v;m12=0,si=0"; // Pixels, Beatsin
static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!;01v;m12=0,si=0"; // Pixels, Beatsin
//////////////////////
@ -6757,7 +6768,7 @@ uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline.
return FRAMETIME;
} // mode_noisefire()
static const char _data_FX_MODE_NOISEFIRE[] PROGMEM = "Noisefire@!,!;;;1v;m12=2,si=0"; // Circle, Beatsin
static const char _data_FX_MODE_NOISEFIRE[] PROGMEM = "Noisefire@!,!;;;01v;m12=2,si=0"; // Circle, Beatsin
///////////////////////
@ -6867,7 +6878,7 @@ uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline.
return FRAMETIME;
} // mode_plasmoid()
static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels;!,!;!;1v;sx=128,ix=128,m12=0,si=0"; // Pixels, Beatsin
static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels;!,!;!;01v;sx=128,ix=128,m12=0,si=0"; // Pixels, Beatsin
///////////////////////
@ -7022,7 +7033,7 @@ static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color
// ** DJLight //
/////////////////////////
uint16_t mode_DJLight(void) { // Written by ??? Adapted by Will Tatam.
if (SEGLEN == 1) return mode_static();
// No need to prevent from executing on single led strips, only mid will be set (mid = 0)
const int mid = SEGLEN / 2;
um_data_t *um_data;
@ -7043,13 +7054,14 @@ uint16_t mode_DJLight(void) { // Written by ??? Adapted by Wil
CRGB color = CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2); // 16-> 15 as 16 is out of bounds
SEGMENT.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[4], 0, 255, 255, 4))); // TODO - Update
// if SEGLEN equals 1 these loops won't execute
for (int i = SEGLEN - 1; i > mid; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); // move to the left
for (int i = 0; i < mid; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right
}
return FRAMETIME;
} // mode_DJLight()
static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;1f;m12=2,si=0"; // Circle, Beatsin
static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;01f;m12=2,si=0"; // Circle, Beatsin
////////////////////
@ -7093,7 +7105,7 @@ static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting
// ** Freqmatrix //
///////////////////////
uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung.
if (SEGLEN == 1) return mode_static();
// No need to prevent from executing on single led strips, we simply change pixel 0 each time and avoid the shift
um_data_t *um_data;
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
// add support for no audio
@ -7136,12 +7148,13 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch
// shift the pixels one pixel up
SEGMENT.setPixelColor(0, color);
// if SEGLEN equals 1 this loop won't execute
for (int i = SEGLEN - 1; i > 0; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left
}
return FRAMETIME;
} // mode_freqmatrix()
static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensitivity;;;1f;m12=3,si=0"; // Corner, Beatsin
static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensitivity;;;01f;m12=3,si=0"; // Corner, Beatsin
//////////////////////
@ -7198,7 +7211,7 @@ static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Sta
// As a compromise between speed and accuracy we are currently sampling with 10240Hz, from which we can then determine with a 512bin FFT our max frequency is 5120Hz.
// Depending on the music stream you have you might find it useful to change the frequency mapping.
uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung.
if (SEGLEN == 1) return mode_static();
// As before, this effect can also work on single pixels, we just lose the shifting effect
um_data_t *um_data;
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
// add support for no audio
@ -7242,13 +7255,14 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun
SEGMENT.setPixelColor(SEGLEN/2, color);
// shift the pixels one pixel outwards
// if SEGLEN equals 1 these loops won't execute
for (int i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left
for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right
}
return FRAMETIME;
} // mode_freqwave()
static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Speed,Sound effect,Low bin,High bin,Pre-amp;;;1f;m12=2,si=0"; // Circle, Beatsin
static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Speed,Sound effect,Low bin,High bin,Pre-amp;;;01f;m12=2,si=0"; // Circle, Beatsin
///////////////////////
@ -7307,7 +7321,6 @@ static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sens
// ** Noisemove //
//////////////////////
uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuline
if (SEGLEN == 1) return mode_static();
um_data_t *um_data;
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
// add support for no audio
@ -7321,20 +7334,20 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli
uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins.
for (int i=0; i<numBins; i++) { // How many active bins are we using.
uint16_t locn = inoise16(strip.now*SEGMENT.speed+i*50000, strip.now*SEGMENT.speed); // Get a new pixel location from moving noise.
// if SEGLEN equals 1 locn will be always 0, hence we set the first pixel only
locn = map(locn, 7500, 58000, 0, SEGLEN-1); // Map that to the length of the strand, and ensure we don't go over.
SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i*64, false, PALETTE_SOLID_WRAP, 0), fftResult[i % 16]*4));
}
return FRAMETIME;
} // mode_noisemove()
static const char _data_FX_MODE_NOISEMOVE[] PROGMEM = "Noisemove@Speed of perlin movement,Fade rate;!,!;!;1f;m12=0,si=0"; // Pixels, Beatsin
static const char _data_FX_MODE_NOISEMOVE[] PROGMEM = "Noisemove@Speed of perlin movement,Fade rate;!,!;!;01f;m12=0,si=0"; // Pixels, Beatsin
//////////////////////
// ** Rocktaves //
//////////////////////
uint16_t mode_rocktaves(void) { // Rocktaves. Same note from each octave is same colour. By: Andrew Tuline
if (SEGLEN == 1) return mode_static();
um_data_t *um_data;
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
// add support for no audio
@ -7362,12 +7375,13 @@ uint16_t mode_rocktaves(void) { // Rocktaves. Same note from eac
frTemp = fabsf(frTemp * 2.1f); // Fudge factors to compress octave range starting at 0 and going to 255;
uint16_t i = map(beatsin8(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1);
// i will be always constrained between 0 and 0 if SEGLEN equals 1
i = constrain(i, 0, SEGLEN-1);
SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint8_t)frTemp, false, PALETTE_SOLID_WRAP, 0), volTemp));
return FRAMETIME;
} // mode_rocktaves()
static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;1f;m12=1,si=0"; // Bar, Beatsin
static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;01f;m12=1,si=0"; // Bar, Beatsin
///////////////////////
@ -7375,8 +7389,8 @@ static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;1f;m12=1
///////////////////////
// Combines peak detection with FFT_MajorPeak and FFT_Magnitude.
uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline
if (SEGLEN == 1) return mode_static();
// effect can work on single pixels, we just lose the shifting effect
um_data_t *um_data;
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
// add support for no audio
@ -7413,12 +7427,13 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin
} else {
SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude));
}
// loop will not execute if SEGLEN equals 1
for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left
}
return FRAMETIME;
} // mode_waterfall()
static const char _data_FX_MODE_WATERFALL[] PROGMEM = "Waterfall@!,Adjust color,Select bin,Volume (min);!,!;!;1f;c2=0,m12=2,si=0"; // Circles, Beatsin
static const char _data_FX_MODE_WATERFALL[] PROGMEM = "Waterfall@!,Adjust color,Select bin,Volume (min);!,!;!;01f;c2=0,m12=2,si=0"; // Circles, Beatsin
#ifndef WLED_DISABLE_2D

View File

@ -90,7 +90,7 @@
//#define SEGCOLOR(x) strip._segments[strip.getCurrSegmentId()].currentColor(x, strip._segments[strip.getCurrSegmentId()].colors[x])
//#define SEGLEN strip._segments[strip.getCurrSegmentId()].virtualLength()
#define SEGCOLOR(x) strip.segColor(x) /* saves us a few kbytes of code */
#define SEGPALETTE strip._currentPalette
#define SEGPALETTE Segment::getCurrentPalette()
#define SEGLEN strip._virtualSegmentLength /* saves us a few kbytes of code */
#define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN)
@ -106,6 +106,10 @@
#define PURPLE (uint32_t)0x400080
#define ORANGE (uint32_t)0xFF3000
#define PINK (uint32_t)0xFF1493
#define GREY (uint32_t)0x808080
#define GRAY GREY
#define DARKGREY (uint32_t)0x333333
#define DARKGRAY DARKGREY
#define ULTRAWHITE (uint32_t)0xFFFFFFFF
#define DARKSLATEGRAY (uint32_t)0x2F4F4F
#define DARKSLATEGREY DARKSLATEGRAY
@ -320,7 +324,8 @@ typedef enum mapping1D2D {
M12_Pixels = 0,
M12_pBar = 1,
M12_pArc = 2,
M12_pCorner = 3
M12_pCorner = 3,
M12_sPinwheel = 4
} mapping1D2D_t;
// segment, 80 bytes
@ -413,6 +418,7 @@ typedef struct Segment {
static uint16_t _usedSegmentData;
// perhaps this should be per segment, not static
static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette())
static CRGBPalette16 _randomPalette; // actual random palette
static CRGBPalette16 _newRandomPalette; // target random palette
static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000
@ -530,6 +536,7 @@ typedef struct Segment {
static void modeBlend(bool blend) { _modeBlend = blend; }
#endif
static void handleRandomPalette();
inline static const CRGBPalette16 &getCurrentPalette(void) { return Segment::_currentPalette; }
void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1);
bool setColor(uint8_t slot, uint32_t c); //returns true if changed
@ -567,7 +574,7 @@ typedef struct Segment {
uint8_t currentMode(void); // currently active effect/mode (while in transition)
uint32_t currentColor(uint8_t slot); // currently active segment color (blended while in transition)
CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal);
CRGBPalette16 &currentPalette(CRGBPalette16 &tgt, uint8_t paletteID);
void setCurrentPalette(void);
// 1D strip
uint16_t virtualLength(void) const;
@ -575,12 +582,14 @@ typedef struct Segment {
inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); }
inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); }
#ifdef WLED_USE_AA_PIXELS
void setPixelColor(float i, uint32_t c, bool aa = true);
inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); }
inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); }
#endif
uint32_t getPixelColor(int i);
// 1D support functions (some implement 2D as well)
void blur(uint8_t);
void blur(uint8_t, bool smear = false);
void fill(uint32_t c);
void fade_out(uint8_t r);
void fadeToBlackBy(uint8_t fadeBy);
@ -603,10 +612,13 @@ typedef struct Segment {
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); }
#ifdef WLED_USE_AA_PIXELS
void setPixelColorXY(float x, float y, uint32_t c, bool aa = true);
inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); }
inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); }
uint32_t getPixelColorXY(uint16_t x, uint16_t y);
#endif
uint32_t getPixelColorXY(int x, int y);
// 2D support functions
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); }
@ -615,32 +627,35 @@ typedef struct Segment {
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); }
void box_blur(uint16_t i, bool vertical, fract8 blur_amount); // 1D box blur (with weight)
void blurRow(uint16_t row, fract8 blur_amount);
void blurCol(uint16_t col, fract8 blur_amount);
void blurRow(uint32_t row, fract8 blur_amount, bool smear = false);
void blurCol(uint32_t col, fract8 blur_amount, bool smear = false);
void moveX(int8_t delta, bool wrap = false);
void moveY(int8_t delta, bool wrap = false);
void move(uint8_t dir, uint8_t delta, bool wrap = false);
void draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c);
void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c);
void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c);
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0)); } // automatic inline
void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false);
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false);
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false);
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline
void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0);
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline
void wu_pixel(uint32_t x, uint32_t y, CRGB c);
void blur1d(fract8 blur_amount); // blur all rows in 1 dimension
inline void blur2d(fract8 blur_amount) { blur(blur_amount); }
inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); }
void nscale8(uint8_t scale);
#else
inline uint16_t XY(uint16_t x, uint16_t y) { return x; }
inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); }
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); }
inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColor(int(x), RGBW32(c.r,c.g,c.b,0)); }
#ifdef WLED_USE_AA_PIXELS
inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); }
inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); }
inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); }
#endif
inline uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(x); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); }
@ -649,14 +664,17 @@ typedef struct Segment {
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); }
inline void box_blur(uint16_t i, bool vertical, fract8 blur_amount) {}
inline void blurRow(uint16_t row, fract8 blur_amount) {}
inline void blurCol(uint16_t col, fract8 blur_amount) {}
inline void blurRow(uint32_t row, fract8 blur_amount, bool smear = false) {}
inline void blurCol(uint32_t col, fract8 blur_amount, bool smear = false) {}
inline void moveX(int8_t delta, bool wrap = false) {}
inline void moveY(int8_t delta, bool wrap = false) {}
inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {}
inline void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) {}
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {}
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {}
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {}
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {}
@ -691,7 +709,6 @@ class WS2812FX { // 96 bytes
panels(1),
#endif
// semi-private (just obscured) used in effect functions through macros
_currentPalette(CRGBPalette16(CRGB::Black)),
_colors_t{0,0,0},
_virtualSegmentLength(0),
// true private variables
@ -886,7 +903,6 @@ class WS2812FX { // 96 bytes
// end 2D support
void loadCustomPalettes(void); // loads custom palettes from JSON
CRGBPalette16 _currentPalette; // palette used for current effect (includes transition)
std::vector<CRGBPalette16> customPalettes; // TODO: move custom palettes out of WS2812FX class
// using public variables to reduce code size increase due to inline function getSegment() (with bounds checking)

View File

@ -110,11 +110,11 @@ void WS2812FX::setUpMatrix() {
releaseJSONBufferLock();
}
uint16_t x, y, pix=0; //pixel
unsigned x, y, pix=0; //pixel
for (size_t pan = 0; pan < panel.size(); pan++) {
Panel &p = panel[pan];
uint16_t h = p.vertical ? p.height : p.width;
uint16_t v = p.vertical ? p.width : p.height;
unsigned h = p.vertical ? p.height : p.width;
unsigned v = p.vertical ? p.width : p.height;
for (size_t j = 0; j < v; j++){
for(size_t i = 0; i < h; i++) {
y = (p.vertical?p.rightStart:p.bottomStart) ? v-j-1 : j;
@ -163,8 +163,8 @@ void WS2812FX::setUpMatrix() {
// XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element)
uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y)
{
uint16_t width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
uint16_t height = virtualHeight(); // segment height in logical pixels (is always >= 1)
unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1)
return isActive() ? (x%width) + (y%height) * width : 0;
}
@ -175,16 +175,12 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col)
uint8_t _bri_t = currentBri();
if (_bri_t < 255) {
byte r = scale8(R(col), _bri_t);
byte g = scale8(G(col), _bri_t);
byte b = scale8(B(col), _bri_t);
byte w = scale8(W(col), _bri_t);
col = RGBW32(r, g, b, w);
col = color_fade(col, _bri_t);
}
if (reverse ) x = virtualWidth() - x - 1;
if (reverse_y) y = virtualHeight() - y - 1;
if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed
if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed
x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels
@ -193,7 +189,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col)
uint32_t tmpCol = col;
for (int j = 0; j < grouping; j++) { // groupping vertically
for (int g = 0; g < grouping; g++) { // groupping horizontally
uint16_t xX = (x+g), yY = (y+j);
unsigned xX = (x+g), yY = (y+j);
if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end
#ifndef WLED_DISABLE_MODE_BLEND
@ -218,22 +214,23 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col)
}
}
#ifdef WLED_USE_AA_PIXELS
// anti-aliased version of setPixelColorXY()
void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
{
if (!isActive()) return; // not active
if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
float fX = x * (cols-1);
float fY = y * (rows-1);
if (aa) {
uint16_t xL = roundf(fX-0.49f);
uint16_t xR = roundf(fX+0.49f);
uint16_t yT = roundf(fY-0.49f);
uint16_t yB = roundf(fY+0.49f);
unsigned xL = roundf(fX-0.49f);
unsigned xR = roundf(fX+0.49f);
unsigned yT = roundf(fY-0.49f);
unsigned yB = roundf(fY+0.49f);
float dL = (fX - xL)*(fX - xL);
float dR = (xR - fX)*(xR - fX);
float dT = (fY - yT)*(fY - yT);
@ -261,14 +258,15 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col);
}
}
#endif
// returns RGBW values of pixel
uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) {
uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) {
if (!isActive()) return 0; // not active
if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit
if (reverse ) x = virtualWidth() - x - 1;
if (reverse_y) y = virtualHeight() - y - 1;
if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed
if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed
x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels
if (x >= width() || y >= height()) return 0;
@ -276,119 +274,110 @@ uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) {
}
// blurRow: perform a blur on a row of a rectangular matrix
void Segment::blurRow(uint16_t row, fract8 blur_amount) {
void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){
if (!isActive() || blur_amount == 0) return; // not active
const uint_fast16_t cols = virtualWidth();
const uint_fast16_t rows = virtualHeight();
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
if (row >= rows) return;
// blur one row
uint8_t keep = 255 - blur_amount;
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
CRGB carryover = CRGB::Black;
uint32_t carryover = BLACK;
uint32_t lastnew;
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned x = 0; x < cols; x++) {
CRGB cur = getPixelColorXY(x, row);
CRGB before = cur; // remember color before blur
CRGB part = cur;
part.nscale8(seep);
cur.nscale8(keep);
cur += carryover;
if (x>0) {
CRGB prev = CRGB(getPixelColorXY(x-1, row)) + part;
setPixelColorXY(x-1, row, prev);
}
if (before != cur) // optimization: only set pixel if color has changed
setPixelColorXY(x, row, cur);
uint32_t cur = getPixelColorXY(x, row);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (x > 0) {
if (carryover)
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed
setPixelColorXY(x - 1, row, prev);
} else // first pixel
setPixelColorXY(x, row, curnew);
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(cols-1, row, curnew); // set last pixel
}
// blurCol: perform a blur on a column of a rectangular matrix
void Segment::blurCol(uint16_t col, fract8 blur_amount) {
void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // not active
const uint_fast16_t cols = virtualWidth();
const uint_fast16_t rows = virtualHeight();
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
if (col >= cols) return;
// blur one column
uint8_t keep = 255 - blur_amount;
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
CRGB carryover = CRGB::Black;
uint32_t carryover = BLACK;
uint32_t lastnew;
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) {
CRGB cur = getPixelColorXY(col, y);
CRGB part = cur;
CRGB before = cur; // remember color before blur
part.nscale8(seep);
cur.nscale8(keep);
cur += carryover;
if (y>0) {
CRGB prev = CRGB(getPixelColorXY(col, y-1)) + part;
setPixelColorXY(col, y-1, prev);
}
if (before != cur) // optimization: only set pixel if color has changed
setPixelColorXY(col, y, cur);
carryover = part;
uint32_t cur = getPixelColorXY(col, y);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (y > 0) {
if (carryover)
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed
setPixelColorXY(col, y - 1, prev);
} else // first pixel
setPixelColorXY(col, y, curnew);
lastnew = curnew;
last = cur; //save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(col, rows - 1, curnew);
}
// 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur])
void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) {
if (!isActive() || blur_amount == 0) return; // not active
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
const uint16_t dim1 = vertical ? rows : cols;
const uint16_t dim2 = vertical ? cols : rows;
const int cols = virtualWidth();
const int rows = virtualHeight();
const int dim1 = vertical ? rows : cols;
const int dim2 = vertical ? cols : rows;
if (i >= dim2) return;
const float seep = blur_amount/255.f;
const float keep = 3.f - 2.f*seep;
// 1D box blur
CRGB tmp[dim1];
uint32_t out[dim1], in[dim1];
for (int j = 0; j < dim1; j++) {
uint16_t x = vertical ? i : j;
uint16_t y = vertical ? j : i;
int16_t xp = vertical ? x : x-1; // "signed" to prevent underflow
int16_t yp = vertical ? y-1 : y; // "signed" to prevent underflow
uint16_t xn = vertical ? x : x+1;
uint16_t yn = vertical ? y+1 : y;
CRGB curr = getPixelColorXY(x,y);
CRGB prev = (xp<0 || yp<0) ? CRGB::Black : getPixelColorXY(xp,yp);
CRGB next = ((vertical && yn>=dim1) || (!vertical && xn>=dim1)) ? CRGB::Black : getPixelColorXY(xn,yn);
uint16_t r, g, b;
r = (curr.r*keep + (prev.r + next.r)*seep) / 3;
g = (curr.g*keep + (prev.g + next.g)*seep) / 3;
b = (curr.b*keep + (prev.b + next.b)*seep) / 3;
tmp[j] = CRGB(r,g,b);
int x = vertical ? i : j;
int y = vertical ? j : i;
in[j] = getPixelColorXY(x, y);
}
for (int j = 0; j < dim1; j++) {
uint16_t x = vertical ? i : j;
uint16_t y = vertical ? j : i;
setPixelColorXY(x, y, tmp[j]);
uint32_t curr = in[j];
uint32_t prev = j > 0 ? in[j-1] : BLACK;
uint32_t next = j < dim1-1 ? in[j+1] : BLACK;
uint8_t r, g, b, w;
r = (R(curr)*keep + (R(prev) + R(next))*seep) / 3;
g = (G(curr)*keep + (G(prev) + G(next))*seep) / 3;
b = (B(curr)*keep + (B(prev) + B(next))*seep) / 3;
w = (W(curr)*keep + (W(prev) + W(next))*seep) / 3;
out[j] = RGBW32(r,g,b,w);
}
for (int j = 0; j < dim1; j++) {
int x = vertical ? i : j;
int y = vertical ? j : i;
setPixelColorXY(x, y, out[j]);
}
}
// blur1d: one-dimensional blur filter. Spreads light to 2 line neighbors.
// blur2d: two-dimensional blur filter. Spreads light to 8 XY neighbors.
//
// 0 = no spread at all
// 64 = moderate spreading
// 172 = maximum smooth, even spreading
//
// 173..255 = wider spreading, but increasing flicker
//
// Total light is NOT entirely conserved, so many repeated
// calls to 'blur' will also result in the light fading,
// eventually all the way to black; this is by design so that
// it can be used to (slowly) clear the LEDs to black.
void Segment::blur1d(fract8 blur_amount) {
const uint16_t rows = virtualHeight();
for (unsigned y = 0; y < rows; y++) blurRow(y, blur_amount);
}
void Segment::moveX(int8_t delta, bool wrap) {
if (!isActive()) return; // not active
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
const int cols = virtualWidth();
const int rows = virtualHeight();
if (!delta || abs(delta) >= cols) return;
uint32_t newPxCol[cols];
for (int y = 0; y < rows; y++) {
@ -405,8 +394,8 @@ void Segment::moveX(int8_t delta, bool wrap) {
void Segment::moveY(int8_t delta, bool wrap) {
if (!isActive()) return; // not active
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
const int cols = virtualWidth();
const int rows = virtualHeight();
if (!delta || abs(delta) >= rows) return;
uint32_t newPxCol[rows];
for (int x = 0; x < cols; x++) {
@ -439,69 +428,131 @@ void Segment::move(uint8_t dir, uint8_t delta, bool wrap) {
}
}
void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) {
if (!isActive() || radius == 0) return; // not active
// Bresenhams Algorithm
int d = 3 - (2*radius);
int y = radius, x = 0;
while (y >= x) {
setPixelColorXY(cx+x, cy+y, col);
setPixelColorXY(cx-x, cy+y, col);
setPixelColorXY(cx+x, cy-y, col);
setPixelColorXY(cx-x, cy-y, col);
setPixelColorXY(cx+y, cy+x, col);
setPixelColorXY(cx-y, cy+x, col);
setPixelColorXY(cx+y, cy-x, col);
setPixelColorXY(cx-y, cy-x, col);
x++;
if (d > 0) {
y--;
d += 4 * (x - y) + 10;
} else {
d += 4 * x + 6;
if (soft) {
// Xiaolin Wus algorithm
int rsq = radius*radius;
int x = 0;
int y = radius;
unsigned oldFade = 0;
while (x < y) {
float yf = sqrtf(float(rsq - x*x)); // needs to be floating point
unsigned fade = float(0xFFFF) * (ceilf(yf) - yf); // how much color to keep
if (oldFade > fade) y--;
oldFade = fade;
setPixelColorXY(cx+x, cy+y, color_blend(col, getPixelColorXY(cx+x, cy+y), fade, true));
setPixelColorXY(cx-x, cy+y, color_blend(col, getPixelColorXY(cx-x, cy+y), fade, true));
setPixelColorXY(cx+x, cy-y, color_blend(col, getPixelColorXY(cx+x, cy-y), fade, true));
setPixelColorXY(cx-x, cy-y, color_blend(col, getPixelColorXY(cx-x, cy-y), fade, true));
setPixelColorXY(cx+y, cy+x, color_blend(col, getPixelColorXY(cx+y, cy+x), fade, true));
setPixelColorXY(cx-y, cy+x, color_blend(col, getPixelColorXY(cx-y, cy+x), fade, true));
setPixelColorXY(cx+y, cy-x, color_blend(col, getPixelColorXY(cx+y, cy-x), fade, true));
setPixelColorXY(cx-y, cy-x, color_blend(col, getPixelColorXY(cx-y, cy-x), fade, true));
setPixelColorXY(cx+x, cy+y-1, color_blend(getPixelColorXY(cx+x, cy+y-1), col, fade, true));
setPixelColorXY(cx-x, cy+y-1, color_blend(getPixelColorXY(cx-x, cy+y-1), col, fade, true));
setPixelColorXY(cx+x, cy-y+1, color_blend(getPixelColorXY(cx+x, cy-y+1), col, fade, true));
setPixelColorXY(cx-x, cy-y+1, color_blend(getPixelColorXY(cx-x, cy-y+1), col, fade, true));
setPixelColorXY(cx+y-1, cy+x, color_blend(getPixelColorXY(cx+y-1, cy+x), col, fade, true));
setPixelColorXY(cx-y+1, cy+x, color_blend(getPixelColorXY(cx-y+1, cy+x), col, fade, true));
setPixelColorXY(cx+y-1, cy-x, color_blend(getPixelColorXY(cx+y-1, cy-x), col, fade, true));
setPixelColorXY(cx-y+1, cy-x, color_blend(getPixelColorXY(cx-y+1, cy-x), col, fade, true));
x++;
}
} else {
// Bresenhams Algorithm
int d = 3 - (2*radius);
int y = radius, x = 0;
while (y >= x) {
setPixelColorXY(cx+x, cy+y, col);
setPixelColorXY(cx-x, cy+y, col);
setPixelColorXY(cx+x, cy-y, col);
setPixelColorXY(cx-x, cy-y, col);
setPixelColorXY(cx+y, cy+x, col);
setPixelColorXY(cx-y, cy+x, col);
setPixelColorXY(cx+y, cy-x, col);
setPixelColorXY(cx-y, cy-x, col);
x++;
if (d > 0) {
y--;
d += 4 * (x - y) + 10;
} else {
d += 4 * x + 6;
}
}
}
}
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) {
if (!isActive() || radius == 0) return; // not active
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
for (int16_t y = -radius; y <= radius; y++) {
for (int16_t x = -radius; x <= radius; x++) {
// draw soft bounding circle
if (soft) drawCircle(cx, cy, radius, col, soft);
// fill it
const int cols = virtualWidth();
const int rows = virtualHeight();
for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) {
if (x * x + y * y <= radius * radius &&
int16_t(cx)+x>=0 && int16_t(cy)+y>=0 &&
int16_t(cx)+x<cols && int16_t(cy)+y<rows)
int(cx)+x>=0 && int(cy)+y>=0 &&
int(cx)+x<cols && int(cy)+y<rows)
setPixelColorXY(cx + x, cy + y, col);
}
}
}
void Segment::nscale8(uint8_t scale) {
if (!isActive()) return; // not active
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
setPixelColorXY(x, y, CRGB(getPixelColorXY(x, y)).nscale8(scale));
}
}
//line function
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) {
if (!isActive()) return; // not active
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
const int cols = virtualWidth();
const int rows = virtualHeight();
if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return;
const int16_t dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
const int16_t dy = abs(y1-y0), sy = y0<y1 ? 1 : -1;
int16_t err = (dx>dy ? dx : -dy)/2, e2;
for (;;) {
setPixelColorXY(x0,y0,c);
if (x0==x1 && y0==y1) break;
e2 = err;
if (e2 >-dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; // x distance & step
const int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; // y distance & step
// single pixel (line length == 0)
if (dx+dy == 0) {
setPixelColorXY(x0, y0, c);
return;
}
if (soft) {
// Xiaolin Wus algorithm
const bool steep = dy > dx;
if (steep) {
// we need to go along longest dimension
std::swap(x0,y0);
std::swap(x1,y1);
}
if (x0 > x1) {
// we need to go in increasing fashion
std::swap(x0,x1);
std::swap(y0,y1);
}
float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0);
float intersectY = y0;
for (int x = x0; x <= x1; x++) {
unsigned keep = float(0xFFFF) * (intersectY-int(intersectY)); // how much color to keep
unsigned seep = 0xFFFF - keep; // how much background to keep
int y = int(intersectY);
if (steep) std::swap(x,y); // temporaryly swap if steep
// pixel coverage is determined by fractional part of y co-ordinate
setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true));
setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true));
intersectY += gradient;
if (steep) std::swap(x,y); // restore if steep
}
} else {
// Bresenham's algorithm
int err = (dx>dy ? dx : -dy)/2; // error direction
for (;;) {
setPixelColorXY(x0, y0, c);
if (x0==x1 && y0==y1) break;
int e2 = err;
if (e2 >-dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
}
}
@ -517,8 +568,8 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
if (!isActive()) return; // not active
if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
chr -= 32; // align with font table entries
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
const int cols = virtualWidth();
const int rows = virtualHeight();
const int font = w*h;
CRGB col = CRGB(color);
@ -557,7 +608,7 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu
if (!isActive()) return; // not active
// extract the fractional parts and derive their inverses
uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
// calculate the intensities for each affected pixel
uint8_t wu[4] = {WU_WEIGHT(ix, iy), WU_WEIGHT(xx, iy),
WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)};

View File

@ -77,6 +77,7 @@ uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for t
uint16_t Segment::maxWidth = DEFAULT_LED_COUNT;
uint16_t Segment::maxHeight = 1;
CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black);
CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment
@ -203,7 +204,7 @@ void Segment::resetIfRequired() {
CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0;
if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0;
if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip
//default palette. Differs depending on effect
if (pal == 0) switch (mode) {
case FX_MODE_FIRE_2012 : pal = 35; break; // heat palette
@ -327,15 +328,15 @@ void Segment::stopTransition() {
}
void Segment::handleTransition() {
uint16_t _progress = progress();
unsigned _progress = progress();
if (_progress == 0xFFFFU) stopTransition();
}
// transition progression between 0-65535
uint16_t IRAM_ATTR Segment::progress() {
if (isInTransition()) {
unsigned long timeNow = millis();
if (_t->_dur > 0 && timeNow - _t->_start < _t->_dur) return (timeNow - _t->_start) * 0xFFFFU / _t->_dur;
unsigned diff = millis() - _t->_start;
if (_t->_dur > 0 && diff < _t->_dur) return diff * 0xFFFFU / _t->_dur;
}
return 0xFFFFU;
}
@ -412,9 +413,9 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) {
#endif
uint8_t IRAM_ATTR Segment::currentBri(bool useCct) {
uint32_t prog = progress();
unsigned prog = progress();
if (prog < 0xFFFFU) {
uint32_t curBri = (useCct ? cct : (on ? opacity : 0)) * prog;
unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog;
curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog);
return curBri / 0xFFFFU;
}
@ -423,7 +424,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) {
uint8_t IRAM_ATTR Segment::currentMode() {
#ifndef WLED_DISABLE_MODE_BLEND
uint16_t prog = progress();
unsigned prog = progress();
if (modeBlending && prog < 0xFFFFU) return _t->_modeT;
#endif
return mode;
@ -438,18 +439,17 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) {
#endif
}
CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
loadPalette(targetPalette, pal);
uint16_t prog = progress();
void Segment::setCurrentPalette() {
loadPalette(_currentPalette, palette);
unsigned prog = progress();
if (strip.paletteFade && prog < 0xFFFFU) {
// blend palettes
// there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time)
// minimum blend time is 100ms maximum is 65535ms
uint16_t noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
for (int i=0; i<noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, targetPalette, 48);
targetPalette = _t->_palT; // copy transitioning/temporary palette
unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48);
_currentPalette = _t->_palT; // copy transitioning/temporary palette
}
return targetPalette;
}
// relies on WS2812FX::service() to call it for each frame
@ -576,7 +576,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) {
mode = fx;
// load default values from effect string
if (loadDefaults) {
int16_t sOpt;
int sOpt;
sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED;
sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY;
sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1;
@ -610,21 +610,21 @@ void Segment::setPalette(uint8_t pal) {
// 2D matrix
uint16_t IRAM_ATTR Segment::virtualWidth() const {
uint16_t groupLen = groupLength();
uint16_t vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen;
unsigned groupLen = groupLength();
unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen;
if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED
return vWidth;
}
uint16_t IRAM_ATTR Segment::virtualHeight() const {
uint16_t groupLen = groupLength();
uint16_t vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen;
unsigned groupLen = groupLength();
unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen;
if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED
return vHeight;
}
uint16_t IRAM_ATTR Segment::nrOfVStrips() const {
uint16_t vLen = 1;
unsigned vLen = 1;
#ifndef WLED_DISABLE_2D
if (is2D()) {
switch (map1D2D) {
@ -637,13 +637,49 @@ uint16_t IRAM_ATTR Segment::nrOfVStrips() const {
return vLen;
}
// Constants for mapping mode "Pinwheel"
#ifndef WLED_DISABLE_2D
constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16
constexpr int Pinwheel_Size_Small = 16; // larger than this -> use "Medium"
constexpr int Pinwheel_Steps_Medium = 192; // no holes up to 32x32
constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big"
constexpr int Pinwheel_Steps_Big = 304; // no holes up to 50x50
constexpr int Pinwheel_Size_Big = 50; // larger than this -> use "XL"
constexpr int Pinwheel_Steps_XL = 368;
constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360) / Pinwheel_Steps_Small; // conversion: from 0...72 to Radians
constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...192 to Radians
constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...304 to Radians
constexpr float Int_to_Rad_XL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XL; // conversion: from 0...368 to Radians
constexpr int Fixed_Scale = 512; // fixpoint scaling factor (9bit for fraction)
// Pinwheel helper function: pixel index to radians
static float getPinwheelAngle(int i, int vW, int vH) {
int maxXY = max(vW, vH);
if (maxXY <= Pinwheel_Size_Small) return float(i) * Int_to_Rad_Small;
if (maxXY <= Pinwheel_Size_Medium) return float(i) * Int_to_Rad_Med;
if (maxXY <= Pinwheel_Size_Big) return float(i) * Int_to_Rad_Big;
// else
return float(i) * Int_to_Rad_XL;
}
// Pinwheel helper function: matrix dimensions to number of rays
static int getPinwheelLength(int vW, int vH) {
int maxXY = max(vW, vH);
if (maxXY <= Pinwheel_Size_Small) return Pinwheel_Steps_Small;
if (maxXY <= Pinwheel_Size_Medium) return Pinwheel_Steps_Medium;
if (maxXY <= Pinwheel_Size_Big) return Pinwheel_Steps_Big;
// else
return Pinwheel_Steps_XL;
}
#endif
// 1D strip
uint16_t IRAM_ATTR Segment::virtualLength() const {
#ifndef WLED_DISABLE_2D
if (is2D()) {
uint16_t vW = virtualWidth();
uint16_t vH = virtualHeight();
uint16_t vLen = vW * vH; // use all pixels from segment
unsigned vW = virtualWidth();
unsigned vH = virtualHeight();
unsigned vLen = vW * vH; // use all pixels from segment
switch (map1D2D) {
case M12_pBar:
vLen = vH;
@ -652,12 +688,15 @@ uint16_t IRAM_ATTR Segment::virtualLength() const {
case M12_pArc:
vLen = max(vW,vH); // get the longest dimension
break;
case M12_sPinwheel:
vLen = getPinwheelLength(vW, vH);
break;
}
return vLen;
}
#endif
uint16_t groupLen = groupLength(); // is always >= 1
uint16_t vLength = (length() + groupLen - 1) / groupLen;
unsigned groupLen = groupLength(); // is always >= 1
unsigned vLength = (length() + groupLen - 1) / groupLen;
if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED
return vLength;
}
@ -674,8 +713,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col)
#ifndef WLED_DISABLE_2D
if (is2D()) {
uint16_t vH = virtualHeight(); // segment height in logical pixels
uint16_t vW = virtualWidth();
int vH = virtualHeight(); // segment height in logical pixels
int vW = virtualWidth();
switch (map1D2D) {
case M12_Pixels:
// use all available pixels as a long strip
@ -718,6 +757,52 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col)
for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col);
for (int y = 0; y < i; y++) setPixelColorXY(i, y, col);
break;
case M12_sPinwheel: {
// i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small)
float centerX = roundf((vW-1) / 2.0f);
float centerY = roundf((vH-1) / 2.0f);
float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians
float cosVal = cos_t(angleRad);
float sinVal = sin_t(angleRad);
// avoid re-painting the same pixel
int lastX = INT_MIN; // impossible position
int lastY = INT_MIN; // impossible position
// draw line at angle, starting at center and ending at the segment edge
// we use fixed point math for better speed. Starting distance is 0.5 for better rounding
// int_fast16_t and int_fast32_t types changed to int, minimum bits commented
int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit
int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit
int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit
int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit
int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint
int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint
// Odd rays start further from center if prevRay started at center.
static int prevRay = INT_MIN; // previous ray number
if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) {
int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel
posx += inc_x * jump;
posy += inc_y * jump;
}
prevRay = i;
// draw ray until we hit any edge
while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) {
// scale down to integer (compiler will replace division with appropriate bitshift)
int x = posx / Fixed_Scale;
int y = posy / Fixed_Scale;
// set pixel
if (x != lastX || y != lastY) setPixelColorXY(x, y, col); // only paint if pixel position is different
lastX = x;
lastY = y;
// advance to next position
posx += inc_x;
posy += inc_y;
}
break;
}
}
return;
} else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) {
@ -732,14 +817,10 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col)
}
#endif
uint16_t len = length();
unsigned len = length();
uint8_t _bri_t = currentBri();
if (_bri_t < 255) {
byte r = scale8(R(col), _bri_t);
byte g = scale8(G(col), _bri_t);
byte b = scale8(B(col), _bri_t);
byte w = scale8(W(col), _bri_t);
col = RGBW32(r, g, b, w);
col = color_fade(col, _bri_t);
}
// expand pixel (taking into account start, grouping, spacing [and offset])
@ -777,6 +858,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col)
}
}
#ifdef WLED_USE_AA_PIXELS
// anti-aliased normalized version of setPixelColor()
void Segment::setPixelColor(float i, uint32_t col, bool aa)
{
@ -788,8 +870,8 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa)
float fC = i * (virtualLength()-1);
if (aa) {
uint16_t iL = roundf(fC-0.49f);
uint16_t iR = roundf(fC+0.49f);
unsigned iL = roundf(fC-0.49f);
unsigned iR = roundf(fC+0.49f);
float dL = (fC - iL)*(fC - iL);
float dR = (iR - fC)*(iR - fC);
uint32_t cIL = getPixelColor(iL | (vStrip<<16));
@ -806,9 +888,10 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa)
setPixelColor(iL | (vStrip<<16), col);
}
} else {
setPixelColor(uint16_t(roundf(fC)) | (vStrip<<16), col);
setPixelColor(int(roundf(fC)) | (vStrip<<16), col);
}
}
#endif
uint32_t IRAM_ATTR Segment::getPixelColor(int i)
{
@ -820,8 +903,8 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i)
#ifndef WLED_DISABLE_2D
if (is2D()) {
uint16_t vH = virtualHeight(); // segment height in logical pixels
uint16_t vW = virtualWidth();
unsigned vH = virtualHeight(); // segment height in logical pixels
unsigned vW = virtualWidth();
switch (map1D2D) {
case M12_Pixels:
return getPixelColorXY(i % vW, i / vW);
@ -835,7 +918,36 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i)
// use longest dimension
return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i);
break;
}
case M12_sPinwheel:
// not 100% accurate, returns pixel at outer edge
// i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small)
float centerX = roundf((vW-1) / 2.0f);
float centerY = roundf((vH-1) / 2.0f);
float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians
float cosVal = cos_t(angleRad);
float sinVal = sin_t(angleRad);
int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit
int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit
int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit
int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit
int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint
int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint
// trace ray from center until we hit any edge - to avoid rounding problems, we use the same method as in setPixelColor
int x = INT_MIN;
int y = INT_MIN;
while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) {
// scale down to integer (compiler will replace division with appropriate bitshift)
x = posx / Fixed_Scale;
y = posy / Fixed_Scale;
// advance to next position
posx += inc_x;
posy += inc_y;
}
return getPixelColorXY(x, y);
break;
}
return 0;
}
#endif
@ -877,9 +989,9 @@ uint8_t Segment::differs(Segment& b) const {
}
void Segment::refreshLightCapabilities() {
uint8_t capabilities = 0;
uint16_t segStartIdx = 0xFFFFU;
uint16_t segStopIdx = 0;
unsigned capabilities = 0;
unsigned segStartIdx = 0xFFFFU;
unsigned segStopIdx = 0;
if (!isActive()) {
_capabilities = 0;
@ -889,7 +1001,7 @@ void Segment::refreshLightCapabilities() {
if (start < Segment::maxWidth * Segment::maxHeight) {
// we are withing 2D matrix (includes 1D segments)
for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) {
uint16_t index = strip.getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical
unsigned index = strip.getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical
if (index < 0xFFFFU) {
if (segStartIdx > index) segStartIdx = index;
if (segStopIdx < index) segStopIdx = index;
@ -914,7 +1026,7 @@ void Segment::refreshLightCapabilities() {
if (!cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT;
if (correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider)
if (bus->hasWhite()) {
uint8_t aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode();
unsigned aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode();
bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed
// if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses
if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB;
@ -930,8 +1042,8 @@ void Segment::refreshLightCapabilities() {
*/
void Segment::fill(uint32_t c) {
if (!isActive()) return; // not active
const uint16_t cols = is2D() ? virtualWidth() : virtualLength();
const uint16_t rows = virtualHeight(); // will be 1 for 1D
const int cols = is2D() ? virtualWidth() : virtualLength();
const int rows = virtualHeight(); // will be 1 for 1D
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
if (is2D()) setPixelColorXY(x, y, c);
else setPixelColor(x, c);
@ -943,8 +1055,8 @@ void Segment::fill(uint32_t c) {
*/
void Segment::fade_out(uint8_t rate) {
if (!isActive()) return; // not active
const uint16_t cols = is2D() ? virtualWidth() : virtualLength();
const uint16_t rows = virtualHeight(); // will be 1 for 1D
const int cols = is2D() ? virtualWidth() : virtualLength();
const int rows = virtualHeight(); // will be 1 for 1D
rate = (255-rate) >> 1;
float mappedRate = float(rate) +1.1f;
@ -981,8 +1093,8 @@ void Segment::fade_out(uint8_t rate) {
// fades all pixels to black using nscale8()
void Segment::fadeToBlackBy(uint8_t fadeBy) {
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
const uint16_t cols = is2D() ? virtualWidth() : virtualLength();
const uint16_t rows = virtualHeight(); // will be 1 for 1D
const int cols = is2D() ? virtualWidth() : virtualLength();
const int rows = virtualHeight(); // will be 1 for 1D
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy));
@ -993,33 +1105,43 @@ void Segment::fadeToBlackBy(uint8_t fadeBy) {
/*
* blurs segment content, source: FastLED colorutils.cpp
*/
void Segment::blur(uint8_t blur_amount) {
void Segment::blur(uint8_t blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur"
#ifndef WLED_DISABLE_2D
if (is2D()) {
// compatibility with 2D
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
for (unsigned i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows
for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns
for (unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); // blur all rows
for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); // blur all columns
return;
}
#endif
uint8_t keep = 255 - blur_amount;
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
uint32_t carryover = BLACK;
unsigned vlength = virtualLength();
uint32_t carryover = BLACK;
uint32_t lastnew;
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned i = 0; i < vlength; i++) {
uint32_t cur = getPixelColor(i);
uint32_t part = color_fade(cur, seep);
cur = color_add(color_fade(cur, keep), carryover, true);
curnew = color_fade(cur, keep);
if (i > 0) {
uint32_t c = getPixelColor(i-1);
setPixelColor(i-1, color_add(c, part, true));
if (carryover)
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed
setPixelColor(i - 1, prev);
}
setPixelColor(i, cur);
else // first pixel
setPixelColor(i, curnew);
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
}
setPixelColor(vlength - 1, curnew);
}
/*
@ -1057,13 +1179,11 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_
// default palette or no RGB support on segment
if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true);
uint8_t paletteIndex = i;
unsigned paletteIndex = i;
if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1);
// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined)
if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGBPalette16 curPal;
curPal = currentPalette(curPal, palette);
CRGB fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global
CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global
return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color));
}
@ -1091,21 +1211,24 @@ void WS2812FX::finalizeInit(void) {
//if busses failed to load, add default (fresh install, FS issue, ...)
if (BusManager::getNumBusses() == 0) {
DEBUG_PRINTLN(F("No busses, init default"));
const uint8_t defDataPins[] = {DATA_PINS};
const uint16_t defCounts[] = {PIXEL_COUNTS};
const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0]));
const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0]));
uint16_t prevLen = 0;
for (int i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
uint8_t defPin[] = {defDataPins[i]};
const unsigned defDataPins[] = {DATA_PINS};
const unsigned defCounts[] = {PIXEL_COUNTS};
const unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0]));
const unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0]));
const unsigned defNumBusses = defNumPins > defNumCounts && defNumCounts > 1 && defNumPins%defNumCounts == 0 ? defNumCounts : defNumPins;
const unsigned pinsPerBus = defNumPins / defNumBusses;
unsigned prevLen = 0;
for (unsigned i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
uint8_t defPin[5]; // max 5 pins
for (unsigned j = 0; j < pinsPerBus; j++) defPin[j] = defDataPins[i*pinsPerBus + j];
// when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware
// i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc
if (pinManager.isPinAllocated(defPin[0])) {
defPin[0] = 1; // start with GPIO1 and work upwards
while (pinManager.isPinAllocated(defPin[0]) && defPin[0] < WLED_NUM_PINS) defPin[0]++;
}
uint16_t start = prevLen;
uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
unsigned start = prevLen;
unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
prevLen += count;
BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY);
if (BusManager::add(defCfg) == -1) break;
@ -1121,7 +1244,7 @@ void WS2812FX::finalizeInit(void) {
_hasWhiteChannel |= bus->hasWhite();
//refresh is required to remain off if at least one of the strips requires the refresh.
_isOffRefreshRequired |= bus->isOffRefreshRequired();
uint16_t busEnd = bus->getStart() + bus->getLength();
unsigned busEnd = bus->getStart() + bus->getLength();
if (busEnd > _length) _length = busEnd;
#ifdef ESP8266
if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue;
@ -1165,15 +1288,15 @@ void WS2812FX::service() {
if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))
{
doShow = true;
uint16_t delay = FRAMETIME;
unsigned delay = FRAMETIME;
if (!seg.freeze) { //only run effect function if not frozen
int16_t oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based)
int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based)
_virtualSegmentLength = seg.virtualLength(); //SEGLEN
_colors_t[0] = gamma32(seg.currentColor(0));
_colors_t[1] = gamma32(seg.currentColor(1));
_colors_t[2] = gamma32(seg.currentColor(2));
seg.currentPalette(_currentPalette, seg.palette); // we need to pass reference
seg.setCurrentPalette(); // load actual palette
// when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values
if (cctFromRgb) BusManager::setSegmentCCT(-1);
@ -1192,7 +1315,7 @@ void WS2812FX::service() {
Segment::modeBlend(true); // set semaphore
seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state)
_virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed)
uint16_t d2 = (*_mode[tmpMode])(); // run old mode
unsigned d2 = (*_mode[tmpMode])(); // run old mode
seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state)
delay = MIN(delay,d2); // use shortest delay
Segment::modeBlend(false); // unset semaphore
@ -1216,7 +1339,7 @@ void WS2812FX::service() {
#endif
if (doShow) {
yield();
Segment::handleRandomPalette(); // slowly transtion random palette; move it into for loop when each segment has individual random palette
Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette
show();
}
#ifdef WLED_DEBUG
@ -1367,13 +1490,13 @@ uint8_t WS2812FX::getActiveSegmentsNum(void) {
}
uint16_t WS2812FX::getLengthTotal(void) {
uint16_t len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D
unsigned len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D
if (isMatrix && _length > len) len = _length; // for 2D with trailing strip
return len;
}
uint16_t WS2812FX::getLengthPhysical(void) {
uint16_t len = 0;
unsigned len = 0;
for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b);
if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses
@ -1450,8 +1573,8 @@ void WS2812FX::resetSegments() {
void WS2812FX::makeAutoSegments(bool forceReset) {
if (autoSegments) { //make one segment per bus
uint16_t segStarts[MAX_NUM_SEGMENTS] = {0};
uint16_t segStops [MAX_NUM_SEGMENTS] = {0};
unsigned segStarts[MAX_NUM_SEGMENTS] = {0};
unsigned segStops [MAX_NUM_SEGMENTS] = {0};
size_t s = 0;
#ifndef WLED_DISABLE_2D
@ -1470,7 +1593,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
segStops[s] = segStarts[s] + b->getLength();
#ifndef WLED_DISABLE_2D
if (isMatrix && segStops[s] < Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix
if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix
if (isMatrix && segStarts[s] < Segment::maxWidth*Segment::maxHeight) segStarts[s] = Segment::maxWidth*Segment::maxHeight;
#endif
@ -1498,6 +1621,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
for (size_t i = 1; i < s; i++) {
_segments.push_back(Segment(segStarts[i], segStops[i]));
}
DEBUG_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size());
} else {
@ -1543,6 +1667,8 @@ void WS2812FX::fixInvalidSegments() {
if (_segments[i].stop > _length) _segments[i].stop = _length;
}
}
// if any segments were deleted free memory
purgeSegments();
// this is always called as the last step after finalizeInit(), update covered bus types
for (segment &seg : _segments)
seg.refreshLightCapabilities();
@ -1573,7 +1699,8 @@ void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) {
void WS2812FX::printSize() {
size_t size = 0;
for (const Segment &seg : _segments) size += seg.getSize();
DEBUG_PRINTF_P(PSTR("Segments: %d -> %uB\n"), _segments.size(), size);
DEBUG_PRINTF_P(PSTR("Segments: %d -> %u/%dB\n"), _segments.size(), size, Segment::getUsedSegmentData());
for (const Segment &seg : _segments) DEBUG_PRINTF_P(PSTR(" Seg: %d,%d [A=%d, 2D=%d, RGB=%d, W=%d, CCT=%d]\n"), seg.width(), seg.height(), seg.isActive(), seg.is2D(), seg.hasRGB(), seg.hasWhite(), seg.isCCT());
DEBUG_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr)));
DEBUG_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *)));
DEBUG_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t));
@ -1656,12 +1783,18 @@ bool WS2812FX::deserializeMap(uint8_t n) {
return false; // if file does not load properly then exit
}
JsonObject root = pDoc->as<JsonObject>();
// if we are loading default ledmap (at boot) set matrix width and height from the ledmap (compatible with WLED MM ledmaps)
if (isMatrix && n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) {
Segment::maxWidth = min(max(root[F("width")].as<int>(), 1), 128);
Segment::maxHeight = min(max(root[F("height")].as<int>(), 1), 128);
}
if (customMappingTable) delete[] customMappingTable;
customMappingTable = new uint16_t[getLengthTotal()];
if (customMappingTable) {
DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName);
JsonObject root = pDoc->as<JsonObject>();
JsonArray map = root[F("map")];
if (!map.isNull() && map.size()) { // not an empty map
customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal());

View File

@ -425,6 +425,7 @@ BusPwm::BusPwm(BusConfig &bc)
}
_data = _pwmdata; // avoid malloc() and use stack
_valid = true;
DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]);
}
void BusPwm::setPixelColor(uint16_t pix, uint32_t c) {
@ -466,7 +467,22 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) {
//does no index check
uint32_t BusPwm::getPixelColor(uint16_t pix) {
if (!_valid) return 0;
return RGBW32(_data[0], _data[1], _data[2], _data[3]);
// TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented)
switch (_type) {
case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
return RGBW32(0, 0, 0, _data[0]);
case TYPE_ANALOG_2CH: //warm white + cold white
if (cctICused) return RGBW32(0, 0, 0, _data[0]);
else return RGBW32(0, 0, 0, _data[0] + _data[1]);
case TYPE_ANALOG_5CH: //RGB + warm white + cold white
if (cctICused) return RGBW32(_data[0], _data[1], _data[2], _data[3]);
else return RGBW32(_data[0], _data[1], _data[2], _data[3] + _data[4]);
case TYPE_ANALOG_4CH: //RGBW
return RGBW32(_data[0], _data[1], _data[2], _data[3]);
case TYPE_ANALOG_3CH: //standard dumb RGB
return RGBW32(_data[0], _data[1], _data[2], 0);
}
return RGBW32(_data[0], _data[0], _data[0], _data[0]);
}
#ifndef ESP8266
@ -560,6 +576,7 @@ BusOnOff::BusOnOff(BusConfig &bc)
pinMode(_pin, OUTPUT);
_data = &_onoffdata; // avoid malloc() and use stack
_valid = true;
DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin);
}
void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) {
@ -614,6 +631,7 @@ BusNetwork::BusNetwork(BusConfig &bc)
_UDPchannels = _rgbw ? 4 : 3;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
_valid = (allocData(_len * _UDPchannels) != nullptr);
DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]);
}
void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
@ -714,9 +732,8 @@ void BusManager::setStatusPixel(uint32_t c) {
void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c) {
for (unsigned i = 0; i < numBusses; i++) {
Bus* b = busses[i];
uint16_t bstart = b->getStart();
if (pix < bstart || pix >= bstart + b->getLength()) continue;
unsigned bstart = busses[i]->getStart();
if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue;
busses[i]->setPixelColor(pix - bstart, c);
}
}
@ -738,10 +755,9 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
uint32_t BusManager::getPixelColor(uint16_t pix) {
for (unsigned i = 0; i < numBusses; i++) {
Bus* b = busses[i];
uint16_t bstart = b->getStart();
if (pix < bstart || pix >= bstart + b->getLength()) continue;
return b->getPixelColor(pix - bstart);
unsigned bstart = busses[i]->getStart();
if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue;
return busses[i]->getPixelColor(pix - bstart);
}
return 0;
}

View File

@ -17,6 +17,11 @@
#if !defined(WLED_NO_I2S1_PIXELBUS) && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2))
#define WLED_NO_I2S1_PIXELBUS
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifdef WLED_USE_PARALLEL_I2S
#warning Use less than 300 pixels per bus.
#endif
#endif
// temporary end
//Hardware SPI Pins
@ -74,11 +79,16 @@
#define I_8266_U1_APA106_3 82
#define I_8266_DM_APA106_3 83
#define I_8266_BB_APA106_3 84
//WS2805
//WS2805 (RGBCW)
#define I_8266_U0_2805_5 89
#define I_8266_U1_2805_5 90
#define I_8266_DM_2805_5 91
#define I_8266_BB_2805_5 92
//TM1914 (RGB)
#define I_8266_U0_TM1914_3 99
#define I_8266_U1_TM1914_3 100
#define I_8266_DM_TM1914_3 101
#define I_8266_BB_TM1914_3 102
/*** ESP32 Neopixel methods ***/
//RGB
@ -117,10 +127,14 @@
#define I_32_RN_APA106_3 85
#define I_32_I0_APA106_3 86
#define I_32_I1_APA106_3 87
//WS2805
//WS2805 (RGBCW)
#define I_32_RN_2805_5 93
#define I_32_I0_2805_5 94
#define I_32_I1_2805_5 95
//TM1914 (RGB)
#define I_32_RN_TM1914_3 96
#define I_32_I0_TM1914_3 97
#define I_32_I1_TM1914_3 98
//APA102
@ -170,10 +184,10 @@
#define B_8266_DM_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266DmaTm1814Method, NeoGammaNullMethod>
#define B_8266_BB_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266BitBangTm1814Method, NeoGammaNullMethod>
//TM1829 (RGB)
#define B_8266_U0_TM2_4 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart0Tm1829Method, NeoGammaNullMethod>
#define B_8266_U1_TM2_4 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart1Tm1829Method, NeoGammaNullMethod>
#define B_8266_DM_TM2_4 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266DmaTm1829Method, NeoGammaNullMethod>
#define B_8266_BB_TM2_4 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266BitBangTm1829Method, NeoGammaNullMethod>
#define B_8266_U0_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart0Tm1829Method, NeoGammaNullMethod>
#define B_8266_U1_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart1Tm1829Method, NeoGammaNullMethod>
#define B_8266_DM_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266DmaTm1829Method, NeoGammaNullMethod>
#define B_8266_BB_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266BitBangTm1829Method, NeoGammaNullMethod>
//UCS8903
#define B_8266_U0_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1
#define B_8266_U1_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2
@ -199,6 +213,11 @@
#define B_8266_U1_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266Uart1Ws2805Method, NeoGammaNullMethod> //esp8266, gpio2
#define B_8266_DM_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266DmaWs2805Method, NeoGammaNullMethod> //esp8266, gpio3
#define B_8266_BB_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266BitBangWs2805Method, NeoGammaNullMethod> //esp8266, bb
//TM1914 (RGB)
#define B_8266_U0_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266Uart0Tm1914Method, NeoGammaNullMethod>
#define B_8266_U1_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266Uart1Tm1914Method, NeoGammaNullMethod>
#define B_8266_DM_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266DmaTm1914Method, NeoGammaNullMethod>
#define B_8266_BB_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266BitBangTm1914Method, NeoGammaNullMethod>
#endif
/*** ESP32 Neopixel methods ***/
@ -210,8 +229,11 @@
//#define B_32_I0_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s0X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s1Ws2812xMethod, NeoGammaNullMethod>
//#define B_32_I1_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s1X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S
#else
#define B_32_I1_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s1X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S
#endif
#endif
//RGBW
#define B_32_RN_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32RmtNSk6812Method, NeoGammaNullMethod>
@ -220,8 +242,11 @@
//#define B_32_I0_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32I2s0X8Sk6812Method, NeoGammaNullMethod> // parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32I2s1Sk6812Method, NeoGammaNullMethod>
//#define B_32_I1_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32I2s1X8Sk6812Method, NeoGammaNullMethod> // parallel I2S
#else
#define B_32_I1_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32I2s1X8Sk6812Method, NeoGammaNullMethod> // parallel I2S
#endif
#endif
//400Kbps
#define B_32_RN_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod, NeoGammaNullMethod>
@ -230,8 +255,11 @@
//#define B_32_I0_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s0X8400KbpsMethod, NeoGammaNullMethod> // parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s1400KbpsMethod, NeoGammaNullMethod>
//#define B_32_I1_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s1X8400KbpsMethod, NeoGammaNullMethod> // parallel I2S
#else
#define B_32_I1_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s1X8400KbpsMethod, NeoGammaNullMethod> // parallel I2S
#endif
#endif
//TM1814 (RGBW)
#define B_32_RN_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method, NeoGammaNullMethod>
@ -240,8 +268,11 @@
//#define B_32_I0_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32I2s0X8Tm1814Method, NeoGammaNullMethod> // parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32I2s1Tm1814Method, NeoGammaNullMethod>
//#define B_32_I1_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32I2s1X8Tm1814Method, NeoGammaNullMethod> // parallel I2S
#else
#define B_32_I1_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32I2s1X8Tm1814Method, NeoGammaNullMethod> // parallel I2S
#endif
#endif
//TM1829 (RGB)
#define B_32_RN_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32RmtNTm1829Method, NeoGammaNullMethod>
@ -250,8 +281,11 @@
//#define B_32_I0_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32I2s0X8Tm1829Method, NeoGammaNullMethod> // parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32I2s1Tm1829Method, NeoGammaNullMethod>
//#define B_32_I1_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32I2s1X8Tm1829Method, NeoGammaNullMethod> // parallel I2S
#else
#define B_32_I1_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32I2s1X8Tm1829Method, NeoGammaNullMethod> // parallel I2S
#endif
#endif
//UCS8903
#define B_32_RN_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
@ -260,8 +294,11 @@
//#define B_32_I0_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32I2s0X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32I2s1800KbpsMethod, NeoGammaNullMethod>
//#define B_32_I1_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32I2s1X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
#else
#define B_32_I1_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32I2s1X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
#endif
#endif
//UCS8904
#define B_32_RN_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
@ -270,8 +307,11 @@
//#define B_32_I0_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32I2s0X8800KbpsMethod, NeoGammaNullMethod>// parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32I2s1800KbpsMethod, NeoGammaNullMethod>
//#define B_32_I1_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32I2s1X8800KbpsMethod, NeoGammaNullMethod>// parallel I2S
#else
#define B_32_I1_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32I2s1X8800KbpsMethod, NeoGammaNullMethod>// parallel I2S
#endif
#endif
#define B_32_RN_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtNApa106Method, NeoGammaNullMethod>
#ifndef WLED_NO_I2S0_PIXELBUS
@ -279,8 +319,11 @@
//#define B_32_I0_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s0X8Apa106Method, NeoGammaNullMethod> // parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s1Apa106Method, NeoGammaNullMethod>
//#define B_32_I1_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s1X8Apa106Method, NeoGammaNullMethod> // parallel I2S
#else
#define B_32_I1_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2s1X8Apa106Method, NeoGammaNullMethod> // parallel I2S
#endif
#endif
//FW1906 GRBCW
#define B_32_RN_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
@ -289,8 +332,11 @@
//#define B_32_I0_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32I2s0X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32I2s1800KbpsMethod, NeoGammaNullMethod>
//#define B_32_I1_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32I2s1X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
#else
#define B_32_I1_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32I2s1X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
#endif
#endif
//WS2805 RGBWC
#define B_32_RN_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method, NeoGammaNullMethod>
@ -299,8 +345,24 @@
//#define B_32_I0_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32I2s0X8Ws2805Method, NeoGammaNullMethod> // parallel I2S
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32I2s1Ws2805Method, NeoGammaNullMethod>
//#define B_32_I1_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32I2s1X8Ws2805Method, NeoGammaNullMethod> // parallel I2S
#else
#define B_32_I1_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32I2s1X8Ws2805Method, NeoGammaNullMethod> // parallel I2S
#endif
#endif
//TM1914 (RGB)
#define B_32_RN_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method, NeoGammaNullMethod>
#ifndef WLED_NO_I2S0_PIXELBUS
#define B_32_I0_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32I2s0Tm1914Method, NeoGammaNullMethod>
//#define B_32_I0_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32I2s0X8Tm1914Method, NeoGammaNullMethod>
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
#ifndef WLED_USE_PARALLEL_I2S
#define B_32_I1_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32I2s1Tm1914Method, NeoGammaNullMethod>
#else
#define B_32_I1_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32I2s1X8Tm1914Method, NeoGammaNullMethod>
#endif
#endif
#endif
@ -363,6 +425,13 @@ class PolyBus {
tm1814_strip->SetPixelSettings(NeoTm1814Settings(/*R*/225, /*G*/225, /*B*/225, /*W*/225));
}
template <class T>
static void beginTM1914(void* busPtr) {
T tm1914_strip = static_cast<T>(busPtr);
tm1914_strip->Begin();
tm1914_strip->SetPixelSettings(NeoTm1914Settings()); //NeoTm1914_Mode_DinFdinAutoSwitch, NeoTm1914_Mode_DinOnly, NeoTm1914_Mode_FdinOnly
}
static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) {
switch (busType) {
case I_NONE: break;
@ -383,10 +452,10 @@ class PolyBus {
case I_8266_U1_TM1_4: beginTM1814<B_8266_U1_TM1_4*>(busPtr); break;
case I_8266_DM_TM1_4: beginTM1814<B_8266_DM_TM1_4*>(busPtr); break;
case I_8266_BB_TM1_4: beginTM1814<B_8266_BB_TM1_4*>(busPtr); break;
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_4*>(busPtr))->Begin(); break;
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_4*>(busPtr))->Begin(); break;
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_4*>(busPtr))->Begin(); break;
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_4*>(busPtr))->Begin(); break;
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_3*>(busPtr))->Begin(); break;
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_3*>(busPtr))->Begin(); break;
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_3*>(busPtr))->Begin(); break;
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_3*>(busPtr))->Begin(); break;
case I_HS_DOT_3: beginDotStar<B_HS_DOT_3*>(busPtr, -1, -1, -1, -1, clock_kHz); break;
case I_HS_LPD_3: beginDotStar<B_HS_LPD_3*>(busPtr, -1, -1, -1, -1, clock_kHz); break;
case I_HS_LPO_3: beginDotStar<B_HS_LPO_3*>(busPtr, -1, -1, -1, -1, clock_kHz); break;
@ -412,6 +481,10 @@ class PolyBus {
case I_8266_U1_2805_5: (static_cast<B_8266_U1_2805_5*>(busPtr))->Begin(); break;
case I_8266_DM_2805_5: (static_cast<B_8266_DM_2805_5*>(busPtr))->Begin(); break;
case I_8266_BB_2805_5: (static_cast<B_8266_BB_2805_5*>(busPtr))->Begin(); break;
case I_8266_U0_TM1914_3: beginTM1914<B_8266_U0_TM1914_3*>(busPtr); break;
case I_8266_U1_TM1914_3: beginTM1914<B_8266_U1_TM1914_3*>(busPtr); break;
case I_8266_DM_TM1914_3: beginTM1914<B_8266_DM_TM1914_3*>(busPtr); break;
case I_8266_BB_TM1914_3: beginTM1914<B_8266_BB_TM1914_3*>(busPtr); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->Begin(); break;
@ -480,6 +553,13 @@ class PolyBus {
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_2805_5: (static_cast<B_32_I1_2805_5*>(busPtr))->Begin(); break;
#endif
case I_32_RN_TM1914_3: beginTM1914<B_32_RN_TM1914_3*>(busPtr); break;
#ifndef WLED_NO_I2S0_PIXELBUS
case I_32_I0_TM1914_3: beginTM1914<B_32_I0_TM1914_3*>(busPtr); break;
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_TM1914_3: beginTM1914<B_32_I1_TM1914_3*>(busPtr); break;
#endif
// ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin()
case I_HS_DOT_3: beginDotStar<B_HS_DOT_3*>(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break;
case I_HS_LPD_3: beginDotStar<B_HS_LPD_3*>(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break;
@ -496,6 +576,15 @@ class PolyBus {
}
static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) {
#if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
// NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation
// since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation
#ifdef WLED_USE_PARALLEL_I2S
if (channel > 7) channel -= 8; // accommodate parallel I2S1 which is used 1st on classic ESP32
#else
if (channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32
#endif
#endif
void* busPtr = nullptr;
switch (busType) {
case I_NONE: break;
@ -516,10 +605,10 @@ class PolyBus {
case I_8266_U1_TM1_4: busPtr = new B_8266_U1_TM1_4(len, pins[0]); break;
case I_8266_DM_TM1_4: busPtr = new B_8266_DM_TM1_4(len, pins[0]); break;
case I_8266_BB_TM1_4: busPtr = new B_8266_BB_TM1_4(len, pins[0]); break;
case I_8266_U0_TM2_3: busPtr = new B_8266_U0_TM2_4(len, pins[0]); break;
case I_8266_U1_TM2_3: busPtr = new B_8266_U1_TM2_4(len, pins[0]); break;
case I_8266_DM_TM2_3: busPtr = new B_8266_DM_TM2_4(len, pins[0]); break;
case I_8266_BB_TM2_3: busPtr = new B_8266_BB_TM2_4(len, pins[0]); break;
case I_8266_U0_TM2_3: busPtr = new B_8266_U0_TM2_3(len, pins[0]); break;
case I_8266_U1_TM2_3: busPtr = new B_8266_U1_TM2_3(len, pins[0]); break;
case I_8266_DM_TM2_3: busPtr = new B_8266_DM_TM2_3(len, pins[0]); break;
case I_8266_BB_TM2_3: busPtr = new B_8266_BB_TM2_3(len, pins[0]); break;
case I_8266_U0_UCS_3: busPtr = new B_8266_U0_UCS_3(len, pins[0]); break;
case I_8266_U1_UCS_3: busPtr = new B_8266_U1_UCS_3(len, pins[0]); break;
case I_8266_DM_UCS_3: busPtr = new B_8266_DM_UCS_3(len, pins[0]); break;
@ -540,6 +629,10 @@ class PolyBus {
case I_8266_U1_2805_5: busPtr = new B_8266_U1_2805_5(len, pins[0]); break;
case I_8266_DM_2805_5: busPtr = new B_8266_DM_2805_5(len, pins[0]); break;
case I_8266_BB_2805_5: busPtr = new B_8266_BB_2805_5(len, pins[0]); break;
case I_8266_U0_TM1914_3: busPtr = new B_8266_U0_TM1914_3(len, pins[0]); break;
case I_8266_U1_TM1914_3: busPtr = new B_8266_U1_TM1914_3(len, pins[0]); break;
case I_8266_DM_TM1914_3: busPtr = new B_8266_DM_TM1914_3(len, pins[0]); break;
case I_8266_BB_TM1914_3: busPtr = new B_8266_BB_TM1914_3(len, pins[0]); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break;
@ -608,6 +701,13 @@ class PolyBus {
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_2805_5: busPtr = new B_32_I1_2805_5(len, pins[0]); break;
#endif
case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)channel); break;
#ifndef WLED_NO_I2S0_PIXELBUS
case I_32_I0_TM1914_3: busPtr = new B_32_I0_TM1914_3(len, pins[0]); break;
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_TM1914_3: busPtr = new B_32_I1_TM1914_3(len, pins[0]); break;
#endif
#endif
// for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat)
case I_HS_DOT_3: busPtr = new B_HS_DOT_3(len, pins[1], pins[0]); break;
@ -645,10 +745,10 @@ class PolyBus {
case I_8266_U1_TM1_4: (static_cast<B_8266_U1_TM1_4*>(busPtr))->Show(consistent); break;
case I_8266_DM_TM1_4: (static_cast<B_8266_DM_TM1_4*>(busPtr))->Show(consistent); break;
case I_8266_BB_TM1_4: (static_cast<B_8266_BB_TM1_4*>(busPtr))->Show(consistent); break;
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_4*>(busPtr))->Show(consistent); break;
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_4*>(busPtr))->Show(consistent); break;
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_4*>(busPtr))->Show(consistent); break;
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_4*>(busPtr))->Show(consistent); break;
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_3*>(busPtr))->Show(consistent); break;
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_3*>(busPtr))->Show(consistent); break;
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_3*>(busPtr))->Show(consistent); break;
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_3*>(busPtr))->Show(consistent); break;
case I_8266_U0_UCS_3: (static_cast<B_8266_U0_UCS_3*>(busPtr))->Show(consistent); break;
case I_8266_U1_UCS_3: (static_cast<B_8266_U1_UCS_3*>(busPtr))->Show(consistent); break;
case I_8266_DM_UCS_3: (static_cast<B_8266_DM_UCS_3*>(busPtr))->Show(consistent); break;
@ -669,6 +769,10 @@ class PolyBus {
case I_8266_U1_2805_5: (static_cast<B_8266_U1_2805_5*>(busPtr))->Show(consistent); break;
case I_8266_DM_2805_5: (static_cast<B_8266_DM_2805_5*>(busPtr))->Show(consistent); break;
case I_8266_BB_2805_5: (static_cast<B_8266_BB_2805_5*>(busPtr))->Show(consistent); break;
case I_8266_U0_TM1914_3: (static_cast<B_8266_U0_TM1914_3*>(busPtr))->Show(consistent); break;
case I_8266_U1_TM1914_3: (static_cast<B_8266_U1_TM1914_3*>(busPtr))->Show(consistent); break;
case I_8266_DM_TM1914_3: (static_cast<B_8266_DM_TM1914_3*>(busPtr))->Show(consistent); break;
case I_8266_BB_TM1914_3: (static_cast<B_8266_BB_TM1914_3*>(busPtr))->Show(consistent); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->Show(consistent); break;
@ -737,6 +841,13 @@ class PolyBus {
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_2805_5: (static_cast<B_32_I1_2805_5*>(busPtr))->Show(consistent); break;
#endif
case I_32_RN_TM1914_3: (static_cast<B_32_RN_TM1914_3*>(busPtr))->Show(consistent); break;
#ifndef WLED_NO_I2S0_PIXELBUS
case I_32_I0_TM1914_3: (static_cast<B_32_I0_TM1914_3*>(busPtr))->Show(consistent); break;
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_TM1914_3: (static_cast<B_32_I1_TM1914_3*>(busPtr))->Show(consistent); break;
#endif
#endif
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->Show(consistent); break;
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->Show(consistent); break;
@ -771,10 +882,10 @@ class PolyBus {
case I_8266_U1_TM1_4: return (static_cast<B_8266_U1_TM1_4*>(busPtr))->CanShow(); break;
case I_8266_DM_TM1_4: return (static_cast<B_8266_DM_TM1_4*>(busPtr))->CanShow(); break;
case I_8266_BB_TM1_4: return (static_cast<B_8266_BB_TM1_4*>(busPtr))->CanShow(); break;
case I_8266_U0_TM2_3: return (static_cast<B_8266_U0_TM2_4*>(busPtr))->CanShow(); break;
case I_8266_U1_TM2_3: return (static_cast<B_8266_U1_TM2_4*>(busPtr))->CanShow(); break;
case I_8266_DM_TM2_3: return (static_cast<B_8266_DM_TM2_4*>(busPtr))->CanShow(); break;
case I_8266_BB_TM2_3: return (static_cast<B_8266_BB_TM2_4*>(busPtr))->CanShow(); break;
case I_8266_U0_TM2_3: return (static_cast<B_8266_U0_TM2_3*>(busPtr))->CanShow(); break;
case I_8266_U1_TM2_3: return (static_cast<B_8266_U1_TM2_3*>(busPtr))->CanShow(); break;
case I_8266_DM_TM2_3: return (static_cast<B_8266_DM_TM2_3*>(busPtr))->CanShow(); break;
case I_8266_BB_TM2_3: return (static_cast<B_8266_BB_TM2_3*>(busPtr))->CanShow(); break;
case I_8266_U0_UCS_3: return (static_cast<B_8266_U0_UCS_3*>(busPtr))->CanShow(); break;
case I_8266_U1_UCS_3: return (static_cast<B_8266_U1_UCS_3*>(busPtr))->CanShow(); break;
case I_8266_DM_UCS_3: return (static_cast<B_8266_DM_UCS_3*>(busPtr))->CanShow(); break;
@ -794,6 +905,10 @@ class PolyBus {
case I_8266_U1_2805_5: return (static_cast<B_8266_U1_2805_5*>(busPtr))->CanShow(); break;
case I_8266_DM_2805_5: return (static_cast<B_8266_DM_2805_5*>(busPtr))->CanShow(); break;
case I_8266_BB_2805_5: return (static_cast<B_8266_BB_2805_5*>(busPtr))->CanShow(); break;
case I_8266_U0_TM1914_3: return (static_cast<B_8266_U0_TM1914_3*>(busPtr))->CanShow(); break;
case I_8266_U1_TM1914_3: return (static_cast<B_8266_U1_TM1914_3*>(busPtr))->CanShow(); break;
case I_8266_DM_TM1914_3: return (static_cast<B_8266_DM_TM1914_3*>(busPtr))->CanShow(); break;
case I_8266_BB_TM1914_3: return (static_cast<B_8266_BB_TM1914_3*>(busPtr))->CanShow(); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: return (static_cast<B_32_RN_NEO_3*>(busPtr))->CanShow(); break;
@ -862,6 +977,13 @@ class PolyBus {
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_2805_5: return (static_cast<B_32_I1_2805_5*>(busPtr))->CanShow(); break;
#endif
case I_32_RN_TM1914_3: return (static_cast<B_32_RN_TM1914_3*>(busPtr))->CanShow(); break;
#ifndef WLED_NO_I2S0_PIXELBUS
case I_32_I0_TM1914_3: return (static_cast<B_32_I0_TM1914_3*>(busPtr))->CanShow(); break;
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_TM1914_3: return (static_cast<B_32_I1_TM1914_3*>(busPtr))->CanShow(); break;
#endif
#endif
case I_HS_DOT_3: return (static_cast<B_HS_DOT_3*>(busPtr))->CanShow(); break;
case I_SS_DOT_3: return (static_cast<B_SS_DOT_3*>(busPtr))->CanShow(); break;
@ -921,10 +1043,10 @@ class PolyBus {
case I_8266_U1_TM1_4: (static_cast<B_8266_U1_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_8266_DM_TM1_4: (static_cast<B_8266_DM_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_8266_BB_TM1_4: (static_cast<B_8266_BB_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_4*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_U0_UCS_3: (static_cast<B_8266_U0_UCS_3*>(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break;
case I_8266_U1_UCS_3: (static_cast<B_8266_U1_UCS_3*>(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break;
case I_8266_DM_UCS_3: (static_cast<B_8266_DM_UCS_3*>(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break;
@ -945,6 +1067,10 @@ class PolyBus {
case I_8266_U1_2805_5: (static_cast<B_8266_U1_2805_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break;
case I_8266_DM_2805_5: (static_cast<B_8266_DM_2805_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break;
case I_8266_BB_2805_5: (static_cast<B_8266_BB_2805_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break;
case I_8266_U0_TM1914_3: (static_cast<B_8266_U0_TM1914_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_U1_TM1914_3: (static_cast<B_8266_U1_TM1914_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_DM_TM1914_3: (static_cast<B_8266_DM_TM1914_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_8266_BB_TM1914_3: (static_cast<B_8266_BB_TM1914_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
@ -1013,6 +1139,13 @@ class PolyBus {
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_2805_5: (static_cast<B_32_I1_2805_5*>(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break;
#endif
case I_32_RN_TM1914_3: (static_cast<B_32_RN_TM1914_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
#ifndef WLED_NO_I2S0_PIXELBUS
case I_32_I0_TM1914_3: (static_cast<B_32_I0_TM1914_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_TM1914_3: (static_cast<B_32_I1_TM1914_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
#endif
#endif
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
@ -1047,10 +1180,10 @@ class PolyBus {
case I_8266_U1_TM1_4: (static_cast<B_8266_U1_TM1_4*>(busPtr))->SetLuminance(b); break;
case I_8266_DM_TM1_4: (static_cast<B_8266_DM_TM1_4*>(busPtr))->SetLuminance(b); break;
case I_8266_BB_TM1_4: (static_cast<B_8266_BB_TM1_4*>(busPtr))->SetLuminance(b); break;
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_4*>(busPtr))->SetLuminance(b); break;
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_4*>(busPtr))->SetLuminance(b); break;
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_4*>(busPtr))->SetLuminance(b); break;
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_4*>(busPtr))->SetLuminance(b); break;
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_3*>(busPtr))->SetLuminance(b); break;
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_3*>(busPtr))->SetLuminance(b); break;
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_3*>(busPtr))->SetLuminance(b); break;
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_3*>(busPtr))->SetLuminance(b); break;
case I_8266_U0_UCS_3: (static_cast<B_8266_U0_UCS_3*>(busPtr))->SetLuminance(b); break;
case I_8266_U1_UCS_3: (static_cast<B_8266_U1_UCS_3*>(busPtr))->SetLuminance(b); break;
case I_8266_DM_UCS_3: (static_cast<B_8266_DM_UCS_3*>(busPtr))->SetLuminance(b); break;
@ -1071,6 +1204,10 @@ class PolyBus {
case I_8266_U1_2805_5: (static_cast<B_8266_U1_2805_5*>(busPtr))->SetLuminance(b); break;
case I_8266_DM_2805_5: (static_cast<B_8266_DM_2805_5*>(busPtr))->SetLuminance(b); break;
case I_8266_BB_2805_5: (static_cast<B_8266_BB_2805_5*>(busPtr))->SetLuminance(b); break;
case I_8266_U0_TM1914_3: (static_cast<B_8266_U0_TM1914_3*>(busPtr))->SetLuminance(b); break;
case I_8266_U1_TM1914_3: (static_cast<B_8266_U1_TM1914_3*>(busPtr))->SetLuminance(b); break;
case I_8266_DM_TM1914_3: (static_cast<B_8266_DM_TM1914_3*>(busPtr))->SetLuminance(b); break;
case I_8266_BB_TM1914_3: (static_cast<B_8266_BB_TM1914_3*>(busPtr))->SetLuminance(b); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->SetLuminance(b); break;
@ -1139,6 +1276,13 @@ class PolyBus {
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_2805_5: (static_cast<B_32_I1_2805_5*>(busPtr))->SetLuminance(b); break;
#endif
case I_32_RN_TM1914_3: (static_cast<B_32_RN_TM1914_3*>(busPtr))->SetLuminance(b); break;
#ifndef WLED_NO_I2S0_PIXELBUS
case I_32_I0_TM1914_3: (static_cast<B_32_I0_TM1914_3*>(busPtr))->SetLuminance(b); break;
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_TM1914_3: (static_cast<B_32_I1_TM1914_3*>(busPtr))->SetLuminance(b); break;
#endif
#endif
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->SetLuminance(b); break;
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->SetLuminance(b); break;
@ -1174,10 +1318,10 @@ class PolyBus {
case I_8266_U1_TM1_4: col = (static_cast<B_8266_U1_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_8266_DM_TM1_4: col = (static_cast<B_8266_DM_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_8266_BB_TM1_4: col = (static_cast<B_8266_BB_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_8266_U0_TM2_3: col = (static_cast<B_8266_U0_TM2_4*>(busPtr))->GetPixelColor(pix); break;
case I_8266_U1_TM2_3: col = (static_cast<B_8266_U1_TM2_4*>(busPtr))->GetPixelColor(pix); break;
case I_8266_DM_TM2_3: col = (static_cast<B_8266_DM_TM2_4*>(busPtr))->GetPixelColor(pix); break;
case I_8266_BB_TM2_3: col = (static_cast<B_8266_BB_TM2_4*>(busPtr))->GetPixelColor(pix); break;
case I_8266_U0_TM2_3: col = (static_cast<B_8266_U0_TM2_3*>(busPtr))->GetPixelColor(pix); break;
case I_8266_U1_TM2_3: col = (static_cast<B_8266_U1_TM2_3*>(busPtr))->GetPixelColor(pix); break;
case I_8266_DM_TM2_3: col = (static_cast<B_8266_DM_TM2_3*>(busPtr))->GetPixelColor(pix); break;
case I_8266_BB_TM2_3: col = (static_cast<B_8266_BB_TM2_3*>(busPtr))->GetPixelColor(pix); break;
case I_8266_U0_UCS_3: { Rgb48Color c = (static_cast<B_8266_U0_UCS_3*>(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break;
case I_8266_U1_UCS_3: { Rgb48Color c = (static_cast<B_8266_U1_UCS_3*>(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break;
case I_8266_DM_UCS_3: { Rgb48Color c = (static_cast<B_8266_DM_UCS_3*>(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break;
@ -1198,6 +1342,10 @@ class PolyBus {
case I_8266_U1_2805_5: { RgbwwColor c = (static_cast<B_8266_U1_2805_5*>(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W
case I_8266_DM_2805_5: { RgbwwColor c = (static_cast<B_8266_DM_2805_5*>(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W
case I_8266_BB_2805_5: { RgbwwColor c = (static_cast<B_8266_BB_2805_5*>(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W
case I_8266_U0_TM1914_3: col = (static_cast<B_8266_U0_TM1914_3*>(busPtr))->GetPixelColor(pix); break;
case I_8266_U1_TM1914_3: col = (static_cast<B_8266_U1_TM1914_3*>(busPtr))->GetPixelColor(pix); break;
case I_8266_DM_TM1914_3: col = (static_cast<B_8266_DM_TM1914_3*>(busPtr))->GetPixelColor(pix); break;
case I_8266_BB_TM1914_3: col = (static_cast<B_8266_BB_TM1914_3*>(busPtr))->GetPixelColor(pix); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: col = (static_cast<B_32_RN_NEO_3*>(busPtr))->GetPixelColor(pix); break;
@ -1266,6 +1414,13 @@ class PolyBus {
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_2805_5: { RgbwwColor c = (static_cast<B_32_I1_2805_5*>(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W
#endif
case I_32_RN_TM1914_3: col = (static_cast<B_32_RN_TM1914_3*>(busPtr))->GetPixelColor(pix); break;
#ifndef WLED_NO_I2S0_PIXELBUS
case I_32_I0_TM1914_3: col = (static_cast<B_32_I0_TM1914_3*>(busPtr))->GetPixelColor(pix); break;
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_TM1914_3: col = (static_cast<B_32_I1_TM1914_3*>(busPtr))->GetPixelColor(pix); break;
#endif
#endif
case I_HS_DOT_3: col = (static_cast<B_HS_DOT_3*>(busPtr))->GetPixelColor(pix); break;
case I_SS_DOT_3: col = (static_cast<B_SS_DOT_3*>(busPtr))->GetPixelColor(pix); break;
@ -1319,10 +1474,10 @@ class PolyBus {
case I_8266_U1_TM1_4: delete (static_cast<B_8266_U1_TM1_4*>(busPtr)); break;
case I_8266_DM_TM1_4: delete (static_cast<B_8266_DM_TM1_4*>(busPtr)); break;
case I_8266_BB_TM1_4: delete (static_cast<B_8266_BB_TM1_4*>(busPtr)); break;
case I_8266_U0_TM2_3: delete (static_cast<B_8266_U0_TM2_4*>(busPtr)); break;
case I_8266_U1_TM2_3: delete (static_cast<B_8266_U1_TM2_4*>(busPtr)); break;
case I_8266_DM_TM2_3: delete (static_cast<B_8266_DM_TM2_4*>(busPtr)); break;
case I_8266_BB_TM2_3: delete (static_cast<B_8266_BB_TM2_4*>(busPtr)); break;
case I_8266_U0_TM2_3: delete (static_cast<B_8266_U0_TM2_3*>(busPtr)); break;
case I_8266_U1_TM2_3: delete (static_cast<B_8266_U1_TM2_3*>(busPtr)); break;
case I_8266_DM_TM2_3: delete (static_cast<B_8266_DM_TM2_3*>(busPtr)); break;
case I_8266_BB_TM2_3: delete (static_cast<B_8266_BB_TM2_3*>(busPtr)); break;
case I_8266_U0_UCS_3: delete (static_cast<B_8266_U0_UCS_3*>(busPtr)); break;
case I_8266_U1_UCS_3: delete (static_cast<B_8266_U1_UCS_3*>(busPtr)); break;
case I_8266_DM_UCS_3: delete (static_cast<B_8266_DM_UCS_3*>(busPtr)); break;
@ -1343,6 +1498,10 @@ class PolyBus {
case I_8266_U1_2805_5: delete (static_cast<B_8266_U1_2805_5*>(busPtr)); break;
case I_8266_DM_2805_5: delete (static_cast<B_8266_DM_2805_5*>(busPtr)); break;
case I_8266_BB_2805_5: delete (static_cast<B_8266_BB_2805_5*>(busPtr)); break;
case I_8266_U0_TM1914_3: delete (static_cast<B_8266_U0_TM1914_3*>(busPtr)); break;
case I_8266_U1_TM1914_3: delete (static_cast<B_8266_U1_TM1914_3*>(busPtr)); break;
case I_8266_DM_TM1914_3: delete (static_cast<B_8266_DM_TM1914_3*>(busPtr)); break;
case I_8266_BB_TM1914_3: delete (static_cast<B_8266_BB_TM1914_3*>(busPtr)); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: delete (static_cast<B_32_RN_NEO_3*>(busPtr)); break;
@ -1411,6 +1570,13 @@ class PolyBus {
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_2805_5: delete (static_cast<B_32_I1_2805_5*>(busPtr)); break;
#endif
case I_32_RN_TM1914_3: delete (static_cast<B_32_RN_TM1914_3*>(busPtr)); break;
#ifndef WLED_NO_I2S0_PIXELBUS
case I_32_I0_TM1914_3: delete (static_cast<B_32_I0_TM1914_3*>(busPtr)); break;
#endif
#ifndef WLED_NO_I2S1_PIXELBUS
case I_32_I1_TM1914_3: delete (static_cast<B_32_I1_TM1914_3*>(busPtr)); break;
#endif
#endif
case I_HS_DOT_3: delete (static_cast<B_HS_DOT_3*>(busPtr)); break;
case I_SS_DOT_3: delete (static_cast<B_SS_DOT_3*>(busPtr)); break;
@ -1476,6 +1642,8 @@ class PolyBus {
return I_8266_U0_FW6_5 + offset;
case TYPE_WS2805:
return I_8266_U0_2805_5 + offset;
case TYPE_TM1914:
return I_8266_U0_TM1914_3 + offset;
}
#else //ESP32
uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S0 (used by Audioreactive), 2 = I2S1
@ -1493,9 +1661,15 @@ class PolyBus {
//if (num > 3) offset = num -4; // I2S not supported yet
#else
// standard ESP32 has 8 RMT and 2 I2S channels
#ifdef WLED_USE_PARALLEL_I2S
if (num > 16) return I_NONE;
if (num < 8) offset = 2; // prefer 8 parallel I2S1 channels
if (num == 16) offset = 1;
#else
if (num > 9) return I_NONE;
if (num > 8) offset = 1;
if (num == 0) offset = 2; // prefer I2S1 for 1st bus (less flickering but more RAM needed)
#endif
#endif
switch (busType) {
case TYPE_WS2812_1CH_X3:
@ -1521,6 +1695,8 @@ class PolyBus {
return I_32_RN_FW6_5 + offset;
case TYPE_WS2805:
return I_32_RN_2805_5 + offset;
case TYPE_TM1914:
return I_32_RN_TM1914_3 + offset;
}
#endif
}

View File

@ -7,11 +7,13 @@
#define WLED_DEBOUNCE_THRESHOLD 50 // only consider button input of at least 50ms as valid (debouncing)
#define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms
#define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press
#define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0
#define WLED_LONG_REPEATED_ACTION 400 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0
#define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP
#define WLED_LONG_FACTORY_RESET 10000 // how long button 0 needs to be held to trigger a factory reset
#define WLED_LONG_BRI_STEPS 16 // how much to increase/decrease the brightness with each long press repetition
static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage
static bool buttonBriDirection = false; // true: increase brightness, false: decrease brightness
void shortPressAction(uint8_t b)
{
@ -39,7 +41,19 @@ void longPressAction(uint8_t b)
if (!macroLongPress[b]) {
switch (b) {
case 0: setRandomColor(col); colorUpdated(CALL_MODE_BUTTON); break;
case 1: bri += 8; stateUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action
case 1:
if(buttonBriDirection) {
if (bri == 255) break; // avoid unnecessary updates to brightness
if (bri >= 255 - WLED_LONG_BRI_STEPS) bri = 255;
else bri += WLED_LONG_BRI_STEPS;
} else {
if (bri == 1) break; // avoid unnecessary updates to brightness
if (bri <= WLED_LONG_BRI_STEPS) bri = 1;
else bri -= WLED_LONG_BRI_STEPS;
}
stateUpdated(CALL_MODE_BUTTON);
buttonPressedTime[b] = millis();
break; // repeatable action
}
} else {
applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
@ -284,10 +298,12 @@ void handleButton()
buttonPressedBefore[b] = true;
if (now - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press
if (!buttonLongPressed[b]) longPressAction(b);
else if (b) { //repeatable action (~3 times per s) on button > 0
if (!buttonLongPressed[b]) {
buttonBriDirection = !buttonBriDirection; //toggle brightness direction on long press
longPressAction(b);
buttonPressedTime[b] = now - WLED_LONG_REPEATED_ACTION; //333ms
} else if (b) { //repeatable action (~5 times per s) on button > 0
longPressAction(b);
buttonPressedTime[b] = now - WLED_LONG_REPEATED_ACTION; //200ms
}
buttonLongPressed[b] = true;
}
@ -379,7 +395,7 @@ void handleIO()
esp32RMTInvertIdle();
#endif
if (rlyPin>=0) {
pinMode(rlyPin, OUTPUT);
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
digitalWrite(rlyPin, rlyMde);
}
offMode = false;
@ -400,7 +416,7 @@ void handleIO()
esp32RMTInvertIdle();
#endif
if (rlyPin>=0) {
pinMode(rlyPin, OUTPUT);
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
digitalWrite(rlyPin, !rlyMde);
}
}

View File

@ -124,7 +124,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(strip.panels, matrix[F("mpc")]);
strip.panel.clear();
JsonArray panels = matrix[F("panels")];
uint8_t s = 0;
int s = 0;
if (!panels.isNull()) {
strip.panel.reserve(max(1U,min((size_t)strip.panels,(size_t)WLED_MAX_PANELS))); // pre-allocate memory for panels
for (JsonObject pnl : panels) {
@ -156,7 +156,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonArray ins = hw_led["ins"];
if (fromFS || !ins.isNull()) {
uint8_t s = 0; // bus iterator
int s = 0; // bus iterator
if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback
uint32_t mem = 0, globalBufMem = 0;
uint16_t maxlen = 0;
@ -335,12 +335,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(irApplyToAllSelected, hw["ir"]["sel"]);
JsonObject relay = hw[F("relay")];
rlyOpenDrain = relay[F("odrain")] | rlyOpenDrain;
int hw_relay_pin = relay["pin"] | -2;
if (hw_relay_pin > -2) {
pinManager.deallocatePin(rlyPin, PinOwner::Relay);
if (pinManager.allocatePin(hw_relay_pin,true, PinOwner::Relay)) {
rlyPin = hw_relay_pin;
pinMode(rlyPin, OUTPUT);
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
} else {
rlyPin = -1;
}
@ -788,7 +790,7 @@ void serializeConfig() {
JsonObject matrix = hw_led.createNestedObject(F("matrix"));
matrix[F("mpc")] = strip.panels;
JsonArray panels = matrix.createNestedArray(F("panels"));
for (uint8_t i=0; i<strip.panel.size(); i++) {
for (size_t i = 0; i < strip.panel.size(); i++) {
JsonObject pnl = panels.createNestedObject();
pnl["b"] = strip.panel[i].bottomStart;
pnl["r"] = strip.panel[i].rightStart;
@ -804,7 +806,7 @@ void serializeConfig() {
JsonArray hw_led_ins = hw_led.createNestedArray("ins");
for (uint8_t s = 0; s < BusManager::getNumBusses(); s++) {
for (size_t s = 0; s < BusManager::getNumBusses(); s++) {
Bus *bus = BusManager::getBus(s);
if (!bus || bus->getLength()==0) break;
JsonObject ins = hw_led_ins.createNestedObject();
@ -813,7 +815,7 @@ void serializeConfig() {
JsonArray ins_pin = ins.createNestedArray("pin");
uint8_t pins[5];
uint8_t nPins = bus->getPins(pins);
for (uint8_t i = 0; i < nPins; i++) ins_pin.add(pins[i]);
for (int i = 0; i < nPins; i++) ins_pin.add(pins[i]);
ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->isReversed();
ins[F("skip")] = bus->skippedLeds();
@ -827,7 +829,7 @@ void serializeConfig() {
JsonArray hw_com = hw.createNestedArray(F("com"));
const ColorOrderMap& com = BusManager::getColorOrderMap();
for (uint8_t s = 0; s < com.count(); s++) {
for (size_t s = 0; s < com.count(); s++) {
const ColorOrderMapEntry *entry = com.get(s);
if (!entry) break;
@ -844,7 +846,7 @@ void serializeConfig() {
JsonArray hw_btn_ins = hw_btn.createNestedArray("ins");
// configuration for all buttons
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject();
hw_btn_ins_0["type"] = buttonType[i];
JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin");
@ -868,6 +870,7 @@ void serializeConfig() {
JsonObject hw_relay = hw.createNestedObject(F("relay"));
hw_relay["pin"] = rlyPin;
hw_relay["rev"] = !rlyMde;
hw_relay[F("odrain")] = rlyOpenDrain;
hw[F("baud")] = serialBaud;

View File

@ -65,24 +65,30 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast)
* fades color toward black
* if using "video" method the resulting color will never become black unless it is already black
*/
uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
{
uint8_t r = R(c1);
uint8_t g = G(c1);
uint8_t b = B(c1);
uint8_t w = W(c1);
if (video) {
r = scale8_video(r, amount);
g = scale8_video(g, amount);
b = scale8_video(b, amount);
w = scale8_video(w, amount);
} else {
r = scale8(r, amount);
g = scale8(g, amount);
b = scale8(b, amount);
w = scale8(w, amount);
uint32_t scaledcolor; // color order is: W R G B from MSB to LSB
uint32_t r = R(c1);
uint32_t g = G(c1);
uint32_t b = B(c1);
uint32_t w = W(c1);
if (video) {
uint32_t scale = amount; // 32bit for faster calculation
scaledcolor = (((r * scale) >> 8) << 16) + ((r && scale) ? 1 : 0);
scaledcolor |= (((g * scale) >> 8) << 8) + ((g && scale) ? 1 : 0);
scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0);
scaledcolor |= (((w * scale) >> 8) << 24) + ((w && scale) ? 1 : 0);
return scaledcolor;
}
else {
uint32_t scale = 1 + amount;
scaledcolor = ((r * scale) >> 8) << 16;
scaledcolor |= ((g * scale) >> 8) << 8;
scaledcolor |= (b * scale) >> 8;
scaledcolor |= ((w * scale) >> 8) << 24;
return scaledcolor;
}
return RGBW32(r, g, b, w);
}
void setRandomColor(byte* rgb)

View File

@ -60,8 +60,12 @@
#define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog
#define WLED_MIN_VIRTUAL_BUSSES 4
#else
// the 10th digital bus (I2S0) will prevent Audioreactive usermod from functioning (it is last used though)
// the last digital bus (I2S0) will prevent Audioreactive usermod from functioning
#ifndef WLED_USE_PARALLEL_I2S
#define WLED_MAX_BUSSES 10
#else
#define WLED_MAX_BUSSES 17
#endif
#define WLED_MIN_VIRTUAL_BUSSES 0
#endif
#endif
@ -174,6 +178,9 @@
#define USERMOD_ID_HTTP_PULL_LIGHT_CONTROL 46 //usermod "usermod_v2_HttpPullLightControl.h"
#define USERMOD_ID_TETRISAI 47 //Usermod "usermod_v2_tetris.h"
#define USERMOD_ID_MAX17048 48 //Usermod "usermod_max17048.h"
#define USERMOD_ID_BME68X 49 //Usermod "usermod_bme68x.h
#define USERMOD_ID_INA226 50 //Usermod "usermod_ina226.h"
#define USERMOD_ID_AHT10 51 //Usermod "usermod_aht10.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@ -268,6 +275,7 @@
#define TYPE_SK6812_RGBW 30
#define TYPE_TM1814 31
#define TYPE_WS2805 32 //RGB + WW + CW
#define TYPE_TM1914 33 //RGB
//"Analog" types (40-47)
#define TYPE_ONOFF 40 //binary output (relays etc.; NOT PWM)
#define TYPE_ANALOG_1CH 41 //single channel PWM. Uses value of brightest RGBW channel

View File

@ -358,7 +358,7 @@ button {
#putil, #segutil, #segutil2 {
min-height: 42px;
margin: 13px auto 0;
margin: 0 auto;
}
#segutil .segin {

View File

@ -272,6 +272,7 @@ function onLoad()
selectSlot(0);
updateTablinks(0);
handleLocationHash();
cpick.on("input:end", () => {setColor(1);});
cpick.on("color:change", () => {updatePSliders()});
pmtLS = localStorage.getItem('wledPmt');
@ -304,7 +305,6 @@ function updateTablinks(tabI)
{
var tablinks = gEBCN("tablinks");
for (var i of tablinks) i.classList.remove('active');
if (pcMode) return;
tablinks[tabI].classList.add('active');
}
@ -315,6 +315,21 @@ function openTab(tabI, force = false)
_C.classList.toggle('smooth', false);
_C.style.setProperty('--i', iSlide);
updateTablinks(tabI);
switch (tabI) {
case 0: window.location.hash = "Colors"; break;
case 1: window.location.hash = "Effects"; break;
case 2: window.location.hash = "Segments"; break;
case 3: window.location.hash = "Presets"; break;
}
}
function handleLocationHash() {
switch (window.location.hash) {
case "#Colors": openTab(0); break;
case "#Effects": openTab(1); break;
case "#Segments": openTab(2); break;
case "#Presets": openTab(3); break;
}
}
var timeout;
@ -430,7 +445,7 @@ function presetError(empty)
if (bckstr.length > 10) hasBackup = true;
} catch (e) {}
var cn = `<div class="pres c" ${empty?'style="padding:8px;margin-top: 16px;"':'onclick="pmtLast=0;loadPresets();" style="cursor:pointer;padding:8px;margin-top: 16px;"'}>`;
var cn = `<div class="pres c" style="padding:8px;margin-bottom:8px;${empty?'':'cursor:pointer;'}" ${empty?'':'onclick="pmtLast=0;loadPresets();"'}>`;
if (empty)
cn += `You have no presets yet!`;
else
@ -442,8 +457,8 @@ function presetError(empty)
cn += `However, there is backup preset data of a previous installation available.<br>(Saving a preset will hide this and overwrite the backup)`;
else
cn += `Here is a backup of the last known good state:`;
cn += `<textarea id="bck"></textarea><br><button class="btn" onclick="cpBck()">Copy to clipboard</button>`;
cn += `<br><button type="button" class="btn" onclick="restore(gId('bck').value)">Restore</button>`;
cn += `<textarea id="bck"></textarea><br><button class="btn" style="margin-top:12px;" onclick="cpBck()">Copy to clipboard</button>`;
cn += `<br><button type="button" class="btn" style="margin-top:12px;" onclick="restore(gId('bck').value)">Restore</button>`;
}
cn += `</div>`;
gId('pcont').innerHTML = cn;
@ -786,6 +801,7 @@ function populateSegments(s)
`<option value="1" ${inst.m12==1?' selected':''}>Bar</option>`+
`<option value="2" ${inst.m12==2?' selected':''}>Arc</option>`+
`<option value="3" ${inst.m12==3?' selected':''}>Corner</option>`+
`<option value="4" ${inst.m12==4?' selected':''}>Pinwheel</option>`+
`</select></div>`+
`</div>`;
let sndSim = `<div data-snd="si" class="lbl-s hide">Sound sim<br>`+
@ -863,14 +879,11 @@ function populateSegments(s)
gId("segcont").classList.remove("hide");
let noNewSegs = (lowestUnused >= maxSeg);
resetUtil(noNewSegs);
if (gId('selall')) gId('selall').checked = true;
for (var i = 0; i <= lSeg; i++) {
if (!gId(`seg${i}`)) continue;
updateLen(i);
updateTrail(gId(`seg${i}bri`));
gId(`segr${i}`).classList.add("hide");
//if (i<lSeg) gId(`segd${i}`).classList.add("hide"); // hide delete button for all but last
if (!gId(`seg${i}sel`).checked && gId('selall')) gId('selall').checked = false; // uncheck if at least one is unselected.
}
if (segCount < 2) {
gId(`segd${lSeg}`).classList.add("hide"); // hide delete if only one segment
@ -1462,8 +1475,6 @@ function readState(s,command=false)
return true;
}
if (s.seg.length>2) d.querySelectorAll(".pop").forEach((e)=>{e.classList.remove("hide");});
var cd = gId('csl').querySelectorAll("button");
for (let e = cd.length-1; e >= 0; e--) {
cd[e].dataset.r = i.col[e][0];
@ -1838,7 +1849,7 @@ function makeSeg()
});
var cn = `<div class="seg lstI expanded">`+
`<div class="segin">`+
`<input type="text" id="seg${lu}t" autocomplete="off" maxlength=32 value="" placeholder="New segment ${lu}"/>`+
`<input class="ptxt show" type="text" id="seg${lu}t" autocomplete="off" maxlength=32 value="" placeholder="New segment ${lu}"/>`+
`<table class="segt">`+
`<tr>`+
`<td width="38%">${isM?'Start X':'Start LED'}</td>`+
@ -1864,13 +1875,19 @@ function makeSeg()
function resetUtil(off=false)
{
gId('segutil').innerHTML = `<div class="seg btn btn-s${off?' off':''}" style="padding:0;">`
gId('segutil').innerHTML = `<div class="seg btn btn-s${off?' off':''}" style="padding:0;margin-bottom:12px;">`
+ '<label class="check schkl"><input type="checkbox" id="selall" onchange="selSegAll(this)"><span class="checkmark"></span></label>'
+ `<div class="segname" ${off?'':'onclick="makeSeg()"'}><i class="icons btn-icon">&#xe18a;</i>Add segment</div>`
+ '<div class="pop hide" onclick="event.stopPropagation();">'
+ `<i class="icons g-icon" title="Select group" onclick="this.nextElementSibling.classList.toggle('hide');">&#xE34B;</i>`
+ '<div class="pop-c hide"><span style="color:var(--c-f);" onclick="selGrp(0);">&#x278A;</span><span style="color:var(--c-r);" onclick="selGrp(1);">&#x278B;</span><span style="color:var(--c-g);" onclick="selGrp(2);">&#x278C;</span><span style="color:var(--c-l);" onclick="selGrp(3);">&#x278D;</span></div>'
+ '</div></div>';
gId('selall').checked = true;
for (var i = 0; i <= lSeg; i++) {
if (!gId(`seg${i}`)) continue;
if (!gId(`seg${i}sel`).checked) gId('selall').checked = false; // uncheck if at least one is unselected.
}
if (lSeg>2) d.querySelectorAll("#Segments .pop").forEach((e)=>{e.classList.remove("hide");});
}
function makePlSel(el, incPl=false)
@ -3050,8 +3067,7 @@ function togglePcMode(fromB = false)
pcMode = (wW >= 1024) && pcModeA;
if (cpick) cpick.resize(pcMode && wW>1023 && wW<1250 ? 230 : 260); // for tablet in landscape
if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size()
openTab(0, true);
updateTablinks(0);
if (pcMode) openTab(0, true);
gId('buttonPcm').className = (pcMode) ? "active":"";
gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto";
sCol('--bh', gId('bot').clientHeight + "px");
@ -3213,6 +3229,7 @@ size();
_C.style.setProperty('--n', N);
window.addEventListener('resize', size, true);
window.addEventListener('hashchange', handleLocationHash);
_C.addEventListener('mousedown', lock, false);
_C.addEventListener('touchstart', lock, false);

View File

@ -5,12 +5,12 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>LED Settings</title>
<script>
var d=document,laprev=55,maxB=1,maxV=0,maxM=4000,maxPB=4096,maxL=1333,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var d=document,laprev=55,maxB=1,maxV=0,maxM=4000,maxPB=4096,maxL=1333,maxCO=10,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
d.um_p = [];
d.rsvd = [];
d.ro_gpio = [];
d.max_gpio = 50;
var customStarts=false,startsDirty=[],maxCOOverrides=5;
var customStarts=false,startsDirty=[];
var loc = false, locip, locproto = "http:";
function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");}
function B(){window.open(getURL("/settings"),"_self");}
@ -57,8 +57,8 @@
x.style.animation = 'none';
timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
}
function bLimits(b,v,p,m,l) {
maxB = b; maxV = v; maxM = m; maxPB = p; maxL = l;
function bLimits(b,v,p,m,l,o) {
maxB = b; maxV = v; maxM = m; maxPB = p; maxL = l; maxCO = o;
}
function pinsOK() {
var ok = true;
@ -138,7 +138,8 @@
gId("psuMA").style.display = ppl ? 'none' : 'inline';
gId("ppldis").style.display = ppl ? 'inline' : 'none';
// set PPL minimum value and clear actual PPL limit if ABL disabled
d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,n)=>{
d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,x)=>{
var n = String.fromCharCode((x<10?48:55)+x);
gId("PSU"+n).style.display = ppl ? "inline" : "none";
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
const c = parseInt(d.Sf["LC"+n].value); //get LED count
@ -153,7 +154,7 @@
{
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
gId('LAdis'+n).style.display = s.selectedIndex==5 ? "inline" : "none";
d.Sf["LA"+n].value = s.value==="0" ? 55 : s.value;
if (s.value!=="0") d.Sf["LA"+n].value = s.value;
d.Sf["LA"+n].min = (isVir(t) || isAna(t)) ? 0 : 1;
}
function setABL()
@ -164,8 +165,9 @@
if (parseInt(i.value) > 0) d.Sf.ABL.checked = true;
});
// select appropriate LED current
d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,n)=>{
d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{
sel.value = 0; // set custom
var n = String.fromCharCode((x<10?48:55)+x);
switch (parseInt(d.Sf["LA"+n].value)) {
case 0: break; // disable ABL
case 15: sel.value = 15; break;
@ -358,10 +360,11 @@
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
}
function lastEnd(i) {
if (i<1) return 0;
v = parseInt(d.getElementsByName("LS"+(i-1))[0].value) + parseInt(d.getElementsByName("LC"+(i-1))[0].value);
var t = parseInt(d.getElementsByName("LT"+(i-1))[0].value);
if (t > 31 && t < 48) v = 1; //PWM busses
if (i-- < 1) return 0;
var s = String.fromCharCode((i<10?48:55)+i);
v = parseInt(d.getElementsByName("LS"+s)[0].value) + parseInt(d.getElementsByName("LC"+s)[0].value);
var t = parseInt(d.getElementsByName("LT"+s)[0].value);
if (isPWM(t)) v = 1; //PWM busses
if (isNaN(v)) return 0;
return v;
}
@ -371,6 +374,7 @@
var i = o.length;
if ((n==1 && i>=maxB+maxV) || (n==-1 && i==0)) return;
var s = String.fromCharCode((i<10?48:55)+i);
var f = gId("mLC");
if (n==1) {
@ -378,7 +382,7 @@
var cn = `<div class="iST">
<hr class="sml">
${i+1}:
<select name="LT${i}" onchange="UI(true)">${i>=maxB ? '' :
<select name="LT${s}" onchange="UI(true)">${i>=maxB ? '' :
'<option value="22" selected>WS281x</option>\
<option value="30">SK6812/WS2814 RGBW</option>\
<option value="31">TM1814</option>\
@ -386,6 +390,7 @@ ${i+1}:
<option value="25">TM1829</option>\
<option value="26">UCS8903</option>\
<option value="27">APA106/PL9823</option>\
<option value="33">TM1914</option>\
<option value="28">FW1906 GRBCW</option>\
<option value="29">UCS8904 RGBW</option>\
<option value="32">WS2805 RGBCW</option>\
@ -408,8 +413,8 @@ ${i+1}:
<option value="88">DDP RGBW (network)</option>
<option value="89">Art-Net RGBW (network)</option>
</select><br>
<div id="abl${i}">
mA/LED: <select name="LAsel${i}" onchange="enLA(this,${i});UI();">
<div id="abl${s}">
mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
<option value="55" selected>55mA (typ. 5V WS281x)</option>
<option value="35">35mA (eco WS2812)</option>
<option value="30">30mA (typ. 12V)</option>
@ -417,11 +422,11 @@ mA/LED: <select name="LAsel${i}" onchange="enLA(this,${i});UI();">
<option value="15">15mA (seed/fairy pixels)</option>
<option value="0">Custom</option>
</select><br>
<div id="LAdis${i}" style="display: none;">max. mA/LED: <input name="LA${i}" type="number" min="1" max="254" oninput="UI()"> mA<br></div>
<div id="PSU${i}">PSU: <input name="MA${i}" type="number" class="xl" min="250" max="65000" oninput="UI()" value="250"> mA<br></div>
<div id="LAdis${s}" style="display: none;">max. mA/LED: <input name="LA${s}" type="number" min="1" max="255" oninput="UI()"> mA<br></div>
<div id="PSU${s}">PSU: <input name="MA${s}" type="number" class="xl" min="250" max="65000" oninput="UI()" value="250"> mA<br></div>
</div>
<div id="co${i}" style="display:inline">Color Order:
<select name="CO${i}">
<div id="co${s}" style="display:inline">Color Order:
<select name="CO${s}">
<option value="0">GRB</option>
<option value="1">RGB</option>
<option value="2">BRG</option>
@ -429,21 +434,21 @@ mA/LED: <select name="LAsel${i}" onchange="enLA(this,${i});UI();">
<option value="4">BGR</option>
<option value="5">GBR</option>
</select></div>
<div id="dig${i}w" style="display:none">Swap: <select name="WO${i}"><option value="0">None</option><option value="1">W & B</option><option value="2">W & G</option><option value="3">W & R</option></select></div>
<div id="dig${i}l" style="display:none">Clock: <select name="SP${i}"><option value="0">Slowest</option><option value="1">Slow</option><option value="2">Normal</option><option value="3">Fast</option><option value="4">Fastest</option></select></div>
<div id="dig${s}w" style="display:none">Swap: <select name="WO${s}"><option value="0">None</option><option value="1">W & B</option><option value="2">W & G</option><option value="3">W & R</option></select></div>
<div id="dig${s}l" style="display:none">Clock: <select name="SP${s}"><option value="0">Slowest</option><option value="1">Slow</option><option value="2">Normal</option><option value="3">Fast</option><option value="4">Fastest</option></select></div>
<div>
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp;
<div id="dig${i}c" style="display:inline">Length: <input type="number" name="LC${i}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div><br>
<span id="psd${s}">Start:</span> <input type="number" name="LS${s}" id="ls${s}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp;
<div id="dig${s}c" style="display:inline">Length: <input type="number" name="LC${s}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div><br>
</div>
<span id="p0d${i}">GPIO:</span><input type="number" name="L0${i}" required class="s" onchange="UI();pinUpd(this);"/>
<span id="p1d${i}"></span><input type="number" name="L1${i}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p2d${i}"></span><input type="number" name="L2${i}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p3d${i}"></span><input type="number" name="L3${i}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" class="s" onchange="UI();pinUpd(this);"/>
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
<div id="dig${i}a" style="display:inline"><br>Auto-calculate white channel from RGB:<br><select name="AW${i}"><option value=0>None</option><option value=1>Brighter</option><option value=2>Accurate</option><option value=3>Dual</option><option value=4>Max</option></select>&nbsp;</div>
<span id="p0d${s}">GPIO:</span><input type="number" name="L0${s}" required class="s" onchange="UI();pinUpd(this);"/>
<span id="p1d${s}"></span><input type="number" name="L1${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p2d${s}"></span><input type="number" name="L2${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p3d${s}"></span><input type="number" name="L3${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p4d${s}"></span><input type="number" name="L4${s}" class="s" onchange="UI();pinUpd(this);"/>
<div id="dig${s}r" style="display:inline"><br><span id="rev${s}">Reversed</span>: <input type="checkbox" name="CV${s}"></div>
<div id="dig${s}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${s}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${s}f" style="display:inline"><br>Off Refresh: <input id="rf${s}" type="checkbox" name="RF${s}"></div>
<div id="dig${s}a" style="display:inline"><br>Auto-calculate white channel from RGB:<br><select name="AW${s}"><option value=0>None</option><option value=1>Brighter</option><option value=2>Accurate</option><option value=3>Dual</option><option value=4>Max</option></select>&nbsp;</div>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
}
@ -461,14 +466,14 @@ mA/LED: <select name="LAsel${i}" onchange="enLA(this,${i});UI();">
function addCOM(start=0,len=1,co=0) {
var i = d.getElementsByClassName("com_entry").length;
if (i >= 10) return;
if (i >= maxCO) return;
var s = String.fromCharCode((i<10?48:55)+i);
var b = `<div class="com_entry">
<hr class="sml">
${i+1}: Start: <input type="number" name="XS${i}" id="xs${i}" class="l starts" min="0" max="65535" value="${start}" oninput="UI();" required="">&nbsp;
Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65535" value="${len}" required="" oninput="UI()">
${i+1}: Start: <input type="number" name="XS${s}" id="xs${s}" class="l starts" min="0" max="65535" value="${start}" oninput="UI();" required="">&nbsp;
Length: <input type="number" name="XC${s}" id="xc${s}" class="l" min="1" max="65535" value="${len}" required="" oninput="UI()">
<div>Color Order:
<select id="xo${i}" name="XO${i}">
<select id="xo${s}" name="XO${s}">
<option value="0">GRB</option>
<option value="1">RGB</option>
<option value="2">BRG</option>
@ -476,7 +481,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
<option value="4">BGR</option>
<option value="5">GBR</option>
</select>
Swap: <select id="xw${i}" name="XW${i}">
Swap: <select id="xw${s}" name="XW${s}">
<option value="0">Use global</option>
<option value="1">W & B</option>
<option value="2">W & G</option>
@ -484,8 +489,8 @@ Swap: <select id="xw${i}" name="XW${i}">
</select>
</div></div>`;
gId("com_entries").insertAdjacentHTML("beforeend", b);
gId("xo"+i).value = co & 0x0F;
gId("xw"+i).value = co >> 4;
gId("xo"+s).value = co & 0x0F;
gId("xw"+s).value = co >> 4;
btnCOM(i+1);
UI();
}
@ -501,7 +506,7 @@ Swap: <select id="xw${i}" name="XW${i}">
function resetCOM(_newMaxCOOverrides=undefined) {
if (_newMaxCOOverrides) {
maxCOOverrides = _newMaxCOOverrides;
maxCO = _newMaxCOOverrides;
}
for (let e of d.getElementsByClassName("com_entry")) {
e.remove();
@ -510,16 +515,15 @@ Swap: <select id="xw${i}" name="XW${i}">
}
function btnCOM(i) {
gId("com_add").style.display = (i<maxCOOverrides) ? "inline":"none";
gId("com_add").style.display = (i<maxCO) ? "inline":"none";
gId("com_rem").style.display = (i>0) ? "inline":"none";
}
function addBtn(i,p,t) {
var c = gId("btns").innerHTML;
var bt = "BT" + String.fromCharCode((i<10?48:55)+i);
var be = "BE" + String.fromCharCode((i<10?48:55)+i);
c += `Button ${i} GPIO: <input type="number" name="${bt}" onchange="UI()" class="xs" value="${p}">`;
c += `&nbsp;<select name="${be}">`
var s = String.fromCharCode((i<10?48:55)+i);
c += `Button ${i} GPIO: <input type="number" name="BT${s}" onchange="UI()" class="xs" value="${p}">`;
c += `&nbsp;<select name="BE${s}">`
c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`;
c += `<option value="2" ${t==2?"selected":""}>Pushbutton</option>`;
c += `<option value="3" ${t==3?"selected":""}>Push inverted</option>`;
@ -530,7 +534,7 @@ Swap: <select id="xw${i}" name="XW${i}">
c += `<option value="8" ${t==8?"selected":""}>Analog inverted</option>`;
c += `<option value="9" ${t==9?"selected":""}>Touch (switch)</option>`;
c += `</select>`;
c += `<span style="cursor: pointer;" onclick="off('${bt}')">&nbsp;&#x2715;</span><br>`;
c += `<span style="cursor: pointer;" onclick="off('BT${s}')">&nbsp;&#x2715;</span><br>`;
gId("btns").innerHTML = c;
}
function tglSi(cs) {
@ -619,7 +623,8 @@ Swap: <select id="xw${i}" name="XW${i}">
}
if (c.hw.relay) {
d.getElementsByName("RL")[0].value = c.hw.relay.pin;
d.getElementsByName("RM")[0].checked = c.hw.relay.inv;
d.getElementsByName("RM")[0].checked = c.hw.relay.rev;
d.getElementsByName("RO")[0].checked = c.hw.relay.odrain;
}
UI();
}
@ -822,7 +827,7 @@ Swap: <select id="xw${i}" name="XW${i}">
Apply IR change to main segment only: <input type="checkbox" name="MSO"><br>
<div id="json" style="display:none;">JSON file: <input type="file" name="data" accept=".json"><button type="button" class="sml" onclick="uploadFile('/ir.json')">Upload</button><br></div>
<a href="https://kno.wled.ge/interfaces/infrared/" target="_blank">IR info</a><br>
Relay GPIO: <input type="number" min="-1" max="48" name="RL" onchange="UI()" class="xs"> Invert <input type="checkbox" name="RM"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#x2715;</span><br>
Relay GPIO: <input type="number" min="-1" max="48" name="RL" onchange="UI()" class="xs"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#x2715;</span> Invert <input type="checkbox" name="RM"> Open drain <input type="checkbox" name="RO"><br>
<hr class="sml">
<h3>Defaults</h3>
Turn LEDs on after power up/reset: <input type="checkbox" name="BO"><br>

View File

@ -137,7 +137,7 @@
<a href="https://github.com/Aircoookie/WLED/wiki/Contributors-and-credits" target="_blank">Contributors, dependencies and special thanks</a><br>
A huge thank you to everyone who helped me create WLED!<br><br>
(c) 2016-2024 Christian Schwinne <br>
<i>Licensed under the&#32;<a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">MIT license</a></i><br><br>
<i>Licensed under the <a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">MIT license</a></i><br><br>
Server message: <span class="sip"> Response error! </span><hr>
<div id="toast"></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>

View File

@ -52,40 +52,42 @@
}
scanLoops = 0;
let cs = d.querySelectorAll("#wifi_entries input[type=text]");
for (let input of (cs||[])) {
let found = false;
let select = cE("select");
select.id = input.id;
select.name = input.name;
select.setAttribute("onchange", "T(this)");
preScanSSID = input.value;
if (networks.length > 0) {
let cs = d.querySelectorAll("#wifi_entries input[type=text]");
for (let input of (cs||[])) {
let found = false;
let select = cE("select");
select.id = input.id;
select.name = input.name;
select.setAttribute("onchange", "T(this)");
preScanSSID = input.value;
for (let i = 0; i < select.children.length; i++) {
select.removeChild(select.children[i]);
}
for (let i = 0; i < networks.length; i++) {
const option = cE("option");
option.setAttribute("value", networks[i].ssid);
option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`;
if (networks[i].ssid === input.value) {
option.setAttribute("selected", "selected");
found = true;
for (let i = 0; i < select.children.length; i++) {
select.removeChild(select.children[i]);
}
for (let i = 0; i < networks.length; i++) {
const option = cE("option");
option.setAttribute("value", networks[i].ssid);
option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`;
if (networks[i].ssid === input.value) {
option.setAttribute("selected", "selected");
found = true;
}
select.appendChild(option);
}
const option = cE("option");
option.setAttribute("value", "!Cs");
option.textContent = "Other network...";
select.appendChild(option);
if (input.value === "" || input.value === "Your_Network" || found) input.replaceWith(select);
else select.remove();
}
const option = cE("option");
option.setAttribute("value", "!Cs");
option.textContent = "Other network...";
select.appendChild(option);
if (input.value === "" || input.value === "Your_Network" || found) input.replaceWith(select);
else select.remove();
}
button.disabled = false;

View File

@ -346,7 +346,6 @@ void handleArtnetPollReply(IPAddress ipAddress) {
switch (DMXMode) {
case DMX_MODE_DISABLED:
return; // nothing to do
break;
case DMX_MODE_SINGLE_RGB:
@ -391,9 +390,17 @@ void handleArtnetPollReply(IPAddress ipAddress) {
break;
}
for (uint16_t i = startUniverse; i <= endUniverse; ++i) {
sendArtnetPollReply(&artnetPollReply, ipAddress, i);
if (DMXMode != DMX_MODE_DISABLED) {
for (uint16_t i = startUniverse; i <= endUniverse; ++i) {
sendArtnetPollReply(&artnetPollReply, ipAddress, i);
}
}
#ifdef WLED_ENABLE_DMX
if (e131ProxyUniverse > 0 && (DMXMode == DMX_MODE_DISABLED || (e131ProxyUniverse < startUniverse || e131ProxyUniverse > endUniverse))) {
sendArtnetPollReply(&artnetPollReply, ipAddress, e131ProxyUniverse);
}
#endif
}
void prepareArtnetPollReply(ArtPollReply *reply) {

View File

@ -143,20 +143,8 @@ void handleImprovWifiScan();
void sendImprovIPRPCResult(ImprovRPCType type);
//ir.cpp
void applyRepeatActions();
byte relativeChange(byte property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF);
void decodeIR(uint32_t code);
void decodeIR24(uint32_t code);
void decodeIR24OLD(uint32_t code);
void decodeIR24CT(uint32_t code);
void decodeIR40(uint32_t code);
void decodeIR44(uint32_t code);
void decodeIR21(uint32_t code);
void decodeIR6(uint32_t code);
void decodeIR9(uint32_t code);
void decodeIRJson(uint32_t code);
void initIR();
void deInitIR();
void handleIR();
//json.cpp
@ -410,7 +398,7 @@ void clearEEPROM();
#endif
//wled_math.cpp
#ifndef WLED_USE_REAL_MATH
#if defined(ESP8266) && !defined(WLED_USE_REAL_MATH)
template <typename T> T atan_t(T x);
float cos_t(float phi);
float sin_t(float x);
@ -421,14 +409,14 @@ void clearEEPROM();
float fmod_t(float num, float denom);
#else
#include <math.h>
#define sin_t sin
#define cos_t cos
#define tan_t tan
#define asin_t asin
#define acos_t acos
#define atan_t atan
#define fmod_t fmod
#define floor_t floor
#define sin_t sinf
#define cos_t cosf
#define tan_t tanf
#define asin_t asinf
#define acos_t acosf
#define atan_t atanf
#define fmod_t fmodf
#define floor_t floorf
#endif
//wled_serial.cpp

View File

@ -381,11 +381,15 @@ void updateFSInfo() {
// original idea by @akaricchi (https://github.com/Akaricchi)
// returns a pointer to the PSRAM buffer, updates size parameter
static const uint8_t *getPresetCache(size_t &size) {
static unsigned long presetsCachedTime;
static uint8_t *presetsCached;
static size_t presetsCachedSize;
static unsigned long presetsCachedTime = 0;
static uint8_t *presetsCached = nullptr;
static size_t presetsCachedSize = 0;
static byte presetsCachedValidate = 0;
if (presetsModifiedTime != presetsCachedTime) {
//if (presetsModifiedTime != presetsCachedTime) DEBUG_PRINTLN(F("getPresetCache(): presetsModifiedTime changed."));
//if (presetsCachedValidate != cacheInvalidate) DEBUG_PRINTLN(F("getPresetCache(): cacheInvalidate changed."));
if ((presetsModifiedTime != presetsCachedTime) || (presetsCachedValidate != cacheInvalidate)) {
if (presetsCached) {
free(presetsCached);
presetsCached = nullptr;
@ -396,6 +400,7 @@ static const uint8_t *getPresetCache(size_t &size) {
File file = WLED_FS.open(FPSTR(getPresetsFileName()), "r");
if (file) {
presetsCachedTime = presetsModifiedTime;
presetsCachedValidate = cacheInvalidate;
presetsCachedSize = 0;
presetsCached = (uint8_t*)ps_malloc(file.size() + 1);
if (presetsCached) {

View File

@ -210,7 +210,7 @@ void sendImprovInfoResponse() {
//Use serverDescription if it has been changed from the default "WLED", else mDNS name
bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0);
char vString[20];
sprintf_P(vString, PSTR("0.15.0-b2/%i"), VERSION);
sprintf_P(vString, PSTR("0.15.0-b3/%i"), VERSION);
const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription};
sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str);

View File

@ -1,20 +1,14 @@
#include "wled.h"
#ifndef WLED_DISABLE_INFRARED
#include "ir_codes.h"
/*
* Infrared sensor support for generic 24/40/44 key RGB remotes
* Infrared sensor support for several generic RGB remotes and custom JSON remote
*/
#if defined(WLED_DISABLE_INFRARED)
void handleIR(){}
#else
IRrecv* irrecv;
//change pin in NpbWrapper.h
decode_results results;
unsigned long irCheckedTime = 0;
uint32_t lastValidCode = 0;
byte lastRepeatableAction = ACTION_NONE;
@ -35,16 +29,16 @@ uint8_t lastIR6ColourIdx = 0;
// print("%d values: %s" % (len(result), result))
//
// It would be hard to maintain repeatable steps if calculating this on the fly.
const byte brightnessSteps[] = {
const uint8_t brightnessSteps[] = {
5, 7, 9, 12, 16, 20, 26, 34, 43, 56, 72, 93, 119, 154, 198, 255
};
const size_t numBrightnessSteps = sizeof(brightnessSteps) / sizeof(uint8_t);
// increment `bri` to the next `brightnessSteps` value
void incBrightness()
static void incBrightness()
{
// dumb incremental search is efficient enough for so few items
for (uint8_t index = 0; index < numBrightnessSteps; ++index)
for (unsigned index = 0; index < numBrightnessSteps; ++index)
{
if (brightnessSteps[index] > bri)
{
@ -56,7 +50,7 @@ void incBrightness()
}
// decrement `bri` to the next `brightnessSteps` value
void decBrightness()
static void decBrightness()
{
// dumb incremental search is efficient enough for so few items
for (int index = numBrightnessSteps - 1; index >= 0; --index)
@ -70,12 +64,12 @@ void decBrightness()
}
}
void presetFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID)
static void presetFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID)
{
applyPresetWithFallback(presetID, CALL_MODE_BUTTON_PRESET, effectID, paletteID);
}
byte relativeChange(byte property, int8_t amount, byte lowerBoundary, byte higherBoundary)
static byte relativeChange(byte property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF)
{
int16_t new_val = (int16_t) property + amount;
if (lowerBoundary >= higherBoundary) return property;
@ -84,10 +78,10 @@ byte relativeChange(byte property, int8_t amount, byte lowerBoundary, byte highe
return (byte)constrain(new_val, 0, 255);
}
void changeEffect(uint8_t fx)
static void changeEffect(uint8_t fx)
{
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
strip.setMode(i, fx);
@ -100,10 +94,10 @@ void changeEffect(uint8_t fx)
stateChanged = true;
}
void changePalette(uint8_t pal)
static void changePalette(uint8_t pal)
{
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.setPalette(pal);
@ -116,13 +110,13 @@ void changePalette(uint8_t pal)
stateChanged = true;
}
void changeEffectSpeed(int8_t amount)
static void changeEffectSpeed(int8_t amount)
{
if (effectCurrent != 0) {
int16_t new_val = (int16_t) effectSpeed + amount;
effectSpeed = (byte)constrain(new_val,0,255);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.speed = effectSpeed;
@ -134,10 +128,7 @@ void changeEffectSpeed(int8_t amount)
}
} else { // if Effect == "solid Color", change the hue of the primary color
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col;
fastled_col.red = R(sseg.colors[0]);
fastled_col.green = G(sseg.colors[0]);
fastled_col.blue = B(sseg.colors[0]);
CRGB fastled_col = CRGB(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col);
int16_t new_val = (int16_t)prim_hsv.h + amount;
if (new_val > 255) new_val -= 255; // roll-over if bigger than 255
@ -145,7 +136,7 @@ void changeEffectSpeed(int8_t amount)
prim_hsv.h = (byte)new_val;
hsv2rgb_rainbow(prim_hsv, fastled_col);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
@ -163,13 +154,13 @@ void changeEffectSpeed(int8_t amount)
lastRepeatableValue = amount;
}
void changeEffectIntensity(int8_t amount)
static void changeEffectIntensity(int8_t amount)
{
if (effectCurrent != 0) {
int16_t new_val = (int16_t) effectIntensity + amount;
effectIntensity = (byte)constrain(new_val,0,255);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.intensity = effectIntensity;
@ -181,16 +172,13 @@ void changeEffectIntensity(int8_t amount)
}
} else { // if Effect == "solid Color", change the saturation of the primary color
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col;
fastled_col.red = R(sseg.colors[0]);
fastled_col.green = G(sseg.colors[0]);
fastled_col.blue = B(sseg.colors[0]);
CRGB fastled_col = CRGB(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col);
int16_t new_val = (int16_t) prim_hsv.s + amount;
prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255
hsv2rgb_rainbow(prim_hsv, fastled_col);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
@ -208,11 +196,11 @@ void changeEffectIntensity(int8_t amount)
lastRepeatableValue = amount;
}
void changeColor(uint32_t c, int16_t cct=-1)
static void changeColor(uint32_t c, int16_t cct=-1)
{
if (irApplyToAllSelected) {
// main segment may not be selected!
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
byte capabilities = seg.getLightCapabilities();
@ -249,7 +237,7 @@ void changeColor(uint32_t c, int16_t cct=-1)
stateChanged = true;
}
void changeWhite(int8_t amount, int16_t cct=-1)
static void changeWhite(int8_t amount, int16_t cct=-1)
{
Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
byte r = R(seg.colors[0]);
@ -259,72 +247,7 @@ void changeWhite(int8_t amount, int16_t cct=-1)
changeColor(RGBW32(r, g, b, w), cct);
}
void decodeIR(uint32_t code)
{
if (code == 0xFFFFFFFF) {
//repeated code, continue brightness up/down
irTimesRepeated++;
applyRepeatActions();
return;
}
lastValidCode = 0; irTimesRepeated = 0;
lastRepeatableAction = ACTION_NONE;
if (irEnabled == 8) { // any remote configurable with ir.json file
decodeIRJson(code);
stateUpdated(CALL_MODE_BUTTON);
return;
}
if (code > 0xFFFFFF) return; //invalid code
switch (irEnabled) {
case 1:
if (code > 0xF80000) decodeIR24OLD(code); // white 24-key remote (old) - it sends 0xFF0000 values
else decodeIR24(code); // 24-key remote - 0xF70000 to 0xF80000
break;
case 2: decodeIR24CT(code); break; // white 24-key remote with CW, WW, CT+ and CT- keys
case 3: decodeIR40(code); break; // blue 40-key remote with 25%, 50%, 75% and 100% keys
case 4: decodeIR44(code); break; // white 44-key remote with color-up/down keys and DIY1 to 6 keys
case 5: decodeIR21(code); break; // white 21-key remote
case 6: decodeIR6(code); break; // black 6-key learning remote defaults: "CH" controls brightness,
// "VOL +" controls effect, "VOL -" controls colour/palette, "MUTE"
// sets bright plain white
case 7: decodeIR9(code); break;
//case 8: return; // ir.json file, handled above switch statement
}
if (nightlightActive && bri == 0) nightlightActive = false;
stateUpdated(CALL_MODE_BUTTON); //for notifier, IR is considered a button input
}
void applyRepeatActions()
{
if (irEnabled == 8) {
decodeIRJson(lastValidCode);
return;
} else switch (lastRepeatableAction) {
case ACTION_BRIGHT_UP : incBrightness(); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_BRIGHT_DOWN : decBrightness(); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_SPEED_UP : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_SPEED_DOWN : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_INTENSITY_UP : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_INTENSITY_DOWN : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
default: break;
}
if (lastValidCode == IR40_WPLUS) {
changeWhite(10);
stateUpdated(CALL_MODE_BUTTON);
} else if (lastValidCode == IR40_WMINUS) {
changeWhite(-10);
stateUpdated(CALL_MODE_BUTTON);
} else if ((lastValidCode == IR24_ON || lastValidCode == IR40_ON) && irTimesRepeated > 7 ) {
nightlightActive = true;
nightlightStartTime = millis();
stateUpdated(CALL_MODE_BUTTON);
}
}
void decodeIR24(uint32_t code)
static void decodeIR24(uint32_t code)
{
switch (code) {
case IR24_BRIGHTER : incBrightness(); break;
@ -356,7 +279,7 @@ void decodeIR24(uint32_t code)
lastValidCode = code;
}
void decodeIR24OLD(uint32_t code)
static void decodeIR24OLD(uint32_t code)
{
switch (code) {
case IR24_OLD_BRIGHTER : incBrightness(); break;
@ -388,7 +311,7 @@ void decodeIR24OLD(uint32_t code)
lastValidCode = code;
}
void decodeIR24CT(uint32_t code)
static void decodeIR24CT(uint32_t code)
{
switch (code) {
case IR24_CT_BRIGHTER : incBrightness(); break;
@ -420,7 +343,7 @@ void decodeIR24CT(uint32_t code)
lastValidCode = code;
}
void decodeIR40(uint32_t code)
static void decodeIR40(uint32_t code)
{
Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
byte r = R(seg.colors[0]);
@ -473,7 +396,7 @@ void decodeIR40(uint32_t code)
lastValidCode = code;
}
void decodeIR44(uint32_t code)
static void decodeIR44(uint32_t code)
{
switch (code) {
case IR44_BPLUS : incBrightness(); break;
@ -525,7 +448,7 @@ void decodeIR44(uint32_t code)
lastValidCode = code;
}
void decodeIR21(uint32_t code)
static void decodeIR21(uint32_t code)
{
switch (code) {
case IR21_BRIGHTER: incBrightness(); break;
@ -554,7 +477,7 @@ void decodeIR21(uint32_t code)
lastValidCode = code;
}
void decodeIR6(uint32_t code)
static void decodeIR6(uint32_t code)
{
switch (code) {
case IR6_POWER: toggleOnOff(); break;
@ -587,7 +510,7 @@ void decodeIR6(uint32_t code)
lastValidCode = code;
}
void decodeIR9(uint32_t code)
static void decodeIR9(uint32_t code)
{
switch (code) {
case IR9_POWER : toggleOnOff(); break;
@ -628,7 +551,7 @@ Sample:
"label": "Preset 1, fallback to Saw - Party if not found"},
}
*/
void decodeIRJson(uint32_t code)
static void decodeIRJson(uint32_t code)
{
char objKey[10];
char fileName[16];
@ -701,41 +624,102 @@ void decodeIRJson(uint32_t code)
releaseJSONBufferLock();
}
static void applyRepeatActions()
{
if (irEnabled == 8) {
decodeIRJson(lastValidCode);
return;
} else switch (lastRepeatableAction) {
case ACTION_BRIGHT_UP : incBrightness(); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_BRIGHT_DOWN : decBrightness(); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_SPEED_UP : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_SPEED_DOWN : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_INTENSITY_UP : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_INTENSITY_DOWN : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
default: break;
}
if (lastValidCode == IR40_WPLUS) {
changeWhite(10);
stateUpdated(CALL_MODE_BUTTON);
} else if (lastValidCode == IR40_WMINUS) {
changeWhite(-10);
stateUpdated(CALL_MODE_BUTTON);
} else if ((lastValidCode == IR24_ON || lastValidCode == IR40_ON) && irTimesRepeated > 7 ) {
nightlightActive = true;
nightlightStartTime = millis();
stateUpdated(CALL_MODE_BUTTON);
}
}
static void decodeIR(uint32_t code)
{
if (code == 0xFFFFFFFF) {
//repeated code, continue brightness up/down
irTimesRepeated++;
applyRepeatActions();
return;
}
lastValidCode = 0; irTimesRepeated = 0;
lastRepeatableAction = ACTION_NONE;
if (irEnabled == 8) { // any remote configurable with ir.json file
decodeIRJson(code);
stateUpdated(CALL_MODE_BUTTON);
return;
}
if (code > 0xFFFFFF) return; //invalid code
switch (irEnabled) {
case 1:
if (code > 0xF80000) decodeIR24OLD(code); // white 24-key remote (old) - it sends 0xFF0000 values
else decodeIR24(code); // 24-key remote - 0xF70000 to 0xF80000
break;
case 2: decodeIR24CT(code); break; // white 24-key remote with CW, WW, CT+ and CT- keys
case 3: decodeIR40(code); break; // blue 40-key remote with 25%, 50%, 75% and 100% keys
case 4: decodeIR44(code); break; // white 44-key remote with color-up/down keys and DIY1 to 6 keys
case 5: decodeIR21(code); break; // white 21-key remote
case 6: decodeIR6(code); break; // black 6-key learning remote defaults: "CH" controls brightness,
// "VOL +" controls effect, "VOL -" controls colour/palette, "MUTE"
// sets bright plain white
case 7: decodeIR9(code); break;
//case 8: return; // ir.json file, handled above switch statement
}
if (nightlightActive && bri == 0) nightlightActive = false;
stateUpdated(CALL_MODE_BUTTON); //for notifier, IR is considered a button input
}
void initIR()
{
if (irEnabled > 0)
{
if (irEnabled > 0) {
irrecv = new IRrecv(irPin);
irrecv->enableIRIn();
if (irrecv) irrecv->enableIRIn();
} else irrecv = nullptr;
}
void deInitIR()
{
if (irrecv) {
irrecv->disableIRIn();
delete irrecv;
}
irrecv = nullptr;
}
void handleIR()
{
if (irEnabled > 0 && millis() - irCheckedTime > 120 && !strip.isUpdating())
{
irCheckedTime = millis();
if (irEnabled > 0)
{
if (irrecv == NULL)
{
initIR(); return;
unsigned long currentTime = millis();
unsigned timeDiff = currentTime - irCheckedTime;
if (timeDiff > 120 && irEnabled > 0 && irrecv) {
if (strip.isUpdating() && timeDiff < 240) return; // be nice, but not too nice
irCheckedTime = currentTime;
if (irrecv->decode(&results)) {
if (results.value != 0) { // only print results if anything is received ( != 0 )
if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut) // Serial TX pin (GPIO 1 on ESP32 and ESP8266)
Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value);
}
if (irrecv->decode(&results))
{
if (results.value != 0) // only print results if anything is received ( != 0 )
{
if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut) // Serial TX pin (GPIO 1 on ESP32 and ESP8266)
Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value);
}
decodeIR(results.value);
irrecv->resume();
}
} else if (irrecv != NULL)
{
irrecv->disableIRIn();
delete irrecv; irrecv = NULL;
decodeIR(results.value);
irrecv->resume();
}
}
}

View File

@ -276,8 +276,8 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
seg.fill(BLACK);
}
uint16_t start = 0, stop = 0;
byte set = 0; //0 nothing set, 1 start set, 2 range set
start = 0, stop = 0;
set = 0; //0 nothing set, 1 start set, 2 range set
for (size_t i = 0; i < iarr.size(); i++) {
if(iarr[i].is<JsonInteger>()) {
@ -416,7 +416,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
//bool didSet = false;
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
Segment &sg = strip.getSegment(s);
if (sg.isSelected()) {
if (sg.isActive() && sg.isSelected()) {
deserializeSegment(segVar, s, presetId);
//didSet = true;
}
@ -487,6 +487,8 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
}
}
doAdvancePlaylist = root[F("np")] | doAdvancePlaylist; //advances to next preset in playlist when true
JsonObject wifi = root[F("wifi")];
if (!wifi.isNull()) {
bool apMode = getBoolVal(wifi[F("ap")], apActive);
@ -596,7 +598,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
nl["dur"] = nightlightDelayMins;
nl["mode"] = nightlightMode;
nl[F("tbri")] = nightlightTargetBri;
nl[F("rem")] = nightlightActive ? (nightlightDelayMs - (millis() - nightlightStartTime)) / 1000 : -1; // seconds remaining
nl[F("rem")] = nightlightActive ? (int)(nightlightDelayMs - (millis() - nightlightStartTime)) / 1000 : -1; // seconds remaining
JsonObject udpn = root.createNestedObject("udpn");
udpn[F("send")] = sendNotificationsRT;
@ -636,6 +638,7 @@ void serializeInfo(JsonObject root)
root[F("ver")] = versionString;
root[F("vid")] = VERSION;
root[F("cn")] = F(WLED_CODENAME);
root[F("release")] = FPSTR(releaseString);
JsonObject leds = root.createNestedObject(F("leds"));
leds[F("count")] = strip.getLengthTotal();

View File

@ -1,197 +1,194 @@
#include "wled.h"
/*
* MQTT communication protocol for home automation
*/
#ifdef WLED_ENABLE_MQTT
#define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds
void parseMQTTBriPayload(char* payload)
{
if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);}
else if (strstr(payload, "T" ) || strstr(payload, "t" )) {toggleOnOff(); stateUpdated(CALL_MODE_DIRECT_CHANGE);}
else {
uint8_t in = strtoul(payload, NULL, 10);
if (in == 0 && bri > 0) briLast = bri;
bri = in;
stateUpdated(CALL_MODE_DIRECT_CHANGE);
}
}
void onMqttConnect(bool sessionPresent)
{
//(re)subscribe to required topics
char subuf[38];
if (mqttDeviceTopic[0] != 0) {
strlcpy(subuf, mqttDeviceTopic, 33);
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0);
}
if (mqttGroupTopic[0] != 0) {
strlcpy(subuf, mqttGroupTopic, 33);
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttGroupTopic, 33);
strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0);
}
usermods.onMqttConnect(sessionPresent);
DEBUG_PRINTLN(F("MQTT ready"));
publishMqtt();
}
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
static char *payloadStr;
DEBUG_PRINT(F("MQTT msg: "));
DEBUG_PRINTLN(topic);
// paranoia check to avoid npe if no payload
if (payload==nullptr) {
DEBUG_PRINTLN(F("no payload -> leave"));
return;
}
if (index == 0) { // start (1st partial packet or the only packet)
if (payloadStr) delete[] payloadStr; // fail-safe: release buffer
payloadStr = new char[total+1]; // allocate new buffer
}
if (payloadStr == nullptr) return; // buffer not allocated
// copy (partial) packet to buffer and 0-terminate it if it is last packet
char* buff = payloadStr + index;
memcpy(buff, payload, len);
if (index + len >= total) { // at end
payloadStr[total] = '\0'; // terminate c style string
} else {
DEBUG_PRINTLN(F("MQTT partial packet received."));
return; // process next packet
}
DEBUG_PRINTLN(payloadStr);
size_t topicPrefixLen = strlen(mqttDeviceTopic);
if (strncmp(topic, mqttDeviceTopic, topicPrefixLen) == 0) {
topic += topicPrefixLen;
} else {
topicPrefixLen = strlen(mqttGroupTopic);
if (strncmp(topic, mqttGroupTopic, topicPrefixLen) == 0) {
topic += topicPrefixLen;
} else {
// Non-Wled Topic used here. Probably a usermod subscribed to this topic.
usermods.onMqttMessage(topic, payloadStr);
delete[] payloadStr;
payloadStr = nullptr;
return;
}
}
//Prefix is stripped from the topic at this point
if (strcmp_P(topic, PSTR("/col")) == 0) {
colorFromDecOrHexString(col, payloadStr);
colorUpdated(CALL_MODE_DIRECT_CHANGE);
} else if (strcmp_P(topic, PSTR("/api")) == 0) {
if (!requestJSONBufferLock(15)) {
delete[] payloadStr;
payloadStr = nullptr;
return;
}
if (payloadStr[0] == '{') { //JSON API
deserializeJson(*pDoc, payloadStr);
deserializeState(pDoc->as<JsonObject>());
} else { //HTTP API
String apireq = "win"; apireq += '&'; // reduce flash string usage
apireq += payloadStr;
handleSet(nullptr, apireq);
}
releaseJSONBufferLock();
} else if (strlen(topic) != 0) {
// non standard topic, check with usermods
usermods.onMqttMessage(topic, payloadStr);
} else {
// topmost topic (just wled/MAC)
parseMQTTBriPayload(payloadStr);
}
delete[] payloadStr;
payloadStr = nullptr;
}
void publishMqtt()
{
if (!WLED_MQTT_CONNECTED) return;
DEBUG_PRINTLN(F("Publish MQTT"));
#ifndef USERMOD_SMARTNEST
char s[10];
char subuf[48];
sprintf_P(s, PSTR("%u"), bri);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/g"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2]));
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/c"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/status"));
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
char apires[1024]; // allocating 1024 bytes from stack can be risky
XML_response(nullptr, apires);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/v"));
mqtt->publish(subuf, 0, retainMqttMsg, apires); // optionally retain message (#2263)
#endif
}
//HA autodiscovery was removed in favor of the native integration in HA v0.102.0
bool initMqtt()
{
if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false;
if (mqtt == nullptr) {
mqtt = new AsyncMqttClient();
mqtt->onMessage(onMqttMessage);
mqtt->onConnect(onMqttConnect);
}
if (mqtt->connected()) return true;
DEBUG_PRINTLN(F("Reconnecting MQTT"));
IPAddress mqttIP;
if (mqttIP.fromString(mqttServer)) //see if server is IP or domain
{
mqtt->setServer(mqttIP, mqttPort);
} else {
mqtt->setServer(mqttServer, mqttPort);
}
mqtt->setClientId(mqttClientID);
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
#ifndef USERMOD_SMARTNEST
strlcpy(mqttStatusTopic, mqttDeviceTopic, 33);
strcat_P(mqttStatusTopic, PSTR("/status"));
mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message
#endif
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);
mqtt->connect();
return true;
}
#endif
#include "wled.h"
/*
* MQTT communication protocol for home automation
*/
#ifdef WLED_ENABLE_MQTT
#define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds
void parseMQTTBriPayload(char* payload)
{
if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);}
else if (strstr(payload, "T" ) || strstr(payload, "t" )) {toggleOnOff(); stateUpdated(CALL_MODE_DIRECT_CHANGE);}
else {
uint8_t in = strtoul(payload, NULL, 10);
if (in == 0 && bri > 0) briLast = bri;
bri = in;
stateUpdated(CALL_MODE_DIRECT_CHANGE);
}
}
void onMqttConnect(bool sessionPresent)
{
//(re)subscribe to required topics
char subuf[38];
if (mqttDeviceTopic[0] != 0) {
strlcpy(subuf, mqttDeviceTopic, 33);
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0);
}
if (mqttGroupTopic[0] != 0) {
strlcpy(subuf, mqttGroupTopic, 33);
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttGroupTopic, 33);
strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0);
}
usermods.onMqttConnect(sessionPresent);
DEBUG_PRINTLN(F("MQTT ready"));
publishMqtt();
}
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
static char *payloadStr;
DEBUG_PRINT(F("MQTT msg: "));
DEBUG_PRINTLN(topic);
// paranoia check to avoid npe if no payload
if (payload==nullptr) {
DEBUG_PRINTLN(F("no payload -> leave"));
return;
}
if (index == 0) { // start (1st partial packet or the only packet)
if (payloadStr) delete[] payloadStr; // fail-safe: release buffer
payloadStr = new char[total+1]; // allocate new buffer
}
if (payloadStr == nullptr) return; // buffer not allocated
// copy (partial) packet to buffer and 0-terminate it if it is last packet
char* buff = payloadStr + index;
memcpy(buff, payload, len);
if (index + len >= total) { // at end
payloadStr[total] = '\0'; // terminate c style string
} else {
DEBUG_PRINTLN(F("MQTT partial packet received."));
return; // process next packet
}
DEBUG_PRINTLN(payloadStr);
size_t topicPrefixLen = strlen(mqttDeviceTopic);
if (strncmp(topic, mqttDeviceTopic, topicPrefixLen) == 0) {
topic += topicPrefixLen;
} else {
topicPrefixLen = strlen(mqttGroupTopic);
if (strncmp(topic, mqttGroupTopic, topicPrefixLen) == 0) {
topic += topicPrefixLen;
} else {
// Non-Wled Topic used here. Probably a usermod subscribed to this topic.
usermods.onMqttMessage(topic, payloadStr);
delete[] payloadStr;
payloadStr = nullptr;
return;
}
}
//Prefix is stripped from the topic at this point
if (strcmp_P(topic, PSTR("/col")) == 0) {
colorFromDecOrHexString(col, payloadStr);
colorUpdated(CALL_MODE_DIRECT_CHANGE);
} else if (strcmp_P(topic, PSTR("/api")) == 0) {
if (requestJSONBufferLock(15)) {
if (payloadStr[0] == '{') { //JSON API
deserializeJson(*pDoc, payloadStr);
deserializeState(pDoc->as<JsonObject>());
} else { //HTTP API
String apireq = "win"; apireq += '&'; // reduce flash string usage
apireq += payloadStr;
handleSet(nullptr, apireq);
}
releaseJSONBufferLock();
}
} else if (strlen(topic) != 0) {
// non standard topic, check with usermods
usermods.onMqttMessage(topic, payloadStr);
} else {
// topmost topic (just wled/MAC)
parseMQTTBriPayload(payloadStr);
}
delete[] payloadStr;
payloadStr = nullptr;
}
void publishMqtt()
{
if (!WLED_MQTT_CONNECTED) return;
DEBUG_PRINTLN(F("Publish MQTT"));
#ifndef USERMOD_SMARTNEST
char s[10];
char subuf[48];
sprintf_P(s, PSTR("%u"), bri);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/g"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2]));
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/c"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/status"));
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
char apires[1024]; // allocating 1024 bytes from stack can be risky
XML_response(nullptr, apires);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/v"));
mqtt->publish(subuf, 0, retainMqttMsg, apires); // optionally retain message (#2263)
#endif
}
//HA autodiscovery was removed in favor of the native integration in HA v0.102.0
bool initMqtt()
{
if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false;
if (mqtt == nullptr) {
mqtt = new AsyncMqttClient();
mqtt->onMessage(onMqttMessage);
mqtt->onConnect(onMqttConnect);
}
if (mqtt->connected()) return true;
DEBUG_PRINTLN(F("Reconnecting MQTT"));
IPAddress mqttIP;
if (mqttIP.fromString(mqttServer)) //see if server is IP or domain
{
mqtt->setServer(mqttIP, mqttPort);
} else {
mqtt->setServer(mqttServer, mqttPort);
}
mqtt->setClientId(mqttClientID);
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
#ifndef USERMOD_SMARTNEST
strlcpy(mqttStatusTopic, mqttDeviceTopic, 33);
strcat_P(mqttStatusTopic, PSTR("/status"));
mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message
#endif
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);
mqtt->connect();
return true;
}
#endif

View File

@ -2,20 +2,8 @@
#include "wled.h"
#include "fcn_declare.h"
// on esp8266, building with `-D WLED_USE_UNREAL_MATH` saves around 7Kb flash and 1KB RAM
// warning: causes errors in sunset calculations, see #3400
#if defined(WLED_USE_UNREAL_MATH)
#define sinf sin_t
#define asinf asin_t
#define cosf cos_t
#define acosf acos_t
#define tanf tan_t
#define atanf atan_t
#define fmodf fmod_t
#define floorf floor_t
#else
#include <math.h>
#endif
// WARNING: may cause errors in sunset calculations on ESP8266, see #3400
// building with `-D WLED_USE_REAL_MATH` will prevent those errors at the expense of flash and RAM
/*
* Acquires time from NTP server
@ -439,7 +427,7 @@ static int getSunriseUTC(int year, int month, int day, float lat, float lon, boo
//1. first calculate the day of the year
float N1 = 275 * month / 9;
float N2 = (month + 9) / 12;
float N3 = (1.0f + floorf((year - 4 * floorf(year / 4) + 2.0f) / 3.0f));
float N3 = (1.0f + floor_t((year - 4 * floor_t(year / 4) + 2.0f) / 3.0f));
float N = N1 - (N2 * N3) + day - 30.0f;
//2. convert the longitude to hour value and calculate an approximate time
@ -450,37 +438,37 @@ static int getSunriseUTC(int year, int month, int day, float lat, float lon, boo
float M = (0.9856f * t) - 3.289f;
//4. calculate the Sun's true longitude
float L = fmodf(M + (1.916f * sinf(DEG_TO_RAD*M)) + (0.02f * sinf(2*DEG_TO_RAD*M)) + 282.634f, 360.0f);
float L = fmod_t(M + (1.916f * sin_t(DEG_TO_RAD*M)) + (0.02f * sin_t(2*DEG_TO_RAD*M)) + 282.634f, 360.0f);
//5a. calculate the Sun's right ascension
float RA = fmodf(RAD_TO_DEG*atanf(0.91764f * tanf(DEG_TO_RAD*L)), 360.0f);
float RA = fmod_t(RAD_TO_DEG*atan_t(0.91764f * tan_t(DEG_TO_RAD*L)), 360.0f);
//5b. right ascension value needs to be in the same quadrant as L
float Lquadrant = floorf( L/90) * 90;
float RAquadrant = floorf(RA/90) * 90;
float Lquadrant = floor_t( L/90) * 90;
float RAquadrant = floor_t(RA/90) * 90;
RA = RA + (Lquadrant - RAquadrant);
//5c. right ascension value needs to be converted into hours
RA /= 15.0f;
//6. calculate the Sun's declination
float sinDec = 0.39782f * sinf(DEG_TO_RAD*L);
float cosDec = cosf(asinf(sinDec));
float sinDec = 0.39782f * sin_t(DEG_TO_RAD*L);
float cosDec = cos_t(asin_t(sinDec));
//7a. calculate the Sun's local hour angle
float cosH = (sinf(DEG_TO_RAD*ZENITH) - (sinDec * sinf(DEG_TO_RAD*lat))) / (cosDec * cosf(DEG_TO_RAD*lat));
float cosH = (sin_t(DEG_TO_RAD*ZENITH) - (sinDec * sin_t(DEG_TO_RAD*lat))) / (cosDec * cos_t(DEG_TO_RAD*lat));
if ((cosH > 1.0f) && !sunset) return INT16_MAX; // the sun never rises on this location (on the specified date)
if ((cosH < -1.0f) && sunset) return INT16_MAX; // the sun never sets on this location (on the specified date)
//7b. finish calculating H and convert into hours
float H = sunset ? RAD_TO_DEG*acosf(cosH) : 360 - RAD_TO_DEG*acosf(cosH);
float H = sunset ? RAD_TO_DEG*acos_t(cosH) : 360 - RAD_TO_DEG*acos_t(cosH);
H /= 15.0f;
//8. calculate local mean time of rising/setting
float T = H + RA - (0.06571f * t) - 6.622f;
//9. adjust back to UTC
float UT = fmodf(T - lngHour, 24.0f);
float UT = fmod_t(T - lngHour, 24.0f);
// return in minutes from midnight
return UT*60;

View File

@ -25,11 +25,11 @@ void _overlayAnalogClock()
{
if (secondPixel < analogClock12pixel)
{
strip.setRange(analogClock12pixel, overlayMax, 0xFF0000);
strip.setRange(overlayMin, secondPixel, 0xFF0000);
strip.setRange(analogClock12pixel, overlayMax, color_fade(0xFF0000, bri));
strip.setRange(overlayMin, secondPixel, color_fade(0xFF0000, bri));
} else
{
strip.setRange(analogClock12pixel, secondPixel, 0xFF0000);
strip.setRange(analogClock12pixel, secondPixel, color_fade(0xFF0000, bri));
}
}
if (analogClock5MinuteMarks)
@ -38,12 +38,12 @@ void _overlayAnalogClock()
{
unsigned pix = analogClock12pixel + roundf((overlaySize / 12.0f) *i);
if (pix > overlayMax) pix -= overlaySize;
strip.setPixelColor(pix, 0x00FFAA);
strip.setPixelColor(pix, color_fade(0x00FFAA, bri));
}
}
if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, 0xFF0000);
strip.setPixelColor(minutePixel, 0x00FF00);
strip.setPixelColor(hourPixel, 0x0000FF);
if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, color_fade(0xFF0000, bri));
strip.setPixelColor(minutePixel, color_fade(0x00FF00, bri));
strip.setPixelColor(hourPixel, color_fade(0x0000FF, bri));
}

View File

@ -54,7 +54,7 @@ enum struct PinOwner : uint8_t {
// #define USERMOD_ID_RTC // 0x0F // Usermod "usermod_rtc.h" -- Uses "standard" HW_I2C pins
// #define USERMOD_ID_ELEKSTUBE_IPS // 0x10 // Usermod "usermod_elekstube_ips.h" -- Uses quite a few pins ... see Hardware.h and User_Setup.h
// #define USERMOD_ID_SN_PHOTORESISTOR // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager
UM_BH1750 = USERMOD_ID_BH1750, // 0x14 // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins
UM_BH1750 = USERMOD_ID_BH1750, // 0x14 // Usermod "bh1750.h -- Uses "standard" HW_I2C pins
UM_RGBRotaryEncoder = USERMOD_RGB_ROTARY_ENCODER, // 0x16 // Usermod "rgb-rotary-encoder.h"
UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h"
UM_BME280 = USERMOD_ID_BME280, // 0x1E // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins
@ -62,7 +62,8 @@ enum struct PinOwner : uint8_t {
UM_SdCard = USERMOD_ID_SD_CARD, // 0x25 // Usermod "usermod_sd_card.h"
UM_PWM_OUTPUTS = USERMOD_ID_PWM_OUTPUTS, // 0x26 // Usermod "usermod_pwm_outputs.h"
UM_LDR_DUSK_DAWN = USERMOD_ID_LDR_DUSK_DAWN, // 0x2B // Usermod "usermod_LDR_Dusk_Dawn_v2.h"
UM_MAX17048 = USERMOD_ID_MAX17048 // 0x2F // Usermod "usermod_max17048.h"
UM_MAX17048 = USERMOD_ID_MAX17048, // 0x2F // Usermod "usermod_max17048.h"
UM_BME68X = USERMOD_ID_BME68X // 0x31 // Usermod "usermod_bme68x.h -- Uses "standard" HW_I2C pins
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");

View File

@ -127,7 +127,7 @@ void handlePlaylist() {
static unsigned long presetCycledTime = 0;
if (currentPlaylist < 0 || playlistEntries == nullptr) return;
if (millis() - presetCycledTime > (100*playlistEntryDur)) {
if (millis() - presetCycledTime > (100 * playlistEntryDur) || doAdvancePlaylist) {
presetCycledTime = millis();
if (bri == 0 || nightlightActive) return;
@ -149,6 +149,7 @@ void handlePlaylist() {
strip.setTransition(fadeTransition ? playlistEntries[playlistIndex].tr * 100 : 0);
playlistEntryDur = playlistEntries[playlistIndex].dur;
applyPresetFromPlaylist(playlistEntries[playlistIndex].preset);
doAdvancePlaylist = false;
}
}

View File

@ -104,7 +104,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
#ifndef WLED_DISABLE_INFRARED
if (irPin>=0 && pinManager.isPinAllocated(irPin, PinOwner::IR)) {
pinManager.deallocatePin(irPin, PinOwner::IR);
deInitIR();
pinManager.deallocatePin(irPin, PinOwner::IR);
}
#endif
for (uint8_t s=0; s<WLED_MAX_BUTTONS; s++) {
@ -135,27 +136,28 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
useGlobalLedBuffer = request->hasArg(F("LD"));
bool busesChanged = false;
for (uint8_t s = 0; s < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; s++) {
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = 48+s; co[3] = 0; //strip color order
char lt[4] = "LT"; lt[2] = 48+s; lt[3] = 0; //strip type
char ls[4] = "LS"; ls[2] = 48+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip first N LEDs
char rf[4] = "RF"; rf[2] = 48+s; rf[3] = 0; //refresh required
char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //channel swap
char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
char la[4] = "LA"; la[2] = 48+s; la[3] = 0; //LED mA
char ma[4] = "MA"; ma[2] = 48+s; ma[3] = 0; //max mA
for (int s = 0; s < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; s++) {
int offset = s < 10 ? 48 : 55;
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order
char lt[4] = "LT"; lt[2] = offset+s; lt[3] = 0; //strip type
char ls[4] = "LS"; ls[2] = offset+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = offset+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = offset+s; sl[3] = 0; //skip first N LEDs
char rf[4] = "RF"; rf[2] = offset+s; rf[3] = 0; //refresh required
char aw[4] = "AW"; aw[2] = offset+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = offset+s; wo[3] = 0; //channel swap
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA
if (!request->hasArg(lp)) {
DEBUG_PRINT(F("No data for "));
DEBUG_PRINTLN(s);
break;
}
for (uint8_t i = 0; i < 5; i++) {
lp[1] = 48+i;
for (int i = 0; i < 5; i++) {
lp[1] = offset+i;
if (!request->hasArg(lp)) break;
pins[i] = (request->arg(lp).length() > 0) ? request->arg(lp).toInt() : 255;
}
@ -209,11 +211,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
ColorOrderMap com = {};
for (uint8_t s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
char xs[4] = "XS"; xs[2] = 48+s; xs[3] = 0; //start LED
char xc[4] = "XC"; xc[2] = 48+s; xc[3] = 0; //strip length
char xo[4] = "XO"; xo[2] = 48+s; xo[3] = 0; //color order
char xw[4] = "XW"; xw[2] = 48+s; xw[3] = 0; //W swap
for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
int offset = s < 10 ? 48 : 55;
char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED
char xc[4] = "XC"; xc[2] = offset+s; xc[3] = 0; //strip length
char xo[4] = "XO"; xo[2] = offset+s; xo[3] = 0; //color order
char xw[4] = "XW"; xw[2] = offset+s; xw[3] = 0; //W swap
if (request->hasArg(xs)) {
start = request->arg(xs).toInt();
length = request->arg(xc).toInt();
@ -233,6 +236,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
irPin = -1;
}
irEnabled = request->arg(F("IT")).toInt();
initIR();
#endif
irApplyToAllSelected = !request->hasArg(F("MSO"));
@ -243,12 +247,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
rlyPin = -1;
}
rlyMde = (bool)request->hasArg(F("RM"));
rlyOpenDrain = (bool)request->hasArg(F("RO"));
disablePullUp = (bool)request->hasArg(F("IP"));
touchThreshold = request->arg(F("TT")).toInt();
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
char bt[4] = "BT"; bt[2] = (i<10?48:55)+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
char be[4] = "BE"; be[2] = (i<10?48:55)+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10)
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
int offset = i < 10 ? 48 : 55;
char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10)
int hw_btn_pin = request->arg(bt).toInt();
if (hw_btn_pin >= 0 && pinManager.allocatePin(hw_btn_pin,false,PinOwner::Button)) {
btnPin[i] = hw_btn_pin;
@ -900,6 +906,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
applyPreset(presetCycCurr);
}
pos = req.indexOf(F("NP")); //advances to next preset in a playlist
if (pos > 0) doAdvancePlaylist = true;
//set brightness
updateVal(req.c_str(), "&A=", &bri);

View File

@ -53,6 +53,11 @@
#include "../usermods/BME280_v2/usermod_bme280.h"
#endif
#ifdef USERMOD_BME68X
#include "../usermods/BME68X_v2/usermod_bme68x.h"
#endif
#ifdef USERMOD_FOUR_LINE_DISPLAY
#include "../usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h"
#endif
@ -217,6 +222,14 @@
#include "../usermods/TetrisAI_v2/usermod_v2_tetrisai.h"
#endif
#ifdef USERMOD_AHT10
#include "../usermods/AHT10_v2/usermod_aht10.h"
#endif
#ifdef USERMOD_INA226
#include "../usermods/INA226_v2/usermod_ina226.h"
#endif
void registerUsermods()
{
/*
@ -254,6 +267,10 @@ void registerUsermods()
usermods.add(new UsermodBME280());
#endif
#ifdef USERMOD_BME68X
usermods.add(new UsermodBME68X());
#endif
#ifdef USERMOD_SENSORSTOMQTT
usermods.add(new UserMod_SensorsToMQTT());
#endif
@ -421,4 +438,12 @@ void registerUsermods()
#ifdef USERMOD_TETRISAI
usermods.add(new TetrisAIUsermod());
#endif
#ifdef USERMOD_AHT10
usermods.add(new UsermodAHT10());
#endif
#ifdef USERMOD_INA226
usermods.add(new UsermodINA226());
#endif
}

View File

@ -505,6 +505,13 @@ void WLED::setup()
initServer();
DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap());
#ifndef WLED_DISABLE_INFRARED
// init IR
DEBUG_PRINTLN(F("initIR"));
initIR();
DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap());
#endif
// Seed FastLED random functions with an esp random value, which already works properly at this point.
#if defined(ARDUINO_ARCH_ESP32)
const uint32_t seed32 = esp_random();

View File

@ -3,12 +3,12 @@
/*
Main sketch, global variable declarations
@title WLED project sketch
@version 0.15.0-b2
@version 0.15.0-b3
@author Christian Schwinne
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2404100
#define VERSION 2405180
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@ -243,27 +243,32 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
// int arr[]{0,1,2} becomes WLED_GLOBAL int arr[] _INIT_N(({0,1,2}));
#ifndef WLED_DEFINE_GLOBAL_VARS
# define WLED_GLOBAL extern
# define _INIT(x)
# define _INIT_N(x)
#define WLED_GLOBAL extern
#define _INIT(x)
#define _INIT_N(x)
#define _INIT_PROGMEM(x)
#else
# define WLED_GLOBAL
# define _INIT(x) = x
//needed to ignore commas in array definitions
#define UNPACK( ... ) __VA_ARGS__
# define _INIT_N(x) UNPACK x
#define WLED_GLOBAL
#define _INIT(x) = x
//needed to ignore commas in array definitions
#define UNPACK( ... ) __VA_ARGS__
#define _INIT_N(x) UNPACK x
#define _INIT_PROGMEM(x) PROGMEM = x
#endif
#define STRINGIFY(X) #X
#define TOSTRING(X) STRINGIFY(X)
#ifndef WLED_VERSION
#define WLED_VERSION "dev"
#define WLED_VERSION dev
#endif
#ifndef WLED_RELEASE_NAME
#define WLED_RELEASE_NAME dev_release
#endif
// Global Variable definitions
WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION));
WLED_GLOBAL char releaseString[] _INIT_PROGMEM(TOSTRING(WLED_RELEASE_NAME)); // somehow this will not work if using "const char releaseString[]
#define WLED_CODENAME "Kōsen"
// AP and OTA default passwords (for maximum security change them!)
@ -288,6 +293,12 @@ WLED_GLOBAL bool rlyMde _INIT(true);
#else
WLED_GLOBAL bool rlyMde _INIT(RLYMDE);
#endif
//Use open drain (floating pin) when relay should be off
#ifndef RLYODRAIN
WLED_GLOBAL bool rlyOpenDrain _INIT(false);
#else
WLED_GLOBAL bool rlyOpenDrain _INIT(RLYODRAIN);
#endif
#ifndef IRPIN
#define IRPIN -1
#endif
@ -634,6 +645,7 @@ WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255,
WLED_GLOBAL byte timerMonth[] _INIT_N(({28,28,28,28,28,28,28,28}));
WLED_GLOBAL byte timerDay[] _INIT_N(({1,1,1,1,1,1,1,1}));
WLED_GLOBAL byte timerDayEnd[] _INIT_N(({31,31,31,31,31,31,31,31}));
WLED_GLOBAL bool doAdvancePlaylist _INIT(false);
//improv
WLED_GLOBAL byte improvActive _INIT(0); //0: no improv packet received, 1: improv active, 2: provisioning

View File

@ -206,9 +206,9 @@ bool sendLiveLedsWs(uint32_t wsClient)
uint8_t g = G(c);
uint8_t b = B(c);
uint8_t w = W(c);
buffer[pos++] = scale8(qadd8(w, r), strip.getBrightness()); //R, add white channel to RGB channels as a simple RGBW -> RGB map
buffer[pos++] = scale8(qadd8(w, g), strip.getBrightness()); //G
buffer[pos++] = scale8(qadd8(w, b), strip.getBrightness()); //B
buffer[pos++] = bri ? qadd8(w, r) : 0; //R, add white channel to RGB channels as a simple RGBW -> RGB map
buffer[pos++] = bri ? qadd8(w, g) : 0; //G
buffer[pos++] = bri ? qadd8(w, b) : 0; //B
}
wsc->binary(std::move(wsBuf));

View File

@ -350,7 +350,8 @@ void getSettingsJS(byte subPage, char* dest)
oappend(itoa(WLED_MIN_VIRTUAL_BUSSES,nS,10)); oappend(",");
oappend(itoa(MAX_LEDS_PER_BUS,nS,10)); oappend(",");
oappend(itoa(MAX_LED_MEMORY,nS,10)); oappend(",");
oappend(itoa(MAX_LEDS,nS,10));
oappend(itoa(MAX_LEDS,nS,10)); oappend(",");
oappend(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10));
oappend(SET_F(");"));
sappend('c',SET_F("MS"),autoSegments);
@ -362,28 +363,29 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',SET_F("AW"),Bus::getGlobalAWMode());
sappend('c',SET_F("LD"),useGlobalLedBuffer);
uint16_t sumMa = 0;
for (uint8_t s=0; s < BusManager::getNumBusses(); s++) {
unsigned sumMa = 0;
for (int s = 0; s < BusManager::getNumBusses(); s++) {
Bus* bus = BusManager::getBus(s);
if (bus == nullptr) continue;
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = 48+s; co[3] = 0; //strip color order
char lt[4] = "LT"; lt[2] = 48+s; lt[3] = 0; //strip type
char ls[4] = "LS"; ls[2] = 48+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip 1st LED
char rf[4] = "RF"; rf[2] = 48+s; rf[3] = 0; //off refresh
char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //swap channels
char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed
char la[4] = "LA"; la[2] = 48+s; la[3] = 0; //LED current
char ma[4] = "MA"; ma[2] = 48+s; ma[3] = 0; //max per-port PSU current
int offset = s < 10 ? 48 : 55;
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order
char lt[4] = "LT"; lt[2] = offset+s; lt[3] = 0; //strip type
char ls[4] = "LS"; ls[2] = offset+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = offset+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = offset+s; sl[3] = 0; //skip 1st LED
char rf[4] = "RF"; rf[2] = offset+s; rf[3] = 0; //off refresh
char aw[4] = "AW"; aw[2] = offset+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = offset+s; wo[3] = 0; //swap channels
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED current
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current
oappend(SET_F("addLEDs(1);"));
uint8_t pins[5];
uint8_t nPins = bus->getPins(pins);
for (uint8_t i = 0; i < nPins; i++) {
lp[1] = 48+i;
int nPins = bus->getPins(pins);
for (int i = 0; i < nPins; i++) {
lp[1] = offset+i;
if (pinManager.isPinOk(pins[i]) || IS_VIRTUAL(bus->getType())) sappend('v',lp,pins[i]);
}
sappend('v',lc,bus->getLength());
@ -395,7 +397,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',rf,bus->isOffRefreshRequired());
sappend('v',aw,bus->getAutoWhiteMode());
sappend('v',wo,bus->getColorOrder() >> 4);
uint16_t speed = bus->getFrequency();
unsigned speed = bus->getFrequency();
if (IS_PWM(bus->getType())) {
switch (speed) {
case WLED_PWM_FREQ/2 : speed = 0; break;
@ -428,7 +430,7 @@ void getSettingsJS(byte subPage, char* dest)
oappend(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10));
oappend(SET_F(");"));
const ColorOrderMap& com = BusManager::getColorOrderMap();
for (uint8_t s=0; s < com.count(); s++) {
for (int s = 0; s < com.count(); s++) {
const ColorOrderMapEntry* entry = com.get(s);
if (entry == nullptr) break;
oappend(SET_F("addCOM("));
@ -458,7 +460,8 @@ void getSettingsJS(byte subPage, char* dest)
sappend('i',SET_F("PB"),strip.paletteBlend);
sappend('v',SET_F("RL"),rlyPin);
sappend('c',SET_F("RM"),rlyMde);
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
sappend('c',SET_F("RO"),rlyOpenDrain);
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
oappend(SET_F("addBtn("));
oappend(itoa(i,nS,10)); oappend(",");
oappend(itoa(btnPin[i],nS,10)); oappend(",");
@ -722,6 +725,8 @@ void getSettingsJS(byte subPage, char* dest)
sappends('m',SET_F("(\"sip\")[0]"),(char*)F("WLED "));
olen -= 2; //delete ";
oappend(versionString);
oappend(SET_F("<br>"));
oappend((char*)FPSTR(releaseString));
oappend(SET_F("<br>("));
#if defined(ARDUINO_ARCH_ESP32)
oappend(ESP.getChipModel());