Merge branch 'main' into HUB75-AC
This commit is contained in:
commit
199bc45ae2
138
.github/copilot-instructions.md
vendored
Normal file
138
.github/copilot-instructions.md
vendored
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
# WLED - ESP32/ESP8266 LED Controller Firmware
|
||||||
|
|
||||||
|
WLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs and SPI-based chipsets. The project consists of C++ firmware for microcontrollers and a modern web interface.
|
||||||
|
|
||||||
|
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
|
||||||
|
|
||||||
|
## Working Effectively
|
||||||
|
|
||||||
|
### Initial Setup
|
||||||
|
- Install Node.js 20+ (specified in `.nvmrc`): Check your version with `node --version`
|
||||||
|
- Install dependencies: `npm install` (takes ~5 seconds)
|
||||||
|
- Install PlatformIO for hardware builds: `pip install -r requirements.txt` (takes ~60 seconds)
|
||||||
|
|
||||||
|
### Build and Test Workflow
|
||||||
|
- **ALWAYS build web UI first**: `npm run build` -- takes 3 seconds. NEVER CANCEL.
|
||||||
|
- **Run tests**: `npm test` -- takes 40 seconds. NEVER CANCEL. Set timeout to 2+ minutes.
|
||||||
|
- **Development mode**: `npm run dev` -- monitors file changes and auto-rebuilds web UI
|
||||||
|
- **Hardware firmware build**: `pio run -e [environment]` -- takes 15+ minutes. NEVER CANCEL. Set timeout to 30+ minutes.
|
||||||
|
|
||||||
|
### Build Process Details
|
||||||
|
The build has two main phases:
|
||||||
|
1. **Web UI Generation** (`npm run build`):
|
||||||
|
- Processes files in `wled00/data/` (HTML, CSS, JS)
|
||||||
|
- Minifies and compresses web content
|
||||||
|
- Generates `wled00/html_*.h` files with embedded web content
|
||||||
|
- **CRITICAL**: Must be done before any hardware build
|
||||||
|
|
||||||
|
2. **Hardware Compilation** (`pio run`):
|
||||||
|
- Compiles C++ firmware for various ESP32/ESP8266 targets
|
||||||
|
- Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`
|
||||||
|
- List all targets: `pio run --list-targets`
|
||||||
|
|
||||||
|
## Validation and Testing
|
||||||
|
|
||||||
|
### Web UI Testing
|
||||||
|
- **ALWAYS validate web UI changes manually**:
|
||||||
|
- Start local server: `cd wled00/data && python3 -m http.server 8080`
|
||||||
|
- Open `http://localhost:8080/index.htm` in browser
|
||||||
|
- Test basic functionality: color picker, effects, settings pages
|
||||||
|
- **Check for JavaScript errors** in browser console
|
||||||
|
|
||||||
|
### Code Validation
|
||||||
|
- **No automated linting configured** - follow existing code style in files you edit
|
||||||
|
- **Code style**: Use tabs for web files (.html/.css/.js), spaces (2 per level) for C++ files
|
||||||
|
- **C++ formatting available**: `clang-format` is installed but not in CI
|
||||||
|
- **Always run tests before finishing**: `npm test`
|
||||||
|
|
||||||
|
### Manual Testing Scenarios
|
||||||
|
After making changes to web UI, always test:
|
||||||
|
- **Load main interface**: Verify index.htm loads without errors
|
||||||
|
- **Navigation**: Test switching between main page and settings pages
|
||||||
|
- **Color controls**: Verify color picker and brightness controls work
|
||||||
|
- **Effects**: Test effect selection and parameter changes
|
||||||
|
- **Settings**: Test form submission and validation
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
### Repository Structure
|
||||||
|
```
|
||||||
|
wled00/ # Main firmware source (C++)
|
||||||
|
├── data/ # Web interface files
|
||||||
|
│ ├── index.htm # Main UI
|
||||||
|
│ ├── settings*.htm # Settings pages
|
||||||
|
│ └── *.js/*.css # Frontend resources
|
||||||
|
├── *.cpp/*.h # Firmware source files
|
||||||
|
└── html_*.h # Generated embedded web files (DO NOT EDIT)
|
||||||
|
tools/ # Build tools (Node.js)
|
||||||
|
├── cdata.js # Web UI build script
|
||||||
|
└── cdata-test.js # Test suite
|
||||||
|
platformio.ini # Hardware build configuration
|
||||||
|
package.json # Node.js dependencies and scripts
|
||||||
|
.github/workflows/ # CI/CD pipelines
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Files and Their Purpose
|
||||||
|
- `wled00/data/index.htm` - Main web interface
|
||||||
|
- `wled00/data/settings*.htm` - Configuration pages
|
||||||
|
- `tools/cdata.js` - Converts web files to C++ headers
|
||||||
|
- `wled00/wled.h` - Main firmware configuration
|
||||||
|
- `platformio.ini` - Hardware build targets and settings
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
1. **For web UI changes**:
|
||||||
|
- Edit files in `wled00/data/`
|
||||||
|
- Run `npm run build` to regenerate headers
|
||||||
|
- Test with local HTTP server
|
||||||
|
- Run `npm test` to validate build system
|
||||||
|
|
||||||
|
2. **For firmware changes**:
|
||||||
|
- Edit files in `wled00/` (but NOT `html_*.h` files)
|
||||||
|
- Ensure web UI is built first (`npm run build`)
|
||||||
|
- Build firmware: `pio run -e [target]`
|
||||||
|
- Flash to device: `pio run -e [target] --target upload`
|
||||||
|
|
||||||
|
3. **For both web and firmware**:
|
||||||
|
- Always build web UI first
|
||||||
|
- Test web interface manually
|
||||||
|
- Build and test firmware if making firmware changes
|
||||||
|
|
||||||
|
## Build Timing and Timeouts
|
||||||
|
|
||||||
|
- **Web UI build**: 3 seconds - Set timeout to 30 seconds minimum
|
||||||
|
- **Test suite**: 40 seconds - Set timeout to 2 minutes minimum
|
||||||
|
- **Hardware builds**: 15+ minutes - Set timeout to 30+ minutes minimum
|
||||||
|
- **NEVER CANCEL long-running builds** - PlatformIO downloads and compilation can take significant time
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
- **Build fails with missing html_*.h**: Run `npm run build` first
|
||||||
|
- **Web UI looks broken**: Check browser console for JavaScript errors
|
||||||
|
- **PlatformIO network errors**: Try again, downloads can be flaky
|
||||||
|
- **Node.js version issues**: Ensure Node.js 20+ is installed (check `.nvmrc`)
|
||||||
|
|
||||||
|
### When Things Go Wrong
|
||||||
|
- **Clear generated files**: `rm -f wled00/html_*.h` then rebuild
|
||||||
|
- **Force web UI rebuild**: `npm run build -- --force` or `npm run build -- -f`
|
||||||
|
- **Clean PlatformIO cache**: `pio run --target clean`
|
||||||
|
- **Reinstall dependencies**: `rm -rf node_modules && npm install`
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- **DO NOT edit `wled00/html_*.h` files** - they are auto-generated
|
||||||
|
- **Always commit both source files AND generated html_*.h files**
|
||||||
|
- **Web UI must be built before firmware compilation**
|
||||||
|
- **Test web interface manually after any web UI changes**
|
||||||
|
- **Use VS Code with PlatformIO extension for best development experience**
|
||||||
|
- **Hardware builds require appropriate ESP32/ESP8266 development board**
|
||||||
|
|
||||||
|
## CI/CD Pipeline
|
||||||
|
The GitHub Actions workflow:
|
||||||
|
1. Installs Node.js and Python dependencies
|
||||||
|
2. Runs `npm test` to validate build system
|
||||||
|
3. Builds web UI with `npm run build`
|
||||||
|
4. Compiles firmware for multiple hardware targets
|
||||||
|
5. Uploads build artifacts
|
||||||
|
|
||||||
|
Match this workflow in your local development to ensure CI success.
|
||||||
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -40,7 +40,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- run: npm ci
|
- run: |
|
||||||
|
npm ci
|
||||||
|
VERSION=`date +%y%m%d0`
|
||||||
|
sed -i -r -e "s/define VERSION .+/define VERSION $VERSION/" wled00/wled.h
|
||||||
- name: Cache PlatformIO
|
- name: Cache PlatformIO
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
19
.github/workflows/pr-merge.yaml
vendored
19
.github/workflows/pr-merge.yaml
vendored
@ -1,12 +1,13 @@
|
|||||||
name: Notify Discord on PR Merge
|
name: Notify Discord on PR Merge
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request_target:
|
||||||
types: [closed]
|
types: [closed]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
notify:
|
notify:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event.pull_request.merged == true
|
||||||
steps:
|
steps:
|
||||||
- name: Get User Permission
|
- name: Get User Permission
|
||||||
id: checkAccess
|
id: checkAccess
|
||||||
@ -23,11 +24,15 @@
|
|||||||
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
|
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
|
||||||
echo "Job originally triggered by ${{ github.actor }}"
|
echo "Job originally triggered by ${{ github.actor }}"
|
||||||
exit 1
|
exit 1
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }} # This is dangerous without the first access check
|
|
||||||
- name: Send Discord notification
|
- name: Send Discord notification
|
||||||
# if: github.event.pull_request.merged == true
|
env:
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||||
|
ACTOR: ${{ github.actor }}
|
||||||
run: |
|
run: |
|
||||||
curl -H "Content-Type: application/json" -d '{"content": "Pull Request ${{ github.event.pull_request.number }} merged by ${{ github.actor }}"}' ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}
|
jq -n \
|
||||||
|
--arg content "Pull Request #${PR_NUMBER} \"${PR_TITLE}\" merged by ${ACTOR}
|
||||||
|
${PR_URL}" \
|
||||||
|
'{content: $content}' \
|
||||||
|
| curl -H "Content-Type: application/json" -d @- ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}
|
||||||
|
|||||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -23,7 +23,8 @@ jobs:
|
|||||||
uses: janheinrichmerker/action-github-changelog-generator@v2.3
|
uses: janheinrichmerker/action-github-changelog-generator@v2.3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
sinceTag: v0.15.0
|
sinceTag: v0.15.0
|
||||||
|
maxIssues: 500
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
# CI/release binaries
|
# CI/release binaries
|
||||||
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
|
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
|
||||||
|
|
||||||
src_dir = ./wled00
|
src_dir = ./wled00
|
||||||
data_dir = ./wled00/data
|
data_dir = ./wled00/data
|
||||||
@ -142,7 +142,7 @@ lib_deps =
|
|||||||
IRremoteESP8266 @ 2.8.2
|
IRremoteESP8266 @ 2.8.2
|
||||||
makuna/NeoPixelBus @ 2.8.3
|
makuna/NeoPixelBus @ 2.8.3
|
||||||
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
|
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
|
||||||
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0
|
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
|
||||||
# for I2C interface
|
# for I2C interface
|
||||||
;Wire
|
;Wire
|
||||||
# ESP-NOW library
|
# ESP-NOW library
|
||||||
@ -234,25 +234,20 @@ lib_deps_compat =
|
|||||||
|
|
||||||
[esp32_all_variants]
|
[esp32_all_variants]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
willmmiles/AsyncTCP @ 1.3.1
|
esp32async/AsyncTCP @ 3.4.7
|
||||||
bitbank2/AnimatedGIF@^1.4.7
|
bitbank2/AnimatedGIF@^1.4.7
|
||||||
https://github.com/Aircoookie/GifDecoder#bc3af18
|
https://github.com/Aircoookie/GifDecoder#bc3af18
|
||||||
build_flags =
|
build_flags =
|
||||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||||
|
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
|
||||||
-D WLED_ENABLE_GIF
|
-D WLED_ENABLE_GIF
|
||||||
|
|
||||||
[esp32]
|
[esp32]
|
||||||
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip
|
platform = ${esp32_idf_V4.platform}
|
||||||
platform = espressif32@3.5.0
|
platform_packages =
|
||||||
platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4
|
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
build_flags = -g
|
build_flags = ${esp32_idf_V4.build_flags}
|
||||||
-DARDUINO_ARCH_ESP32
|
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||||
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
|
|
||||||
#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
|
|
||||||
-D LOROL_LITTLEFS
|
|
||||||
; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
|
||||||
${esp32_all_variants.build_flags}
|
|
||||||
|
|
||||||
tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
|
tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
|
||||||
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||||
@ -260,10 +255,7 @@ extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv
|
|||||||
big_partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, 256KB filesystem, coredump support
|
big_partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, 256KB filesystem, coredump support
|
||||||
large_partitions = tools/WLED_ESP32_8MB.csv
|
large_partitions = tools/WLED_ESP32_8MB.csv
|
||||||
extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv
|
extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv
|
||||||
lib_deps =
|
|
||||||
https://github.com/lorol/LITTLEFS.git
|
|
||||||
${esp32_all_variants.lib_deps}
|
|
||||||
${env.lib_deps}
|
|
||||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||||
# additional build flags for audioreactive - must be applied globally
|
# additional build flags for audioreactive - must be applied globally
|
||||||
AR_build_flags = ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster)
|
AR_build_flags = ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster)
|
||||||
@ -271,8 +263,7 @@ AR_lib_deps = ;; for pre-usermod-library platformio_override compatibility
|
|||||||
|
|
||||||
|
|
||||||
[esp32_idf_V4]
|
[esp32_idf_V4]
|
||||||
;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
|
;; build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
|
||||||
;; very similar to the normal ESP32 flags, but omitting Lorol LittleFS, as littlefs is included in the new framework already.
|
|
||||||
;;
|
;;
|
||||||
;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly.
|
;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly.
|
||||||
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
|
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
|
||||||
@ -283,14 +274,12 @@ build_unflags = ${common.build_unflags}
|
|||||||
build_flags = -g
|
build_flags = -g
|
||||||
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
|
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
|
||||||
-DARDUINO_ARCH_ESP32 -DESP32
|
-DARDUINO_ARCH_ESP32 -DESP32
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
|
||||||
${esp32_all_variants.build_flags}
|
${esp32_all_variants.build_flags}
|
||||||
-D WLED_ENABLE_DMX_INPUT
|
-D WLED_ENABLE_DMX_INPUT
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32_all_variants.lib_deps}
|
${esp32_all_variants.lib_deps}
|
||||||
https://github.com/someweisguy/esp_dmx.git#47db25d
|
https://github.com/someweisguy/esp_dmx.git#47db25d
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
|
||||||
|
|
||||||
[esp32s2]
|
[esp32s2]
|
||||||
;; generic definitions for all ESP32-S2 boards
|
;; generic definitions for all ESP32-S2 boards
|
||||||
@ -305,10 +294,9 @@ build_flags = -g
|
|||||||
-DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 !
|
-DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 !
|
||||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||||
;; ARDUINO_USB_CDC_ON_BOOT
|
;; ARDUINO_USB_CDC_ON_BOOT
|
||||||
${esp32_all_variants.build_flags}
|
${esp32_idf_V4.build_flags}
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32_all_variants.lib_deps}
|
${esp32_idf_V4.lib_deps}
|
||||||
${env.lib_deps}
|
|
||||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||||
|
|
||||||
[esp32c3]
|
[esp32c3]
|
||||||
@ -323,10 +311,9 @@ build_flags = -g
|
|||||||
-DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3
|
-DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3
|
||||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||||
;; ARDUINO_USB_CDC_ON_BOOT
|
;; ARDUINO_USB_CDC_ON_BOOT
|
||||||
${esp32_all_variants.build_flags}
|
${esp32_idf_V4.build_flags}
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32_all_variants.lib_deps}
|
${esp32_idf_V4.lib_deps}
|
||||||
${env.lib_deps}
|
|
||||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||||
board_build.flash_mode = qio
|
board_build.flash_mode = qio
|
||||||
|
|
||||||
@ -343,10 +330,9 @@ build_flags = -g
|
|||||||
-DCO
|
-DCO
|
||||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||||
;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT
|
;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT
|
||||||
${esp32_all_variants.build_flags}
|
${esp32_idf_V4.build_flags}
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32_all_variants.lib_deps}
|
${esp32_idf_V4.lib_deps}
|
||||||
${env.lib_deps}
|
|
||||||
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
|
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
|
||||||
|
|
||||||
|
|
||||||
@ -441,21 +427,11 @@ custom_usermods = audioreactive
|
|||||||
|
|
||||||
[env:esp32dev]
|
[env:esp32dev]
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
platform = ${esp32.platform}
|
|
||||||
platform_packages = ${esp32.platform_packages}
|
|
||||||
custom_usermods = audioreactive
|
|
||||||
build_unflags = ${common.build_unflags}
|
|
||||||
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
|
|
||||||
lib_deps = ${esp32.lib_deps}
|
|
||||||
monitor_filters = esp32_exception_decoder
|
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
|
||||||
|
|
||||||
[env:esp32dev_V4]
|
|
||||||
board = esp32dev
|
|
||||||
platform = ${esp32_idf_V4.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET
|
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
||||||
lib_deps = ${esp32_idf_V4.lib_deps}
|
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
@ -489,23 +465,9 @@ board_upload.maximum_size = 16777216
|
|||||||
board_build.f_flash = 80000000L
|
board_build.f_flash = 80000000L
|
||||||
board_build.flash_mode = dio
|
board_build.flash_mode = dio
|
||||||
|
|
||||||
;[env:esp32dev_audioreactive]
|
|
||||||
;board = esp32dev
|
|
||||||
;platform = ${esp32.platform}
|
|
||||||
;platform_packages = ${esp32.platform_packages}
|
|
||||||
;custom_usermods = audioreactive
|
|
||||||
;build_unflags = ${common.build_unflags}
|
|
||||||
;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_audioreactive\" #-D WLED_DISABLE_BROWNOUT_DET
|
|
||||||
;lib_deps = ${esp32.lib_deps}
|
|
||||||
;monitor_filters = esp32_exception_decoder
|
|
||||||
;board_build.partitions = ${esp32.default_partitions}
|
|
||||||
;; board_build.f_flash = 80000000L
|
|
||||||
;; board_build.flash_mode = dio
|
|
||||||
|
|
||||||
[env:esp32_eth]
|
[env:esp32_eth]
|
||||||
board = esp32-poe
|
board = esp32-poe
|
||||||
platform = ${esp32.platform}
|
platform = ${esp32_idf_V4.platform}
|
||||||
platform_packages = ${esp32.platform_packages}
|
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
custom_usermods = audioreactive
|
custom_usermods = audioreactive
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
@ -513,10 +475,10 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"
|
|||||||
; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
|
; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
|
||||||
lib_deps = ${esp32.lib_deps}
|
lib_deps = ${esp32.lib_deps}
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
|
board_build.flash_mode = dio
|
||||||
|
|
||||||
[env:esp32_wrover]
|
[env:esp32_wrover]
|
||||||
extends = esp32_idf_V4
|
extends = esp32_idf_V4
|
||||||
platform = ${esp32_idf_V4.platform}
|
|
||||||
board = ttgo-t7-v14-mini32
|
board = ttgo-t7-v14-mini32
|
||||||
board_build.f_flash = 80000000L
|
board_build.f_flash = 80000000L
|
||||||
board_build.flash_mode = qio
|
board_build.flash_mode = qio
|
||||||
|
|||||||
@ -28,7 +28,6 @@ lib_deps = ${esp8266.lib_deps}
|
|||||||
; robtillaart/SHT85@~0.3.3
|
; robtillaart/SHT85@~0.3.3
|
||||||
; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug
|
; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug
|
||||||
; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library
|
; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library
|
||||||
; bitbank2/PNGdec@^1.0.1 ;; used for POV display uncomment following
|
|
||||||
; ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
|
; ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
|
||||||
|
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
|
|||||||
286
tools/wled-tools
Executable file
286
tools/wled-tools
Executable file
@ -0,0 +1,286 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# WLED Tools
|
||||||
|
# A utility for managing WLED devices in a local network
|
||||||
|
# https://github.com/wled/WLED
|
||||||
|
|
||||||
|
# Color Definitions
|
||||||
|
GREEN="\e[32m"
|
||||||
|
RED="\e[31m"
|
||||||
|
BLUE="\e[34m"
|
||||||
|
YELLOW="\e[33m"
|
||||||
|
RESET="\e[0m"
|
||||||
|
|
||||||
|
# Logging function
|
||||||
|
log() {
|
||||||
|
local category="$1"
|
||||||
|
local color="$2"
|
||||||
|
local text="$3"
|
||||||
|
|
||||||
|
if [ "$quiet" = true ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -t 1 ]; then # Check if output is a terminal
|
||||||
|
echo -e "${color}[${category}]${RESET} ${text}"
|
||||||
|
else
|
||||||
|
echo "[${category}] ${text}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generic curl handler function
|
||||||
|
curl_handler() {
|
||||||
|
local command="$1"
|
||||||
|
local hostname="$2"
|
||||||
|
|
||||||
|
response=$($command -w "%{http_code}" -o /dev/null)
|
||||||
|
curl_exit_code=$?
|
||||||
|
|
||||||
|
if [ "$response" -ge 200 ] && [ "$response" -lt 300 ]; then
|
||||||
|
return 0
|
||||||
|
elif [ $curl_exit_code -ne 0 ]; then
|
||||||
|
log "ERROR" "$RED" "Connection error during request to $hostname (curl exit code: $curl_exit_code)."
|
||||||
|
return 1
|
||||||
|
elif [ "$response" -ge 400 ]; then
|
||||||
|
log "ERROR" "$RED" "Server error during request to $hostname (HTTP status code: $response)."
|
||||||
|
return 2
|
||||||
|
else
|
||||||
|
log "ERROR" "$RED" "Unexpected response from $hostname (HTTP status code: $response)."
|
||||||
|
return 3
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print help message
|
||||||
|
show_help() {
|
||||||
|
cat << EOF
|
||||||
|
Usage: wled-tools.sh [OPTIONS] COMMAND [ARGS...]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Show this help message and exit.
|
||||||
|
-t, --target <IP/Host> Specify a single WLED device by IP address or hostname.
|
||||||
|
-D, --discover Discover multiple WLED devices using mDNS.
|
||||||
|
-d, --directory <Path> Specify a directory for saving backups (default: working directory).
|
||||||
|
-f, --firmware <File> Specify the firmware file for updating devices.
|
||||||
|
-q, --quiet Suppress logging output (also makes discover output hostnames only).
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
backup Backup the current state of a WLED device or multiple discovered devices.
|
||||||
|
update Update the firmware of a WLED device or multiple discovered devices.
|
||||||
|
discover Discover WLED devices using mDNS and list their IP addresses and names.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
# Discover all WLED devices on the network
|
||||||
|
./wled-tools discover
|
||||||
|
|
||||||
|
# Backup a specific WLED device
|
||||||
|
./wled-tools -t 192.168.1.100 backup
|
||||||
|
|
||||||
|
# Backup all discovered WLED devices to a specific directory
|
||||||
|
./wled-tools -D -d /path/to/backups backup
|
||||||
|
|
||||||
|
# Update firmware on all discovered WLED devices
|
||||||
|
./wled-tools -D -f /path/to/firmware.bin update
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Discover devices using mDNS
|
||||||
|
discover_devices() {
|
||||||
|
if ! command -v avahi-browse &> /dev/null; then
|
||||||
|
log "ERROR" "$RED" "'avahi-browse' is required but not installed, please install avahi-utils using your preferred package manager."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Map avahi responses to strings seperated by 0x1F (unit separator)
|
||||||
|
mapfile -t raw_devices < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7"\x1F"$8"\x1F"$9}')
|
||||||
|
|
||||||
|
local devices_array=()
|
||||||
|
for device in "${raw_devices[@]}"; do
|
||||||
|
IFS=$'\x1F' read -r hostname address port <<< "$device"
|
||||||
|
devices_array+=("$hostname" "$address" "$port")
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "${devices_array[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backup one device
|
||||||
|
backup_one() {
|
||||||
|
local hostname="$1"
|
||||||
|
local address="$2"
|
||||||
|
local port="$3"
|
||||||
|
|
||||||
|
log "INFO" "$YELLOW" "Backing up device config/presets: $hostname ($address:$port)"
|
||||||
|
|
||||||
|
mkdir -p "$backup_dir"
|
||||||
|
|
||||||
|
local cfg_url="http://$address:$port/cfg.json"
|
||||||
|
local presets_url="http://$address:$port/presets.json"
|
||||||
|
local cfg_dest="${backup_dir}/${hostname}.cfg.json"
|
||||||
|
local presets_dest="${backup_dir}/${hostname}.presets.json"
|
||||||
|
|
||||||
|
# Write to ".tmp" files first, then move when success, to ensure we don't write partial files
|
||||||
|
local curl_command_cfg="curl -s "$cfg_url" -o "$cfg_dest.tmp""
|
||||||
|
local curl_command_presets="curl -s "$presets_url" -o "$presets_dest.tmp""
|
||||||
|
|
||||||
|
if ! curl_handler "$curl_command_cfg" "$hostname"; then
|
||||||
|
log "ERROR" "$RED" "Failed to backup configuration for $hostname"
|
||||||
|
rm -f "$cfg_dest.tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! curl_handler "$curl_command_presets" "$hostname"; then
|
||||||
|
log "ERROR" "$RED" "Failed to backup presets for $hostname"
|
||||||
|
rm -f "$presets_dest.tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$cfg_dest.tmp" "$cfg_dest"
|
||||||
|
mv "$presets_dest.tmp" "$presets_dest"
|
||||||
|
log "INFO" "$GREEN" "Successfully backed up config and presets for $hostname"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update one device
|
||||||
|
update_one() {
|
||||||
|
local hostname="$1"
|
||||||
|
local address="$2"
|
||||||
|
local port="$3"
|
||||||
|
local firmware="$4"
|
||||||
|
|
||||||
|
log "INFO" "$YELLOW" "Starting firmware update for device: $hostname ($address:$port)"
|
||||||
|
|
||||||
|
local url="http://$address:$port/update"
|
||||||
|
local curl_command="curl -s -X POST -F "file=@$firmware" "$url""
|
||||||
|
|
||||||
|
if ! curl_handler "$curl_command" "$hostname"; then
|
||||||
|
log "ERROR" "$RED" "Failed to update firmware for $hostname"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "INFO" "$GREEN" "Successfully initiated firmware update for $hostname"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command-line arguments processing
|
||||||
|
command=""
|
||||||
|
target=""
|
||||||
|
discover=false
|
||||||
|
quiet=false
|
||||||
|
backup_dir="./"
|
||||||
|
firmware_file=""
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-t|--target)
|
||||||
|
if [ -z "$2" ] || [[ "$2" == -* ]]; then
|
||||||
|
log "ERROR" "$RED" "The --target option requires an argument."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
target="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-D|--discover)
|
||||||
|
discover=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-d|--directory)
|
||||||
|
if [ -z "$2" ] || [[ "$2" == -* ]]; then
|
||||||
|
log "ERROR" "$RED" "The --directory option requires an argument."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
backup_dir="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-f|--firmware)
|
||||||
|
if [ -z "$2" ] || [[ "$2" == -* ]]; then
|
||||||
|
log "ERROR" "$RED" "The --firmware option requires an argument."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
firmware_file="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-q|--quiet)
|
||||||
|
quiet=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
backup|update|discover)
|
||||||
|
command="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "ERROR" "$RED" "Unknown argument: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Execute the appropriate command
|
||||||
|
case "$command" in
|
||||||
|
discover)
|
||||||
|
read -ra devices <<< "$(discover_devices)"
|
||||||
|
for ((i=0; i<${#devices[@]}; i+=3)); do
|
||||||
|
hostname="${devices[$i]}"
|
||||||
|
address="${devices[$i+1]}"
|
||||||
|
port="${devices[$i+2]}"
|
||||||
|
|
||||||
|
if [ "$quiet" = true ]; then
|
||||||
|
echo "$hostname"
|
||||||
|
else
|
||||||
|
log "INFO" "$BLUE" "Discovered device: Hostname=$hostname, Address=$address, Port=$port"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
backup)
|
||||||
|
if [ -n "$target" ]; then
|
||||||
|
# Assume target is both the hostname and address, with port 80
|
||||||
|
backup_one "$target" "$target" "80"
|
||||||
|
elif [ "$discover" = true ]; then
|
||||||
|
read -ra devices <<< "$(discover_devices)"
|
||||||
|
for ((i=0; i<${#devices[@]}; i+=3)); do
|
||||||
|
hostname="${devices[$i]}"
|
||||||
|
address="${devices[$i+1]}"
|
||||||
|
port="${devices[$i+2]}"
|
||||||
|
backup_one "$hostname" "$address" "$port"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log "ERROR" "$RED" "No target specified. Use --target or --discover."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
update)
|
||||||
|
# Validate firmware before proceeding
|
||||||
|
if [ -z "$firmware_file" ] || [ ! -f "$firmware_file" ]; then
|
||||||
|
log "ERROR" "$RED" "Please provide a file in --firmware that exists"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$target" ]; then
|
||||||
|
# Assume target is both the hostname and address, with port 80
|
||||||
|
update_one "$target" "$target" "80" "$firmware_file"
|
||||||
|
elif [ "$discover" = true ]; then
|
||||||
|
read -ra devices <<< "$(discover_devices)"
|
||||||
|
for ((i=0; i<${#devices[@]}; i+=3)); do
|
||||||
|
hostname="${devices[$i]}"
|
||||||
|
address="${devices[$i+1]}"
|
||||||
|
port="${devices[$i+2]}"
|
||||||
|
update_one "$hostname" "$address" "$port" "$firmware_file"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log "ERROR" "$RED" "No target specified. Use --target or --discover."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
48
usermods/pov_display/README.md
Normal file
48
usermods/pov_display/README.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
## POV Display usermod
|
||||||
|
|
||||||
|
This usermod adds a new effect called “POV Image”.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
###How does it work?
|
||||||
|
With proper configuration (see below) the main segment will display a single row of pixels from an image stored on the ESP.
|
||||||
|
It displays the image row by row at a high refresh rate.
|
||||||
|
If you move the pixel segment at the right speed, you will see the full image floating in the air thanks to the persistence of vision.
|
||||||
|
RGB LEDs only (no RGBW), with grouping set to 1 and spacing set to 0.
|
||||||
|
Best results with high-density strips (e.g., 144 LEDs/m).
|
||||||
|
|
||||||
|
To get it working:
|
||||||
|
- Resize your image. The height must match the number of LEDs in your strip/segment.
|
||||||
|
- Rotate your image 90° clockwise (height becomes width).
|
||||||
|
- Upload a BMP image (24-bit, uncompressed) to the ESP filesystem using the “/edit” URL.
|
||||||
|
- Select the “POV Image” effect.
|
||||||
|
- Set the segment name to the absolute filesystem path of the image (e.g., “/myimage.bmp”).
|
||||||
|
- The path is case-sensitive and must start with “/”.
|
||||||
|
- Rotate the pixel strip at approximately 20 RPM.
|
||||||
|
- Tune as needed so that one full revolution maps to the image width (if the image appears stretched or compressed, adjust RPM slightly).
|
||||||
|
- Enjoy the show!
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Only 24-bit uncompressed BMP files are supported.
|
||||||
|
- The image must fit into ~64 KB of RAM (width × height × 3 bytes, plus row padding to a 4-byte boundary).
|
||||||
|
- Examples (approximate, excluding row padding):
|
||||||
|
- 128×128 (49,152 bytes) fits.
|
||||||
|
- 160×160 (76,800 bytes) does NOT fit.
|
||||||
|
- 96×192 (55,296 bytes) fits; padding may add a small overhead.
|
||||||
|
- If the rendered image appears mirrored or upside‑down, rotate 90° the other way or flip horizontally in your editor and try again.
|
||||||
|
- The path must be absolute.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
- 1D rotating LED strip/segment (POV setup). Ensure the segment length equals the number of physical LEDs.
|
||||||
|
- BMP image saved as 24‑bit, uncompressed (no alpha, no palette).
|
||||||
|
- Sufficient free RAM (~64 KB) for the image buffer.
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
- Nothing displays: verify the file exists at the exact absolute path (case‑sensitive) and is a 24‑bit uncompressed BMP.
|
||||||
|
- Garbled colors or wrong orientation: re‑export as 24‑bit BMP and retry the rotation/flip guidance above.
|
||||||
|
- Image too large: reduce width and/or height until it fits within ~64 KB (see examples).
|
||||||
|
- Path issues: confirm you uploaded the file via the “/edit” URL and can see it in the filesystem browser.
|
||||||
|
|
||||||
|
### Safety
|
||||||
|
- Secure the rotating assembly and keep clear of moving parts.
|
||||||
|
- Balance the strip/hub to minimize vibration before running at speed.
|
||||||
146
usermods/pov_display/bmpimage.cpp
Normal file
146
usermods/pov_display/bmpimage.cpp
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#include "bmpimage.h"
|
||||||
|
#define BUF_SIZE 64000
|
||||||
|
|
||||||
|
byte * _buffer = nullptr;
|
||||||
|
|
||||||
|
uint16_t read16(File &f) {
|
||||||
|
uint16_t result;
|
||||||
|
f.read((uint8_t *)&result,2);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t read32(File &f) {
|
||||||
|
uint32_t result;
|
||||||
|
f.read((uint8_t *)&result,4);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BMPimage::init(const char * fn) {
|
||||||
|
File bmpFile;
|
||||||
|
int bmpDepth;
|
||||||
|
//first, check if filename exists
|
||||||
|
if (!WLED_FS.exists(fn)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bmpFile = WLED_FS.open(fn);
|
||||||
|
if (!bmpFile) {
|
||||||
|
_valid=false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//so, the file exists and is opened
|
||||||
|
// Parse BMP header
|
||||||
|
uint16_t header = read16(bmpFile);
|
||||||
|
if(header != 0x4D42) { // BMP signature
|
||||||
|
_valid=false;
|
||||||
|
bmpFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//read and ingnore file size
|
||||||
|
read32(bmpFile);
|
||||||
|
(void)read32(bmpFile); // Read & ignore creator bytes
|
||||||
|
_imageOffset = read32(bmpFile); // Start of image data
|
||||||
|
// Read DIB header
|
||||||
|
read32(bmpFile);
|
||||||
|
_width = read32(bmpFile);
|
||||||
|
_height = read32(bmpFile);
|
||||||
|
if(read16(bmpFile) != 1) { // # planes -- must be '1'
|
||||||
|
_valid=false;
|
||||||
|
bmpFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bmpDepth = read16(bmpFile); // bits per pixel
|
||||||
|
if((bmpDepth != 24) || (read32(bmpFile) != 0)) { // 0 = uncompressed {
|
||||||
|
_width=0;
|
||||||
|
_valid=false;
|
||||||
|
bmpFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If _height is negative, image is in top-down order.
|
||||||
|
// This is not canon but has been observed in the wild.
|
||||||
|
if(_height < 0) {
|
||||||
|
_height = -_height;
|
||||||
|
}
|
||||||
|
//now, we have successfully got all the basics
|
||||||
|
// BMP rows are padded (if needed) to 4-byte boundary
|
||||||
|
_rowSize = (_width * 3 + 3) & ~3;
|
||||||
|
//check image size - if it is too large, it will be unusable
|
||||||
|
if (_rowSize*_height>BUF_SIZE) {
|
||||||
|
_valid=false;
|
||||||
|
bmpFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bmpFile.close();
|
||||||
|
// Ensure filename fits our buffer (segment name length constraint).
|
||||||
|
size_t len = strlen(fn);
|
||||||
|
if (len > WLED_MAX_SEGNAME_LEN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strncpy(filename, fn, sizeof(filename));
|
||||||
|
filename[sizeof(filename) - 1] = '\0';
|
||||||
|
_valid = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BMPimage::clear(){
|
||||||
|
strcpy(filename, "");
|
||||||
|
_width=0;
|
||||||
|
_height=0;
|
||||||
|
_rowSize=0;
|
||||||
|
_imageOffset=0;
|
||||||
|
_loaded=false;
|
||||||
|
_valid=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BMPimage::load(){
|
||||||
|
const size_t size = (size_t)_rowSize * (size_t)_height;
|
||||||
|
if (size > BUF_SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
File bmpFile = WLED_FS.open(filename);
|
||||||
|
if (!bmpFile) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_buffer != nullptr) free(_buffer);
|
||||||
|
_buffer = (byte*)malloc(size);
|
||||||
|
if (_buffer == nullptr) return false;
|
||||||
|
|
||||||
|
bmpFile.seek(_imageOffset);
|
||||||
|
const size_t readBytes = bmpFile.read(_buffer, size);
|
||||||
|
bmpFile.close();
|
||||||
|
if (readBytes != size) {
|
||||||
|
_loaded = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_loaded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte* BMPimage::line(uint16_t n){
|
||||||
|
if (_loaded) {
|
||||||
|
return (_buffer+n*_rowSize);
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t BMPimage::pixelColor(uint16_t x, uint16_t y){
|
||||||
|
uint32_t pos;
|
||||||
|
byte b,g,r; //colors
|
||||||
|
if (! _loaded) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ( (x>=_width) || (y>=_height) ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
pos=y*_rowSize + 3*x;
|
||||||
|
//get colors. Note that in BMP files, they go in BGR order
|
||||||
|
b= _buffer[pos++];
|
||||||
|
g= _buffer[pos++];
|
||||||
|
r= _buffer[pos];
|
||||||
|
return (r<<16|g<<8|b);
|
||||||
|
}
|
||||||
50
usermods/pov_display/bmpimage.h
Normal file
50
usermods/pov_display/bmpimage.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#ifndef _BMPIMAGE_H
|
||||||
|
#define _BMPIMAGE_H
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class describes a bitmap image. Each object refers to a bmp file on
|
||||||
|
* filesystem fatfs.
|
||||||
|
* To initialize, call init(), passign to it name of a bitmap file
|
||||||
|
* at the root of fatfs filesystem:
|
||||||
|
*
|
||||||
|
* BMPimage myImage;
|
||||||
|
* myImage.init("logo.bmp");
|
||||||
|
*
|
||||||
|
* For performance reasons, before actually usign the image, you need to load
|
||||||
|
* it from filesystem to RAM:
|
||||||
|
* myImage.load();
|
||||||
|
* All load() operations use the same reserved buffer in RAM, so you can only
|
||||||
|
* have one file loaded at a time. Before loading a new file, always unload the
|
||||||
|
* previous one:
|
||||||
|
* myImage.unload();
|
||||||
|
*/
|
||||||
|
|
||||||
|
class BMPimage {
|
||||||
|
public:
|
||||||
|
int height() {return _height; }
|
||||||
|
int width() {return _width; }
|
||||||
|
int rowSize() {return _rowSize;}
|
||||||
|
bool isLoaded() {return _loaded; }
|
||||||
|
bool load();
|
||||||
|
void unload() {_loaded=false; }
|
||||||
|
byte * line(uint16_t n);
|
||||||
|
uint32_t pixelColor(uint16_t x,uint16_t y);
|
||||||
|
bool init(const char* fn);
|
||||||
|
void clear();
|
||||||
|
char * getFilename() {return filename;};
|
||||||
|
|
||||||
|
private:
|
||||||
|
char filename[WLED_MAX_SEGNAME_LEN+1]="";
|
||||||
|
int _width=0;
|
||||||
|
int _height=0;
|
||||||
|
int _rowSize=0;
|
||||||
|
int _imageOffset=0;
|
||||||
|
bool _loaded=false;
|
||||||
|
bool _valid=false;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern byte * _buffer;
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,7 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name:": "pov_display",
|
"name:": "pov_display",
|
||||||
"build": { "libArchive": false},
|
"build": { "libArchive": false},
|
||||||
"dependencies": {
|
"platforms": ["espressif32"]
|
||||||
"bitbank2/PNGdec":"^1.0.3"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
47
usermods/pov_display/pov.cpp
Normal file
47
usermods/pov_display/pov.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "pov.h"
|
||||||
|
|
||||||
|
POV::POV() {}
|
||||||
|
|
||||||
|
void POV::showLine(const byte * line, uint16_t size){
|
||||||
|
uint16_t i, pos;
|
||||||
|
uint8_t r, g, b;
|
||||||
|
if (!line) {
|
||||||
|
// All-black frame on null input
|
||||||
|
for (i = 0; i < SEGLEN; i++) {
|
||||||
|
SEGMENT.setPixelColor(i, CRGB::Black);
|
||||||
|
}
|
||||||
|
strip.show();
|
||||||
|
lastLineUpdate = micros();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (i = 0; i < SEGLEN; i++) {
|
||||||
|
if (i < size) {
|
||||||
|
pos = 3 * i;
|
||||||
|
// using bgr order
|
||||||
|
b = line[pos++];
|
||||||
|
g = line[pos++];
|
||||||
|
r = line[pos];
|
||||||
|
SEGMENT.setPixelColor(i, CRGB(r, g, b));
|
||||||
|
} else {
|
||||||
|
SEGMENT.setPixelColor(i, CRGB::Black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strip.show();
|
||||||
|
lastLineUpdate = micros();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool POV::loadImage(const char * filename){
|
||||||
|
if(!image.init(filename)) return false;
|
||||||
|
if(!image.load()) return false;
|
||||||
|
currentLine=0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t POV::showNextLine(){
|
||||||
|
if (!image.isLoaded()) return 0;
|
||||||
|
//move to next line
|
||||||
|
showLine(image.line(currentLine), image.width());
|
||||||
|
currentLine++;
|
||||||
|
if (currentLine == image.height()) {currentLine=0;}
|
||||||
|
return currentLine;
|
||||||
|
}
|
||||||
42
usermods/pov_display/pov.h
Normal file
42
usermods/pov_display/pov.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#ifndef _POV_H
|
||||||
|
#define _POV_H
|
||||||
|
#include "bmpimage.h"
|
||||||
|
|
||||||
|
|
||||||
|
class POV {
|
||||||
|
public:
|
||||||
|
POV();
|
||||||
|
|
||||||
|
/* Shows one line. line should be pointer to array which holds pixel colors
|
||||||
|
* (3 bytes per pixel, in BGR order). Note: 3, not 4!!!
|
||||||
|
* size should be size of array (number of pixels, not number of bytes)
|
||||||
|
*/
|
||||||
|
void showLine(const byte * line, uint16_t size);
|
||||||
|
|
||||||
|
/* Reads from file an image and making it current image */
|
||||||
|
bool loadImage(const char * filename);
|
||||||
|
|
||||||
|
/* Show next line of active image
|
||||||
|
Retunrs the index of next line to be shown (not yet shown!)
|
||||||
|
If it retunrs 0, it means we have completed showing the image and
|
||||||
|
next call will start again
|
||||||
|
*/
|
||||||
|
int16_t showNextLine();
|
||||||
|
|
||||||
|
//time since strip was last updated, in micro sec
|
||||||
|
uint32_t timeSinceUpdate() {return (micros()-lastLineUpdate);}
|
||||||
|
|
||||||
|
|
||||||
|
BMPimage * currentImage() {return ℑ}
|
||||||
|
|
||||||
|
char * getFilename() {return image.getFilename();}
|
||||||
|
|
||||||
|
private:
|
||||||
|
BMPimage image;
|
||||||
|
int16_t currentLine=0; //next line to be shown
|
||||||
|
uint32_t lastLineUpdate=0; //time in microseconds
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,88 +1,75 @@
|
|||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include <PNGdec.h>
|
#include "pov.h"
|
||||||
|
|
||||||
void * openFile(const char *filename, int32_t *size) {
|
static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;;";
|
||||||
f = WLED_FS.open(filename);
|
|
||||||
*size = f.size();
|
|
||||||
return &f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeFile(void *handle) {
|
static POV s_pov;
|
||||||
if (f) f.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t readFile(PNGFILE *pFile, uint8_t *pBuf, int32_t iLen)
|
|
||||||
{
|
|
||||||
int32_t iBytesRead;
|
|
||||||
iBytesRead = iLen;
|
|
||||||
File *f = static_cast<File *>(pFile->fHandle);
|
|
||||||
// Note: If you read a file all the way to the last byte, seek() stops working
|
|
||||||
if ((pFile->iSize - pFile->iPos) < iLen)
|
|
||||||
iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
|
|
||||||
if (iBytesRead <= 0)
|
|
||||||
return 0;
|
|
||||||
iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
|
|
||||||
pFile->iPos = f->position();
|
|
||||||
return iBytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t seekFile(PNGFILE *pFile, int32_t iPosition)
|
|
||||||
{
|
|
||||||
int i = micros();
|
|
||||||
File *f = static_cast<File *>(pFile->fHandle);
|
|
||||||
f->seek(iPosition);
|
|
||||||
pFile->iPos = (int32_t)f->position();
|
|
||||||
i = micros() - i;
|
|
||||||
return pFile->iPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw(PNGDRAW *pDraw) {
|
|
||||||
uint16_t usPixels[SEGLEN];
|
|
||||||
png.getLineAsRGB565(pDraw, usPixels, PNG_RGB565_LITTLE_ENDIAN, 0xffffffff);
|
|
||||||
for(int x=0; x < SEGLEN; x++) {
|
|
||||||
uint16_t color = usPixels[x];
|
|
||||||
byte r = ((color >> 11) & 0x1F);
|
|
||||||
byte g = ((color >> 5) & 0x3F);
|
|
||||||
byte b = (color & 0x1F);
|
|
||||||
SEGMENT.setPixelColor(x, RGBW32(r,g,b,0));
|
|
||||||
}
|
|
||||||
strip.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t mode_pov_image(void) {
|
uint16_t mode_pov_image(void) {
|
||||||
const char * filepath = SEGMENT.name;
|
Segment& mainseg = strip.getMainSegment();
|
||||||
int rc = png.open(filepath, openFile, closeFile, readFile, seekFile, draw);
|
const char* segName = mainseg.name;
|
||||||
if (rc == PNG_SUCCESS) {
|
if (!segName) {
|
||||||
rc = png.decode(NULL, 0);
|
return FRAMETIME;
|
||||||
png.close();
|
}
|
||||||
return FRAMETIME;
|
// Only proceed for files ending with .bmp (case-insensitive)
|
||||||
}
|
size_t segLen = strlen(segName);
|
||||||
|
if (segLen < 4) return FRAMETIME;
|
||||||
|
const char* ext = segName + (segLen - 4);
|
||||||
|
// compare case-insensitive to ".bmp"
|
||||||
|
if (!((ext[0]=='.') &&
|
||||||
|
(ext[1]=='b' || ext[1]=='B') &&
|
||||||
|
(ext[2]=='m' || ext[2]=='M') &&
|
||||||
|
(ext[3]=='p' || ext[3]=='P'))) {
|
||||||
return FRAMETIME;
|
return FRAMETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* current = s_pov.getFilename();
|
||||||
|
if (current && strcmp(segName, current) == 0) {
|
||||||
|
s_pov.showNextLine();
|
||||||
|
return FRAMETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long s_lastLoadAttemptMs = 0;
|
||||||
|
unsigned long nowMs = millis();
|
||||||
|
// Retry at most twice per second if the image is not yet loaded.
|
||||||
|
if (nowMs - s_lastLoadAttemptMs < 500) return FRAMETIME;
|
||||||
|
s_lastLoadAttemptMs = nowMs;
|
||||||
|
s_pov.loadImage(segName);
|
||||||
|
return FRAMETIME;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PovDisplayUsermod : public Usermod
|
class PovDisplayUsermod : public Usermod {
|
||||||
{
|
protected:
|
||||||
public:
|
bool enabled = false; //WLEDMM
|
||||||
static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;1";
|
const char *_name; //WLEDMM
|
||||||
|
bool initDone = false; //WLEDMM
|
||||||
|
unsigned long lastTime = 0; //WLEDMM
|
||||||
|
public:
|
||||||
|
|
||||||
PNG png;
|
PovDisplayUsermod(const char *name, bool enabled)
|
||||||
File f;
|
: enabled(enabled) , _name(name) {}
|
||||||
|
|
||||||
|
void setup() override {
|
||||||
|
strip.addEffect(255, &mode_pov_image, _data_FX_MODE_POV_IMAGE);
|
||||||
|
//initDone removed (unused)
|
||||||
|
}
|
||||||
|
|
||||||
void setup() {
|
|
||||||
strip.addEffect(255, &mode_pov_image, _data_FX_MODE_POV_IMAGE);
|
void loop() override {
|
||||||
|
// 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 (!enabled || strip.isUpdating()) return;
|
||||||
|
|
||||||
|
// do your magic here
|
||||||
|
if (millis() - lastTime > 1000) {
|
||||||
|
lastTime = millis();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void loop() {
|
uint16_t getId() override {
|
||||||
}
|
return USERMOD_ID_POV_DISPLAY;
|
||||||
|
}
|
||||||
uint16_t getId()
|
|
||||||
{
|
|
||||||
return USERMOD_ID_POV_DISPLAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
void connected() {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static PovDisplayUsermod pov_display("POV Display", false);
|
||||||
static PovDisplayUsermod pov_display;
|
REGISTER_USERMOD(pov_display);
|
||||||
REGISTER_USERMOD(pov_display);
|
|
||||||
|
|||||||
BIN
usermods/pov_display/pov_display.gif
Normal file
BIN
usermods/pov_display/pov_display.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 988 KiB |
@ -114,6 +114,7 @@ static um_data_t* getAudioData() {
|
|||||||
return um_data;
|
return um_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// effect functions
|
// effect functions
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -125,6 +126,56 @@ uint16_t mode_static(void) {
|
|||||||
}
|
}
|
||||||
static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid";
|
static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy a segment and perform (optional) color adjustments
|
||||||
|
*/
|
||||||
|
uint16_t mode_copy_segment(void) {
|
||||||
|
uint32_t sourceid = SEGMENT.custom3;
|
||||||
|
if (sourceid >= strip.getSegmentsNum() || sourceid == strip.getCurrSegmentId()) { // invalid source
|
||||||
|
SEGMENT.fadeToBlackBy(5); // fade out
|
||||||
|
return FRAMETIME;
|
||||||
|
}
|
||||||
|
Segment sourcesegment = strip.getSegment(sourceid);
|
||||||
|
if (sourcesegment.isActive()) {
|
||||||
|
uint32_t sourcecolor;
|
||||||
|
uint32_t destcolor;
|
||||||
|
if(sourcesegment.is2D()) { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX)
|
||||||
|
for (unsigned y = 0; y < SEGMENT.vHeight(); y++) {
|
||||||
|
for (unsigned x = 0; x < SEGMENT.vWidth(); x++) {
|
||||||
|
unsigned sx = x; // source coordinates
|
||||||
|
unsigned sy = y;
|
||||||
|
if(SEGMENT.check1) std::swap(sx, sy); // flip axis
|
||||||
|
if(SEGMENT.check2) {
|
||||||
|
sourcecolor = strip.getPixelColorXY(sx + sourcesegment.start, sy + sourcesegment.startY); // read from global buffer (reads the last rendered frame)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sourcesegment.setDrawDimensions(); // set to source segment dimensions
|
||||||
|
sourcecolor = sourcesegment.getPixelColorXY(sx, sy); // read from segment buffer
|
||||||
|
}
|
||||||
|
destcolor = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2);
|
||||||
|
SEGMENT.setDrawDimensions(); // reset to current segment dimensions
|
||||||
|
SEGMENT.setPixelColorXY(x, y, destcolor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // 1D source, source can be expanded into 2D
|
||||||
|
for (unsigned i = 0; i < SEGMENT.vLength(); i++) {
|
||||||
|
if(SEGMENT.check2) {
|
||||||
|
sourcecolor = strip.getPixelColor(i + sourcesegment.start); // read from global buffer (reads the last rendered frame)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sourcesegment.setDrawDimensions(); // set to source segment dimensions
|
||||||
|
sourcecolor = sourcesegment.getPixelColor(i);
|
||||||
|
}
|
||||||
|
destcolor = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2);
|
||||||
|
SEGMENT.setDrawDimensions(); // reset to current segment dimensions
|
||||||
|
SEGMENT.setPixelColor(i, destcolor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FRAMETIME;
|
||||||
|
}
|
||||||
|
static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,Axis(2D),FullStack(last frame);;;12;ix=0,c1=0,c2=0,c3=0";
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Blink/strobe function
|
* Blink/strobe function
|
||||||
@ -4715,30 +4766,17 @@ class AuroraWave {
|
|||||||
};
|
};
|
||||||
|
|
||||||
uint16_t mode_aurora(void) {
|
uint16_t mode_aurora(void) {
|
||||||
//aux1 = Wavecount
|
|
||||||
//aux2 = Intensity in last loop
|
|
||||||
|
|
||||||
AuroraWave* waves;
|
AuroraWave* waves;
|
||||||
|
SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); // aux1 = Wavecount
|
||||||
|
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 20 on ESP32, 9 on ESP8266
|
||||||
|
return mode_static(); //allocation failed
|
||||||
|
}
|
||||||
|
waves = reinterpret_cast<AuroraWave*>(SEGENV.data);
|
||||||
|
|
||||||
//TODO: I am not sure this is a correct way of handling memory allocation since if it fails on 1st run
|
if(SEGENV.call == 0) {
|
||||||
// it will display static effect but on second run it may crash ESP since data will be nullptr
|
|
||||||
|
|
||||||
if(SEGENV.aux0 != SEGMENT.intensity || SEGENV.call == 0) {
|
|
||||||
//Intensity slider changed or first call
|
|
||||||
SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT);
|
|
||||||
SEGENV.aux0 = SEGMENT.intensity;
|
|
||||||
|
|
||||||
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 16 segment ESP8266
|
|
||||||
return mode_static(); //allocation failed
|
|
||||||
}
|
|
||||||
|
|
||||||
waves = reinterpret_cast<AuroraWave*>(SEGENV.data);
|
|
||||||
|
|
||||||
for (int i = 0; i < SEGENV.aux1; i++) {
|
for (int i = 0; i < SEGENV.aux1; i++) {
|
||||||
waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(hw_random8(), false, false, hw_random8(0, 3))));
|
waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(hw_random8(), false, false, hw_random8(0, 3))));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
waves = reinterpret_cast<AuroraWave*>(SEGENV.data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < SEGENV.aux1; i++) {
|
for (int i = 0; i < SEGENV.aux1; i++) {
|
||||||
@ -7490,9 +7528,9 @@ uint16_t mode_2Ddistortionwaves() {
|
|||||||
byte valueG = gdistort + ((a2-( ((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1))>>7 ))<<1);
|
byte valueG = gdistort + ((a2-( ((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1))>>7 ))<<1);
|
||||||
byte valueB = bdistort + ((a3-( ((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2))>>7 ))<<1);
|
byte valueB = bdistort + ((a3-( ((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2))>>7 ))<<1);
|
||||||
|
|
||||||
valueR = gamma8(cos8_t(valueR));
|
valueR = cos8_t(valueR);
|
||||||
valueG = gamma8(cos8_t(valueG));
|
valueG = cos8_t(valueG);
|
||||||
valueB = gamma8(cos8_t(valueB));
|
valueB = cos8_t(valueB);
|
||||||
|
|
||||||
if(SEGMENT.palette == 0) {
|
if(SEGMENT.palette == 0) {
|
||||||
// use RGB values (original color mode)
|
// use RGB values (original color mode)
|
||||||
@ -8444,7 +8482,6 @@ static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed
|
|||||||
#define NUMBEROFSOURCES 8
|
#define NUMBEROFSOURCES 8
|
||||||
uint16_t mode_particleimpact(void) {
|
uint16_t mode_particleimpact(void) {
|
||||||
ParticleSystem2D *PartSys = nullptr;
|
ParticleSystem2D *PartSys = nullptr;
|
||||||
uint32_t i = 0;
|
|
||||||
uint32_t numMeteors;
|
uint32_t numMeteors;
|
||||||
PSsettings2D meteorsettings;
|
PSsettings2D meteorsettings;
|
||||||
meteorsettings.asByte = 0b00101000; // PS settings for meteors: bounceY and gravity enabled
|
meteorsettings.asByte = 0b00101000; // PS settings for meteors: bounceY and gravity enabled
|
||||||
@ -8457,7 +8494,7 @@ uint16_t mode_particleimpact(void) {
|
|||||||
PartSys->setBounceY(true); // always use ground bounce
|
PartSys->setBounceY(true); // always use ground bounce
|
||||||
PartSys->setWallRoughness(220); // high roughness
|
PartSys->setWallRoughness(220); // high roughness
|
||||||
numMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES);
|
numMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES);
|
||||||
for (i = 0; i < numMeteors; i++) {
|
for (uint32_t i = 0; i < numMeteors; i++) {
|
||||||
PartSys->sources[i].source.ttl = hw_random16(10 * i); // set initial delay for meteors
|
PartSys->sources[i].source.ttl = hw_random16(10 * i); // set initial delay for meteors
|
||||||
PartSys->sources[i].source.vy = 10; // at positive speeds, no particles are emitted and if particle dies, it will be relaunched
|
PartSys->sources[i].source.vy = 10; // at positive speeds, no particles are emitted and if particle dies, it will be relaunched
|
||||||
}
|
}
|
||||||
@ -8479,7 +8516,7 @@ uint16_t mode_particleimpact(void) {
|
|||||||
numMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES);
|
numMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES);
|
||||||
uint32_t emitparticles; // number of particles to emit for each rocket's state
|
uint32_t emitparticles; // number of particles to emit for each rocket's state
|
||||||
|
|
||||||
for (i = 0; i < numMeteors; i++) {
|
for (uint32_t i = 0; i < numMeteors; i++) {
|
||||||
// determine meteor state by its speed:
|
// determine meteor state by its speed:
|
||||||
if ( PartSys->sources[i].source.vy < 0) // moving down, emit sparks
|
if ( PartSys->sources[i].source.vy < 0) // moving down, emit sparks
|
||||||
emitparticles = 1;
|
emitparticles = 1;
|
||||||
@ -8495,7 +8532,7 @@ uint16_t mode_particleimpact(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update the meteors, set the speed state
|
// update the meteors, set the speed state
|
||||||
for (i = 0; i < numMeteors; i++) {
|
for (uint32_t i = 0; i < numMeteors; i++) {
|
||||||
if (PartSys->sources[i].source.ttl) {
|
if (PartSys->sources[i].source.ttl) {
|
||||||
PartSys->sources[i].source.ttl--; // note: this saves an if statement, but moving down particles age twice
|
PartSys->sources[i].source.ttl--; // note: this saves an if statement, but moving down particles age twice
|
||||||
if (PartSys->sources[i].source.vy < 0) { // move down
|
if (PartSys->sources[i].source.vy < 0) { // move down
|
||||||
@ -8786,7 +8823,7 @@ uint16_t mode_particleGEQ(void) {
|
|||||||
//set particle properties TODO: could also use the spray...
|
//set particle properties TODO: could also use the spray...
|
||||||
PartSys->particles[i].ttl = 20 + map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + hw_random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames
|
PartSys->particles[i].ttl = 20 + map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + hw_random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames
|
||||||
PartSys->particles[i].x = xposition + hw_random16(binwidth) - (binwidth>>1); // position randomly, deviating half a bin width
|
PartSys->particles[i].x = xposition + hw_random16(binwidth) - (binwidth>>1); // position randomly, deviating half a bin width
|
||||||
PartSys->particles[i].y = PS_P_RADIUS; // start at the bottom (PS_P_RADIUS is minimum position a particle is fully in frame)
|
PartSys->particles[i].y = 0; // start at the bottom
|
||||||
PartSys->particles[i].vx = hw_random16(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation: +/- custom1/4
|
PartSys->particles[i].vx = hw_random16(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation: +/- custom1/4
|
||||||
PartSys->particles[i].vy = emitspeed;
|
PartSys->particles[i].vy = emitspeed;
|
||||||
PartSys->particles[i].hue = (bin<<4) + hw_random16(17) - 8; // color from palette according to bin
|
PartSys->particles[i].hue = (bin<<4) + hw_random16(17) - 8; // color from palette according to bin
|
||||||
@ -10617,6 +10654,7 @@ void WS2812FX::setupEffectData() {
|
|||||||
_modeData.push_back(_data_RESERVED);
|
_modeData.push_back(_data_RESERVED);
|
||||||
}
|
}
|
||||||
// now replace all pre-allocated effects
|
// now replace all pre-allocated effects
|
||||||
|
addEffect(FX_MODE_COPY, &mode_copy_segment, _data_FX_MODE_COPY);
|
||||||
// --- 1D non-audio effects ---
|
// --- 1D non-audio effects ---
|
||||||
addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK);
|
addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK);
|
||||||
addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH);
|
addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH);
|
||||||
|
|||||||
11
wled00/FX.h
Executable file → Normal file
11
wled00/FX.h
Executable file → Normal file
@ -228,6 +228,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
|
|||||||
#define FX_MODE_LAKE 75
|
#define FX_MODE_LAKE 75
|
||||||
#define FX_MODE_METEOR 76
|
#define FX_MODE_METEOR 76
|
||||||
//#define FX_MODE_METEOR_SMOOTH 77 // replaced by Meteor
|
//#define FX_MODE_METEOR_SMOOTH 77 // replaced by Meteor
|
||||||
|
#define FX_MODE_COPY 77
|
||||||
#define FX_MODE_RAILWAY 78
|
#define FX_MODE_RAILWAY 78
|
||||||
#define FX_MODE_RIPPLE 79
|
#define FX_MODE_RIPPLE 79
|
||||||
#define FX_MODE_TWINKLEFOX 80
|
#define FX_MODE_TWINKLEFOX 80
|
||||||
@ -685,6 +686,7 @@ class Segment {
|
|||||||
|
|
||||||
// 1D strip
|
// 1D strip
|
||||||
uint16_t virtualLength() const;
|
uint16_t virtualLength() const;
|
||||||
|
uint16_t maxMappingLength() const;
|
||||||
[[gnu::hot]] void setPixelColor(int n, uint32_t c) const; // set relative pixel within segment with color
|
[[gnu::hot]] void setPixelColor(int n, uint32_t c) const; // set relative pixel within segment with color
|
||||||
inline void setPixelColor(unsigned n, uint32_t c) const { setPixelColor(int(n), c); }
|
inline void setPixelColor(unsigned n, uint32_t c) const { setPixelColor(int(n), c); }
|
||||||
inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); }
|
inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); }
|
||||||
@ -723,6 +725,13 @@ class Segment {
|
|||||||
return 1;
|
return 1;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
inline unsigned rawLength() const { // returns length of used raw pixel buffer (eg. get/setPixelColorRaw())
|
||||||
|
#ifndef WLED_DISABLE_2D
|
||||||
|
if (is2D()) return virtualWidth() * virtualHeight();
|
||||||
|
#endif
|
||||||
|
return virtualLength();
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_2D
|
#ifndef WLED_DISABLE_2D
|
||||||
inline bool is2D() const { return (width()>1 && height()>1); }
|
inline bool is2D() const { return (width()>1 && height()>1); }
|
||||||
[[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c) const; // set relative pixel within segment with color
|
[[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c) const; // set relative pixel within segment with color
|
||||||
@ -800,6 +809,8 @@ class Segment {
|
|||||||
inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {}
|
inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {}
|
||||||
#endif
|
#endif
|
||||||
friend class WS2812FX;
|
friend class WS2812FX;
|
||||||
|
friend class ParticleSystem2D;
|
||||||
|
friend class ParticleSystem1D;
|
||||||
};
|
};
|
||||||
|
|
||||||
// main "strip" class (108 bytes)
|
// main "strip" class (108 bytes)
|
||||||
|
|||||||
240
wled00/FX_fcn.cpp
Executable file → Normal file
240
wled00/FX_fcn.cpp
Executable file → Normal file
@ -66,13 +66,15 @@ Segment::Segment(const Segment &orig) {
|
|||||||
_dataLen = 0;
|
_dataLen = 0;
|
||||||
pixels = nullptr;
|
pixels = nullptr;
|
||||||
if (!stop) return; // nothing to do if segment is inactive/invalid
|
if (!stop) return; // nothing to do if segment is inactive/invalid
|
||||||
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
|
|
||||||
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
|
|
||||||
if (orig.pixels) {
|
if (orig.pixels) {
|
||||||
|
// allocate pixel buffer: prefer IRAM/PSRAM
|
||||||
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
|
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
|
||||||
if (pixels) memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
|
if (pixels) {
|
||||||
else {
|
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
|
||||||
DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
|
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
|
||||||
|
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
|
||||||
|
} else {
|
||||||
|
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
|
||||||
errorFlag = ERR_NORAM_PX;
|
errorFlag = ERR_NORAM_PX;
|
||||||
stop = 0; // mark segment as inactive/invalid
|
stop = 0; // mark segment as inactive/invalid
|
||||||
}
|
}
|
||||||
@ -107,12 +109,14 @@ Segment& Segment::operator= (const Segment &orig) {
|
|||||||
pixels = nullptr;
|
pixels = nullptr;
|
||||||
if (!stop) return *this; // nothing to do if segment is inactive/invalid
|
if (!stop) return *this; // nothing to do if segment is inactive/invalid
|
||||||
// copy source data
|
// copy source data
|
||||||
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
|
|
||||||
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
|
|
||||||
if (orig.pixels) {
|
if (orig.pixels) {
|
||||||
|
// allocate pixel buffer: prefer IRAM/PSRAM
|
||||||
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
|
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
|
||||||
if (pixels) memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
|
if (pixels) {
|
||||||
else {
|
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
|
||||||
|
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
|
||||||
|
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
|
||||||
|
} else {
|
||||||
DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
|
DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
|
||||||
errorFlag = ERR_NORAM_PX;
|
errorFlag = ERR_NORAM_PX;
|
||||||
stop = 0; // mark segment as inactive/invalid
|
stop = 0; // mark segment as inactive/invalid
|
||||||
@ -159,8 +163,16 @@ bool Segment::allocateData(size_t len) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// prefer DRAM over SPI RAM on ESP32 since it is slow
|
// prefer DRAM over SPI RAM on ESP32 since it is slow
|
||||||
if (data) data = (byte*)d_realloc(data, len);
|
if (data) {
|
||||||
else data = (byte*)d_malloc(len);
|
data = (byte*)d_realloc_malloc(data, len); // realloc with malloc fallback
|
||||||
|
if (!data) {
|
||||||
|
data = nullptr;
|
||||||
|
Segment::addUsedSegmentData(-_dataLen); // subtract original buffer size
|
||||||
|
_dataLen = 0; // reset data length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else data = (byte*)d_malloc(len);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
memset(data, 0, len); // erase buffer
|
memset(data, 0, len); // erase buffer
|
||||||
Segment::addUsedSegmentData(len - _dataLen);
|
Segment::addUsedSegmentData(len - _dataLen);
|
||||||
@ -170,7 +182,6 @@ bool Segment::allocateData(size_t len) {
|
|||||||
}
|
}
|
||||||
// allocation failed
|
// allocation failed
|
||||||
DEBUG_PRINTLN(F("!!! Allocation failed. !!!"));
|
DEBUG_PRINTLN(F("!!! Allocation failed. !!!"));
|
||||||
Segment::addUsedSegmentData(-_dataLen); // subtract original buffer size
|
|
||||||
errorFlag = ERR_NORAM;
|
errorFlag = ERR_NORAM;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -271,11 +282,13 @@ void Segment::startTransition(uint16_t dur, bool segmentCopy) {
|
|||||||
_t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings
|
_t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings
|
||||||
_t->_start = millis(); // restart countdown
|
_t->_start = millis(); // restart countdown
|
||||||
_t->_dur = dur;
|
_t->_dur = dur;
|
||||||
|
_t->_prevPaletteBlends = 0;
|
||||||
if (_t->_oldSegment) {
|
if (_t->_oldSegment) {
|
||||||
_t->_oldSegment->palette = _t->_palette; // restore original palette and colors (from start of transition)
|
_t->_oldSegment->palette = _t->_palette; // restore original palette and colors (from start of transition)
|
||||||
for (unsigned i = 0; i < NUM_COLORS; i++) _t->_oldSegment->colors[i] = _t->_colors[i];
|
for (unsigned i = 0; i < NUM_COLORS; i++) _t->_oldSegment->colors[i] = _t->_colors[i];
|
||||||
|
DEBUGFX_PRINTF_P(PSTR("-- Updated transition with segment copy: S=%p T(%p) O[%p] OP[%p]\n"), this, _t, _t->_oldSegment, _t->_oldSegment->pixels);
|
||||||
|
if (!_t->_oldSegment->isActive()) stopTransition();
|
||||||
}
|
}
|
||||||
DEBUG_PRINTF_P(PSTR("-- Updated transition with segment copy: S=%p T(%p) O[%p] OP[%p]\n"), this, _t, _t->_oldSegment, _t->_oldSegment->pixels);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -291,13 +304,12 @@ void Segment::startTransition(uint16_t dur, bool segmentCopy) {
|
|||||||
#endif
|
#endif
|
||||||
for (int i=0; i<NUM_COLORS; i++) _t->_colors[i] = colors[i];
|
for (int i=0; i<NUM_COLORS; i++) _t->_colors[i] = colors[i];
|
||||||
if (segmentCopy) _t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings
|
if (segmentCopy) _t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings
|
||||||
#ifdef WLED_DEBUG
|
|
||||||
if (_t->_oldSegment) {
|
if (_t->_oldSegment) {
|
||||||
DEBUG_PRINTF_P(PSTR("-- Started transition: S=%p T(%p) O[%p] OP[%p]\n"), this, _t, _t->_oldSegment, _t->_oldSegment->pixels);
|
DEBUGFX_PRINTF_P(PSTR("-- Started transition: S=%p T(%p) O[%p] OP[%p]\n"), this, _t, _t->_oldSegment, _t->_oldSegment->pixels);
|
||||||
|
if (!_t->_oldSegment->isActive()) stopTransition();
|
||||||
} else {
|
} else {
|
||||||
DEBUG_PRINTF_P(PSTR("-- Started transition without old segment: S=%p T(%p)\n"), this, _t);
|
DEBUGFX_PRINTF_P(PSTR("-- Started transition without old segment: S=%p T(%p)\n"), this, _t);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,6 +369,7 @@ void Segment::beginDraw(uint16_t prog) {
|
|||||||
// minimum blend time is 100ms maximum is 65535ms
|
// minimum blend time is 100ms maximum is 65535ms
|
||||||
#ifndef WLED_SAVE_RAM
|
#ifndef WLED_SAVE_RAM
|
||||||
unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
|
unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
|
||||||
|
if(noOfBlends > 255) noOfBlends = 255; // safety check
|
||||||
for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, Segment::_currentPalette, 48);
|
for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, Segment::_currentPalette, 48);
|
||||||
Segment::_currentPalette = _t->_palT; // copy transitioning/temporary palette
|
Segment::_currentPalette = _t->_palT; // copy transitioning/temporary palette
|
||||||
#else
|
#else
|
||||||
@ -418,14 +431,15 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
|
|||||||
|
|
||||||
unsigned oldLength = length();
|
unsigned oldLength = length();
|
||||||
|
|
||||||
DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d [%d,%d]\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y, (int)grp, (int)spc);
|
DEBUGFX_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d [%d,%d]\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y, (int)grp, (int)spc);
|
||||||
markForReset();
|
markForReset();
|
||||||
startTransition(strip.getTransition()); // start transition prior to change (if segment is deactivated (start>stop) no transition will happen)
|
if (_t) stopTransition(); // we can't use transition if segment dimensions changed
|
||||||
stateChanged = true; // send UDP/WS broadcast
|
stateChanged = true; // send UDP/WS broadcast
|
||||||
|
|
||||||
// apply change immediately
|
// apply change immediately
|
||||||
if (i2 <= i1) { //disable segment
|
if (i2 <= i1) { //disable segment
|
||||||
d_free(pixels);
|
deallocateData();
|
||||||
|
p_free(pixels);
|
||||||
pixels = nullptr;
|
pixels = nullptr;
|
||||||
stop = 0;
|
stop = 0;
|
||||||
return;
|
return;
|
||||||
@ -442,21 +456,25 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
|
|||||||
#endif
|
#endif
|
||||||
// safety check
|
// safety check
|
||||||
if (start >= stop || startY >= stopY) {
|
if (start >= stop || startY >= stopY) {
|
||||||
d_free(pixels);
|
deallocateData();
|
||||||
|
p_free(pixels);
|
||||||
pixels = nullptr;
|
pixels = nullptr;
|
||||||
stop = 0;
|
stop = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// re-allocate FX render buffer
|
// allocate FX render buffer
|
||||||
if (length() != oldLength) {
|
if (length() != oldLength) {
|
||||||
if (pixels) pixels = static_cast<uint32_t*>(d_realloc(pixels, sizeof(uint32_t) * length()));
|
// allocate render buffer (always entire segment), prefer IRAM/PSRAM. Note: impact on FPS with PSRAM buffer is low (<2% with QSPI PSRAM) on S2/S3
|
||||||
else pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * length()));
|
p_free(pixels);
|
||||||
|
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * length()));
|
||||||
if (!pixels) {
|
if (!pixels) {
|
||||||
DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
|
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
|
||||||
|
deallocateData();
|
||||||
errorFlag = ERR_NORAM_PX;
|
errorFlag = ERR_NORAM_PX;
|
||||||
stop = 0;
|
stop = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
refreshLightCapabilities();
|
refreshLightCapabilities();
|
||||||
}
|
}
|
||||||
@ -563,10 +581,10 @@ Segment &Segment::setName(const char *newName) {
|
|||||||
if (newName) {
|
if (newName) {
|
||||||
const int newLen = min(strlen(newName), (size_t)WLED_MAX_SEGNAME_LEN);
|
const int newLen = min(strlen(newName), (size_t)WLED_MAX_SEGNAME_LEN);
|
||||||
if (newLen) {
|
if (newLen) {
|
||||||
if (name) name = static_cast<char*>(d_realloc(name, newLen+1));
|
if (name) d_free(name); // free old name
|
||||||
else name = static_cast<char*>(d_malloc(newLen+1));
|
name = static_cast<char*>(d_malloc(newLen+1));
|
||||||
|
if (mode == FX_MODE_2DSCROLLTEXT) startTransition(strip.getTransition(), true); // if the name changes in scrolling text mode, we need to copy the segment for blending
|
||||||
if (name) strlcpy(name, newName, newLen+1);
|
if (name) strlcpy(name, newName, newLen+1);
|
||||||
name[newLen] = 0;
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -645,6 +663,14 @@ uint16_t Segment::virtualLength() const {
|
|||||||
return vLength;
|
return vLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_2D
|
||||||
|
// maximum length of a mapped 1D segment, used in PS for buffer allocation
|
||||||
|
uint16_t Segment::maxMappingLength() const {
|
||||||
|
uint32_t vW = virtualWidth();
|
||||||
|
uint32_t vH = virtualHeight();
|
||||||
|
return max(sqrt32_bw(vH*vH + vW*vW), (uint32_t)getPinwheelLength(vW, vH)); // use diagonal
|
||||||
|
}
|
||||||
|
#endif
|
||||||
// pixel is clipped if it falls outside clipping range
|
// pixel is clipped if it falls outside clipping range
|
||||||
// if clipping start > stop the clipping range is inverted
|
// if clipping start > stop the clipping range is inverted
|
||||||
bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const {
|
bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const {
|
||||||
@ -729,8 +755,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case M12_pCorner:
|
case M12_pCorner:
|
||||||
for (int x = 0; x <= i; x++) setPixelColorRaw(XY(x, i), col);
|
for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); // note: <= to include i=0. Relies on overflow check in sPC()
|
||||||
for (int y = 0; y < i; y++) setPixelColorRaw(XY(i, y), col);
|
for (int y = 0; y < i; y++) setPixelColorXY(i, y, col);
|
||||||
break;
|
break;
|
||||||
case M12_sPinwheel: {
|
case M12_sPinwheel: {
|
||||||
// Uses Bresenham's algorithm to place coordinates of two lines in arrays then draws between them
|
// Uses Bresenham's algorithm to place coordinates of two lines in arrays then draws between them
|
||||||
@ -987,7 +1013,8 @@ void Segment::fade_out(uint8_t rate) const {
|
|||||||
if (!isActive()) return; // not active
|
if (!isActive()) return; // not active
|
||||||
rate = (256-rate) >> 1;
|
rate = (256-rate) >> 1;
|
||||||
const int mappedRate = 256 / (rate + 1);
|
const int mappedRate = 256 / (rate + 1);
|
||||||
for (unsigned j = 0; j < vLength(); j++) {
|
const size_t rlength = rawLength(); // calculate only once
|
||||||
|
for (unsigned j = 0; j < rlength; j++) {
|
||||||
uint32_t color = getPixelColorRaw(j);
|
uint32_t color = getPixelColorRaw(j);
|
||||||
if (color == colors[1]) continue; // already at target color
|
if (color == colors[1]) continue; // already at target color
|
||||||
for (int i = 0; i < 32; i += 8) {
|
for (int i = 0; i < 32; i += 8) {
|
||||||
@ -1008,13 +1035,15 @@ void Segment::fade_out(uint8_t rate) const {
|
|||||||
// fades all pixels to secondary color
|
// fades all pixels to secondary color
|
||||||
void Segment::fadeToSecondaryBy(uint8_t fadeBy) const {
|
void Segment::fadeToSecondaryBy(uint8_t fadeBy) const {
|
||||||
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
|
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
|
||||||
for (unsigned i = 0; i < vLength(); i++) setPixelColorRaw(i, color_blend(getPixelColorRaw(i), colors[1], fadeBy));
|
const size_t rlength = rawLength(); // calculate only once
|
||||||
|
for (unsigned i = 0; i < rlength; i++) setPixelColorRaw(i, color_blend(getPixelColorRaw(i), colors[1], fadeBy));
|
||||||
}
|
}
|
||||||
|
|
||||||
// fades all pixels to black using nscale8()
|
// fades all pixels to black using nscale8()
|
||||||
void Segment::fadeToBlackBy(uint8_t fadeBy) const {
|
void Segment::fadeToBlackBy(uint8_t fadeBy) const {
|
||||||
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
|
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
|
||||||
for (unsigned i = 0; i < vLength(); i++) setPixelColorRaw(i, color_fade(getPixelColorRaw(i), 255-fadeBy));
|
const size_t rlength = rawLength(); // calculate only once
|
||||||
|
for (unsigned i = 0; i < rlength; i++) setPixelColorRaw(i, color_fade(getPixelColorRaw(i), 255-fadeBy));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1058,27 +1087,14 @@ void Segment::blur(uint8_t blur_amount, bool smear) const {
|
|||||||
/*
|
/*
|
||||||
* Put a value 0 to 255 in to get a color value.
|
* Put a value 0 to 255 in to get a color value.
|
||||||
* The colours are a transition r -> g -> b -> back to r
|
* The colours are a transition r -> g -> b -> back to r
|
||||||
* Inspired by the Adafruit examples.
|
* Rotates the color in HSV space, where pos is H. (0=0deg, 256=360deg)
|
||||||
*/
|
*/
|
||||||
uint32_t Segment::color_wheel(uint8_t pos) const {
|
uint32_t Segment::color_wheel(uint8_t pos) const {
|
||||||
if (palette) return color_from_palette(pos, false, false, 0); // never wrap palette
|
if (palette) return color_from_palette(pos, false, false, 0); // only wrap if "always wrap" is set
|
||||||
uint8_t w = W(getCurrentColor(0));
|
uint8_t w = W(getCurrentColor(0));
|
||||||
pos = 255 - pos;
|
uint32_t rgb;
|
||||||
if (useRainbowWheel) {
|
hsv2rgb(CHSV32(static_cast<uint16_t>(pos << 8), 255, 255), rgb);
|
||||||
CRGB rgb;
|
return rgb | (w << 24); // add white channel
|
||||||
hsv2rgb_rainbow(CHSV(pos, 255, 255), rgb);
|
|
||||||
return RGBW32(rgb.r, rgb.g, rgb.b, w);
|
|
||||||
} else {
|
|
||||||
if (pos < 85) {
|
|
||||||
return RGBW32((255 - pos * 3), 0, (pos * 3), w);
|
|
||||||
} else if (pos < 170) {
|
|
||||||
pos -= 85;
|
|
||||||
return RGBW32(0, (pos * 3), (255 - pos * 3), w);
|
|
||||||
} else {
|
|
||||||
pos -= 170;
|
|
||||||
return RGBW32((pos * 3), (255 - pos * 3), 0, w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1136,21 +1152,27 @@ void WS2812FX::finalizeInit() {
|
|||||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
// determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT)
|
// determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT)
|
||||||
unsigned maxLedsOnBus = 0;
|
unsigned maxLedsOnBus = 0;
|
||||||
|
unsigned busType = 0;
|
||||||
for (const auto &bus : busConfigs) {
|
for (const auto &bus : busConfigs) {
|
||||||
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) {
|
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) {
|
||||||
digitalCount++;
|
digitalCount++;
|
||||||
|
if (busType == 0) busType = bus.type; // remember first bus type
|
||||||
|
if (busType != bus.type) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("Mixed digital bus types detected! Forcing single I2S output.\n"));
|
||||||
|
useParallelI2S = false; // mixed bus types, no parallel I2S
|
||||||
|
}
|
||||||
if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count;
|
if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount);
|
DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount);
|
||||||
// we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0
|
// we may remove 600 LEDs per bus limit when NeoPixelBus is updated beyond 2.8.3
|
||||||
if (maxLedsOnBus <= 300 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses
|
if (maxLedsOnBus <= 600 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses
|
||||||
else useParallelI2S = false; // enforce single I2S
|
else useParallelI2S = false; // enforce single I2S
|
||||||
|
digitalCount = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// create buses/outputs
|
// create buses/outputs
|
||||||
unsigned mem = 0;
|
unsigned mem = 0;
|
||||||
digitalCount = 0;
|
|
||||||
for (const auto &bus : busConfigs) {
|
for (const auto &bus : busConfigs) {
|
||||||
mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer
|
mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer
|
||||||
if (mem <= MAX_LED_MEMORY) {
|
if (mem <= MAX_LED_MEMORY) {
|
||||||
@ -1172,8 +1194,9 @@ void WS2812FX::finalizeInit() {
|
|||||||
if (busEnd > _length) _length = busEnd;
|
if (busEnd > _length) _length = busEnd;
|
||||||
// This must be done after all buses have been created, as some kinds (parallel I2S) interact
|
// This must be done after all buses have been created, as some kinds (parallel I2S) interact
|
||||||
bus->begin();
|
bus->begin();
|
||||||
bus->setBrightness(bri);
|
bus->setBrightness(scaledBri(bri));
|
||||||
}
|
}
|
||||||
|
BusManager::initializeABL(); // init brightness limiter
|
||||||
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
|
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
|
||||||
|
|
||||||
Segment::maxWidth = _length;
|
Segment::maxWidth = _length;
|
||||||
@ -1186,10 +1209,9 @@ void WS2812FX::finalizeInit() {
|
|||||||
deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
|
deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
|
||||||
|
|
||||||
// allocate frame buffer after matrix has been set up (gaps!)
|
// allocate frame buffer after matrix has been set up (gaps!)
|
||||||
if (_pixels) _pixels = static_cast<uint32_t*>(d_realloc(_pixels, getLengthTotal() * sizeof(uint32_t)));
|
d_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
|
||||||
else _pixels = static_cast<uint32_t*>(d_malloc(getLengthTotal() * sizeof(uint32_t)));
|
_pixels = static_cast<uint32_t*>(d_malloc(getLengthTotal() * sizeof(uint32_t)));
|
||||||
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t));
|
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t));
|
||||||
|
|
||||||
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), ESP.getFreeHeap());
|
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1234,7 +1256,8 @@ void WS2812FX::service() {
|
|||||||
// if segment is in transition and no old segment exists we don't need to run the old mode
|
// if segment is in transition and no old segment exists we don't need to run the old mode
|
||||||
// (blendSegments() takes care of On/Off transitions and clipping)
|
// (blendSegments() takes care of On/Off transitions and clipping)
|
||||||
Segment *segO = seg.getOldSegment();
|
Segment *segO = seg.getOldSegment();
|
||||||
if (segO && (seg.mode != segO->mode || blendingStyle != BLEND_STYLE_FADE)) {
|
if (segO && segO->isActive() && (seg.mode != segO->mode || blendingStyle != BLEND_STYLE_FADE ||
|
||||||
|
(segO->name != seg.name && segO->name && seg.name && strncmp(segO->name, seg.name, WLED_MAX_SEGNAME_LEN) != 0))) {
|
||||||
Segment::modeBlend(true); // set semaphore for beginDraw() to blend colors and palette
|
Segment::modeBlend(true); // set semaphore for beginDraw() to blend colors and palette
|
||||||
segO->beginDraw(prog); // set up palette & colors (also sets draw dimensions), parent segment has transition progress
|
segO->beginDraw(prog); // set up palette & colors (also sets draw dimensions), parent segment has transition progress
|
||||||
_currentSegment = segO; // set current segment
|
_currentSegment = segO; // set current segment
|
||||||
@ -1275,7 +1298,7 @@ static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t
|
|||||||
static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; }
|
static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; }
|
||||||
static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); }
|
static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); }
|
||||||
static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; }
|
static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; }
|
||||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3 but slightly less accurate
|
static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3 but slightly less accurate
|
||||||
#else
|
#else
|
||||||
static uint8_t _multiply (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1]
|
static uint8_t _multiply (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1]
|
||||||
@ -1286,10 +1309,10 @@ static uint8_t _darken (uint8_t a, uint8_t b) { return a < b ? a : b; }
|
|||||||
static uint8_t _screen (uint8_t a, uint8_t b) { return 255 - _multiply(~a,~b); } // 255 - (255-a)*(255-b)/255
|
static uint8_t _screen (uint8_t a, uint8_t b) { return 255 - _multiply(~a,~b); } // 255 - (255-a)*(255-b)/255
|
||||||
static uint8_t _overlay (uint8_t a, uint8_t b) { return b < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
static uint8_t _overlay (uint8_t a, uint8_t b) { return b < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
||||||
static uint8_t _hardlight (uint8_t a, uint8_t b) { return a < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
static uint8_t _hardlight (uint8_t a, uint8_t b) { return a < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
||||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a) + 255) >> 8) + 2 * a * b + 255) >> 8; } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a))) + ((2 * a * b + 256) << 8)) >> 16; } // Pegtop's formula (1 - 2a)b^2
|
||||||
#else
|
#else
|
||||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) / 255 + 2 * a * b) / 255; } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) + 255 * 2 * a * b) / (255 * 255); } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
||||||
#endif
|
#endif
|
||||||
static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); }
|
static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); }
|
||||||
static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); }
|
static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); }
|
||||||
@ -1432,8 +1455,10 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
|||||||
}
|
}
|
||||||
uint32_t c_a = BLACK;
|
uint32_t c_a = BLACK;
|
||||||
if (x < vCols && y < vRows) c_a = seg->getPixelColorRaw(x + y*vCols); // will get clipped pixel from old segment or unclipped pixel from new segment
|
if (x < vCols && y < vRows) c_a = seg->getPixelColorRaw(x + y*vCols); // will get clipped pixel from old segment or unclipped pixel from new segment
|
||||||
if (segO && blendingStyle == BLEND_STYLE_FADE && topSegment.mode != segO->mode && x < oCols && y < oRows) {
|
if (segO && blendingStyle == BLEND_STYLE_FADE
|
||||||
// we need to blend old segment using fade as pixels ae not clipped
|
&& (topSegment.mode != segO->mode || (segO->name != topSegment.name && segO->name && topSegment.name && strncmp(segO->name, topSegment.name, WLED_MAX_SEGNAME_LEN) != 0))
|
||||||
|
&& x < oCols && y < oRows) {
|
||||||
|
// we need to blend old segment using fade as pixels are not clipped
|
||||||
c_a = color_blend16(c_a, segO->getPixelColorRaw(x + y*oCols), progInv);
|
c_a = color_blend16(c_a, segO->getPixelColorRaw(x + y*oCols), progInv);
|
||||||
} else if (blendingStyle != BLEND_STYLE_FADE) {
|
} else if (blendingStyle != BLEND_STYLE_FADE) {
|
||||||
// workaround for On/Off transition
|
// workaround for On/Off transition
|
||||||
@ -1526,67 +1551,9 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
|||||||
Segment::setClippingRect(0, 0); // disable clipping for overlays
|
Segment::setClippingRect(0, 0); // disable clipping for overlays
|
||||||
}
|
}
|
||||||
|
|
||||||
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
|
|
||||||
static uint8_t estimateCurrentAndLimitBri(uint8_t brightness, uint32_t *pixels) {
|
|
||||||
unsigned milliAmpsMax = BusManager::ablMilliampsMax();
|
|
||||||
if (milliAmpsMax > 0) {
|
|
||||||
unsigned milliAmpsTotal = 0;
|
|
||||||
unsigned avgMilliAmpsPerLED = 0;
|
|
||||||
unsigned lengthDigital = 0;
|
|
||||||
bool useWackyWS2815PowerModel = false;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < BusManager::getNumBusses(); i++) {
|
|
||||||
const Bus *bus = BusManager::getBus(i);
|
|
||||||
if (!(bus && bus->isDigital() && bus->isOk())) continue;
|
|
||||||
unsigned maPL = bus->getLEDCurrent();
|
|
||||||
if (maPL == 0 || bus->getMaxCurrent() > 0) continue; // skip buses with 0 mA per LED or max current per bus defined (PP-ABL)
|
|
||||||
if (maPL == 255) {
|
|
||||||
useWackyWS2815PowerModel = true;
|
|
||||||
maPL = 12; // WS2815 uses 12mA per channel
|
|
||||||
}
|
|
||||||
avgMilliAmpsPerLED += maPL * bus->getLength();
|
|
||||||
lengthDigital += bus->getLength();
|
|
||||||
// sum up the usage of each LED on digital bus
|
|
||||||
uint32_t busPowerSum = 0;
|
|
||||||
for (unsigned j = 0; j < bus->getLength(); j++) {
|
|
||||||
uint32_t c = pixels[j + bus->getStart()];
|
|
||||||
byte r = R(c), g = G(c), b = B(c), w = W(c);
|
|
||||||
if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
|
|
||||||
busPowerSum += (max(max(r,g),b)) * 3;
|
|
||||||
} else {
|
|
||||||
busPowerSum += (r + g + b + w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
|
|
||||||
if (bus->hasWhite()) {
|
|
||||||
busPowerSum *= 3;
|
|
||||||
busPowerSum >>= 2; //same as /= 4
|
|
||||||
}
|
|
||||||
// powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps
|
|
||||||
milliAmpsTotal += (busPowerSum * maPL * brightness) / (765*255);
|
|
||||||
}
|
|
||||||
if (lengthDigital > 0) {
|
|
||||||
avgMilliAmpsPerLED /= lengthDigital;
|
|
||||||
|
|
||||||
if (milliAmpsMax > MA_FOR_ESP && avgMilliAmpsPerLED > 0) { //0 mA per LED and too low numbers turn off calculation
|
|
||||||
unsigned powerBudget = (milliAmpsMax - MA_FOR_ESP); //80/120mA for ESP power
|
|
||||||
if (powerBudget > lengthDigital) { //each LED uses about 1mA in standby, exclude that from power budget
|
|
||||||
powerBudget -= lengthDigital;
|
|
||||||
} else {
|
|
||||||
powerBudget = 0;
|
|
||||||
}
|
|
||||||
if (milliAmpsTotal > powerBudget) {
|
|
||||||
//scale brightness down to stay in current limit
|
|
||||||
unsigned scaleB = powerBudget * 255 / milliAmpsTotal;
|
|
||||||
brightness = ((brightness * scaleB) >> 8) + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return brightness;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WS2812FX::show() {
|
void WS2812FX::show() {
|
||||||
|
if (!_pixels) return; // no pixels allocated, nothing to show
|
||||||
|
|
||||||
unsigned long showNow = millis();
|
unsigned long showNow = millis();
|
||||||
size_t diff = showNow - _lastShow;
|
size_t diff = showNow - _lastShow;
|
||||||
|
|
||||||
@ -1611,10 +1578,6 @@ void WS2812FX::show() {
|
|||||||
show_callback callback = _callback;
|
show_callback callback = _callback;
|
||||||
if (callback) callback(); // will call setPixelColor or setRealtimePixelColor
|
if (callback) callback(); // will call setPixelColor or setRealtimePixelColor
|
||||||
|
|
||||||
// determine ABL brightness
|
|
||||||
uint8_t newBri = estimateCurrentAndLimitBri(_brightness, _pixels);
|
|
||||||
if (newBri != _brightness) BusManager::setBrightness(newBri);
|
|
||||||
|
|
||||||
// paint actual pixels
|
// paint actual pixels
|
||||||
int oldCCT = Bus::getCCT(); // store original CCT value (since it is global)
|
int oldCCT = Bus::getCCT(); // store original CCT value (since it is global)
|
||||||
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values (cct==-1)
|
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values (cct==-1)
|
||||||
@ -1625,7 +1588,11 @@ void WS2812FX::show() {
|
|||||||
if (_pixelCCT) { // cctFromRgb already exluded at allocation
|
if (_pixelCCT) { // cctFromRgb already exluded at allocation
|
||||||
if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB);
|
if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB);
|
||||||
}
|
}
|
||||||
BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i]));
|
|
||||||
|
uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32)
|
||||||
|
if(c > 0 && !(realtimeMode && arlsDisableGammaCorrection))
|
||||||
|
c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss
|
||||||
|
BusManager::setPixelColor(getMappedPixelIndex(i), c);
|
||||||
}
|
}
|
||||||
Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments
|
Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments
|
||||||
|
|
||||||
@ -1637,9 +1604,6 @@ void WS2812FX::show() {
|
|||||||
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
|
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
|
||||||
BusManager::show();
|
BusManager::show();
|
||||||
|
|
||||||
// restore brightness for next frame
|
|
||||||
if (newBri != _brightness) BusManager::setBrightness(_brightness);
|
|
||||||
|
|
||||||
if (diff > 0) { // skip calculation if no time has passed
|
if (diff > 0) { // skip calculation if no time has passed
|
||||||
size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math
|
size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math
|
||||||
_cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding
|
_cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding
|
||||||
@ -1674,7 +1638,7 @@ void WS2812FX::setTransitionMode(bool t) {
|
|||||||
|
|
||||||
// wait until frame is over (service() has finished or time for 1 frame has passed; yield() crashes on 8266)
|
// wait until frame is over (service() has finished or time for 1 frame has passed; yield() crashes on 8266)
|
||||||
void WS2812FX::waitForIt() {
|
void WS2812FX::waitForIt() {
|
||||||
unsigned long maxWait = millis() + getFrameTime();
|
unsigned long maxWait = millis() + getFrameTime() + 100; // TODO: this needs a proper fix for timeout!
|
||||||
while (isServicing() && maxWait > millis()) delay(1);
|
while (isServicing() && maxWait > millis()) delay(1);
|
||||||
#ifdef WLED_DEBUG
|
#ifdef WLED_DEBUG
|
||||||
if (millis() >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing."));
|
if (millis() >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing."));
|
||||||
@ -1704,7 +1668,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) {
|
|||||||
if (_brightness == 0) { //unfreeze all segments on power off
|
if (_brightness == 0) { //unfreeze all segments on power off
|
||||||
for (const Segment &seg : _segments) seg.freeze = false; // freeze is mutable
|
for (const Segment &seg : _segments) seg.freeze = false; // freeze is mutable
|
||||||
}
|
}
|
||||||
BusManager::setBrightness(b);
|
BusManager::setBrightness(scaledBri(b));
|
||||||
if (!direct) {
|
if (!direct) {
|
||||||
unsigned long t = millis();
|
unsigned long t = millis();
|
||||||
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon
|
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon
|
||||||
@ -1859,7 +1823,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
|
|||||||
for (size_t i = 1; i < s; i++) {
|
for (size_t i = 1; i < s; i++) {
|
||||||
_segments.emplace_back(segStarts[i], segStops[i]);
|
_segments.emplace_back(segStarts[i], segStops[i]);
|
||||||
}
|
}
|
||||||
DEBUG_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size());
|
DEBUGFX_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -1983,7 +1947,7 @@ bool WS2812FX::deserializeMap(unsigned n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
d_free(customMappingTable);
|
d_free(customMappingTable);
|
||||||
customMappingTable = static_cast<uint16_t*>(d_malloc(sizeof(uint16_t)*getLengthTotal())); // do not use SPI RAM
|
customMappingTable = static_cast<uint16_t*>(d_malloc(sizeof(uint16_t)*getLengthTotal())); // prefer DRAM for speed
|
||||||
|
|
||||||
if (customMappingTable) {
|
if (customMappingTable) {
|
||||||
DEBUG_PRINTF_P(PSTR("ledmap allocated: %uB\n"), sizeof(uint16_t)*getLengthTotal());
|
DEBUG_PRINTF_P(PSTR("ledmap allocated: %uB\n"), sizeof(uint16_t)*getLengthTotal());
|
||||||
|
|||||||
@ -17,9 +17,8 @@
|
|||||||
// local shared functions (used both in 1D and 2D system)
|
// local shared functions (used both in 1D and 2D system)
|
||||||
static int32_t calcForce_dv(const int8_t force, uint8_t &counter);
|
static int32_t calcForce_dv(const int8_t force, uint8_t &counter);
|
||||||
static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, const bool wrap); // returns false if out of bounds by more than particleradius
|
static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, const bool wrap); // returns false if out of bounds by more than particleradius
|
||||||
static void fast_color_add(CRGB &c1, const CRGB &c2, uint8_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding)
|
static uint32_t fast_color_add(CRGBW c1, const CRGBW c2, uint8_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding)
|
||||||
static void fast_color_scale(CRGB &c, const uint8_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255
|
static uint32_t fast_color_scale(CRGBW c, const uint8_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255
|
||||||
//static CRGB *allocateCRGBbuffer(uint32_t length);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
|
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
|
||||||
@ -558,7 +557,11 @@ void ParticleSystem2D::pointAttractor(const uint32_t particleindex, PSparticle &
|
|||||||
// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds
|
// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds
|
||||||
// firemode is only used for PS Fire FX
|
// firemode is only used for PS Fire FX
|
||||||
void ParticleSystem2D::render() {
|
void ParticleSystem2D::render() {
|
||||||
CRGB baseRGB;
|
if(framebuffer == nullptr) {
|
||||||
|
PSPRINTLN(F("PS render: no framebuffer!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CRGBW baseRGB;
|
||||||
uint32_t brightness; // particle brightness, fades if dying
|
uint32_t brightness; // particle brightness, fades if dying
|
||||||
TBlendType blend = LINEARBLEND; // default color rendering: wrap palette
|
TBlendType blend = LINEARBLEND; // default color rendering: wrap palette
|
||||||
if (particlesettings.colorByAge) {
|
if (particlesettings.colorByAge) {
|
||||||
@ -569,13 +572,13 @@ void ParticleSystem2D::render() {
|
|||||||
for (int32_t y = 0; y <= maxYpixel; y++) {
|
for (int32_t y = 0; y <= maxYpixel; y++) {
|
||||||
int index = y * (maxXpixel + 1);
|
int index = y * (maxXpixel + 1);
|
||||||
for (int32_t x = 0; x <= maxXpixel; x++) {
|
for (int32_t x = 0; x <= maxXpixel; x++) {
|
||||||
fast_color_scale(framebuffer[index], motionBlur); // note: could skip if only globalsmear is active but usually they are both active and scaling is fast enough
|
framebuffer[index] = fast_color_scale(framebuffer[index], motionBlur); // note: could skip if only globalsmear is active but usually they are both active and scaling is fast enough
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { // no blurring: clear buffer
|
else { // no blurring: clear buffer
|
||||||
memset(framebuffer, 0, (maxXpixel+1) * (maxYpixel+1) * sizeof(CRGB));
|
memset(framebuffer, 0, (maxXpixel+1) * (maxYpixel+1) * sizeof(CRGBW));
|
||||||
}
|
}
|
||||||
|
|
||||||
// go over particles and render them to the buffer
|
// go over particles and render them to the buffer
|
||||||
@ -593,14 +596,12 @@ void ParticleSystem2D::render() {
|
|||||||
baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255, blend);
|
baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255, blend);
|
||||||
if (particles[i].sat < 255) {
|
if (particles[i].sat < 255) {
|
||||||
CHSV32 baseHSV;
|
CHSV32 baseHSV;
|
||||||
rgb2hsv((uint32_t((byte(baseRGB.r) << 16) | (byte(baseRGB.g) << 8) | (byte(baseRGB.b)))), baseHSV); // convert to HSV
|
rgb2hsv(baseRGB.color32, baseHSV); // convert to HSV
|
||||||
baseHSV.s = min(baseHSV.s, particles[i].sat); // set the saturation but don't increase it
|
baseHSV.s = min(baseHSV.s, particles[i].sat); // set the saturation but don't increase it
|
||||||
uint32_t tempcolor;
|
hsv2rgb(baseHSV, baseRGB.color32); // convert back to RGB
|
||||||
hsv2rgb(baseHSV, tempcolor); // convert back to RGB
|
|
||||||
baseRGB = (CRGB)tempcolor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
|
if(gammaCorrectCol) brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
|
||||||
renderParticle(i, brightness, baseRGB, particlesettings.wrapX, particlesettings.wrapY);
|
renderParticle(i, brightness, baseRGB, particlesettings.wrapX, particlesettings.wrapY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,18 +622,10 @@ void ParticleSystem2D::render() {
|
|||||||
if (smearBlur) {
|
if (smearBlur) {
|
||||||
blur2D(framebuffer, maxXpixel + 1, maxYpixel + 1, smearBlur, smearBlur);
|
blur2D(framebuffer, maxXpixel + 1, maxYpixel + 1, smearBlur, smearBlur);
|
||||||
}
|
}
|
||||||
|
|
||||||
// transfer the framebuffer to the segment
|
|
||||||
for (int y = 0; y <= maxYpixel; y++) {
|
|
||||||
int index = y * (maxXpixel + 1); // current row index for 1D buffer
|
|
||||||
for (int x = 0; x <= maxXpixel; x++) {
|
|
||||||
SEGMENT.setPixelColorXY(x, y, framebuffer[index++]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
|
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
|
||||||
__attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY) {
|
__attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW& color, const bool wrapX, const bool wrapY) {
|
||||||
uint32_t size = particlesize;
|
uint32_t size = particlesize;
|
||||||
if (advPartProps && advPartProps[particleindex].size > 0) // use advanced size properties (0 means use global size including single pixel rendering)
|
if (advPartProps && advPartProps[particleindex].size > 0) // use advanced size properties (0 means use global size including single pixel rendering)
|
||||||
size = advPartProps[particleindex].size;
|
size = advPartProps[particleindex].size;
|
||||||
@ -641,7 +634,8 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint
|
|||||||
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT;
|
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT;
|
||||||
uint32_t y = particles[particleindex].y >> PS_P_RADIUS_SHIFT;
|
uint32_t y = particles[particleindex].y >> PS_P_RADIUS_SHIFT;
|
||||||
if (x <= (uint32_t)maxXpixel && y <= (uint32_t)maxYpixel) {
|
if (x <= (uint32_t)maxXpixel && y <= (uint32_t)maxYpixel) {
|
||||||
fast_color_add(framebuffer[x + (maxYpixel - y) * (maxXpixel + 1)], color, brightness);
|
uint32_t index = x + (maxYpixel - y) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
|
||||||
|
framebuffer[index] = fast_color_add(framebuffer[index], color, brightness);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -681,20 +675,22 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint
|
|||||||
// - scale brigthness with gamma correction (done in render())
|
// - scale brigthness with gamma correction (done in render())
|
||||||
// - apply inverse gamma correction to brightness values
|
// - apply inverse gamma correction to brightness values
|
||||||
// - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total
|
// - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total
|
||||||
pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma
|
if(gammaCorrectCol) {
|
||||||
pxlbrightness[1] = gamma8inv(pxlbrightness[1]);
|
pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma
|
||||||
pxlbrightness[2] = gamma8inv(pxlbrightness[2]);
|
pxlbrightness[1] = gamma8inv(pxlbrightness[1]);
|
||||||
pxlbrightness[3] = gamma8inv(pxlbrightness[3]);
|
pxlbrightness[2] = gamma8inv(pxlbrightness[2]);
|
||||||
|
pxlbrightness[3] = gamma8inv(pxlbrightness[3]);
|
||||||
|
}
|
||||||
|
|
||||||
if (advPartProps && advPartProps[particleindex].size > 1) { //render particle to a bigger size
|
if (advPartProps && advPartProps[particleindex].size > 1) { //render particle to a bigger size
|
||||||
CRGB renderbuffer[100]; // 10x10 pixel buffer
|
uint32_t renderbuffer[100]; // 10x10 pixel buffer
|
||||||
memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer
|
memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer
|
||||||
//particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10
|
//particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10
|
||||||
//first, render the pixel to the center of the renderbuffer, then apply 2D blurring
|
//first, render the pixel to the center of the renderbuffer, then apply 2D blurring
|
||||||
fast_color_add(renderbuffer[4 + (4 * 10)], color, pxlbrightness[0]); // oCrder is: bottom left, bottom right, top right, top left
|
renderbuffer[4 + (4 * 10)] = fast_color_add(renderbuffer[4 + (4 * 10)], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left
|
||||||
fast_color_add(renderbuffer[5 + (4 * 10)], color, pxlbrightness[1]);
|
renderbuffer[5 + (4 * 10)] = fast_color_add(renderbuffer[5 + (4 * 10)], color, pxlbrightness[1]);
|
||||||
fast_color_add(renderbuffer[5 + (5 * 10)], color, pxlbrightness[2]);
|
renderbuffer[5 + (5 * 10)] = fast_color_add(renderbuffer[5 + (5 * 10)], color, pxlbrightness[2]);
|
||||||
fast_color_add(renderbuffer[4 + (5 * 10)], color, pxlbrightness[3]);
|
renderbuffer[4 + (5 * 10)] = fast_color_add(renderbuffer[4 + (5 * 10)], color, pxlbrightness[3]);
|
||||||
uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4
|
uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4
|
||||||
uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below)
|
uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below)
|
||||||
uint32_t maxsize = advPartProps[particleindex].size;
|
uint32_t maxsize = advPartProps[particleindex].size;
|
||||||
@ -751,7 +747,8 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint
|
|||||||
else
|
else
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
fast_color_add(framebuffer[xfb + (maxYpixel - yfb) * (maxXpixel + 1)], renderbuffer[xrb + yrb * 10]);
|
uint32_t idx = xfb + (maxYpixel - yfb) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
|
||||||
|
framebuffer[idx] = fast_color_add(framebuffer[idx], renderbuffer[xrb + yrb * 10]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // standard rendering (2x2 pixels)
|
} else { // standard rendering (2x2 pixels)
|
||||||
@ -786,8 +783,10 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (uint32_t i = 0; i < 4; i++) {
|
for (uint32_t i = 0; i < 4; i++) {
|
||||||
if (pixelvalid[i])
|
if (pixelvalid[i]) {
|
||||||
fast_color_add(framebuffer[pixco[i].x + (maxYpixel - pixco[i].y) * (maxXpixel + 1)], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left
|
uint32_t idx = pixco[i].x + (maxYpixel - pixco[i].y) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
|
||||||
|
framebuffer[idx] = fast_color_add(framebuffer[idx], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -970,17 +969,17 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::collideParticles(PSpartic
|
|||||||
// update size and pointers (memory location and size can change dynamically)
|
// update size and pointers (memory location and size can change dynamically)
|
||||||
// note: do not access the PS class in FX befor running this function (or it messes up SEGENV.data)
|
// note: do not access the PS class in FX befor running this function (or it messes up SEGENV.data)
|
||||||
void ParticleSystem2D::updateSystem(void) {
|
void ParticleSystem2D::updateSystem(void) {
|
||||||
PSPRINTLN("updateSystem2D");
|
//PSPRINTLN("updateSystem2D");
|
||||||
setMatrixSize(SEGMENT.vWidth(), SEGMENT.vHeight());
|
setMatrixSize(SEGMENT.vWidth(), SEGMENT.vHeight());
|
||||||
updatePSpointers(advPartProps != nullptr, advPartSize != nullptr); // update pointers to PS data, also updates availableParticles
|
updatePSpointers(advPartProps != nullptr, advPartSize != nullptr); // update pointers to PS data, also updates availableParticles
|
||||||
PSPRINTLN("\n END update System2D, running FX...");
|
//PSPRINTLN("\n END update System2D, running FX...");
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the pointers for the class (this only has to be done once and not on every FX call, only the class pointer needs to be reassigned to SEGENV.data every time)
|
// set the pointers for the class (this only has to be done once and not on every FX call, only the class pointer needs to be reassigned to SEGENV.data every time)
|
||||||
// function returns the pointer to the next byte available for the FX (if it assigned more memory for other stuff using the above allocate function)
|
// function returns the pointer to the next byte available for the FX (if it assigned more memory for other stuff using the above allocate function)
|
||||||
// FX handles the PSsources, need to tell this function how many there are
|
// FX handles the PSsources, need to tell this function how many there are
|
||||||
void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) {
|
void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) {
|
||||||
PSPRINTLN("updatePSpointers");
|
//PSPRINTLN("updatePSpointers");
|
||||||
// Note on memory alignment:
|
// Note on memory alignment:
|
||||||
// a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment.
|
// a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment.
|
||||||
// The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock.
|
// The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock.
|
||||||
@ -988,11 +987,8 @@ void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) {
|
|||||||
particles = reinterpret_cast<PSparticle *>(this + 1); // pointer to particles
|
particles = reinterpret_cast<PSparticle *>(this + 1); // pointer to particles
|
||||||
particleFlags = reinterpret_cast<PSparticleFlags *>(particles + numParticles); // pointer to particle flags
|
particleFlags = reinterpret_cast<PSparticleFlags *>(particles + numParticles); // pointer to particle flags
|
||||||
sources = reinterpret_cast<PSsource *>(particleFlags + numParticles); // pointer to source(s) at data+sizeof(ParticleSystem2D)
|
sources = reinterpret_cast<PSsource *>(particleFlags + numParticles); // pointer to source(s) at data+sizeof(ParticleSystem2D)
|
||||||
framebuffer = reinterpret_cast<CRGB *>(sources + numSources); // pointer to framebuffer
|
framebuffer = SEGMENT.getPixels(); // pointer to framebuffer
|
||||||
// align pointer after framebuffer
|
PSdataEnd = reinterpret_cast<uint8_t *>(sources + numSources); // pointer to first available byte after the PS for FX additional data (already aligned to 4 byte boundary)
|
||||||
uintptr_t p = reinterpret_cast<uintptr_t>(framebuffer + (maxXpixel+1)*(maxYpixel+1));
|
|
||||||
p = (p + 3) & ~0x03; // align to 4-byte boundary
|
|
||||||
PSdataEnd = reinterpret_cast<uint8_t *>(p); // pointer to first available byte after the PS for FX additional data
|
|
||||||
if (isadvanced) {
|
if (isadvanced) {
|
||||||
advPartProps = reinterpret_cast<PSadvancedParticle *>(PSdataEnd);
|
advPartProps = reinterpret_cast<PSadvancedParticle *>(PSdataEnd);
|
||||||
PSdataEnd = reinterpret_cast<uint8_t *>(advPartProps + numParticles);
|
PSdataEnd = reinterpret_cast<uint8_t *>(advPartProps + numParticles);
|
||||||
@ -1015,8 +1011,8 @@ void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) {
|
|||||||
// for speed, 1D array and 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined
|
// for speed, 1D array and 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined
|
||||||
// to blur a subset of the buffer, change the xsize/ysize and set xstart/ystart to the desired starting coordinates (default start is 0/0)
|
// to blur a subset of the buffer, change the xsize/ysize and set xstart/ystart to the desired starting coordinates (default start is 0/0)
|
||||||
// subset blurring only works on 10x10 buffer (single particle rendering), if other sizes are needed, buffer width must be passed as parameter
|
// subset blurring only works on 10x10 buffer (single particle rendering), if other sizes are needed, buffer width must be passed as parameter
|
||||||
void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, uint32_t xstart, uint32_t ystart, bool isparticle) {
|
void blur2D(uint32_t *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, uint32_t xstart, uint32_t ystart, bool isparticle) {
|
||||||
CRGB seeppart, carryover;
|
CRGBW seeppart, carryover;
|
||||||
uint32_t seep = xblur >> 1;
|
uint32_t seep = xblur >> 1;
|
||||||
uint32_t width = xsize; // width of the buffer, used to calculate the index of the pixel
|
uint32_t width = xsize; // width of the buffer, used to calculate the index of the pixel
|
||||||
|
|
||||||
@ -1030,12 +1026,11 @@ void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, u
|
|||||||
carryover = BLACK;
|
carryover = BLACK;
|
||||||
uint32_t indexXY = xstart + y * width;
|
uint32_t indexXY = xstart + y * width;
|
||||||
for (uint32_t x = xstart; x < xstart + xsize; x++) {
|
for (uint32_t x = xstart; x < xstart + xsize; x++) {
|
||||||
seeppart = colorbuffer[indexXY]; // create copy of current color
|
seeppart = fast_color_scale(colorbuffer[indexXY], seep); // scale it and seep to neighbours
|
||||||
fast_color_scale(seeppart, seep); // scale it and seep to neighbours
|
|
||||||
if (x > 0) {
|
if (x > 0) {
|
||||||
fast_color_add(colorbuffer[indexXY - 1], seeppart);
|
colorbuffer[indexXY - 1] = fast_color_add(colorbuffer[indexXY - 1], seeppart);
|
||||||
if (carryover) // note: check adds overhead but is faster on average
|
if (carryover.color32) // note: check adds overhead but is faster on average
|
||||||
fast_color_add(colorbuffer[indexXY], carryover);
|
colorbuffer[indexXY] = fast_color_add(colorbuffer[indexXY], carryover);
|
||||||
}
|
}
|
||||||
carryover = seeppart;
|
carryover = seeppart;
|
||||||
indexXY++; // next pixel in x direction
|
indexXY++; // next pixel in x direction
|
||||||
@ -1052,12 +1047,11 @@ void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, u
|
|||||||
carryover = BLACK;
|
carryover = BLACK;
|
||||||
uint32_t indexXY = x + ystart * width;
|
uint32_t indexXY = x + ystart * width;
|
||||||
for (uint32_t y = ystart; y < ystart + ysize; y++) {
|
for (uint32_t y = ystart; y < ystart + ysize; y++) {
|
||||||
seeppart = colorbuffer[indexXY]; // create copy of current color
|
seeppart = fast_color_scale(colorbuffer[indexXY], seep); // scale it and seep to neighbours
|
||||||
fast_color_scale(seeppart, seep); // scale it and seep to neighbours
|
|
||||||
if (y > 0) {
|
if (y > 0) {
|
||||||
fast_color_add(colorbuffer[indexXY - width], seeppart);
|
colorbuffer[indexXY - width] = fast_color_add(colorbuffer[indexXY - width], seeppart);
|
||||||
if (carryover) // note: check adds overhead but is faster on average
|
if (carryover.color32) // note: check adds overhead but is faster on average
|
||||||
fast_color_add(colorbuffer[indexXY], carryover);
|
colorbuffer[indexXY] = fast_color_add(colorbuffer[indexXY], carryover);
|
||||||
}
|
}
|
||||||
carryover = seeppart;
|
carryover = seeppart;
|
||||||
indexXY += width; // next pixel in y direction
|
indexXY += width; // next pixel in y direction
|
||||||
@ -1068,13 +1062,7 @@ void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, u
|
|||||||
//non class functions to use for initialization
|
//non class functions to use for initialization
|
||||||
uint32_t calculateNumberOfParticles2D(uint32_t const pixels, const bool isadvanced, const bool sizecontrol) {
|
uint32_t calculateNumberOfParticles2D(uint32_t const pixels, const bool isadvanced, const bool sizecontrol) {
|
||||||
uint32_t numberofParticles = pixels; // 1 particle per pixel (for example 512 particles on 32x16)
|
uint32_t numberofParticles = pixels; // 1 particle per pixel (for example 512 particles on 32x16)
|
||||||
#ifdef ESP8266
|
uint32_t particlelimit = MAXPARTICLES_2D; // maximum number of paticles allowed
|
||||||
uint32_t particlelimit = ESP8266_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 16x16 and 4k effect ram)
|
|
||||||
#elif ARDUINO_ARCH_ESP32S2
|
|
||||||
uint32_t particlelimit = ESP32S2_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 32x32 and 24k effect ram)
|
|
||||||
#else
|
|
||||||
uint32_t particlelimit = ESP32_MAXPARTICLES; // maximum number of paticles allowed (based on two segments of 32x32 and 40k effect ram)
|
|
||||||
#endif
|
|
||||||
numberofParticles = max((uint32_t)4, min(numberofParticles, particlelimit)); // limit to 4 - particlelimit
|
numberofParticles = max((uint32_t)4, min(numberofParticles, particlelimit)); // limit to 4 - particlelimit
|
||||||
if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount
|
if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount
|
||||||
numberofParticles = (numberofParticles * sizeof(PSparticle)) / (sizeof(PSparticle) + sizeof(PSadvancedParticle));
|
numberofParticles = (numberofParticles * sizeof(PSparticle)) / (sizeof(PSparticle) + sizeof(PSadvancedParticle));
|
||||||
@ -1087,16 +1075,8 @@ uint32_t calculateNumberOfParticles2D(uint32_t const pixels, const bool isadvanc
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t calculateNumberOfSources2D(uint32_t pixels, uint32_t requestedsources) {
|
uint32_t calculateNumberOfSources2D(uint32_t pixels, uint32_t requestedsources) {
|
||||||
#ifdef ESP8266
|
int numberofSources = min((pixels) / SOURCEREDUCTIONFACTOR, (uint32_t)requestedsources);
|
||||||
int numberofSources = min((pixels) / 8, (uint32_t)requestedsources);
|
numberofSources = max(1, min(numberofSources, MAXSOURCES_2D)); // limit
|
||||||
numberofSources = max(1, min(numberofSources, ESP8266_MAXSOURCES)); // limit
|
|
||||||
#elif ARDUINO_ARCH_ESP32S2
|
|
||||||
int numberofSources = min((pixels) / 6, (uint32_t)requestedsources);
|
|
||||||
numberofSources = max(1, min(numberofSources, ESP32S2_MAXSOURCES)); // limit
|
|
||||||
#else
|
|
||||||
int numberofSources = min((pixels) / 4, (uint32_t)requestedsources);
|
|
||||||
numberofSources = max(1, min(numberofSources, ESP32_MAXSOURCES)); // limit
|
|
||||||
#endif
|
|
||||||
// make sure it is a multiple of 4 for proper memory alignment
|
// make sure it is a multiple of 4 for proper memory alignment
|
||||||
numberofSources = (numberofSources+3) & ~0x03;
|
numberofSources = (numberofSources+3) & ~0x03;
|
||||||
return numberofSources;
|
return numberofSources;
|
||||||
@ -1115,10 +1095,7 @@ bool allocateParticleSystemMemory2D(uint32_t numparticles, uint32_t numsources,
|
|||||||
if (sizecontrol)
|
if (sizecontrol)
|
||||||
requiredmemory += sizeof(PSsizeControl) * numparticles;
|
requiredmemory += sizeof(PSsizeControl) * numparticles;
|
||||||
requiredmemory += sizeof(PSsource) * numsources;
|
requiredmemory += sizeof(PSsource) * numsources;
|
||||||
requiredmemory += sizeof(CRGB) * SEGMENT.virtualLength(); // virtualLength is witdh * height
|
requiredmemory += additionalbytes;
|
||||||
requiredmemory += additionalbytes + 3; // add 3 to ensure there is room for stuffing bytes
|
|
||||||
//requiredmemory = (requiredmemory + 3) & ~0x03; // align memory block to next 4-byte boundary
|
|
||||||
PSPRINTLN("mem alloc: " + String(requiredmemory));
|
|
||||||
return(SEGMENT.allocateData(requiredmemory));
|
return(SEGMENT.allocateData(requiredmemory));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1132,17 +1109,26 @@ bool initParticleSystem2D(ParticleSystem2D *&PartSys, uint32_t requestedsources,
|
|||||||
|
|
||||||
uint32_t numparticles = calculateNumberOfParticles2D(pixels, advanced, sizecontrol);
|
uint32_t numparticles = calculateNumberOfParticles2D(pixels, advanced, sizecontrol);
|
||||||
PSPRINT(" segmentsize:" + String(cols) + " x " + String(rows));
|
PSPRINT(" segmentsize:" + String(cols) + " x " + String(rows));
|
||||||
PSPRINT(" request numparticles:" + String(numparticles));
|
PSPRINTLN(" request numparticles:" + String(numparticles));
|
||||||
uint32_t numsources = calculateNumberOfSources2D(pixels, requestedsources);
|
uint32_t numsources = calculateNumberOfSources2D(pixels, requestedsources);
|
||||||
if (!allocateParticleSystemMemory2D(numparticles, numsources, advanced, sizecontrol, additionalbytes))
|
bool allocsuccess = false;
|
||||||
{
|
while(numparticles >= 4) { // make sure we have at least 4 particles or quit
|
||||||
DEBUG_PRINT(F("PS init failed: memory depleted"));
|
if (allocateParticleSystemMemory2D(numparticles, numsources, advanced, sizecontrol, additionalbytes)) {
|
||||||
return false;
|
PSPRINTLN(F("PS 2D alloc succeeded"));
|
||||||
|
allocsuccess = true;
|
||||||
|
break; // allocation succeeded
|
||||||
|
}
|
||||||
|
numparticles = ((numparticles / 2) + 3) & ~0x03; // cut number of particles in half and try again, must be 4 byte aligned
|
||||||
|
PSPRINTLN(F("PS 2D alloc failed, trying with less particles..."));
|
||||||
|
}
|
||||||
|
if (!allocsuccess) {
|
||||||
|
PSPRINTLN(F("PS 2D alloc failed, not enough memory!"));
|
||||||
|
return false; // allocation failed
|
||||||
}
|
}
|
||||||
|
|
||||||
PartSys = new (SEGENV.data) ParticleSystem2D(cols, rows, numparticles, numsources, advanced, sizecontrol); // particle system constructor
|
PartSys = new (SEGENV.data) ParticleSystem2D(cols, rows, numparticles, numsources, advanced, sizecontrol); // particle system constructor
|
||||||
|
|
||||||
PSPRINTLN("******init done, pointers:");
|
PSPRINTLN(F("2D PS init done"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1435,28 +1421,26 @@ void ParticleSystem1D::applyFriction(int32_t coefficient) {
|
|||||||
// if wrap is set, particles half out of bounds are rendered to the other side of the matrix
|
// if wrap is set, particles half out of bounds are rendered to the other side of the matrix
|
||||||
// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds
|
// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds
|
||||||
void ParticleSystem1D::render() {
|
void ParticleSystem1D::render() {
|
||||||
CRGB baseRGB;
|
if(framebuffer == nullptr) {
|
||||||
|
PSPRINTLN(F("PS render: no framebuffer!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CRGBW baseRGB;
|
||||||
uint32_t brightness; // particle brightness, fades if dying
|
uint32_t brightness; // particle brightness, fades if dying
|
||||||
TBlendType blend = LINEARBLEND; // default color rendering: wrap palette
|
TBlendType blend = LINEARBLEND; // default color rendering: wrap palette
|
||||||
if (particlesettings.colorByAge || particlesettings.colorByPosition) {
|
if (particlesettings.colorByAge || particlesettings.colorByPosition) {
|
||||||
blend = LINEARBLEND_NOWRAP;
|
blend = LINEARBLEND_NOWRAP;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ESP8266 // no local buffer on ESP8266
|
|
||||||
if (motionBlur)
|
|
||||||
SEGMENT.fadeToBlackBy(255 - motionBlur);
|
|
||||||
else
|
|
||||||
SEGMENT.fill(BLACK); // clear the buffer before rendering to it
|
|
||||||
#else
|
|
||||||
if (motionBlur) { // blurring active
|
if (motionBlur) { // blurring active
|
||||||
for (int32_t x = 0; x <= maxXpixel; x++) {
|
for (int32_t x = 0; x <= maxXpixel; x++) {
|
||||||
fast_color_scale(framebuffer[x], motionBlur);
|
framebuffer[x] = fast_color_scale(framebuffer[x], motionBlur);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { // no blurring: clear buffer
|
else { // no blurring: clear buffer
|
||||||
memset(framebuffer, 0, (maxXpixel+1) * sizeof(CRGB));
|
memset(framebuffer, 0, (maxXpixel+1) * sizeof(CRGBW));
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
// go over particles and render them to the buffer
|
// go over particles and render them to the buffer
|
||||||
for (uint32_t i = 0; i < usedParticles; i++) {
|
for (uint32_t i = 0; i < usedParticles; i++) {
|
||||||
if ( particles[i].ttl == 0 || particleFlags[i].outofbounds)
|
if ( particles[i].ttl == 0 || particleFlags[i].outofbounds)
|
||||||
@ -1469,48 +1453,39 @@ void ParticleSystem1D::render() {
|
|||||||
if (advPartProps) { //saturation is advanced property in 1D system
|
if (advPartProps) { //saturation is advanced property in 1D system
|
||||||
if (advPartProps[i].sat < 255) {
|
if (advPartProps[i].sat < 255) {
|
||||||
CHSV32 baseHSV;
|
CHSV32 baseHSV;
|
||||||
rgb2hsv((uint32_t((byte(baseRGB.r) << 16) | (byte(baseRGB.g) << 8) | (byte(baseRGB.b)))), baseHSV); // convert to HSV
|
rgb2hsv(baseRGB.color32, baseHSV); // convert to HSV
|
||||||
baseHSV.s = min(baseHSV.s, advPartProps[i].sat); // set the saturation but don't increase it
|
baseHSV.s = min(baseHSV.s, advPartProps[i].sat); // set the saturation but don't increase it
|
||||||
uint32_t tempcolor;
|
hsv2rgb(baseHSV, baseRGB.color32); // convert back to RGB
|
||||||
hsv2rgb(baseHSV, tempcolor); // convert back to RGB
|
|
||||||
baseRGB = (CRGB)tempcolor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
|
if(gammaCorrectCol) brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
|
||||||
renderParticle(i, brightness, baseRGB, particlesettings.wrap);
|
renderParticle(i, brightness, baseRGB, particlesettings.wrap);
|
||||||
}
|
}
|
||||||
// apply smear-blur to rendered frame
|
// apply smear-blur to rendered frame
|
||||||
if (smearBlur) {
|
if (smearBlur) {
|
||||||
#ifdef ESP8266
|
|
||||||
SEGMENT.blur(smearBlur, true); // no local buffer on ESP8266
|
|
||||||
#else
|
|
||||||
blur1D(framebuffer, maxXpixel + 1, smearBlur, 0);
|
blur1D(framebuffer, maxXpixel + 1, smearBlur, 0);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add background color
|
// add background color
|
||||||
uint32_t bg_color = SEGCOLOR(1);
|
CRGBW bg_color = SEGCOLOR(1);
|
||||||
if (bg_color > 0) { //if not black
|
if (bg_color > 0) { //if not black
|
||||||
CRGB bg_color_crgb = bg_color; // convert to CRGB
|
|
||||||
for (int32_t i = 0; i <= maxXpixel; i++) {
|
for (int32_t i = 0; i <= maxXpixel; i++) {
|
||||||
#ifdef ESP8266 // no local buffer on ESP8266
|
framebuffer[i] = fast_color_add(framebuffer[i], bg_color);
|
||||||
SEGMENT.addPixelColor(i, bg_color, true);
|
|
||||||
#else
|
|
||||||
fast_color_add(framebuffer[i], bg_color_crgb);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifndef WLED_DISABLE_2D
|
||||||
#ifndef ESP8266
|
// transfer local buffer to segment if using 1D->2D mapping
|
||||||
// transfer the frame-buffer to segment
|
if(SEGMENT.is2D() && SEGMENT.map1D2D) {
|
||||||
for (int x = 0; x <= maxXpixel; x++) {
|
for (int x = 0; x <= maxXpixel; x++) {
|
||||||
SEGMENT.setPixelColor(x, framebuffer[x]);
|
//for (int x = 0; x < SEGMENT.vLength(); x++) {
|
||||||
|
SEGMENT.setPixelColor(x, framebuffer[x]); // this applies the mapping
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
|
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
|
||||||
__attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap) {
|
__attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW &color, const bool wrap) {
|
||||||
uint32_t size = particlesize;
|
uint32_t size = particlesize;
|
||||||
if (advPartProps) // use advanced size properties (1D system has no large size global rendering TODO: add large global rendering?)
|
if (advPartProps) // use advanced size properties (1D system has no large size global rendering TODO: add large global rendering?)
|
||||||
size = advPartProps[particleindex].size;
|
size = advPartProps[particleindex].size;
|
||||||
@ -1518,11 +1493,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint
|
|||||||
if (size == 0) { //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles (and updating it uses more code)
|
if (size == 0) { //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles (and updating it uses more code)
|
||||||
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D;
|
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D;
|
||||||
if (x <= (uint32_t)maxXpixel) { //by making x unsigned there is no need to check < 0 as it will overflow
|
if (x <= (uint32_t)maxXpixel) { //by making x unsigned there is no need to check < 0 as it will overflow
|
||||||
#ifdef ESP8266 // no local buffer on ESP8266
|
framebuffer[x] = fast_color_add(framebuffer[x], color, brightness);
|
||||||
SEGMENT.addPixelColor(x, color.scale8(brightness), true);
|
|
||||||
#else
|
|
||||||
fast_color_add(framebuffer[x], color, brightness);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1548,18 +1519,19 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint
|
|||||||
// - scale brigthness with gamma correction (done in render())
|
// - scale brigthness with gamma correction (done in render())
|
||||||
// - apply inverse gamma correction to brightness values
|
// - apply inverse gamma correction to brightness values
|
||||||
// - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total
|
// - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total
|
||||||
pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma
|
if(gammaCorrectCol) {
|
||||||
pxlbrightness[1] = gamma8inv(pxlbrightness[1]);
|
pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma
|
||||||
|
pxlbrightness[1] = gamma8inv(pxlbrightness[1]);
|
||||||
|
}
|
||||||
// check if particle has advanced size properties and buffer is available
|
// check if particle has advanced size properties and buffer is available
|
||||||
if (advPartProps && advPartProps[particleindex].size > 1) {
|
if (advPartProps && advPartProps[particleindex].size > 1) {
|
||||||
CRGB renderbuffer[10]; // 10 pixel buffer
|
uint32_t renderbuffer[10]; // 10 pixel buffer
|
||||||
memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer
|
memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer
|
||||||
//render particle to a bigger size
|
//render particle to a bigger size
|
||||||
//particle size to pixels: 2 - 63 is 4 pixels, < 128 is 6pixels, < 192 is 8 pixels, bigger is 10 pixels
|
//particle size to pixels: 2 - 63 is 4 pixels, < 128 is 6pixels, < 192 is 8 pixels, bigger is 10 pixels
|
||||||
//first, render the pixel to the center of the renderbuffer, then apply 1D blurring
|
//first, render the pixel to the center of the renderbuffer, then apply 1D blurring
|
||||||
fast_color_add(renderbuffer[4], color, pxlbrightness[0]);
|
renderbuffer[4] = fast_color_add(renderbuffer[4], color, pxlbrightness[0]);
|
||||||
fast_color_add(renderbuffer[5], color, pxlbrightness[1]);
|
renderbuffer[5] = fast_color_add(renderbuffer[5], color, pxlbrightness[1]);
|
||||||
uint32_t rendersize = 2; // initialize render size, minimum is 4 pixels, it is incremented int he loop below to start with 4
|
uint32_t rendersize = 2; // initialize render size, minimum is 4 pixels, it is incremented int he loop below to start with 4
|
||||||
uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below)
|
uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below)
|
||||||
uint32_t blurpasses = size/64 + 1; // number of blur passes depends on size, four passes max
|
uint32_t blurpasses = size/64 + 1; // number of blur passes depends on size, four passes max
|
||||||
@ -1593,7 +1565,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint
|
|||||||
#ifdef ESP8266 // no local buffer on ESP8266
|
#ifdef ESP8266 // no local buffer on ESP8266
|
||||||
SEGMENT.addPixelColor(xfb, renderbuffer[xrb], true);
|
SEGMENT.addPixelColor(xfb, renderbuffer[xrb], true);
|
||||||
#else
|
#else
|
||||||
fast_color_add(framebuffer[xfb], renderbuffer[xrb]);
|
framebuffer[xfb] = fast_color_add(framebuffer[xfb], renderbuffer[xrb]);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1613,11 +1585,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint
|
|||||||
}
|
}
|
||||||
for (uint32_t i = 0; i < 2; i++) {
|
for (uint32_t i = 0; i < 2; i++) {
|
||||||
if (pxlisinframe[i]) {
|
if (pxlisinframe[i]) {
|
||||||
#ifdef ESP8266 // no local buffer on ESP8266
|
framebuffer[pixco[i]] = fast_color_add(framebuffer[pixco[i]], color, pxlbrightness[i]);
|
||||||
SEGMENT.addPixelColor(pixco[i], color.scale8((uint8_t)pxlbrightness[i]), true);
|
|
||||||
#else
|
|
||||||
fast_color_add(framebuffer[pixco[i]], color, pxlbrightness[i]);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1753,7 +1721,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::collideParticles(PSpartic
|
|||||||
// update size and pointers (memory location and size can change dynamically)
|
// update size and pointers (memory location and size can change dynamically)
|
||||||
// note: do not access the PS class in FX befor running this function (or it messes up SEGENV.data)
|
// note: do not access the PS class in FX befor running this function (or it messes up SEGENV.data)
|
||||||
void ParticleSystem1D::updateSystem(void) {
|
void ParticleSystem1D::updateSystem(void) {
|
||||||
setSize(SEGMENT.virtualLength()); // update size
|
setSize(SEGMENT.vLength()); // update size
|
||||||
updatePSpointers(advPartProps != nullptr);
|
updatePSpointers(advPartProps != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1768,15 +1736,16 @@ void ParticleSystem1D::updatePSpointers(bool isadvanced) {
|
|||||||
particles = reinterpret_cast<PSparticle1D *>(this + 1); // pointer to particles
|
particles = reinterpret_cast<PSparticle1D *>(this + 1); // pointer to particles
|
||||||
particleFlags = reinterpret_cast<PSparticleFlags1D *>(particles + numParticles); // pointer to particle flags
|
particleFlags = reinterpret_cast<PSparticleFlags1D *>(particles + numParticles); // pointer to particle flags
|
||||||
sources = reinterpret_cast<PSsource1D *>(particleFlags + numParticles); // pointer to source(s)
|
sources = reinterpret_cast<PSsource1D *>(particleFlags + numParticles); // pointer to source(s)
|
||||||
#ifdef ESP8266 // no local buffer on ESP8266
|
PSdataEnd = reinterpret_cast<uint8_t *>(sources + numSources); // pointer to first available byte after the PS for FX additional data (already aligned to 4 byte boundary)
|
||||||
PSdataEnd = reinterpret_cast<uint8_t *>(sources + numSources);
|
#ifndef WLED_DISABLE_2D
|
||||||
#else
|
if(SEGMENT.is2D() && SEGMENT.map1D2D) {
|
||||||
framebuffer = reinterpret_cast<CRGB *>(sources + numSources); // pointer to framebuffer
|
framebuffer = reinterpret_cast<uint32_t *>(sources + numSources); // use local framebuffer for 1D->2D mapping
|
||||||
// align pointer after framebuffer to 4bytes
|
PSdataEnd = reinterpret_cast<uint8_t *>(framebuffer + SEGMENT.maxMappingLength()); // pointer to first available byte after the PS for FX additional data (still aligned to 4 byte boundary)
|
||||||
uintptr_t p = reinterpret_cast<uintptr_t>(framebuffer + (maxXpixel+1)); // maxXpixel is SEGMENT.virtualLength() - 1
|
}
|
||||||
p = (p + 3) & ~0x03; // align to 4-byte boundary
|
else
|
||||||
PSdataEnd = reinterpret_cast<uint8_t *>(p); // pointer to first available byte after the PS for FX additional data
|
#endif
|
||||||
#endif
|
framebuffer = SEGMENT.getPixels(); // use segment buffer for standard 1D rendering
|
||||||
|
|
||||||
if (isadvanced) {
|
if (isadvanced) {
|
||||||
advPartProps = reinterpret_cast<PSadvancedParticle1D *>(PSdataEnd);
|
advPartProps = reinterpret_cast<PSadvancedParticle1D *>(PSdataEnd);
|
||||||
PSdataEnd = reinterpret_cast<uint8_t *>(advPartProps + numParticles); // since numParticles is a multiple of 4, this is always aligned to 4 bytes. No need to add padding bytes here
|
PSdataEnd = reinterpret_cast<uint8_t *>(advPartProps + numParticles); // since numParticles is a multiple of 4, this is always aligned to 4 bytes. No need to add padding bytes here
|
||||||
@ -1797,32 +1766,20 @@ void ParticleSystem1D::updatePSpointers(bool isadvanced) {
|
|||||||
//non class functions to use for initialization, fraction is uint8_t: 255 means 100%
|
//non class functions to use for initialization, fraction is uint8_t: 255 means 100%
|
||||||
uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced) {
|
uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced) {
|
||||||
uint32_t numberofParticles = SEGMENT.virtualLength(); // one particle per pixel (if possible)
|
uint32_t numberofParticles = SEGMENT.virtualLength(); // one particle per pixel (if possible)
|
||||||
#ifdef ESP8266
|
uint32_t particlelimit = MAXPARTICLES_1D; // maximum number of paticles allowed
|
||||||
uint32_t particlelimit = ESP8266_MAXPARTICLES_1D; // maximum number of paticles allowed
|
|
||||||
#elif ARDUINO_ARCH_ESP32S2
|
|
||||||
uint32_t particlelimit = ESP32S2_MAXPARTICLES_1D; // maximum number of paticles allowed
|
|
||||||
#else
|
|
||||||
uint32_t particlelimit = ESP32_MAXPARTICLES_1D; // maximum number of paticles allowed
|
|
||||||
#endif
|
|
||||||
numberofParticles = min(numberofParticles, particlelimit); // limit to particlelimit
|
numberofParticles = min(numberofParticles, particlelimit); // limit to particlelimit
|
||||||
if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount
|
if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount
|
||||||
numberofParticles = (numberofParticles * sizeof(PSparticle1D)) / (sizeof(PSparticle1D) + sizeof(PSadvancedParticle1D));
|
numberofParticles = (numberofParticles * sizeof(PSparticle1D)) / (sizeof(PSparticle1D) + sizeof(PSadvancedParticle1D));
|
||||||
numberofParticles = (numberofParticles * (fraction + 1)) >> 8; // calculate fraction of particles
|
numberofParticles = (numberofParticles * (fraction + 1)) >> 8; // calculate fraction of particles
|
||||||
numberofParticles = numberofParticles < 20 ? 20 : numberofParticles; // 20 minimum
|
numberofParticles = numberofParticles < 10 ? 10 : numberofParticles; // 10 minimum
|
||||||
//make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes)
|
//make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes)
|
||||||
numberofParticles = (numberofParticles+3) & ~0x03; // note: with a separate particle buffer, this is probably unnecessary
|
numberofParticles = (numberofParticles+3) & ~0x03; // note: with a separate particle buffer, this is probably unnecessary
|
||||||
PSPRINTLN(" calc numparticles:" + String(numberofParticles))
|
PSPRINTLN(" calc numparticles:" + String(numberofParticles));
|
||||||
return numberofParticles;
|
return numberofParticles;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t calculateNumberOfSources1D(const uint32_t requestedsources) {
|
uint32_t calculateNumberOfSources1D(const uint32_t requestedsources) {
|
||||||
#ifdef ESP8266
|
int numberofSources = max(1, min((int)requestedsources,MAXSOURCES_1D)); // limit
|
||||||
int numberofSources = max(1, min((int)requestedsources,ESP8266_MAXSOURCES_1D)); // limit
|
|
||||||
#elif ARDUINO_ARCH_ESP32S2
|
|
||||||
int numberofSources = max(1, min((int)requestedsources, ESP32S2_MAXSOURCES_1D)); // limit
|
|
||||||
#else
|
|
||||||
int numberofSources = max(1, min((int)requestedsources, ESP32_MAXSOURCES_1D)); // limit
|
|
||||||
#endif
|
|
||||||
// make sure it is a multiple of 4 for proper memory alignment (so minimum is acutally 4)
|
// make sure it is a multiple of 4 for proper memory alignment (so minimum is acutally 4)
|
||||||
numberofSources = (numberofSources+3) & ~0x03;
|
numberofSources = (numberofSources+3) & ~0x03;
|
||||||
return numberofSources;
|
return numberofSources;
|
||||||
@ -1835,10 +1792,11 @@ bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t
|
|||||||
requiredmemory += sizeof(PSparticleFlags1D) * numparticles;
|
requiredmemory += sizeof(PSparticleFlags1D) * numparticles;
|
||||||
requiredmemory += sizeof(PSparticle1D) * numparticles;
|
requiredmemory += sizeof(PSparticle1D) * numparticles;
|
||||||
requiredmemory += sizeof(PSsource1D) * numsources;
|
requiredmemory += sizeof(PSsource1D) * numsources;
|
||||||
#ifndef ESP8266 // no local buffer on ESP8266
|
#ifndef WLED_DISABLE_2D
|
||||||
requiredmemory += sizeof(CRGB) * SEGMENT.virtualLength();
|
if(SEGMENT.is2D())
|
||||||
#endif
|
requiredmemory += sizeof(uint32_t) * SEGMENT.maxMappingLength(); // need local buffer for mapped rendering
|
||||||
requiredmemory += additionalbytes + 3; // add 3 to ensure room for stuffing bytes to make it 4 byte aligned
|
#endif
|
||||||
|
requiredmemory += additionalbytes;
|
||||||
if (isadvanced)
|
if (isadvanced)
|
||||||
requiredmemory += sizeof(PSadvancedParticle1D) * numparticles;
|
requiredmemory += sizeof(PSadvancedParticle1D) * numparticles;
|
||||||
return(SEGMENT.allocateData(requiredmemory));
|
return(SEGMENT.allocateData(requiredmemory));
|
||||||
@ -1850,9 +1808,19 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedso
|
|||||||
if (SEGLEN == 1) return false; // single pixel not supported
|
if (SEGLEN == 1) return false; // single pixel not supported
|
||||||
uint32_t numparticles = calculateNumberOfParticles1D(fractionofparticles, advanced);
|
uint32_t numparticles = calculateNumberOfParticles1D(fractionofparticles, advanced);
|
||||||
uint32_t numsources = calculateNumberOfSources1D(requestedsources);
|
uint32_t numsources = calculateNumberOfSources1D(requestedsources);
|
||||||
if (!allocateParticleSystemMemory1D(numparticles, numsources, advanced, additionalbytes)) {
|
bool allocsuccess = false;
|
||||||
DEBUG_PRINT(F("PS init failed: memory depleted"));
|
while(numparticles >= 10) { // make sure we have at least 10 particles or quit
|
||||||
return false;
|
if (allocateParticleSystemMemory1D(numparticles, numsources, advanced, additionalbytes)) {
|
||||||
|
PSPRINT(F("PS 1D alloc succeeded"));
|
||||||
|
allocsuccess = true;
|
||||||
|
break; // allocation succeeded
|
||||||
|
}
|
||||||
|
numparticles = ((numparticles / 2) + 3) & ~0x03; // cut number of particles in half and try again, must be 4 byte aligned
|
||||||
|
PSPRINTLN(F("PS 1D alloc failed, trying with less particles..."));
|
||||||
|
}
|
||||||
|
if (!allocsuccess) {
|
||||||
|
PSPRINTLN(F("PS init failed: memory depleted"));
|
||||||
|
return false; // allocation failed
|
||||||
}
|
}
|
||||||
PartSys = new (SEGENV.data) ParticleSystem1D(SEGMENT.virtualLength(), numparticles, numsources, advanced); // particle system constructor
|
PartSys = new (SEGENV.data) ParticleSystem1D(SEGMENT.virtualLength(), numparticles, numsources, advanced); // particle system constructor
|
||||||
return true;
|
return true;
|
||||||
@ -1861,18 +1829,17 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedso
|
|||||||
// blur a 1D buffer, sub-size blurring can be done using start and size
|
// blur a 1D buffer, sub-size blurring can be done using start and size
|
||||||
// for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined
|
// for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined
|
||||||
// to blur a subset of the buffer, change the size and set start to the desired starting coordinates
|
// to blur a subset of the buffer, change the size and set start to the desired starting coordinates
|
||||||
void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start)
|
void blur1D(uint32_t *colorbuffer, uint32_t size, uint32_t blur, uint32_t start)
|
||||||
{
|
{
|
||||||
CRGB seeppart, carryover;
|
CRGBW seeppart, carryover;
|
||||||
uint32_t seep = blur >> 1;
|
uint32_t seep = blur >> 1;
|
||||||
carryover = BLACK;
|
carryover = BLACK;
|
||||||
for (uint32_t x = start; x < start + size; x++) {
|
for (uint32_t x = start; x < start + size; x++) {
|
||||||
seeppart = colorbuffer[x]; // create copy of current color
|
seeppart = fast_color_scale(colorbuffer[x], seep); // scale it and seep to neighbours
|
||||||
fast_color_scale(seeppart, seep); // scale it and seep to neighbours
|
|
||||||
if (x > 0) {
|
if (x > 0) {
|
||||||
fast_color_add(colorbuffer[x-1], seeppart);
|
colorbuffer[x-1] = fast_color_add(colorbuffer[x-1], seeppart);
|
||||||
if (carryover) // note: check adds overhead but is faster on average
|
if (carryover.color32) // note: check adds overhead but is faster on average
|
||||||
fast_color_add(colorbuffer[x], carryover); // is black on first pass
|
colorbuffer[x] = fast_color_add(colorbuffer[x], carryover); // is black on first pass
|
||||||
}
|
}
|
||||||
carryover = seeppart;
|
carryover = seeppart;
|
||||||
}
|
}
|
||||||
@ -1921,23 +1888,14 @@ static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32
|
|||||||
return true; // particle is in bounds
|
return true; // particle is in bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
// fastled color adding is very inaccurate in color preservation (but it is fast)
|
// this is a fast version for CRGBW color adding ignoring white channel (PS does not handle white) including scaling of second color
|
||||||
// a better color add function is implemented in colors.cpp but it uses 32bit RGBW. to use it colors need to be shifted just to then be shifted back by that function, which is slow
|
// note: function is mainly used to add scaled colors, so checking if one color is black is slower
|
||||||
// this is a fast version for RGB (no white channel, PS does not handle white) and with native CRGB including scaling of second color
|
// note2: returning CRGBW value is slightly slower as the return value gets written to uint32_t framebuffer
|
||||||
// note: result is stored in c1, not using a return value is faster as the CRGB struct does not need to be copied upon return
|
__attribute__((optimize("O2"))) static uint32_t fast_color_add(CRGBW c1, const CRGBW c2, const uint8_t scale) {
|
||||||
// note2: function is mainly used to add scaled colors, so checking if one color is black is slower
|
|
||||||
// note3: scale is 255 when using blur, checking for that makes blur faster
|
|
||||||
__attribute__((optimize("O2"))) static void fast_color_add(CRGB &c1, const CRGB &c2, const uint8_t scale) {
|
|
||||||
uint32_t r, g, b;
|
uint32_t r, g, b;
|
||||||
if (scale < 255) {
|
r = c1.r + ((c2.r * scale) >> 8);
|
||||||
r = c1.r + ((c2.r * scale) >> 8);
|
g = c1.g + ((c2.g * scale) >> 8);
|
||||||
g = c1.g + ((c2.g * scale) >> 8);
|
b = c1.b + ((c2.b * scale) >> 8);
|
||||||
b = c1.b + ((c2.b * scale) >> 8);
|
|
||||||
} else {
|
|
||||||
r = c1.r + c2.r;
|
|
||||||
g = c1.g + c2.g;
|
|
||||||
b = c1.b + c2.b;
|
|
||||||
}
|
|
||||||
|
|
||||||
// note: this chained comparison is the fastest method for max of 3 values (faster than std:max() or using xor)
|
// note: this chained comparison is the fastest method for max of 3 values (faster than std:max() or using xor)
|
||||||
uint32_t max = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b);
|
uint32_t max = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b);
|
||||||
@ -1951,13 +1909,15 @@ static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32
|
|||||||
c1.g = (g * newscale) >> 16;
|
c1.g = (g * newscale) >> 16;
|
||||||
c1.b = (b * newscale) >> 16;
|
c1.b = (b * newscale) >> 16;
|
||||||
}
|
}
|
||||||
|
return c1.color32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// faster than fastled color scaling as it does in place scaling
|
// fast CRGBW color scaling ignoring white channel (PS does not handle white)
|
||||||
__attribute__((optimize("O2"))) static void fast_color_scale(CRGB &c, const uint8_t scale) {
|
__attribute__((optimize("O2"))) static uint32_t fast_color_scale(CRGBW c, const uint8_t scale) {
|
||||||
c.r = ((c.r * scale) >> 8);
|
c.r = ((c.r * scale) >> 8);
|
||||||
c.g = ((c.g * scale) >> 8);
|
c.g = ((c.g * scale) >> 8);
|
||||||
c.b = ((c.b * scale) >> 8);
|
c.b = ((c.b * scale) >> 8);
|
||||||
|
return c.color32;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D))
|
#endif // !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D))
|
||||||
|
|||||||
@ -37,13 +37,20 @@ static inline int32_t limitSpeed(const int32_t speed) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
|
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
|
||||||
// memory allocation
|
// memory allocation (based on reasonable segment size and available FX memory)
|
||||||
#define ESP8266_MAXPARTICLES 256 // enough up to 16x16 pixels
|
#ifdef ESP8266
|
||||||
#define ESP8266_MAXSOURCES 24
|
#define MAXPARTICLES_2D 256
|
||||||
#define ESP32S2_MAXPARTICLES 1024 // enough up to 32x32 pixels
|
#define MAXSOURCES_2D 24
|
||||||
#define ESP32S2_MAXSOURCES 64
|
#define SOURCEREDUCTIONFACTOR 8
|
||||||
#define ESP32_MAXPARTICLES 2048 // enough up to 64x32 pixels
|
#elif ARDUINO_ARCH_ESP32S2
|
||||||
#define ESP32_MAXSOURCES 128
|
#define MAXPARTICLES_2D 1024
|
||||||
|
#define MAXSOURCES_2D 64
|
||||||
|
#define SOURCEREDUCTIONFACTOR 6
|
||||||
|
#else
|
||||||
|
#define MAXPARTICLES_2D 2048
|
||||||
|
#define MAXSOURCES_2D 128
|
||||||
|
#define SOURCEREDUCTIONFACTOR 4
|
||||||
|
#endif
|
||||||
|
|
||||||
// particle dimensions (subpixel division)
|
// particle dimensions (subpixel division)
|
||||||
#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement (must be a power of 2)
|
#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement (must be a power of 2)
|
||||||
@ -188,7 +195,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
//rendering functions
|
//rendering functions
|
||||||
void render();
|
void render();
|
||||||
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY);
|
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW& color, const bool wrapX, const bool wrapY);
|
||||||
//paricle physics applied by system if flags are set
|
//paricle physics applied by system if flags are set
|
||||||
void applyGravity(); // applies gravity to all particles
|
void applyGravity(); // applies gravity to all particles
|
||||||
void handleCollisions();
|
void handleCollisions();
|
||||||
@ -200,13 +207,13 @@ private:
|
|||||||
void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize);
|
void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize);
|
||||||
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
|
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
|
||||||
// note: variables that are accessed often are 32bit for speed
|
// note: variables that are accessed often are 32bit for speed
|
||||||
CRGB *framebuffer; // local frame buffer for rendering
|
uint32_t *framebuffer; // frame buffer for rendering. note: using CRGBW as the buffer is slower, ESP compiler seems to optimize this better giving more consistent FPS
|
||||||
PSsettings2D particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above
|
PSsettings2D particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above
|
||||||
uint32_t numParticles; // total number of particles allocated by this system
|
uint32_t numParticles; // total number of particles allocated by this system
|
||||||
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
|
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
|
||||||
int32_t collisionHardness;
|
int32_t collisionHardness;
|
||||||
uint32_t wallHardness;
|
uint32_t wallHardness;
|
||||||
uint32_t wallRoughness; // randomizes wall collisions
|
uint32_t wallRoughness; // randomizes wall collisions
|
||||||
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed)
|
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed)
|
||||||
uint16_t collisionStartIdx; // particle array start index for collision detection
|
uint16_t collisionStartIdx; // particle array start index for collision detection
|
||||||
uint8_t fireIntesity = 0; // fire intensity, used for fire mode (flash use optimization, better than passing an argument to render function)
|
uint8_t fireIntesity = 0; // fire intensity, used for fire mode (flash use optimization, better than passing an argument to render function)
|
||||||
@ -219,7 +226,7 @@ private:
|
|||||||
uint8_t smearBlur; // 2D smeared blurring of full frame
|
uint8_t smearBlur; // 2D smeared blurring of full frame
|
||||||
};
|
};
|
||||||
|
|
||||||
void blur2D(CRGB *colorbuffer, const uint32_t xsize, uint32_t ysize, const uint32_t xblur, const uint32_t yblur, const uint32_t xstart = 0, uint32_t ystart = 0, const bool isparticle = false);
|
void blur2D(uint32_t *colorbuffer, const uint32_t xsize, uint32_t ysize, const uint32_t xblur, const uint32_t yblur, const uint32_t xstart = 0, uint32_t ystart = 0, const bool isparticle = false);
|
||||||
// initialization functions (not part of class)
|
// initialization functions (not part of class)
|
||||||
bool initParticleSystem2D(ParticleSystem2D *&PartSys, const uint32_t requestedsources, const uint32_t additionalbytes = 0, const bool advanced = false, const bool sizecontrol = false);
|
bool initParticleSystem2D(ParticleSystem2D *&PartSys, const uint32_t requestedsources, const uint32_t additionalbytes = 0, const bool advanced = false, const bool sizecontrol = false);
|
||||||
uint32_t calculateNumberOfParticles2D(const uint32_t pixels, const bool advanced, const bool sizecontrol);
|
uint32_t calculateNumberOfParticles2D(const uint32_t pixels, const bool advanced, const bool sizecontrol);
|
||||||
@ -232,12 +239,18 @@ bool allocateParticleSystemMemory2D(const uint32_t numparticles, const uint32_t
|
|||||||
////////////////////////
|
////////////////////////
|
||||||
#ifndef WLED_DISABLE_PARTICLESYSTEM1D
|
#ifndef WLED_DISABLE_PARTICLESYSTEM1D
|
||||||
// memory allocation
|
// memory allocation
|
||||||
#define ESP8266_MAXPARTICLES_1D 320
|
#ifdef ESP8266
|
||||||
#define ESP8266_MAXSOURCES_1D 16
|
#define MAXPARTICLES_1D 320
|
||||||
#define ESP32S2_MAXPARTICLES_1D 1300
|
#define MAXSOURCES_1D 16
|
||||||
#define ESP32S2_MAXSOURCES_1D 32
|
#elif ARDUINO_ARCH_ESP32S2
|
||||||
#define ESP32_MAXPARTICLES_1D 2600
|
#define MAXPARTICLES_1D 1300
|
||||||
#define ESP32_MAXSOURCES_1D 64
|
#define MAXSOURCES_1D 32
|
||||||
|
#else
|
||||||
|
#define MAXPARTICLES_1D 2600
|
||||||
|
#define MAXSOURCES_1D 64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// particle dimensions (subpixel division)
|
// particle dimensions (subpixel division)
|
||||||
#define PS_P_RADIUS_1D 32 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines)
|
#define PS_P_RADIUS_1D 32 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines)
|
||||||
@ -351,7 +364,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
//rendering functions
|
//rendering functions
|
||||||
void render(void);
|
void render(void);
|
||||||
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap);
|
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW &color, const bool wrap);
|
||||||
|
|
||||||
//paricle physics applied by system if flags are set
|
//paricle physics applied by system if flags are set
|
||||||
void applyGravity(); // applies gravity to all particles
|
void applyGravity(); // applies gravity to all particles
|
||||||
@ -363,9 +376,7 @@ private:
|
|||||||
//void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
|
//void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
|
||||||
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
|
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
|
||||||
// note: variables that are accessed often are 32bit for speed
|
// note: variables that are accessed often are 32bit for speed
|
||||||
#ifndef ESP8266
|
uint32_t *framebuffer; // frame buffer for rendering. note: using CRGBW as the buffer is slower, ESP compiler seems to optimize this better giving more consistent FPS
|
||||||
CRGB *framebuffer; // local frame buffer for rendering
|
|
||||||
#endif
|
|
||||||
PSsettings1D particlesettings; // settings used when updating particles
|
PSsettings1D particlesettings; // settings used when updating particles
|
||||||
uint32_t numParticles; // total number of particles allocated by this system
|
uint32_t numParticles; // total number of particles allocated by this system
|
||||||
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
|
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
|
||||||
@ -386,5 +397,5 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedso
|
|||||||
uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced);
|
uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced);
|
||||||
uint32_t calculateNumberOfSources1D(const uint32_t requestedsources);
|
uint32_t calculateNumberOfSources1D(const uint32_t requestedsources);
|
||||||
bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t numsources, const bool isadvanced, const uint32_t additionalbytes);
|
bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t numsources, const bool isadvanced, const uint32_t additionalbytes);
|
||||||
void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start);
|
void blur1D(uint32_t *colorbuffer, uint32_t size, uint32_t blur, uint32_t start);
|
||||||
#endif // WLED_DISABLE_PARTICLESYSTEM1D
|
#endif // WLED_DISABLE_PARTICLESYSTEM1D
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <IPAddress.h>
|
#include <IPAddress.h>
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#include "src/dependencies/network/Network.h" // for isConnected() (& WiFi)
|
||||||
#include "driver/ledc.h"
|
#include "driver/ledc.h"
|
||||||
#include "soc/ledc_struct.h"
|
#include "soc/ledc_struct.h"
|
||||||
#if !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3))
|
#if !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3))
|
||||||
@ -20,21 +22,24 @@
|
|||||||
#include "core_esp8266_waveform.h"
|
#include "core_esp8266_waveform.h"
|
||||||
#endif
|
#endif
|
||||||
#include "const.h"
|
#include "const.h"
|
||||||
|
#include "colors.h"
|
||||||
#include "pin_manager.h"
|
#include "pin_manager.h"
|
||||||
#include "bus_manager.h"
|
#include "bus_manager.h"
|
||||||
#include "bus_wrapper.h"
|
#include "bus_wrapper.h"
|
||||||
#include <bits/unique_ptr.h>
|
#include <bits/unique_ptr.h>
|
||||||
|
|
||||||
|
extern char cmDNS[];
|
||||||
|
extern bool cctICused;
|
||||||
|
extern bool useParallelI2S;
|
||||||
|
|
||||||
// functions to get/set bits in an array - based on functions created by Brandon for GOL
|
// functions to get/set bits in an array - based on functions created by Brandon for GOL
|
||||||
// toDo : make this a class that's completely defined in a header file
|
// toDo : make this a class that's completely defined in a header file
|
||||||
bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value
|
bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value
|
||||||
size_t byteIndex = position / 8;
|
size_t byteIndex = position / 8;
|
||||||
unsigned bitIndex = position % 8;
|
unsigned bitIndex = position % 8;
|
||||||
uint8_t byteValue = byteArray[byteIndex];
|
uint8_t byteValue = byteArray[byteIndex];
|
||||||
return (byteValue >> bitIndex) & 1;
|
return (byteValue >> bitIndex) & 1;
|
||||||
}
|
}
|
||||||
extern bool cctICused;
|
|
||||||
extern bool useParallelI2S;
|
|
||||||
|
|
||||||
void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr
|
void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr
|
||||||
//if (byteArray == nullptr) return;
|
//if (byteArray == nullptr) return;
|
||||||
@ -65,25 +70,32 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const
|
|||||||
|
|
||||||
//util.cpp
|
//util.cpp
|
||||||
// PSRAM allocation wrappers
|
// PSRAM allocation wrappers
|
||||||
#ifndef ESP8266
|
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
extern "C" {
|
extern "C" {
|
||||||
void *p_malloc(size_t); // prefer PSRAM over DRAM
|
void *p_malloc(size_t); // prefer PSRAM over DRAM
|
||||||
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
|
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
|
||||||
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
|
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
|
||||||
|
void *p_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer PSRAM over DRAM
|
||||||
inline void p_free(void *ptr) { heap_caps_free(ptr); }
|
inline void p_free(void *ptr) { heap_caps_free(ptr); }
|
||||||
void *d_malloc(size_t); // prefer DRAM over PSRAM
|
void *d_malloc(size_t); // prefer DRAM over PSRAM
|
||||||
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
|
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
|
||||||
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
|
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
|
||||||
|
void *d_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer DRAM over PSRAM
|
||||||
inline void d_free(void *ptr) { heap_caps_free(ptr); }
|
inline void d_free(void *ptr) { heap_caps_free(ptr); }
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
extern "C" {
|
||||||
|
void *realloc_malloc(void *ptr, size_t size);
|
||||||
|
}
|
||||||
#define p_malloc malloc
|
#define p_malloc malloc
|
||||||
#define p_calloc calloc
|
#define p_calloc calloc
|
||||||
#define p_realloc realloc
|
#define p_realloc realloc
|
||||||
|
#define p_realloc_malloc realloc_malloc
|
||||||
#define p_free free
|
#define p_free free
|
||||||
#define d_malloc malloc
|
#define d_malloc malloc
|
||||||
#define d_calloc calloc
|
#define d_calloc calloc
|
||||||
#define d_realloc realloc
|
#define d_realloc realloc
|
||||||
|
#define d_realloc_malloc realloc_malloc
|
||||||
#define d_free free
|
#define d_free free
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -124,7 +136,7 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) {
|
|||||||
} else {
|
} else {
|
||||||
cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format
|
cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format
|
||||||
}
|
}
|
||||||
|
|
||||||
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
|
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
|
||||||
if (cct < _cctBlend) ww = 255;
|
if (cct < _cctBlend) ww = 255;
|
||||||
else ww = ((255-cct) * 255) / (255 - _cctBlend);
|
else ww = ((255-cct) * 255) / (255 - _cctBlend);
|
||||||
@ -163,6 +175,7 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
|
|||||||
if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; }
|
if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; }
|
||||||
if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; }
|
if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; }
|
||||||
_frequencykHz = 0U;
|
_frequencykHz = 0U;
|
||||||
|
_colorSum = 0;
|
||||||
_pins[0] = bc.pins[0];
|
_pins[0] = bc.pins[0];
|
||||||
if (is2Pin(bc.type)) {
|
if (is2Pin(bc.type)) {
|
||||||
if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) {
|
if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) {
|
||||||
@ -182,6 +195,10 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
|
|||||||
if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus
|
if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus
|
||||||
_busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr);
|
_busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr);
|
||||||
_valid = (_busPtr != nullptr) && bc.count > 0;
|
_valid = (_busPtr != nullptr) && bc.count > 0;
|
||||||
|
// fix for wled#4759
|
||||||
|
if (_valid) for (unsigned i = 0; i < _skip; i++) {
|
||||||
|
PolyBus::setPixelColor(_busPtr, _iType, i, 0, COL_ORDER_GRB); // set sacrificial pixels to black (CO does not matter here)
|
||||||
|
}
|
||||||
DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"),
|
DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"),
|
||||||
_valid?"S":"Uns",
|
_valid?"S":"Uns",
|
||||||
(int)nr,
|
(int)nr,
|
||||||
@ -201,80 +218,62 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
|
|||||||
//Stay safe with high amperage and have a reasonable safety margin!
|
//Stay safe with high amperage and have a reasonable safety margin!
|
||||||
//I am NOT to be held liable for burned down garages or houses!
|
//I am NOT to be held liable for burned down garages or houses!
|
||||||
|
|
||||||
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
|
// note on ABL implementation:
|
||||||
uint8_t BusDigital::estimateCurrentAndLimitBri() const {
|
// ABL is set up in finalizeInit()
|
||||||
bool useWackyWS2815PowerModel = false;
|
// scaled color channels are summed in BusDigital::setPixelColor()
|
||||||
byte actualMilliampsPerLed = _milliAmpsPerLed;
|
// the used current is estimated and limited in BusManager::show()
|
||||||
|
// if limit is set too low, brightness is limited to 1 to at least show some light
|
||||||
if (_milliAmpsMax < MA_FOR_ESP/BusManager::getNumBusses() || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation
|
// to disable brightness limiter for a bus, set LED current to 0
|
||||||
return _bri;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void BusDigital::estimateCurrent() {
|
||||||
|
uint32_t actualMilliampsPerLed = _milliAmpsPerLed;
|
||||||
if (_milliAmpsPerLed == 255) {
|
if (_milliAmpsPerLed == 255) {
|
||||||
useWackyWS2815PowerModel = true;
|
// use wacky WS2815 power model, see WLED issue #549
|
||||||
|
_colorSum *= 3; // sum is sum of max value for each color, need to multiply by three to account for clrUnitsPerChannel being 3*255
|
||||||
actualMilliampsPerLed = 12; // from testing an actual strip
|
actualMilliampsPerLed = 12; // from testing an actual strip
|
||||||
}
|
}
|
||||||
|
// _colorSum has all the values of color channels summed, max would be getLength()*(3*255 + (255 if hasWhite()): convert to milliAmps
|
||||||
|
uint32_t clrUnitsPerChannel = hasWhite() ? 4*255 : 3*255;
|
||||||
|
_milliAmpsTotal = ((uint64_t)_colorSum * actualMilliampsPerLed) / clrUnitsPerChannel + getLength(); // add 1mA standby current per LED to total (WS2812: ~0.7mA, WS2815: ~2mA)
|
||||||
|
}
|
||||||
|
|
||||||
unsigned powerBudget = (_milliAmpsMax - MA_FOR_ESP/BusManager::getNumBusses()); //80/120mA for ESP power
|
void BusDigital::applyBriLimit(uint8_t newBri) {
|
||||||
if (powerBudget > getLength()) { //each LED uses about 1mA in standby, exclude that from power budget
|
// a newBri of 0 means calculate per-bus brightness limit
|
||||||
powerBudget -= getLength();
|
if (newBri == 0) {
|
||||||
} else {
|
if (_milliAmpsLimit == 0 || _milliAmpsTotal == 0) return; // ABL not used for this bus
|
||||||
powerBudget = 0;
|
newBri = 255;
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t busPowerSum = 0;
|
if (_milliAmpsLimit > getLength()) { // each LED uses about 1mA in standby
|
||||||
for (unsigned i = 0; i < getLength(); i++) { //sum up the usage of each LED
|
if (_milliAmpsTotal > _milliAmpsLimit) {
|
||||||
uint32_t c = getPixelColor(i); // always returns original or restored color without brightness scaling
|
// scale brightness down to stay in current limit
|
||||||
byte r = R(c), g = G(c), b = B(c), w = W(c);
|
newBri = ((uint32_t)_milliAmpsLimit * 255) / _milliAmpsTotal + 1; // +1 to avoid 0 brightness
|
||||||
|
_milliAmpsTotal = _milliAmpsLimit;
|
||||||
if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
|
}
|
||||||
busPowerSum += (max(max(r,g),b)) * 3;
|
|
||||||
} else {
|
} else {
|
||||||
busPowerSum += (r + g + b + w);
|
newBri = 1; // limit too low, set brightness to 1, this will dim down all colors to minimum since we use video scaling
|
||||||
|
_milliAmpsTotal = getLength(); // estimate bus current as minimum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
|
if (newBri < 255) {
|
||||||
busPowerSum *= 3;
|
uint8_t cctWW = 0, cctCW = 0;
|
||||||
busPowerSum >>= 2; //same as /= 4
|
unsigned hwLen = _len;
|
||||||
|
if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus
|
||||||
|
for (unsigned i = 0; i < hwLen; i++) {
|
||||||
|
uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); // need to revert color order for correct color scaling and CCT calc in case white is swapped
|
||||||
|
uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, i, co);
|
||||||
|
c = color_fade(c, newBri, true); // apply additional dimming note: using inline version is a bit faster but overhead of getPixelColor() dominates the speed impact by far
|
||||||
|
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW);
|
||||||
|
PolyBus::setPixelColor(_busPtr, _iType, i, c, co, (cctCW<<8) | cctWW); // repaint all pixels with new brightness
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps
|
_colorSum = 0; // reset for next frame
|
||||||
BusDigital::_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * _bri) / (765*255);
|
|
||||||
|
|
||||||
uint8_t newBri = _bri;
|
|
||||||
if (BusDigital::_milliAmpsTotal > powerBudget) {
|
|
||||||
//scale brightness down to stay in current limit
|
|
||||||
unsigned scaleB = powerBudget * 255 / BusDigital::_milliAmpsTotal;
|
|
||||||
newBri = (_bri * scaleB) / 256 + 1;
|
|
||||||
BusDigital::_milliAmpsTotal = powerBudget;
|
|
||||||
//_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * newBri) / (765*255);
|
|
||||||
}
|
|
||||||
return newBri;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BusDigital::show() {
|
void BusDigital::show() {
|
||||||
BusDigital::_milliAmpsTotal = 0;
|
|
||||||
if (!_valid) return;
|
if (!_valid) return;
|
||||||
|
PolyBus::show(_busPtr, _iType, _skip); // faster if buffer consistency is not important (no skipped LEDs)
|
||||||
uint8_t cctWW = 0, cctCW = 0;
|
|
||||||
unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal (TODO: could use PolyBus::CalcTotalMilliAmpere())
|
|
||||||
if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits
|
|
||||||
if (newBri < _bri) {
|
|
||||||
unsigned hwLen = _len;
|
|
||||||
if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus
|
|
||||||
for (unsigned i = 0; i < hwLen; i++) {
|
|
||||||
// use 0 as color order, actual order does not matter here as we just update the channel values as-is
|
|
||||||
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri);
|
|
||||||
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); // this will unfortunately corrupt (segment) CCT data on every bus
|
|
||||||
PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PolyBus::show(_busPtr, _iType, false); // faster if buffer consistency is not important
|
|
||||||
// restore bus brightness to its original value
|
|
||||||
// this is done right after show, so this is only OK if LED updates are completed before show() returns
|
|
||||||
// or async show has a separate buffer (ESP32 RMT and I2S are ok)
|
|
||||||
if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, _bri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BusDigital::canShow() const {
|
bool BusDigital::canShow() const {
|
||||||
@ -282,12 +281,6 @@ bool BusDigital::canShow() const {
|
|||||||
return PolyBus::canShow(_busPtr, _iType);
|
return PolyBus::canShow(_busPtr, _iType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BusDigital::setBrightness(uint8_t b) {
|
|
||||||
if (_bri == b) return;
|
|
||||||
Bus::setBrightness(b);
|
|
||||||
PolyBus::setBrightness(_busPtr, _iType, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
//If LEDs are skipped, it is possible to use the first as a status LED.
|
//If LEDs are skipped, it is possible to use the first as a status LED.
|
||||||
//TODO only show if no new show due in the next 50ms
|
//TODO only show if no new show due in the next 50ms
|
||||||
void BusDigital::setStatusPixel(uint32_t c) {
|
void BusDigital::setStatusPixel(uint32_t c) {
|
||||||
@ -301,13 +294,25 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
|
|||||||
if (!_valid) return;
|
if (!_valid) return;
|
||||||
if (hasWhite()) c = autoWhiteCalc(c);
|
if (hasWhite()) c = autoWhiteCalc(c);
|
||||||
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
|
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
|
||||||
|
c = color_fade(c, _bri, true); // apply brightness
|
||||||
|
|
||||||
|
if (BusManager::_useABL) {
|
||||||
|
// if using ABL, sum all color channels to estimate current and limit brightness in show()
|
||||||
|
uint8_t r = R(c), g = G(c), b = B(c);
|
||||||
|
if (_milliAmpsPerLed < 255) { // normal ABL
|
||||||
|
_colorSum += r + g + b + W(c);
|
||||||
|
} else { // wacky WS2815 power model, ignore white channel, use max of RGB (issue #549)
|
||||||
|
_colorSum += ((r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_reversed) pix = _len - pix -1;
|
if (_reversed) pix = _len - pix -1;
|
||||||
pix += _skip;
|
pix += _skip;
|
||||||
unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
|
const uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
|
||||||
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
|
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
|
||||||
unsigned pOld = pix;
|
unsigned pOld = pix;
|
||||||
pix = IC_INDEX_WS2812_1CH_3X(pix);
|
pix = IC_INDEX_WS2812_1CH_3X(pix);
|
||||||
uint32_t cOld = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, pix, co),_bri);
|
uint32_t cOld = PolyBus::getPixelColor(_busPtr, _iType, pix, co);
|
||||||
switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set)
|
switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set)
|
||||||
case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break;
|
case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break;
|
||||||
case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break;
|
case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break;
|
||||||
@ -324,17 +329,17 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
|
|||||||
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
|
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns original color if global buffering is enabled, else returns lossly restored color from bus
|
// returns lossly restored color from bus
|
||||||
uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
|
uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
|
||||||
if (!_valid) return 0;
|
if (!_valid) return 0;
|
||||||
if (_reversed) pix = _len - pix -1;
|
if (_reversed) pix = _len - pix -1;
|
||||||
pix += _skip;
|
pix += _skip;
|
||||||
const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
|
const uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
|
||||||
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri);
|
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri);
|
||||||
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
|
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
|
||||||
unsigned r = R(c);
|
uint8_t r = R(c);
|
||||||
unsigned g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed?
|
uint8_t g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed?
|
||||||
unsigned b = _reversed ? G(c) : B(c);
|
uint8_t b = _reversed ? G(c) : B(c);
|
||||||
switch (pix % 3) { // get only the single channel
|
switch (pix % 3) { // get only the single channel
|
||||||
case 0: c = RGBW32(g, g, g, g); break;
|
case 0: c = RGBW32(g, g, g, g); break;
|
||||||
case 1: c = RGBW32(r, r, r, r); break;
|
case 1: c = RGBW32(r, r, r, r); break;
|
||||||
@ -355,7 +360,7 @@ size_t BusDigital::getPins(uint8_t* pinArray) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t BusDigital::getBusSize() const {
|
size_t BusDigital::getBusSize() const {
|
||||||
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) /*+ (_data ? _len * getNumberOfChannels() : 0)*/ : 0);
|
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BusDigital::setColorOrder(uint8_t colorOrder) {
|
void BusDigital::setColorOrder(uint8_t colorOrder) {
|
||||||
@ -486,10 +491,7 @@ void BusPwm::setPixelColor(unsigned pix, uint32_t c) {
|
|||||||
if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
|
if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
|
||||||
c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
|
c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
|
||||||
}
|
}
|
||||||
uint8_t r = R(c);
|
uint8_t r = R(c), g = G(c), b = B(c), w = W(c);
|
||||||
uint8_t g = G(c);
|
|
||||||
uint8_t b = B(c);
|
|
||||||
uint8_t w = W(c);
|
|
||||||
|
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
|
case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
|
||||||
@ -664,10 +666,7 @@ BusOnOff::BusOnOff(const BusConfig &bc)
|
|||||||
void BusOnOff::setPixelColor(unsigned pix, uint32_t c) {
|
void BusOnOff::setPixelColor(unsigned pix, uint32_t c) {
|
||||||
if (pix != 0 || !_valid) return; //only react to first pixel
|
if (pix != 0 || !_valid) return; //only react to first pixel
|
||||||
c = autoWhiteCalc(c);
|
c = autoWhiteCalc(c);
|
||||||
uint8_t r = R(c);
|
uint8_t r = R(c), g = G(c), b = B(c), w = W(c);
|
||||||
uint8_t g = G(c);
|
|
||||||
uint8_t b = B(c);
|
|
||||||
uint8_t w = W(c);
|
|
||||||
_data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
|
_data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,6 +716,10 @@ BusNetwork::BusNetwork(const BusConfig &bc)
|
|||||||
_hasCCT = false;
|
_hasCCT = false;
|
||||||
_UDPchannels = _hasWhite + 3;
|
_UDPchannels = _hasWhite + 3;
|
||||||
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
|
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
_hostname = bc.text;
|
||||||
|
resolveHostname(); // resolve hostname to IP address if needed
|
||||||
|
#endif
|
||||||
_data = (uint8_t*)d_calloc(_len, _UDPchannels);
|
_data = (uint8_t*)d_calloc(_len, _UDPchannels);
|
||||||
_valid = (_data != nullptr);
|
_valid = (_data != nullptr);
|
||||||
DEBUGBUS_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]);
|
DEBUGBUS_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]);
|
||||||
@ -751,6 +754,19 @@ size_t BusNetwork::getPins(uint8_t* pinArray) const {
|
|||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
void BusNetwork::resolveHostname() {
|
||||||
|
static unsigned long nextResolve = 0;
|
||||||
|
if (Network.isConnected() && millis() > nextResolve && _hostname.length() > 0) {
|
||||||
|
nextResolve = millis() + 600000; // resolve only every 10 minutes
|
||||||
|
IPAddress clnt;
|
||||||
|
if (strlen(cmDNS) > 0) clnt = MDNS.queryHost(_hostname);
|
||||||
|
else WiFi.hostByName(_hostname.c_str(), clnt);
|
||||||
|
if (clnt != IPAddress()) _client = clnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
|
// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
|
||||||
std::vector<LEDType> BusNetwork::getLEDTypes() {
|
std::vector<LEDType> BusNetwork::getLEDTypes() {
|
||||||
return {
|
return {
|
||||||
@ -1288,6 +1304,13 @@ void BusManager::on() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
for (auto &bus : busses) if (bus->isVirtual()) {
|
||||||
|
// virtual/network bus should check for IP change if hostname is specified
|
||||||
|
// otherwise there are no endpoints to force DNS resolution
|
||||||
|
BusNetwork &b = static_cast<BusNetwork&>(*bus);
|
||||||
|
b.resolveHostname();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef ESP32_DATA_IDLE_HIGH
|
#ifdef ESP32_DATA_IDLE_HIGH
|
||||||
esp32RMTInvertIdle();
|
esp32RMTInvertIdle();
|
||||||
@ -1307,13 +1330,13 @@ void BusManager::off() {
|
|||||||
#ifdef ESP32_DATA_IDLE_HIGH
|
#ifdef ESP32_DATA_IDLE_HIGH
|
||||||
esp32RMTInvertIdle();
|
esp32RMTInvertIdle();
|
||||||
#endif
|
#endif
|
||||||
|
_gMilliAmpsUsed = 0; // reset, assume no LED idle current if relay is off
|
||||||
}
|
}
|
||||||
|
|
||||||
void BusManager::show() {
|
void BusManager::show() {
|
||||||
_gMilliAmpsUsed = 0;
|
applyABL(); // apply brightness limit, updates _gMilliAmpsUsed
|
||||||
for (auto &bus : busses) {
|
for (auto &bus : busses) {
|
||||||
bus->show();
|
bus->show();
|
||||||
_gMilliAmpsUsed += bus->getUsedCurrent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1346,6 +1369,85 @@ bool BusManager::canAllShow() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BusManager::initializeABL() {
|
||||||
|
_useABL = false; // reset
|
||||||
|
if (_gMilliAmpsMax > 0) {
|
||||||
|
// check global brightness limit
|
||||||
|
for (auto &bus : busses) {
|
||||||
|
if (bus->isDigital() && bus->getLEDCurrent() > 0) {
|
||||||
|
_useABL = true; // at least one bus has valid LED current
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// check per bus brightness limit
|
||||||
|
unsigned numABLbuses = 0;
|
||||||
|
for (auto &bus : busses) {
|
||||||
|
if (bus->isDigital() && bus->getLEDCurrent() > 0 && bus->getMaxCurrent() > 0)
|
||||||
|
numABLbuses++; // count ABL enabled buses
|
||||||
|
}
|
||||||
|
if (numABLbuses > 0) {
|
||||||
|
_useABL = true; // at least one bus has ABL set
|
||||||
|
uint32_t ESPshare = MA_FOR_ESP / numABLbuses; // share of ESP current per ABL bus
|
||||||
|
for (auto &bus : busses) {
|
||||||
|
if (bus->isDigital()) {
|
||||||
|
BusDigital &busd = static_cast<BusDigital&>(*bus);
|
||||||
|
uint32_t busLength = busd.getLength();
|
||||||
|
uint32_t busDemand = busLength * busd.getLEDCurrent();
|
||||||
|
uint32_t busMax = busd.getMaxCurrent();
|
||||||
|
if (busMax > ESPshare) busMax -= ESPshare;
|
||||||
|
if (busMax < busLength) busMax = busLength; // give each LED 1mA, ABL will dim down to minimum
|
||||||
|
if (busDemand == 0) busMax = 0; // no LED current set, disable ABL for this bus
|
||||||
|
busd.setCurrentLimit(busMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BusManager::applyABL() {
|
||||||
|
if (_useABL) {
|
||||||
|
unsigned milliAmpsSum = 0; // use temporary variable to always return a valid _gMilliAmpsUsed to UI
|
||||||
|
unsigned totalLEDs = 0;
|
||||||
|
for (auto &bus : busses) {
|
||||||
|
if (bus->isDigital() && bus->isOk()) {
|
||||||
|
BusDigital &busd = static_cast<BusDigital&>(*bus);
|
||||||
|
busd.estimateCurrent(); // sets _milliAmpsTotal, current is estimated for all buses even if they have the limit set to 0
|
||||||
|
if (_gMilliAmpsMax == 0)
|
||||||
|
busd.applyBriLimit(0); // apply per bus ABL limit, updates _milliAmpsTotal if limit reached
|
||||||
|
milliAmpsSum += busd.getUsedCurrent();
|
||||||
|
totalLEDs += busd.getLength(); // sum total number of LEDs for global Limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check global current limit and apply global ABL limit, total current is summed above
|
||||||
|
if (_gMilliAmpsMax > 0) {
|
||||||
|
uint8_t newBri = 255;
|
||||||
|
uint32_t globalMax = _gMilliAmpsMax > MA_FOR_ESP ? _gMilliAmpsMax - MA_FOR_ESP : 1; // subtract ESP current consumption, fully limit if too low
|
||||||
|
if (globalMax > totalLEDs) { // check if budget is larger than standby current
|
||||||
|
if (milliAmpsSum > globalMax) {
|
||||||
|
newBri = globalMax * 255 / milliAmpsSum + 1; // scale brightness down to stay in current limit, +1 to avoid 0 brightness
|
||||||
|
milliAmpsSum = globalMax; // update total used current
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newBri = 1; // limit too low, set brightness to minimum
|
||||||
|
milliAmpsSum = totalLEDs; // estimate total used current as minimum
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply brightness limit to each bus, if its 255 it will only reset _colorSum
|
||||||
|
for (auto &bus : busses) {
|
||||||
|
if (bus->isDigital() && bus->isOk()) {
|
||||||
|
BusDigital &busd = static_cast<BusDigital&>(*bus);
|
||||||
|
if (busd.getLEDCurrent() > 0) // skip buses with LED current set to 0
|
||||||
|
busd.applyBriLimit(newBri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_gMilliAmpsUsed = milliAmpsSum;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_gMilliAmpsUsed = 0; // reset, we have no current estimation without ABL
|
||||||
|
}
|
||||||
|
|
||||||
ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; }
|
ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; }
|
||||||
|
|
||||||
|
|
||||||
@ -1361,3 +1463,4 @@ uint16_t BusDigital::_milliAmpsTotal = 0;
|
|||||||
std::vector<std::unique_ptr<Bus>> BusManager::busses;
|
std::vector<std::unique_ptr<Bus>> BusManager::busses;
|
||||||
uint16_t BusManager::_gMilliAmpsUsed = 0;
|
uint16_t BusManager::_gMilliAmpsUsed = 0;
|
||||||
uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;
|
uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;
|
||||||
|
bool BusManager::_useABL = false;
|
||||||
|
|||||||
@ -140,6 +140,7 @@ class Bus {
|
|||||||
virtual uint16_t getUsedCurrent() const { return 0; }
|
virtual uint16_t getUsedCurrent() const { return 0; }
|
||||||
virtual uint16_t getMaxCurrent() const { return 0; }
|
virtual uint16_t getMaxCurrent() const { return 0; }
|
||||||
virtual size_t getBusSize() const { return sizeof(Bus); }
|
virtual size_t getBusSize() const { return sizeof(Bus); }
|
||||||
|
virtual const String getCustomText() const { return String(); }
|
||||||
|
|
||||||
inline bool hasRGB() const { return _hasRgb; }
|
inline bool hasRGB() const { return _hasRgb; }
|
||||||
inline bool hasWhite() const { return _hasWhite; }
|
inline bool hasWhite() const { return _hasWhite; }
|
||||||
@ -223,7 +224,7 @@ class Bus {
|
|||||||
uint8_t _autoWhiteMode;
|
uint8_t _autoWhiteMode;
|
||||||
// global Auto White Calculation override
|
// global Auto White Calculation override
|
||||||
static uint8_t _gAWM;
|
static uint8_t _gAWM;
|
||||||
// _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()):
|
// _cct has the following meanings (see calculateCCT() & BusManager::setSegmentCCT()):
|
||||||
// -1 means to extract approximate CCT value in K from RGB (in calcualteCCT())
|
// -1 means to extract approximate CCT value in K from RGB (in calcualteCCT())
|
||||||
// [0,255] is the exact CCT value where 0 means warm and 255 cold
|
// [0,255] is the exact CCT value where 0 means warm and 255 cold
|
||||||
// [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin())
|
// [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin())
|
||||||
@ -245,7 +246,6 @@ class BusDigital : public Bus {
|
|||||||
|
|
||||||
void show() override;
|
void show() override;
|
||||||
bool canShow() const override;
|
bool canShow() const override;
|
||||||
void setBrightness(uint8_t b) override;
|
|
||||||
void setStatusPixel(uint32_t c) override;
|
void setStatusPixel(uint32_t c) override;
|
||||||
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
|
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
|
||||||
void setColorOrder(uint8_t colorOrder) override;
|
void setColorOrder(uint8_t colorOrder) override;
|
||||||
@ -257,6 +257,9 @@ class BusDigital : public Bus {
|
|||||||
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
|
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
|
||||||
uint16_t getUsedCurrent() const override { return _milliAmpsTotal; }
|
uint16_t getUsedCurrent() const override { return _milliAmpsTotal; }
|
||||||
uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
|
uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
|
||||||
|
void setCurrentLimit(uint16_t milliAmps) { _milliAmpsLimit = milliAmps; }
|
||||||
|
void estimateCurrent(); // estimate used current from summed colors
|
||||||
|
void applyBriLimit(uint8_t newBri);
|
||||||
size_t getBusSize() const override;
|
size_t getBusSize() const override;
|
||||||
void begin() override;
|
void begin() override;
|
||||||
void cleanup();
|
void cleanup();
|
||||||
@ -269,8 +272,10 @@ class BusDigital : public Bus {
|
|||||||
uint8_t _pins[2];
|
uint8_t _pins[2];
|
||||||
uint8_t _iType;
|
uint8_t _iType;
|
||||||
uint16_t _frequencykHz;
|
uint16_t _frequencykHz;
|
||||||
uint8_t _milliAmpsPerLed;
|
|
||||||
uint16_t _milliAmpsMax;
|
uint16_t _milliAmpsMax;
|
||||||
|
uint8_t _milliAmpsPerLed;
|
||||||
|
uint16_t _milliAmpsLimit;
|
||||||
|
uint32_t _colorSum; // total color value for the bus, updated in setPixelColor(), used to estimate current
|
||||||
void *_busPtr;
|
void *_busPtr;
|
||||||
|
|
||||||
static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show()
|
static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show()
|
||||||
@ -285,8 +290,6 @@ class BusDigital : public Bus {
|
|||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t estimateCurrentAndLimitBri() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -350,6 +353,10 @@ class BusNetwork : public Bus {
|
|||||||
size_t getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); }
|
size_t getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); }
|
||||||
void show() override;
|
void show() override;
|
||||||
void cleanup();
|
void cleanup();
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
void resolveHostname();
|
||||||
|
const String getCustomText() const override { return _hostname; }
|
||||||
|
#endif
|
||||||
|
|
||||||
static std::vector<LEDType> getLEDTypes();
|
static std::vector<LEDType> getLEDTypes();
|
||||||
|
|
||||||
@ -359,6 +366,9 @@ class BusNetwork : public Bus {
|
|||||||
uint8_t _UDPchannels;
|
uint8_t _UDPchannels;
|
||||||
bool _broadcastLock;
|
bool _broadcastLock;
|
||||||
uint8_t *_data;
|
uint8_t *_data;
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
String _hostname;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef WLED_ENABLE_HUB75MATRIX
|
#ifdef WLED_ENABLE_HUB75MATRIX
|
||||||
@ -407,8 +417,9 @@ struct BusConfig {
|
|||||||
uint16_t frequency;
|
uint16_t frequency;
|
||||||
uint8_t milliAmpsPerLed;
|
uint8_t milliAmpsPerLed;
|
||||||
uint16_t milliAmpsMax;
|
uint16_t milliAmpsMax;
|
||||||
|
String text;
|
||||||
|
|
||||||
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, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, 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, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, String sometext = "")
|
||||||
: count(std::max(len,(uint16_t)1))
|
: count(std::max(len,(uint16_t)1))
|
||||||
, start(pstart)
|
, start(pstart)
|
||||||
, colorOrder(pcolorOrder)
|
, colorOrder(pcolorOrder)
|
||||||
@ -418,6 +429,7 @@ struct BusConfig {
|
|||||||
, frequency(clock_kHz)
|
, frequency(clock_kHz)
|
||||||
, milliAmpsPerLed(maPerLed)
|
, milliAmpsPerLed(maPerLed)
|
||||||
, milliAmpsMax(maMax)
|
, milliAmpsMax(maMax)
|
||||||
|
, text(sometext)
|
||||||
{
|
{
|
||||||
refreshReq = (bool) GET_BIT(busType,7);
|
refreshReq = (bool) GET_BIT(busType,7);
|
||||||
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)
|
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)
|
||||||
@ -451,8 +463,8 @@ struct BusConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//fine tune power estimation constants for your setup
|
// milliamps used by ESP (for power estimation)
|
||||||
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
|
// you can set it to 0 if the ESP is powered by USB and the LEDs by external
|
||||||
#ifndef MA_FOR_ESP
|
#ifndef MA_FOR_ESP
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
|
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
|
||||||
@ -467,6 +479,7 @@ namespace BusManager {
|
|||||||
//extern std::vector<Bus*> busses;
|
//extern std::vector<Bus*> busses;
|
||||||
extern uint16_t _gMilliAmpsUsed;
|
extern uint16_t _gMilliAmpsUsed;
|
||||||
extern uint16_t _gMilliAmpsMax;
|
extern uint16_t _gMilliAmpsMax;
|
||||||
|
extern bool _useABL;
|
||||||
|
|
||||||
#ifdef ESP32_DATA_IDLE_HIGH
|
#ifdef ESP32_DATA_IDLE_HIGH
|
||||||
void esp32RMTInvertIdle() ;
|
void esp32RMTInvertIdle() ;
|
||||||
@ -482,6 +495,8 @@ namespace BusManager {
|
|||||||
//inline uint16_t ablMilliampsMax() { unsigned sum = 0; for (auto &bus : busses) sum += bus->getMaxCurrent(); return sum; }
|
//inline uint16_t ablMilliampsMax() { unsigned sum = 0; for (auto &bus : busses) sum += bus->getMaxCurrent(); return sum; }
|
||||||
inline uint16_t ablMilliampsMax() { return _gMilliAmpsMax; } // used for compatibility reasons (and enabling virtual global ABL)
|
inline uint16_t ablMilliampsMax() { return _gMilliAmpsMax; } // used for compatibility reasons (and enabling virtual global ABL)
|
||||||
inline void setMilliampsMax(uint16_t max) { _gMilliAmpsMax = max;}
|
inline void setMilliampsMax(uint16_t max) { _gMilliAmpsMax = max;}
|
||||||
|
void initializeABL(); // setup automatic brightness limiter parameters, call once after buses are initialized
|
||||||
|
void applyABL(); // apply automatic brightness limiter, global or per bus
|
||||||
|
|
||||||
void useParallelOutput(); // workaround for inaccessible PolyBus
|
void useParallelOutput(); // workaround for inaccessible PolyBus
|
||||||
bool hasParallelOutput(); // workaround for inaccessible PolyBus
|
bool hasParallelOutput(); // workaround for inaccessible PolyBus
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
#define BusWrapper_h
|
#define BusWrapper_h
|
||||||
|
|
||||||
//#define NPB_CONF_4STEP_CADENCE
|
//#define NPB_CONF_4STEP_CADENCE
|
||||||
#include "NeoPixelBusLg.h"
|
#include "NeoPixelBus.h"
|
||||||
|
|
||||||
//Hardware SPI Pins
|
//Hardware SPI Pins
|
||||||
#define P_8266_HS_MOSI 13
|
#define P_8266_HS_MOSI 13
|
||||||
@ -141,65 +141,65 @@
|
|||||||
/*** ESP8266 Neopixel methods ***/
|
/*** ESP8266 Neopixel methods ***/
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
//RGB
|
//RGB
|
||||||
#define B_8266_U0_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1
|
#define B_8266_U0_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart0Ws2813Method> //3 chan, esp8266, gpio1
|
||||||
#define B_8266_U1_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2
|
#define B_8266_U1_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart1Ws2813Method> //3 chan, esp8266, gpio2
|
||||||
#define B_8266_DM_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio3
|
#define B_8266_DM_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Dma800KbpsMethod> //3 chan, esp8266, gpio3
|
||||||
#define B_8266_BB_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, bb (any pin but 16)
|
#define B_8266_BB_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBang800KbpsMethod> //3 chan, esp8266, bb (any pin but 16)
|
||||||
//RGBW
|
//RGBW
|
||||||
#define B_8266_U0_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //4 chan, esp8266, gpio1
|
#define B_8266_U0_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266Uart0Ws2813Method> //4 chan, esp8266, gpio1
|
||||||
#define B_8266_U1_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //4 chan, esp8266, gpio2
|
#define B_8266_U1_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266Uart1Ws2813Method> //4 chan, esp8266, gpio2
|
||||||
#define B_8266_DM_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, gpio3
|
#define B_8266_DM_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266Dma800KbpsMethod> //4 chan, esp8266, gpio3
|
||||||
#define B_8266_BB_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, bb (any pin)
|
#define B_8266_BB_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266BitBang800KbpsMethod> //4 chan, esp8266, bb (any pin)
|
||||||
//400Kbps
|
//400Kbps
|
||||||
#define B_8266_U0_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart0400KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio1
|
#define B_8266_U0_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart0400KbpsMethod> //3 chan, esp8266, gpio1
|
||||||
#define B_8266_U1_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart1400KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio2
|
#define B_8266_U1_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart1400KbpsMethod> //3 chan, esp8266, gpio2
|
||||||
#define B_8266_DM_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Dma400KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio3
|
#define B_8266_DM_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Dma400KbpsMethod> //3 chan, esp8266, gpio3
|
||||||
#define B_8266_BB_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266BitBang400KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, bb (any pin)
|
#define B_8266_BB_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBang400KbpsMethod> //3 chan, esp8266, bb (any pin)
|
||||||
//TM1814 (RGBW)
|
//TM1814 (RGBW)
|
||||||
#define B_8266_U0_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266Uart0Tm1814Method, NeoGammaNullMethod>
|
#define B_8266_U0_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266Uart0Tm1814Method>
|
||||||
#define B_8266_U1_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266Uart1Tm1814Method, NeoGammaNullMethod>
|
#define B_8266_U1_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266Uart1Tm1814Method>
|
||||||
#define B_8266_DM_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266DmaTm1814Method, NeoGammaNullMethod>
|
#define B_8266_DM_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266DmaTm1814Method>
|
||||||
#define B_8266_BB_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266BitBangTm1814Method, NeoGammaNullMethod>
|
#define B_8266_BB_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266BitBangTm1814Method>
|
||||||
//TM1829 (RGB)
|
//TM1829 (RGB)
|
||||||
#define B_8266_U0_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart0Tm1829Method, NeoGammaNullMethod>
|
#define B_8266_U0_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266Uart0Tm1829Method>
|
||||||
#define B_8266_U1_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart1Tm1829Method, NeoGammaNullMethod>
|
#define B_8266_U1_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266Uart1Tm1829Method>
|
||||||
#define B_8266_DM_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266DmaTm1829Method, NeoGammaNullMethod>
|
#define B_8266_DM_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266DmaTm1829Method>
|
||||||
#define B_8266_BB_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266BitBangTm1829Method, NeoGammaNullMethod>
|
#define B_8266_BB_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266BitBangTm1829Method>
|
||||||
//UCS8903
|
//UCS8903
|
||||||
#define B_8266_U0_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1
|
#define B_8266_U0_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266Uart0Ws2813Method> //3 chan, esp8266, gpio1
|
||||||
#define B_8266_U1_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2
|
#define B_8266_U1_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266Uart1Ws2813Method> //3 chan, esp8266, gpio2
|
||||||
#define B_8266_DM_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, gpio3
|
#define B_8266_DM_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266Dma800KbpsMethod> //3 chan, esp8266, gpio3
|
||||||
#define B_8266_BB_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, bb (any pin but 16)
|
#define B_8266_BB_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266BitBang800KbpsMethod> //3 chan, esp8266, bb (any pin but 16)
|
||||||
//UCS8904 RGBW
|
//UCS8904 RGBW
|
||||||
#define B_8266_U0_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //4 chan, esp8266, gpio1
|
#define B_8266_U0_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266Uart0Ws2813Method> //4 chan, esp8266, gpio1
|
||||||
#define B_8266_U1_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //4 chan, esp8266, gpio2
|
#define B_8266_U1_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266Uart1Ws2813Method> //4 chan, esp8266, gpio2
|
||||||
#define B_8266_DM_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, gpio3
|
#define B_8266_DM_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266Dma800KbpsMethod> //4 chan, esp8266, gpio3
|
||||||
#define B_8266_BB_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, bb (any pin)
|
#define B_8266_BB_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266BitBang800KbpsMethod> //4 chan, esp8266, bb (any pin)
|
||||||
//APA106
|
//APA106
|
||||||
#define B_8266_U0_APA106_3 NeoPixelBusLg<NeoRbgFeature, NeoEsp8266Uart0Apa106Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1
|
#define B_8266_U0_APA106_3 NeoPixelBus<NeoRbgFeature, NeoEsp8266Uart0Apa106Method> //3 chan, esp8266, gpio1
|
||||||
#define B_8266_U1_APA106_3 NeoPixelBusLg<NeoRbgFeature, NeoEsp8266Uart1Apa106Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2
|
#define B_8266_U1_APA106_3 NeoPixelBus<NeoRbgFeature, NeoEsp8266Uart1Apa106Method> //3 chan, esp8266, gpio2
|
||||||
#define B_8266_DM_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266DmaApa106Method, NeoGammaNullMethod> //3 chan, esp8266, gpio3
|
#define B_8266_DM_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266DmaApa106Method> //3 chan, esp8266, gpio3
|
||||||
#define B_8266_BB_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266BitBangApa106Method, NeoGammaNullMethod> //3 chan, esp8266, bb (any pin but 16)
|
#define B_8266_BB_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBangApa106Method> //3 chan, esp8266, bb (any pin but 16)
|
||||||
//FW1906 GRBCW
|
//FW1906 GRBCW
|
||||||
#define B_8266_U0_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //esp8266, gpio1
|
#define B_8266_U0_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Uart0Ws2813Method> //esp8266, gpio1
|
||||||
#define B_8266_U1_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //esp8266, gpio2
|
#define B_8266_U1_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Uart1Ws2813Method> //esp8266, gpio2
|
||||||
#define B_8266_DM_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> //esp8266, gpio3
|
#define B_8266_DM_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Dma800KbpsMethod> //esp8266, gpio3
|
||||||
#define B_8266_BB_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //esp8266, bb
|
#define B_8266_BB_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266BitBang800KbpsMethod> //esp8266, bb
|
||||||
//WS2805 GRBCW
|
//WS2805 GRBCW
|
||||||
#define B_8266_U0_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266Uart0Ws2805Method, NeoGammaNullMethod> //esp8266, gpio1
|
#define B_8266_U0_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266Uart0Ws2805Method> //esp8266, gpio1
|
||||||
#define B_8266_U1_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266Uart1Ws2805Method, NeoGammaNullMethod> //esp8266, gpio2
|
#define B_8266_U1_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266Uart1Ws2805Method> //esp8266, gpio2
|
||||||
#define B_8266_DM_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266DmaWs2805Method, NeoGammaNullMethod> //esp8266, gpio3
|
#define B_8266_DM_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266DmaWs2805Method> //esp8266, gpio3
|
||||||
#define B_8266_BB_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266BitBangWs2805Method, NeoGammaNullMethod> //esp8266, bb
|
#define B_8266_BB_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266BitBangWs2805Method> //esp8266, bb
|
||||||
//TM1914 (RGB)
|
//TM1914 (RGB)
|
||||||
#define B_8266_U0_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266Uart0Tm1914Method, NeoGammaNullMethod>
|
#define B_8266_U0_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266Uart0Tm1914Method>
|
||||||
#define B_8266_U1_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266Uart1Tm1914Method, NeoGammaNullMethod>
|
#define B_8266_U1_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266Uart1Tm1914Method>
|
||||||
#define B_8266_DM_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266DmaTm1914Method, NeoGammaNullMethod>
|
#define B_8266_DM_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266DmaTm1914Method>
|
||||||
#define B_8266_BB_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266BitBangTm1914Method, NeoGammaNullMethod>
|
#define B_8266_BB_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266BitBangTm1914Method>
|
||||||
//Sm16825 (RGBWC)
|
//Sm16825 (RGBWC)
|
||||||
#define B_8266_U0_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod>
|
#define B_8266_U0_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Uart0Ws2813Method>
|
||||||
#define B_8266_U1_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod>
|
#define B_8266_U1_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Uart1Ws2813Method>
|
||||||
#define B_8266_DM_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod>
|
#define B_8266_DM_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Dma800KbpsMethod>
|
||||||
#define B_8266_BB_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266BitBangWs2813Method, NeoGammaNullMethod>
|
#define B_8266_BB_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266BitBangWs2813Method>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*** ESP32 Neopixel methods ***/
|
/*** ESP32 Neopixel methods ***/
|
||||||
@ -245,84 +245,84 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
//RGB
|
//RGB
|
||||||
#define B_32_RN_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod> // ESP32, S2, S3, C3
|
#define B_32_RN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod> // ESP32, S2, S3, C3
|
||||||
//#define B_32_IN_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2sNWs2812xMethod, NeoGammaNullMethod> // ESP32 (dynamic I2S selection)
|
//#define B_32_IN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32I2sNWs2812xMethod> // ESP32 (dynamic I2S selection)
|
||||||
#define B_32_I2_NEO_3 NeoPixelBusLg<NeoGrbFeature, X1Ws2812xMethod, NeoGammaNullMethod> // ESP32, S2, S3 (automatic I2S selection, see typedef above)
|
#define B_32_I2_NEO_3 NeoPixelBus<NeoGrbFeature, X1Ws2812xMethod> // ESP32, S2, S3 (automatic I2S selection, see typedef above)
|
||||||
#define B_32_IP_NEO_3 NeoPixelBusLg<NeoGrbFeature, X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S (ESP32, S2, S3)
|
#define B_32_IP_NEO_3 NeoPixelBus<NeoGrbFeature, X8Ws2812xMethod> // parallel I2S (ESP32, S2, S3)
|
||||||
//RGBW
|
//RGBW
|
||||||
#define B_32_RN_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32RmtNSk6812Method, NeoGammaNullMethod>
|
#define B_32_RN_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtNSk6812Method>
|
||||||
#define B_32_I2_NEO_4 NeoPixelBusLg<NeoGrbwFeature, X1Sk6812Method, NeoGammaNullMethod>
|
#define B_32_I2_NEO_4 NeoPixelBus<NeoGrbwFeature, X1Sk6812Method>
|
||||||
#define B_32_IP_NEO_4 NeoPixelBusLg<NeoGrbwFeature, X8Sk6812Method, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_NEO_4 NeoPixelBus<NeoGrbwFeature, X8Sk6812Method> // parallel I2S
|
||||||
//400Kbps
|
//400Kbps
|
||||||
#define B_32_RN_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod, NeoGammaNullMethod>
|
#define B_32_RN_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod>
|
||||||
#define B_32_I2_400_3 NeoPixelBusLg<NeoGrbFeature, X1400KbpsMethod, NeoGammaNullMethod>
|
#define B_32_I2_400_3 NeoPixelBus<NeoGrbFeature, X1400KbpsMethod>
|
||||||
#define B_32_IP_400_3 NeoPixelBusLg<NeoGrbFeature, X8400KbpsMethod, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_400_3 NeoPixelBus<NeoGrbFeature, X8400KbpsMethod> // parallel I2S
|
||||||
//TM1814 (RGBW)
|
//TM1814 (RGBW)
|
||||||
#define B_32_RN_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method, NeoGammaNullMethod>
|
#define B_32_RN_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method>
|
||||||
#define B_32_I2_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, X1Tm1814Method, NeoGammaNullMethod>
|
#define B_32_I2_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X1Tm1814Method>
|
||||||
#define B_32_IP_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, X8Tm1814Method, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X8Tm1814Method> // parallel I2S
|
||||||
//TM1829 (RGB)
|
//TM1829 (RGB)
|
||||||
#define B_32_RN_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32RmtNTm1829Method, NeoGammaNullMethod>
|
#define B_32_RN_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp32RmtNTm1829Method>
|
||||||
#define B_32_I2_TM2_3 NeoPixelBusLg<NeoBrgFeature, X1Tm1829Method, NeoGammaNullMethod>
|
#define B_32_I2_TM2_3 NeoPixelBus<NeoBrgFeature, X1Tm1829Method>
|
||||||
#define B_32_IP_TM2_3 NeoPixelBusLg<NeoBrgFeature, X8Tm1829Method, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_TM2_3 NeoPixelBus<NeoBrgFeature, X8Tm1829Method> // parallel I2S
|
||||||
//UCS8903
|
//UCS8903
|
||||||
#define B_32_RN_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
#define B_32_RN_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod>
|
||||||
#define B_32_I2_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, X1800KbpsMethod, NeoGammaNullMethod>
|
#define B_32_I2_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X1800KbpsMethod>
|
||||||
#define B_32_IP_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X8800KbpsMethod> // parallel I2S
|
||||||
//UCS8904
|
//UCS8904
|
||||||
#define B_32_RN_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
#define B_32_RN_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod>
|
||||||
#define B_32_I2_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, X1800KbpsMethod, NeoGammaNullMethod>
|
#define B_32_I2_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X1800KbpsMethod>
|
||||||
#define B_32_IP_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, X8800KbpsMethod, NeoGammaNullMethod>// parallel I2S
|
#define B_32_IP_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X8800KbpsMethod>// parallel I2S
|
||||||
//APA106
|
//APA106
|
||||||
#define B_32_RN_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtNApa106Method, NeoGammaNullMethod>
|
#define B_32_RN_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNApa106Method>
|
||||||
#define B_32_I2_APA106_3 NeoPixelBusLg<NeoGrbFeature, X1Apa106Method, NeoGammaNullMethod>
|
#define B_32_I2_APA106_3 NeoPixelBus<NeoGrbFeature, X1Apa106Method>
|
||||||
#define B_32_IP_APA106_3 NeoPixelBusLg<NeoGrbFeature, X8Apa106Method, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_APA106_3 NeoPixelBus<NeoGrbFeature, X8Apa106Method> // parallel I2S
|
||||||
//FW1906 GRBCW
|
//FW1906 GRBCW
|
||||||
#define B_32_RN_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
#define B_32_RN_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod>
|
||||||
#define B_32_I2_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, X1800KbpsMethod, NeoGammaNullMethod>
|
#define B_32_I2_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X1800KbpsMethod>
|
||||||
#define B_32_IP_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X8800KbpsMethod> // parallel I2S
|
||||||
//WS2805 RGBWC
|
//WS2805 RGBWC
|
||||||
#define B_32_RN_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method, NeoGammaNullMethod>
|
#define B_32_RN_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method>
|
||||||
#define B_32_I2_2805_5 NeoPixelBusLg<NeoGrbwwFeature, X1Ws2805Method, NeoGammaNullMethod>
|
#define B_32_I2_2805_5 NeoPixelBus<NeoGrbwwFeature, X1Ws2805Method>
|
||||||
#define B_32_IP_2805_5 NeoPixelBusLg<NeoGrbwwFeature, X8Ws2805Method, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_2805_5 NeoPixelBus<NeoGrbwwFeature, X8Ws2805Method> // parallel I2S
|
||||||
//TM1914 (RGB)
|
//TM1914 (RGB)
|
||||||
#define B_32_RN_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method, NeoGammaNullMethod>
|
#define B_32_RN_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method>
|
||||||
#define B_32_I2_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, X1Tm1914Method, NeoGammaNullMethod>
|
#define B_32_I2_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X1Tm1914Method>
|
||||||
#define B_32_IP_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, X8Tm1914Method, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X8Tm1914Method> // parallel I2S
|
||||||
//Sm16825 (RGBWC)
|
//Sm16825 (RGBWC)
|
||||||
#define B_32_RN_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
#define B_32_RN_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, NeoEsp32RmtNWs2812xMethod>
|
||||||
#define B_32_I2_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, X1Ws2812xMethod, NeoGammaNullMethod>
|
#define B_32_I2_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X1Ws2812xMethod>
|
||||||
#define B_32_IP_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S
|
#define B_32_IP_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X8Ws2812xMethod> // parallel I2S
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//APA102
|
//APA102
|
||||||
#ifdef WLED_USE_ETHERNET
|
#ifdef WLED_USE_ETHERNET
|
||||||
// fix for #2542 (by @BlackBird77)
|
// fix for #2542 (by @BlackBird77)
|
||||||
#define B_HS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarEsp32HspiHzMethod, NeoGammaNullMethod> //hardware HSPI (was DotStarEsp32DmaHspi5MhzMethod in NPB @ 2.6.9)
|
#define B_HS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarEsp32HspiHzMethod> //hardware HSPI (was DotStarEsp32DmaHspi5MhzMethod in NPB @ 2.6.9)
|
||||||
#else
|
#else
|
||||||
#define B_HS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarSpiHzMethod, NeoGammaNullMethod> //hardware VSPI
|
#define B_HS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarSpiHzMethod> //hardware VSPI
|
||||||
#endif
|
#endif
|
||||||
#define B_SS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarMethod, NeoGammaNullMethod> //soft SPI
|
#define B_SS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarMethod> //soft SPI
|
||||||
|
|
||||||
//LPD8806
|
//LPD8806
|
||||||
#define B_HS_LPD_3 NeoPixelBusLg<Lpd8806GrbFeature, Lpd8806SpiHzMethod, NeoGammaNullMethod>
|
#define B_HS_LPD_3 NeoPixelBus<Lpd8806GrbFeature, Lpd8806SpiHzMethod>
|
||||||
#define B_SS_LPD_3 NeoPixelBusLg<Lpd8806GrbFeature, Lpd8806Method, NeoGammaNullMethod>
|
#define B_SS_LPD_3 NeoPixelBus<Lpd8806GrbFeature, Lpd8806Method>
|
||||||
|
|
||||||
//LPD6803
|
//LPD6803
|
||||||
#define B_HS_LPO_3 NeoPixelBusLg<Lpd6803GrbFeature, Lpd6803SpiHzMethod, NeoGammaNullMethod>
|
#define B_HS_LPO_3 NeoPixelBus<Lpd6803GrbFeature, Lpd6803SpiHzMethod>
|
||||||
#define B_SS_LPO_3 NeoPixelBusLg<Lpd6803GrbFeature, Lpd6803Method, NeoGammaNullMethod>
|
#define B_SS_LPO_3 NeoPixelBus<Lpd6803GrbFeature, Lpd6803Method>
|
||||||
|
|
||||||
//WS2801
|
//WS2801
|
||||||
#ifdef WLED_USE_ETHERNET
|
#ifdef WLED_USE_ETHERNET
|
||||||
#define B_HS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801MethodBase<TwoWireHspiImple<SpiSpeedHz>>, NeoGammaNullMethod>
|
#define B_HS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801MethodBase<TwoWireHspiImple<SpiSpeedHz>>>
|
||||||
#else
|
#else
|
||||||
#define B_HS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801SpiHzMethod, NeoGammaNullMethod>
|
#define B_HS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801SpiHzMethod>
|
||||||
#endif
|
#endif
|
||||||
#define B_SS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801Method, NeoGammaNullMethod>
|
#define B_SS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801Method>
|
||||||
|
|
||||||
//P9813
|
//P9813
|
||||||
#define B_HS_P98_3 NeoPixelBusLg<P9813BgrFeature, P9813SpiHzMethod, NeoGammaNullMethod>
|
#define B_HS_P98_3 NeoPixelBus<P9813BgrFeature, P9813SpiHzMethod>
|
||||||
#define B_SS_P98_3 NeoPixelBusLg<P9813BgrFeature, P9813Method, NeoGammaNullMethod>
|
#define B_SS_P98_3 NeoPixelBus<P9813BgrFeature, P9813Method>
|
||||||
|
|
||||||
// 48bit & 64bit to 24bit & 32bit RGB(W) conversion
|
// 48bit & 64bit to 24bit & 32bit RGB(W) conversion
|
||||||
#define toRGBW32(c) (RGBW32((c>>40)&0xFF, (c>>24)&0xFF, (c>>8)&0xFF, (c>>56)&0xFF))
|
#define toRGBW32(c) (RGBW32((c>>40)&0xFF, (c>>24)&0xFF, (c>>8)&0xFF, (c>>56)&0xFF))
|
||||||
@ -469,12 +469,20 @@ class PolyBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) {
|
static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) {
|
||||||
#if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
|
|
||||||
// NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation
|
// NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
|
if (_useParallelI2S && (channel >= 8)) {
|
||||||
|
// Parallel I2S channels are to be used first, so subtract 8 to get the RMT channel number
|
||||||
|
channel -= 8;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
|
||||||
// since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation
|
// since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation
|
||||||
if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32
|
if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32
|
||||||
// if user selected parallel I2S, RMT is used 1st (8 channels) followed by parallel I2S (8 channels)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void* busPtr = nullptr;
|
void* busPtr = nullptr;
|
||||||
switch (busType) {
|
switch (busType) {
|
||||||
case I_NONE: break;
|
case I_NONE: break;
|
||||||
@ -888,102 +896,6 @@ class PolyBus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setBrightness(void* busPtr, uint8_t busType, uint8_t b) {
|
|
||||||
switch (busType) {
|
|
||||||
case I_NONE: break;
|
|
||||||
#ifdef ESP8266
|
|
||||||
case I_8266_U0_NEO_3: (static_cast<B_8266_U0_NEO_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_NEO_3: (static_cast<B_8266_U1_NEO_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_NEO_3: (static_cast<B_8266_DM_NEO_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_NEO_3: (static_cast<B_8266_BB_NEO_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_NEO_4: (static_cast<B_8266_U0_NEO_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_NEO_4: (static_cast<B_8266_U1_NEO_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_NEO_4: (static_cast<B_8266_DM_NEO_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_NEO_4: (static_cast<B_8266_BB_NEO_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_400_3: (static_cast<B_8266_U0_400_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_400_3: (static_cast<B_8266_U1_400_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_400_3: (static_cast<B_8266_DM_400_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_400_3: (static_cast<B_8266_BB_400_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_TM1_4: (static_cast<B_8266_U0_TM1_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_TM1_4: (static_cast<B_8266_U1_TM1_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_TM1_4: (static_cast<B_8266_DM_TM1_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_TM1_4: (static_cast<B_8266_BB_TM1_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_TM2_3: (static_cast<B_8266_U0_TM2_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_TM2_3: (static_cast<B_8266_U1_TM2_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_TM2_3: (static_cast<B_8266_DM_TM2_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_TM2_3: (static_cast<B_8266_BB_TM2_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_UCS_3: (static_cast<B_8266_U0_UCS_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_UCS_3: (static_cast<B_8266_U1_UCS_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_UCS_3: (static_cast<B_8266_DM_UCS_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_UCS_3: (static_cast<B_8266_BB_UCS_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_UCS_4: (static_cast<B_8266_U0_UCS_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_UCS_4: (static_cast<B_8266_U1_UCS_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_UCS_4: (static_cast<B_8266_DM_UCS_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_UCS_4: (static_cast<B_8266_BB_UCS_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_APA106_3: (static_cast<B_8266_U0_APA106_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_APA106_3: (static_cast<B_8266_U1_APA106_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_APA106_3: (static_cast<B_8266_DM_APA106_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_APA106_3: (static_cast<B_8266_BB_APA106_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_FW6_5: (static_cast<B_8266_U0_FW6_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_FW6_5: (static_cast<B_8266_U1_FW6_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_FW6_5: (static_cast<B_8266_DM_FW6_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_FW6_5: (static_cast<B_8266_BB_FW6_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_2805_5: (static_cast<B_8266_U0_2805_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_2805_5: (static_cast<B_8266_U1_2805_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_2805_5: (static_cast<B_8266_DM_2805_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_2805_5: (static_cast<B_8266_BB_2805_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_TM1914_3: (static_cast<B_8266_U0_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_TM1914_3: (static_cast<B_8266_U1_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_TM1914_3: (static_cast<B_8266_DM_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_TM1914_3: (static_cast<B_8266_BB_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U0_SM16825_5: (static_cast<B_8266_U0_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_U1_SM16825_5: (static_cast<B_8266_U1_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_DM_SM16825_5: (static_cast<B_8266_DM_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_8266_BB_SM16825_5: (static_cast<B_8266_BB_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
#endif
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
// RMT buses
|
|
||||||
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_NEO_4: (static_cast<B_32_RN_NEO_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_400_3: (static_cast<B_32_RN_400_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_TM1_4: (static_cast<B_32_RN_TM1_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_TM2_3: (static_cast<B_32_RN_TM2_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_UCS_3: (static_cast<B_32_RN_UCS_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_UCS_4: (static_cast<B_32_RN_UCS_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_APA106_3: (static_cast<B_32_RN_APA106_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_FW6_5: (static_cast<B_32_RN_FW6_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_2805_5: (static_cast<B_32_RN_2805_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_TM1914_3: (static_cast<B_32_RN_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_RN_SM16825_5: (static_cast<B_32_RN_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
// I2S1 bus or paralell buses
|
|
||||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
|
||||||
case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast<B_32_IP_NEO_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_NEO_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast<B_32_IP_NEO_4*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_NEO_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_400_3: if (_useParallelI2S) (static_cast<B_32_IP_400_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_400_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast<B_32_IP_TM1_4*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_TM1_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast<B_32_IP_TM2_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_TM2_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast<B_32_IP_UCS_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_UCS_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast<B_32_IP_UCS_4*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_UCS_4*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast<B_32_IP_APA106_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_APA106_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast<B_32_IP_FW6_5*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_FW6_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_2805_5: if (_useParallelI2S) (static_cast<B_32_IP_2805_5*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_2805_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_TM1914_3: if (_useParallelI2S) (static_cast<B_32_IP_TM1914_3*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_TM1914_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast<B_32_IP_SM16825_5*>(busPtr))->SetLuminance(b); else (static_cast<B_32_I2_SM16825_5*>(busPtr))->SetLuminance(b); break;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_HS_LPD_3: (static_cast<B_HS_LPD_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_SS_LPD_3: (static_cast<B_SS_LPD_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_HS_LPO_3: (static_cast<B_HS_LPO_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_SS_LPO_3: (static_cast<B_SS_LPO_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_HS_WS1_3: (static_cast<B_HS_WS1_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_SS_WS1_3: (static_cast<B_SS_WS1_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_HS_P98_3: (static_cast<B_HS_P98_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
case I_SS_P98_3: (static_cast<B_SS_P98_3*>(busPtr))->SetLuminance(b); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[gnu::hot]] static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) {
|
[[gnu::hot]] static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) {
|
||||||
RgbwColor col(0,0,0,0);
|
RgbwColor col(0,0,0,0);
|
||||||
switch (busType) {
|
switch (busType) {
|
||||||
@ -1428,7 +1340,8 @@ class PolyBus {
|
|||||||
// ESP32-S2 only has 4 RMT channels
|
// ESP32-S2 only has 4 RMT channels
|
||||||
if (_useParallelI2S) {
|
if (_useParallelI2S) {
|
||||||
if (num > 11) return I_NONE;
|
if (num > 11) return I_NONE;
|
||||||
if (num > 3) offset = 1; // use x8 parallel I2S0 channels (use last to allow Audioreactive)
|
if (num < 8) offset = 1; // use x8 parallel I2S0 channels followed by RMT
|
||||||
|
// Note: conflicts with AudioReactive if enabled
|
||||||
} else {
|
} else {
|
||||||
if (num > 4) return I_NONE;
|
if (num > 4) return I_NONE;
|
||||||
if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive)
|
if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive)
|
||||||
@ -1441,7 +1354,7 @@ class PolyBus {
|
|||||||
// On ESP32-S3 only the first 4 RMT channels are usable for transmitting
|
// On ESP32-S3 only the first 4 RMT channels are usable for transmitting
|
||||||
if (_useParallelI2S) {
|
if (_useParallelI2S) {
|
||||||
if (num > 11) return I_NONE;
|
if (num > 11) return I_NONE;
|
||||||
if (num > 3) offset = 1; // use x8 parallel I2S LCD channels
|
if (num < 8) offset = 1; // use x8 parallel I2S LCD channels, followed by RMT
|
||||||
} else {
|
} else {
|
||||||
if (num > 3) return I_NONE; // do not use single I2S (as it is not supported)
|
if (num > 3) return I_NONE; // do not use single I2S (as it is not supported)
|
||||||
}
|
}
|
||||||
@ -1449,7 +1362,7 @@ class PolyBus {
|
|||||||
// standard ESP32 has 8 RMT and x1/x8 I2S1 channels
|
// standard ESP32 has 8 RMT and x1/x8 I2S1 channels
|
||||||
if (_useParallelI2S) {
|
if (_useParallelI2S) {
|
||||||
if (num > 15) return I_NONE;
|
if (num > 15) return I_NONE;
|
||||||
if (num > 7) offset = 1; // 8 RMT followed by 8 I2S
|
if (num < 8) offset = 1; // 8 I2S followed by 8 RMT
|
||||||
} else {
|
} else {
|
||||||
if (num > 9) return I_NONE;
|
if (num > 9) return I_NONE;
|
||||||
if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed)
|
if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed)
|
||||||
|
|||||||
@ -235,7 +235,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
|||||||
}
|
}
|
||||||
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
|
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
|
||||||
|
|
||||||
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax);
|
String host = elm[F("text")] | String();
|
||||||
|
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, host);
|
||||||
doInitBusses = true; // finalization done in beginStrip()
|
doInitBusses = true; // finalization done in beginStrip()
|
||||||
if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want
|
if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want
|
||||||
}
|
}
|
||||||
@ -379,7 +380,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
|||||||
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), btnPin[s], s);
|
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), btnPin[s], s);
|
||||||
btnPin[s] = -1;
|
btnPin[s] = -1;
|
||||||
PinManager::deallocatePin(pin,PinOwner::Button);
|
PinManager::deallocatePin(pin,PinOwner::Button);
|
||||||
}
|
}
|
||||||
//if touch pin, enable the touch interrupt on ESP32 S2 & S3
|
//if touch pin, enable the touch interrupt on ESP32 S2 & S3
|
||||||
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so
|
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so
|
||||||
else
|
else
|
||||||
@ -518,7 +519,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
|||||||
CJSON(briMultiplier, light[F("scale-bri")]);
|
CJSON(briMultiplier, light[F("scale-bri")]);
|
||||||
CJSON(paletteBlend, light[F("pal-mode")]);
|
CJSON(paletteBlend, light[F("pal-mode")]);
|
||||||
CJSON(strip.autoSegments, light[F("aseg")]);
|
CJSON(strip.autoSegments, light[F("aseg")]);
|
||||||
CJSON(useRainbowWheel, light[F("rw")]);
|
|
||||||
|
|
||||||
CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.2
|
CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.2
|
||||||
float light_gc_bri = light["gc"]["bri"];
|
float light_gc_bri = light["gc"]["bri"];
|
||||||
@ -772,9 +772,32 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
|||||||
return (doc["sv"] | true);
|
return (doc["sv"] | true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static const char s_cfg_json[] PROGMEM = "/cfg.json";
|
static const char s_cfg_json[] PROGMEM = "/cfg.json";
|
||||||
|
|
||||||
|
bool backupConfig() {
|
||||||
|
return backupFile(s_cfg_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool restoreConfig() {
|
||||||
|
return restoreFile(s_cfg_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verifyConfig() {
|
||||||
|
return validateJsonFile(s_cfg_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename config file and reboot
|
||||||
|
// if the cfg file doesn't exist, such as after a reset, do nothing
|
||||||
|
void resetConfig() {
|
||||||
|
if (WLED_FS.exists(s_cfg_json)) {
|
||||||
|
DEBUG_PRINTLN(F("Reset config"));
|
||||||
|
char backupname[32];
|
||||||
|
snprintf_P(backupname, sizeof(backupname), PSTR("/rst.%s"), &s_cfg_json[1]);
|
||||||
|
WLED_FS.rename(s_cfg_json, backupname);
|
||||||
|
doReboot = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool deserializeConfigFromFS() {
|
bool deserializeConfigFromFS() {
|
||||||
[[maybe_unused]] bool success = deserializeConfigSec();
|
[[maybe_unused]] bool success = deserializeConfigSec();
|
||||||
#ifdef WLED_ADD_EEPROM_SUPPORT
|
#ifdef WLED_ADD_EEPROM_SUPPORT
|
||||||
@ -800,6 +823,7 @@ bool deserializeConfigFromFS() {
|
|||||||
|
|
||||||
void serializeConfigToFS() {
|
void serializeConfigToFS() {
|
||||||
serializeConfigSec();
|
serializeConfigSec();
|
||||||
|
backupConfig(); // backup before writing new config
|
||||||
|
|
||||||
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
|
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
|
||||||
|
|
||||||
@ -976,6 +1000,7 @@ void serializeConfig(JsonObject root) {
|
|||||||
ins[F("freq")] = bus->getFrequency();
|
ins[F("freq")] = bus->getFrequency();
|
||||||
ins[F("maxpwr")] = bus->getMaxCurrent();
|
ins[F("maxpwr")] = bus->getMaxCurrent();
|
||||||
ins[F("ledma")] = bus->getLEDCurrent();
|
ins[F("ledma")] = bus->getLEDCurrent();
|
||||||
|
ins[F("text")] = bus->getCustomText();
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArray hw_com = hw.createNestedArray(F("com"));
|
JsonArray hw_com = hw.createNestedArray(F("com"));
|
||||||
@ -1040,7 +1065,6 @@ void serializeConfig(JsonObject root) {
|
|||||||
light[F("scale-bri")] = briMultiplier;
|
light[F("scale-bri")] = briMultiplier;
|
||||||
light[F("pal-mode")] = paletteBlend;
|
light[F("pal-mode")] = paletteBlend;
|
||||||
light[F("aseg")] = strip.autoSegments;
|
light[F("aseg")] = strip.autoSegments;
|
||||||
light[F("rw")] = useRainbowWheel;
|
|
||||||
|
|
||||||
JsonObject light_gc = light.createNestedObject("gc");
|
JsonObject light_gc = light.createNestedObject("gc");
|
||||||
light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility
|
light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility
|
||||||
@ -1340,4 +1364,4 @@ void serializeConfigSec() {
|
|||||||
if (f) serializeJson(root, f);
|
if (f) serializeJson(root, f);
|
||||||
f.close();
|
f.close();
|
||||||
releaseJSONBufferLock();
|
releaseJSONBufferLock();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
* color blend function, based on FastLED blend function
|
* color blend function, based on FastLED blend function
|
||||||
* the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB
|
* the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB
|
||||||
*/
|
*/
|
||||||
uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
|
uint32_t IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
|
||||||
// min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance
|
// min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance
|
||||||
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated (poorman's SIMD; https://github.com/wled/WLED/pull/4568#discussion_r1986587221)
|
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated (poorman's SIMD; https://github.com/wled/WLED/pull/4568#discussion_r1986587221)
|
||||||
uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1
|
uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1
|
||||||
@ -64,31 +64,47 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR)
|
|||||||
* fades color toward black
|
* fades color toward black
|
||||||
* if using "video" method the resulting color will never become black unless it is already black
|
* if using "video" method the resulting color will never become black unless it is already black
|
||||||
*/
|
*/
|
||||||
|
uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) {
|
||||||
uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
|
if (c1 == 0 || amount == 0) return 0; // black or no change
|
||||||
{
|
|
||||||
if (amount == 255) return c1;
|
if (amount == 255) return c1;
|
||||||
if (c1 == BLACK || amount == 0) return BLACK;
|
|
||||||
uint32_t scaledcolor; // color order is: W R G B from MSB to LSB
|
|
||||||
uint32_t scale = amount; // 32bit for faster calculation
|
|
||||||
uint32_t addRemains = 0;
|
uint32_t addRemains = 0;
|
||||||
if (!video) scale++; // add one for correct scaling using bitshifts
|
|
||||||
else { // video scaling: make sure colors do not dim to zero if they started non-zero
|
if (!video) amount++; // add one for correct scaling using bitshifts
|
||||||
addRemains = R(c1) ? 0x00010000 : 0;
|
else {
|
||||||
addRemains |= G(c1) ? 0x00000100 : 0;
|
// video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue
|
||||||
addRemains |= B(c1) ? 0x00000001 : 0;
|
uint8_t r = byte(c1>>16), g = byte(c1>>8), b = byte(c1), w = byte(c1>>24); // extract r, g, b, w channels
|
||||||
addRemains |= W(c1) ? 0x01000000 : 0;
|
uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // determine dominant channel for hue preservation
|
||||||
|
uint8_t quarterMax = maxc >> 2; // note: using half of max results in color artefacts
|
||||||
|
addRemains = r && r > quarterMax ? 0x00010000 : 0;
|
||||||
|
addRemains |= g && g > quarterMax ? 0x00000100 : 0;
|
||||||
|
addRemains |= b && b > quarterMax ? 0x00000001 : 0;
|
||||||
|
addRemains |= w ? 0x01000000 : 0;
|
||||||
}
|
}
|
||||||
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||||
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * scale) >> 8) & TWO_CHANNEL_MASK; // scale red and blue
|
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK; // scale red and blue
|
||||||
uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; // scale white and green
|
uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * amount) & ~TWO_CHANNEL_MASK; // scale white and green
|
||||||
scaledcolor = (rb | wg) + addRemains;
|
return (rb | wg) + addRemains;
|
||||||
return scaledcolor;
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* color adjustment in HSV color space (converts RGB to HSV and back), color conversions are not 100% accurate!
|
||||||
|
shifts hue, increase brightness, decreases saturation (if not black)
|
||||||
|
note: inputs are 32bit to speed up the function, useful input value ranges are 0-255
|
||||||
|
*/
|
||||||
|
uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) {
|
||||||
|
if (rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change
|
||||||
|
CHSV32 hsv;
|
||||||
|
rgb2hsv(rgb, hsv); //convert to HSV
|
||||||
|
hsv.h += (hueShift << 8); // shift hue (hue is 16 bits)
|
||||||
|
hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate
|
||||||
|
hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness
|
||||||
|
uint32_t rgb_adjusted;
|
||||||
|
hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion
|
||||||
|
return rgb_adjusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes)
|
// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes)
|
||||||
uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType)
|
uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) {
|
||||||
{
|
|
||||||
if (blendType == LINEARBLEND_NOWRAP) {
|
if (blendType == LINEARBLEND_NOWRAP) {
|
||||||
index = (index * 0xF0) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping
|
index = (index * 0xF0) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping
|
||||||
}
|
}
|
||||||
@ -103,16 +119,16 @@ uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t
|
|||||||
else ++entry;
|
else ++entry;
|
||||||
unsigned f2 = (lo4 << 4);
|
unsigned f2 = (lo4 << 4);
|
||||||
unsigned f1 = 256 - f2;
|
unsigned f1 = 256 - f2;
|
||||||
red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; // note: using color_blend() is 20% slower
|
red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; // note: using color_blend() is slower
|
||||||
green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8;
|
green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8;
|
||||||
blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8;
|
blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8;
|
||||||
}
|
}
|
||||||
if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted
|
if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted
|
||||||
// actually color_fade(c1, brightness)
|
// actually same as color_fade(), using color_fade() is slower
|
||||||
uint32_t scale = brightness + 1; // adjust for rounding (bitshift)
|
uint32_t scale = brightness + 1; // adjust for rounding (bitshift)
|
||||||
red1 = (red1 * scale) >> 8; // note: using color_fade() is 30% slower
|
red1 = (red1 * scale) >> 8;
|
||||||
green1 = (green1 * scale) >> 8;
|
green1 = (green1 * scale) >> 8;
|
||||||
blue1 = (blue1 * scale) >> 8;
|
blue1 = (blue1 * scale) >> 8;
|
||||||
}
|
}
|
||||||
return RGBW32(red1,green1,blue1,0);
|
return RGBW32(red1,green1,blue1,0);
|
||||||
}
|
}
|
||||||
@ -572,10 +588,13 @@ uint8_t NeoGammaWLEDMethod::gammaT_inv[256];
|
|||||||
void NeoGammaWLEDMethod::calcGammaTable(float gamma)
|
void NeoGammaWLEDMethod::calcGammaTable(float gamma)
|
||||||
{
|
{
|
||||||
float gamma_inv = 1.0f / gamma; // inverse gamma
|
float gamma_inv = 1.0f / gamma; // inverse gamma
|
||||||
for (size_t i = 0; i < 256; i++) {
|
for (size_t i = 1; i < 256; i++) {
|
||||||
gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f);
|
gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f);
|
||||||
gammaT_inv[i] = (int)(powf((float)i / 255.0f, gamma_inv) * 255.0f + 0.5f);
|
gammaT_inv[i] = (int)(powf(((float)i - 0.5f) / 255.0f, gamma_inv) * 255.0f + 0.5f);
|
||||||
|
//DEBUG_PRINTF_P(PSTR("gammaT[%d] = %d gammaT_inv[%d] = %d\n"), i, gammaT[i], i, gammaT_inv[i]);
|
||||||
}
|
}
|
||||||
|
gammaT[0] = 0;
|
||||||
|
gammaT_inv[0] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value)
|
uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value)
|
||||||
@ -584,21 +603,6 @@ uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value)
|
|||||||
return gammaT[value];
|
return gammaT[value];
|
||||||
}
|
}
|
||||||
|
|
||||||
// used for color gamma correction
|
|
||||||
uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct32(uint32_t color)
|
|
||||||
{
|
|
||||||
if (!gammaCorrectCol) return color;
|
|
||||||
uint8_t w = W(color);
|
|
||||||
uint8_t r = R(color);
|
|
||||||
uint8_t g = G(color);
|
|
||||||
uint8_t b = B(color);
|
|
||||||
w = gammaT[w];
|
|
||||||
r = gammaT[r];
|
|
||||||
g = gammaT[g];
|
|
||||||
b = gammaT[b];
|
|
||||||
return RGBW32(r, g, b, w);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::inverseGamma32(uint32_t color)
|
uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::inverseGamma32(uint32_t color)
|
||||||
{
|
{
|
||||||
if (!gammaCorrectCol) return color;
|
if (!gammaCorrectCol) return color;
|
||||||
|
|||||||
144
wled00/colors.h
Normal file
144
wled00/colors.h
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef WLED_COLORS_H
|
||||||
|
#define WLED_COLORS_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Color structs and color utility functions
|
||||||
|
*/
|
||||||
|
#include <vector>
|
||||||
|
#include "FastLED.h"
|
||||||
|
|
||||||
|
#define ColorFromPalette ColorFromPaletteWLED // override fastled version
|
||||||
|
|
||||||
|
// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color
|
||||||
|
// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts
|
||||||
|
// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB
|
||||||
|
struct CRGBW {
|
||||||
|
union {
|
||||||
|
uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB)
|
||||||
|
struct {
|
||||||
|
uint8_t b;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t w;
|
||||||
|
};
|
||||||
|
uint8_t raw[4]; // Access as an array in the order B, G, R, W
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default constructor
|
||||||
|
inline CRGBW() __attribute__((always_inline)) = default;
|
||||||
|
|
||||||
|
// Constructor from a 32-bit color (0xWWRRGGBB)
|
||||||
|
constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {}
|
||||||
|
|
||||||
|
// Constructor with r, g, b, w values
|
||||||
|
constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {}
|
||||||
|
|
||||||
|
// Constructor from CRGB
|
||||||
|
constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {}
|
||||||
|
|
||||||
|
// Access as an array
|
||||||
|
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
|
||||||
|
|
||||||
|
// Assignment from 32-bit color
|
||||||
|
inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; }
|
||||||
|
|
||||||
|
// Assignment from r, g, b, w
|
||||||
|
inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; }
|
||||||
|
|
||||||
|
// Conversion operator to uint32_t
|
||||||
|
inline operator uint32_t() const __attribute__((always_inline)) {
|
||||||
|
return color32;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
// Conversion operator to CRGB
|
||||||
|
inline operator CRGB() const __attribute__((always_inline)) {
|
||||||
|
return CRGB(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
CRGBW& scale32 (uint8_t scaledown) // 32bit math
|
||||||
|
{
|
||||||
|
if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit
|
||||||
|
uint32_t scale = scaledown + 1;
|
||||||
|
uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
|
||||||
|
uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
|
||||||
|
color32 = rb | wg;
|
||||||
|
return *this;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint16_t h; // hue
|
||||||
|
uint8_t s; // saturation
|
||||||
|
uint8_t v; // value
|
||||||
|
};
|
||||||
|
uint32_t raw; // 32bit access
|
||||||
|
};
|
||||||
|
inline CHSV32() __attribute__((always_inline)) = default; // default constructor
|
||||||
|
|
||||||
|
/// Allow construction from hue, saturation, and value
|
||||||
|
/// @param ih input hue
|
||||||
|
/// @param is input saturation
|
||||||
|
/// @param iv input value
|
||||||
|
inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v
|
||||||
|
: h(ih), s(is), v(iv) {}
|
||||||
|
inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v
|
||||||
|
: h((uint16_t)ih << 8), s(is), v(iv) {}
|
||||||
|
inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV
|
||||||
|
: h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {}
|
||||||
|
inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV
|
||||||
|
};
|
||||||
|
extern bool gammaCorrectCol;
|
||||||
|
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
|
||||||
|
class NeoGammaWLEDMethod {
|
||||||
|
public:
|
||||||
|
[[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel
|
||||||
|
[[gnu::hot]] static uint32_t inverseGamma32(uint32_t color); // apply inverse Gamma to RGBW32 color
|
||||||
|
static void calcGammaTable(float gamma); // re-calculates & fills gamma tables
|
||||||
|
static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB)
|
||||||
|
static inline uint8_t rawInverseGamma8(uint8_t val) { return gammaT_inv[val]; } // get value from inverse Gamma table (WLED specific, not used by NPB)
|
||||||
|
static inline uint32_t Correct32(uint32_t color) { // apply Gamma to RGBW32 color (WLED specific, not used by NPB)
|
||||||
|
if (!gammaCorrectCol) return color; // no gamma correction
|
||||||
|
uint8_t w = byte(color>>24), r = byte(color>>16), g = byte(color>>8), b = byte(color); // extract r, g, b, w channels
|
||||||
|
w = gammaT[w]; r = gammaT[r]; g = gammaT[g]; b = gammaT[b];
|
||||||
|
return (uint32_t(w) << 24) | (uint32_t(r) << 16) | (uint32_t(g) << 8) | uint32_t(b);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static uint8_t gammaT[];
|
||||||
|
static uint8_t gammaT_inv[];
|
||||||
|
};
|
||||||
|
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
|
||||||
|
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
|
||||||
|
#define gamma32inv(c) NeoGammaWLEDMethod::inverseGamma32(c)
|
||||||
|
#define gamma8inv(c) NeoGammaWLEDMethod::rawInverseGamma8(c)
|
||||||
|
[[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend);
|
||||||
|
inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); };
|
||||||
|
[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
|
||||||
|
[[gnu::hot, gnu::pure]] uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten);
|
||||||
|
[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
|
||||||
|
CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette);
|
||||||
|
CRGBPalette16 generateRandomPalette();
|
||||||
|
void loadCustomPalettes();
|
||||||
|
extern std::vector<CRGBPalette16> customPalettes;
|
||||||
|
inline size_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); }
|
||||||
|
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
|
||||||
|
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
|
||||||
|
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
|
||||||
|
void rgb2hsv(const uint32_t rgb, CHSV32& hsv);
|
||||||
|
inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv
|
||||||
|
void colorKtoRGB(uint16_t kelvin, byte* rgb);
|
||||||
|
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb
|
||||||
|
void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO
|
||||||
|
void colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO
|
||||||
|
void colorFromDecOrHexString(byte* rgb, const char* in);
|
||||||
|
bool colorFromHexString(byte* rgb, const char* in);
|
||||||
|
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
|
||||||
|
uint16_t approximateKelvinFromRGB(uint32_t rgb);
|
||||||
|
void setRandomColor(byte* rgb);
|
||||||
|
|
||||||
|
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -552,8 +552,8 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//#define MIN_HEAP_SIZE
|
// minimum heap size required to process web requests
|
||||||
#define MIN_HEAP_SIZE 2048
|
#define MIN_HEAP_SIZE 8192
|
||||||
|
|
||||||
// Web server limits
|
// Web server limits
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
|
|||||||
@ -13,7 +13,7 @@ function isN(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // isNumber
|
|||||||
// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer
|
// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer
|
||||||
function isF(n) { return n === +n && n !== (n|0); } // isFloat
|
function isF(n) { return n === +n && n !== (n|0); } // isFloat
|
||||||
function isI(n) { return n === +n && n === (n|0); } // isInteger
|
function isI(n) { return n === +n && n === (n|0); } // isInteger
|
||||||
function toggle(el) { gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide"); }
|
function toggle(el) { gId(el).classList.toggle("hide"); let n = gId('No'+el); if (n) n.classList.toggle("hide"); }
|
||||||
function tooltip(cont=null) {
|
function tooltip(cont=null) {
|
||||||
d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{
|
d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{
|
||||||
element.addEventListener("pointerover", ()=>{
|
element.addEventListener("pointerover", ()=>{
|
||||||
|
|||||||
@ -329,7 +329,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<button class="btn ibtn" onclick="requestJson()">Refresh</button>
|
<button class="btn ibtn" onclick="requestJson()">Refresh</button>
|
||||||
<button class="btn ibtn" onclick="toggleNodes()">Instance List</button>
|
<button class="btn ibtn" onclick="toggleNodes()">Instance List</button>
|
||||||
<button class="btn ibtn" onclick="window.open(getURL('/update'),'_self');">Update WLED</button>
|
<button class="btn ibtn" onclick="window.open(getURL('/update'),'_self');" id="updBt">Update WLED</button>
|
||||||
<button class="btn ibtn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button>
|
<button class="btn ibtn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@ -685,6 +685,7 @@ function parseInfo(i) {
|
|||||||
gId("filter2D").classList.remove('hide');
|
gId("filter2D").classList.remove('hide');
|
||||||
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';});
|
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';});
|
||||||
}
|
}
|
||||||
|
gId("updBt").style.display = (i.opt & 1) ? '':'none';
|
||||||
// if (i.noaudio) {
|
// if (i.noaudio) {
|
||||||
// gId("filterVol").classList.add("hide");
|
// gId("filterVol").classList.add("hide");
|
||||||
// gId("filterFreq").classList.add("hide");
|
// gId("filterFreq").classList.add("hide");
|
||||||
@ -1527,6 +1528,9 @@ function readState(s,command=false)
|
|||||||
case 3:
|
case 3:
|
||||||
errstr = "Buffer locked!";
|
errstr = "Buffer locked!";
|
||||||
break;
|
break;
|
||||||
|
case 7:
|
||||||
|
errstr = "No RAM for buffer!";
|
||||||
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
errstr = "Effect RAM depleted!";
|
errstr = "Effect RAM depleted!";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
<script src="common.js" async type="text/javascript"></script>
|
<script src="common.js" async type="text/javascript"></script>
|
||||||
<script>
|
<script>
|
||||||
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
|
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
|
||||||
var oMaxB=1;
|
|
||||||
var customStarts=false,startsDirty=[];
|
var customStarts=false,startsDirty=[];
|
||||||
function off(n) { gN(n).value = -1;}
|
function off(n) { gN(n).value = -1;}
|
||||||
// these functions correspond to C macros found in const.h
|
// these functions correspond to C macros found in const.h
|
||||||
@ -44,8 +43,8 @@
|
|||||||
if (loc) d.Sf.action = getURL('/settings/leds');
|
if (loc) d.Sf.action = getURL('/settings/leds');
|
||||||
}
|
}
|
||||||
function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
|
function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
|
||||||
oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S): 20 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
|
maxB = b; // maxB - max physical (analog + digital) buses: 32 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
|
||||||
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 17 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
|
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
|
||||||
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
|
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
|
||||||
maxV = v; // maxV - min virtual buses: 6 - ESP32/S3, 4 - S2/C3, 3 - ESP8266 (only used to distinguish S2/S3)
|
maxV = v; // maxV - min virtual buses: 6 - ESP32/S3, 4 - S2/C3, 3 - ESP8266 (only used to distinguish S2/S3)
|
||||||
maxPB = p; // maxPB - max LEDs per bus
|
maxPB = p; // maxPB - max LEDs per bus
|
||||||
@ -53,6 +52,11 @@
|
|||||||
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
|
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
|
||||||
maxCO = o; // maxCO - max Color Order mappings
|
maxCO = o; // maxCO - max Color Order mappings
|
||||||
}
|
}
|
||||||
|
function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h
|
||||||
|
function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h
|
||||||
|
function isC3() { return maxA == 6 && maxD == 2; } // NOTE: see const.h
|
||||||
|
function isS2() { return maxA == 8 && maxD == 12 && maxV == 4; } // NOTE: see const.h
|
||||||
|
function isS3() { return maxA == 8 && maxD == 12 && maxV == 6; } // NOTE: see const.h
|
||||||
function pinsOK() {
|
function pinsOK() {
|
||||||
var ok = true;
|
var ok = true;
|
||||||
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
|
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
|
||||||
@ -216,7 +220,6 @@
|
|||||||
let busMA = 0;
|
let busMA = 0;
|
||||||
let sLC = 0, sPC = 0, sDI = 0, maxLC = 0;
|
let sLC = 0, sPC = 0, sDI = 0, maxLC = 0;
|
||||||
const abl = d.Sf.ABL.checked;
|
const abl = d.Sf.ABL.checked;
|
||||||
maxB = oMaxB; // TODO make sure we start with all possible buses
|
|
||||||
let setPinConfig = (n,t) => {
|
let setPinConfig = (n,t) => {
|
||||||
let p0d = "GPIO:";
|
let p0d = "GPIO:";
|
||||||
let p1d = "";
|
let p1d = "";
|
||||||
@ -278,7 +281,7 @@
|
|||||||
gRGBW |= hasW(t); // RGBW checkbox
|
gRGBW |= hasW(t); // RGBW checkbox
|
||||||
gId("co"+n).style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide color order for PWM
|
gId("co"+n).style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide color order for PWM
|
||||||
gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown
|
gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown
|
||||||
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
|
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
|
||||||
if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping
|
if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping
|
||||||
gId("dig"+n+"c").style.display = (isAna(t) || isHub75(t)) ? "none":"inline"; // hide count for analog
|
gId("dig"+n+"c").style.display = (isAna(t) || isHub75(t)) ? "none":"inline"; // hide count for analog
|
||||||
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
|
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
|
||||||
@ -288,6 +291,8 @@
|
|||||||
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
|
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
|
||||||
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°)
|
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°)
|
||||||
//gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description
|
//gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description
|
||||||
|
gId("net"+n+"h").style.display = isNet(t) && !is8266() ? "block" : "none"; // show host field for network types except on ESP8266
|
||||||
|
if (!isNet(t) || is8266()) d.Sf["HS"+n].value = ""; // cleart host field if not network type or ESP8266
|
||||||
});
|
});
|
||||||
// display global white channel overrides
|
// display global white channel overrides
|
||||||
gId("wc").style.display = (gRGBW) ? 'inline':'none';
|
gId("wc").style.display = (gRGBW) ? 'inline':'none';
|
||||||
@ -296,11 +301,16 @@
|
|||||||
d.Sf.CR.checked = false;
|
d.Sf.CR.checked = false;
|
||||||
}
|
}
|
||||||
// update start indexes, max values, calculate current, etc
|
// update start indexes, max values, calculate current, etc
|
||||||
|
let sameType = 0;
|
||||||
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
|
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
|
||||||
nList.forEach((LC,i)=>{
|
nList.forEach((LC,i)=>{
|
||||||
let nm = LC.name.substring(0,2); // field name
|
let nm = LC.name.substring(0,2); // field name
|
||||||
let n = LC.name.substring(2); // bus number
|
let n = LC.name.substring(2); // bus number
|
||||||
let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
|
let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
|
||||||
|
if (isDig(t)) {
|
||||||
|
if (sameType == 0) sameType = t; // first bus type
|
||||||
|
else if (sameType != t) sameType = -1; // different bus type
|
||||||
|
}
|
||||||
// do we have a led count field
|
// do we have a led count field
|
||||||
if (nm=="LC") {
|
if (nm=="LC") {
|
||||||
let c = parseInt(LC.value,10); //get LED count
|
let c = parseInt(LC.value,10); //get LED count
|
||||||
@ -378,18 +388,13 @@
|
|||||||
else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff";
|
else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const S2 = (oMaxB == 14) && (maxV == 4);
|
if (is32() || isS2() || isS3()) {
|
||||||
const S3 = (oMaxB == 14) && (maxV == 6);
|
if (maxLC > 600 || dC < 2 || sameType <= 0) {
|
||||||
if (oMaxB == 32 || S2 || S3) { // TODO: crude ESP32 & S2/S3 detection
|
|
||||||
if (maxLC > 300 || dC <= 2) {
|
|
||||||
d.Sf["PR"].checked = false;
|
d.Sf["PR"].checked = false;
|
||||||
gId("prl").classList.add("hide");
|
gId("prl").classList.add("hide");
|
||||||
} else
|
} else
|
||||||
gId("prl").classList.remove("hide");
|
gId("prl").classList.remove("hide");
|
||||||
// S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
|
} else d.Sf["PR"].checked = false;
|
||||||
maxD = (S2 || S3 ? 4 : 8) + (d.Sf["PR"].checked ? 8 : S2); // TODO: use bLimits() : 4/8RMT + (x1/x8 parallel) I2S1
|
|
||||||
maxB = oMaxB - (d.Sf["PR"].checked ? 0 : 7 + S3); // S2 (maxV==4) does support mono I2S
|
|
||||||
}
|
|
||||||
// distribute ABL current if not using PPL
|
// distribute ABL current if not using PPL
|
||||||
enPPL(sDI);
|
enPPL(sDI);
|
||||||
|
|
||||||
@ -490,6 +495,7 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
|
|||||||
<span id="p2d${s}"></span><input type="number" name="L2${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="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);"/>
|
<span id="p4d${s}"></span><input type="number" name="L4${s}" class="s" onchange="UI();pinUpd(this);"/>
|
||||||
|
<div id="net${s}h" class="hide">Host: <input type="text" name="HS${s}" maxlength="32" pattern="[a-zA-Z0-9_\\-]*" onchange="UI()"/>.local</div>
|
||||||
<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}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}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><span id="off${s}">Off Refresh</span>: <input id="rf${s}" type="checkbox" name="RF${s}"></div>
|
<div id="dig${s}f" style="display:inline"><br><span id="off${s}">Off Refresh</span>: <input id="rf${s}" type="checkbox" name="RF${s}"></div>
|
||||||
@ -506,15 +512,17 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
|
|||||||
if (type.t != undefined && type.t != "") {
|
if (type.t != undefined && type.t != "") {
|
||||||
opt.setAttribute('data-type', type.t);
|
opt.setAttribute('data-type', type.t);
|
||||||
}
|
}
|
||||||
sel.appendChild(opt);
|
sel.appendChild(opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
enLA(d.Sf["LAsel"+s],s); // update LED mA
|
enLA(d.Sf["LAsel"+s],s); // update LED mA
|
||||||
// disable inappropriate LED types
|
// disable inappropriate LED types
|
||||||
let sel = d.getElementsByName("LT"+s)[0]
|
let sel = d.getElementsByName("LT"+s)[0];
|
||||||
if (i >= maxB || digitalB >= maxD) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
|
// 32 & S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
|
||||||
if (i >= maxB || twopinB >= 1) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P()
|
let maxDB = maxD - (is32() || isS2() || isS3() ? (!d.Sf["PR"].checked)*8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used
|
||||||
|
if (digitalB >= maxDB) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
|
||||||
|
if (twopinB >= 2) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P() (we will only allow 2 2pin buses)
|
||||||
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
|
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
|
||||||
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
|
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
|
||||||
}
|
}
|
||||||
@ -613,7 +621,7 @@ Swap: <select id="xw${s}" name="XW${s}">
|
|||||||
var cs = false;
|
var cs = false;
|
||||||
for (var i=1; i < gEBCN("iST").length; i++) {
|
for (var i=1; i < gEBCN("iST").length; i++) {
|
||||||
var s = chrID(i);
|
var s = chrID(i);
|
||||||
var p = chrID(i-1); // cover edge case 'A' previous char being '9'
|
var p = chrID(i-1); // cover edge case 'A' previous char being '9'
|
||||||
var v = parseInt(gId("ls"+p).value) + parseInt(gN("LC"+p).value);
|
var v = parseInt(gId("ls"+p).value) + parseInt(gN("LC"+p).value);
|
||||||
if (v != parseInt(gId("ls"+s).value)) {cs = true; startsDirty[i] = true;}
|
if (v != parseInt(gId("ls"+s).value)) {cs = true; startsDirty[i] = true;}
|
||||||
}
|
}
|
||||||
@ -644,7 +652,7 @@ Swap: <select id="xw${s}" name="XW${s}">
|
|||||||
|
|
||||||
function receivedText(e) {
|
function receivedText(e) {
|
||||||
let lines = e.target.result;
|
let lines = e.target.result;
|
||||||
let c = JSON.parse(lines);
|
let c = JSON.parse(lines);
|
||||||
if (c.hw) {
|
if (c.hw) {
|
||||||
if (c.hw.led) {
|
if (c.hw.led) {
|
||||||
// remove all existing outputs
|
// remove all existing outputs
|
||||||
@ -927,7 +935,6 @@ Swap: <select id="xw${s}" name="XW${s}">
|
|||||||
<option value="3">None (not recommended)</option>
|
<option value="3">None (not recommended)</option>
|
||||||
</select><br>
|
</select><br>
|
||||||
Use harmonic <i>Random Cycle</i> palette: <input type="checkbox" name="TH"><br>
|
Use harmonic <i>Random Cycle</i> palette: <input type="checkbox" name="TH"><br>
|
||||||
Use "rainbow" color wheel: <input type="checkbox" name="RW"><br>
|
|
||||||
Target refresh rate: <input type="number" class="s" min="0" max="250" name="FR" oninput="UI()" required> FPS
|
Target refresh rate: <input type="number" class="s" min="0" max="250" name="FR" oninput="UI()" required> FPS
|
||||||
<div id="fpsNone" class="warn" style="display: none;">⚠ Unlimited FPS Mode is experimental ⚠<br></div>
|
<div id="fpsNone" class="warn" style="display: none;">⚠ Unlimited FPS Mode is experimental ⚠<br></div>
|
||||||
<div id="fpsHigh" class="warn" style="display: none;">⚠ High FPS Mode is experimental.<br></div>
|
<div id="fpsHigh" class="warn" style="display: none;">⚠ High FPS Mode is experimental.<br></div>
|
||||||
|
|||||||
@ -53,13 +53,13 @@
|
|||||||
Factory reset: <input type="checkbox" name="RS"><br>
|
Factory reset: <input type="checkbox" name="RS"><br>
|
||||||
All settings and presets will be erased.<br><br>
|
All settings and presets will be erased.<br><br>
|
||||||
<div class="warn">⚠ Unencrypted transmission. An attacker on the same network can intercept form data!</div>
|
<div class="warn">⚠ Unencrypted transmission. An attacker on the same network can intercept form data!</div>
|
||||||
<hr>
|
<span id="OTA"><hr>
|
||||||
<h3>Software Update</h3>
|
<h3>Software Update</h3>
|
||||||
<button type="button" onclick="U()">Manual OTA Update</button><br>
|
<button type="button" onclick="U()">Manual OTA Update</button><br>
|
||||||
<div id="aOTA">Enable ArduinoOTA: <input type="checkbox" name="AO"></div>
|
<div id="aOTA">Enable ArduinoOTA: <input type="checkbox" name="AO"></div>
|
||||||
Only allow update from same network/WiFi: <input type="checkbox" name="SU"><br>
|
Only allow update from same network/WiFi: <input type="checkbox" name="SU"><br>
|
||||||
<i class="warn">⚠ If you are using multiple VLANs (i.e. IoT or guest network) either set PIN or disable this option.<br>
|
<i class="warn">⚠ If you are using multiple VLANs (i.e. IoT or guest network) either set PIN or disable this option.<br>
|
||||||
Disabling this option will make your device less secure.</i><br>
|
Disabling this option will make your device less secure.</i><br></span>
|
||||||
<hr id="backup">
|
<hr id="backup">
|
||||||
<h3>Backup & Restore</h3>
|
<h3>Backup & Restore</h3>
|
||||||
<div class="warn">⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
|
<div class="warn">⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
|
||||||
@ -76,7 +76,7 @@
|
|||||||
A huge thank you to everyone who helped me create WLED!<br><br>
|
A huge thank you to everyone who helped me create WLED!<br><br>
|
||||||
(c) 2016-2024 Christian Schwinne <br>
|
(c) 2016-2024 Christian Schwinne <br>
|
||||||
<i>Licensed under the <a href="https://github.com/wled-dev/WLED/blob/main/LICENSE" target="_blank">EUPL v1.2 license</a></i><br><br>
|
<i>Licensed under the <a href="https://github.com/wled-dev/WLED/blob/main/LICENSE" target="_blank">EUPL v1.2 license</a></i><br><br>
|
||||||
Server message: <span class="sip"> Response error! </span><hr>
|
Installed version: <span class="sip">WLED ##VERSION##</span><hr>
|
||||||
<div id="toast"></div>
|
<div id="toast"></div>
|
||||||
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -261,7 +261,7 @@ Static subnet mask:<br>
|
|||||||
<option value="9">ABC! WLED V43 & compatible</option>
|
<option value="9">ABC! WLED V43 & compatible</option>
|
||||||
<option value="2">ESP32-POE</option>
|
<option value="2">ESP32-POE</option>
|
||||||
<option value="11">ESP32-POE-WROVER</option>
|
<option value="11">ESP32-POE-WROVER</option>
|
||||||
<option value="6">ESP32Deux/RGB2Go Tetra</option>
|
<option value="6">ESP32Deux/RGB2Go</option>
|
||||||
<option value="7">KIT-VE</option>
|
<option value="7">KIT-VE</option>
|
||||||
<option value="12">LILYGO T-POE Pro</option>
|
<option value="12">LILYGO T-POE Pro</option>
|
||||||
<option value="8">QuinLED-Dig-Octa & T-ETH-POE</option>
|
<option value="8">QuinLED-Dig-Octa & T-ETH-POE</option>
|
||||||
|
|||||||
@ -79,6 +79,9 @@ input {
|
|||||||
input:disabled {
|
input:disabled {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
input:invalid {
|
||||||
|
color: #f00;
|
||||||
|
}
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
input[type="number"],
|
input[type="number"],
|
||||||
input[type="password"],
|
input[type="password"],
|
||||||
@ -202,4 +205,4 @@ td {
|
|||||||
#btns select {
|
#btns select {
|
||||||
width: 144px;
|
width: 144px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
<body onload="GetV()">
|
<body onload="GetV()">
|
||||||
<h2>WLED Software Update</h2>
|
<h2>WLED Software Update</h2>
|
||||||
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
|
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
|
||||||
Installed version: <span class="sip">##VERSION##</span><br>
|
Installed version: <span class="sip">WLED ##VERSION##</span><br>
|
||||||
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
|
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
|
||||||
style="vertical-align: text-bottom; display: inline-flex;">
|
style="vertical-align: text-bottom; display: inline-flex;">
|
||||||
<img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a><br>
|
<img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a><br>
|
||||||
|
|||||||
@ -191,7 +191,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
|
|||||||
// only change brightness if value changed
|
// only change brightness if value changed
|
||||||
if (bri != e131_data[dataOffset]) {
|
if (bri != e131_data[dataOffset]) {
|
||||||
bri = e131_data[dataOffset];
|
bri = e131_data[dataOffset];
|
||||||
strip.setBrightness(scaledBri(bri), false);
|
strip.setBrightness(bri, false);
|
||||||
stateUpdated(CALL_MODE_WS_SEND);
|
stateUpdated(CALL_MODE_WS_SEND);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -24,6 +24,10 @@ void handleIO();
|
|||||||
void IRAM_ATTR touchButtonISR();
|
void IRAM_ATTR touchButtonISR();
|
||||||
|
|
||||||
//cfg.cpp
|
//cfg.cpp
|
||||||
|
bool backupConfig();
|
||||||
|
bool restoreConfig();
|
||||||
|
bool verifyConfig();
|
||||||
|
void resetConfig();
|
||||||
bool deserializeConfig(JsonObject doc, bool fromFS = false);
|
bool deserializeConfig(JsonObject doc, bool fromFS = false);
|
||||||
bool deserializeConfigFromFS();
|
bool deserializeConfigFromFS();
|
||||||
bool deserializeConfigSec();
|
bool deserializeConfigSec();
|
||||||
@ -69,132 +73,6 @@ typedef struct WiFiConfig {
|
|||||||
}
|
}
|
||||||
} wifi_config;
|
} wifi_config;
|
||||||
|
|
||||||
//colors.cpp
|
|
||||||
#define ColorFromPalette ColorFromPaletteWLED // override fastled version
|
|
||||||
|
|
||||||
// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color
|
|
||||||
// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts
|
|
||||||
// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB
|
|
||||||
struct CRGBW {
|
|
||||||
union {
|
|
||||||
uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB)
|
|
||||||
struct {
|
|
||||||
uint8_t b;
|
|
||||||
uint8_t g;
|
|
||||||
uint8_t r;
|
|
||||||
uint8_t w;
|
|
||||||
};
|
|
||||||
uint8_t raw[4]; // Access as an array in the order B, G, R, W
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default constructor
|
|
||||||
inline CRGBW() __attribute__((always_inline)) = default;
|
|
||||||
|
|
||||||
// Constructor from a 32-bit color (0xWWRRGGBB)
|
|
||||||
constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {}
|
|
||||||
|
|
||||||
// Constructor with r, g, b, w values
|
|
||||||
constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {}
|
|
||||||
|
|
||||||
// Constructor from CRGB
|
|
||||||
constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {}
|
|
||||||
|
|
||||||
// Access as an array
|
|
||||||
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
|
|
||||||
|
|
||||||
// Assignment from 32-bit color
|
|
||||||
inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; }
|
|
||||||
|
|
||||||
// Assignment from r, g, b, w
|
|
||||||
inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; }
|
|
||||||
|
|
||||||
// Conversion operator to uint32_t
|
|
||||||
inline operator uint32_t() const __attribute__((always_inline)) {
|
|
||||||
return color32;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
// Conversion operator to CRGB
|
|
||||||
inline operator CRGB() const __attribute__((always_inline)) {
|
|
||||||
return CRGB(r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
CRGBW& scale32 (uint8_t scaledown) // 32bit math
|
|
||||||
{
|
|
||||||
if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit
|
|
||||||
uint32_t scale = scaledown + 1;
|
|
||||||
uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
|
|
||||||
uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
|
|
||||||
color32 = rb | wg;
|
|
||||||
return *this;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
uint16_t h; // hue
|
|
||||||
uint8_t s; // saturation
|
|
||||||
uint8_t v; // value
|
|
||||||
};
|
|
||||||
uint32_t raw; // 32bit access
|
|
||||||
};
|
|
||||||
inline CHSV32() __attribute__((always_inline)) = default; // default constructor
|
|
||||||
|
|
||||||
/// Allow construction from hue, saturation, and value
|
|
||||||
/// @param ih input hue
|
|
||||||
/// @param is input saturation
|
|
||||||
/// @param iv input value
|
|
||||||
inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v
|
|
||||||
: h(ih), s(is), v(iv) {}
|
|
||||||
inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v
|
|
||||||
: h((uint16_t)ih << 8), s(is), v(iv) {}
|
|
||||||
inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV
|
|
||||||
: h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {}
|
|
||||||
inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV
|
|
||||||
};
|
|
||||||
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
|
|
||||||
class NeoGammaWLEDMethod {
|
|
||||||
public:
|
|
||||||
[[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel
|
|
||||||
[[gnu::hot]] static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB)
|
|
||||||
[[gnu::hot]] static uint32_t inverseGamma32(uint32_t color); // apply inverse Gamma to RGBW32 color
|
|
||||||
static void calcGammaTable(float gamma); // re-calculates & fills gamma tables
|
|
||||||
static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB)
|
|
||||||
static inline uint8_t rawInverseGamma8(uint8_t val) { return gammaT_inv[val]; } // get value from inverse Gamma table (WLED specific, not used by NPB)
|
|
||||||
private:
|
|
||||||
static uint8_t gammaT[];
|
|
||||||
static uint8_t gammaT_inv[];
|
|
||||||
};
|
|
||||||
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
|
|
||||||
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
|
|
||||||
#define gamma32inv(c) NeoGammaWLEDMethod::inverseGamma32(c)
|
|
||||||
#define gamma8inv(c) NeoGammaWLEDMethod::rawInverseGamma8(c)
|
|
||||||
[[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend);
|
|
||||||
inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); };
|
|
||||||
[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
|
|
||||||
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false);
|
|
||||||
[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
|
|
||||||
CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette);
|
|
||||||
CRGBPalette16 generateRandomPalette();
|
|
||||||
void loadCustomPalettes();
|
|
||||||
extern std::vector<CRGBPalette16> customPalettes;
|
|
||||||
inline size_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); }
|
|
||||||
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
|
|
||||||
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
|
|
||||||
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
|
|
||||||
void rgb2hsv(const uint32_t rgb, CHSV32& hsv);
|
|
||||||
inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv
|
|
||||||
void colorKtoRGB(uint16_t kelvin, byte* rgb);
|
|
||||||
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb
|
|
||||||
void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO
|
|
||||||
void colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO
|
|
||||||
void colorFromDecOrHexString(byte* rgb, const char* in);
|
|
||||||
bool colorFromHexString(byte* rgb, const char* in);
|
|
||||||
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
|
|
||||||
uint16_t approximateKelvinFromRGB(uint32_t rgb);
|
|
||||||
void setRandomColor(byte* rgb);
|
|
||||||
|
|
||||||
//dmx_output.cpp
|
//dmx_output.cpp
|
||||||
void initDMXOutput();
|
void initDMXOutput();
|
||||||
void handleDMXOutput();
|
void handleDMXOutput();
|
||||||
@ -222,6 +100,11 @@ inline bool writeObjectToFileUsingId(const String &file, uint16_t id, const Json
|
|||||||
inline bool writeObjectToFile(const String &file, const char* key, const JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); };
|
inline bool writeObjectToFile(const String &file, const char* key, const JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); };
|
||||||
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
|
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
|
||||||
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest); };
|
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest); };
|
||||||
|
bool copyFile(const char* src_path, const char* dst_path);
|
||||||
|
bool backupFile(const char* filename);
|
||||||
|
bool restoreFile(const char* filename);
|
||||||
|
bool validateJsonFile(const char* filename);
|
||||||
|
void dumpFilesToSerial();
|
||||||
|
|
||||||
//hue.cpp
|
//hue.cpp
|
||||||
void handleHue();
|
void handleHue();
|
||||||
@ -550,28 +433,39 @@ inline uint8_t hw_random8(uint32_t upperlimit) { return (hw_random8() * upperlim
|
|||||||
inline uint8_t hw_random8(uint32_t lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255
|
inline uint8_t hw_random8(uint32_t lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255
|
||||||
|
|
||||||
// PSRAM allocation wrappers
|
// PSRAM allocation wrappers
|
||||||
#ifndef ESP8266
|
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
extern "C" {
|
extern "C" {
|
||||||
void *p_malloc(size_t); // prefer PSRAM over DRAM
|
void *p_malloc(size_t); // prefer PSRAM over DRAM
|
||||||
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
|
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
|
||||||
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
|
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
|
||||||
|
void *p_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer PSRAM over DRAM
|
||||||
inline void p_free(void *ptr) { heap_caps_free(ptr); }
|
inline void p_free(void *ptr) { heap_caps_free(ptr); }
|
||||||
void *d_malloc(size_t); // prefer DRAM over PSRAM
|
void *d_malloc(size_t); // prefer DRAM over PSRAM
|
||||||
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
|
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
|
||||||
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
|
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
|
||||||
|
void *d_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer DRAM over PSRAM
|
||||||
inline void d_free(void *ptr) { heap_caps_free(ptr); }
|
inline void d_free(void *ptr) { heap_caps_free(ptr); }
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
extern "C" {
|
||||||
|
void *realloc_malloc(void *ptr, size_t size);
|
||||||
|
}
|
||||||
#define p_malloc malloc
|
#define p_malloc malloc
|
||||||
#define p_calloc calloc
|
#define p_calloc calloc
|
||||||
#define p_realloc realloc
|
#define p_realloc realloc
|
||||||
|
#define p_realloc_malloc realloc_malloc
|
||||||
#define p_free free
|
#define p_free free
|
||||||
#define d_malloc malloc
|
#define d_malloc malloc
|
||||||
#define d_calloc calloc
|
#define d_calloc calloc
|
||||||
#define d_realloc realloc
|
#define d_realloc realloc
|
||||||
|
#define d_realloc_malloc realloc_malloc
|
||||||
#define d_free free
|
#define d_free free
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void handleBootLoop(); // detect and handle bootloops
|
||||||
|
#ifndef ESP8266
|
||||||
|
void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
|
||||||
|
#endif
|
||||||
// RAII guard class for the JSON Buffer lock
|
// RAII guard class for the JSON Buffer lock
|
||||||
// Modeled after std::lock_guard
|
// Modeled after std::lock_guard
|
||||||
class JSONBufferGuard {
|
class JSONBufferGuard {
|
||||||
|
|||||||
153
wled00/file.cpp
153
wled00/file.cpp
@ -439,3 +439,156 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy a file, delete destination file if incomplete to prevent corrupted files
|
||||||
|
bool copyFile(const char* src_path, const char* dst_path) {
|
||||||
|
DEBUG_PRINTF("copyFile from %s to %s\n", src_path, dst_path);
|
||||||
|
if(!WLED_FS.exists(src_path)) {
|
||||||
|
DEBUG_PRINTLN(F("file not found"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = true; // is set to false on error
|
||||||
|
File src = WLED_FS.open(src_path, "r");
|
||||||
|
File dst = WLED_FS.open(dst_path, "w");
|
||||||
|
|
||||||
|
if (src && dst) {
|
||||||
|
uint8_t buf[128]; // copy file in 128-byte blocks
|
||||||
|
while (src.available() > 0) {
|
||||||
|
size_t bytesRead = src.read(buf, sizeof(buf));
|
||||||
|
if (bytesRead == 0) {
|
||||||
|
success = false;
|
||||||
|
break; // error, no data read
|
||||||
|
}
|
||||||
|
size_t bytesWritten = dst.write(buf, bytesRead);
|
||||||
|
if (bytesWritten != bytesRead) {
|
||||||
|
success = false;
|
||||||
|
break; // error, not all data written
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
success = false; // error, could not open files
|
||||||
|
}
|
||||||
|
if(src) src.close();
|
||||||
|
if(dst) dst.close();
|
||||||
|
if (!success) {
|
||||||
|
DEBUG_PRINTLN(F("copy failed"));
|
||||||
|
WLED_FS.remove(dst_path); // delete incomplete file
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare two files, return true if identical
|
||||||
|
bool compareFiles(const char* path1, const char* path2) {
|
||||||
|
DEBUG_PRINTF("compareFile %s and %s\n", path1, path2);
|
||||||
|
if (!WLED_FS.exists(path1) || !WLED_FS.exists(path2)) {
|
||||||
|
DEBUG_PRINTLN(F("file not found"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool identical = true; // set to false on mismatch
|
||||||
|
File f1 = WLED_FS.open(path1, "r");
|
||||||
|
File f2 = WLED_FS.open(path2, "r");
|
||||||
|
|
||||||
|
if (f1 && f2) {
|
||||||
|
uint8_t buf1[128], buf2[128];
|
||||||
|
while (f1.available() > 0 || f2.available() > 0) {
|
||||||
|
size_t len1 = f1.read(buf1, sizeof(buf1));
|
||||||
|
size_t len2 = f2.read(buf2, sizeof(buf2));
|
||||||
|
|
||||||
|
if (len1 != len2) {
|
||||||
|
identical = false;
|
||||||
|
break; // files differ in size or read failed
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(buf1, buf2, len1) != 0) {
|
||||||
|
identical = false;
|
||||||
|
break; // files differ in content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
identical = false; // error opening files
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f1) f1.close();
|
||||||
|
if (f2) f2.close();
|
||||||
|
return identical;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char s_backup_fmt[] PROGMEM = "/bkp.%s";
|
||||||
|
|
||||||
|
bool backupFile(const char* filename) {
|
||||||
|
DEBUG_PRINTF("backup %s \n", filename);
|
||||||
|
if (!validateJsonFile(filename)) {
|
||||||
|
DEBUG_PRINTLN(F("broken file"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char backupname[32];
|
||||||
|
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
|
||||||
|
|
||||||
|
if (copyFile(filename, backupname)) {
|
||||||
|
DEBUG_PRINTLN(F("backup ok"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
DEBUG_PRINTLN(F("backup failed"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool restoreFile(const char* filename) {
|
||||||
|
DEBUG_PRINTF("restore %s \n", filename);
|
||||||
|
char backupname[32];
|
||||||
|
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
|
||||||
|
|
||||||
|
if (!WLED_FS.exists(backupname)) {
|
||||||
|
DEBUG_PRINTLN(F("no backup found"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateJsonFile(backupname)) {
|
||||||
|
DEBUG_PRINTLN(F("broken backup"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copyFile(backupname, filename)) {
|
||||||
|
DEBUG_PRINTLN(F("restore ok"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
DEBUG_PRINTLN(F("restore failed"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateJsonFile(const char* filename) {
|
||||||
|
if (!WLED_FS.exists(filename)) return false;
|
||||||
|
File file = WLED_FS.open(filename, "r");
|
||||||
|
if (!file) return false;
|
||||||
|
StaticJsonDocument<0> doc, filter; // https://arduinojson.org/v6/how-to/validate-json/
|
||||||
|
bool result = deserializeJson(doc, file, DeserializationOption::Filter(filter)) == DeserializationError::Ok;
|
||||||
|
file.close();
|
||||||
|
if (!result) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("Invalid JSON file %s\n"), filename);
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTF_P(PSTR("Valid JSON file %s\n"), filename);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// print contents of all files in root dir to Serial except wsec files
|
||||||
|
void dumpFilesToSerial() {
|
||||||
|
File rootdir = WLED_FS.open("/", "r");
|
||||||
|
File rootfile = rootdir.openNextFile();
|
||||||
|
while (rootfile) {
|
||||||
|
size_t len = strlen(rootfile.name());
|
||||||
|
// skip files starting with "wsec" and dont end in .json
|
||||||
|
if (strncmp(rootfile.name(), "wsec", 4) != 0 && len >= 6 && strcmp(rootfile.name() + len - 5, ".json") == 0) {
|
||||||
|
Serial.println(rootfile.name());
|
||||||
|
while (rootfile.available()) {
|
||||||
|
Serial.write(rootfile.read());
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
rootfile.close();
|
||||||
|
rootfile = rootdir.openNextFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -58,7 +58,7 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t
|
|||||||
// set multiple pixels if upscaling
|
// set multiple pixels if upscaling
|
||||||
for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) {
|
for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) {
|
||||||
for (int16_t j = 0; j < (activeSeg->height()+(gifHeight-1)) / gifHeight; j++) {
|
for (int16_t j = 0; j < (activeSeg->height()+(gifHeight-1)) / gifHeight; j++) {
|
||||||
activeSeg->setPixelColorXY(outX + i, outY + j, gamma8(red), gamma8(green), gamma8(blue));
|
activeSeg->setPixelColorXY(outX + i, outY + j, red, green, blue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -312,7 +312,7 @@ static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0)
|
|||||||
jsonTransitionOnce = true;
|
jsonTransitionOnce = true;
|
||||||
if (seg.isInTransition()) seg.startTransition(0); // setting transition time to 0 will stop transition in next frame
|
if (seg.isInTransition()) seg.startTransition(0); // setting transition time to 0 will stop transition in next frame
|
||||||
strip.setTransition(0);
|
strip.setTransition(0);
|
||||||
strip.setBrightness(scaledBri(bri), true);
|
strip.setBrightness(bri, true);
|
||||||
|
|
||||||
// freeze and init to black
|
// freeze and init to black
|
||||||
if (!seg.freeze) {
|
if (!seg.freeze) {
|
||||||
|
|||||||
@ -57,7 +57,7 @@ void toggleOnOff()
|
|||||||
//scales the brightness with the briMultiplier factor
|
//scales the brightness with the briMultiplier factor
|
||||||
byte scaledBri(byte in)
|
byte scaledBri(byte in)
|
||||||
{
|
{
|
||||||
unsigned val = ((uint16_t)in*briMultiplier)/100;
|
unsigned val = ((unsigned)in*briMultiplier)/100;
|
||||||
if (val > 255) val = 255;
|
if (val > 255) val = 255;
|
||||||
return (byte)val;
|
return (byte)val;
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ void applyBri() {
|
|||||||
if (realtimeOverride || !(realtimeMode && arlsForceMaxBri))
|
if (realtimeOverride || !(realtimeMode && arlsForceMaxBri))
|
||||||
{
|
{
|
||||||
//DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld);
|
//DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld);
|
||||||
strip.setBrightness(scaledBri(briT));
|
strip.setBrightness(briT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,11 @@
|
|||||||
#warning "MQTT topics length > 32 is not recommended for compatibility with usermods!"
|
#warning "MQTT topics length > 32 is not recommended for compatibility with usermods!"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static const char* sTopicFormat PROGMEM = "%.*s/%s";
|
||||||
|
|
||||||
|
// parse payload for brightness, ON/OFF or toggle
|
||||||
|
// briLast is used to remember last brightness value in case of ON/OFF or toggle
|
||||||
|
// bri is set to 0 if payload is "0" or "OFF" or "false"
|
||||||
static void parseMQTTBriPayload(char* payload)
|
static void parseMQTTBriPayload(char* payload)
|
||||||
{
|
{
|
||||||
if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);}
|
if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);}
|
||||||
@ -30,22 +35,18 @@ static void onMqttConnect(bool sessionPresent)
|
|||||||
char subuf[MQTT_MAX_TOPIC_LEN + 9];
|
char subuf[MQTT_MAX_TOPIC_LEN + 9];
|
||||||
|
|
||||||
if (mqttDeviceTopic[0] != 0) {
|
if (mqttDeviceTopic[0] != 0) {
|
||||||
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
|
mqtt->subscribe(mqttDeviceTopic, 0);
|
||||||
|
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "col");
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
strcat_P(subuf, PSTR("/col"));
|
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "api");
|
||||||
mqtt->subscribe(subuf, 0);
|
|
||||||
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
|
|
||||||
strcat_P(subuf, PSTR("/api"));
|
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mqttGroupTopic[0] != 0) {
|
if (mqttGroupTopic[0] != 0) {
|
||||||
strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1);
|
mqtt->subscribe(mqttGroupTopic, 0);
|
||||||
|
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttGroupTopic, "col");
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
strcat_P(subuf, PSTR("/col"));
|
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttGroupTopic, "api");
|
||||||
mqtt->subscribe(subuf, 0);
|
|
||||||
strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1);
|
|
||||||
strcat_P(subuf, PSTR("/api"));
|
|
||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,8 +55,7 @@ static void onMqttConnect(bool sessionPresent)
|
|||||||
DEBUG_PRINTLN(F("MQTT ready"));
|
DEBUG_PRINTLN(F("MQTT ready"));
|
||||||
|
|
||||||
#ifndef USERMOD_SMARTNEST
|
#ifndef USERMOD_SMARTNEST
|
||||||
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
|
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "status");
|
||||||
strcat_P(subuf, PSTR("/status"));
|
|
||||||
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
|
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print adapter for flat buffers
|
// Print adapter for flat buffers
|
||||||
namespace {
|
namespace {
|
||||||
class bufferPrint : public Print {
|
class bufferPrint : public Print {
|
||||||
char* _buf;
|
char* _buf;
|
||||||
size_t _size, _offset;
|
size_t _size, _offset;
|
||||||
@ -172,21 +172,21 @@ void publishMqtt()
|
|||||||
char subuf[MQTT_MAX_TOPIC_LEN + 16];
|
char subuf[MQTT_MAX_TOPIC_LEN + 16];
|
||||||
|
|
||||||
sprintf_P(s, PSTR("%u"), bri);
|
sprintf_P(s, PSTR("%u"), bri);
|
||||||
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
|
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "g");
|
||||||
strcat_P(subuf, PSTR("/g"));
|
|
||||||
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
|
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
|
||||||
|
|
||||||
sprintf_P(s, PSTR("#%06X"), (colPri[3] << 24) | (colPri[0] << 16) | (colPri[1] << 8) | (colPri[2]));
|
sprintf_P(s, PSTR("#%06X"), (colPri[3] << 24) | (colPri[0] << 16) | (colPri[1] << 8) | (colPri[2]));
|
||||||
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
|
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "c");
|
||||||
strcat_P(subuf, PSTR("/c"));
|
|
||||||
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
|
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
|
||||||
|
|
||||||
|
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "status");
|
||||||
|
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
|
||||||
|
|
||||||
// TODO: use a DynamicBufferList. Requires a list-read-capable MQTT client API.
|
// TODO: use a DynamicBufferList. Requires a list-read-capable MQTT client API.
|
||||||
DynamicBuffer buf(1024);
|
DynamicBuffer buf(1024);
|
||||||
bufferPrint pbuf(buf.data(), buf.size());
|
bufferPrint pbuf(buf.data(), buf.size());
|
||||||
XML_response(pbuf);
|
XML_response(pbuf);
|
||||||
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
|
snprintf_P(subuf, sizeof(subuf)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "v");
|
||||||
strcat_P(subuf, PSTR("/v"));
|
|
||||||
mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263)
|
mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -213,14 +213,26 @@ bool initMqtt()
|
|||||||
{
|
{
|
||||||
mqtt->setServer(mqttIP, mqttPort);
|
mqtt->setServer(mqttIP, mqttPort);
|
||||||
} else {
|
} else {
|
||||||
mqtt->setServer(mqttServer, mqttPort);
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
String mqttMDNS = mqttServer;
|
||||||
|
mqttMDNS.toLowerCase(); // make sure we have a lowercase hostname
|
||||||
|
int pos = mqttMDNS.indexOf(F(".local"));
|
||||||
|
if (pos > 0) mqttMDNS.remove(pos); // remove .local domain if present (and anything following it)
|
||||||
|
if (strlen(cmDNS) > 0 && mqttMDNS.length() > 0 && mqttMDNS.indexOf('.') < 0) { // if mDNS is enabled and server does not have domain
|
||||||
|
mqttIP = MDNS.queryHost(mqttMDNS.c_str());
|
||||||
|
if (mqttIP != IPAddress()) // if MDNS resolved the hostname
|
||||||
|
mqtt->setServer(mqttIP, mqttPort);
|
||||||
|
else
|
||||||
|
mqtt->setServer(mqttServer, mqttPort);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
mqtt->setServer(mqttServer, mqttPort);
|
||||||
}
|
}
|
||||||
mqtt->setClientId(mqttClientID);
|
mqtt->setClientId(mqttClientID);
|
||||||
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
|
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
|
||||||
|
|
||||||
#ifndef USERMOD_SMARTNEST
|
#ifndef USERMOD_SMARTNEST
|
||||||
strlcpy(mqttStatusTopic, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
|
snprintf_P(mqttStatusTopic, sizeof(mqttStatusTopic)-1, sTopicFormat, MQTT_MAX_TOPIC_LEN, mqttDeviceTopic, "status");
|
||||||
strcat_P(mqttStatusTopic, PSTR("/status"));
|
|
||||||
mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message
|
mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message
|
||||||
#endif
|
#endif
|
||||||
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);
|
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);
|
||||||
|
|||||||
@ -141,6 +141,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed;
|
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed;
|
||||||
unsigned length, start, maMax;
|
unsigned length, start, maMax;
|
||||||
uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255};
|
uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255};
|
||||||
|
String text;
|
||||||
|
|
||||||
// this will set global ABL max current used when per-port ABL is not used
|
// this will set global ABL max current used when per-port ABL is not used
|
||||||
unsigned ablMilliampsMax = request->arg(F("MA")).toInt();
|
unsigned ablMilliampsMax = request->arg(F("MA")).toInt();
|
||||||
@ -174,6 +175,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
|
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 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
|
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA
|
||||||
|
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
|
||||||
if (!request->hasArg(lp)) {
|
if (!request->hasArg(lp)) {
|
||||||
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1);
|
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1);
|
||||||
break;
|
break;
|
||||||
@ -224,9 +226,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
maMax = request->arg(ma).toInt() * request->hasArg(F("PPL")); // if PP-ABL is disabled maMax (per bus) must be 0
|
maMax = request->arg(ma).toInt() * request->hasArg(F("PPL")); // if PP-ABL is disabled maMax (per bus) must be 0
|
||||||
}
|
}
|
||||||
type |= request->hasArg(rf) << 7; // off refresh override
|
type |= request->hasArg(rf) << 7; // off refresh override
|
||||||
|
text = request->arg(hs).substring(0,31);
|
||||||
// actual finalization is done in WLED::loop() (removing old busses and adding new)
|
// actual finalization is done in WLED::loop() (removing old busses and adding new)
|
||||||
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
|
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
|
||||||
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax);
|
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, text);
|
||||||
busesChanged = true;
|
busesChanged = true;
|
||||||
}
|
}
|
||||||
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
|
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
|
||||||
@ -348,7 +351,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
t = request->arg(F("TP")).toInt();
|
t = request->arg(F("TP")).toInt();
|
||||||
randomPaletteChangeTime = MIN(255,MAX(1,t));
|
randomPaletteChangeTime = MIN(255,MAX(1,t));
|
||||||
useHarmonicRandomPalette = request->hasArg(F("TH"));
|
useHarmonicRandomPalette = request->hasArg(F("TH"));
|
||||||
useRainbowWheel = request->hasArg(F("RW"));
|
|
||||||
|
|
||||||
nightlightTargetBri = request->arg(F("TB")).toInt();
|
nightlightTargetBri = request->arg(F("TB")).toInt();
|
||||||
t = request->arg(F("TL")).toInt();
|
t = request->arg(F("TL")).toInt();
|
||||||
|
|||||||
@ -73,17 +73,13 @@ void NetworkClass::localMAC(uint8_t* MAC)
|
|||||||
|
|
||||||
bool NetworkClass::isConnected()
|
bool NetworkClass::isConnected()
|
||||||
{
|
{
|
||||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
|
return (WiFi.localIP()[0] != 0 && WiFi.status() == WL_CONNECTED) || isEthernet();
|
||||||
return (WiFi.localIP()[0] != 0 && WiFi.status() == WL_CONNECTED) || ETH.localIP()[0] != 0;
|
|
||||||
#else
|
|
||||||
return (WiFi.localIP()[0] != 0 && WiFi.status() == WL_CONNECTED);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetworkClass::isEthernet()
|
bool NetworkClass::isEthernet()
|
||||||
{
|
{
|
||||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
|
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
|
||||||
return (ETH.localIP()[0] != 0);
|
return (ETH.localIP()[0] != 0) && ETH.linkUp();
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -424,7 +424,7 @@ void realtimeLock(uint32_t timeoutMs, byte md)
|
|||||||
}
|
}
|
||||||
// if strip is off (bri==0) and not already in RTM
|
// if strip is off (bri==0) and not already in RTM
|
||||||
if (briT == 0) {
|
if (briT == 0) {
|
||||||
strip.setBrightness(scaledBri(briLast), true);
|
strip.setBrightness(briLast, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,14 +434,14 @@ void realtimeLock(uint32_t timeoutMs, byte md)
|
|||||||
realtimeMode = md;
|
realtimeMode = md;
|
||||||
|
|
||||||
if (realtimeOverride) return;
|
if (realtimeOverride) return;
|
||||||
if (arlsForceMaxBri) strip.setBrightness(scaledBri(255), true);
|
if (arlsForceMaxBri) strip.setBrightness(255, true);
|
||||||
if (briT > 0 && md == REALTIME_MODE_GENERIC) strip.show();
|
if (briT > 0 && md == REALTIME_MODE_GENERIC) strip.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void exitRealtime() {
|
void exitRealtime() {
|
||||||
if (!realtimeMode) return;
|
if (!realtimeMode) return;
|
||||||
if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE;
|
if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE;
|
||||||
strip.setBrightness(scaledBri(bri), true);
|
strip.setBrightness(bri, true);
|
||||||
realtimeTimeout = 0; // cancel realtime mode immediately
|
realtimeTimeout = 0; // cancel realtime mode immediately
|
||||||
realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately
|
realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately
|
||||||
realtimeIP[0] = 0;
|
realtimeIP[0] = 0;
|
||||||
|
|||||||
167
wled00/util.cpp
167
wled00/util.cpp
@ -1,6 +1,16 @@
|
|||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include "fcn_declare.h"
|
#include "fcn_declare.h"
|
||||||
#include "const.h"
|
#include "const.h"
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include "user_interface.h" // for bootloop detection
|
||||||
|
#else
|
||||||
|
#include <Update.h>
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||||
|
#include "esp32/rtc.h" // for bootloop detection
|
||||||
|
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
|
||||||
|
#include "soc/rtc.h"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
//helper to get int value at a position in string
|
//helper to get int value at a position in string
|
||||||
@ -619,7 +629,8 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
|
|||||||
return hw_random(diff) + lowerlimit;
|
return hw_random(diff) + lowerlimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef ESP8266
|
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3) // ESP8266 does not support PSRAM, ESP32-C3 does not have PSRAM
|
||||||
|
// p_x prefer PSRAM, d_x prefer DRAM
|
||||||
void *p_malloc(size_t size) {
|
void *p_malloc(size_t size) {
|
||||||
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
||||||
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
||||||
@ -640,6 +651,14 @@ void *p_realloc(void *ptr, size_t size) {
|
|||||||
return heap_caps_realloc(ptr, size, caps2);
|
return heap_caps_realloc(ptr, size, caps2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
|
||||||
|
void *p_realloc_malloc(void *ptr, size_t size) {
|
||||||
|
void *newbuf = p_realloc(ptr, size); // try realloc first
|
||||||
|
if (newbuf) return newbuf; // realloc successful
|
||||||
|
p_free(ptr); // free old buffer if realloc failed
|
||||||
|
return p_malloc(size); // fallback to malloc
|
||||||
|
}
|
||||||
|
|
||||||
void *p_calloc(size_t count, size_t size) {
|
void *p_calloc(size_t count, size_t size) {
|
||||||
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
||||||
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
||||||
@ -654,7 +673,7 @@ void *d_malloc(size_t size) {
|
|||||||
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
||||||
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
||||||
if (psramSafe) {
|
if (psramSafe) {
|
||||||
if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
|
if (heap_caps_get_largest_free_block(caps1) < 3*MIN_HEAP_SIZE && size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions & when DRAM is low
|
||||||
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer DRAM
|
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer DRAM
|
||||||
}
|
}
|
||||||
return heap_caps_malloc(size, caps1);
|
return heap_caps_malloc(size, caps1);
|
||||||
@ -664,12 +683,20 @@ void *d_realloc(void *ptr, size_t size) {
|
|||||||
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
||||||
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
||||||
if (psramSafe) {
|
if (psramSafe) {
|
||||||
if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
|
if (heap_caps_get_largest_free_block(caps1) < 3*MIN_HEAP_SIZE && size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions & when DRAM is low
|
||||||
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer DRAM
|
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer DRAM
|
||||||
}
|
}
|
||||||
return heap_caps_realloc(ptr, size, caps1);
|
return heap_caps_realloc(ptr, size, caps1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
|
||||||
|
void *d_realloc_malloc(void *ptr, size_t size) {
|
||||||
|
void *newbuf = d_realloc(ptr, size); // try realloc first
|
||||||
|
if (newbuf) return newbuf; // realloc successful
|
||||||
|
d_free(ptr); // free old buffer if realloc failed
|
||||||
|
return d_malloc(size); // fallback to malloc
|
||||||
|
}
|
||||||
|
|
||||||
void *d_calloc(size_t count, size_t size) {
|
void *d_calloc(size_t count, size_t size) {
|
||||||
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
|
||||||
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
|
||||||
@ -679,8 +706,142 @@ void *d_calloc(size_t count, size_t size) {
|
|||||||
}
|
}
|
||||||
return heap_caps_calloc(count, size, caps1);
|
return heap_caps_calloc(count, size, caps1);
|
||||||
}
|
}
|
||||||
|
#else // ESP8266 & ESP32-C3
|
||||||
|
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
|
||||||
|
void *realloc_malloc(void *ptr, size_t size) {
|
||||||
|
void *newbuf = realloc(ptr, size); // try realloc first
|
||||||
|
if (newbuf) return newbuf; // realloc successful
|
||||||
|
free(ptr); // free old buffer if realloc failed
|
||||||
|
return malloc(size); // fallback to malloc
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// bootloop detection and handling
|
||||||
|
// checks if the ESP reboots multiple times due to a crash or watchdog timeout
|
||||||
|
// if a bootloop is detected: restore settings from backup, then reset settings, then switch boot image (and repeat)
|
||||||
|
|
||||||
|
#define BOOTLOOP_THRESHOLD 5 // number of consecutive crashes to trigger bootloop detection
|
||||||
|
#define BOOTLOOP_ACTION_RESTORE 0 // default action: restore config from /bak.cfg.json
|
||||||
|
#define BOOTLOOP_ACTION_RESET 1 // if restore does not work, reset config (rename /cfg.json to /rst.cfg.json)
|
||||||
|
#define BOOTLOOP_ACTION_OTA 2 // swap the boot partition
|
||||||
|
#define BOOTLOOP_ACTION_DUMP 3 // nothing seems to help, dump files to serial and reboot (until hardware reset)
|
||||||
|
#ifdef ESP8266
|
||||||
|
#define BOOTLOOP_INTERVAL_TICKS (5 * 160000) // time limit between crashes: ~5 seconds in RTC ticks
|
||||||
|
#define BOOT_TIME_IDX 0 // index in RTC memory for boot time
|
||||||
|
#define CRASH_COUNTER_IDX 1 // index in RTC memory for crash counter
|
||||||
|
#define ACTIONT_TRACKER_IDX 2 // index in RTC memory for boot action
|
||||||
|
#else
|
||||||
|
#define BOOTLOOP_INTERVAL_TICKS 5000 // time limit between crashes: ~5 seconds in milliseconds
|
||||||
|
// variables in RTC_NOINIT memory persist between reboots (but not on hardware reset)
|
||||||
|
RTC_NOINIT_ATTR static uint32_t bl_last_boottime;
|
||||||
|
RTC_NOINIT_ATTR static uint32_t bl_crashcounter;
|
||||||
|
RTC_NOINIT_ATTR static uint32_t bl_actiontracker;
|
||||||
|
void bootloopCheckOTA() { bl_actiontracker = BOOTLOOP_ACTION_OTA; } // swap boot image if bootloop is detected instead of restoring config
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// detect bootloop by checking the reset reason and the time since last boot
|
||||||
|
static bool detectBootLoop() {
|
||||||
|
#if !defined(ESP8266)
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||||
|
uint32_t rtctime = esp_rtc_get_time_us() / 1000; // convert to milliseconds
|
||||||
|
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
|
||||||
|
uint64_t rtc_ticks = rtc_time_get();
|
||||||
|
uint32_t rtctime = rtc_time_slowclk_to_us(rtc_ticks, rtc_clk_slow_freq_get_hz()) / 1000; // convert to milliseconds
|
||||||
|
#endif
|
||||||
|
|
||||||
|
esp_reset_reason_t reason = esp_reset_reason();
|
||||||
|
|
||||||
|
if (!(reason == ESP_RST_PANIC || reason == ESP_RST_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_TASK_WDT)) {
|
||||||
|
// no crash detected, init variables
|
||||||
|
bl_crashcounter = 0;
|
||||||
|
bl_last_boottime = rtctime;
|
||||||
|
if(reason != ESP_RST_SW)
|
||||||
|
bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler)
|
||||||
|
} else if (reason == ESP_RST_BROWNOUT) {
|
||||||
|
// crash due to brownout can't be detected unless using flash memory to store bootloop variables
|
||||||
|
// this is a simpler way to preemtively revert the config in case current brownout is caused by a bad choice of settings
|
||||||
|
DEBUG_PRINTLN(F("brownout detected"));
|
||||||
|
//restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all)
|
||||||
|
} else {
|
||||||
|
uint32_t rebootinterval = rtctime - bl_last_boottime;
|
||||||
|
bl_last_boottime = rtctime; // store current runtime for next reboot
|
||||||
|
if (rebootinterval < BOOTLOOP_INTERVAL_TICKS) {
|
||||||
|
bl_crashcounter++;
|
||||||
|
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
|
||||||
|
DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!"));
|
||||||
|
bl_crashcounter = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else // ESP8266
|
||||||
|
rst_info* resetreason = system_get_rst_info();
|
||||||
|
uint32_t bl_last_boottime;
|
||||||
|
uint32_t bl_crashcounter;
|
||||||
|
uint32_t bl_actiontracker;
|
||||||
|
uint32_t rtctime = system_get_rtc_time();
|
||||||
|
|
||||||
|
if (!(resetreason->reason == REASON_EXCEPTION_RST || resetreason->reason == REASON_WDT_RST)) {
|
||||||
|
// no crash detected, init variables
|
||||||
|
bl_crashcounter = 0;
|
||||||
|
ESP.rtcUserMemoryWrite(BOOT_TIME_IDX, &rtctime, sizeof(uint32_t));
|
||||||
|
ESP.rtcUserMemoryWrite(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t));
|
||||||
|
if(resetreason->reason != REASON_SOFT_RESTART) {
|
||||||
|
bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler)
|
||||||
|
ESP.rtcUserMemoryWrite(ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// system has crashed
|
||||||
|
ESP.rtcUserMemoryRead(BOOT_TIME_IDX, &bl_last_boottime, sizeof(uint32_t));
|
||||||
|
ESP.rtcUserMemoryRead(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t));
|
||||||
|
uint32_t rebootinterval = rtctime - bl_last_boottime;
|
||||||
|
ESP.rtcUserMemoryWrite(BOOT_TIME_IDX, &rtctime, sizeof(uint32_t)); // store current ticks for next reboot
|
||||||
|
if (rebootinterval < BOOTLOOP_INTERVAL_TICKS) {
|
||||||
|
bl_crashcounter++;
|
||||||
|
ESP.rtcUserMemoryWrite(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t));
|
||||||
|
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
|
||||||
|
DEBUG_PRINTLN(F("BOOTLOOP DETECTED"));
|
||||||
|
bl_crashcounter = 0;
|
||||||
|
ESP.rtcUserMemoryWrite(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return false; // no bootloop detected
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleBootLoop() {
|
||||||
|
DEBUG_PRINTLN(F("checking for bootloop"));
|
||||||
|
if (!detectBootLoop()) return; // no bootloop detected
|
||||||
|
#ifdef ESP8266
|
||||||
|
uint32_t bl_actiontracker;
|
||||||
|
ESP.rtcUserMemoryRead(ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof(uint32_t));
|
||||||
|
#endif
|
||||||
|
if (bl_actiontracker == BOOTLOOP_ACTION_RESTORE) {
|
||||||
|
restoreConfig(); // note: if this fails, could reset immediately. instead just let things play out and save a few lines of code
|
||||||
|
bl_actiontracker = BOOTLOOP_ACTION_RESET; // reset config if it keeps bootlooping
|
||||||
|
} else if (bl_actiontracker == BOOTLOOP_ACTION_RESET) {
|
||||||
|
resetConfig();
|
||||||
|
bl_actiontracker = BOOTLOOP_ACTION_OTA; // swap boot partition if it keeps bootlooping. On ESP8266 this is the same as BOOTLOOP_ACTION_NONE
|
||||||
|
}
|
||||||
|
#ifndef ESP8266
|
||||||
|
else if (bl_actiontracker == BOOTLOOP_ACTION_OTA) {
|
||||||
|
if(Update.canRollBack()) {
|
||||||
|
DEBUG_PRINTLN(F("Swapping boot partition..."));
|
||||||
|
Update.rollBack(); // swap boot partition
|
||||||
|
}
|
||||||
|
bl_actiontracker = BOOTLOOP_ACTION_DUMP; // out of options
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else
|
||||||
|
dumpFilesToSerial();
|
||||||
|
#ifdef ESP8266
|
||||||
|
ESP.rtcUserMemoryWrite(ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof(uint32_t));
|
||||||
|
#endif
|
||||||
|
ESP.restart(); // restart cleanly and don't wait for another crash
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fixed point integer based Perlin noise functions by @dedehai
|
* Fixed point integer based Perlin noise functions by @dedehai
|
||||||
* Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness
|
* Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp!
|
#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp!
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include "wled_ethernet.h"
|
#include "wled_ethernet.h"
|
||||||
#include <Arduino.h>
|
#ifdef WLED_ENABLE_AOTA
|
||||||
|
#define NO_OTA_PORT
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
|
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
|
||||||
#include "soc/soc.h"
|
#include "soc/soc.h"
|
||||||
@ -105,8 +108,8 @@ void WLED::loop()
|
|||||||
if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) // block stuff if WARLS/Adalight is enabled
|
if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) // block stuff if WARLS/Adalight is enabled
|
||||||
{
|
{
|
||||||
if (apActive) dnsServer.processNextRequest();
|
if (apActive) dnsServer.processNextRequest();
|
||||||
#ifndef WLED_DISABLE_OTA
|
#ifdef WLED_ENABLE_AOTA
|
||||||
if (WLED_CONNECTED && aOtaEnabled && !otaLock && correctPIN) ArduinoOTA.handle();
|
if (Network.isConnected() && aOtaEnabled && !otaLock && correctPIN) ArduinoOTA.handle();
|
||||||
#endif
|
#endif
|
||||||
handleNightlight();
|
handleNightlight();
|
||||||
yield();
|
yield();
|
||||||
@ -187,12 +190,10 @@ void WLED::loop()
|
|||||||
doInitBusses = false;
|
doInitBusses = false;
|
||||||
DEBUG_PRINTLN(F("Re-init busses."));
|
DEBUG_PRINTLN(F("Re-init busses."));
|
||||||
bool aligned = strip.checkSegmentAlignment(); //see if old segments match old bus(ses)
|
bool aligned = strip.checkSegmentAlignment(); //see if old segments match old bus(ses)
|
||||||
BusManager::removeAll();
|
|
||||||
strip.finalizeInit(); // will create buses and also load default ledmap if present
|
strip.finalizeInit(); // will create buses and also load default ledmap if present
|
||||||
BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005
|
|
||||||
if (aligned) strip.makeAutoSegments();
|
if (aligned) strip.makeAutoSegments();
|
||||||
else strip.fixInvalidSegments();
|
else strip.fixInvalidSegments();
|
||||||
BusManager::setBrightness(bri); // fix re-initialised bus' brightness
|
BusManager::setBrightness(scaledBri(bri)); // fix re-initialised bus' brightness #4005 and #4824
|
||||||
configNeedsWrite = true;
|
configNeedsWrite = true;
|
||||||
}
|
}
|
||||||
if (loadLedmap >= 0) {
|
if (loadLedmap >= 0) {
|
||||||
@ -407,6 +408,9 @@ void WLED::setup()
|
|||||||
DEBUGFS_PRINTLN(F("FS failed!"));
|
DEBUGFS_PRINTLN(F("FS failed!"));
|
||||||
errorFlag = ERR_FS_BEGIN;
|
errorFlag = ERR_FS_BEGIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleBootLoop(); // check for bootloop and take action (requires WLED_FS)
|
||||||
|
|
||||||
#ifdef WLED_ADD_EEPROM_SUPPORT
|
#ifdef WLED_ADD_EEPROM_SUPPORT
|
||||||
else deEEP();
|
else deEEP();
|
||||||
#else
|
#else
|
||||||
@ -422,6 +426,11 @@ void WLED::setup()
|
|||||||
WLED_SET_AP_SSID(); // otherwise it is empty on first boot until config is saved
|
WLED_SET_AP_SSID(); // otherwise it is empty on first boot until config is saved
|
||||||
multiWiFi.push_back(WiFiConfig(CLIENT_SSID,CLIENT_PASS)); // initialise vector with default WiFi
|
multiWiFi.push_back(WiFiConfig(CLIENT_SSID,CLIENT_PASS)); // initialise vector with default WiFi
|
||||||
|
|
||||||
|
if(!verifyConfig()) {
|
||||||
|
if(!restoreConfig()) {
|
||||||
|
resetConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
DEBUG_PRINTLN(F("Reading config"));
|
DEBUG_PRINTLN(F("Reading config"));
|
||||||
bool needsCfgSave = deserializeConfigFromFS();
|
bool needsCfgSave = deserializeConfigFromFS();
|
||||||
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
|
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
|
||||||
@ -471,7 +480,7 @@ void WLED::setup()
|
|||||||
if (mqttClientID[0] == 0) sprintf_P(mqttClientID, PSTR("WLED-%*s"), 6, escapedMac.c_str() + 6);
|
if (mqttClientID[0] == 0) sprintf_P(mqttClientID, PSTR("WLED-%*s"), 6, escapedMac.c_str() + 6);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_OTA
|
#ifdef WLED_ENABLE_AOTA
|
||||||
if (aOtaEnabled) {
|
if (aOtaEnabled) {
|
||||||
ArduinoOTA.onStart([]() {
|
ArduinoOTA.onStart([]() {
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
@ -711,9 +720,8 @@ void WLED::initInterfaces()
|
|||||||
alexaInit();
|
alexaInit();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_OTA
|
#ifdef WLED_ENABLE_AOTA
|
||||||
if (aOtaEnabled)
|
if (aOtaEnabled) ArduinoOTA.begin();
|
||||||
ArduinoOTA.begin();
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Set up mDNS responder:
|
// Set up mDNS responder:
|
||||||
@ -784,7 +792,7 @@ void WLED::handleConnection()
|
|||||||
if (stac != stacO) {
|
if (stac != stacO) {
|
||||||
stacO = stac;
|
stacO = stac;
|
||||||
DEBUG_PRINTF_P(PSTR("Connected AP clients: %d\n"), (int)stac);
|
DEBUG_PRINTF_P(PSTR("Connected AP clients: %d\n"), (int)stac);
|
||||||
if (!WLED_CONNECTED && wifiConfigured) { // trying to connect, but not connected
|
if (!Network.isConnected() && wifiConfigured) { // trying to connect, but not connected
|
||||||
if (stac)
|
if (stac)
|
||||||
WiFi.disconnect(); // disable search so that AP can work
|
WiFi.disconnect(); // disable search so that AP can work
|
||||||
else
|
else
|
||||||
@ -859,7 +867,7 @@ void WLED::handleConnection()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If status LED pin is allocated for other uses, does nothing
|
// If status LED pin is allocated for other uses, does nothing
|
||||||
// else blink at 1Hz when WLED_CONNECTED is false (no WiFi, ?? no Ethernet ??)
|
// else blink at 1Hz when Network.isConnected() is false (no WiFi, ?? no Ethernet ??)
|
||||||
// else blink at 2Hz when MQTT is enabled but not connected
|
// else blink at 2Hz when MQTT is enabled but not connected
|
||||||
// else turn the status LED off
|
// else turn the status LED off
|
||||||
#if defined(STATUSLED)
|
#if defined(STATUSLED)
|
||||||
@ -873,7 +881,7 @@ void WLED::handleStatusLED()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (WLED_CONNECTED) {
|
if (Network.isConnected()) {
|
||||||
c = RGBW32(0,255,0,0);
|
c = RGBW32(0,255,0,0);
|
||||||
ledStatusType = 2;
|
ledStatusType = 2;
|
||||||
} else if (WLED_MQTT_CONNECTED) {
|
} else if (WLED_MQTT_CONNECTED) {
|
||||||
|
|||||||
@ -21,6 +21,12 @@
|
|||||||
|
|
||||||
// You are required to disable over-the-air updates:
|
// You are required to disable over-the-air updates:
|
||||||
//#define WLED_DISABLE_OTA // saves 14kb
|
//#define WLED_DISABLE_OTA // saves 14kb
|
||||||
|
#ifdef WLED_ENABLE_AOTA
|
||||||
|
#if defined(WLED_DISABLE_OTA)
|
||||||
|
#warning WLED_DISABLE_OTA was defined but it will be ignored due to WLED_ENABLE_AOTA.
|
||||||
|
#endif
|
||||||
|
#undef WLED_DISABLE_OTA
|
||||||
|
#endif
|
||||||
|
|
||||||
// You can choose some of these features to disable:
|
// You can choose some of these features to disable:
|
||||||
//#define WLED_DISABLE_ALEXA // saves 11kb
|
//#define WLED_DISABLE_ALEXA // saves 11kb
|
||||||
@ -121,10 +127,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
#include <DNSServer.h>
|
#include <DNSServer.h>
|
||||||
#ifndef WLED_DISABLE_OTA
|
|
||||||
#define NO_OTA_PORT
|
|
||||||
#include <ArduinoOTA.h>
|
|
||||||
#endif
|
|
||||||
#include <SPIFFSEditor.h>
|
#include <SPIFFSEditor.h>
|
||||||
#include "src/dependencies/time/TimeLib.h"
|
#include "src/dependencies/time/TimeLib.h"
|
||||||
#include "src/dependencies/timezone/Timezone.h"
|
#include "src/dependencies/timezone/Timezone.h"
|
||||||
@ -192,6 +194,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
|
|||||||
#include "fcn_declare.h"
|
#include "fcn_declare.h"
|
||||||
#include "NodeStruct.h"
|
#include "NodeStruct.h"
|
||||||
#include "pin_manager.h"
|
#include "pin_manager.h"
|
||||||
|
#include "colors.h"
|
||||||
#include "bus_manager.h"
|
#include "bus_manager.h"
|
||||||
#include "FX.h"
|
#include "FX.h"
|
||||||
|
|
||||||
@ -588,7 +591,7 @@ WLED_GLOBAL bool otaLock _INIT(true); // prevents OTA firmware update
|
|||||||
WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
|
WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
|
||||||
#endif
|
#endif
|
||||||
WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled
|
WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled
|
||||||
#ifndef WLED_DISABLE_OTA
|
#ifdef WLED_ENABLE_AOTA
|
||||||
WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
|
WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
|
||||||
#else
|
#else
|
||||||
WLED_GLOBAL bool aOtaEnabled _INIT(false); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
|
WLED_GLOBAL bool aOtaEnabled _INIT(false); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on
|
||||||
@ -623,7 +626,6 @@ WLED_GLOBAL unsigned long transitionStartTime;
|
|||||||
WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt")
|
WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt")
|
||||||
WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s)
|
WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s)
|
||||||
WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random
|
WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random
|
||||||
WLED_GLOBAL bool useRainbowWheel _INIT(false); // use "rainbow" color wheel instead of "spectrum" color wheel
|
|
||||||
|
|
||||||
// nightlight
|
// nightlight
|
||||||
WLED_GLOBAL bool nightlightActive _INIT(false);
|
WLED_GLOBAL bool nightlightActive _INIT(false);
|
||||||
@ -1024,11 +1026,7 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0);
|
|||||||
WLED_GLOBAL unsigned loops _INIT(0);
|
WLED_GLOBAL unsigned loops _INIT(0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#define WLED_CONNECTED (Network.isConnected())
|
||||||
#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED || ETH.localIP()[0] != 0)
|
|
||||||
#else
|
|
||||||
#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef WLED_AP_SSID_UNIQUE
|
#ifndef WLED_AP_SSID_UNIQUE
|
||||||
#define WLED_SET_AP_SSID() do { \
|
#define WLED_SET_AP_SSID() do { \
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
|
|
||||||
#ifdef ESP8266
|
#ifndef WLED_DISABLE_OTA
|
||||||
#include <Updater.h>
|
#ifdef ESP8266
|
||||||
#else
|
#include <Updater.h>
|
||||||
#include <Update.h>
|
#else
|
||||||
|
#include <Update.h>
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#include "html_ui.h"
|
#include "html_ui.h"
|
||||||
#include "html_settings.h"
|
#include "html_settings.h"
|
||||||
@ -387,6 +389,7 @@ void initServer()
|
|||||||
createEditHandler(correctPIN);
|
createEditHandler(correctPIN);
|
||||||
|
|
||||||
static const char _update[] PROGMEM = "/update";
|
static const char _update[] PROGMEM = "/update";
|
||||||
|
#ifndef WLED_DISABLE_OTA
|
||||||
//init ota page
|
//init ota page
|
||||||
server.on(_update, HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on(_update, HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
if (otaLock) {
|
if (otaLock) {
|
||||||
@ -408,6 +411,9 @@ void initServer()
|
|||||||
serveMessage(request, 500, F("Update failed!"), F("Please check your file and retry!"), 254);
|
serveMessage(request, 500, F("Update failed!"), F("Please check your file and retry!"), 254);
|
||||||
} else {
|
} else {
|
||||||
serveMessage(request, 200, F("Update successful!"), FPSTR(s_rebooting), 131);
|
serveMessage(request, 200, F("Update successful!"), FPSTR(s_rebooting), 131);
|
||||||
|
#ifndef ESP8266
|
||||||
|
bootloopCheckOTA(); // let the bootloop-checker know there was an OTA update
|
||||||
|
#endif
|
||||||
doReboot = true;
|
doReboot = true;
|
||||||
}
|
}
|
||||||
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
|
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
|
||||||
@ -426,8 +432,9 @@ void initServer()
|
|||||||
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
|
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
|
||||||
lastEditTime = millis(); // make sure PIN does not lock during update
|
lastEditTime = millis(); // make sure PIN does not lock during update
|
||||||
strip.suspend();
|
strip.suspend();
|
||||||
#ifdef ESP8266
|
backupConfig(); // backup current config in case the update ends badly
|
||||||
strip.resetSegments(); // free as much memory as you can
|
strip.resetSegments(); // free as much memory as you can
|
||||||
|
#ifdef ESP8266
|
||||||
Update.runAsync(true);
|
Update.runAsync(true);
|
||||||
#endif
|
#endif
|
||||||
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
||||||
@ -446,14 +453,17 @@ void initServer()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
#else
|
||||||
|
const auto notSupported = [](AsyncWebServerRequest *request){
|
||||||
|
serveMessage(request, 501, FPSTR(s_notimplemented), F("This build does not support OTA update."), 254);
|
||||||
|
};
|
||||||
|
server.on(_update, HTTP_GET, notSupported);
|
||||||
|
server.on(_update, HTTP_POST, notSupported, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){});
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WLED_ENABLE_DMX
|
#ifdef WLED_ENABLE_DMX
|
||||||
server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap , dmxProcessor);
|
request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap, dmxProcessor);
|
||||||
});
|
|
||||||
#else
|
|
||||||
server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){
|
|
||||||
serveMessage(request, 501, FPSTR(s_notimplemented), F("DMX support is not enabled in this build."), 254);
|
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -657,6 +667,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
|
|||||||
case SUBPAGE_DMX : content = PAGE_settings_dmx; len = PAGE_settings_dmx_length; break;
|
case SUBPAGE_DMX : content = PAGE_settings_dmx; len = PAGE_settings_dmx_length; break;
|
||||||
#endif
|
#endif
|
||||||
case SUBPAGE_UM : content = PAGE_settings_um; len = PAGE_settings_um_length; break;
|
case SUBPAGE_UM : content = PAGE_settings_um; len = PAGE_settings_um_length; break;
|
||||||
|
#ifndef WLED_DISABLE_OTA
|
||||||
case SUBPAGE_UPDATE : content = PAGE_update; len = PAGE_update_length;
|
case SUBPAGE_UPDATE : content = PAGE_update; len = PAGE_update_length;
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
if (request->hasArg(F("revert")) && inLocalSubnet(request->client()->remoteIP()) && Update.canRollBack()) {
|
if (request->hasArg(F("revert")) && inLocalSubnet(request->client()->remoteIP()) && Update.canRollBack()) {
|
||||||
@ -670,6 +681,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
#ifndef WLED_DISABLE_2D
|
#ifndef WLED_DISABLE_2D
|
||||||
case SUBPAGE_2D : content = PAGE_settings_2D; len = PAGE_settings_2D_length; break;
|
case SUBPAGE_2D : content = PAGE_settings_2D; len = PAGE_settings_2D_length; break;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -26,7 +26,8 @@ void XML_response(Print& dest)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void extractPin(Print& settingsScript, const JsonObject &obj, const char *key) {
|
static void extractPin(Print& settingsScript, const JsonObject &obj, const char *key)
|
||||||
|
{
|
||||||
if (obj[key].is<JsonArray>()) {
|
if (obj[key].is<JsonArray>()) {
|
||||||
JsonArray pins = obj[key].as<JsonArray>();
|
JsonArray pins = obj[key].as<JsonArray>();
|
||||||
for (JsonVariant pv : pins) {
|
for (JsonVariant pv : pins) {
|
||||||
@ -37,6 +38,22 @@ static void extractPin(Print& settingsScript, const JsonObject &obj, const char
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fillWLEDVersion(char *buf, size_t len)
|
||||||
|
{
|
||||||
|
if (!buf || len == 0) return;
|
||||||
|
|
||||||
|
snprintf_P(buf,len,PSTR("WLED %s (%d)<br>\\\"%s\\\"<br>(Processor: %s)"),
|
||||||
|
versionString,
|
||||||
|
VERSION,
|
||||||
|
releaseString,
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
ESP.getChipModel()
|
||||||
|
#else
|
||||||
|
"ESP8266"
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// print used pins by scanning JsonObject (1 level deep)
|
// print used pins by scanning JsonObject (1 level deep)
|
||||||
static void fillUMPins(Print& settingsScript, const JsonObject &mods)
|
static void fillUMPins(Print& settingsScript, const JsonObject &mods)
|
||||||
{
|
{
|
||||||
@ -72,7 +89,8 @@ static void fillUMPins(Print& settingsScript, const JsonObject &mods)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void appendGPIOinfo(Print& settingsScript) {
|
void appendGPIOinfo(Print& settingsScript)
|
||||||
|
{
|
||||||
settingsScript.print(F("d.um_p=[-1")); // has to have 1 element
|
settingsScript.print(F("d.um_p=[-1")); // has to have 1 element
|
||||||
if (i2c_sda > -1 && i2c_scl > -1) {
|
if (i2c_sda > -1 && i2c_scl > -1) {
|
||||||
settingsScript.printf_P(PSTR(",%d,%d"), i2c_sda, i2c_scl);
|
settingsScript.printf_P(PSTR(",%d,%d"), i2c_sda, i2c_scl);
|
||||||
@ -311,6 +329,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
|||||||
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed
|
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 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
|
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current
|
||||||
|
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
|
||||||
settingsScript.print(F("addLEDs(1);"));
|
settingsScript.print(F("addLEDs(1);"));
|
||||||
uint8_t pins[OUTPUT_MAX_PINS];
|
uint8_t pins[OUTPUT_MAX_PINS];
|
||||||
int nPins = bus->getPins(pins);
|
int nPins = bus->getPins(pins);
|
||||||
@ -350,6 +369,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
|||||||
printSetFormValue(settingsScript,sp,speed);
|
printSetFormValue(settingsScript,sp,speed);
|
||||||
printSetFormValue(settingsScript,la,bus->getLEDCurrent());
|
printSetFormValue(settingsScript,la,bus->getLEDCurrent());
|
||||||
printSetFormValue(settingsScript,ma,bus->getMaxCurrent());
|
printSetFormValue(settingsScript,ma,bus->getMaxCurrent());
|
||||||
|
printSetFormValue(settingsScript,hs,bus->getCustomText().c_str());
|
||||||
sumMa += bus->getMaxCurrent();
|
sumMa += bus->getMaxCurrent();
|
||||||
}
|
}
|
||||||
printSetFormValue(settingsScript,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa);
|
printSetFormValue(settingsScript,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa);
|
||||||
@ -380,7 +400,6 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
|||||||
printSetFormValue(settingsScript,PSTR("TL"),nightlightDelayMinsDefault);
|
printSetFormValue(settingsScript,PSTR("TL"),nightlightDelayMinsDefault);
|
||||||
printSetFormValue(settingsScript,PSTR("TW"),nightlightMode);
|
printSetFormValue(settingsScript,PSTR("TW"),nightlightMode);
|
||||||
printSetFormIndex(settingsScript,PSTR("PB"),paletteBlend);
|
printSetFormIndex(settingsScript,PSTR("PB"),paletteBlend);
|
||||||
printSetFormCheckbox(settingsScript,PSTR("RW"),useRainbowWheel);
|
|
||||||
printSetFormValue(settingsScript,PSTR("RL"),rlyPin);
|
printSetFormValue(settingsScript,PSTR("RL"),rlyPin);
|
||||||
printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde);
|
printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde);
|
||||||
printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain);
|
printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain);
|
||||||
@ -593,11 +612,14 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
|||||||
printSetFormCheckbox(settingsScript,PSTR("AO"),aOtaEnabled);
|
printSetFormCheckbox(settingsScript,PSTR("AO"),aOtaEnabled);
|
||||||
printSetFormCheckbox(settingsScript,PSTR("SU"),otaSameSubnet);
|
printSetFormCheckbox(settingsScript,PSTR("SU"),otaSameSubnet);
|
||||||
char tmp_buf[128];
|
char tmp_buf[128];
|
||||||
snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s (build %d)"),versionString,VERSION);
|
fillWLEDVersion(tmp_buf,sizeof(tmp_buf));
|
||||||
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
|
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
|
||||||
settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription);
|
settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription);
|
||||||
#ifdef WLED_DISABLE_OTA
|
|
||||||
//hide settings if not compiled
|
//hide settings if not compiled
|
||||||
|
#ifdef WLED_DISABLE_OTA
|
||||||
|
settingsScript.print(F("toggle('OTA');")); // hide update section
|
||||||
|
#endif
|
||||||
|
#ifndef WLED_ENABLE_AOTA
|
||||||
settingsScript.print(F("toggle('aOTA');")); // hide ArduinoOTA checkbox
|
settingsScript.print(F("toggle('aOTA');")); // hide ArduinoOTA checkbox
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -652,16 +674,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
|||||||
if (subPage == SUBPAGE_UPDATE) // update
|
if (subPage == SUBPAGE_UPDATE) // update
|
||||||
{
|
{
|
||||||
char tmp_buf[128];
|
char tmp_buf[128];
|
||||||
snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s<br>%s<br>(%s build %d)"),
|
fillWLEDVersion(tmp_buf,sizeof(tmp_buf));
|
||||||
versionString,
|
|
||||||
releaseString,
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
ESP.getChipModel(),
|
|
||||||
#else
|
|
||||||
"esp8266",
|
|
||||||
#endif
|
|
||||||
VERSION);
|
|
||||||
|
|
||||||
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
|
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
|
||||||
#ifndef ARDUINO_ARCH_ESP32
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
settingsScript.print(F("toggle('rev');")); // hide revert button on ESP8266
|
settingsScript.print(F("toggle('rev');")); // hide revert button on ESP8266
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user