Merge branch '0_15' into power-ap
20
.github/stale.yml
vendored
@ -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
@ -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! ❤️
|
75
CHANGELOG.md
@ -1,5 +1,75 @@
|
||||
## WLED changelog
|
||||
|
||||
#### Build 2406290
|
||||
- WLED 0.15.0-b4 release
|
||||
- LED settings bus management update (WARNING only allow available outputs)
|
||||
- Add ETH support for LILYGO-POE-Pro (#4030 by @rorosaurus)
|
||||
- Update usermod_sn_photoresistor (#4017 by @xkvmoto)
|
||||
- Several internal fixes and optimisations
|
||||
- move LED_BUILTIN handling to BusManager class
|
||||
- reduce max panels (web server limitation)
|
||||
- edit WiFi TX power (ESP32)
|
||||
- keep current ledmap ID in UI
|
||||
- limit outputs in UI based on length
|
||||
- wifi.ap addition to JSON Info (JSON API)
|
||||
- relay pin init bugfix
|
||||
- file editor button in UI
|
||||
- ESP8266: update was restarting device on some occasions
|
||||
- a bit of throttling in UI (for ESP8266)
|
||||
|
||||
#### Build 2406120
|
||||
- Update NeoPixelBus to v2.8.0
|
||||
- Increased LED outputs one ESP32 using parallel I2S (up to 17)
|
||||
- use single/mono I2S + 4x RMT for 5 outputs or less
|
||||
- use parallel x8 I2S + 8x RMT for >5 outputs (limit of 300 LEDs per output)
|
||||
- Fixed code of Smartnest and updated documentation (#4001 by @DevilPro1)
|
||||
- ESP32-S3 WiFi fix (#4010 by @cstruck)
|
||||
- TetrisAI usermod fix (#3897 by @muebau)
|
||||
- ESP-NOW usermod hook
|
||||
- Update wled.h regarding OTA Password (#3993 by @gsieben)
|
||||
- Usermod BME68X Sensor Implementation (#3994 by @gsieben)
|
||||
- Add a usermod for AHT10, AHT15 and AHT20 temperature/humidity sensors (#3977 by @LordMike)
|
||||
- Update Battery usermod documentation (#3968 by @adamsthws)
|
||||
- Add INA226 usermod for reading current and power over i2c (#3986 by @LordMike)
|
||||
- Bugfixes: #3991
|
||||
- Several internal fixes and optimisations (WARNING: some effects may be broken that rely on overflow/narrow width)
|
||||
- replace uint8_t and uint16_t with unsigned
|
||||
- replace in8_t and int16_t with int
|
||||
- reduces code by 1kB
|
||||
|
||||
#### Build 2405180
|
||||
- WLED 0.14.4 release
|
||||
- Fix for #3978
|
||||
- Official 0.15.0-b3 release
|
||||
- Merge 0.14.3 fixes into 0_15
|
||||
- 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
|
||||
@ -22,6 +92,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]
|
||||
|
@ -28,7 +28,7 @@ You are all set if you have enabled `Editor: Detect Indentation` in VS Code.
|
||||
|
||||
#### Blocks
|
||||
|
||||
Whether the opening bracket of e.g. an `if` block is in the same line as the condition or in a separate line is up to your discretion. If there is only one statement, leaving out block braches is acceptable.
|
||||
Whether the opening bracket of e.g. an `if` block is in the same line as the condition or in a separate line is up to your discretion. If there is only one statement, leaving out block brackets is acceptable.
|
||||
|
||||
Good:
|
||||
```cpp
|
||||
@ -49,7 +49,7 @@ if (a == b) doStuff(a);
|
||||
```
|
||||
|
||||
There should always be a space between a keyword and its condition and between the condition and brace.
|
||||
Within the condition, no space should be between the paranthesis and variables.
|
||||
Within the condition, no space should be between the parenthesis and variables.
|
||||
Spaces between variables and operators are up to the authors discretion.
|
||||
There should be no space between function names and their argument parenthesis.
|
||||
|
||||
|
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.15.0-b3",
|
||||
"version": "0.15.0-b4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wled",
|
||||
"version": "0.15.0-b3",
|
||||
"version": "0.15.0-b4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"clean-css": "^5.3.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.15.0-b3",
|
||||
"version": "0.15.0-b4",
|
||||
"description": "Tools for WLED project",
|
||||
"main": "tools/cdata.js",
|
||||
"directories": {
|
||||
|
@ -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])
|
||||
|
@ -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
|
||||
|
@ -115,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:
|
||||
@ -137,7 +138,8 @@ lib_compat_mode = strict
|
||||
lib_deps =
|
||||
fastled/FastLED @ 3.6.0
|
||||
IRremoteESP8266 @ 2.8.2
|
||||
makuna/NeoPixelBus @ 2.7.9
|
||||
makuna/NeoPixelBus @ 2.8.0
|
||||
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1
|
||||
# for I2C interface
|
||||
;Wire
|
||||
@ -338,14 +340,14 @@ platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_1m128k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags} -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
|
||||
; -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.build_flags} -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
|
||||
; -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
|
||||
@ -471,8 +473,7 @@ monitor_filters = esp32_exception_decoder
|
||||
|
||||
[env:esp32s3_4M_qspi]
|
||||
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
|
||||
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 = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
|
||||
platform = ${esp32s3.platform}
|
||||
platform_packages = ${esp32s3.platform_packages}
|
||||
upload_speed = 921600
|
||||
@ -480,6 +481,7 @@ 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
|
||||
-DLOLIN_WIFI_FIX ; seems to work much better with this
|
||||
-D WLED_WATCHDOG_TIMEOUT=0
|
||||
${esp32.AR_build_flags}
|
||||
lib_deps = ${esp32s3.lib_deps}
|
||||
|
@ -10,7 +10,7 @@ default_envs = WLED_tasmota_1M # define as many as you need
|
||||
#----------
|
||||
# SAMPLE
|
||||
#----------
|
||||
[env:WLED_tasmota_1M]
|
||||
[env:WLED_generic8266_1M]
|
||||
extends = env:esp01_1m_full # when you want to extend the existing environment (define only updated options)
|
||||
; board = esp01_1m # uncomment when ou need different board
|
||||
; platform = ${common.platform_wled_default} # uncomment and change when you want particular platform
|
||||
@ -26,9 +26,9 @@ lib_deps = ${esp8266.lib_deps}
|
||||
; adafruit/Adafruit BME280 Library@^2.2.2
|
||||
; Wire
|
||||
; robtillaart/SHT85@~0.3.3
|
||||
; gmag11/QuickESPNow ;@ 0.6.2
|
||||
; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug
|
||||
; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library
|
||||
; https://github.com/kosme/arduinoFFT#develop @ 2.0.1 ;; used for USERMOD_AUDIOREACTIVE
|
||||
; ${esp32.AR_lib_deps} ;; used for USERMOD_AUDIOREACTIVE
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
;
|
||||
@ -51,6 +51,11 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
; -D WLED_DISABLE_ESPNOW
|
||||
; -D WLED_DISABLE_BROWNOUT_DET
|
||||
;
|
||||
; enable optional built-in features
|
||||
; -D WLED_ENABLE_PIXART
|
||||
; -D WLED_ENABLE_USERMOD_PAGE # if created
|
||||
; -D WLED_ENABLE_DMX
|
||||
;
|
||||
; PIN defines - uncomment and change, if needed:
|
||||
; -D LEDPIN=2
|
||||
; or use this for multiple outputs
|
||||
@ -64,6 +69,8 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
;
|
||||
; Limit max buses
|
||||
; -D WLED_MAX_BUSSES=2
|
||||
; -D WLED_MAX_ANALOG_CHANNELS=3 # only 3 PWM HW pins available
|
||||
; -D WLED_MAX_DIGITAL_CHANNELS=2 # only 2 HW accelerated pins available
|
||||
;
|
||||
; Configure default WiFi
|
||||
; -D CLIENT_SSID='"MyNetwork"'
|
||||
@ -94,6 +101,12 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
; -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
|
||||
@ -122,12 +135,12 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
;
|
||||
; Use PIR sensor usermod and configure it to use GPIO4 and timer of 60s
|
||||
; -D USERMOD_PIRSWITCH
|
||||
; -D PIR_SENSOR_PIN=4
|
||||
; -D PIR_SENSOR_PIN=4 # use -1 to disable usermod
|
||||
; -D PIR_SENSOR_OFF_SEC=60
|
||||
; -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering)
|
||||
;
|
||||
; Use Audioreactive usermod and configure I2S microphone
|
||||
; -D USERMOD_AUDIOREACTIVE
|
||||
; -D UM_AUDIOREACTIVE_USE_NEW_FFT
|
||||
; -D AUDIOPIN=-1
|
||||
; -D DMTYPE=1 # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM
|
||||
; -D I2S_SDPIN=36
|
||||
@ -150,17 +163,21 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
; or this for multiple outputs
|
||||
; -D PIXEL_COUNTS=30,30
|
||||
;
|
||||
; set milliampere limit when using ESP pin to power leds
|
||||
; set the default LED type
|
||||
; -D DEFAULT_LED_TYPE=22 # see const.h (TYPE_xxxx)
|
||||
;
|
||||
; set milliampere limit when using ESP power pin (or inadequate PSU) to power LEDs
|
||||
; -D ABL_MILLIAMPS_DEFAULT=850
|
||||
; -D LED_MILLIAMPS_DEFAULT=55
|
||||
;
|
||||
; enable IR by setting remote type
|
||||
; -D IRTYPE=0 ;0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote
|
||||
; -D IRTYPE=0 # 0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote
|
||||
;
|
||||
; set default color order of your led strip
|
||||
; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB
|
||||
;
|
||||
; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues)
|
||||
; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue
|
||||
; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue # needed only for classic ESP32 rev.1
|
||||
;
|
||||
; configure I2C and SPI interface (for various hardware)
|
||||
; -D I2CSDAPIN=33 # initialise interface
|
||||
|
@ -28,7 +28,7 @@ h11==0.14.0
|
||||
# via
|
||||
# uvicorn
|
||||
# wsproto
|
||||
idna==3.4
|
||||
idna==3.7
|
||||
# via
|
||||
# anyio
|
||||
# requests
|
||||
@ -42,7 +42,7 @@ pyelftools==0.29
|
||||
# via platformio
|
||||
pyserial==3.5
|
||||
# via platformio
|
||||
requests==2.31.0
|
||||
requests==2.32.0
|
||||
# via platformio
|
||||
semantic-version==2.10.0
|
||||
# via platformio
|
||||
@ -52,7 +52,9 @@ starlette==0.23.1
|
||||
# via platformio
|
||||
tabulate==0.9.0
|
||||
# via platformio
|
||||
urllib3==1.26.18
|
||||
typing-extensions==4.11.0
|
||||
# via starlette
|
||||
urllib3==1.26.19
|
||||
# via requests
|
||||
uvicorn==0.20.0
|
||||
# via platformio
|
||||
|
36
usermods/AHT10_v2/README.md
Normal 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
|
||||
```
|
9
usermods/AHT10_v2/platformio_override.ini
Normal 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
|
327
usermods/AHT10_v2/usermod_aht10.h
Normal 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";
|
@ -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
|
||||
|
@ -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;
|
||||
|
BIN
usermods/BME68X_v2/BME680.pdf
Normal file
152
usermods/BME68X_v2/README.md
Normal 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)
|
76
usermods/BME68X_v2/pics/GeoGab.svg
Normal 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 |
BIN
usermods/BME68X_v2/pics/pic1.png
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
usermods/BME68X_v2/pics/pic2.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
usermods/BME68X_v2/pics/pic3.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
usermods/BME68X_v2/pics/pic4.png
Normal file
After Width: | Height: | Size: 134 KiB |
BIN
usermods/BME68X_v2/pics/pic5.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
usermods/BME68X_v2/pics/pic6.png
Normal file
After Width: | Height: | Size: 35 KiB |
1114
usermods/BME68X_v2/usermod_bme68x.h
Normal file
160
usermods/Battery/UMBattery.h
Normal 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
|
BIN
usermods/Battery/assets/battery_connection_schematic_esp32.png
Normal file
After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 64 KiB |
BIN
usermods/Battery/assets/installation_my_config_h.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
usermods/Battery/assets/installation_platformio_override_ini.png
Normal file
After Width: | Height: | Size: 54 KiB |
@ -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
|
||||
@ -79,3 +116,25 @@
|
||||
#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
|
@ -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 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!
|
||||
|
||||
Example:
|
||||
|
||||
| Your battery specification table | | Options you can define |
|
||||
| :-------------------------------- |:--------------- | :---------------------------- |
|
||||
| Capacity | 3500mAh 12,5 Wh | |
|
||||
| Minimum capacity | 3350mAh 11,9 Wh | |
|
||||
| --------------------------------- | --------------- | ----------------------------- |
|
||||
| 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
|
||||
|
38
usermods/Battery/types/LionUMBattery.h
Normal 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
|
54
usermods/Battery/types/LipoUMBattery.h
Normal 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
|
39
usermods/Battery/types/UnkownUMBattery.h
Normal 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
|
@ -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;
|
||||
|
||||
@ -68,21 +60,16 @@ class UsermodBattery : public Usermod
|
||||
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()
|
||||
{
|
||||
|
77
usermods/INA226_v2/README.md
Normal 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
|
||||
```
|
9
usermods/INA226_v2/platformio_override.ini
Normal 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
|
556
usermods/INA226_v2/usermod_ina226.h
Normal 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";
|
36
usermods/LD2410_v2/readme.md
Normal file
@ -0,0 +1,36 @@
|
||||
# BH1750 usermod
|
||||
|
||||
> This usermod requires a second UART and was only tested on the ESP32
|
||||
|
||||
|
||||
This usermod will read from a LD2410 movement/presence sensor.
|
||||
|
||||
The movement and presence state are displayed in both the Info section of the web UI, as well as published to the `/movement` and `/stationary` MQTT topics respectively.
|
||||
|
||||
## Dependencies
|
||||
- Libraries
|
||||
- `ncmreynolds/ld2410@^0.1.3`
|
||||
- This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`).
|
||||
- Data is published over MQTT - make sure you've enabled the MQTT sync interface.
|
||||
|
||||
## Compilation
|
||||
|
||||
To enable, compile with `USERMOD_LD2410` defined (e.g. in `platformio_override.ini`)
|
||||
```ini
|
||||
[env:usermod_USERMOD_LD2410_esp32dev]
|
||||
extends = env:esp32dev
|
||||
build_flags =
|
||||
${common.build_flags_esp32}
|
||||
-D USERMOD_LD2410
|
||||
lib_deps =
|
||||
${esp32.lib_deps}
|
||||
ncmreynolds/ld2410@^0.1.3
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
The Usermod screen allows you to:
|
||||
- enable/disable the usermod
|
||||
- Configure the RX/TX pins
|
||||
|
||||
## Change log
|
||||
- 2024-06 Created by @wesleygas (https://github.com/wesleygas/)
|
237
usermods/LD2410_v2/usermod_ld2410.h
Normal file
@ -0,0 +1,237 @@
|
||||
#warning **** Included USERMOD_LD2410 ****
|
||||
|
||||
#ifndef WLED_ENABLE_MQTT
|
||||
#error "This user mod requires MQTT to be enabled."
|
||||
#endif
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
#include <ld2410.h>
|
||||
|
||||
class LD2410Usermod : public Usermod {
|
||||
|
||||
private:
|
||||
|
||||
bool enabled = true;
|
||||
bool initDone = false;
|
||||
bool sensorFound = false;
|
||||
unsigned long lastTime = 0;
|
||||
unsigned long last_mqtt_sent = 0;
|
||||
|
||||
int8_t default_uart_rx = 19;
|
||||
int8_t default_uart_tx = 18;
|
||||
|
||||
|
||||
String mqttMovementTopic = F("");
|
||||
String mqttStationaryTopic = F("");
|
||||
bool mqttInitialized = false;
|
||||
bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages
|
||||
|
||||
|
||||
ld2410 radar;
|
||||
bool stationary_detected = false;
|
||||
bool last_stationary_state = false;
|
||||
bool movement_detected = false;
|
||||
bool last_movement_state = false;
|
||||
|
||||
// These config variables have defaults set inside readFromConfig()
|
||||
int8_t uart_rx_pin;
|
||||
int8_t uart_tx_pin;
|
||||
|
||||
// string that are used multiple time (this will save some flash memory)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
|
||||
void publishMqtt(const char* topic, const char* state, bool retain); // example for publishing MQTT message
|
||||
|
||||
void _mqttInitialize()
|
||||
{
|
||||
mqttMovementTopic = String(mqttDeviceTopic) + F("/ld2410/movement");
|
||||
mqttStationaryTopic = String(mqttDeviceTopic) + F("/ld2410/stationary");
|
||||
if (HomeAssistantDiscovery){
|
||||
_createMqttSensor(F("Movement"), mqttMovementTopic, F("motion"), F(""));
|
||||
_createMqttSensor(F("Stationary"), mqttStationaryTopic, F("occupancy"), F(""));
|
||||
}
|
||||
}
|
||||
|
||||
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
|
||||
void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
|
||||
{
|
||||
String t = String(F("homeassistant/binary_sensor/")) + mqttClientID + F("/") + name + F("/config");
|
||||
|
||||
StaticJsonDocument<600> doc;
|
||||
|
||||
doc[F("name")] = String(serverDescription) + F(" Module");
|
||||
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;
|
||||
doc[F("payload_off")] = "OFF";
|
||||
doc[F("payload_on")] = "ON";
|
||||
|
||||
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");
|
||||
device[F("model")] = F("FOSS");
|
||||
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());
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
inline bool isEnabled() { return enabled; }
|
||||
|
||||
void setup() {
|
||||
Serial1.begin(256000, SERIAL_8N1, uart_rx_pin, uart_tx_pin);
|
||||
Serial.print(F("\nLD2410 radar sensor initialising: "));
|
||||
if(radar.begin(Serial1)){
|
||||
Serial.println(F("OK"));
|
||||
} else {
|
||||
Serial.println(F("not connected"));
|
||||
}
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
radar.read();
|
||||
unsigned long curr_time = millis();
|
||||
if(curr_time - lastTime > 1000) //Try to Report every 1000ms
|
||||
{
|
||||
lastTime = curr_time;
|
||||
sensorFound = radar.isConnected();
|
||||
if(!sensorFound) return;
|
||||
stationary_detected = radar.presenceDetected();
|
||||
if(stationary_detected != last_stationary_state){
|
||||
if (WLED_MQTT_CONNECTED){
|
||||
publishMqtt("/ld2410/stationary", stationary_detected ? "ON":"OFF", false);
|
||||
last_stationary_state = stationary_detected;
|
||||
}
|
||||
}
|
||||
movement_detected = radar.movingTargetDetected();
|
||||
if(movement_detected != last_movement_state){
|
||||
if (WLED_MQTT_CONNECTED){
|
||||
publishMqtt("/ld2410/movement", movement_detected ? "ON":"OFF", false);
|
||||
last_movement_state = movement_detected;
|
||||
}
|
||||
}
|
||||
// If there hasn't been any activity, send current state to confirm sensor is alive
|
||||
if(curr_time - last_mqtt_sent > 1000*60*5 && WLED_MQTT_CONNECTED){
|
||||
publishMqtt("/ld2410/stationary", stationary_detected ? "ON":"OFF", false);
|
||||
publishMqtt("/ld2410/movement", movement_detected ? "ON":"OFF", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
// if "u" object does not exist yet wee need to create it
|
||||
JsonObject user = root[F("u")];
|
||||
if (user.isNull()) user = root.createNestedObject(F("u"));
|
||||
|
||||
JsonArray ld2410_sta_json = user.createNestedArray(F("LD2410 Stationary"));
|
||||
JsonArray ld2410_mov_json = user.createNestedArray(F("LD2410 Movement"));
|
||||
if (!enabled){
|
||||
ld2410_sta_json.add(F("disabled"));
|
||||
ld2410_mov_json.add(F("disabled"));
|
||||
} else if(!sensorFound){
|
||||
ld2410_sta_json.add(F("LD2410"));
|
||||
ld2410_sta_json.add(" Not Found");
|
||||
} else {
|
||||
ld2410_sta_json.add("Sta ");
|
||||
ld2410_sta_json.add(stationary_detected ? "ON":"OFF");
|
||||
ld2410_mov_json.add("Mov ");
|
||||
ld2410_mov_json.add(movement_detected ? "ON":"OFF");
|
||||
}
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
//save these vars persistently whenever settings are saved
|
||||
top["uart_rx_pin"] = default_uart_rx;
|
||||
top["uart_tx_pin"] = default_uart_tx;
|
||||
}
|
||||
|
||||
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINT(F("LD2410"));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
configComplete &= getJsonValue(top["uart_rx_pin"], uart_rx_pin, default_uart_rx);
|
||||
configComplete &= getJsonValue(top["uart_tx_pin"], uart_tx_pin, default_uart_tx);
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
/**
|
||||
* onMqttConnect() is called when MQTT connection is established
|
||||
*/
|
||||
void onMqttConnect(bool sessionPresent) {
|
||||
// do any MQTT related initialisation here
|
||||
if(!radar.isConnected()) return;
|
||||
publishMqtt("/ld2410/status", "I am alive!", false);
|
||||
if (!mqttInitialized)
|
||||
{
|
||||
_mqttInitialize();
|
||||
mqttInitialized = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_LD2410;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// add more strings here to reduce flash memory usage
|
||||
const char LD2410Usermod::_name[] PROGMEM = "LD2410Usermod";
|
||||
const char LD2410Usermod::_enabled[] PROGMEM = "enabled";
|
||||
|
||||
|
||||
// implementation of non-inline member methods
|
||||
|
||||
void LD2410Usermod::publishMqtt(const char* topic, const char* state, bool retain)
|
||||
{
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
//Check if MQTT Connected, otherwise it will crash
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
last_mqtt_sent = millis();
|
||||
char subuf[64];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat(subuf, topic);
|
||||
mqtt->publish(subuf, 0, retain, state);
|
||||
}
|
||||
#endif
|
||||
}
|
@ -58,7 +58,11 @@ private:
|
||||
bool sensorPinState[PIR_SENSOR_MAX_SENSORS] = {LOW}; // current PIR sensor pin state
|
||||
|
||||
// configurable parameters
|
||||
#if PIR_SENSOR_PIN < 0
|
||||
bool enabled = false; // PIR sensor disabled
|
||||
#else
|
||||
bool enabled = true; // PIR sensor enabled
|
||||
#endif
|
||||
int8_t PIRsensorPin[PIR_SENSOR_MAX_SENSORS] = {PIR_SENSOR_PIN}; // PIR sensor pin
|
||||
uint32_t m_switchOffDelay = PIR_SENSOR_OFF_SEC*1000; // delay before switch off after the sensor state goes LOW (10min)
|
||||
uint8_t m_onPreset = 0; // on preset
|
||||
|
@ -3,7 +3,9 @@
|
||||
#include "wled.h"
|
||||
|
||||
//Pin defaults for QuinLed Dig-Uno (A0)
|
||||
#ifndef PHOTORESISTOR_PIN
|
||||
#define PHOTORESISTOR_PIN A0
|
||||
#endif
|
||||
|
||||
// the frequency to check photoresistor, 10 seconds
|
||||
#ifndef USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL
|
||||
|
@ -34,7 +34,7 @@ public:
|
||||
{
|
||||
if (width > 32)
|
||||
{
|
||||
throw std::invalid_argument("maximal width is 32");
|
||||
this->width = 32;
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,6 +112,17 @@ public:
|
||||
{
|
||||
return pixels[y] == (uint32_t)((1 << width) - 1);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
if (width > 32)
|
||||
{
|
||||
width = 32;
|
||||
}
|
||||
|
||||
pixels.clear();
|
||||
pixels.resize(height);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __GRIDBW_H__ */
|
@ -127,6 +127,14 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
gridBW.reset();
|
||||
pixels.clear();
|
||||
pixels.resize(width* height);
|
||||
clear();
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __GRIDCOLOR_H__ */
|
@ -32,7 +32,7 @@ public:
|
||||
uint8_t fullLines;
|
||||
uint16_t bumpiness;
|
||||
uint16_t aggregatedHeight;
|
||||
double score;
|
||||
float score;
|
||||
uint8_t width;
|
||||
std::vector<uint8_t> lineHights;
|
||||
|
||||
@ -57,7 +57,7 @@ public:
|
||||
this->fullLines = 0;
|
||||
this->bumpiness = 0;
|
||||
this->aggregatedHeight = 0;
|
||||
this->score = -DBL_MAX;
|
||||
this->score = -FLT_MAX;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,16 +1,22 @@
|
||||
# Tetris AI effect usermod
|
||||
|
||||
This usermod brings you a effect brings a self playing Tetris game. The mod needs version 0.14 or above as it is based on matrix support. The effect was tested on an ESP32 with a WS2812B 16x16 matrix.
|
||||
This usermod adds a self-playing Tetris game as an 'effect'. The mod requires version 0.14 or higher as it relies on matrix support. The effect was tested on an ESP32 4MB with a WS2812B 16x16 matrix.
|
||||
|
||||
Version 1.0
|
||||
|
||||
## Installation
|
||||
|
||||
Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'.
|
||||
Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'. If you are running out of flash memory, use a different memory layout (e.g. [WLED_ESP32_4MB_256KB_FS.csv](https://github.com/Aircoookie/WLED/blob/main/tools/WLED_ESP32_4MB_256KB_FS.csv)).
|
||||
|
||||
If needed simply add to `platformio_override.ini` (or `platformio_override.ini`):
|
||||
|
||||
```ini
|
||||
board_build.partitions = tools/WLED_ESP32_4MB_256KB_FS.csv
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
It is best to set the background color to black, the border color to light grey and the game over color (foreground) to dark grey.
|
||||
It is best to set the background color to black 🖤, the border color to light grey 🤍, the game over color (foreground) to dark grey 🩶, and color palette to 'Rainbow' 🌈.
|
||||
|
||||
### Sliders and boxes
|
||||
|
||||
@ -19,15 +25,18 @@ It is best to set the background color to black, the border color to light grey
|
||||
* speed: speed the game plays
|
||||
* look ahead: how many pieces is the AI allowed to know the next pieces (0 - 2)
|
||||
* intelligence: how good the AI will play
|
||||
* Rotate color: make the colors shift (rotate) every few cicles
|
||||
* Mistakes free: how many good moves between mistakes (if activated)
|
||||
* Rotate color: make the colors shift (rotate) every few moves
|
||||
* Mistakes free: how many good moves between mistakes (if enabled)
|
||||
|
||||
#### Checkboxes
|
||||
|
||||
* show next: if true a space of 5 pixels from the right is used to show the next pieces. The whole segment is used for the grid otherwise.
|
||||
* show next: if true, a space of 5 pixels from the right will be used to show the next pieces. Otherwise the whole segment is used for the grid.
|
||||
* show border: if true an additional column of 1 pixel is used to draw a border between the grid and the next pieces
|
||||
* mistakes: if true the worst instead of the best move is choosen every few moves (read above)
|
||||
* mistakes: if true, the worst decision will be made every few moves instead of the best (see above).
|
||||
|
||||
## Best results
|
||||
|
||||
If the speed is set to be a little bit faster than a good human could play with maximal intelligence and very few mistakes it makes people furious/happy at a party.
|
||||
If the speed is set to be a little bit faster than a good human could play with maximal intelligence and very few mistakes it makes people furious/happy at a party 😉.
|
||||
|
||||
## Limits
|
||||
The game grid is limited to a maximum width of 32 and a maximum height of 255 due to the internal structure of the code. The canvas of the effect will be centred in the segment if the segment exceeds the maximum width or height.
|
@ -22,10 +22,10 @@ class TetrisAI
|
||||
{
|
||||
private:
|
||||
public:
|
||||
double aHeight;
|
||||
double fullLines;
|
||||
double holes;
|
||||
double bumpiness;
|
||||
float aHeight;
|
||||
float fullLines;
|
||||
float holes;
|
||||
float bumpiness;
|
||||
bool findWorstMove = false;
|
||||
|
||||
uint8_t countOnes(uint32_t vector)
|
||||
@ -107,10 +107,10 @@ public:
|
||||
rating->score = (aHeight * (rating->aggregatedHeight)) + (fullLines * (rating->fullLines)) + (holes * (rating->holes)) + (bumpiness * (rating->bumpiness));
|
||||
}
|
||||
|
||||
TetrisAI(): TetrisAI(-0.510066, 0.760666, -0.35663, -0.184483)
|
||||
TetrisAI(): TetrisAI(-0.510066f, 0.760666f, -0.35663f, -0.184483f)
|
||||
{}
|
||||
|
||||
TetrisAI(double aHeight, double fullLines, double holes, double bumpiness):
|
||||
TetrisAI(float aHeight, float fullLines, float holes, float bumpiness):
|
||||
aHeight(aHeight),
|
||||
fullLines(fullLines),
|
||||
holes(holes),
|
||||
@ -178,9 +178,9 @@ public:
|
||||
if(findWorstMove)
|
||||
{
|
||||
//init rating for worst
|
||||
if(bestRating->score == -DBL_MAX)
|
||||
if(bestRating->score == -FLT_MAX)
|
||||
{
|
||||
bestRating->score = DBL_MAX;
|
||||
bestRating->score = FLT_MAX;
|
||||
}
|
||||
|
||||
// update if we found a worse one
|
||||
@ -202,101 +202,6 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool findBestMoveNonBlocking(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
|
||||
{
|
||||
//vector with pieces
|
||||
//for every piece
|
||||
//for every
|
||||
switch (expression)
|
||||
{
|
||||
case INIT:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool findBestMoveNonBlocking(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
|
||||
{
|
||||
//INIT
|
||||
grid.cleanupFullLines();
|
||||
Rating curRating(grid.width);
|
||||
Rating deeperRating(grid.width);
|
||||
Piece piece = *start;
|
||||
|
||||
// for every rotation of the piece
|
||||
piece.rotation = 0;
|
||||
|
||||
//HANDLE
|
||||
while (piece.rotation < piece.pieceData->rotCount)
|
||||
{
|
||||
// put piece to top left corner
|
||||
piece.x = 0;
|
||||
piece.y = 0;
|
||||
|
||||
//test for every column
|
||||
piece.x = 0;
|
||||
while (piece.x <= grid.width - piece.getRotation().width)
|
||||
{
|
||||
|
||||
//todo optimise by the use of the previous grids height
|
||||
piece.landingY = 0;
|
||||
//will set landingY to final position
|
||||
grid.findLandingPosition(&piece);
|
||||
|
||||
// draw piece
|
||||
grid.placePiece(&piece, piece.x, piece.landingY);
|
||||
|
||||
if(start == end - 1)
|
||||
{
|
||||
//at the deepest level
|
||||
updateRating(grid, &curRating);
|
||||
}
|
||||
else
|
||||
{
|
||||
//go deeper to take another piece into account
|
||||
findBestMove(grid, start + 1, end, &deeperRating);
|
||||
curRating = deeperRating;
|
||||
}
|
||||
|
||||
// eraese piece
|
||||
grid.erasePiece(&piece, piece.x, piece.landingY);
|
||||
|
||||
if(findWorstMove)
|
||||
{
|
||||
//init rating for worst
|
||||
if(bestRating->score == -DBL_MAX)
|
||||
{
|
||||
bestRating->score = DBL_MAX;
|
||||
}
|
||||
|
||||
// update if we found a worse one
|
||||
if (bestRating->score > curRating.score)
|
||||
{
|
||||
*bestRating = curRating;
|
||||
(*start) = piece;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// update if we found a better one
|
||||
if (bestRating->score < curRating.score)
|
||||
{
|
||||
*bestRating = curRating;
|
||||
(*start) = piece;
|
||||
}
|
||||
}
|
||||
piece.x++;
|
||||
}
|
||||
piece.rotation++;
|
||||
}
|
||||
|
||||
//EXIT
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __AI_H__ */
|
@ -54,6 +54,7 @@ public:
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t nLookAhead;
|
||||
uint8_t nPieces;
|
||||
TetrisBag bag;
|
||||
GridColor grid;
|
||||
TetrisAI ai;
|
||||
@ -65,6 +66,7 @@ public:
|
||||
width(width),
|
||||
height(height),
|
||||
nLookAhead(nLookAhead),
|
||||
nPieces(nPieces),
|
||||
bag(nPieces, 1, nLookAhead),
|
||||
grid(width, height + 4),
|
||||
ai(),
|
||||
@ -142,8 +144,10 @@ public:
|
||||
|
||||
void reset()
|
||||
{
|
||||
grid.clear();
|
||||
bag.init();
|
||||
grid.width = width;
|
||||
grid.height = height + 4;
|
||||
grid.reset();
|
||||
bag.reset();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -25,6 +25,7 @@ private:
|
||||
public:
|
||||
uint8_t nPieces;
|
||||
uint8_t nBagLength;
|
||||
uint8_t queueLength;
|
||||
uint8_t bagIdx;
|
||||
std::vector<uint8_t> bag;
|
||||
std::vector<Piece> piecesQueue;
|
||||
@ -32,6 +33,7 @@ public:
|
||||
TetrisBag(uint8_t nPieces, uint8_t nBagLength, uint8_t queueLength):
|
||||
nPieces(nPieces),
|
||||
nBagLength(nBagLength),
|
||||
queueLength(queueLength),
|
||||
bag(nPieces * nBagLength),
|
||||
piecesQueue(queueLength)
|
||||
{
|
||||
@ -95,6 +97,15 @@ public:
|
||||
std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end());
|
||||
piecesQueue[piecesQueue.size() - 1] = Piece(idx % nPieces);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
bag.clear();
|
||||
bag.resize(nPieces * nBagLength);
|
||||
piecesQueue.clear();
|
||||
piecesQueue.resize(queueLength);
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __TETRISBAG_H__ */
|
||||
|
@ -18,6 +18,12 @@ typedef struct TetrisAI_data
|
||||
uint8_t colorOffset;
|
||||
uint8_t colorInc;
|
||||
uint8_t mistaceCountdown;
|
||||
uint16_t segcols;
|
||||
uint16_t segrows;
|
||||
uint16_t segOffsetX;
|
||||
uint16_t segOffsetY;
|
||||
uint16_t effectWidth;
|
||||
uint16_t effectHeight;
|
||||
} tetrisai_data;
|
||||
|
||||
void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
|
||||
@ -49,7 +55,7 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
|
||||
color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND);
|
||||
}
|
||||
|
||||
SEGMENT.setPixelColorXY(index_x, index_y - 4, color);
|
||||
SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + index_x, tetrisai_data->segOffsetY + index_y - 4, color);
|
||||
}
|
||||
}
|
||||
tetrisai_data->colorOffset += tetrisai_data->colorInc;
|
||||
@ -61,14 +67,14 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
|
||||
if (tetrisai_data->showBorder)
|
||||
{
|
||||
//draw a line 6 pixels from right with the border color
|
||||
for (auto index_y = 0; index_y < SEGMENT.virtualHeight(); index_y++)
|
||||
for (auto index_y = 0; index_y < tetrisai_data->effectHeight; index_y++)
|
||||
{
|
||||
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() - 6, index_y, SEGCOLOR(2));
|
||||
SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + tetrisai_data->effectWidth - 6, tetrisai_data->segOffsetY + index_y, SEGCOLOR(2));
|
||||
}
|
||||
}
|
||||
|
||||
//NEXT PIECE
|
||||
int piecesOffsetX = SEGMENT.virtualWidth() - 4;
|
||||
int piecesOffsetX = tetrisai_data->effectWidth - 4;
|
||||
int piecesOffsetY = 1;
|
||||
for (uint8_t nextPieceIdx = 1; nextPieceIdx < tetris->nLookAhead; nextPieceIdx++)
|
||||
{
|
||||
@ -83,7 +89,7 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
|
||||
if (piece.getPixel(pieceX, pieceY))
|
||||
{
|
||||
uint8_t colIdx = ((piece.pieceData->colorIndex * 32) + tetrisai_data->colorOffset);
|
||||
SEGMENT.setPixelColorXY(piecesOffsetX + pieceX, piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND));
|
||||
SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + piecesOffsetX + pieceX, tetrisai_data->segOffsetY + piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,62 +122,86 @@ uint16_t mode_2DTetrisAI()
|
||||
//range 0 - 16
|
||||
tetrisai_data->colorInc = SEGMENT.custom2 >> 4;
|
||||
|
||||
if (!tetrisai_data->tetris || (tetrisai_data->tetris.nLookAhead != nLookAhead
|
||||
if (tetrisai_data->tetris.nLookAhead != nLookAhead
|
||||
|| tetrisai_data->segcols != cols
|
||||
|| tetrisai_data->segrows != rows
|
||||
|| tetrisai_data->showNext != SEGMENT.check1
|
||||
|| tetrisai_data->showBorder != SEGMENT.check2
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
tetrisai_data->segcols = cols;
|
||||
tetrisai_data->segrows = rows;
|
||||
tetrisai_data->showNext = SEGMENT.check1;
|
||||
tetrisai_data->showBorder = SEGMENT.check2;
|
||||
|
||||
//not more than 32 as this is the limit of this implementation
|
||||
uint8_t gridWidth = cols < 32 ? cols : 32;
|
||||
uint8_t gridHeight = rows;
|
||||
//not more than 32 columns and 255 rows as this is the limit of this implementation
|
||||
uint8_t gridWidth = cols > 32 ? 32 : cols;
|
||||
uint8_t gridHeight = rows > 255 ? 255 : rows;
|
||||
|
||||
tetrisai_data->effectWidth = 0;
|
||||
tetrisai_data->effectHeight = 0;
|
||||
|
||||
// do we need space for the 'next' section?
|
||||
if (tetrisai_data->showNext)
|
||||
{
|
||||
// make space for the piece and one pixel of space
|
||||
gridWidth = gridWidth - 5;
|
||||
//does it get to tight?
|
||||
if (gridWidth + 5 > cols)
|
||||
{
|
||||
// yes, so make the grid smaller
|
||||
// make space for the piece and one pixel of space
|
||||
gridWidth = (gridWidth - ((gridWidth + 5) - cols));
|
||||
}
|
||||
tetrisai_data->effectWidth += 5;
|
||||
|
||||
// do we need space for a border?
|
||||
if (tetrisai_data->showBorder)
|
||||
{
|
||||
gridWidth = gridWidth - 1;
|
||||
if (gridWidth + 5 + 1 > cols)
|
||||
{
|
||||
gridWidth -= 1;
|
||||
}
|
||||
tetrisai_data->effectWidth += 1;
|
||||
}
|
||||
}
|
||||
|
||||
tetrisai_data->effectWidth += gridWidth;
|
||||
tetrisai_data->effectHeight += gridHeight;
|
||||
|
||||
tetrisai_data->segOffsetX = cols > tetrisai_data->effectWidth ? ((cols - tetrisai_data->effectWidth) / 2) : 0;
|
||||
tetrisai_data->segOffsetY = rows > tetrisai_data->effectHeight ? ((rows - tetrisai_data->effectHeight) / 2) : 0;
|
||||
|
||||
tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces);
|
||||
tetrisai_data->tetris.state = TetrisAIGame::States::INIT;
|
||||
SEGMENT.fill(SEGCOLOR(1));
|
||||
}
|
||||
|
||||
if (tetrisai_data->intelligence != SEGMENT.custom1)
|
||||
{
|
||||
tetrisai_data->intelligence = SEGMENT.custom1;
|
||||
double dui = 0.2 - (0.2 * (tetrisai_data->intelligence / 255.0));
|
||||
float dui = 0.2f - (0.2f * (tetrisai_data->intelligence / 255.0f));
|
||||
|
||||
tetrisai_data->tetris.ai.aHeight = -0.510066 + dui;
|
||||
tetrisai_data->tetris.ai.fullLines = 0.760666 - dui;
|
||||
tetrisai_data->tetris.ai.holes = -0.35663 + dui;
|
||||
tetrisai_data->tetris.ai.bumpiness = -0.184483 + dui;
|
||||
tetrisai_data->tetris.ai.aHeight = -0.510066f + dui;
|
||||
tetrisai_data->tetris.ai.fullLines = 0.760666f - dui;
|
||||
tetrisai_data->tetris.ai.holes = -0.35663f + dui;
|
||||
tetrisai_data->tetris.ai.bumpiness = -0.184483f + dui;
|
||||
}
|
||||
|
||||
if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE)
|
||||
{
|
||||
if (millis() - tetrisai_data->lastTime > msDelayMove)
|
||||
|
||||
if (strip.now - tetrisai_data->lastTime > msDelayMove)
|
||||
{
|
||||
drawGrid(&tetrisai_data->tetris, tetrisai_data);
|
||||
tetrisai_data->lastTime = millis();
|
||||
tetrisai_data->lastTime = strip.now;
|
||||
tetrisai_data->tetris.poll();
|
||||
}
|
||||
}
|
||||
else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_GAME_OVER)
|
||||
{
|
||||
if (millis() - tetrisai_data->lastTime > msDelayGameOver)
|
||||
if (strip.now - tetrisai_data->lastTime > msDelayGameOver)
|
||||
{
|
||||
drawGrid(&tetrisai_data->tetris, tetrisai_data);
|
||||
tetrisai_data->lastTime = millis();
|
||||
tetrisai_data->lastTime = strip.now;
|
||||
tetrisai_data->tetris.poll();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,61 +1,41 @@
|
||||
# Smartnest
|
||||
|
||||
Enables integration with `smartnest.cz` service which provides MQTT integration with voice assistants.
|
||||
Enables integration with `smartnest.cz` service which provides MQTT integration with voice assistants, for example Google Home, Alexa, Siri, Home Assistant and more!
|
||||
|
||||
In order to setup Smartnest follow the [documentation](https://www.docu.smartnest.cz/).
|
||||
- You can create up to 5 different devices
|
||||
- To add the project to Google Home you can find the information [here](https://www.docu.smartnest.cz/google-home-integration)
|
||||
- To add the project to Alexa you can find the information [here](https://www.docu.smartnest.cz/alexa-integration)
|
||||
|
||||
## MQTT API
|
||||
|
||||
The API is described in the Smartnest [Github repo](https://github.com/aososam/Smartnest/blob/master/Devices/lightRgb/lightRgb.ino).
|
||||
|
||||
|
||||
## Usermod installation
|
||||
|
||||
1. Register the usermod by adding `#include "../usermods/smartnest/usermod_smartnest.h"` at the top and `usermods.add(new Smartnest());` at the bottom of `usermods_list.cpp`.
|
||||
or
|
||||
2. Use `#define USERMOD_SMARTNEST` in wled.h or `-D USERMOD_SMARTNEST` in your platformio.ini
|
||||
|
||||
|
||||
Example **usermods_list.cpp**:
|
||||
|
||||
```cpp
|
||||
#include "wled.h"
|
||||
/*
|
||||
* Register your v2 usermods here!
|
||||
* (for v1 usermods using just usermod.cpp, you can ignore this file)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Add/uncomment your usermod filename here (and once more below)
|
||||
* || || ||
|
||||
* \/ \/ \/
|
||||
*/
|
||||
//#include "usermod_v2_example.h"
|
||||
//#include "usermod_temperature.h"
|
||||
#include "../usermods/usermod_smartnest.h"
|
||||
|
||||
void registerUsermods()
|
||||
{
|
||||
/*
|
||||
* Add your usermod class name here
|
||||
* || || ||
|
||||
* \/ \/ \/
|
||||
*/
|
||||
//usermods.add(new MyExampleUsermod());
|
||||
//usermods.add(new UsermodTemperature());
|
||||
usermods.add(new Smartnest());
|
||||
|
||||
}
|
||||
```
|
||||
1. Use `#define USERMOD_SMARTNEST` in wled.h or `-D USERMOD_SMARTNEST` in your platformio.ini (recommended).
|
||||
|
||||
## Configuration
|
||||
|
||||
Usermod has no configuration, but it relies on the MQTT configuration.\
|
||||
Under Config > Sync Interfaces > MQTT:
|
||||
* Enable MQTT check box
|
||||
* Set the `Broker` field to: `smartnest.cz`
|
||||
* The `Username` and `Password` fields are the login information from the `smartnest.cz` website.
|
||||
|
||||
* Enable `MQTT` check box.
|
||||
* Set the `Broker` field to: `smartnest.cz` or `3.122.209.170`(both work).
|
||||
* Set the `Port` field to: `1883`
|
||||
* The `Username` and `Password` fields are the login information from the `smartnest.cz` website (It is located above in the 3 points).
|
||||
* `Client ID` field is obtained from the device configuration panel in `smartnest.cz`.
|
||||
* `Device Topic` is obtained by entering the ClientID/report , remember to replace ClientId with your real information (Because they can ban your device).
|
||||
* `Group Topic` keep the same Group Topic.
|
||||
|
||||
Wait `1 minute` after turning it on, as it usually takes a while.
|
||||
|
||||
## Change log
|
||||
|
||||
2022-09
|
||||
* First implementation.
|
||||
* First implementation.
|
||||
|
||||
2024-05
|
||||
* Solved code.
|
||||
* Updated documentation.
|
||||
* Second implementation.
|
||||
|
@ -9,6 +9,10 @@
|
||||
class Smartnest : public Usermod
|
||||
{
|
||||
private:
|
||||
bool initialized = false;
|
||||
unsigned long lastMqttReport = 0;
|
||||
unsigned long mqttReportInterval = 60000; // Report every minute
|
||||
|
||||
void sendToBroker(const char *const topic, const char *const message)
|
||||
{
|
||||
if (!WLED_MQTT_CONNECTED)
|
||||
@ -61,7 +65,7 @@ private:
|
||||
int position = 0;
|
||||
|
||||
// We need to copy the string in order to keep it read only as strtok_r function requires mutable string
|
||||
color_ = (char *)malloc(strlen(color));
|
||||
color_ = (char *)malloc(strlen(color) + 1);
|
||||
if (NULL == color_) {
|
||||
return -1;
|
||||
}
|
||||
@ -150,7 +154,7 @@ public:
|
||||
delay(100);
|
||||
sendToBroker("report/firmware", versionString); // Reports the firmware version
|
||||
delay(100);
|
||||
sendToBroker("report/ip", (char *)WiFi.localIP().toString().c_str()); // Reports the ip
|
||||
sendToBroker("report/ip", (char *)WiFi.localIP().toString().c_str()); // Reports the IP
|
||||
delay(100);
|
||||
sendToBroker("report/network", (char *)WiFi.SSID().c_str()); // Reports the network name
|
||||
delay(100);
|
||||
@ -168,4 +172,34 @@ public:
|
||||
{
|
||||
return USERMOD_ID_SMARTNEST;
|
||||
}
|
||||
|
||||
/**
|
||||
* setup() is called once at startup to initialize the usermod.
|
||||
*/
|
||||
void setup() {
|
||||
DEBUG_PRINTF("Smartnest usermod setup initializing...");
|
||||
|
||||
// Publish initial status
|
||||
sendToBroker("report/status", "Smartnest usermod initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
* loop() is called continuously to keep the usermod running.
|
||||
*/
|
||||
void loop() {
|
||||
// Periodically report status to MQTT broker
|
||||
unsigned long currentMillis = millis();
|
||||
if (currentMillis - lastMqttReport >= mqttReportInterval) {
|
||||
lastMqttReport = currentMillis;
|
||||
|
||||
// Report current brightness
|
||||
char brightnessMsg[11];
|
||||
sprintf(brightnessMsg, "%u", bri);
|
||||
sendToBroker("report/brightness", brightnessMsg);
|
||||
|
||||
// Report current signal strength
|
||||
String signal(WiFi.RSSI(), 10);
|
||||
sendToBroker("report/signal", signal.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -122,9 +122,9 @@ class AutoSaveUsermod : public Usermod {
|
||||
* Da loop.
|
||||
*/
|
||||
void loop() {
|
||||
if (!autoSaveAfterSec || !enabled || strip.isUpdating() || currentPreset>0) return; // setting 0 as autosave seconds disables autosave
|
||||
|
||||
static unsigned long lastRun = 0;
|
||||
unsigned long now = millis();
|
||||
if (!autoSaveAfterSec || !enabled || currentPreset>0 || (strip.isUpdating() && now - lastRun < 240)) return; // setting 0 as autosave seconds disables autosave
|
||||
uint8_t currentMode = strip.getMainSegment().mode;
|
||||
uint8_t currentPalette = strip.getMainSegment().palette;
|
||||
|
||||
|
1398
wled00/FX.cpp
40
wled00/FX.h
@ -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 ¤tPalette(CRGBPalette16 &tgt, uint8_t paletteID);
|
||||
void setCurrentPalette(void);
|
||||
|
||||
// 1D strip
|
||||
uint16_t virtualLength(void) const;
|
||||
@ -605,6 +612,7 @@ 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); }
|
||||
@ -624,24 +632,25 @@ typedef struct Segment {
|
||||
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); }
|
||||
@ -660,9 +669,12 @@ typedef struct Segment {
|
||||
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) {}
|
||||
@ -697,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
|
||||
@ -851,7 +862,7 @@ class WS2812FX { // 96 bytes
|
||||
isMatrix;
|
||||
|
||||
#ifndef WLED_DISABLE_2D
|
||||
#define WLED_MAX_PANELS 64
|
||||
#define WLED_MAX_PANELS 18
|
||||
uint8_t
|
||||
panels;
|
||||
|
||||
@ -892,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)
|
||||
|
@ -342,55 +342,36 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) {
|
||||
// 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 unsigned cols = virtualWidth();
|
||||
const unsigned rows = virtualHeight();
|
||||
const unsigned dim1 = vertical ? rows : cols;
|
||||
const unsigned 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];
|
||||
for (unsigned j = 0; j < dim1; j++) {
|
||||
unsigned x = vertical ? i : j;
|
||||
unsigned y = vertical ? j : i;
|
||||
int xp = vertical ? x : x-1; // "signed" to prevent underflow
|
||||
int yp = vertical ? y-1 : y; // "signed" to prevent underflow
|
||||
unsigned xn = vertical ? x : x+1;
|
||||
unsigned 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);
|
||||
unsigned 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);
|
||||
uint32_t out[dim1], in[dim1];
|
||||
for (int j = 0; j < dim1; j++) {
|
||||
int x = vertical ? i : j;
|
||||
int y = vertical ? j : i;
|
||||
in[j] = getPixelColorXY(x, y);
|
||||
}
|
||||
for (unsigned j = 0; j < dim1; j++) {
|
||||
unsigned x = vertical ? i : j;
|
||||
unsigned y = vertical ? j : i;
|
||||
setPixelColorXY(x, y, tmp[j]);
|
||||
for (int j = 0; j < dim1; 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 unsigned rows = virtualHeight();
|
||||
for (unsigned y = 0; y < rows; y++) blurRow(y, blur_amount);
|
||||
}
|
||||
|
||||
void Segment::moveX(int8_t delta, bool wrap) {
|
||||
@ -447,33 +428,67 @@ 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
|
||||
// Bresenham’s 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 Wu’s 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 {
|
||||
// Bresenham’s 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
|
||||
// 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++) {
|
||||
@ -486,30 +501,58 @@ void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
|
||||
}
|
||||
}
|
||||
|
||||
void Segment::nscale8(uint8_t scale) {
|
||||
if (!isActive()) return; // not active
|
||||
const unsigned cols = virtualWidth();
|
||||
const unsigned rows = virtualHeight();
|
||||
for (unsigned y = 0; y < rows; y++) for (unsigned 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 unsigned cols = virtualWidth();
|
||||
const unsigned rows = virtualHeight();
|
||||
const int cols = virtualWidth();
|
||||
const int rows = virtualHeight();
|
||||
if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return;
|
||||
const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
|
||||
const int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1;
|
||||
int 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 Wu’s 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
@ -334,8 +335,8 @@ void Segment::handleTransition() {
|
||||
// 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;
|
||||
}
|
||||
@ -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);
|
||||
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
|
||||
unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
|
||||
for (unsigned i=0; i<noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, targetPalette, 48);
|
||||
targetPalette = _t->_palT; // copy transitioning/temporary palette
|
||||
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
|
||||
@ -637,6 +637,42 @@ 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
|
||||
@ -652,6 +688,9 @@ 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;
|
||||
}
|
||||
@ -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)) {
|
||||
@ -833,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
|
||||
@ -1069,9 +1183,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_
|
||||
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));
|
||||
}
|
||||
@ -1092,6 +1204,7 @@ void WS2812FX::finalizeInit(void) {
|
||||
// for the lack of better place enumerate ledmaps here
|
||||
// if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs
|
||||
// unfortunately this means we do not get updates after uploads
|
||||
// the other option is saving UI settings which will cause enumeration
|
||||
enumerateLedmaps();
|
||||
|
||||
_hasWhiteChannel = _isOffRefreshRequired = false;
|
||||
@ -1118,7 +1231,7 @@ void WS2812FX::finalizeInit(void) {
|
||||
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);
|
||||
BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer);
|
||||
if (BusManager::add(defCfg) == -1) break;
|
||||
}
|
||||
}
|
||||
@ -1135,11 +1248,12 @@ void WS2812FX::finalizeInit(void) {
|
||||
unsigned busEnd = bus->getStart() + bus->getLength();
|
||||
if (busEnd > _length) _length = busEnd;
|
||||
#ifdef ESP8266
|
||||
if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue;
|
||||
uint8_t pins[5];
|
||||
if (!bus->getPins(pins)) continue;
|
||||
BusDigital* bd = static_cast<BusDigital*>(bus);
|
||||
if (pins[0] == 3) bd->reinit();
|
||||
// why do we need to reinitialise GPIO3???
|
||||
//if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue;
|
||||
//uint8_t pins[5];
|
||||
//if (!bus->getPins(pins)) continue;
|
||||
//BusDigital* bd = static_cast<BusDigital*>(bus);
|
||||
//if (pins[0] == 3) bd->reinit();
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -1184,7 +1298,7 @@ void WS2812FX::service() {
|
||||
_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);
|
||||
@ -1227,7 +1341,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
|
||||
@ -1481,7 +1595,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
|
||||
|
||||
@ -1509,6 +1623,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 {
|
||||
|
||||
@ -1554,6 +1669,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();
|
||||
@ -1584,12 +1701,11 @@ 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));
|
||||
size = getLengthTotal();
|
||||
if (useGlobalLedBuffer) DEBUG_PRINTF_P(PSTR("Buffer: %d*%u=%uB\n"), sizeof(CRGB), size, size*sizeof(CRGB));
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1653,13 +1769,15 @@ bool WS2812FX::deserializeMap(uint8_t n) {
|
||||
bool isFile = WLED_FS.exists(fileName);
|
||||
|
||||
customMappingSize = 0; // prevent use of mapping if anything goes wrong
|
||||
currentLedmap = 0;
|
||||
if (n == 0 || isFile) interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update (to inform UI)
|
||||
|
||||
if (!isFile && n==0 && isMatrix) {
|
||||
setUpMatrix();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isFile || !requestJSONBufferLock(7)) return false; // this will trigger setUpMatrix() when called from wled.cpp
|
||||
if (!isFile || !requestJSONBufferLock(7)) return false;
|
||||
|
||||
if (!readObjectFromFile(fileName, nullptr, pDoc)) {
|
||||
DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName);
|
||||
@ -1683,6 +1801,7 @@ bool WS2812FX::deserializeMap(uint8_t n) {
|
||||
if (!map.isNull() && map.size()) { // not an empty map
|
||||
customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal());
|
||||
for (unsigned i=0; i<customMappingSize; i++) customMappingTable[i] = (uint16_t) (map[i]<0 ? 0xFFFFU : map[i]);
|
||||
currentLedmap = n;
|
||||
}
|
||||
} else {
|
||||
DEBUG_PRINTLN(F("ERROR LED map allocation error."));
|
||||
|
@ -270,12 +270,6 @@ bool BusDigital::canShow() {
|
||||
|
||||
void BusDigital::setBrightness(uint8_t b) {
|
||||
if (_bri == b) return;
|
||||
//Fix for turning off onboard LED breaking bus
|
||||
#ifdef LED_BUILTIN
|
||||
if (_bri == 0) { // && b > 0, covered by guard if above
|
||||
if (_pins[0] == LED_BUILTIN || _pins[1] == LED_BUILTIN) reinit();
|
||||
}
|
||||
#endif
|
||||
Bus::setBrightness(b);
|
||||
PolyBus::setBrightness(_busPtr, _iType, b);
|
||||
}
|
||||
@ -391,14 +385,14 @@ BusPwm::BusPwm(BusConfig &bc)
|
||||
uint8_t numPins = NUM_PWM_PINS(bc.type);
|
||||
_frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ;
|
||||
|
||||
#ifdef ESP8266
|
||||
#ifdef ESP8266
|
||||
// duty cycle resolution (_depth) can be extracted from this formula: 1MHz > _frequency * 2^_depth
|
||||
if (_frequency > 1760) _depth = 8;
|
||||
else if (_frequency > 880) _depth = 9;
|
||||
else _depth = 10; // WLED_PWM_FREQ <= 880Hz
|
||||
analogWriteRange((1<<_depth)-1);
|
||||
analogWriteFreq(_frequency);
|
||||
#else
|
||||
#else
|
||||
_ledcStart = pinManager.allocateLedc(numPins);
|
||||
if (_ledcStart == 255) { //no more free LEDC channels
|
||||
deallocatePins(); return;
|
||||
@ -408,7 +402,7 @@ BusPwm::BusPwm(BusConfig &bc)
|
||||
else if (_frequency > 39062) _depth = 10;
|
||||
else if (_frequency > 19531) _depth = 11;
|
||||
else _depth = 12; // WLED_PWM_FREQ <= 19531Hz
|
||||
#endif
|
||||
#endif
|
||||
|
||||
for (unsigned i = 0; i < numPins; i++) {
|
||||
uint8_t currentPin = bc.pins[i];
|
||||
@ -425,6 +419,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) {
|
||||
@ -575,6 +570,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) {
|
||||
@ -629,6 +625,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) {
|
||||
@ -682,11 +679,11 @@ uint32_t BusManager::memUsage(BusConfig &bc) {
|
||||
if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem
|
||||
multiplier = 5;
|
||||
}
|
||||
#else //ESP32 RMT uses double buffer, I2S uses 5x buffer
|
||||
multiplier = 2;
|
||||
#else //ESP32 RMT uses double buffer, parallel I2S uses 8x buffer (3 times)
|
||||
multiplier = PolyBus::isParallelI2S1Output() ? 24 : 2;
|
||||
#endif
|
||||
}
|
||||
return len * channels * multiplier; //RGB
|
||||
return (len * multiplier + bc.doubleBuffer * (bc.count + bc.skipAmount)) * channels;
|
||||
}
|
||||
|
||||
int BusManager::add(BusConfig &bc) {
|
||||
@ -703,6 +700,11 @@ int BusManager::add(BusConfig &bc) {
|
||||
return numBusses++;
|
||||
}
|
||||
|
||||
void BusManager::useParallelOutput(void) {
|
||||
_parallelOutputs = 8; // hardcoded since we use NPB I2S x8 methods
|
||||
PolyBus::setParallelI2S1Output();
|
||||
}
|
||||
|
||||
//do not call this method from system context (network callback)
|
||||
void BusManager::removeAll() {
|
||||
DEBUG_PRINTLN(F("Removing all."));
|
||||
@ -710,6 +712,79 @@ void BusManager::removeAll() {
|
||||
while (!canAllShow()) yield();
|
||||
for (unsigned i = 0; i < numBusses; i++) delete busses[i];
|
||||
numBusses = 0;
|
||||
_parallelOutputs = 1;
|
||||
PolyBus::setParallelI2S1Output(false);
|
||||
}
|
||||
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
// #2478
|
||||
// If enabled, RMT idle level is set to HIGH when off
|
||||
// to prevent leakage current when using an N-channel MOSFET to toggle LED power
|
||||
void BusManager::esp32RMTInvertIdle() {
|
||||
bool idle_out;
|
||||
unsigned rmt = 0;
|
||||
for (unsigned u = 0; u < numBusses(); u++) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, only has 1 I2S but NPB does not support it ATM
|
||||
if (u > 1) return;
|
||||
rmt = u;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, only has 1 I2S bus, supported in NPB
|
||||
if (u > 3) return;
|
||||
rmt = u;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, has 2 I2S but NPB does not support them ATM
|
||||
if (u > 3) return;
|
||||
rmt = u;
|
||||
#else
|
||||
if (u < _parallelOutputs) continue;
|
||||
if (u >= _parallelOutputs + 8) return; // only 8 RMT channels
|
||||
rmt = u - _parallelOutputs;
|
||||
#endif
|
||||
if (busses[u]->getLength()==0 || !IS_DIGITAL(busses[u]->getType()) || IS_2PIN(busses[u]->getType())) continue;
|
||||
//assumes that bus number to rmt channel mapping stays 1:1
|
||||
rmt_channel_t ch = static_cast<rmt_channel_t>(rmt);
|
||||
rmt_idle_level_t lvl;
|
||||
rmt_get_idle_level(ch, &idle_out, &lvl);
|
||||
if (lvl == RMT_IDLE_LEVEL_HIGH) lvl = RMT_IDLE_LEVEL_LOW;
|
||||
else if (lvl == RMT_IDLE_LEVEL_LOW) lvl = RMT_IDLE_LEVEL_HIGH;
|
||||
else continue;
|
||||
rmt_set_idle_level(ch, idle_out, lvl);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void BusManager::on() {
|
||||
#ifdef ESP8266
|
||||
//Fix for turning off onboard LED breaking bus
|
||||
if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) {
|
||||
for (unsigned i = 0; i < numBusses; i++) {
|
||||
uint8_t pins[2] = {255,255};
|
||||
if (IS_DIGITAL(busses[i]->getType()) && busses[i]->getPins(pins)) {
|
||||
if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) {
|
||||
BusDigital *bus = static_cast<BusDigital*>(busses[i]);
|
||||
bus->reinit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
esp32RMTInvertIdle();
|
||||
#endif
|
||||
}
|
||||
|
||||
void BusManager::off() {
|
||||
#ifdef ESP8266
|
||||
// turn off built-in LED if strip is turned off
|
||||
// this will break digital bus so will need to be re-initialised on On
|
||||
if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) {
|
||||
for (unsigned i = 0; i < numBusses; i++) if (busses[i]->isOffRefreshRequired()) return;
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
}
|
||||
#endif
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
esp32RMTInvertIdle();
|
||||
#endif
|
||||
}
|
||||
|
||||
void BusManager::show() {
|
||||
@ -729,9 +804,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);
|
||||
}
|
||||
}
|
||||
@ -753,10 +827,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;
|
||||
}
|
||||
@ -780,6 +853,8 @@ uint16_t BusManager::getTotalLength() {
|
||||
return len;
|
||||
}
|
||||
|
||||
bool PolyBus::useParallelI2S = false;
|
||||
|
||||
// Bus static member definition
|
||||
int16_t Bus::_cct = -1;
|
||||
uint8_t Bus::_cctBlend = 0;
|
||||
@ -792,3 +867,4 @@ Bus* BusManager::busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES];
|
||||
ColorOrderMap BusManager::colorOrderMap = {};
|
||||
uint16_t BusManager::_milliAmpsUsed = 0;
|
||||
uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT;
|
||||
uint8_t BusManager::_parallelOutputs = 1;
|
||||
|
@ -21,10 +21,6 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb);
|
||||
#define IC_INDEX_WS2812_2CH_3X(i) ((i)*2/3)
|
||||
#define WS2812_2CH_3X_SPANS_2_ICS(i) ((i)&0x01) // every other LED zone is on two different ICs
|
||||
|
||||
// flag for using double buffering in BusDigital
|
||||
extern bool useGlobalLedBuffer;
|
||||
|
||||
|
||||
//temporary struct for passing bus configuration to bus
|
||||
struct BusConfig {
|
||||
uint8_t type;
|
||||
@ -41,7 +37,7 @@ struct BusConfig {
|
||||
uint8_t milliAmpsPerLed;
|
||||
uint16_t milliAmpsMax;
|
||||
|
||||
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=55, uint16_t maMax=ABL_MILLIAMPS_DEFAULT)
|
||||
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT)
|
||||
: count(len)
|
||||
, start(pstart)
|
||||
, colorOrder(pcolorOrder)
|
||||
@ -133,7 +129,7 @@ class Bus {
|
||||
virtual uint32_t getPixelColor(uint16_t pix) { return 0; }
|
||||
virtual void setBrightness(uint8_t b) { _bri = b; };
|
||||
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
|
||||
virtual uint16_t getLength() { return _len; }
|
||||
virtual uint16_t getLength() { return isOk() ? _len : 0; }
|
||||
virtual void setColorOrder(uint8_t co) {}
|
||||
virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
|
||||
virtual uint8_t skippedLeds() { return 0; }
|
||||
@ -173,10 +169,11 @@ class Bus {
|
||||
type == TYPE_FW1906 || type == TYPE_WS2805 ) return true;
|
||||
return false;
|
||||
}
|
||||
static int16_t getCCT() { return _cct; }
|
||||
static inline int16_t getCCT() { return _cct; }
|
||||
static void setCCT(int16_t cct) {
|
||||
_cct = cct;
|
||||
}
|
||||
static inline uint8_t getCCTBlend() { return _cctBlend; }
|
||||
static void setCCTBlend(uint8_t b) {
|
||||
if (b > 100) b = 100;
|
||||
_cctBlend = (b * 127) / 100;
|
||||
@ -363,10 +360,14 @@ class BusManager {
|
||||
static uint16_t ablMilliampsMax(void) { return _milliAmpsMax; }
|
||||
|
||||
static int add(BusConfig &bc);
|
||||
static void useParallelOutput(void); // workaround for inaccessible PolyBus
|
||||
|
||||
//do not call this method from system context (network callback)
|
||||
static void removeAll();
|
||||
|
||||
static void on(void);
|
||||
static void off(void);
|
||||
|
||||
static void show();
|
||||
static bool canAllShow();
|
||||
static void setStatusPixel(uint32_t c);
|
||||
@ -394,7 +395,11 @@ class BusManager {
|
||||
static ColorOrderMap colorOrderMap;
|
||||
static uint16_t _milliAmpsUsed;
|
||||
static uint16_t _milliAmpsMax;
|
||||
static uint8_t _parallelOutputs;
|
||||
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
static void esp32RMTInvertIdle();
|
||||
#endif
|
||||
static uint8_t getNumVirtualBusses() {
|
||||
int j = 0;
|
||||
for (int i=0; i<numBusses; i++) if (busses[i]->getType() >= TYPE_NET_DDP_RGB && busses[i]->getType() < 96) j++;
|
||||
|
@ -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;
|
||||
}
|
||||
@ -342,69 +358,35 @@ void handleButton()
|
||||
}
|
||||
}
|
||||
|
||||
// If enabled, RMT idle level is set to HIGH when off
|
||||
// to prevent leakage current when using an N-channel MOSFET to toggle LED power
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
void esp32RMTInvertIdle()
|
||||
{
|
||||
bool idle_out;
|
||||
for (uint8_t u = 0; u < BusManager::getNumBusses(); u++)
|
||||
{
|
||||
if (u > 7) return; // only 8 RMT channels, TODO: ESP32 variants have less RMT channels
|
||||
Bus *bus = BusManager::getBus(u);
|
||||
if (!bus || bus->getLength()==0 || !IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType())) continue;
|
||||
//assumes that bus number to rmt channel mapping stays 1:1
|
||||
rmt_channel_t ch = static_cast<rmt_channel_t>(u);
|
||||
rmt_idle_level_t lvl;
|
||||
rmt_get_idle_level(ch, &idle_out, &lvl);
|
||||
if (lvl == RMT_IDLE_LEVEL_HIGH) lvl = RMT_IDLE_LEVEL_LOW;
|
||||
else if (lvl == RMT_IDLE_LEVEL_LOW) lvl = RMT_IDLE_LEVEL_HIGH;
|
||||
else continue;
|
||||
rmt_set_idle_level(ch, idle_out, lvl);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// handleIO() happens *after* handleTransitions() (see wled.cpp) which may change bri/briT but *before* strip.service()
|
||||
// where actual LED painting occurrs
|
||||
// this is important for relay control and in the event of turning off on-board LED
|
||||
void handleIO()
|
||||
{
|
||||
handleButton();
|
||||
|
||||
//set relay when LEDs turn on
|
||||
if (strip.getBrightness())
|
||||
{
|
||||
// if we want to control on-board LED (ESP8266) or relay we have to do it here as the final show() may not happen until
|
||||
// next loop() cycle
|
||||
if (strip.getBrightness()) {
|
||||
lastOnTime = millis();
|
||||
if (offMode)
|
||||
{
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
esp32RMTInvertIdle();
|
||||
#endif
|
||||
if (offMode) {
|
||||
BusManager::on();
|
||||
if (rlyPin>=0) {
|
||||
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
|
||||
digitalWrite(rlyPin, rlyMde);
|
||||
}
|
||||
offMode = false;
|
||||
}
|
||||
} else if (millis() - lastOnTime > 600)
|
||||
{
|
||||
} else if (millis() - lastOnTime > 600 && !strip.needsUpdate()) {
|
||||
// for turning LED or relay off we need to wait until strip no longer needs updates (strip.trigger())
|
||||
if (!offMode) {
|
||||
#ifdef ESP8266
|
||||
// turn off built-in LED if strip is turned off
|
||||
// this will break digital bus so will need to be re-initialised on On
|
||||
PinOwner ledPinOwner = pinManager.getPinOwner(LED_BUILTIN);
|
||||
if (!strip.isOffRefreshRequired() && (ledPinOwner == PinOwner::None || ledPinOwner == PinOwner::BusDigital)) {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
}
|
||||
#endif
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
esp32RMTInvertIdle();
|
||||
#endif
|
||||
BusManager::off();
|
||||
if (rlyPin>=0) {
|
||||
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
|
||||
digitalWrite(rlyPin, !rlyMde);
|
||||
}
|
||||
offMode = true;
|
||||
}
|
||||
offMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,15 +79,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
getStringFromJson(apSSID, ap[F("ssid")], 33);
|
||||
getStringFromJson(apPass, ap["psk"] , 65); //normally not present due to security
|
||||
//int ap_pskl = ap[F("pskl")];
|
||||
|
||||
CJSON(apChannel, ap[F("chan")]);
|
||||
if (apChannel > 13 || apChannel < 1) apChannel = 1;
|
||||
|
||||
CJSON(apHide, ap[F("hide")]);
|
||||
if (apHide > 1) apHide = 1;
|
||||
|
||||
CJSON(apBehavior, ap[F("behav")]);
|
||||
|
||||
/*
|
||||
JsonArray ap_ip = ap["ip"];
|
||||
for (byte i = 0; i < 4; i++) {
|
||||
@ -95,9 +91,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
}
|
||||
*/
|
||||
|
||||
noWifiSleep = doc[F("wifi")][F("sleep")] | !noWifiSleep; // inverted
|
||||
noWifiSleep = !noWifiSleep;
|
||||
force802_3g = doc[F("wifi")][F("phy")] | force802_3g; //force phy mode g?
|
||||
JsonObject wifi = doc[F("wifi")];
|
||||
noWifiSleep = !(wifi[F("sleep")] | !noWifiSleep); // inverted
|
||||
//noWifiSleep = !noWifiSleep;
|
||||
CJSON(force802_3g, wifi[F("phy")]); //force phy mode g?
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
CJSON(txPower, wifi[F("txpwr")]);
|
||||
txPower = min(max((int)txPower, (int)WIFI_POWER_2dBm), (int)WIFI_POWER_19_5dBm);
|
||||
#endif
|
||||
|
||||
JsonObject hw = doc[F("hw")];
|
||||
|
||||
@ -124,7 +125,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,18 +157,42 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
JsonArray ins = hw_led["ins"];
|
||||
|
||||
if (fromFS || !ins.isNull()) {
|
||||
uint8_t s = 0; // bus iterator
|
||||
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
|
||||
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;
|
||||
uint32_t mem = 0;
|
||||
bool busesChanged = false;
|
||||
// determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT)
|
||||
bool useParallel = false;
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH_ESP32S2) && !defined(ARDUINO_ARCH_ESP32S3) && !defined(ARDUINO_ARCH_ESP32C3)
|
||||
unsigned digitalCount = 0;
|
||||
unsigned maxLeds = 0;
|
||||
int oldType = 0;
|
||||
int j = 0;
|
||||
for (JsonObject elm : ins) {
|
||||
unsigned type = elm["type"] | TYPE_WS2812_RGB;
|
||||
unsigned len = elm["len"] | 30;
|
||||
if (IS_DIGITAL(type) && !IS_2PIN(type)) digitalCount++;
|
||||
if (len > maxLeds) maxLeds = len;
|
||||
// we need to have all LEDs of the same type for parallel
|
||||
if (j++ < 8 && oldType > 0 && oldType != type) oldType = -1;
|
||||
else if (oldType == 0) oldType = type;
|
||||
}
|
||||
DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\nDifferent types: %d\n"), maxLeds, digitalCount, (int)(oldType == -1));
|
||||
// we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0
|
||||
if (/*oldType != -1 && */maxLeds <= 300 && digitalCount > 5) {
|
||||
useParallel = true;
|
||||
BusManager::useParallelOutput();
|
||||
DEBUG_PRINTF_P(PSTR("Switching to parallel I2S with max. %d LEDs per ouptut.\n"), maxLeds);
|
||||
}
|
||||
#endif
|
||||
for (JsonObject elm : ins) {
|
||||
if (s >= WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES) break;
|
||||
uint8_t pins[5] = {255, 255, 255, 255, 255};
|
||||
JsonArray pinArr = elm["pin"];
|
||||
if (pinArr.size() == 0) continue;
|
||||
pins[0] = pinArr[0];
|
||||
uint8_t i = 0;
|
||||
unsigned i = 0;
|
||||
for (int p : pinArr) {
|
||||
pins[i++] = p;
|
||||
if (i>4) break;
|
||||
@ -193,12 +218,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
|
||||
if (fromFS) {
|
||||
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax);
|
||||
mem += BusManager::memUsage(bc);
|
||||
if (useGlobalLedBuffer && start + length > maxlen) {
|
||||
maxlen = start + length;
|
||||
globalBufMem = maxlen * 4;
|
||||
}
|
||||
if (mem + globalBufMem <= MAX_LED_MEMORY) if (BusManager::add(bc) == -1) break; // finalization will be done in WLED::beginStrip()
|
||||
if (useParallel && s < 8) {
|
||||
// we are using parallel I2S and memUsage() will include x8 allocation into account
|
||||
if (s == 0)
|
||||
mem = BusManager::memUsage(bc); // includes x8 memory allocation for parallel I2S
|
||||
else
|
||||
if (BusManager::memUsage(bc) > mem)
|
||||
mem = BusManager::memUsage(bc); // if we have unequal LED count use the largest
|
||||
} else
|
||||
mem += BusManager::memUsage(bc); // includes global buffer
|
||||
if (mem <= MAX_LED_MEMORY) if (BusManager::add(bc) == -1) break; // finalization will be done in WLED::beginStrip()
|
||||
} else {
|
||||
if (busConfigs[s] != nullptr) delete busConfigs[s];
|
||||
busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax);
|
||||
@ -206,6 +235,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
}
|
||||
s++;
|
||||
}
|
||||
DEBUG_PRINTF_P(PSTR("LED buffer size: %uB\n"), mem);
|
||||
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
|
||||
doInitBusses = busesChanged;
|
||||
// finalization done in beginStrip()
|
||||
}
|
||||
@ -215,7 +246,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
JsonArray hw_com = hw[F("com")];
|
||||
if (!hw_com.isNull()) {
|
||||
ColorOrderMap com = {};
|
||||
uint8_t s = 0;
|
||||
unsigned s = 0;
|
||||
for (JsonObject entry : hw_com) {
|
||||
if (s > WLED_MAX_COLOR_ORDER_MAPPINGS) break;
|
||||
uint16_t start = entry["start"] | 0;
|
||||
@ -234,10 +265,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
disablePullUp = !pull;
|
||||
JsonArray hw_btn_ins = btn_obj["ins"];
|
||||
if (!hw_btn_ins.isNull()) {
|
||||
for (uint8_t b = 0; b < WLED_MAX_BUTTONS; b++) { // deallocate existing button pins
|
||||
pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button
|
||||
}
|
||||
uint8_t s = 0;
|
||||
// deallocate existing button pins
|
||||
for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button
|
||||
unsigned s = 0;
|
||||
for (JsonObject btn : hw_btn_ins) {
|
||||
CJSON(buttonType[s], btn["type"]);
|
||||
int8_t pin = btn["pin"][0] | -1;
|
||||
@ -745,8 +775,11 @@ void serializeConfig() {
|
||||
JsonObject wifi = root.createNestedObject(F("wifi"));
|
||||
wifi[F("sleep")] = !noWifiSleep;
|
||||
wifi[F("phy")] = force802_3g;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
wifi[F("txpwr")] = txPower;
|
||||
#endif
|
||||
|
||||
#ifdef WLED_USE_ETHERNET
|
||||
#ifdef WLED_USE_ETHERNET
|
||||
JsonObject ethernet = root.createNestedObject("eth");
|
||||
ethernet["type"] = ethernetType;
|
||||
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
|
||||
@ -768,7 +801,7 @@ void serializeConfig() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
JsonObject hw = root.createNestedObject(F("hw"));
|
||||
|
||||
@ -790,7 +823,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;
|
||||
@ -806,7 +839,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();
|
||||
@ -815,7 +848,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();
|
||||
@ -829,7 +862,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;
|
||||
|
||||
@ -846,7 +879,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");
|
||||
|
@ -208,13 +208,13 @@ CRGBPalette16 generateRandomPalette(void) //generate fully random palette
|
||||
|
||||
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb
|
||||
{
|
||||
float h = ((float)hue)/65535.0f;
|
||||
float h = ((float)hue)/10922.5f; // hue*6/65535
|
||||
float s = ((float)sat)/255.0f;
|
||||
int i = floorf(h*6);
|
||||
float f = h * 6.0f - i;
|
||||
int i = int(h);
|
||||
float f = h - i;
|
||||
int p = int(255.0f * (1.0f-s));
|
||||
int q = int(255.0f * (1.0f-f*s));
|
||||
int t = int(255.0f * (1.0f-(1.0f-f)*s));
|
||||
int q = int(255.0f * (1.0f-s*f));
|
||||
int t = int(255.0f * (1.0f-s*(1.0f-f)));
|
||||
p = constrain(p, 0, 255);
|
||||
q = constrain(q, 0, 255);
|
||||
t = constrain(t, 0, 255);
|
||||
|
@ -46,36 +46,58 @@
|
||||
|
||||
#ifndef WLED_MAX_BUSSES
|
||||
#ifdef ESP8266
|
||||
#define WLED_MAX_BUSSES 3
|
||||
#define WLED_MAX_DIGITAL_CHANNELS 3
|
||||
#define WLED_MAX_ANALOG_CHANNELS 5
|
||||
#define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB
|
||||
#define WLED_MIN_VIRTUAL_BUSSES 2
|
||||
#else
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM
|
||||
#define WLED_MAX_BUSSES 3 // will allow 2 digital & 1 analog (or the other way around)
|
||||
#define WLED_MAX_BUSSES 4 // will allow 2 digital & 2 analog RGB
|
||||
#define WLED_MAX_DIGITAL_CHANNELS 2
|
||||
#define WLED_MAX_ANALOG_CHANNELS 6
|
||||
#define WLED_MIN_VIRTUAL_BUSSES 3
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB
|
||||
// the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though)
|
||||
#define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog
|
||||
#define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB
|
||||
#define WLED_MAX_DIGITAL_CHANNELS 5
|
||||
#define WLED_MAX_ANALOG_CHANNELS 8
|
||||
#define WLED_MIN_VIRTUAL_BUSSES 3
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM
|
||||
#define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog
|
||||
#define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog RGB
|
||||
#define WLED_MAX_DIGITAL_CHANNELS 4
|
||||
#define WLED_MAX_ANALOG_CHANNELS 8
|
||||
#define WLED_MIN_VIRTUAL_BUSSES 4
|
||||
#else
|
||||
// the 10th digital bus (I2S0) will prevent Audioreactive usermod from functioning (it is last used though)
|
||||
#define WLED_MAX_BUSSES 10
|
||||
#define WLED_MIN_VIRTUAL_BUSSES 0
|
||||
// the last digital bus (I2S0) will prevent Audioreactive usermod from functioning
|
||||
#define WLED_MAX_BUSSES 20 // will allow 17 digital & 3 analog RGB
|
||||
#define WLED_MAX_DIGITAL_CHANNELS 17
|
||||
#define WLED_MAX_ANALOG_CHANNELS 10
|
||||
#define WLED_MIN_VIRTUAL_BUSSES 4
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#ifdef ESP8266
|
||||
#if WLED_MAX_BUSES > 5
|
||||
#if WLED_MAX_BUSSES > 5
|
||||
#error Maximum number of buses is 5.
|
||||
#endif
|
||||
#ifndef WLED_MAX_ANALOG_CHANNELS
|
||||
#error You must also define WLED_MAX_ANALOG_CHANNELS.
|
||||
#endif
|
||||
#ifndef WLED_MAX_DIGITAL_CHANNELS
|
||||
#error You must also define WLED_MAX_DIGITAL_CHANNELS.
|
||||
#endif
|
||||
#define WLED_MIN_VIRTUAL_BUSSES (5-WLED_MAX_BUSSES)
|
||||
#else
|
||||
#if WLED_MAX_BUSES > 10
|
||||
#error Maximum number of buses is 10.
|
||||
#if WLED_MAX_BUSSES > 20
|
||||
#error Maximum number of buses is 20.
|
||||
#endif
|
||||
#define WLED_MIN_VIRTUAL_BUSSES (10-WLED_MAX_BUSSES)
|
||||
#ifndef WLED_MAX_ANALOG_CHANNELS
|
||||
#error You must also define WLED_MAX_ANALOG_CHANNELS.
|
||||
#endif
|
||||
#ifndef WLED_MAX_DIGITAL_CHANNELS
|
||||
#error You must also define WLED_MAX_DIGITAL_CHANNELS.
|
||||
#endif
|
||||
#define WLED_MIN_VIRTUAL_BUSSES (20-WLED_MAX_BUSSES)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -174,6 +196,10 @@
|
||||
#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"
|
||||
#define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h"
|
||||
|
||||
//Access point behavior
|
||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||
@ -189,9 +215,9 @@
|
||||
#define CALL_MODE_INIT 0 //no updates on init, can be used to disable updates
|
||||
#define CALL_MODE_DIRECT_CHANGE 1
|
||||
#define CALL_MODE_BUTTON 2 //default button actions applied to selected segments
|
||||
#define CALL_MODE_NOTIFICATION 3
|
||||
#define CALL_MODE_NIGHTLIGHT 4
|
||||
#define CALL_MODE_NO_NOTIFY 5
|
||||
#define CALL_MODE_NOTIFICATION 3 //caused by incoming notification (UDP or DMX preset)
|
||||
#define CALL_MODE_NIGHTLIGHT 4 //nightlight progress
|
||||
#define CALL_MODE_NO_NOTIFY 5 //change state but do not send notifications (UDP)
|
||||
#define CALL_MODE_FX_CHANGED 6 //no longer used
|
||||
#define CALL_MODE_HUE 7
|
||||
#define CALL_MODE_PRESET_CYCLE 8 //no longer used
|
||||
@ -268,6 +294,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
|
||||
@ -324,7 +351,7 @@
|
||||
#define BTN_TYPE_TOUCH_SWITCH 9
|
||||
|
||||
//Ethernet board types
|
||||
#define WLED_NUM_ETH_TYPES 12
|
||||
#define WLED_NUM_ETH_TYPES 13
|
||||
|
||||
#define WLED_ETH_NONE 0
|
||||
#define WLED_ETH_WT32_ETH01 1
|
||||
@ -338,6 +365,7 @@
|
||||
#define WLED_ETH_ABCWLEDV43ETH 9
|
||||
#define WLED_ETH_SERG74 10
|
||||
#define WLED_ETH_ESP32_POE_WROVER 11
|
||||
#define WLED_ETH_LILYGO_T_POE_PRO 12
|
||||
|
||||
//Hue error codes
|
||||
#define HUE_ERROR_INACTIVE 0
|
||||
@ -474,6 +502,16 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef LED_MILLIAMPS_DEFAULT
|
||||
#define LED_MILLIAMPS_DEFAULT 55 // common WS2812B
|
||||
#else
|
||||
#if LED_MILLIAMPS_DEFAULT < 1 || LED_MILLIAMPS_DEFAULT > 100
|
||||
#warning "Unusual LED mA current, overriding with default value."
|
||||
#undef LED_MILLIAMPS_DEFAULT
|
||||
#define LED_MILLIAMPS_DEFAULT 55
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// PWM settings
|
||||
#ifndef WLED_PWM_FREQ
|
||||
#ifdef ESP8266
|
||||
|
@ -923,6 +923,7 @@ select.sel-p, select.sel-pl, select.sel-ple {
|
||||
margin: 5px 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 20px 0 8px;
|
||||
}
|
||||
div.sel-p {
|
||||
position: relative;
|
||||
|
@ -126,9 +126,10 @@
|
||||
<button id="hexcnf" class="btn btn-xs" onclick="fromHex();"><i class="icons btn-icon"></i></button>
|
||||
</div>
|
||||
<div style="padding: 8px 0;" id="btns">
|
||||
<button class="btn btn-xs" title="File editor" type="button" id="edit" onclick="window.location.href=getURL('/edit')"><i class="icons btn-icon"></i></button>
|
||||
<button class="btn btn-xs" title="Pixel Magic Tool" type="button" id="pxmb" onclick="window.location.href=getURL('/pxmagic.htm')"><i class="icons btn-icon"></i></button>
|
||||
<button class="btn btn-xs" title="Add custom palette" type="button" onclick="window.location.href=getURL('/cpal.htm')"><i class="icons btn-icon"></i></button>
|
||||
<button class="btn btn-xs" title="Remove custom palette" type="button" id="rmPal" onclick="palettesData=null;localStorage.removeItem('wledPalx');requestJson({rmcpal:true});setTimeout(loadPalettes,250,loadPalettesData);"><i class="icons btn-icon"></i></button>
|
||||
<button class="btn btn-xs" title="Remove last custom palette" type="button" id="rmPal" onclick="palettesData=null;localStorage.removeItem('wledPalx');requestJson({rmcpal:true});setTimeout(loadPalettes,250,loadPalettesData);"><i class="icons btn-icon"></i></button>
|
||||
</div>
|
||||
<p class="labels hd" id="pall"><i class="icons sel-icon" onclick="tglHex()"></i> Color palette</p>
|
||||
<div id="palw" class="il">
|
||||
|
@ -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');
|
||||
@ -281,12 +282,12 @@ function onLoad()
|
||||
// fill effect extra data array
|
||||
loadFXData(()=>{
|
||||
// load and populate effects
|
||||
loadFX(()=>{
|
||||
setTimeout(()=>{loadFX(()=>{
|
||||
loadPalettesData(()=>{
|
||||
requestJson();// will load presets and create WS
|
||||
if (cfg.comp.css) setTimeout(()=>{loadSkinCSS('skinCss')},50);
|
||||
});
|
||||
});
|
||||
})},50);
|
||||
});
|
||||
});
|
||||
resetUtil();
|
||||
@ -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;
|
||||
@ -573,7 +588,7 @@ function loadFXData(callback = null)
|
||||
fxdata = [];
|
||||
if (!retry) {
|
||||
retry = true;
|
||||
setTimeout(loadFXData, 500); // retry
|
||||
setTimeout(()=>{loadFXData(loadFX);}, 500); // retry
|
||||
}
|
||||
showToast(e, true);
|
||||
})
|
||||
@ -654,18 +669,15 @@ function parseInfo(i) {
|
||||
//syncTglRecv = i.str;
|
||||
maxSeg = i.leds.maxseg;
|
||||
pmt = i.fs.pmt;
|
||||
if (pcMode && !i.wifi.ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide");
|
||||
gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none";
|
||||
// do we have a matrix set-up
|
||||
mw = i.leds.matrix ? i.leds.matrix.w : 0;
|
||||
mh = i.leds.matrix ? i.leds.matrix.h : 0;
|
||||
isM = mw>0 && mh>0;
|
||||
if (!isM) {
|
||||
//gId("filter0D").classList.remove('hide');
|
||||
//gId("filter1D").classList.add('hide');
|
||||
gId("filter2D").classList.add('hide');
|
||||
} else {
|
||||
//gId("filter0D").classList.add('hide');
|
||||
//gId("filter1D").classList.remove('hide');
|
||||
gId("filter2D").classList.remove('hide');
|
||||
}
|
||||
// if (i.noaudio) {
|
||||
@ -730,10 +742,10 @@ ${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")}
|
||||
</table>`;
|
||||
gId('kv').innerHTML = cn;
|
||||
// update all sliders in Info
|
||||
for (let sd of (d.querySelectorAll('#kv .sliderdisplay')||[])) {
|
||||
d.querySelectorAll('#kv .sliderdisplay').forEach((sd,i) => {
|
||||
let s = sd.previousElementSibling;
|
||||
if (s) updateTrail(s);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function populateSegments(s)
|
||||
@ -786,6 +798,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>`+
|
||||
@ -879,8 +892,8 @@ function populateSegments(s)
|
||||
gId('segutil2').style.display = (segCount > 1) ? "block":"none"; // rsbtn parent
|
||||
|
||||
if (Array.isArray(li.maps) && li.maps.length>1) {
|
||||
let cont = `Ledmap: <select class="sel-sg" onchange="requestJson({'ledmap':parseInt(this.value)})"><option value="" selected>Unchanged</option>`;
|
||||
for (const k of (li.maps||[])) cont += `<option value="${k.id}">${k.id==0?'Default':(k.n?k.n:'ledmap'+k.id+'.json')}</option>`;
|
||||
let cont = `Ledmap: <select class="sel-sg" onchange="requestJson({'ledmap':parseInt(this.value)})">`;
|
||||
for (const k of li.maps) cont += `<option ${s.ledmap===k.id?"selected":""} value="${k.id}">${k.id==0?'Default':(k.n?k.n:'ledmap'+k.id+'.json')}</option>`;
|
||||
cont += "</select></div>";
|
||||
gId("ledmap").innerHTML = cont;
|
||||
gId("ledmap").classList.remove('hide');
|
||||
@ -975,13 +988,12 @@ function populatePalettes()
|
||||
|
||||
function redrawPalPrev()
|
||||
{
|
||||
let palettes = d.querySelectorAll('#pallist .lstI');
|
||||
for (var pal of (palettes||[])) {
|
||||
d.querySelectorAll('#pallist .lstI').forEach((pal,i) =>{
|
||||
let lP = pal.querySelector('.lstIprev');
|
||||
if (lP) {
|
||||
lP.style = genPalPrevCss(pal.dataset.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function genPalPrevCss(id)
|
||||
@ -1342,10 +1354,12 @@ function updateSelectedFx()
|
||||
}
|
||||
|
||||
// hide 2D mapping and/or sound simulation options
|
||||
var segs = gId("segcont").querySelectorAll(`div[data-map="map2D"]`);
|
||||
for (const seg of segs) if (selectedName.indexOf("\u25A6")<0) seg.classList.remove('hide'); else seg.classList.add('hide');
|
||||
var segs = gId("segcont").querySelectorAll(`div[data-snd="si"]`);
|
||||
for (const seg of segs) if (selectedName.indexOf("\u266A")<0 && selectedName.indexOf("\u266B")<0) seg.classList.add('hide'); else seg.classList.remove('hide'); // also "♫ "?
|
||||
gId("segcont").querySelectorAll(`div[data-map="map2D"]`).forEach((seg)=>{
|
||||
if (selectedName.indexOf("\u25A6")<0) seg.classList.remove('hide'); else seg.classList.add('hide');
|
||||
});
|
||||
gId("segcont").querySelectorAll(`div[data-snd="si"]`).forEach((seg)=>{
|
||||
if (selectedName.indexOf("\u266A")<0 && selectedName.indexOf("\u266B")<0) seg.classList.add('hide'); else seg.classList.remove('hide'); // also "♫ "?
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1482,6 +1496,12 @@ function readState(s,command=false)
|
||||
if (s.error && s.error != 0) {
|
||||
var errstr = "";
|
||||
switch (s.error) {
|
||||
case 1:
|
||||
errstr = "Denied!";
|
||||
break;
|
||||
case 3:
|
||||
errstr = "Buffer locked!";
|
||||
break;
|
||||
case 8:
|
||||
errstr = "Effect RAM depleted!";
|
||||
break;
|
||||
@ -1546,8 +1566,7 @@ function setEffectParameters(idx)
|
||||
var paOnOff = (effectPars.length<3 || effectPars[2]=='')?[]:effectPars[2].split(",");
|
||||
|
||||
// set html slider items on/off
|
||||
let sliders = d.querySelectorAll("#sliders .sliderwrap");
|
||||
sliders.forEach((slider, i)=>{
|
||||
d.querySelectorAll("#sliders .sliderwrap").forEach((slider, i)=>{
|
||||
let text = slider.getAttribute("title");
|
||||
if ((!controlDefined && i<((idx<128)?2:nSliders)) || (slOnOff.length>i && slOnOff[i]!="")) {
|
||||
if (slOnOff.length>i && slOnOff[i]!="!") text = slOnOff[i];
|
||||
@ -1561,8 +1580,7 @@ function setEffectParameters(idx)
|
||||
|
||||
if (slOnOff.length > 5) { // up to 3 checkboxes
|
||||
gId('fxopt').classList.remove('fade');
|
||||
let checks = d.querySelectorAll("#sliders .ochkl");
|
||||
checks.forEach((check, i)=>{
|
||||
d.querySelectorAll("#sliders .ochkl").forEach((check, i)=>{
|
||||
let text = check.getAttribute("title");
|
||||
if (5+i<slOnOff.length && slOnOff[5+i]!=='') {
|
||||
if (slOnOff.length>5+i && slOnOff[5+i]!="!") text = slOnOff[5+i];
|
||||
@ -1689,9 +1707,7 @@ function requestJson(command=null)
|
||||
|
||||
fetch(getURL('/json/si'), {
|
||||
method: type,
|
||||
headers: {
|
||||
"Content-type": "application/json; charset=UTF-8"
|
||||
},
|
||||
headers: {"Content-Type": "application/json; charset=UTF-8"},
|
||||
body: req
|
||||
})
|
||||
.then(res => {
|
||||
@ -2009,7 +2025,7 @@ ${makePlSel(plJson[i].end?plJson[i].end:0, true)}
|
||||
</label>`;
|
||||
if (Array.isArray(lastinfo.maps) && lastinfo.maps.length>1) {
|
||||
content += `<div class="lbl-l">Ledmap: <div class="sel-p"><select class="sel-p" id="p${i}lmp"><option value="">Unchanged</option>`;
|
||||
for (const k of (lastinfo.maps||[])) content += `<option value="${k.id}"${(i>0 && pJson[i].ledmap==k.id)?" selected":""}>${k.id==0?'Default':(k.n?k.n:'ledmap'+k.id+'.json')}</option>`;
|
||||
for (const k of lastinfo.maps) content += `<option value="${k.id}"${(i>0 && pJson[i].ledmap==k.id)?" selected":""}>${k.id==0?'Default':(k.n?k.n:'ledmap'+k.id+'.json')}</option>`;
|
||||
content += "</select></div></div>";
|
||||
}
|
||||
}
|
||||
@ -2157,13 +2173,12 @@ function selGrp(g)
|
||||
{
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
var sel = gId(`segcont`).querySelectorAll(`div[data-set="${g}"]`);
|
||||
var obj = {"seg":[]};
|
||||
for (let i=0; i<=lSeg; i++) if (gId(`seg${i}`)) obj.seg.push({"id":i,"sel":false});
|
||||
for (let s of (sel||[])) {
|
||||
gId(`segcont`).querySelectorAll(`div[data-set="${g}"]`).forEach((s)=>{
|
||||
let i = parseInt(s.id.substring(3));
|
||||
obj.seg[i] = {"id":i,"sel":true};
|
||||
}
|
||||
});
|
||||
if (obj.seg.length) requestJson(obj);
|
||||
}
|
||||
|
||||
@ -2675,7 +2690,9 @@ function setBalance(b)
|
||||
function rmtTgl(ip,i) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
fetch(`http://${ip}/win&T=2`, {method: 'get'})
|
||||
fetch(`http://${ip}/win&T=2`, {
|
||||
method: 'get'
|
||||
})
|
||||
.then((r)=>{
|
||||
return r.text();
|
||||
})
|
||||
@ -2767,21 +2784,23 @@ function loadPalettesData(callback = null)
|
||||
function getPalettesData(page, callback)
|
||||
{
|
||||
fetch(getURL(`/json/palx?page=${page}`), {
|
||||
method: 'get',
|
||||
headers: {
|
||||
"Content-type": "application/json; charset=UTF-8"
|
||||
}
|
||||
method: 'get'
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) showErrorToast();
|
||||
return res.json();
|
||||
})
|
||||
.then(json => {
|
||||
retry = false;
|
||||
palettesData = Object.assign({}, palettesData, json.p);
|
||||
if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 50);
|
||||
if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 75);
|
||||
else callback();
|
||||
})
|
||||
.catch((error)=>{
|
||||
if (!retry) {
|
||||
retry = true;
|
||||
setTimeout(()=>{getPalettesData(page,callback);}, 500); // retry
|
||||
}
|
||||
showToast(error, true);
|
||||
});
|
||||
}
|
||||
@ -2804,9 +2823,9 @@ function search(field, listId = null) {
|
||||
const search = field.value !== '';
|
||||
|
||||
// restore default preset sorting if no search term is entered
|
||||
if (listId === 'pcont' && !search) {
|
||||
populatePresets();
|
||||
return;
|
||||
if (!search) {
|
||||
if (listId === 'pcont') { populatePresets(); return; }
|
||||
if (listId === 'pallist') { populatePalettes(); return; }
|
||||
}
|
||||
|
||||
// clear filter if searching in fxlist
|
||||
@ -2817,15 +2836,15 @@ function search(field, listId = null) {
|
||||
// do not search if filter is active
|
||||
if (gId("filters").querySelectorAll("input[type=checkbox]:checked").length) return;
|
||||
|
||||
const listItems = gId(listId).querySelectorAll('.lstI');
|
||||
// filter list items but leave (Default & Solid) always visible
|
||||
for (i = (listId === 'pcont' ? 0 : 1); i < listItems.length; i++) {
|
||||
const listItem = listItems[i];
|
||||
const listItems = gId("fxlist").querySelectorAll('.lstI');
|
||||
listItems.forEach((listItem,i)=>{
|
||||
if (listId!=='pcont' && i===0) return;
|
||||
const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase();
|
||||
const searchIndex = listItemName.indexOf(field.value.toUpperCase());
|
||||
listItem.style.display = (searchIndex < 0) ? 'none' : '';
|
||||
listItem.dataset.searchIndex = searchIndex;
|
||||
}
|
||||
});
|
||||
|
||||
// sort list items by search index and name
|
||||
const sortedListItems = Array.from(listItems).sort((a, b) => {
|
||||
@ -2886,14 +2905,12 @@ function filterFx() {
|
||||
inputField.value = '';
|
||||
inputField.focus();
|
||||
clean(inputField.nextElementSibling);
|
||||
const listItems = gId("fxlist").querySelectorAll('.lstI');
|
||||
for (let i = 1; i < listItems.length; i++) {
|
||||
const listItem = listItems[i];
|
||||
gId("fxlist").querySelectorAll('.lstI').forEach((listItem,i) => {
|
||||
const listItemName = listItem.querySelector('.lstIname').innerText;
|
||||
let hide = false;
|
||||
gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = true; });
|
||||
listItem.style.display = hide ? 'none' : '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function preventBlur(e) {
|
||||
@ -3044,6 +3061,7 @@ function size()
|
||||
|
||||
function togglePcMode(fromB = false)
|
||||
{
|
||||
let ap = (fromB && !lastinfo) || (lastinfo && lastinfo.wifi && lastinfo.wifi.ap);
|
||||
if (fromB) {
|
||||
pcModeA = !pcModeA;
|
||||
localStorage.setItem('pcm', pcModeA);
|
||||
@ -3051,9 +3069,9 @@ 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":"";
|
||||
if (pcMode && !ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide");
|
||||
gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto";
|
||||
sCol('--bh', gId('bot').clientHeight + "px");
|
||||
_C.style.width = (pcMode || simplifiedUI)?'100%':'400%';
|
||||
@ -3079,8 +3097,7 @@ function mergeDeep(target, ...sources)
|
||||
|
||||
function tooltip(cont=null)
|
||||
{
|
||||
const elements = d.querySelectorAll((cont?cont+" ":"")+"[title]");
|
||||
elements.forEach((element)=>{
|
||||
d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{
|
||||
element.addEventListener("mouseover", ()=>{
|
||||
// save title
|
||||
element.setAttribute("data-title", element.getAttribute("title"));
|
||||
@ -3107,8 +3124,7 @@ function tooltip(cont=null)
|
||||
});
|
||||
|
||||
element.addEventListener("mouseout", ()=>{
|
||||
const tooltips = d.querySelectorAll('.tooltip');
|
||||
tooltips.forEach((tooltip)=>{
|
||||
d.querySelectorAll('.tooltip').forEach((tooltip)=>{
|
||||
tooltip.classList.remove("visible");
|
||||
d.body.removeChild(tooltip);
|
||||
});
|
||||
@ -3214,6 +3230,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);
|
||||
|
@ -5,12 +5,13 @@
|
||||
<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,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=4096,maxL=1333,maxCO=10,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
|
||||
var oMaxB=1;
|
||||
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 +58,16 @@
|
||||
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=5,d=2,a=6) {
|
||||
// maxB - max buses (can be changed if using ESP32 parallel I2S)
|
||||
// maxD - max digital channels (can be changed if using ESP32 parallel I2S)
|
||||
// maxA - max analog channels
|
||||
// maxV - min virtual buses
|
||||
// maxPB - max LEDs per bus
|
||||
// maxM - max LED memory
|
||||
// maxL - max LEDs
|
||||
// maxCO - max Color Order mappings
|
||||
oMaxB = maxB = b; maxD = d, maxA = a, maxV = v; maxM = m; maxPB = p; maxL = l; maxCO = o;
|
||||
}
|
||||
function pinsOK() {
|
||||
var ok = true;
|
||||
@ -117,7 +126,10 @@
|
||||
if (!pinsOK()) {e.stopPropagation();return false;} // Prevent form submission and contact with server
|
||||
if (bquot > 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);}
|
||||
if (!d.Sf.ABL.checked || d.Sf.PPL.checked) d.Sf.MA.value = 0; // submit 0 as ABL (PPL will handle it)
|
||||
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
|
||||
if (d.Sf.checkValidity()) {
|
||||
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{s.disabled=false;}); // just in case
|
||||
d.Sf.submit(); //https://stackoverflow.com/q/37323914
|
||||
}
|
||||
}
|
||||
function enABL()
|
||||
{
|
||||
@ -138,7 +150,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
|
||||
@ -164,8 +177,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;
|
||||
@ -209,9 +223,12 @@
|
||||
let busMA = 0;
|
||||
let sLC = 0, sPC = 0, sDI = 0, maxLC = 0;
|
||||
const ablEN = d.Sf.ABL.checked;
|
||||
maxB = oMaxB; // TODO make sure we start with all possible buses
|
||||
|
||||
// enable/disable LED fields
|
||||
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{
|
||||
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
|
||||
LTs.forEach((s,i)=>{
|
||||
if (i < LTs.length-1) s.disabled = true; // prevent changing type (as we can't update options)
|
||||
// is the field a LED type?
|
||||
var n = s.name.substring(2);
|
||||
var t = parseInt(s.value);
|
||||
@ -273,6 +290,7 @@
|
||||
// do we have a led count field
|
||||
if (nm=="LC") {
|
||||
let c = parseInt(LC.value,10); //get LED count
|
||||
if (c > 300 && i < 8) maxB = oMaxB - max(maxD-7,0); //TODO: hard limit for buses when using ESP32 parallel I2S
|
||||
if (!customStarts || !startsDirty[n]) gId("ls"+n).value=sLC; //update start value
|
||||
gId("ls"+n).disabled = !customStarts; //enable/disable field editing
|
||||
if (c) {
|
||||
@ -358,58 +376,70 @@
|
||||
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 (isNaN(v)) return 0;
|
||||
return v;
|
||||
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
|
||||
return isNaN(v) ? 0 : v;
|
||||
}
|
||||
function addLEDs(n,init=true)
|
||||
{
|
||||
var o = d.getElementsByClassName("iST");
|
||||
var i = o.length;
|
||||
|
||||
if ((n==1 && i>=maxB+maxV) || (n==-1 && i==0)) return;
|
||||
let disable = (sel,opt) => { sel.querySelectorAll(opt).forEach((o)=>{o.disabled=true;}); }
|
||||
|
||||
var f = gId("mLC");
|
||||
let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0;
|
||||
f.querySelectorAll("select[name^=LT]").forEach((s)=>{
|
||||
let t = s.value;
|
||||
if (isDig(t) && !isD2P(t)) digitalB++;
|
||||
if (isD2P(t)) twopinB++;
|
||||
if (isPWM(t)) analogB += t-40; // type defines PWM pins
|
||||
if (isVir(t)) virtB++;
|
||||
});
|
||||
|
||||
if ((n==1 && i>=maxB+maxV) || (n==-1 && i==0)) return;
|
||||
var s = String.fromCharCode((i<10?48:55)+i);
|
||||
|
||||
if (n==1) {
|
||||
// npm run build has trouble minimizing spaces inside string
|
||||
var cn = `<div class="iST">
|
||||
<hr class="sml">
|
||||
${i+1}:
|
||||
<select name="LT${i}" onchange="UI(true)">${i>=maxB ? '' :
|
||||
'<option value="22" selected>WS281x</option>\
|
||||
<option value="30">SK6812/WS2814 RGBW</option>\
|
||||
<option value="31">TM1814</option>\
|
||||
<option value="24">400kHz</option>\
|
||||
<option value="25">TM1829</option>\
|
||||
<option value="26">UCS8903</option>\
|
||||
<option value="27">APA106/PL9823</option>\
|
||||
<option value="28">FW1906 GRBCW</option>\
|
||||
<option value="29">UCS8904 RGBW</option>\
|
||||
<option value="32">WS2805 RGBCW</option>\
|
||||
<option value="50">WS2801</option>\
|
||||
<option value="51">APA102</option>\
|
||||
<option value="52">LPD8806</option>\
|
||||
<option value="54">LPD6803</option>\
|
||||
<option value="53">P9813</option>\
|
||||
<option value="19">WS2811 White</option>\
|
||||
<select name="LT${s}" onchange="UI(true)">${i>=maxB && false ? '' :
|
||||
'<option value="22" data-type="D">WS281x</option>\
|
||||
<option value="30" data-type="D">SK6812/WS2814 RGBW</option>\
|
||||
<option value="31" data-type="D">TM1814</option>\
|
||||
<option value="24" data-type="D">400kHz</option>\
|
||||
<option value="25" data-type="D">TM1829</option>\
|
||||
<option value="26" data-type="D">UCS8903</option>\
|
||||
<option value="27" data-type="D">APA106/PL9823</option>\
|
||||
<option value="33" data-type="D">TM1914</option>\
|
||||
<option value="28" data-type="D">FW1906 GRBCW</option>\
|
||||
<option value="29" data-type="D">UCS8904 RGBW</option>\
|
||||
<option value="32" data-type="D">WS2805 RGBCW</option>\
|
||||
<option value="50" data-type="2P">WS2801</option>\
|
||||
<option value="51" data-type="2P">APA102</option>\
|
||||
<option value="52" data-type="2P">LPD8806</option>\
|
||||
<option value="54" data-type="2P">LPD6803</option>\
|
||||
<option value="53" data-type="2P">P9813</option>\
|
||||
<option value="19" data-type="D">WS2811 White</option>\
|
||||
<option value="40">On/Off</option>\
|
||||
<option value="41">PWM White</option>\
|
||||
<option value="42">PWM CCT</option>\
|
||||
<option value="43">PWM RGB</option>\
|
||||
<option value="44">PWM RGBW</option>\
|
||||
<option value="45">PWM RGB+CCT</option>\
|
||||
<!--option value="46">PWM RGB+DCCT</option-->'}
|
||||
<option value="80">DDP RGB (network)</option>
|
||||
<!--option value="81">E1.31 RGB (network)</option-->
|
||||
<option value="82">Art-Net RGB (network)</option>
|
||||
<option value="88">DDP RGBW (network)</option>
|
||||
<option value="89">Art-Net RGBW (network)</option>
|
||||
<option value="41" data-type="A">PWM White</option>\
|
||||
<option value="42" data-type="AA">PWM CCT</option>\
|
||||
<option value="43" data-type="AAA">PWM RGB</option>\
|
||||
<option value="44" data-type="AAAA">PWM RGBW</option>\
|
||||
<option value="45" data-type="AAAAA">PWM RGB+CCT</option>\
|
||||
<!--option value="46" data-type="AAAAAA">PWM RGB+DCCT</option-->'}
|
||||
<option value="80" data-type="V">DDP RGB (network)</option>
|
||||
<!--option value="81" data-type="V">E1.31 RGB (network)</option-->
|
||||
<option value="82" data-type="V">Art-Net RGB (network)</option>
|
||||
<option value="88" data-type="V">DDP RGBW (network)</option>
|
||||
<option value="89" data-type="V">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 +447,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="255" 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,23 +459,28 @@ 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 />
|
||||
<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 />
|
||||
<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> </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> </div>
|
||||
</div>`;
|
||||
f.insertAdjacentHTML("beforeend", cn);
|
||||
let sel = d.getElementsByName("LT"+s)[0]
|
||||
if (i >= maxB || digitalB >= maxD) disable(sel,'option[data-type="D"]');
|
||||
if (i >= maxB || twopinB >= 1) disable(sel,'option[data-type="2P"]');
|
||||
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`);
|
||||
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
|
||||
}
|
||||
if (n==-1) {
|
||||
o[--i].remove();--i;
|
||||
@ -461,14 +496,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="">
|
||||
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="">
|
||||
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 +511,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 +519,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 +536,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 +545,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 += ` <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 += ` <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 +564,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}')"> ✕</span><br>`;
|
||||
c += `<span style="cursor: pointer;" onclick="off('BT${s}')"> ✕</span><br>`;
|
||||
gId("btns").innerHTML = c;
|
||||
}
|
||||
function tglSi(cs) {
|
||||
@ -809,6 +843,7 @@ Swap: <select id="xw${i}" name="XW${i}">
|
||||
<div id="btns"></div>
|
||||
Disable internal pull-up/down: <input type="checkbox" name="IP"><br>
|
||||
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
|
||||
<hr class="sml">
|
||||
IR GPIO: <input type="number" min="-1" max="48" name="IR" onchange="UI()" class="xs"><select name="IT" onchange="UI()">
|
||||
<option value=0>Remote disabled</option>
|
||||
<option value=1>24-key RGB</option>
|
||||
@ -823,7 +858,9 @@ 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"><span style="cursor: pointer;" onclick="off('RL')"> ✕</span> Invert <input type="checkbox" name="RM"> Open drain <input type="checkbox" name="RO"><br>
|
||||
<hr class="sml">
|
||||
Relay GPIO: <input type="number" min="-1" max="48" name="RL" onchange="UI()" class="xs"><span style="cursor: pointer;" onclick="off('RL')"> ✕</span><br>
|
||||
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>
|
||||
|
@ -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 <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>
|
||||
|
@ -224,8 +224,23 @@ Static subnet mask:<br>
|
||||
<h3>Experimental</h3>
|
||||
Force 802.11g mode (ESP8266 only): <input type="checkbox" name="FG"><br>
|
||||
Disable WiFi sleep: <input type="checkbox" name="WS"><br>
|
||||
<i>Can help with connectivity issues.<br>
|
||||
Do not enable if WiFi is working correctly, increases power consumption.</i>
|
||||
<i>Can help with connectivity issues and Audioreactive sync.<br>
|
||||
Disabling WiFi sleep increases power consumption.</i><br>
|
||||
<div id="tx">TX power: <select name="TX">
|
||||
<option value="78">19.5 dBm</option>
|
||||
<option value="76">19 dBm</option>
|
||||
<option value="74">18.5 dBm</option>
|
||||
<option value="68">17 dBm</option>
|
||||
<option value="60">15 dBm</option>
|
||||
<option value="52">13 dBm</option>
|
||||
<option value="44">11 dBm</option>
|
||||
<option value="34">8.5 dBm</option>
|
||||
<option value="28">7 dBm</option>
|
||||
<option value="20">5 dBm</option>
|
||||
<option value="8">2 dBm</option>
|
||||
</select><br>
|
||||
<i class="warn">WARNING: Modifying TX power may render device unreachable.</i>
|
||||
</div>
|
||||
|
||||
<h3>ESP-NOW Wireless</h3>
|
||||
<div id="NoESPNOW" class="hide">
|
||||
@ -248,6 +263,7 @@ Static subnet mask:<br>
|
||||
<option value="11">ESP32-POE-WROVER</option>
|
||||
<option value="6">ESP32Deux/RGB2Go Tetra</option>
|
||||
<option value="7">KIT-VE</option>
|
||||
<option value="12">LILYGO T-POE Pro</option>
|
||||
<option value="8">QuinLED-Dig-Octa & T-ETH-POE</option>
|
||||
<option value="4">QuinLED-ESP32</option>
|
||||
<option value="10">Serg74-ETH32</option>
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
@ -322,6 +310,7 @@ class Usermod {
|
||||
virtual bool readFromConfig(JsonObject& obj) { return true; } // Note as of 2021-06 readFromConfig() now needs to return a bool, see usermod_v2_example.h
|
||||
virtual void onMqttConnect(bool sessionPresent) {} // fired when MQTT connection is established (so usermod can subscribe)
|
||||
virtual bool onMqttMessage(char* topic, char* payload) { return false; } // fired upon MQTT message received (wled topic)
|
||||
virtual bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) { return false; } // fired upon ESP-NOW message received
|
||||
virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update
|
||||
virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change
|
||||
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
|
||||
@ -345,8 +334,13 @@ class UsermodManager {
|
||||
void readFromJsonState(JsonObject& obj);
|
||||
void addToConfig(JsonObject& obj);
|
||||
bool readFromConfig(JsonObject& obj);
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
void onMqttConnect(bool sessionPresent);
|
||||
bool onMqttMessage(char* topic, char* payload);
|
||||
#endif
|
||||
#ifndef WLED_DISABLE_ESPNOW
|
||||
bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len);
|
||||
#endif
|
||||
void onUpdateBegin(bool);
|
||||
void onStateChange(uint8_t);
|
||||
bool add(Usermod* um);
|
||||
@ -410,7 +404,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 +415,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
|
||||
|
@ -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) {
|
||||
|
@ -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-b3/%i"), VERSION);
|
||||
sprintf_P(vString, PSTR("0.15.0-b4/%i"), VERSION);
|
||||
const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription};
|
||||
|
||||
sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str);
|
||||
|
254
wled00/ir.cpp
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
@ -588,6 +590,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
|
||||
|
||||
root["ps"] = (currentPreset > 0) ? currentPreset : -1;
|
||||
root[F("pl")] = currentPlaylist;
|
||||
root[F("ledmap")] = currentLedmap;
|
||||
|
||||
usermods.addToJsonState(root);
|
||||
|
||||
@ -596,7 +599,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,7 +639,7 @@ void serializeInfo(JsonObject root)
|
||||
root[F("ver")] = versionString;
|
||||
root[F("vid")] = VERSION;
|
||||
root[F("cn")] = F(WLED_CODENAME);
|
||||
root[F("release")] = FPSTR(releaseString);
|
||||
root[F("release")] = releaseString;
|
||||
|
||||
JsonObject leds = root.createNestedObject(F("leds"));
|
||||
leds[F("count")] = strip.getLengthTotal();
|
||||
@ -731,6 +734,7 @@ void serializeInfo(JsonObject root)
|
||||
wifi_info[F("rssi")] = qrssi;
|
||||
wifi_info[F("signal")] = getSignalQuality(qrssi);
|
||||
wifi_info[F("channel")] = WiFi.channel();
|
||||
wifi_info[F("ap")] = apActive;
|
||||
|
||||
JsonObject fs_info = root.createNestedObject("fs");
|
||||
fs_info["u"] = fsBytesUsed / 1000;
|
||||
|
@ -195,7 +195,7 @@ void handleTransitions()
|
||||
applyFinalBri();
|
||||
return;
|
||||
}
|
||||
if (tper - tperLast < 0.004f) return;
|
||||
if (tper - tperLast < 0.004f) return; // less than 1 bit change (1/255)
|
||||
tperLast = tper;
|
||||
briT = briOld + ((bri - briOld) * tper);
|
||||
|
||||
|
@ -103,20 +103,17 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
|
||||
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 (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();
|
||||
}
|
||||
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);
|
||||
|
@ -133,6 +133,17 @@ const ethernet_settings ethernetBoards[] = {
|
||||
18, // eth_mdio,
|
||||
ETH_PHY_LAN8720, // eth_type,
|
||||
ETH_CLOCK_GPIO0_OUT // eth_clk_mode
|
||||
},
|
||||
|
||||
// LILYGO T-POE Pro
|
||||
// https://github.com/Xinyuan-LilyGO/LilyGO-T-ETH-Series/blob/master/schematic/T-POE-PRO.pdf
|
||||
{
|
||||
0, // eth_address,
|
||||
5, // eth_power,
|
||||
23, // eth_mdc,
|
||||
18, // eth_mdio,
|
||||
ETH_PHY_LAN8720, // eth_type,
|
||||
ETH_CLOCK_GPIO0_OUT // eth_clk_mode
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
@ -243,10 +243,14 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
// strapping pins: 2, 8, & 9
|
||||
if (gpio > 11 && gpio < 18) return false; // 11-17 SPI FLASH
|
||||
#if ARDUINO_USB_CDC_ON_BOOT == 1 || ARDUINO_USB_DFU_ON_BOOT == 1
|
||||
if (gpio > 17 && gpio < 20) return false; // 18-19 USB-JTAG
|
||||
#endif
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// 00 to 18 are for general use. Be careful about straping pins GPIO0 and GPIO3 - these may be pulled-up or pulled-down on your board.
|
||||
#if ARDUINO_USB_CDC_ON_BOOT == 1 || ARDUINO_USB_DFU_ON_BOOT == 1
|
||||
if (gpio > 18 && gpio < 21) return false; // 19 + 20 = USB-JTAG. Not recommended for other uses.
|
||||
#endif
|
||||
if (gpio > 21 && gpio < 33) return false; // 22 to 32: not connected + SPI FLASH
|
||||
if (gpio > 32 && gpio < 38) return !psramFound(); // 33 to 37: not available if using _octal_ SPI Flash or _octal_ PSRAM
|
||||
// 38 to 48 are for general use. Be careful about straping pins GPIO45 and GPIO46 - these may be pull-up or pulled-down on your board.
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
if (t != apChannel) forceReconnect = true;
|
||||
if (t > 0 && t < 14) apChannel = t;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
int tx = request->arg(F("TX")).toInt();
|
||||
txPower = min(max(tx, (int)WIFI_POWER_2dBm), (int)WIFI_POWER_19_5dBm);
|
||||
#endif
|
||||
|
||||
force802_3g = request->hasArg(F("FG"));
|
||||
noWifiSleep = request->hasArg(F("WS"));
|
||||
|
||||
@ -104,7 +109,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 +141,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 +216,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 +241,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
irPin = -1;
|
||||
}
|
||||
irEnabled = request->arg(F("IT")).toInt();
|
||||
initIR();
|
||||
#endif
|
||||
irApplyToAllSelected = !request->hasArg(F("MSO"));
|
||||
|
||||
@ -247,9 +256,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
|
||||
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;
|
||||
@ -901,6 +911,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);
|
||||
|
||||
|
@ -969,6 +969,11 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs
|
||||
DEBUG_PRINTLN();
|
||||
#endif
|
||||
|
||||
#ifndef WLED_DISABLE_ESPNOW
|
||||
// usermods hook can override processing
|
||||
if (usermods.onEspNowMessage(address, data, len)) return;
|
||||
#endif
|
||||
|
||||
// handle WiZ Mote data
|
||||
if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) {
|
||||
handleRemote(data, len);
|
||||
|
@ -34,11 +34,19 @@ bool UsermodManager::readFromConfig(JsonObject& obj) {
|
||||
}
|
||||
return allComplete;
|
||||
}
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
void UsermodManager::onMqttConnect(bool sessionPresent) { for (byte i = 0; i < numMods; i++) ums[i]->onMqttConnect(sessionPresent); }
|
||||
bool UsermodManager::onMqttMessage(char* topic, char* payload) {
|
||||
for (byte i = 0; i < numMods; i++) if (ums[i]->onMqttMessage(topic, payload)) return true;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
#ifndef WLED_DISABLE_ESPNOW
|
||||
bool UsermodManager::onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) {
|
||||
for (byte i = 0; i < numMods; i++) if (ums[i]->onEspNowMessage(sender, payload, len)) return true;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
void UsermodManager::onUpdateBegin(bool init) { for (byte i = 0; i < numMods; i++) ums[i]->onUpdateBegin(init); } // notify usermods that update is to begin
|
||||
void UsermodManager::onStateChange(uint8_t mode) { for (byte i = 0; i < numMods; i++) ums[i]->onStateChange(mode); } // notify usermods that WLED state changed
|
||||
|
||||
|
@ -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
|
||||
@ -221,6 +226,18 @@
|
||||
#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
|
||||
|
||||
#ifdef USERMOD_LD2410
|
||||
#include "../usermods/LD2410_v2/usermod_ld2410.h"
|
||||
#endif
|
||||
|
||||
void registerUsermods()
|
||||
{
|
||||
/*
|
||||
@ -258,6 +275,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
|
||||
@ -429,4 +450,16 @@ 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
|
||||
|
||||
#ifdef USERMOD_LD2410
|
||||
usermods.add(new LD2410Usermod());
|
||||
#endif
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ bool requestJSONBufferLock(uint8_t module)
|
||||
}
|
||||
unsigned long now = millis();
|
||||
|
||||
while (jsonBufferLock && millis()-now < 100) delay(1); // wait for fraction for buffer lock
|
||||
while (jsonBufferLock && millis()-now < 250) delay(1); // wait for fraction for buffer lock
|
||||
|
||||
if (jsonBufferLock) {
|
||||
DEBUG_PRINT(F("ERROR: Locking JSON buffer failed! (still locked by "));
|
||||
|
@ -175,19 +175,44 @@ void WLED::loop()
|
||||
DEBUG_PRINTLN(F("Re-init busses."));
|
||||
bool aligned = strip.checkSegmentAlignment(); //see if old segments match old bus(ses)
|
||||
BusManager::removeAll();
|
||||
uint32_t mem = 0, globalBufMem = 0;
|
||||
uint16_t maxlen = 0;
|
||||
for (uint8_t i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
|
||||
// determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT)
|
||||
bool useParallel = false;
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH_ESP32S2) && !defined(ARDUINO_ARCH_ESP32S3) && !defined(ARDUINO_ARCH_ESP32C3)
|
||||
unsigned digitalCount = 0;
|
||||
unsigned maxLeds = 0;
|
||||
int oldType = 0;
|
||||
for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
|
||||
if (busConfigs[i] == nullptr) break;
|
||||
mem += BusManager::memUsage(*busConfigs[i]);
|
||||
if (useGlobalLedBuffer && busConfigs[i]->start + busConfigs[i]->count > maxlen) {
|
||||
maxlen = busConfigs[i]->start + busConfigs[i]->count;
|
||||
globalBufMem = maxlen * 4;
|
||||
}
|
||||
if (mem + globalBufMem <= MAX_LED_MEMORY) {
|
||||
BusManager::add(*busConfigs[i]);
|
||||
}
|
||||
delete busConfigs[i]; busConfigs[i] = nullptr;
|
||||
if (IS_DIGITAL(busConfigs[i]->type) && !IS_2PIN(busConfigs[i]->type)) digitalCount++;
|
||||
if (busConfigs[i]->count > maxLeds) maxLeds = busConfigs[i]->count;
|
||||
// we need to have all LEDs of the same type for parallel
|
||||
if (i < 8 && oldType > 0 && oldType != busConfigs[i]->type) oldType = -1;
|
||||
else if (oldType == 0) oldType = busConfigs[i]->type;
|
||||
}
|
||||
DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\nDifferent types: %d\n"), maxLeds, digitalCount, (int)(oldType == -1));
|
||||
// we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0
|
||||
if (/*oldType != -1 && */maxLeds <= 300 && digitalCount > 5) {
|
||||
useParallel = true;
|
||||
BusManager::useParallelOutput();
|
||||
DEBUG_PRINTF_P(PSTR("Switching to parallel I2S with max. %d LEDs per ouptut.\n"), maxLeds);
|
||||
}
|
||||
#endif
|
||||
// create buses/outputs
|
||||
unsigned mem = 0;
|
||||
for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
|
||||
if (busConfigs[i] == nullptr || (!useParallel && i > 10)) break;
|
||||
if (useParallel && i < 8) {
|
||||
// we are using parallel I2S and memUsage() will include x8 allocation into account
|
||||
if (i == 0)
|
||||
mem = BusManager::memUsage(*busConfigs[i]); // includes x8 memory allocation for parallel I2S
|
||||
else
|
||||
if (BusManager::memUsage(*busConfigs[i]) > mem)
|
||||
mem = BusManager::memUsage(*busConfigs[i]); // if we have unequal LED count use the largest
|
||||
} else
|
||||
mem += BusManager::memUsage(*busConfigs[i]); // includes global buffer
|
||||
if (mem <= MAX_LED_MEMORY) BusManager::add(*busConfigs[i]);
|
||||
delete busConfigs[i];
|
||||
busConfigs[i] = nullptr;
|
||||
}
|
||||
strip.finalizeInit(); // also loads default ledmap if present
|
||||
if (aligned) strip.makeAutoSegments();
|
||||
@ -242,12 +267,12 @@ void WLED::loop()
|
||||
DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap());
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
if (psramFound()) {
|
||||
DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB");
|
||||
DEBUG_PRINT(F("Free PSRAM: ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB");
|
||||
DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024);
|
||||
if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM."));
|
||||
}
|
||||
DEBUG_PRINTF_P(PSTR("TX power: %d/%d\n"), WiFi.getTxPower(), txPower);
|
||||
#endif
|
||||
DEBUG_PRINT(F("Wifi state: ")); DEBUG_PRINTLN(WiFi.status());
|
||||
DEBUG_PRINTF_P(PSTR("Wifi state: %d\n"), WiFi.status());
|
||||
#ifndef WLED_DISABLE_ESPNOW
|
||||
DEBUG_PRINT(F("ESP-NOW state: ")); DEBUG_PRINTLN(statusESPNow);
|
||||
#endif
|
||||
@ -378,9 +403,9 @@ void WLED::setup()
|
||||
DEBUG_PRINT(F("JSON buffer allocated: ")); DEBUG_PRINTLN((psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE);
|
||||
// if the above fails requestJsonBufferLock() will always return false preventing crashes
|
||||
if (psramFound()) {
|
||||
DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB");
|
||||
DEBUG_PRINT(F("Free PSRAM : ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB");
|
||||
DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024);
|
||||
}
|
||||
DEBUG_PRINTF_P(PSTR("TX power: %d/%d\n"), WiFi.getTxPower(), txPower);
|
||||
#endif
|
||||
|
||||
#if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST)
|
||||
@ -505,6 +530,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();
|
||||
@ -551,11 +583,13 @@ void WLED::beginStrip()
|
||||
if (bootPreset > 0) {
|
||||
applyPreset(bootPreset, CALL_MODE_INIT);
|
||||
}
|
||||
colorUpdated(CALL_MODE_INIT);
|
||||
colorUpdated(CALL_MODE_INIT); // will not send notification
|
||||
|
||||
// init relay pin
|
||||
if (rlyPin>=0)
|
||||
if (rlyPin >= 0) {
|
||||
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
|
||||
digitalWrite(rlyPin, (rlyMde ? bri : !bri));
|
||||
}
|
||||
}
|
||||
|
||||
void WLED::initAP(bool resetAP)
|
||||
@ -571,8 +605,8 @@ void WLED::initAP(bool resetAP)
|
||||
DEBUG_PRINTLN(apSSID);
|
||||
WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0));
|
||||
WiFi.softAP(apSSID, apPass, apChannel, apHide);
|
||||
#if defined(LOLIN_WIFI_FIX) && (defined(ARDUINO_ARCH_ESP32C3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32S3))
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
WiFi.setTxPower(wifi_power_t(txPower));
|
||||
#endif
|
||||
|
||||
if (!apActive) // start captive portal if AP active
|
||||
@ -795,9 +829,7 @@ void WLED::initConnection()
|
||||
WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass); // no harm if called multiple times
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#if defined(LOLIN_WIFI_FIX) && (defined(ARDUINO_ARCH_ESP32C3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32S3))
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm);
|
||||
#endif
|
||||
WiFi.setTxPower(wifi_power_t(txPower));
|
||||
WiFi.setSleep(!noWifiSleep);
|
||||
WiFi.setHostname(hostname);
|
||||
#else
|
||||
|
@ -3,12 +3,12 @@
|
||||
/*
|
||||
Main sketch, global variable declarations
|
||||
@title WLED project sketch
|
||||
@version 0.15.0-b3
|
||||
@version 0.15.0-b4
|
||||
@author Christian Schwinne
|
||||
*/
|
||||
|
||||
// version code in format yymmddb (b = daily build)
|
||||
#define VERSION 2404120
|
||||
#define VERSION 2406290
|
||||
|
||||
//uncomment this if you have a "my_config.h" file you'd like to use
|
||||
//#define WLED_USE_MY_CONFIG
|
||||
@ -268,7 +268,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
|
||||
|
||||
// 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[]
|
||||
WLED_GLOBAL char releaseString[] _INIT(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!)
|
||||
@ -334,6 +334,13 @@ WLED_GLOBAL bool noWifiSleep _INIT(true); // disabling m
|
||||
#else
|
||||
WLED_GLOBAL bool noWifiSleep _INIT(false);
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#if defined(LOLIN_WIFI_FIX) && (defined(ARDUINO_ARCH_ESP32C3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32S3))
|
||||
WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_8_5dBm);
|
||||
#else
|
||||
WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_19_5dBm);
|
||||
#endif
|
||||
#endif
|
||||
WLED_GLOBAL bool force802_3g _INIT(false);
|
||||
#define WLED_WIFI_CONFIGURED (strlen(multiWiFi[0].clientSSID) >= 1 && strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) != 0)
|
||||
|
||||
@ -645,6 +652,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
|
||||
@ -754,6 +762,7 @@ WLED_GLOBAL WS2812FX strip _INIT(WS2812FX());
|
||||
WLED_GLOBAL BusConfig* busConfigs[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES] _INIT({nullptr}); //temporary, to remember values from network callback until after
|
||||
WLED_GLOBAL bool doInitBusses _INIT(false);
|
||||
WLED_GLOBAL int8_t loadLedmap _INIT(-1);
|
||||
WLED_GLOBAL uint8_t currentLedmap _INIT(0);
|
||||
#ifndef ESP8266
|
||||
WLED_GLOBAL char *ledmapNames[WLED_MAX_LEDMAPS-1] _INIT_N(({nullptr}));
|
||||
#endif
|
||||
|
@ -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));
|
||||
|
@ -283,6 +283,11 @@ void getSettingsJS(byte subPage, char* dest)
|
||||
sappends('s',SET_F("AP"),fapass);
|
||||
|
||||
sappend('v',SET_F("AC"),apChannel);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
sappend('v',SET_F("TX"),txPower);
|
||||
#else
|
||||
oappend(SET_F("gId('tx').style.display='none';"));
|
||||
#endif
|
||||
sappend('c',SET_F("FG"),force802_3g);
|
||||
sappend('c',SET_F("WS"),noWifiSleep);
|
||||
|
||||
@ -298,7 +303,7 @@ void getSettingsJS(byte subPage, char* dest)
|
||||
sappend('v',SET_F("ETH"),ethernetType);
|
||||
#else
|
||||
//hide ethernet setting if not compiled in
|
||||
oappend(SET_F("document.getElementById('ethd').style.display='none';"));
|
||||
oappend(SET_F("gId('ethd').style.display='none';"));
|
||||
#endif
|
||||
|
||||
if (Network.isConnected()) //is connected
|
||||
@ -350,7 +355,10 @@ 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(",");
|
||||
oappend(itoa(WLED_MAX_DIGITAL_CHANNELS,nS,10)); oappend(",");
|
||||
oappend(itoa(WLED_MAX_ANALOG_CHANNELS,nS,10));
|
||||
oappend(SET_F(");"));
|
||||
|
||||
sappend('c',SET_F("MS"),autoSegments);
|
||||
@ -362,28 +370,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 +404,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 +437,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("));
|
||||
@ -459,7 +468,7 @@ void getSettingsJS(byte subPage, char* dest)
|
||||
sappend('v',SET_F("RL"),rlyPin);
|
||||
sappend('c',SET_F("RM"),rlyMde);
|
||||
sappend('c',SET_F("RO"),rlyOpenDrain);
|
||||
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
|
||||
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(",");
|
||||
@ -724,7 +733,7 @@ void getSettingsJS(byte subPage, char* dest)
|
||||
olen -= 2; //delete ";
|
||||
oappend(versionString);
|
||||
oappend(SET_F("<br>"));
|
||||
oappend((char*)FPSTR(releaseString));
|
||||
oappend(releaseString);
|
||||
oappend(SET_F("<br>("));
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
oappend(ESP.getChipModel());
|
||||
|