Compare commits
	
		
			83 Commits
		
	
	
		
			multibutto
			...
			copilot/fi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 8590538272 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | f75b13a29a | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 1b19c35625 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 2dfea5c1e2 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | a441ee81b6 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 6c0c875fe6 | ||
|   | 9d706010f5 | ||
|   | 97b20438fd | ||
|   | 65913f990d | ||
|   | 56d00357d3 | ||
|   | 1864e550e6 | ||
|   | 75f6de9dc2 | ||
|   | 16cfbf7500 | ||
|   | aecac2c56c | ||
|   | 385504e6db | ||
|   | a0321170d0 | ||
|   | d70018ae9f | ||
|   | 9359f0b7fc | ||
|   | be74196a62 | ||
|   | 705f2035f4 | ||
|   | 65efcb351e | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 72ad39d6a7 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 5950204d34 | ||
|   | 46df9410b3 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | b7e4cd0d9a | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 0becd61323 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 3f2e92c4c5 | ||
|   | 666a59ff53 | ||
|   | ce7ca3f2d2 | ||
|   | 87092ccb80 | ||
|   | a037d99469 | ||
|   | 8cc5d64819 | ||
|   | 4c948cca13 | ||
|   | 5cb8dc3978 | ||
|   | 8fc87aa17d | ||
|   | c8757d45c8 | ||
|   | 62fad4dcdf | ||
|   | da7f107273 | ||
|   | d5d7fde30f | ||
|   | 6f914d79b1 | ||
|   | dd13c2df47 | ||
|   | 8aeb9e1abe | ||
|   | cfad0b8a52 | ||
|   | a60be251d2 | ||
|   | f15c1fbca6 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 708baf1ed7 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 4155a6bc23 | ||
|   | c92f0a9d90 | ||
|   | 5fa901c37c | ||
|   | 46f3bc0ced | ||
|   | f8ce5980a1 | ||
|   | 4b5c3a396d | ||
|   | 550b4d9dea | ||
|   | f3e3f585df | ||
|   | 2082b01a3c | ||
|   | 8baa6a4616 | ||
|   | 1fb9eb771e | ||
|   | dee581f58d | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 7943b00017 | ||
|   | 4de6656bc4 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | cd8ddb81e1 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 890860ebf6 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 624042d97e | ||
|   | 85d4db83ed | ||
|   | 5146926723 | ||
|   | 3b5c6ca284 | ||
|   | dcc1fbc96e | ||
|   | 7865985eeb | ||
|   | 4ac7eb7eb2 | ||
|   | 7285efebca | ||
|   | af2d46c30d | ||
|   | f4d89c4196 | ||
|   | c9c442a933 | ||
|   | b8b59b2bb1 | ||
|   | c33e303323 | ||
|   | caf3c7a2f9 | ||
|   | c8d8ab020e | ||
|   | 297d5ced75 | ||
|   | 3f90366aa8 | ||
|   | e3653baf74 | ||
|   | f74d1459b9 | ||
|   | e374c7ae55 | ||
|   | 9e4675ef46 | 
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,3 @@ | ||||
| github: [Aircoookie,blazoncek] | ||||
| github: [Aircoookie,blazoncek,DedeHai,lost-hope,willmmiles] | ||||
| custom: ['https://paypal.me/Aircoookie','https://paypal.me/blazoncek'] | ||||
| thanks_dev: u/gh/netmindz | ||||
|   | ||||
							
								
								
									
										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 ci` (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: | ||||
|         node-version-file: '.nvmrc' | ||||
|         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 | ||||
|       uses: actions/cache@v4 | ||||
|       with: | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							| @@ -27,6 +27,8 @@ jobs: | ||||
|         with: | ||||
|           token: ${{ secrets.GITHUB_TOKEN }}  | ||||
|           sinceTag: v0.15.0 | ||||
|           # Exclude issues that were closed without resolution from changelog | ||||
|           exclude-labels: 'stale,wontfix,duplicate,invalid' | ||||
|       - name: Update Nightly Release | ||||
|         uses: andelf/nightly-release@main | ||||
|         env: | ||||
|   | ||||
							
								
								
									
										19
									
								
								.github/workflows/pr-merge.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/pr-merge.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -1,12 +1,13 @@ | ||||
|     name: Notify Discord on PR Merge | ||||
|     on: | ||||
|       workflow_dispatch: | ||||
|       pull_request: | ||||
|       pull_request_target: | ||||
|         types: [closed] | ||||
|  | ||||
|     jobs: | ||||
|       notify: | ||||
|         runs-on: ubuntu-latest | ||||
|         if: github.event.pull_request.merged == true | ||||
|         steps: | ||||
|         - name: Get User Permission | ||||
|           id: checkAccess | ||||
| @@ -23,11 +24,15 @@ | ||||
|             echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}" | ||||
|             echo "Job originally triggered by ${{ github.actor }}" | ||||
|             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 | ||||
|           # 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: | | ||||
|             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}. It will be included in the next nightly builds, please test" \ | ||||
|               '{content: $content}' \ | ||||
|               | curl -H "Content-Type: application/json" -d @- ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }} | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -24,7 +24,9 @@ jobs: | ||||
|       with: | ||||
|           token: ${{ secrets.GITHUB_TOKEN }}  | ||||
|           sinceTag: v0.15.0 | ||||
|           maxIssues: 500        | ||||
|           maxIssues: 500 | ||||
|           # Exclude issues that were closed without resolution from changelog | ||||
|           exclude-labels: 'stale,wontfix,duplicate,invalid'        | ||||
|     - name: Create draft release | ||||
|       uses: softprops/action-gh-release@v1 | ||||
|       with: | ||||
|   | ||||
							
								
								
									
										3
									
								
								.github/workflows/usermods.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/usermods.yml
									
									
									
									
										vendored
									
									
								
							| @@ -5,6 +5,9 @@ on: | ||||
|     paths: | ||||
|       - usermods/** | ||||
|       - .github/workflows/usermods.yml | ||||
|   pull_request: | ||||
|     paths: | ||||
|       - usermods/** | ||||
|      | ||||
| jobs: | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
| # ------------------------------------------------------------------------------ | ||||
|  | ||||
| # 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 | ||||
| data_dir = ./wled00/data | ||||
| @@ -142,7 +142,8 @@ lib_deps = | ||||
|     IRremoteESP8266 @ 2.8.2 | ||||
|     makuna/NeoPixelBus @ 2.8.3 | ||||
|     #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 | ||||
|     marvinroger/AsyncMqttClient @ 0.9.0 | ||||
|   # for I2C interface | ||||
|     ;Wire | ||||
|   # ESP-NOW library | ||||
| @@ -234,25 +235,20 @@ lib_deps_compat = | ||||
|  | ||||
| [esp32_all_variants] | ||||
| lib_deps = | ||||
|   willmmiles/AsyncTCP @ 1.3.1 | ||||
|   esp32async/AsyncTCP @ 3.4.7 | ||||
|   bitbank2/AnimatedGIF@^1.4.7 | ||||
|   https://github.com/Aircoookie/GifDecoder#bc3af18 | ||||
| build_flags = | ||||
|   -D CONFIG_ASYNC_TCP_USE_WDT=0 | ||||
|   -D CONFIG_ASYNC_TCP_STACK_SIZE=8192 | ||||
|   -D WLED_ENABLE_GIF | ||||
|  | ||||
| [esp32] | ||||
| #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip | ||||
| platform = espressif32@3.5.0 | ||||
| platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4 | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| platform_packages = | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = -g | ||||
|   -DARDUINO_ARCH_ESP32 | ||||
|   #-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} | ||||
| build_flags = ${esp32_idf_V4.build_flags} | ||||
| lib_deps = ${esp32_idf_V4.lib_deps} | ||||
|  | ||||
| tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv | ||||
| default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv | ||||
| @@ -260,10 +256,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 | ||||
| large_partitions = tools/WLED_ESP32_8MB.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 | ||||
| # additional build flags for audioreactive - must be applied globally | ||||
| AR_build_flags = ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster) | ||||
| @@ -271,8 +264,7 @@ AR_lib_deps =  ;; for pre-usermod-library platformio_override compatibility | ||||
|  | ||||
|  | ||||
| [esp32_idf_V4] | ||||
| ;; experimental 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. | ||||
| ;; build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 | ||||
| ;; | ||||
| ;; 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. | ||||
| @@ -283,14 +275,12 @@ build_unflags = ${common.build_unflags} | ||||
| build_flags = -g | ||||
|   -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one | ||||
|   -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} | ||||
|   -D WLED_ENABLE_DMX_INPUT | ||||
| lib_deps = | ||||
|   ${esp32_all_variants.lib_deps} | ||||
|   https://github.com/someweisguy/esp_dmx.git#47db25d | ||||
|   ${env.lib_deps} | ||||
| board_build.partitions = ${esp32.default_partitions}   ;; default partioning for 4MB Flash - can be overridden in build envs | ||||
|  | ||||
| [esp32s2] | ||||
| ;; generic definitions for all ESP32-S2 boards | ||||
| @@ -305,10 +295,9 @@ build_flags = -g | ||||
|   -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: | ||||
|   ;; ARDUINO_USB_CDC_ON_BOOT | ||||
|   ${esp32_all_variants.build_flags} | ||||
|   ${esp32_idf_V4.build_flags} | ||||
| lib_deps = | ||||
|   ${esp32_all_variants.lib_deps} | ||||
|   ${env.lib_deps} | ||||
|   ${esp32_idf_V4.lib_deps} | ||||
| board_build.partitions = ${esp32.default_partitions}   ;; default partioning for 4MB Flash - can be overridden in build envs | ||||
|  | ||||
| [esp32c3] | ||||
| @@ -323,10 +312,9 @@ build_flags = -g | ||||
|   -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: | ||||
|   ;; ARDUINO_USB_CDC_ON_BOOT | ||||
|   ${esp32_all_variants.build_flags} | ||||
|   ${esp32_idf_V4.build_flags} | ||||
| lib_deps = | ||||
|   ${esp32_all_variants.lib_deps} | ||||
|   ${env.lib_deps} | ||||
|   ${esp32_idf_V4.lib_deps} | ||||
| board_build.partitions = ${esp32.default_partitions}   ;; default partioning for 4MB Flash - can be overridden in build envs | ||||
| board_build.flash_mode = qio | ||||
|  | ||||
| @@ -343,10 +331,9 @@ build_flags = -g | ||||
|   -DCO | ||||
|   ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: | ||||
|   ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT | ||||
|   ${esp32_all_variants.build_flags} | ||||
|   ${esp32_idf_V4.build_flags} | ||||
| lib_deps = | ||||
|   ${esp32_all_variants.lib_deps} | ||||
|   ${env.lib_deps} | ||||
|   ${esp32_idf_V4.lib_deps} | ||||
| board_build.partitions = ${esp32.large_partitions}   ;; default partioning for 8MB flash - can be overridden in build envs | ||||
|  | ||||
|  | ||||
| @@ -441,21 +428,11 @@ custom_usermods = audioreactive | ||||
|  | ||||
| [env: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} | ||||
| build_unflags = ${common.build_unflags} | ||||
| custom_usermods = audioreactive | ||||
| 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} | ||||
| monitor_filters = esp32_exception_decoder | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
| @@ -489,23 +466,9 @@ board_upload.maximum_size = 16777216 | ||||
| board_build.f_flash = 80000000L | ||||
| 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] | ||||
| board = esp32-poe | ||||
| platform = ${esp32.platform} | ||||
| platform_packages = ${esp32.platform_packages} | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| upload_speed = 921600 | ||||
| custom_usermods = audioreactive | ||||
| build_unflags = ${common.build_unflags} | ||||
| @@ -513,10 +476,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 | ||||
| lib_deps = ${esp32.lib_deps} | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
| board_build.flash_mode = dio | ||||
|  | ||||
| [env:esp32_wrover] | ||||
| extends = esp32_idf_V4 | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| board = ttgo-t7-v14-mini32 | ||||
| board_build.f_flash = 80000000L | ||||
| board_build.flash_mode = qio | ||||
|   | ||||
| @@ -28,7 +28,6 @@ lib_deps = ${esp8266.lib_deps} | ||||
| ;  robtillaart/SHT85@~0.3.3 | ||||
| ;  ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug | ||||
| ;  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 | ||||
|  | ||||
| build_unflags = ${common.build_unflags} | ||||
|   | ||||
| @@ -10,10 +10,12 @@ | ||||
|  | ||||
|   </p> | ||||
|  | ||||
| # Welcome to my project WLED! ✨ | ||||
| # Welcome to WLED! ✨ | ||||
|  | ||||
| A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! | ||||
|  | ||||
| Originally created by [Aircoookie](https://github.com/Aircoookie) | ||||
|  | ||||
| ## ⚙️ Features | ||||
| - WS2812FX library with more than 100 special effects   | ||||
| - FastLED noise effects and 50 palettes   | ||||
| @@ -32,7 +34,7 @@ A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to cont | ||||
| - Filesystem-based config for easier backup of presets and settings   | ||||
|  | ||||
| ## 💡 Supported light control interfaces | ||||
| - WLED app for [Android](https://play.google.com/store/apps/details?id=com.aircoookie.WLED) and [iOS](https://apps.apple.com/us/app/wled/id1475695033) | ||||
| - WLED app for [Android](https://play.google.com/store/apps/details?id=ca.cgagnier.wlednativeandroid) and [iOS](https://apps.apple.com/gb/app/wled-native/id6446207239) | ||||
| - JSON and HTTP request APIs   | ||||
| - MQTT    | ||||
| - E1.31, Art-Net, DDP and TPM2.net | ||||
| @@ -63,6 +65,7 @@ See [here](https://kno.wled.ge/basics/compatible-hardware)! | ||||
|  | ||||
| Licensed under the EUPL v1.2 license   | ||||
| Credits [here](https://kno.wled.ge/about/contributors/)! | ||||
| CORS proxy by [Corsfix](https://corsfix.com/) | ||||
|  | ||||
| Join the Discord server to discuss everything about WLED! | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| default_envs = usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp32s3 | ||||
|  | ||||
| [env:usermods_esp32] | ||||
| extends = env:esp32dev_V4 | ||||
| extends = env:esp32dev | ||||
| custom_usermods = ${usermods.custom_usermods} | ||||
| board_build.partitions = ${esp32.extreme_partitions}  ; We're gonna need a bigger boat | ||||
|  | ||||
| @@ -28,4 +28,4 @@ board_build.partitions = ${esp32.extreme_partitions}  ; We're gonna need a bigge | ||||
|  | ||||
|  | ||||
| [usermods] | ||||
| # Added in CI | ||||
| # Added in CI | ||||
|   | ||||
							
								
								
									
										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", | ||||
|   "build": { "libArchive": false}, | ||||
|   "dependencies": { | ||||
|     "bitbank2/PNGdec":"^1.0.3" | ||||
|   } | ||||
|   "platforms": ["espressif32"] | ||||
| } | ||||
							
								
								
									
										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 <PNGdec.h> | ||||
| #include "pov.h" | ||||
|  | ||||
| void * openFile(const char *filename, int32_t *size) { | ||||
|     f = WLED_FS.open(filename); | ||||
|     *size = f.size(); | ||||
|     return &f; | ||||
| } | ||||
| static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;;"; | ||||
|  | ||||
| void closeFile(void *handle) { | ||||
|     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(); | ||||
| } | ||||
| static POV s_pov; | ||||
|  | ||||
| uint16_t mode_pov_image(void) { | ||||
|     const char * filepath = SEGMENT.name; | ||||
|     int rc = png.open(filepath, openFile, closeFile, readFile, seekFile, draw); | ||||
|     if (rc == PNG_SUCCESS) { | ||||
| 	rc = png.decode(NULL, 0); | ||||
| 	png.close(); | ||||
| 	return FRAMETIME; | ||||
|     } | ||||
|   Segment& mainseg = strip.getMainSegment(); | ||||
|   const char* segName = mainseg.name; | ||||
|   if (!segName) { | ||||
|      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; | ||||
|   } | ||||
|  | ||||
|   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 | ||||
| { | ||||
|   public: | ||||
|     static const char _data_FX_MODE_POV_IMAGE[] PROGMEM = "POV Image@!;;;1"; | ||||
| class PovDisplayUsermod : public Usermod { | ||||
| protected: | ||||
|   bool enabled = false; //WLEDMM | ||||
|   const char *_name; //WLEDMM | ||||
|   bool initDone = false; //WLEDMM | ||||
|   unsigned long lastTime = 0; //WLEDMM | ||||
| public: | ||||
|  | ||||
|     PNG png; | ||||
|     File f; | ||||
|   PovDisplayUsermod(const char *name, bool enabled) | ||||
|     : 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() | ||||
|     { | ||||
|       return USERMOD_ID_POV_DISPLAY; | ||||
|     } | ||||
|  | ||||
|     void connected() {} | ||||
|   uint16_t getId() override { | ||||
|     return USERMOD_ID_POV_DISPLAY; | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| static PovDisplayUsermod pov_display; | ||||
| REGISTER_USERMOD(pov_display); | ||||
| static PovDisplayUsermod pov_display("POV Display", false); | ||||
| 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 | 
							
								
								
									
										5
									
								
								usermods/udp_name_sync/library.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								usermods/udp_name_sync/library.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| { | ||||
|   "name": "udp_name_sync", | ||||
|   "build": { "libArchive": false }, | ||||
|   "dependencies": {} | ||||
| } | ||||
							
								
								
									
										85
									
								
								usermods/udp_name_sync/udp_name_sync.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								usermods/udp_name_sync/udp_name_sync.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| #include "wled.h" | ||||
|  | ||||
| class UdpNameSync : public Usermod { | ||||
|  | ||||
|   private: | ||||
|  | ||||
|     bool enabled = false; | ||||
|     char segmentName[WLED_MAX_SEGNAME_LEN] = {0}; | ||||
|     static constexpr uint8_t kPacketType = 200; // custom usermod packet type | ||||
|     static const char _name[]; | ||||
|     static const char _enabled[]; | ||||
|  | ||||
|   public: | ||||
|     /** | ||||
|      * Enable/Disable the usermod | ||||
|      */ | ||||
|     inline void enable(bool value) { enabled = value; } | ||||
|  | ||||
|     /** | ||||
|      * Get usermod enabled/disabled state | ||||
|      */ | ||||
|     inline bool isEnabled() const { return enabled; } | ||||
|  | ||||
|     void setup() override { | ||||
|       // Enabled when this usermod is compiled, set to false if you prefer runtime opt-in | ||||
|       enable(true); | ||||
|     } | ||||
|  | ||||
|     void loop() override { | ||||
|       if (!enabled) return; | ||||
|       if (!WLED_CONNECTED) return; | ||||
|       if (!udpConnected) return; | ||||
|       Segment& mainseg = strip.getMainSegment(); | ||||
|       if (segmentName[0] == '\0' && !mainseg.name) return; //name was never set, do nothing | ||||
|  | ||||
|       const char* curName = mainseg.name ? mainseg.name : ""; | ||||
|       if (strncmp(curName, segmentName, sizeof(segmentName)) == 0) return; // same name, do nothing | ||||
|  | ||||
|       IPAddress broadcastIp = uint32_t(Network.localIP()) | ~uint32_t(Network.subnetMask()); | ||||
|       byte udpOut[WLED_MAX_SEGNAME_LEN + 2]; | ||||
|       udpOut[0] = kPacketType; // custom usermod packet type (avoid 0..5 used by core protocols) | ||||
|  | ||||
|       if (segmentName[0] != '\0' && !mainseg.name) { // name cleared | ||||
|         notifierUdp.beginPacket(broadcastIp, udpPort); | ||||
|         segmentName[0] = '\0'; | ||||
|         DEBUG_PRINTLN(F("UdpNameSync: sending empty name")); | ||||
|         udpOut[1] = 0; // explicit empty string | ||||
|         notifierUdp.write(udpOut, 2); | ||||
|         notifierUdp.endPacket(); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       notifierUdp.beginPacket(broadcastIp, udpPort); | ||||
|       DEBUG_PRINT(F("UdpNameSync: saving segment name ")); | ||||
|       DEBUG_PRINTLN(curName); | ||||
|       strlcpy(segmentName, curName, sizeof(segmentName)); | ||||
|       strlcpy((char *)&udpOut[1], segmentName, sizeof(udpOut) - 1); // leave room for header byte | ||||
|       size_t nameLen = strnlen((char *)&udpOut[1], sizeof(udpOut) - 1); | ||||
|       notifierUdp.write(udpOut, 2 + nameLen); | ||||
|       notifierUdp.endPacket(); | ||||
|       DEBUG_PRINT(F("UdpNameSync: Sent segment name : ")); | ||||
|       DEBUG_PRINTLN(segmentName); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     bool onUdpPacket(uint8_t * payload, size_t len) override { | ||||
|       DEBUG_PRINT(F("UdpNameSync: Received packet")); | ||||
|       if (!enabled) return false; | ||||
|       if (receiveDirect) return false; | ||||
|       if (len < 2) return false;                 // need type + at least 1 byte for name (can be 0) | ||||
|       if (payload[0] != kPacketType) return false; | ||||
|       Segment& mainseg = strip.getMainSegment(); | ||||
|       char tmp[WLED_MAX_SEGNAME_LEN] = {0}; | ||||
|       size_t copyLen = len - 1; | ||||
|       if (copyLen > sizeof(tmp) - 1) copyLen = sizeof(tmp) - 1; | ||||
|       memcpy(tmp, &payload[1], copyLen); | ||||
|       tmp[copyLen] = '\0'; | ||||
|       mainseg.setName(tmp); | ||||
|       DEBUG_PRINT(F("UdpNameSync: set segment name")); | ||||
|       return true; | ||||
|      } | ||||
| }; | ||||
|  | ||||
| static UdpNameSync udp_name_sync; | ||||
| REGISTER_USERMOD(udp_name_sync); | ||||
| @@ -2328,7 +2328,7 @@ uint16_t mode_colortwinkle() { | ||||
|       } | ||||
|  | ||||
|       if (cur == prev) {  //fix "stuck" pixels | ||||
|         color_add(col, col); | ||||
|         col = color_add(col, col); | ||||
|         SEGMENT.setPixelColor(i, col); | ||||
|       } | ||||
|       else SEGMENT.setPixelColor(i, col); | ||||
| @@ -3940,7 +3940,7 @@ uint16_t mode_percent(void) { | ||||
|  | ||||
|  	return FRAMETIME; | ||||
| } | ||||
| static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@,% of fill,,,,One color;!,!;!"; | ||||
| static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@!,% of fill,,,,One color;!,!;!"; | ||||
|  | ||||
|  | ||||
| /* | ||||
| @@ -7528,9 +7528,9 @@ uint16_t mode_2Ddistortionwaves() { | ||||
|       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); | ||||
|  | ||||
|       valueR = gamma8(cos8_t(valueR)); | ||||
|       valueG = gamma8(cos8_t(valueG)); | ||||
|       valueB = gamma8(cos8_t(valueB)); | ||||
|       valueR = cos8_t(valueR); | ||||
|       valueG = cos8_t(valueG); | ||||
|       valueB = cos8_t(valueB); | ||||
|  | ||||
|       if(SEGMENT.palette == 0) { | ||||
|         // use RGB values (original color mode) | ||||
|   | ||||
							
								
								
									
										213
									
								
								wled00/FX_fcn.cpp
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										213
									
								
								wled00/FX_fcn.cpp
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @@ -13,6 +13,10 @@ | ||||
| #include "FXparticleSystem.h"  // TODO: better define the required function (mem service) in FX.h? | ||||
| #include "palettes.h" | ||||
|  | ||||
| #ifndef DEFAULT_LED_COLOR_ORDER | ||||
|   #define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB  //default to GRB | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|   Custom per-LED mapping has moved! | ||||
|  | ||||
| @@ -66,13 +70,15 @@ Segment::Segment(const Segment &orig) { | ||||
|   _dataLen = 0; | ||||
|   pixels = nullptr; | ||||
|   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) { | ||||
|     // allocate pixel buffer: prefer IRAM/PSRAM | ||||
|     pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length())); | ||||
|     if (pixels) memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length()); | ||||
|     else { | ||||
|       DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); | ||||
|     if (pixels) { | ||||
|       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 { | ||||
|       DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); | ||||
|       errorFlag = ERR_NORAM_PX; | ||||
|       stop = 0; // mark segment as inactive/invalid | ||||
|     } | ||||
| @@ -107,12 +113,14 @@ Segment& Segment::operator= (const Segment &orig) { | ||||
|     pixels = nullptr; | ||||
|     if (!stop) return *this;  // nothing to do if segment is inactive/invalid | ||||
|     // 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) { | ||||
|       // allocate pixel buffer: prefer IRAM/PSRAM | ||||
|       pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length())); | ||||
|       if (pixels) memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length()); | ||||
|       else { | ||||
|       if (pixels) { | ||||
|         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 !!!")); | ||||
|         errorFlag = ERR_NORAM_PX; | ||||
|         stop = 0; // mark segment as inactive/invalid | ||||
| @@ -278,11 +286,13 @@ void Segment::startTransition(uint16_t dur, bool segmentCopy) { | ||||
|       _t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings | ||||
|       _t->_start = millis();                              // restart countdown | ||||
|       _t->_dur   = dur; | ||||
|       _t->_prevPaletteBlends = 0; | ||||
|       if (_t->_oldSegment) { | ||||
|         _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]; | ||||
|         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; | ||||
|   } | ||||
| @@ -298,13 +308,12 @@ void Segment::startTransition(uint16_t dur, bool segmentCopy) { | ||||
|     #endif | ||||
|     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 | ||||
|     #ifdef WLED_DEBUG | ||||
|     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 { | ||||
|       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 | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @@ -364,6 +373,7 @@ void Segment::beginDraw(uint16_t prog) { | ||||
|     // minimum blend time is 100ms maximum is 65535ms | ||||
|     #ifndef WLED_SAVE_RAM | ||||
|     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); | ||||
|     Segment::_currentPalette = _t->_palT; // copy transitioning/temporary palette | ||||
|     #else | ||||
| @@ -425,14 +435,15 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui | ||||
|  | ||||
|   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(); | ||||
|   startTransition(strip.getTransition()); // start transition prior to change (if segment is deactivated (start>stop) no transition will happen) | ||||
|   stateChanged = true; // send UDP/WS broadcast | ||||
|   if (_t) stopTransition(); // we can't use transition if segment dimensions changed | ||||
|   stateChanged = true;      // send UDP/WS broadcast | ||||
|  | ||||
|   // apply change immediately | ||||
|   if (i2 <= i1) { //disable segment | ||||
|     d_free(pixels); | ||||
|     deallocateData(); | ||||
|     p_free(pixels); | ||||
|     pixels = nullptr; | ||||
|     stop = 0; | ||||
|     return; | ||||
| @@ -449,21 +460,25 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui | ||||
|   #endif | ||||
|   // safety check | ||||
|   if (start >= stop || startY >= stopY) { | ||||
|     d_free(pixels); | ||||
|     deallocateData(); | ||||
|     p_free(pixels); | ||||
|     pixels = nullptr; | ||||
|     stop = 0; | ||||
|     return; | ||||
|   } | ||||
|   // re-allocate FX render buffer | ||||
|   // allocate FX render buffer | ||||
|   if (length() != oldLength) { | ||||
|     if (pixels) d_free(pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it | ||||
|     // 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 | ||||
|     p_free(pixels); | ||||
|     pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * length())); | ||||
|     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; | ||||
|       stop = 0; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|   } | ||||
|   refreshLightCapabilities(); | ||||
| } | ||||
| @@ -572,8 +587,8 @@ Segment &Segment::setName(const char *newName) { | ||||
|     if (newLen) { | ||||
|       if (name) d_free(name); // free old name | ||||
|       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); | ||||
|       name[newLen] = 0; | ||||
|       return *this; | ||||
|     } | ||||
|   } | ||||
| @@ -1076,27 +1091,14 @@ void Segment::blur(uint8_t blur_amount, bool smear) const { | ||||
| /* | ||||
|  * Put a value 0 to 255 in to get a color value. | ||||
|  * 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 { | ||||
|   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)); | ||||
|   pos = 255 - pos; | ||||
|   if (useRainbowWheel) { | ||||
|     CRGB rgb; | ||||
|     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); | ||||
|     } | ||||
|   } | ||||
|   uint32_t rgb; | ||||
|   hsv2rgb(CHSV32(static_cast<uint16_t>(pos << 8), 255, 255), rgb); | ||||
|   return rgb | (w << 24); // add white channel | ||||
| } | ||||
|  | ||||
| /* | ||||
| @@ -1175,12 +1177,22 @@ void WS2812FX::finalizeInit() { | ||||
|  | ||||
|   // create buses/outputs | ||||
|   unsigned mem = 0; | ||||
|   for (const auto &bus : busConfigs) { | ||||
|     mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer | ||||
|     if (mem <= MAX_LED_MEMORY) { | ||||
|       if (BusManager::add(bus) == -1) break; | ||||
|     } else DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount); | ||||
|   for (auto bus : busConfigs) { | ||||
|     // Calculate what this bus would use with its current configuration | ||||
|     unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0); | ||||
|      | ||||
|     // If memory exceeds limit, set count to minimum of current count and default length | ||||
|     if (mem + busMemUsage > MAX_LED_MEMORY) { | ||||
|       bus.count = min(bus.count, DEFAULT_LED_COUNT); | ||||
|       DEBUG_PRINTF_P(PSTR("Bus %d memory usage exceeds limit, setting count to %d\n"), (int)bus.type, bus.count); | ||||
|     } | ||||
|      | ||||
|     if (BusManager::add(bus) != -1) { | ||||
|       mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0); | ||||
|       if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) digitalCount++; | ||||
|     } else break; | ||||
|   } | ||||
|    | ||||
|   busConfigs.clear(); | ||||
|   busConfigs.shrink_to_fit(); | ||||
|  | ||||
| @@ -1196,8 +1208,9 @@ void WS2812FX::finalizeInit() { | ||||
|     if (busEnd > _length) _length = busEnd; | ||||
|     // This must be done after all buses have been created, as some kinds (parallel I2S) interact | ||||
|     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()); | ||||
|  | ||||
|   Segment::maxWidth  = _length; | ||||
| @@ -1210,10 +1223,9 @@ void WS2812FX::finalizeInit() { | ||||
|   deserializeMap();     // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist) | ||||
|  | ||||
|   // allocate frame buffer after matrix has been set up (gaps!) | ||||
|   if (_pixels) d_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it | ||||
|   d_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it | ||||
|   _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("Heap after strip init: %uB\n"), ESP.getFreeHeap()); | ||||
| } | ||||
|  | ||||
| @@ -1258,7 +1270,8 @@ void WS2812FX::service() { | ||||
|         // 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) | ||||
|         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 | ||||
|           segO->beginDraw(prog);            // set up palette & colors (also sets draw dimensions), parent segment has transition progress | ||||
|           _currentSegment = segO;           // set current segment | ||||
| @@ -1299,7 +1312,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 _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; } | ||||
| #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 | ||||
| #else | ||||
| static uint8_t _multiply  (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1] | ||||
| @@ -1310,10 +1323,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 _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)); } | ||||
| #ifdef 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 | ||||
| #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) | ||||
| 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 | ||||
| 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 | ||||
| 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); } | ||||
| @@ -1344,11 +1357,6 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { | ||||
|   uint8_t       opacity    = topSegment.currentBri(); // returns transitioned opacity for style FADE | ||||
|   uint8_t       cct        = topSegment.currentCCT(); | ||||
|  | ||||
|   if (length == 1) { | ||||
|     // Can't blend only a single pixel, prevents crash when bus init fails | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   Segment::setClippingRect(0, 0);             // disable clipping by default | ||||
|  | ||||
|   const unsigned dw = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1; | ||||
| @@ -1461,8 +1469,10 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { | ||||
|       } | ||||
|       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 (segO && blendingStyle == BLEND_STYLE_FADE && topSegment.mode != segO->mode && x < oCols && y < oRows) { | ||||
|         // we need to blend old segment using fade as pixels ae not clipped | ||||
|       if (segO && blendingStyle == BLEND_STYLE_FADE | ||||
|         && (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); | ||||
|       } else if (blendingStyle != BLEND_STYLE_FADE) { | ||||
|         // workaround for On/Off transition | ||||
| @@ -1555,67 +1565,9 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { | ||||
|   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() { | ||||
|   if (!_pixels) return; // no pixels allocated, nothing to show | ||||
|  | ||||
|   unsigned long showNow = millis(); | ||||
|   size_t diff = showNow - _lastShow; | ||||
|  | ||||
| @@ -1640,10 +1592,6 @@ void WS2812FX::show() { | ||||
|   show_callback callback = _callback; | ||||
|   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 | ||||
|   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) | ||||
| @@ -1654,7 +1602,11 @@ void WS2812FX::show() { | ||||
|     if (_pixelCCT) { // cctFromRgb already exluded at allocation | ||||
|       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 | ||||
|  | ||||
| @@ -1666,9 +1618,6 @@ void WS2812FX::show() { | ||||
|   // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods | ||||
|   BusManager::show(); | ||||
|  | ||||
|   // restore brightness for next frame | ||||
|   if (newBri != _brightness) BusManager::setBrightness(_brightness); | ||||
|  | ||||
|   if (diff > 0) { // skip calculation if no time has passed | ||||
|     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 | ||||
| @@ -1733,7 +1682,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { | ||||
|   if (_brightness == 0) { //unfreeze all segments on power off | ||||
|     for (const Segment &seg : _segments) seg.freeze = false; // freeze is mutable | ||||
|   } | ||||
|   BusManager::setBrightness(b); | ||||
|   BusManager::setBrightness(scaledBri(b)); | ||||
|   if (!direct) { | ||||
|     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 | ||||
| @@ -1888,7 +1837,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) { | ||||
|     for (size_t i = 1; i < s; 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 { | ||||
|  | ||||
| @@ -2012,7 +1961,7 @@ bool WS2812FX::deserializeMap(unsigned n) { | ||||
|   } | ||||
|  | ||||
|   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) { | ||||
|     DEBUG_PRINTF_P(PSTR("ledmap allocated: %uB\n"), sizeof(uint16_t)*getLengthTotal()); | ||||
|   | ||||
| @@ -1118,7 +1118,7 @@ bool initParticleSystem2D(ParticleSystem2D *&PartSys, uint32_t requestedsources, | ||||
|       allocsuccess = true; | ||||
|       break; // allocation succeeded | ||||
|     } | ||||
|     numparticles /= 2; // cut number of particles in half and try again | ||||
|     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) { | ||||
| @@ -1815,7 +1815,7 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedso | ||||
|       allocsuccess = true; | ||||
|       break; // allocation succeeded | ||||
|     } | ||||
|     numparticles /= 2; // cut number of particles in half and try again | ||||
|     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) { | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
| #include "core_esp8266_waveform.h" | ||||
| #endif | ||||
| #include "const.h" | ||||
| #include "colors.h" | ||||
| #include "pin_manager.h" | ||||
| #include "bus_manager.h" | ||||
| #include "bus_wrapper.h" | ||||
| @@ -144,6 +145,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 (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; } | ||||
|   _frequencykHz = 0U; | ||||
|   _colorSum = 0; | ||||
|   _pins[0] = bc.pins[0]; | ||||
|   if (is2Pin(bc.type)) { | ||||
|     if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { | ||||
| @@ -186,80 +188,62 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) | ||||
| //Stay safe with high amperage and have a reasonable safety margin! | ||||
| //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 | ||||
| uint8_t BusDigital::estimateCurrentAndLimitBri() const { | ||||
|   bool useWackyWS2815PowerModel = false; | ||||
|   byte actualMilliampsPerLed = _milliAmpsPerLed; | ||||
|  | ||||
|   if (_milliAmpsMax < MA_FOR_ESP/BusManager::getNumBusses() || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation | ||||
|     return _bri; | ||||
|   } | ||||
| // note on ABL implementation: | ||||
| // ABL is set up in finalizeInit() | ||||
| // scaled color channels are summed in BusDigital::setPixelColor() | ||||
| // 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 | ||||
| // to disable brightness limiter for a bus, set LED current to 0 | ||||
|  | ||||
| void BusDigital::estimateCurrent() { | ||||
|   uint32_t actualMilliampsPerLed = _milliAmpsPerLed; | ||||
|   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 | ||||
|   } | ||||
|   // _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 | ||||
|   if (powerBudget > getLength()) { //each LED uses about 1mA in standby, exclude that from power budget | ||||
|     powerBudget -= getLength(); | ||||
|   } else { | ||||
|     powerBudget = 0; | ||||
|   } | ||||
| void BusDigital::applyBriLimit(uint8_t newBri) { | ||||
|   // a newBri of 0 means calculate per-bus brightness limit | ||||
|   if (newBri == 0) { | ||||
|     if (_milliAmpsLimit == 0 || _milliAmpsTotal == 0) return; // ABL not used for this bus | ||||
|     newBri = 255; | ||||
|  | ||||
|   uint32_t busPowerSum = 0; | ||||
|   for (unsigned i = 0; i < getLength(); i++) {  //sum up the usage of each LED | ||||
|     uint32_t c = getPixelColor(i); // always returns original or restored color without brightness scaling | ||||
|     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; | ||||
|     if (_milliAmpsLimit > getLength()) { // each LED uses about 1mA in standby | ||||
|       if (_milliAmpsTotal > _milliAmpsLimit) { | ||||
|         // scale brightness down to stay in current limit | ||||
|         newBri = ((uint32_t)_milliAmpsLimit * 255) / _milliAmpsTotal + 1; // +1 to avoid 0 brightness | ||||
|         _milliAmpsTotal = _milliAmpsLimit; | ||||
|       } | ||||
|     } 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 | ||||
|     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 | ||||
|   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() { | ||||
|   BusDigital::_milliAmpsTotal = 0; | ||||
|   if (!_valid) return; | ||||
|  | ||||
|   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 < 255) { | ||||
|     uint8_t cctWW = 0, cctCW = 0; | ||||
|     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 | ||||
|       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 | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   _colorSum = 0; // reset for next frame | ||||
| } | ||||
|  | ||||
| void BusDigital::show() { | ||||
|   if (!_valid) return; | ||||
|   PolyBus::show(_busPtr, _iType, _skip); // faster if buffer consistency is not important (no skipped LEDs) | ||||
|   // 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 { | ||||
| @@ -267,12 +251,6 @@ bool BusDigital::canShow() const { | ||||
|   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. | ||||
| //TODO only show if no new show due in the next 50ms | ||||
| void BusDigital::setStatusPixel(uint32_t c) { | ||||
| @@ -286,13 +264,25 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { | ||||
|   if (!_valid) return; | ||||
|   if (hasWhite()) c = autoWhiteCalc(c); | ||||
|   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; | ||||
|   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 | ||||
|     unsigned pOld = 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) | ||||
|       case 0: c = RGBW32(R(cOld), W(c)   , B(cOld), 0); break; | ||||
|       case 1: c = RGBW32(W(c)   , G(cOld), B(cOld), 0); break; | ||||
| @@ -309,17 +299,17 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { | ||||
|   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 { | ||||
|   if (!_valid) return 0; | ||||
|   if (_reversed) pix = _len - pix -1; | ||||
|   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); | ||||
|   if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs | ||||
|     unsigned r = R(c); | ||||
|     unsigned g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed? | ||||
|     unsigned b = _reversed ? G(c) : B(c); | ||||
|     uint8_t r = R(c); | ||||
|     uint8_t g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed? | ||||
|     uint8_t b = _reversed ? G(c) : B(c); | ||||
|     switch (pix % 3) { // get only the single channel | ||||
|       case 0: c = RGBW32(g, g, g, g); break; | ||||
|       case 1: c = RGBW32(r, r, r, r); break; | ||||
| @@ -471,10 +461,7 @@ void BusPwm::setPixelColor(unsigned pix, uint32_t c) { | ||||
|   if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { | ||||
|     c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT | ||||
|   } | ||||
|   uint8_t r = R(c); | ||||
|   uint8_t g = G(c); | ||||
|   uint8_t b = B(c); | ||||
|   uint8_t w = W(c); | ||||
|   uint8_t r = R(c), g = G(c), b = B(c), w = W(c); | ||||
|  | ||||
|   switch (_type) { | ||||
|     case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation | ||||
| @@ -649,10 +636,7 @@ BusOnOff::BusOnOff(const BusConfig &bc) | ||||
| void BusOnOff::setPixelColor(unsigned pix, uint32_t c) { | ||||
|   if (pix != 0 || !_valid) return; //only react to first pixel | ||||
|   c = autoWhiteCalc(c); | ||||
|   uint8_t r = R(c); | ||||
|   uint8_t g = G(c); | ||||
|   uint8_t b = B(c); | ||||
|   uint8_t w = W(c); | ||||
|   uint8_t r = R(c), g = G(c), b = B(c), w = W(c); | ||||
|   _data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; | ||||
| } | ||||
|  | ||||
| @@ -964,13 +948,13 @@ void BusManager::off() { | ||||
|   #ifdef ESP32_DATA_IDLE_HIGH | ||||
|   esp32RMTInvertIdle(); | ||||
|   #endif | ||||
|   _gMilliAmpsUsed = 0; // reset, assume no LED idle current if relay is off | ||||
| } | ||||
|  | ||||
| void BusManager::show() { | ||||
|   _gMilliAmpsUsed = 0; | ||||
|   applyABL(); // apply brightness limit, updates _gMilliAmpsUsed | ||||
|   for (auto &bus : busses) { | ||||
|     bus->show(); | ||||
|     _gMilliAmpsUsed += bus->getUsedCurrent(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -1003,6 +987,85 @@ bool BusManager::canAllShow() { | ||||
|   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; } | ||||
|  | ||||
|  | ||||
| @@ -1018,3 +1081,4 @@ uint16_t BusDigital::_milliAmpsTotal = 0; | ||||
| std::vector<std::unique_ptr<Bus>> BusManager::busses; | ||||
| uint16_t BusManager::_gMilliAmpsUsed = 0; | ||||
| uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT; | ||||
| bool BusManager::_useABL = false; | ||||
|   | ||||
| @@ -238,7 +238,6 @@ class BusDigital : public Bus { | ||||
|  | ||||
|     void show() override; | ||||
|     bool canShow() const override; | ||||
|     void setBrightness(uint8_t b) override; | ||||
|     void setStatusPixel(uint32_t c) override; | ||||
|     [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; | ||||
|     void setColorOrder(uint8_t colorOrder) override; | ||||
| @@ -250,6 +249,9 @@ class BusDigital : public Bus { | ||||
|     uint16_t getLEDCurrent() const override  { return _milliAmpsPerLed; } | ||||
|     uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } | ||||
|     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; | ||||
|     void begin() override; | ||||
|     void cleanup(); | ||||
| @@ -262,8 +264,10 @@ class BusDigital : public Bus { | ||||
|     uint8_t  _pins[2]; | ||||
|     uint8_t  _iType; | ||||
|     uint16_t _frequencykHz; | ||||
|     uint8_t  _milliAmpsPerLed; | ||||
|     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; | ||||
|  | ||||
|     static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() | ||||
| @@ -278,8 +282,6 @@ class BusDigital : public Bus { | ||||
|       } | ||||
|       return c; | ||||
|     } | ||||
|  | ||||
|     uint8_t  estimateCurrentAndLimitBri() const; | ||||
| }; | ||||
|  | ||||
|  | ||||
| @@ -422,8 +424,8 @@ struct BusConfig { | ||||
| }; | ||||
|  | ||||
|  | ||||
| //fine tune power estimation constants for your setup | ||||
| //you can set it to 0 if the ESP is powered by USB and the LEDs by external | ||||
| // 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 | ||||
| #ifndef MA_FOR_ESP | ||||
|   #ifdef ESP8266 | ||||
|     #define MA_FOR_ESP         80 //how much mA does the ESP use (Wemos D1 about 80mA) | ||||
| @@ -438,6 +440,7 @@ namespace BusManager { | ||||
|   //extern std::vector<Bus*> busses; | ||||
|   extern uint16_t _gMilliAmpsUsed; | ||||
|   extern uint16_t _gMilliAmpsMax; | ||||
|   extern bool     _useABL; | ||||
|  | ||||
|   #ifdef ESP32_DATA_IDLE_HIGH | ||||
|   void    esp32RMTInvertIdle() ; | ||||
| @@ -453,6 +456,8 @@ namespace BusManager { | ||||
|   //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 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 | ||||
|   bool hasParallelOutput(); // workaround for inaccessible PolyBus | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| #define BusWrapper_h | ||||
|  | ||||
| //#define NPB_CONF_4STEP_CADENCE | ||||
| #include "NeoPixelBusLg.h" | ||||
| #include "NeoPixelBus.h" | ||||
|  | ||||
| //Hardware SPI Pins | ||||
| #define P_8266_HS_MOSI 13 | ||||
| @@ -141,65 +141,65 @@ | ||||
| /*** ESP8266 Neopixel methods ***/ | ||||
| #ifdef ESP8266 | ||||
| //RGB | ||||
| #define B_8266_U0_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod>  //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_U0_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart0Ws2813Method> //3 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart1Ws2813Method> //3 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Dma800KbpsMethod>  //3 chan, esp8266, gpio3 | ||||
| #define B_8266_BB_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBang800KbpsMethod> //3 chan, esp8266, bb (any pin but 16) | ||||
| //RGBW | ||||
| #define B_8266_U0_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod>   //4 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod>   //4 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod>    //4 chan, esp8266, gpio3 | ||||
| #define B_8266_BB_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, bb (any pin) | ||||
| #define B_8266_U0_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266Uart0Ws2813Method>   //4 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266Uart1Ws2813Method>   //4 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266Dma800KbpsMethod>    //4 chan, esp8266, gpio3 | ||||
| #define B_8266_BB_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp8266BitBang800KbpsMethod> //4 chan, esp8266, bb (any pin) | ||||
| //400Kbps | ||||
| #define B_8266_U0_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart0400KbpsMethod, NeoGammaNullMethod>   //3 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Uart1400KbpsMethod, NeoGammaNullMethod>   //3 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266Dma400KbpsMethod, NeoGammaNullMethod>     //3 chan, esp8266, gpio3 | ||||
| #define B_8266_BB_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266BitBang400KbpsMethod, NeoGammaNullMethod> //3 chan, esp8266, bb (any pin) | ||||
| #define B_8266_U0_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart0400KbpsMethod>   //3 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart1400KbpsMethod>   //3 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266Dma400KbpsMethod>     //3 chan, esp8266, gpio3 | ||||
| #define B_8266_BB_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBang400KbpsMethod> //3 chan, esp8266, bb (any pin) | ||||
| //TM1814 (RGBW) | ||||
| #define B_8266_U0_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266Uart0Tm1814Method, NeoGammaNullMethod> | ||||
| #define B_8266_U1_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266Uart1Tm1814Method, NeoGammaNullMethod> | ||||
| #define B_8266_DM_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266DmaTm1814Method, NeoGammaNullMethod> | ||||
| #define B_8266_BB_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp8266BitBangTm1814Method, NeoGammaNullMethod> | ||||
| #define B_8266_U0_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266Uart0Tm1814Method> | ||||
| #define B_8266_U1_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266Uart1Tm1814Method> | ||||
| #define B_8266_DM_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266DmaTm1814Method> | ||||
| #define B_8266_BB_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp8266BitBangTm1814Method> | ||||
| //TM1829 (RGB) | ||||
| #define B_8266_U0_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart0Tm1829Method, NeoGammaNullMethod> | ||||
| #define B_8266_U1_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266Uart1Tm1829Method, NeoGammaNullMethod> | ||||
| #define B_8266_DM_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266DmaTm1829Method, NeoGammaNullMethod> | ||||
| #define B_8266_BB_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp8266BitBangTm1829Method, NeoGammaNullMethod> | ||||
| #define B_8266_U0_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266Uart0Tm1829Method> | ||||
| #define B_8266_U1_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266Uart1Tm1829Method> | ||||
| #define B_8266_DM_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266DmaTm1829Method> | ||||
| #define B_8266_BB_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp8266BitBangTm1829Method> | ||||
| //UCS8903 | ||||
| #define B_8266_U0_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod>  //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_U0_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266Uart0Ws2813Method> //3 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266Uart1Ws2813Method> //3 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266Dma800KbpsMethod>  //3 chan, esp8266, gpio3 | ||||
| #define B_8266_BB_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp8266BitBang800KbpsMethod> //3 chan, esp8266, bb (any pin but 16) | ||||
| //UCS8904 RGBW | ||||
| #define B_8266_U0_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod>   //4 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod>   //4 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod>    //4 chan, esp8266, gpio3 | ||||
| #define B_8266_BB_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod> //4 chan, esp8266, bb (any pin) | ||||
| #define B_8266_U0_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266Uart0Ws2813Method>   //4 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266Uart1Ws2813Method>   //4 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266Dma800KbpsMethod>    //4 chan, esp8266, gpio3 | ||||
| #define B_8266_BB_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp8266BitBang800KbpsMethod> //4 chan, esp8266, bb (any pin) | ||||
| //APA106 | ||||
| #define B_8266_U0_APA106_3 NeoPixelBusLg<NeoRbgFeature, NeoEsp8266Uart0Apa106Method, NeoGammaNullMethod> //3 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_APA106_3 NeoPixelBusLg<NeoRbgFeature, NeoEsp8266Uart1Apa106Method, NeoGammaNullMethod> //3 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp8266DmaApa106Method, NeoGammaNullMethod>  //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_U0_APA106_3 NeoPixelBus<NeoRbgFeature, NeoEsp8266Uart0Apa106Method> //3 chan, esp8266, gpio1 | ||||
| #define B_8266_U1_APA106_3 NeoPixelBus<NeoRbgFeature, NeoEsp8266Uart1Apa106Method> //3 chan, esp8266, gpio2 | ||||
| #define B_8266_DM_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266DmaApa106Method>  //3 chan, esp8266, gpio3 | ||||
| #define B_8266_BB_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBangApa106Method> //3 chan, esp8266, bb (any pin but 16) | ||||
| //FW1906 GRBCW | ||||
| #define B_8266_U0_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod>   //esp8266, gpio1 | ||||
| #define B_8266_U1_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod>   //esp8266, gpio2 | ||||
| #define B_8266_DM_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod>   //esp8266, gpio3 | ||||
| #define B_8266_BB_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp8266BitBang800KbpsMethod, NeoGammaNullMethod>   //esp8266, bb | ||||
| #define B_8266_U0_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Uart0Ws2813Method>   //esp8266, gpio1 | ||||
| #define B_8266_U1_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Uart1Ws2813Method>   //esp8266, gpio2 | ||||
| #define B_8266_DM_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Dma800KbpsMethod>   //esp8266, gpio3 | ||||
| #define B_8266_BB_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266BitBang800KbpsMethod>   //esp8266, bb | ||||
| //WS2805 GRBCW | ||||
| #define B_8266_U0_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266Uart0Ws2805Method, NeoGammaNullMethod> //esp8266, gpio1 | ||||
| #define B_8266_U1_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266Uart1Ws2805Method, NeoGammaNullMethod> //esp8266, gpio2 | ||||
| #define B_8266_DM_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266DmaWs2805Method, NeoGammaNullMethod> //esp8266, gpio3 | ||||
| #define B_8266_BB_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp8266BitBangWs2805Method, NeoGammaNullMethod> //esp8266, bb | ||||
| #define B_8266_U0_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266Uart0Ws2805Method> //esp8266, gpio1 | ||||
| #define B_8266_U1_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266Uart1Ws2805Method> //esp8266, gpio2 | ||||
| #define B_8266_DM_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266DmaWs2805Method> //esp8266, gpio3 | ||||
| #define B_8266_BB_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266BitBangWs2805Method> //esp8266, bb | ||||
| //TM1914 (RGB) | ||||
| #define B_8266_U0_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266Uart0Tm1914Method, NeoGammaNullMethod> | ||||
| #define B_8266_U1_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266Uart1Tm1914Method, NeoGammaNullMethod> | ||||
| #define B_8266_DM_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266DmaTm1914Method, NeoGammaNullMethod> | ||||
| #define B_8266_BB_TM1914_3 NeoPixelBusLg<NeoRgbTm1914Feature, NeoEsp8266BitBangTm1914Method, NeoGammaNullMethod> | ||||
| #define B_8266_U0_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266Uart0Tm1914Method> | ||||
| #define B_8266_U1_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266Uart1Tm1914Method> | ||||
| #define B_8266_DM_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266DmaTm1914Method> | ||||
| #define B_8266_BB_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266BitBangTm1914Method> | ||||
| //Sm16825 (RGBWC) | ||||
| #define B_8266_U0_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266Uart0Ws2813Method, NeoGammaNullMethod> | ||||
| #define B_8266_U1_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266Uart1Ws2813Method, NeoGammaNullMethod> | ||||
| #define B_8266_DM_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266Dma800KbpsMethod, NeoGammaNullMethod> | ||||
| #define B_8266_BB_SM16825_5 NeoPixelBusLg<NeoRgbwcSm16825eFeature, NeoEsp8266BitBangWs2813Method, NeoGammaNullMethod> | ||||
| #define B_8266_U0_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Uart0Ws2813Method> | ||||
| #define B_8266_U1_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Uart1Ws2813Method> | ||||
| #define B_8266_DM_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Dma800KbpsMethod> | ||||
| #define B_8266_BB_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266BitBangWs2813Method> | ||||
| #endif | ||||
|  | ||||
| /*** ESP32 Neopixel methods ***/ | ||||
| @@ -245,84 +245,84 @@ | ||||
| #endif | ||||
|  | ||||
| //RGB | ||||
| #define B_32_RN_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod> // ESP32, S2, S3, C3 | ||||
| //#define B_32_IN_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2sNWs2812xMethod, NeoGammaNullMethod> // 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_IP_NEO_3 NeoPixelBusLg<NeoGrbFeature, X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S (ESP32, S2, S3) | ||||
| #define B_32_RN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod> // ESP32, S2, S3, C3 | ||||
| //#define B_32_IN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32I2sNWs2812xMethod> // ESP32 (dynamic I2S selection) | ||||
| #define B_32_I2_NEO_3 NeoPixelBus<NeoGrbFeature, X1Ws2812xMethod> // ESP32, S2, S3 (automatic I2S selection, see typedef above) | ||||
| #define B_32_IP_NEO_3 NeoPixelBus<NeoGrbFeature, X8Ws2812xMethod> // parallel I2S (ESP32, S2, S3) | ||||
| //RGBW | ||||
| #define B_32_RN_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32RmtNSk6812Method, NeoGammaNullMethod> | ||||
| #define B_32_I2_NEO_4 NeoPixelBusLg<NeoGrbwFeature, X1Sk6812Method, NeoGammaNullMethod> | ||||
| #define B_32_IP_NEO_4 NeoPixelBusLg<NeoGrbwFeature, X8Sk6812Method, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtNSk6812Method> | ||||
| #define B_32_I2_NEO_4 NeoPixelBus<NeoGrbwFeature, X1Sk6812Method> | ||||
| #define B_32_IP_NEO_4 NeoPixelBus<NeoGrbwFeature, X8Sk6812Method> // parallel I2S | ||||
| //400Kbps | ||||
| #define B_32_RN_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod, NeoGammaNullMethod> | ||||
| #define B_32_I2_400_3 NeoPixelBusLg<NeoGrbFeature, X1400KbpsMethod, NeoGammaNullMethod> | ||||
| #define B_32_IP_400_3 NeoPixelBusLg<NeoGrbFeature, X8400KbpsMethod, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod> | ||||
| #define B_32_I2_400_3 NeoPixelBus<NeoGrbFeature, X1400KbpsMethod> | ||||
| #define B_32_IP_400_3 NeoPixelBus<NeoGrbFeature, X8400KbpsMethod> // parallel I2S | ||||
| //TM1814 (RGBW) | ||||
| #define B_32_RN_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method, NeoGammaNullMethod> | ||||
| #define B_32_I2_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, X1Tm1814Method, NeoGammaNullMethod> | ||||
| #define B_32_IP_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, X8Tm1814Method, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method> | ||||
| #define B_32_I2_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X1Tm1814Method> | ||||
| #define B_32_IP_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X8Tm1814Method> // parallel I2S | ||||
| //TM1829 (RGB) | ||||
| #define B_32_RN_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32RmtNTm1829Method, NeoGammaNullMethod> | ||||
| #define B_32_I2_TM2_3 NeoPixelBusLg<NeoBrgFeature, X1Tm1829Method, NeoGammaNullMethod> | ||||
| #define B_32_IP_TM2_3 NeoPixelBusLg<NeoBrgFeature, X8Tm1829Method, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp32RmtNTm1829Method> | ||||
| #define B_32_I2_TM2_3 NeoPixelBus<NeoBrgFeature, X1Tm1829Method> | ||||
| #define B_32_IP_TM2_3 NeoPixelBus<NeoBrgFeature, X8Tm1829Method> // parallel I2S | ||||
| //UCS8903 | ||||
| #define B_32_RN_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod> | ||||
| #define B_32_I2_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, X1800KbpsMethod, NeoGammaNullMethod> | ||||
| #define B_32_IP_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod> | ||||
| #define B_32_I2_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X1800KbpsMethod> | ||||
| #define B_32_IP_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X8800KbpsMethod> // parallel I2S | ||||
| //UCS8904 | ||||
| #define B_32_RN_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod> | ||||
| #define B_32_I2_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, X1800KbpsMethod, NeoGammaNullMethod> | ||||
| #define B_32_IP_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, X8800KbpsMethod, NeoGammaNullMethod>// parallel I2S | ||||
| #define B_32_RN_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod> | ||||
| #define B_32_I2_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X1800KbpsMethod> | ||||
| #define B_32_IP_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X8800KbpsMethod>// parallel I2S | ||||
| //APA106 | ||||
| #define B_32_RN_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtNApa106Method, NeoGammaNullMethod> | ||||
| #define B_32_I2_APA106_3 NeoPixelBusLg<NeoGrbFeature, X1Apa106Method, NeoGammaNullMethod> | ||||
| #define B_32_IP_APA106_3 NeoPixelBusLg<NeoGrbFeature, X8Apa106Method, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNApa106Method> | ||||
| #define B_32_I2_APA106_3 NeoPixelBus<NeoGrbFeature, X1Apa106Method> | ||||
| #define B_32_IP_APA106_3 NeoPixelBus<NeoGrbFeature, X8Apa106Method> // parallel I2S | ||||
| //FW1906 GRBCW | ||||
| #define B_32_RN_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod> | ||||
| #define B_32_I2_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, X1800KbpsMethod, NeoGammaNullMethod> | ||||
| #define B_32_IP_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod> | ||||
| #define B_32_I2_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X1800KbpsMethod> | ||||
| #define B_32_IP_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X8800KbpsMethod> // parallel I2S | ||||
| //WS2805 RGBWC | ||||
| #define B_32_RN_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method, NeoGammaNullMethod> | ||||
| #define B_32_I2_2805_5 NeoPixelBusLg<NeoGrbwwFeature, X1Ws2805Method, NeoGammaNullMethod> | ||||
| #define B_32_IP_2805_5 NeoPixelBusLg<NeoGrbwwFeature, X8Ws2805Method, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method> | ||||
| #define B_32_I2_2805_5 NeoPixelBus<NeoGrbwwFeature, X1Ws2805Method> | ||||
| #define B_32_IP_2805_5 NeoPixelBus<NeoGrbwwFeature, X8Ws2805Method> // parallel I2S | ||||
| //TM1914 (RGB) | ||||
| #define B_32_RN_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method, NeoGammaNullMethod> | ||||
| #define B_32_I2_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, X1Tm1914Method, NeoGammaNullMethod> | ||||
| #define B_32_IP_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, X8Tm1914Method, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method> | ||||
| #define B_32_I2_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X1Tm1914Method> | ||||
| #define B_32_IP_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X8Tm1914Method> // parallel I2S | ||||
| //Sm16825 (RGBWC) | ||||
| #define B_32_RN_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod> | ||||
| #define B_32_I2_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, X1Ws2812xMethod, NeoGammaNullMethod> | ||||
| #define B_32_IP_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S | ||||
| #define B_32_RN_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, NeoEsp32RmtNWs2812xMethod> | ||||
| #define B_32_I2_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X1Ws2812xMethod> | ||||
| #define B_32_IP_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X8Ws2812xMethod> // parallel I2S | ||||
| #endif | ||||
|  | ||||
| //APA102 | ||||
| #ifdef WLED_USE_ETHERNET | ||||
| // 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 | ||||
| #define B_HS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarSpiHzMethod, NeoGammaNullMethod> //hardware VSPI | ||||
| #define B_HS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarSpiHzMethod> //hardware VSPI | ||||
| #endif | ||||
| #define B_SS_DOT_3 NeoPixelBusLg<DotStarBgrFeature, DotStarMethod, NeoGammaNullMethod>    //soft SPI | ||||
| #define B_SS_DOT_3 NeoPixelBus<DotStarBgrFeature, DotStarMethod>    //soft SPI | ||||
|  | ||||
| //LPD8806 | ||||
| #define B_HS_LPD_3 NeoPixelBusLg<Lpd8806GrbFeature, Lpd8806SpiHzMethod, NeoGammaNullMethod> | ||||
| #define B_SS_LPD_3 NeoPixelBusLg<Lpd8806GrbFeature, Lpd8806Method, NeoGammaNullMethod> | ||||
| #define B_HS_LPD_3 NeoPixelBus<Lpd8806GrbFeature, Lpd8806SpiHzMethod> | ||||
| #define B_SS_LPD_3 NeoPixelBus<Lpd8806GrbFeature, Lpd8806Method> | ||||
|  | ||||
| //LPD6803 | ||||
| #define B_HS_LPO_3 NeoPixelBusLg<Lpd6803GrbFeature, Lpd6803SpiHzMethod, NeoGammaNullMethod> | ||||
| #define B_SS_LPO_3 NeoPixelBusLg<Lpd6803GrbFeature, Lpd6803Method, NeoGammaNullMethod> | ||||
| #define B_HS_LPO_3 NeoPixelBus<Lpd6803GrbFeature, Lpd6803SpiHzMethod> | ||||
| #define B_SS_LPO_3 NeoPixelBus<Lpd6803GrbFeature, Lpd6803Method> | ||||
|  | ||||
| //WS2801 | ||||
| #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 | ||||
| #define B_HS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801SpiHzMethod, NeoGammaNullMethod> | ||||
| #define B_HS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801SpiHzMethod> | ||||
| #endif | ||||
| #define B_SS_WS1_3 NeoPixelBusLg<NeoRbgFeature, Ws2801Method, NeoGammaNullMethod> | ||||
| #define B_SS_WS1_3 NeoPixelBus<NeoRbgFeature, Ws2801Method> | ||||
|  | ||||
| //P9813 | ||||
| #define B_HS_P98_3 NeoPixelBusLg<P9813BgrFeature, P9813SpiHzMethod, NeoGammaNullMethod> | ||||
| #define B_SS_P98_3 NeoPixelBusLg<P9813BgrFeature, P9813Method, NeoGammaNullMethod> | ||||
| #define B_HS_P98_3 NeoPixelBus<P9813BgrFeature, P9813SpiHzMethod> | ||||
| #define B_SS_P98_3 NeoPixelBus<P9813BgrFeature, P9813Method> | ||||
|  | ||||
| // 48bit & 64bit to 24bit & 32bit RGB(W) conversion | ||||
| #define toRGBW32(c) (RGBW32((c>>40)&0xFF, (c>>24)&0xFF, (c>>8)&0xFF, (c>>56)&0xFF)) | ||||
| @@ -896,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) { | ||||
|     RgbwColor col(0,0,0,0); | ||||
|     switch (busType) { | ||||
|   | ||||
| @@ -519,7 +519,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { | ||||
|   CJSON(briMultiplier, light[F("scale-bri")]); | ||||
|   CJSON(paletteBlend, light[F("pal-mode")]); | ||||
|   CJSON(strip.autoSegments, light[F("aseg")]); | ||||
|   CJSON(useRainbowWheel, light[F("rw")]); | ||||
|  | ||||
|   CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.2 | ||||
|   float light_gc_bri = light["gc"]["bri"]; | ||||
| @@ -773,9 +772,32 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { | ||||
|   return (doc["sv"] | true); | ||||
| } | ||||
|  | ||||
|  | ||||
| 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() { | ||||
|   [[maybe_unused]] bool success = deserializeConfigSec(); | ||||
|   #ifdef WLED_ADD_EEPROM_SUPPORT | ||||
| @@ -801,6 +823,7 @@ bool deserializeConfigFromFS() { | ||||
|  | ||||
| void serializeConfigToFS() { | ||||
|   serializeConfigSec(); | ||||
|   backupConfig(); // backup before writing new config | ||||
|  | ||||
|   DEBUG_PRINTLN(F("Writing settings to /cfg.json...")); | ||||
|  | ||||
| @@ -1042,7 +1065,6 @@ void serializeConfig(JsonObject root) { | ||||
|   light[F("scale-bri")] = briMultiplier; | ||||
|   light[F("pal-mode")] = paletteBlend; | ||||
|   light[F("aseg")] = strip.autoSegments; | ||||
|   light[F("rw")] = useRainbowWheel; | ||||
|  | ||||
|   JsonObject light_gc = light.createNestedObject("gc"); | ||||
|   light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f;  // keep compatibility | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|  * 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 | ||||
|  */ | ||||
| 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 | ||||
|   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 | ||||
| @@ -64,26 +64,26 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) | ||||
|  * fades color toward black | ||||
|  * if using "video" method the resulting color will never become black unless it is already black | ||||
|  */ | ||||
|  | ||||
| uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) | ||||
| { | ||||
| uint32_t IRAM_ATTR 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 (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; | ||||
|   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 | ||||
|     addRemains  = R(c1) ? 0x00010000 : 0; | ||||
|     addRemains |= G(c1) ? 0x00000100 : 0; | ||||
|     addRemains |= B(c1) ? 0x00000001 : 0; | ||||
|     addRemains |= W(c1) ? 0x01000000 : 0; | ||||
|  | ||||
|   if (!video) amount++; // add one for correct scaling using bitshifts | ||||
|   else { | ||||
|     // video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue | ||||
|     uint8_t r = byte(c1>>16), g = byte(c1>>8), b = byte(c1), w = byte(c1>>24); // extract r, g, b, w channels | ||||
|     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; | ||||
|   uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * scale) >> 8) &  TWO_CHANNEL_MASK; // scale red and blue | ||||
|   uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; // scale white and green | ||||
|   scaledcolor = (rb | wg) + addRemains; | ||||
|   return scaledcolor; | ||||
|   uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) &  TWO_CHANNEL_MASK; // scale red and blue | ||||
|   uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * amount) & ~TWO_CHANNEL_MASK; // scale white and green | ||||
|   return (rb | wg) + addRemains; | ||||
| } | ||||
|  | ||||
| /* | ||||
| @@ -92,7 +92,7 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) | ||||
|    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 | ||||
|     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) | ||||
| @@ -104,8 +104,7 @@ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_ | ||||
| } | ||||
|  | ||||
| // 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) { | ||||
|     index = (index * 0xF0) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping | ||||
|   } | ||||
| @@ -120,16 +119,16 @@ uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t | ||||
|     else ++entry; | ||||
|     unsigned f2 = (lo4 << 4); | ||||
|     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; | ||||
|     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 | ||||
|     // actually color_fade(c1, brightness) | ||||
|     // actually same as color_fade(), using color_fade() is slower | ||||
|     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; | ||||
|     blue1  = (blue1 * scale) >> 8; | ||||
|     blue1  = (blue1  * scale) >> 8; | ||||
|   } | ||||
|   return RGBW32(red1,green1,blue1,0); | ||||
| } | ||||
| @@ -589,10 +588,13 @@ uint8_t NeoGammaWLEDMethod::gammaT_inv[256]; | ||||
| void NeoGammaWLEDMethod::calcGammaTable(float 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_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) | ||||
| @@ -601,21 +603,6 @@ uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t 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) | ||||
| { | ||||
|   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 | ||||
| @@ -668,9 +668,20 @@ function parseInfo(i) { | ||||
| 	if (loc)    name = "(L) " + name; | ||||
| 	d.title      = name; | ||||
| 	simplifiedUI = i.simplifiedui; | ||||
| 	ledCount     = i.leds.count; | ||||
| 	// Add safety checks for LED count data to prevent UI crashes | ||||
| 	if (i.leds && typeof i.leds.count !== 'undefined') { | ||||
| 		ledCount = i.leds.count; | ||||
| 	} else { | ||||
| 		console.warn('LED count data missing, using fallback value'); | ||||
| 		ledCount = 30; // Fallback value matching firmware default | ||||
| 	} | ||||
| 	//syncTglRecv   = i.str; | ||||
| 	maxSeg       = i.leds.maxseg; | ||||
| 	if (i.leds && typeof i.leds.maxseg !== 'undefined') { | ||||
| 		maxSeg = i.leds.maxseg; | ||||
| 	} else { | ||||
| 		console.warn('Max segment data missing, using fallback value'); | ||||
| 		maxSeg = 16; // Reasonable fallback for max segments | ||||
| 	} | ||||
| 	pmt          = i.fs.pmt; | ||||
| 	if (pcMode && !i.wifi.ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide"); | ||||
| 	gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none"; | ||||
| @@ -912,12 +923,24 @@ function populateSegments(s) | ||||
| 		gId(`segr${i}`).classList.add("hide"); | ||||
| 	} | ||||
| 	if (segCount < 2) { | ||||
| 		gId(`segd${lSeg}`).classList.add("hide"); // hide delete if only one segment | ||||
| 		if (parseInt(gId("seg0bri").value)==255) gId(`segp0`).classList.add("hide"); | ||||
| 		// Add safety check for segment elements to prevent UI crashes | ||||
| 		const segdElement = gId(`segd${lSeg}`); | ||||
| 		if (segdElement) segdElement.classList.add("hide"); // hide delete if only one segment | ||||
| 		const seg0briElement = gId("seg0bri"); | ||||
| 		const segp0Element = gId(`segp0`); | ||||
| 		if (seg0briElement && segp0Element && parseInt(seg0briElement.value)==255) segp0Element.classList.add("hide"); | ||||
| 		// hide segment controls if there is only one segment in simplified UI | ||||
| 		if (simplifiedUI) gId("segcont").classList.add("hide"); | ||||
| 	} | ||||
| 	if (!isM && !noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value)<ledCount) gId(`segr${lSeg}`).classList.remove("hide"); | ||||
| 	// Add safety checks for segment control elements | ||||
| 	const segSElement = gId(`seg${lSeg}s`); | ||||
| 	const segEElement = gId(`seg${lSeg}e`); | ||||
| 	const segrElement = gId(`segr${lSeg}`); | ||||
| 	if (!isM && !noNewSegs && segSElement && segEElement && segrElement) { | ||||
| 		const segLen = cfg.comp.seglen ? parseInt(segSElement.value) : 0; | ||||
| 		const segEnd = parseInt(segEElement.value); | ||||
| 		if (segLen + segEnd < ledCount) segrElement.classList.remove("hide"); | ||||
| 	} | ||||
| 	gId('segutil2').style.display = (segCount > 1) ? "block":"none"; // rsbtn parent | ||||
|  | ||||
| 	if (Array.isArray(li.maps) && li.maps.length>1) { | ||||
| @@ -2253,7 +2276,9 @@ function rptSeg(s) | ||||
| 	var rev = gId(`seg${s}rev`).checked; | ||||
| 	var mi = gId(`seg${s}mi`).checked; | ||||
| 	var sel = gId(`seg${s}sel`).checked; | ||||
| 	var pwr = gId(`seg${s}pwr`).classList.contains('act'); | ||||
| 	// Add safety check for segment power element to prevent UI crashes | ||||
| 	const segPwrElement = gId(`seg${s}pwr`); | ||||
| 	var pwr = segPwrElement ? segPwrElement.classList.contains('act') : false; | ||||
| 	var obj = {"seg": {"id": s, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop, "rev": rev, "mi": mi, "on": pwr, "bri": parseInt(gId(`seg${s}bri`).value), "sel": sel}}; | ||||
| 	if (gId(`seg${s}grp`)) { | ||||
| 		var grp = parseInt(gId(`seg${s}grp`).value); | ||||
| @@ -2380,7 +2405,13 @@ function setGrp(s, g) | ||||
|  | ||||
| function setSegPwr(s) | ||||
| { | ||||
| 	var pwr = gId(`seg${s}pwr`).classList.contains('act'); | ||||
| 	// Add safety check for segment power element to prevent UI crashes | ||||
| 	const segPwrElement = gId(`seg${s}pwr`); | ||||
| 	if (!segPwrElement) { | ||||
| 		console.warn('Segment power element not found, skipping power toggle'); | ||||
| 		return; | ||||
| 	} | ||||
| 	var pwr = segPwrElement.classList.contains('act'); | ||||
| 	var obj = {"seg": {"id": s, "on": !pwr}}; | ||||
| 	requestJson(obj); | ||||
| } | ||||
|   | ||||
| @@ -107,6 +107,7 @@ Y: <input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()" | ||||
| 				Sf[`P${p}H`].value = ph; | ||||
| 			} | ||||
| 		} | ||||
| 		UI(); // Update the preview after generating panels | ||||
| 	} | ||||
|  | ||||
| 	function expand(o,i) | ||||
|   | ||||
| @@ -908,7 +908,6 @@ Swap: <select id="xw${s}" name="XW${s}"> | ||||
| 			<option value="3">None (not recommended)</option> | ||||
| 		</select><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 | ||||
| 		<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> | ||||
|   | ||||
| @@ -76,7 +76,7 @@ | ||||
| 		A huge thank you to everyone who helped me create WLED!<br><br> | ||||
| 		(c) 2016-2024 Christian Schwinne <br> | ||||
| 		<i>Licensed under the <a href="https://github.com/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> | ||||
| 		<button type="button" onclick="B()">Back</button><button type="submit">Save</button> | ||||
| 	</form> | ||||
|   | ||||
| @@ -258,10 +258,10 @@ Static subnet mask:<br> | ||||
| 			<h3>Ethernet Type</h3> | ||||
| 			<select name="ETH"> | ||||
| 				<option value="0">None</option> | ||||
| 				<option value="6">IoTorero/ESP32Deux/RGB2Go</option> | ||||
| 				<option value="9">ABC! WLED V43 & compatible</option> | ||||
| 				<option value="2">ESP32-POE</option> | ||||
| 				<option value="11">ESP32-POE-WROVER</option> | ||||
| 				<option value="6">ESP32Deux/RGB2Go</option> | ||||
| 				<option value="7">KIT-VE</option> | ||||
| 				<option value="12">LILYGO T-POE Pro</option> | ||||
| 				<option value="8">QuinLED-Dig-Octa & T-ETH-POE</option> | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
| <body onload="GetV()"> | ||||
| 	<h2>WLED Software Update</h2> | ||||
| 	<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"  | ||||
| 		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> | ||||
|   | ||||
| @@ -191,7 +191,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 | ||||
|         // only change brightness if value changed | ||||
|         if (bri != e131_data[dataOffset]) {                                         | ||||
|           bri = e131_data[dataOffset]; | ||||
|           strip.setBrightness(scaledBri(bri), false); | ||||
|           strip.setBrightness(bri, false); | ||||
|           stateUpdated(CALL_MODE_WS_SEND); | ||||
|         } | ||||
|         return; | ||||
|   | ||||
| @@ -24,6 +24,10 @@ void handleIO(); | ||||
| void IRAM_ATTR touchButtonISR(); | ||||
|  | ||||
| //cfg.cpp | ||||
| bool backupConfig(); | ||||
| bool restoreConfig(); | ||||
| bool verifyConfig(); | ||||
| void resetConfig(); | ||||
| bool deserializeConfig(JsonObject doc, bool fromFS = false); | ||||
| bool deserializeConfigFromFS(); | ||||
| bool deserializeConfigSec(); | ||||
| @@ -69,133 +73,6 @@ typedef struct WiFiConfig { | ||||
|   } | ||||
| } 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 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); | ||||
|  | ||||
| //dmx_output.cpp | ||||
| void initDMXOutput(); | ||||
| void handleDMXOutput(); | ||||
| @@ -223,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 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); }; | ||||
| 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 | ||||
| void handleHue(); | ||||
| @@ -433,6 +315,7 @@ class Usermod { | ||||
|     virtual void onMqttConnect(bool sessionPresent) {}                       // fired when MQTT connection is established (so usermod can subscribe) | ||||
|     virtual bool onMqttMessage(char* topic, char* payload) { return false; } // fired upon MQTT message received (wled topic) | ||||
|     virtual bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) { return false; } // fired upon ESP-NOW message received | ||||
|     virtual bool onUdpPacket(uint8_t* payload, size_t len) { return false; } //fired upon UDP packet received | ||||
|     virtual void onUpdateBegin(bool) {}                                      // fired prior to and after unsuccessful firmware update | ||||
|     virtual void onStateChange(uint8_t mode) {}                              // fired upon WLED state change | ||||
|     virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} | ||||
| @@ -472,6 +355,7 @@ namespace UsermodManager { | ||||
| #ifndef WLED_DISABLE_ESPNOW | ||||
|   bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); | ||||
| #endif | ||||
|   bool onUdpPacket(uint8_t* payload, size_t len); | ||||
|   void onUpdateBegin(bool); | ||||
|   void onStateChange(uint8_t); | ||||
|   Usermod* lookup(uint16_t mod_id); | ||||
| @@ -580,6 +464,10 @@ extern "C" { | ||||
| #define d_free free | ||||
| #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 | ||||
| // Modeled after std::lock_guard | ||||
| class JSONBufferGuard { | ||||
|   | ||||
							
								
								
									
										153
									
								
								wled00/file.cpp
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								wled00/file.cpp
									
									
									
									
									
								
							| @@ -439,3 +439,156 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ | ||||
|   } | ||||
|   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 | ||||
|   for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) { | ||||
|     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; | ||||
|     if (seg.isInTransition()) seg.startTransition(0); // setting transition time to 0 will stop transition in next frame | ||||
|     strip.setTransition(0); | ||||
|     strip.setBrightness(scaledBri(bri), true); | ||||
|     strip.setBrightness(bri, true); | ||||
|  | ||||
|     // freeze and init to black | ||||
|     if (!seg.freeze) { | ||||
|   | ||||
| @@ -57,7 +57,7 @@ void toggleOnOff() | ||||
| //scales the brightness with the briMultiplier factor | ||||
| byte scaledBri(byte in) | ||||
| { | ||||
|   unsigned val = ((uint16_t)in*briMultiplier)/100; | ||||
|   unsigned val = ((unsigned)in*briMultiplier)/100; | ||||
|   if (val > 255) val = 255; | ||||
|   return (byte)val; | ||||
| } | ||||
| @@ -68,7 +68,7 @@ void applyBri() { | ||||
|   if (realtimeOverride || !(realtimeMode && arlsForceMaxBri)) | ||||
|   { | ||||
|     //DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld); | ||||
|     strip.setBrightness(scaledBri(briT)); | ||||
|     strip.setBrightness(briT); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -351,7 +351,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) | ||||
|     t = request->arg(F("TP")).toInt(); | ||||
|     randomPaletteChangeTime = MIN(255,MAX(1,t)); | ||||
|     useHarmonicRandomPalette = request->hasArg(F("TH")); | ||||
|     useRainbowWheel = request->hasArg(F("RW")); | ||||
|  | ||||
|     nightlightTargetBri = request->arg(F("TB")).toInt(); | ||||
|     t = request->arg(F("TL")).toInt(); | ||||
|   | ||||
| @@ -1,877 +0,0 @@ | ||||
| #include "AsyncMqttClient.hpp" | ||||
|  | ||||
| AsyncMqttClient::AsyncMqttClient() | ||||
| : _connected(false) | ||||
| , _connectPacketNotEnoughSpace(false) | ||||
| , _disconnectFlagged(false) | ||||
| , _tlsBadFingerprint(false) | ||||
| , _lastClientActivity(0) | ||||
| , _lastServerActivity(0) | ||||
| , _lastPingRequestTime(0) | ||||
| , _host(nullptr) | ||||
| , _useIp(false) | ||||
| #if ASYNC_TCP_SSL_ENABLED | ||||
| , _secure(false) | ||||
| #endif | ||||
| , _port(0) | ||||
| , _keepAlive(15) | ||||
| , _cleanSession(true) | ||||
| , _clientId(nullptr) | ||||
| , _username(nullptr) | ||||
| , _password(nullptr) | ||||
| , _willTopic(nullptr) | ||||
| , _willPayload(nullptr) | ||||
| , _willPayloadLength(0) | ||||
| , _willQos(0) | ||||
| , _willRetain(false) | ||||
| , _parsingInformation { .bufferState = AsyncMqttClientInternals::BufferState::NONE } | ||||
| , _currentParsedPacket(nullptr) | ||||
| , _remainingLengthBufferPosition(0) | ||||
| , _nextPacketId(1) { | ||||
|   _client.onConnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onConnect(c); }, this); | ||||
|   _client.onDisconnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onDisconnect(c); }, this); | ||||
|   _client.onError([](void* obj, AsyncClient* c, int8_t error) { (static_cast<AsyncMqttClient*>(obj))->_onError(c, error); }, this); | ||||
|   _client.onTimeout([](void* obj, AsyncClient* c, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onTimeout(c, time); }, this); | ||||
|   _client.onAck([](void* obj, AsyncClient* c, size_t len, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onAck(c, len, time); }, this); | ||||
|   _client.onData([](void* obj, AsyncClient* c, void* data, size_t len) { (static_cast<AsyncMqttClient*>(obj))->_onData(c, static_cast<char*>(data), len); }, this); | ||||
|   _client.onPoll([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onPoll(c); }, this); | ||||
|  | ||||
| #ifdef ESP32 | ||||
|   sprintf(_generatedClientId, "esp32%06x", (uint32_t)ESP.getEfuseMac()); | ||||
|   _xSemaphore = xSemaphoreCreateMutex(); | ||||
| #elif defined(ESP8266) | ||||
|   sprintf(_generatedClientId, "esp8266%06x", (uint32_t)ESP.getChipId()); | ||||
| #endif | ||||
|   _clientId = _generatedClientId; | ||||
|  | ||||
|   setMaxTopicLength(128); | ||||
| } | ||||
|  | ||||
| AsyncMqttClient::~AsyncMqttClient() { | ||||
|   delete _currentParsedPacket; | ||||
|   delete[] _parsingInformation.topicBuffer; | ||||
| #ifdef ESP32 | ||||
|   vSemaphoreDelete(_xSemaphore); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::setKeepAlive(uint16_t keepAlive) { | ||||
|   _keepAlive = keepAlive; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::setClientId(const char* clientId) { | ||||
|   _clientId = clientId; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::setCleanSession(bool cleanSession) { | ||||
|   _cleanSession = cleanSession; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::setMaxTopicLength(uint16_t maxTopicLength) { | ||||
|   _parsingInformation.maxTopicLength = maxTopicLength; | ||||
|   delete[] _parsingInformation.topicBuffer; | ||||
|   _parsingInformation.topicBuffer = new char[maxTopicLength + 1]; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::setCredentials(const char* username, const char* password) { | ||||
|   _username = username; | ||||
|   _password = password; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::setWill(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length) { | ||||
|   _willTopic = topic; | ||||
|   _willQos = qos; | ||||
|   _willRetain = retain; | ||||
|   _willPayload = payload; | ||||
|   _willPayloadLength = length; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::setServer(IPAddress ip, uint16_t port) { | ||||
|   _useIp = true; | ||||
|   _ip = ip; | ||||
|   _port = port; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::setServer(const char* host, uint16_t port) { | ||||
|   _useIp = false; | ||||
|   _host = host; | ||||
|   _port = port; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| #if ASYNC_TCP_SSL_ENABLED | ||||
| AsyncMqttClient& AsyncMqttClient::setSecure(bool secure) { | ||||
|   _secure = secure; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::addServerFingerprint(const uint8_t* fingerprint) { | ||||
|   std::array<uint8_t, SHA1_SIZE> newFingerprint; | ||||
|   memcpy(newFingerprint.data(), fingerprint, SHA1_SIZE); | ||||
|   _secureServerFingerprints.push_back(newFingerprint); | ||||
|   return *this; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) { | ||||
|   _onConnectUserCallbacks.push_back(callback); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) { | ||||
|   _onDisconnectUserCallbacks.push_back(callback); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) { | ||||
|   _onSubscribeUserCallbacks.push_back(callback); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) { | ||||
|   _onUnsubscribeUserCallbacks.push_back(callback); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) { | ||||
|   _onMessageUserCallbacks.push_back(callback); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| AsyncMqttClient& AsyncMqttClient::onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) { | ||||
|   _onPublishUserCallbacks.push_back(callback); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_freeCurrentParsedPacket() { | ||||
|   delete _currentParsedPacket; | ||||
|   _currentParsedPacket = nullptr; | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_clear() { | ||||
|   _lastPingRequestTime = 0; | ||||
|   _connected = false; | ||||
|   _disconnectFlagged = false; | ||||
|   _connectPacketNotEnoughSpace = false; | ||||
|   _tlsBadFingerprint = false; | ||||
|   _freeCurrentParsedPacket(); | ||||
|  | ||||
|   _pendingPubRels.clear(); | ||||
|   _pendingPubRels.shrink_to_fit(); | ||||
|  | ||||
|   _toSendAcks.clear(); | ||||
|   _toSendAcks.shrink_to_fit(); | ||||
|  | ||||
|   _nextPacketId = 1; | ||||
|   _parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE; | ||||
| } | ||||
|  | ||||
| /* TCP */ | ||||
| void AsyncMqttClient::_onConnect(AsyncClient* client) { | ||||
|   (void)client; | ||||
|  | ||||
| #if ASYNC_TCP_SSL_ENABLED | ||||
|   if (_secure && _secureServerFingerprints.size() > 0) { | ||||
|     SSL* clientSsl = _client.getSSL(); | ||||
|  | ||||
|     bool sslFoundFingerprint = false; | ||||
|     for (std::array<uint8_t, SHA1_SIZE> fingerprint : _secureServerFingerprints) { | ||||
|       if (ssl_match_fingerprint(clientSsl, fingerprint.data()) == SSL_OK) { | ||||
|         sslFoundFingerprint = true; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (!sslFoundFingerprint) { | ||||
|       _tlsBadFingerprint = true; | ||||
|       _client.close(true); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   char fixedHeader[5]; | ||||
|   fixedHeader[0] = AsyncMqttClientInternals::PacketType.CONNECT; | ||||
|   fixedHeader[0] = fixedHeader[0] << 4; | ||||
|   fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.CONNECT_RESERVED; | ||||
|  | ||||
|   uint16_t protocolNameLength = 4; | ||||
|   char protocolNameLengthBytes[2]; | ||||
|   protocolNameLengthBytes[0] = protocolNameLength >> 8; | ||||
|   protocolNameLengthBytes[1] = protocolNameLength & 0xFF; | ||||
|  | ||||
|   char protocolLevel[1]; | ||||
|   protocolLevel[0] = 0x04; | ||||
|  | ||||
|   char connectFlags[1]; | ||||
|   connectFlags[0] = 0; | ||||
|   if (_cleanSession) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.CLEAN_SESSION; | ||||
|   if (_username != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.USERNAME; | ||||
|   if (_password != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.PASSWORD; | ||||
|   if (_willTopic != nullptr) { | ||||
|     connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL; | ||||
|     if (_willRetain) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_RETAIN; | ||||
|     switch (_willQos) { | ||||
|       case 0: | ||||
|         connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS0; | ||||
|         break; | ||||
|       case 1: | ||||
|         connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS1; | ||||
|         break; | ||||
|       case 2: | ||||
|         connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS2; | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   char keepAliveBytes[2]; | ||||
|   keepAliveBytes[0] = _keepAlive >> 8; | ||||
|   keepAliveBytes[1] = _keepAlive & 0xFF; | ||||
|  | ||||
|   uint16_t clientIdLength = strlen(_clientId); | ||||
|   char clientIdLengthBytes[2]; | ||||
|   clientIdLengthBytes[0] = clientIdLength >> 8; | ||||
|   clientIdLengthBytes[1] = clientIdLength & 0xFF; | ||||
|  | ||||
|   // Optional fields | ||||
|   uint16_t willTopicLength = 0; | ||||
|   char willTopicLengthBytes[2]; | ||||
|   uint16_t willPayloadLength = _willPayloadLength; | ||||
|   char willPayloadLengthBytes[2]; | ||||
|   if (_willTopic != nullptr) { | ||||
|     willTopicLength = strlen(_willTopic); | ||||
|     willTopicLengthBytes[0] = willTopicLength >> 8; | ||||
|     willTopicLengthBytes[1] = willTopicLength & 0xFF; | ||||
|  | ||||
|     if (_willPayload != nullptr && willPayloadLength == 0) willPayloadLength = strlen(_willPayload); | ||||
|  | ||||
|     willPayloadLengthBytes[0] = willPayloadLength >> 8; | ||||
|     willPayloadLengthBytes[1] = willPayloadLength & 0xFF; | ||||
|   } | ||||
|  | ||||
|   uint16_t usernameLength = 0; | ||||
|   char usernameLengthBytes[2]; | ||||
|   if (_username != nullptr) { | ||||
|     usernameLength = strlen(_username); | ||||
|     usernameLengthBytes[0] = usernameLength >> 8; | ||||
|     usernameLengthBytes[1] = usernameLength & 0xFF; | ||||
|   } | ||||
|  | ||||
|   uint16_t passwordLength = 0; | ||||
|   char passwordLengthBytes[2]; | ||||
|   if (_password != nullptr) { | ||||
|     passwordLength = strlen(_password); | ||||
|     passwordLengthBytes[0] = passwordLength >> 8; | ||||
|     passwordLengthBytes[1] = passwordLength & 0xFF; | ||||
|   } | ||||
|  | ||||
|   uint32_t remainingLength = 2 + protocolNameLength + 1 + 1 + 2 + 2 + clientIdLength;  // always present | ||||
|   if (_willTopic != nullptr) remainingLength += 2 + willTopicLength + 2 + willPayloadLength; | ||||
|   if (_username != nullptr) remainingLength += 2 + usernameLength; | ||||
|   if (_password != nullptr) remainingLength += 2 + passwordLength; | ||||
|   uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1); | ||||
|  | ||||
|   uint32_t neededSpace = 1 + remainingLengthLength; | ||||
|   neededSpace += 2; | ||||
|   neededSpace += protocolNameLength; | ||||
|   neededSpace += 1; | ||||
|   neededSpace += 1; | ||||
|   neededSpace += 2; | ||||
|   neededSpace += 2; | ||||
|   neededSpace += clientIdLength; | ||||
|   if (_willTopic != nullptr) { | ||||
|     neededSpace += 2; | ||||
|     neededSpace += willTopicLength; | ||||
|  | ||||
|     neededSpace += 2; | ||||
|     if (_willPayload != nullptr) neededSpace += willPayloadLength; | ||||
|   } | ||||
|   if (_username != nullptr) { | ||||
|     neededSpace += 2; | ||||
|     neededSpace += usernameLength; | ||||
|   } | ||||
|   if (_password != nullptr) { | ||||
|     neededSpace += 2; | ||||
|     neededSpace += passwordLength; | ||||
|   } | ||||
|  | ||||
|   SEMAPHORE_TAKE(); | ||||
|   if (_client.space() < neededSpace) { | ||||
|     _connectPacketNotEnoughSpace = true; | ||||
|     _client.close(true); | ||||
|     SEMAPHORE_GIVE(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   _client.add(fixedHeader, 1 + remainingLengthLength); | ||||
|   _client.add(protocolNameLengthBytes, 2); | ||||
|   _client.add("MQTT", protocolNameLength); | ||||
|   _client.add(protocolLevel, 1); | ||||
|   _client.add(connectFlags, 1); | ||||
|   _client.add(keepAliveBytes, 2); | ||||
|   _client.add(clientIdLengthBytes, 2); | ||||
|   _client.add(_clientId, clientIdLength); | ||||
|   if (_willTopic != nullptr) { | ||||
|     _client.add(willTopicLengthBytes, 2); | ||||
|     _client.add(_willTopic, willTopicLength); | ||||
|  | ||||
|     _client.add(willPayloadLengthBytes, 2); | ||||
|     if (_willPayload != nullptr) _client.add(_willPayload, willPayloadLength); | ||||
|   } | ||||
|   if (_username != nullptr) { | ||||
|     _client.add(usernameLengthBytes, 2); | ||||
|     _client.add(_username, usernameLength); | ||||
|   } | ||||
|   if (_password != nullptr) { | ||||
|     _client.add(passwordLengthBytes, 2); | ||||
|     _client.add(_password, passwordLength); | ||||
|   } | ||||
|   _client.send(); | ||||
|   _lastClientActivity = millis(); | ||||
|   SEMAPHORE_GIVE(); | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onDisconnect(AsyncClient* client) { | ||||
|   (void)client; | ||||
|   if (!_disconnectFlagged) { | ||||
|     AsyncMqttClientDisconnectReason reason; | ||||
|  | ||||
|     if (_connectPacketNotEnoughSpace) { | ||||
|       reason = AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE; | ||||
|     } else if (_tlsBadFingerprint) { | ||||
|       reason = AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT; | ||||
|     } else { | ||||
|       reason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED; | ||||
|     } | ||||
|     for (auto callback : _onDisconnectUserCallbacks) callback(reason); | ||||
|   } | ||||
|   _clear(); | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onError(AsyncClient* client, int8_t error) { | ||||
|   (void)client; | ||||
|   (void)error; | ||||
|   // _onDisconnect called anyway | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onTimeout(AsyncClient* client, uint32_t time) { | ||||
|   (void)client; | ||||
|   (void)time; | ||||
|   // disconnection will be handled by ping/pong management | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onAck(AsyncClient* client, size_t len, uint32_t time) { | ||||
|   (void)client; | ||||
|   (void)len; | ||||
|   (void)time; | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onData(AsyncClient* client, char* data, size_t len) { | ||||
|   (void)client; | ||||
|   size_t currentBytePosition = 0; | ||||
|   char currentByte; | ||||
|   do { | ||||
|     switch (_parsingInformation.bufferState) { | ||||
|       case AsyncMqttClientInternals::BufferState::NONE: | ||||
|         currentByte = data[currentBytePosition++]; | ||||
|         _parsingInformation.packetType = currentByte >> 4; | ||||
|         _parsingInformation.packetFlags = (currentByte << 4) >> 4; | ||||
|         _parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::REMAINING_LENGTH; | ||||
|         _lastServerActivity = millis(); | ||||
|         switch (_parsingInformation.packetType) { | ||||
|           case AsyncMqttClientInternals::PacketType.CONNACK: | ||||
|             _currentParsedPacket = new AsyncMqttClientInternals::ConnAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onConnAck, this, std::placeholders::_1, std::placeholders::_2)); | ||||
|             break; | ||||
|           case AsyncMqttClientInternals::PacketType.PINGRESP: | ||||
|             _currentParsedPacket = new AsyncMqttClientInternals::PingRespPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPingResp, this)); | ||||
|             break; | ||||
|           case AsyncMqttClientInternals::PacketType.SUBACK: | ||||
|             _currentParsedPacket = new AsyncMqttClientInternals::SubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onSubAck, this, std::placeholders::_1, std::placeholders::_2)); | ||||
|             break; | ||||
|           case AsyncMqttClientInternals::PacketType.UNSUBACK: | ||||
|             _currentParsedPacket = new AsyncMqttClientInternals::UnsubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onUnsubAck, this, std::placeholders::_1)); | ||||
|             break; | ||||
|           case AsyncMqttClientInternals::PacketType.PUBLISH: | ||||
|             _currentParsedPacket = new AsyncMqttClientInternals::PublishPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7, std::placeholders::_8, std::placeholders::_9), std::bind(&AsyncMqttClient::_onPublish, this, std::placeholders::_1, std::placeholders::_2)); | ||||
|             break; | ||||
|           case AsyncMqttClientInternals::PacketType.PUBREL: | ||||
|             _currentParsedPacket = new AsyncMqttClientInternals::PubRelPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRel, this, std::placeholders::_1)); | ||||
|             break; | ||||
|           case AsyncMqttClientInternals::PacketType.PUBACK: | ||||
|             _currentParsedPacket = new AsyncMqttClientInternals::PubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubAck, this, std::placeholders::_1)); | ||||
|             break; | ||||
|           case AsyncMqttClientInternals::PacketType.PUBREC: | ||||
|             _currentParsedPacket = new AsyncMqttClientInternals::PubRecPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRec, this, std::placeholders::_1)); | ||||
|             break; | ||||
|           case AsyncMqttClientInternals::PacketType.PUBCOMP: | ||||
|             _currentParsedPacket = new AsyncMqttClientInternals::PubCompPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubComp, this, std::placeholders::_1)); | ||||
|             break; | ||||
|           default: | ||||
|             break; | ||||
|         } | ||||
|         break; | ||||
|       case AsyncMqttClientInternals::BufferState::REMAINING_LENGTH: | ||||
|         currentByte = data[currentBytePosition++]; | ||||
|         _remainingLengthBuffer[_remainingLengthBufferPosition++] = currentByte; | ||||
|         if (currentByte >> 7 == 0) { | ||||
|           _parsingInformation.remainingLength = AsyncMqttClientInternals::Helpers::decodeRemainingLength(_remainingLengthBuffer); | ||||
|           _remainingLengthBufferPosition = 0; | ||||
|           if (_parsingInformation.remainingLength > 0) { | ||||
|             _parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::VARIABLE_HEADER; | ||||
|           } else { | ||||
|             // PINGRESP is a special case where it has no variable header, so the packet ends right here | ||||
|             _parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE; | ||||
|             _onPingResp(); | ||||
|           } | ||||
|         } | ||||
|         break; | ||||
|       case AsyncMqttClientInternals::BufferState::VARIABLE_HEADER: | ||||
|         _currentParsedPacket->parseVariableHeader(data, len, ¤tBytePosition); | ||||
|         break; | ||||
|       case AsyncMqttClientInternals::BufferState::PAYLOAD: | ||||
|         _currentParsedPacket->parsePayload(data, len, ¤tBytePosition); | ||||
|         break; | ||||
|       default: | ||||
|         currentBytePosition = len; | ||||
|     } | ||||
|   } while (currentBytePosition != len); | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onPoll(AsyncClient* client) { | ||||
|   if (!_connected) return; | ||||
|  | ||||
|   // if there is too much time the client has sent a ping request without a response, disconnect client to avoid half open connections | ||||
|   if (_lastPingRequestTime != 0 && (millis() - _lastPingRequestTime) >= (_keepAlive * 1000 * 2)) { | ||||
|     disconnect(); | ||||
|     return; | ||||
|   // send ping to ensure the server will receive at least one message inside keepalive window | ||||
|   } else if (_lastPingRequestTime == 0 && (millis() - _lastClientActivity) >= (_keepAlive * 1000 * 0.7)) { | ||||
|     _sendPing(); | ||||
|  | ||||
|   // send ping to verify if the server is still there (ensure this is not a half connection) | ||||
|   } else if (_connected && _lastPingRequestTime == 0 && (millis() - _lastServerActivity) >= (_keepAlive * 1000 * 0.7)) { | ||||
|     _sendPing(); | ||||
|   } | ||||
|  | ||||
|   // handle to send ack packets | ||||
|  | ||||
|   _sendAcks(); | ||||
|  | ||||
|   // handle disconnect | ||||
|  | ||||
|   if (_disconnectFlagged) { | ||||
|     _sendDisconnect(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* MQTT */ | ||||
| void AsyncMqttClient::_onPingResp() { | ||||
|   _freeCurrentParsedPacket(); | ||||
|   _lastPingRequestTime = 0; | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onConnAck(bool sessionPresent, uint8_t connectReturnCode) { | ||||
|   (void)sessionPresent; | ||||
|   _freeCurrentParsedPacket(); | ||||
|  | ||||
|   if (connectReturnCode == 0) { | ||||
|     _connected = true; | ||||
|     for (auto callback : _onConnectUserCallbacks) callback(sessionPresent); | ||||
|   } else { | ||||
|     for (auto callback : _onDisconnectUserCallbacks) callback(static_cast<AsyncMqttClientDisconnectReason>(connectReturnCode)); | ||||
|     _disconnectFlagged = true; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onSubAck(uint16_t packetId, char status) { | ||||
|   _freeCurrentParsedPacket(); | ||||
|  | ||||
|   for (auto callback : _onSubscribeUserCallbacks) callback(packetId, status); | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onUnsubAck(uint16_t packetId) { | ||||
|   _freeCurrentParsedPacket(); | ||||
|  | ||||
|   for (auto callback : _onUnsubscribeUserCallbacks) callback(packetId); | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId) { | ||||
|   bool notifyPublish = true; | ||||
|  | ||||
|   if (qos == 2) { | ||||
|     for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) { | ||||
|       if (pendingPubRel.packetId == packetId) { | ||||
|         notifyPublish = false; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (notifyPublish) { | ||||
|     AsyncMqttClientMessageProperties properties; | ||||
|     properties.qos = qos; | ||||
|     properties.dup = dup; | ||||
|     properties.retain = retain; | ||||
|  | ||||
|     for (auto callback : _onMessageUserCallbacks) callback(topic, payload, properties, len, index, total); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onPublish(uint16_t packetId, uint8_t qos) { | ||||
|   AsyncMqttClientInternals::PendingAck pendingAck; | ||||
|  | ||||
|   if (qos == 1) { | ||||
|     pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBACK; | ||||
|     pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBACK_RESERVED; | ||||
|     pendingAck.packetId = packetId; | ||||
|     _toSendAcks.push_back(pendingAck); | ||||
|   } else if (qos == 2) { | ||||
|     pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREC; | ||||
|     pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREC_RESERVED; | ||||
|     pendingAck.packetId = packetId; | ||||
|     _toSendAcks.push_back(pendingAck); | ||||
|  | ||||
|     bool pubRelAwaiting = false; | ||||
|     for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) { | ||||
|       if (pendingPubRel.packetId == packetId) { | ||||
|         pubRelAwaiting = true; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (!pubRelAwaiting) { | ||||
|       AsyncMqttClientInternals::PendingPubRel pendingPubRel; | ||||
|       pendingPubRel.packetId = packetId; | ||||
|       _pendingPubRels.push_back(pendingPubRel); | ||||
|     } | ||||
|  | ||||
|     _sendAcks(); | ||||
|   } | ||||
|  | ||||
|   _freeCurrentParsedPacket(); | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onPubRel(uint16_t packetId) { | ||||
|   _freeCurrentParsedPacket(); | ||||
|  | ||||
|   AsyncMqttClientInternals::PendingAck pendingAck; | ||||
|   pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBCOMP; | ||||
|   pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBCOMP_RESERVED; | ||||
|   pendingAck.packetId = packetId; | ||||
|   _toSendAcks.push_back(pendingAck); | ||||
|  | ||||
|   for (size_t i = 0; i < _pendingPubRels.size(); i++) { | ||||
|     if (_pendingPubRels[i].packetId == packetId) { | ||||
|       _pendingPubRels.erase(_pendingPubRels.begin() + i); | ||||
|       _pendingPubRels.shrink_to_fit(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   _sendAcks(); | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onPubAck(uint16_t packetId) { | ||||
|   _freeCurrentParsedPacket(); | ||||
|  | ||||
|   for (auto callback : _onPublishUserCallbacks) callback(packetId); | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onPubRec(uint16_t packetId) { | ||||
|   _freeCurrentParsedPacket(); | ||||
|  | ||||
|   AsyncMqttClientInternals::PendingAck pendingAck; | ||||
|   pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREL; | ||||
|   pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREL_RESERVED; | ||||
|   pendingAck.packetId = packetId; | ||||
|   _toSendAcks.push_back(pendingAck); | ||||
|  | ||||
|   _sendAcks(); | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_onPubComp(uint16_t packetId) { | ||||
|   _freeCurrentParsedPacket(); | ||||
|  | ||||
|   for (auto callback : _onPublishUserCallbacks) callback(packetId); | ||||
| } | ||||
|  | ||||
| bool AsyncMqttClient::_sendPing() { | ||||
|   char fixedHeader[2]; | ||||
|   fixedHeader[0] = AsyncMqttClientInternals::PacketType.PINGREQ; | ||||
|   fixedHeader[0] = fixedHeader[0] << 4; | ||||
|   fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.PINGREQ_RESERVED; | ||||
|   fixedHeader[1] = 0; | ||||
|  | ||||
|   size_t neededSpace = 2; | ||||
|  | ||||
|   SEMAPHORE_TAKE(false); | ||||
|   if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; } | ||||
|  | ||||
|   _client.add(fixedHeader, 2); | ||||
|   _client.send(); | ||||
|   _lastClientActivity = millis(); | ||||
|   _lastPingRequestTime = millis(); | ||||
|  | ||||
|   SEMAPHORE_GIVE(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::_sendAcks() { | ||||
|   uint8_t neededAckSpace = 2 + 2; | ||||
|  | ||||
|   SEMAPHORE_TAKE(); | ||||
|   for (size_t i = 0; i < _toSendAcks.size(); i++) { | ||||
|     if (_client.space() < neededAckSpace) break; | ||||
|  | ||||
|     AsyncMqttClientInternals::PendingAck pendingAck = _toSendAcks[i]; | ||||
|  | ||||
|     char fixedHeader[2]; | ||||
|     fixedHeader[0] = pendingAck.packetType; | ||||
|     fixedHeader[0] = fixedHeader[0] << 4; | ||||
|     fixedHeader[0] = fixedHeader[0] | pendingAck.headerFlag; | ||||
|     fixedHeader[1] = 2; | ||||
|  | ||||
|     char packetIdBytes[2]; | ||||
|     packetIdBytes[0] = pendingAck.packetId >> 8; | ||||
|     packetIdBytes[1] = pendingAck.packetId & 0xFF; | ||||
|  | ||||
|     _client.add(fixedHeader, 2); | ||||
|     _client.add(packetIdBytes, 2); | ||||
|     _client.send(); | ||||
|  | ||||
|     _toSendAcks.erase(_toSendAcks.begin() + i); | ||||
|     _toSendAcks.shrink_to_fit(); | ||||
|  | ||||
|     _lastClientActivity = millis(); | ||||
|   } | ||||
|   SEMAPHORE_GIVE(); | ||||
| } | ||||
|  | ||||
| bool AsyncMqttClient::_sendDisconnect() { | ||||
|   if (!_connected) return true; | ||||
|  | ||||
|   const uint8_t neededSpace = 2; | ||||
|  | ||||
|   SEMAPHORE_TAKE(false); | ||||
|  | ||||
|   if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; } | ||||
|  | ||||
|   char fixedHeader[2]; | ||||
|   fixedHeader[0] = AsyncMqttClientInternals::PacketType.DISCONNECT; | ||||
|   fixedHeader[0] = fixedHeader[0] << 4; | ||||
|   fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.DISCONNECT_RESERVED; | ||||
|   fixedHeader[1] = 0; | ||||
|  | ||||
|   _client.add(fixedHeader, 2); | ||||
|   _client.send(); | ||||
|   _client.close(true); | ||||
|  | ||||
|   _disconnectFlagged = false; | ||||
|  | ||||
|   SEMAPHORE_GIVE(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| uint16_t AsyncMqttClient::_getNextPacketId() { | ||||
|   uint16_t nextPacketId = _nextPacketId; | ||||
|  | ||||
|   if (_nextPacketId == 65535) _nextPacketId = 0;  // 0 is forbidden | ||||
|   _nextPacketId++; | ||||
|  | ||||
|   return nextPacketId; | ||||
| } | ||||
|  | ||||
| bool AsyncMqttClient::connected() const { | ||||
|   return _connected; | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::connect() { | ||||
|   if (_connected) return; | ||||
|  | ||||
| #if ASYNC_TCP_SSL_ENABLED | ||||
|   if (_useIp) { | ||||
|     _client.connect(_ip, _port, _secure); | ||||
|   } else { | ||||
|     _client.connect(_host, _port, _secure); | ||||
|   } | ||||
| #else | ||||
|   if (_useIp) { | ||||
|     _client.connect(_ip, _port); | ||||
|   } else { | ||||
|     _client.connect(_host, _port); | ||||
|   } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void AsyncMqttClient::disconnect(bool force) { | ||||
|   if (!_connected) return; | ||||
|  | ||||
|   if (force) { | ||||
|     _client.close(true); | ||||
|   } else { | ||||
|     _disconnectFlagged = true; | ||||
|     _sendDisconnect(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| uint16_t AsyncMqttClient::subscribe(const char* topic, uint8_t qos) { | ||||
|   if (!_connected) return 0; | ||||
|  | ||||
|   char fixedHeader[5]; | ||||
|   fixedHeader[0] = AsyncMqttClientInternals::PacketType.SUBSCRIBE; | ||||
|   fixedHeader[0] = fixedHeader[0] << 4; | ||||
|   fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.SUBSCRIBE_RESERVED; | ||||
|  | ||||
|   uint16_t topicLength = strlen(topic); | ||||
|   char topicLengthBytes[2]; | ||||
|   topicLengthBytes[0] = topicLength >> 8; | ||||
|   topicLengthBytes[1] = topicLength & 0xFF; | ||||
|  | ||||
|   char qosByte[1]; | ||||
|   qosByte[0] = qos; | ||||
|  | ||||
|   uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength + 1, fixedHeader + 1); | ||||
|  | ||||
|   size_t neededSpace = 0; | ||||
|   neededSpace += 1 + remainingLengthLength; | ||||
|   neededSpace += 2; | ||||
|   neededSpace += 2; | ||||
|   neededSpace += topicLength; | ||||
|   neededSpace += 1; | ||||
|  | ||||
|   SEMAPHORE_TAKE(0); | ||||
|   if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; } | ||||
|  | ||||
|   uint16_t packetId = _getNextPacketId(); | ||||
|   char packetIdBytes[2]; | ||||
|   packetIdBytes[0] = packetId >> 8; | ||||
|   packetIdBytes[1] = packetId & 0xFF; | ||||
|  | ||||
|   _client.add(fixedHeader, 1 + remainingLengthLength); | ||||
|   _client.add(packetIdBytes, 2); | ||||
|   _client.add(topicLengthBytes, 2); | ||||
|   _client.add(topic, topicLength); | ||||
|   _client.add(qosByte, 1); | ||||
|   _client.send(); | ||||
|   _lastClientActivity = millis(); | ||||
|  | ||||
|   SEMAPHORE_GIVE(); | ||||
|   return packetId; | ||||
| } | ||||
|  | ||||
| uint16_t AsyncMqttClient::unsubscribe(const char* topic) { | ||||
|   if (!_connected) return 0; | ||||
|  | ||||
|   char fixedHeader[5]; | ||||
|   fixedHeader[0] = AsyncMqttClientInternals::PacketType.UNSUBSCRIBE; | ||||
|   fixedHeader[0] = fixedHeader[0] << 4; | ||||
|   fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.UNSUBSCRIBE_RESERVED; | ||||
|  | ||||
|   uint16_t topicLength = strlen(topic); | ||||
|   char topicLengthBytes[2]; | ||||
|   topicLengthBytes[0] = topicLength >> 8; | ||||
|   topicLengthBytes[1] = topicLength & 0xFF; | ||||
|  | ||||
|   uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength, fixedHeader + 1); | ||||
|  | ||||
|   size_t neededSpace = 0; | ||||
|   neededSpace += 1 + remainingLengthLength; | ||||
|   neededSpace += 2; | ||||
|   neededSpace += 2; | ||||
|   neededSpace += topicLength; | ||||
|  | ||||
|   SEMAPHORE_TAKE(0); | ||||
|   if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; } | ||||
|  | ||||
|   uint16_t packetId = _getNextPacketId(); | ||||
|   char packetIdBytes[2]; | ||||
|   packetIdBytes[0] = packetId >> 8; | ||||
|   packetIdBytes[1] = packetId & 0xFF; | ||||
|  | ||||
|   _client.add(fixedHeader, 1 + remainingLengthLength); | ||||
|   _client.add(packetIdBytes, 2); | ||||
|   _client.add(topicLengthBytes, 2); | ||||
|   _client.add(topic, topicLength); | ||||
|   _client.send(); | ||||
|   _lastClientActivity = millis(); | ||||
|  | ||||
|   SEMAPHORE_GIVE(); | ||||
|   return packetId; | ||||
| } | ||||
|  | ||||
| uint16_t AsyncMqttClient::publish(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length, bool dup, uint16_t message_id) { | ||||
|   if (!_connected) return 0; | ||||
|  | ||||
|   char fixedHeader[5]; | ||||
|   fixedHeader[0] = AsyncMqttClientInternals::PacketType.PUBLISH; | ||||
|   fixedHeader[0] = fixedHeader[0] << 4; | ||||
|   if (dup) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_DUP; | ||||
|   if (retain) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_RETAIN; | ||||
|   switch (qos) { | ||||
|     case 0: | ||||
|       fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS0; | ||||
|       break; | ||||
|     case 1: | ||||
|       fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS1; | ||||
|       break; | ||||
|     case 2: | ||||
|       fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS2; | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   uint16_t topicLength = strlen(topic); | ||||
|   char topicLengthBytes[2]; | ||||
|   topicLengthBytes[0] = topicLength >> 8; | ||||
|   topicLengthBytes[1] = topicLength & 0xFF; | ||||
|  | ||||
|   uint32_t payloadLength = length; | ||||
|   if (payload != nullptr && payloadLength == 0) payloadLength = strlen(payload); | ||||
|  | ||||
|   uint32_t remainingLength = 2 + topicLength + payloadLength; | ||||
|   if (qos != 0) remainingLength += 2; | ||||
|   uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1); | ||||
|  | ||||
|   size_t neededSpace = 0; | ||||
|   neededSpace += 1 + remainingLengthLength; | ||||
|   neededSpace += 2; | ||||
|   neededSpace += topicLength; | ||||
|   if (qos != 0) neededSpace += 2; | ||||
|   if (payload != nullptr) neededSpace += payloadLength; | ||||
|  | ||||
|   SEMAPHORE_TAKE(0); | ||||
|   if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; } | ||||
|  | ||||
|   uint16_t packetId = 0; | ||||
|   char packetIdBytes[2]; | ||||
|   if (qos != 0) { | ||||
|     if (dup && message_id > 0) { | ||||
|       packetId = message_id; | ||||
|     } else { | ||||
|       packetId = _getNextPacketId(); | ||||
|     } | ||||
|  | ||||
|     packetIdBytes[0] = packetId >> 8; | ||||
|     packetIdBytes[1] = packetId & 0xFF; | ||||
|   } | ||||
|  | ||||
|   _client.add(fixedHeader, 1 + remainingLengthLength); | ||||
|   _client.add(topicLengthBytes, 2); | ||||
|   _client.add(topic, topicLength); | ||||
|   if (qos != 0) _client.add(packetIdBytes, 2); | ||||
|   if (payload != nullptr) _client.add(payload, payloadLength); | ||||
|   _client.send(); | ||||
|   _lastClientActivity = millis(); | ||||
|  | ||||
|   SEMAPHORE_GIVE(); | ||||
|   if (qos != 0) { | ||||
|     return packetId; | ||||
|   } else { | ||||
|     return 1; | ||||
|   } | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| #ifndef SRC_ASYNCMQTTCLIENT_H_ | ||||
| #define SRC_ASYNCMQTTCLIENT_H_ | ||||
|  | ||||
| #include "AsyncMqttClient.hpp" | ||||
|  | ||||
| #endif  // SRC_ASYNCMQTTCLIENT_H_ | ||||
| @@ -1,166 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <functional> | ||||
| #include <vector> | ||||
|  | ||||
| #include "Arduino.h" | ||||
|  | ||||
| #ifdef ESP32 | ||||
| #include <AsyncTCP.h> | ||||
| #include <freertos/semphr.h> | ||||
| #elif defined(ESP8266) | ||||
| #include <ESPAsyncTCP.h> | ||||
| #else | ||||
| #error Platform not supported | ||||
| #endif | ||||
|  | ||||
| #if ASYNC_TCP_SSL_ENABLED | ||||
| #include <tcp_axtls.h> | ||||
| #define SHA1_SIZE 20 | ||||
| #endif | ||||
|  | ||||
| #include "AsyncMqttClient/Flags.hpp" | ||||
| #include "AsyncMqttClient/ParsingInformation.hpp" | ||||
| #include "AsyncMqttClient/MessageProperties.hpp" | ||||
| #include "AsyncMqttClient/Helpers.hpp" | ||||
| #include "AsyncMqttClient/Callbacks.hpp" | ||||
| #include "AsyncMqttClient/DisconnectReasons.hpp" | ||||
| #include "AsyncMqttClient/Storage.hpp" | ||||
|  | ||||
| #include "AsyncMqttClient/Packets/Packet.hpp" | ||||
| #include "AsyncMqttClient/Packets/ConnAckPacket.hpp" | ||||
| #include "AsyncMqttClient/Packets/PingRespPacket.hpp" | ||||
| #include "AsyncMqttClient/Packets/SubAckPacket.hpp" | ||||
| #include "AsyncMqttClient/Packets/UnsubAckPacket.hpp" | ||||
| #include "AsyncMqttClient/Packets/PublishPacket.hpp" | ||||
| #include "AsyncMqttClient/Packets/PubRelPacket.hpp" | ||||
| #include "AsyncMqttClient/Packets/PubAckPacket.hpp" | ||||
| #include "AsyncMqttClient/Packets/PubRecPacket.hpp" | ||||
| #include "AsyncMqttClient/Packets/PubCompPacket.hpp" | ||||
|  | ||||
| #if ESP32 | ||||
| #define SEMAPHORE_TAKE(X) if (xSemaphoreTake(_xSemaphore, 1000 / portTICK_PERIOD_MS) != pdTRUE) { return X; }  // Waits max 1000ms | ||||
| #define SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore); | ||||
| #elif defined(ESP8266) | ||||
| #define SEMAPHORE_TAKE(X) void() | ||||
| #define SEMAPHORE_GIVE() void() | ||||
| #endif | ||||
|  | ||||
| class AsyncMqttClient { | ||||
|  public: | ||||
|   AsyncMqttClient(); | ||||
|   ~AsyncMqttClient(); | ||||
|  | ||||
|   AsyncMqttClient& setKeepAlive(uint16_t keepAlive); | ||||
|   AsyncMqttClient& setClientId(const char* clientId); | ||||
|   AsyncMqttClient& setCleanSession(bool cleanSession); | ||||
|   AsyncMqttClient& setMaxTopicLength(uint16_t maxTopicLength); | ||||
|   AsyncMqttClient& setCredentials(const char* username, const char* password = nullptr); | ||||
|   AsyncMqttClient& setWill(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0); | ||||
|   AsyncMqttClient& setServer(IPAddress ip, uint16_t port); | ||||
|   AsyncMqttClient& setServer(const char* host, uint16_t port); | ||||
| #if ASYNC_TCP_SSL_ENABLED | ||||
|   AsyncMqttClient& setSecure(bool secure); | ||||
|   AsyncMqttClient& addServerFingerprint(const uint8_t* fingerprint); | ||||
| #endif | ||||
|  | ||||
|   AsyncMqttClient& onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback); | ||||
|   AsyncMqttClient& onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback); | ||||
|   AsyncMqttClient& onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback); | ||||
|   AsyncMqttClient& onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback); | ||||
|   AsyncMqttClient& onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback); | ||||
|   AsyncMqttClient& onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback); | ||||
|  | ||||
|   bool connected() const; | ||||
|   void connect(); | ||||
|   void disconnect(bool force = false); | ||||
|   uint16_t subscribe(const char* topic, uint8_t qos); | ||||
|   uint16_t unsubscribe(const char* topic); | ||||
|   uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0); | ||||
|  | ||||
|  private: | ||||
|   AsyncClient _client; | ||||
|  | ||||
|   bool _connected; | ||||
|   bool _connectPacketNotEnoughSpace; | ||||
|   bool _disconnectFlagged; | ||||
|   bool _tlsBadFingerprint; | ||||
|   uint32_t _lastClientActivity; | ||||
|   uint32_t _lastServerActivity; | ||||
|   uint32_t _lastPingRequestTime; | ||||
|  | ||||
|   char _generatedClientId[13 + 1];  // esp8266abc123 | ||||
|   IPAddress _ip; | ||||
|   const char* _host; | ||||
|   bool _useIp; | ||||
| #if ASYNC_TCP_SSL_ENABLED | ||||
|   bool _secure; | ||||
| #endif | ||||
|   uint16_t _port; | ||||
|   uint16_t _keepAlive; | ||||
|   bool _cleanSession; | ||||
|   const char* _clientId; | ||||
|   const char* _username; | ||||
|   const char* _password; | ||||
|   const char* _willTopic; | ||||
|   const char* _willPayload; | ||||
|   uint16_t _willPayloadLength; | ||||
|   uint8_t _willQos; | ||||
|   bool _willRetain; | ||||
|  | ||||
| #if ASYNC_TCP_SSL_ENABLED | ||||
|   std::vector<std::array<uint8_t, SHA1_SIZE>> _secureServerFingerprints; | ||||
| #endif | ||||
|  | ||||
|   std::vector<AsyncMqttClientInternals::OnConnectUserCallback> _onConnectUserCallbacks; | ||||
|   std::vector<AsyncMqttClientInternals::OnDisconnectUserCallback> _onDisconnectUserCallbacks; | ||||
|   std::vector<AsyncMqttClientInternals::OnSubscribeUserCallback> _onSubscribeUserCallbacks; | ||||
|   std::vector<AsyncMqttClientInternals::OnUnsubscribeUserCallback> _onUnsubscribeUserCallbacks; | ||||
|   std::vector<AsyncMqttClientInternals::OnMessageUserCallback> _onMessageUserCallbacks; | ||||
|   std::vector<AsyncMqttClientInternals::OnPublishUserCallback> _onPublishUserCallbacks; | ||||
|  | ||||
|   AsyncMqttClientInternals::ParsingInformation _parsingInformation; | ||||
|   AsyncMqttClientInternals::Packet* _currentParsedPacket; | ||||
|   uint8_t _remainingLengthBufferPosition; | ||||
|   char _remainingLengthBuffer[4]; | ||||
|  | ||||
|   uint16_t _nextPacketId; | ||||
|  | ||||
|   std::vector<AsyncMqttClientInternals::PendingPubRel> _pendingPubRels; | ||||
|  | ||||
|   std::vector<AsyncMqttClientInternals::PendingAck> _toSendAcks; | ||||
|  | ||||
| #ifdef ESP32 | ||||
|   SemaphoreHandle_t _xSemaphore = nullptr; | ||||
| #endif | ||||
|  | ||||
|   void _clear(); | ||||
|   void _freeCurrentParsedPacket(); | ||||
|  | ||||
|   // TCP | ||||
|   void _onConnect(AsyncClient* client); | ||||
|   void _onDisconnect(AsyncClient* client); | ||||
|   static void _onError(AsyncClient* client, int8_t error); | ||||
|   void _onTimeout(AsyncClient* client, uint32_t time); | ||||
|   static void _onAck(AsyncClient* client, size_t len, uint32_t time); | ||||
|   void _onData(AsyncClient* client, char* data, size_t len); | ||||
|   void _onPoll(AsyncClient* client); | ||||
|  | ||||
|   // MQTT | ||||
|   void _onPingResp(); | ||||
|   void _onConnAck(bool sessionPresent, uint8_t connectReturnCode); | ||||
|   void _onSubAck(uint16_t packetId, char status); | ||||
|   void _onUnsubAck(uint16_t packetId); | ||||
|   void _onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId); | ||||
|   void _onPublish(uint16_t packetId, uint8_t qos); | ||||
|   void _onPubRel(uint16_t packetId); | ||||
|   void _onPubAck(uint16_t packetId); | ||||
|   void _onPubRec(uint16_t packetId); | ||||
|   void _onPubComp(uint16_t packetId); | ||||
|  | ||||
|   bool _sendPing(); | ||||
|   void _sendAcks(); | ||||
|   bool _sendDisconnect(); | ||||
|  | ||||
|   uint16_t _getNextPacketId(); | ||||
| }; | ||||
| @@ -1,28 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <functional> | ||||
|  | ||||
| #include "DisconnectReasons.hpp" | ||||
| #include "MessageProperties.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| // user callbacks | ||||
| typedef std::function<void(bool sessionPresent)> OnConnectUserCallback; | ||||
| typedef std::function<void(AsyncMqttClientDisconnectReason reason)> OnDisconnectUserCallback; | ||||
| typedef std::function<void(uint16_t packetId, uint8_t qos)> OnSubscribeUserCallback; | ||||
| typedef std::function<void(uint16_t packetId)> OnUnsubscribeUserCallback; | ||||
| typedef std::function<void(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)> OnMessageUserCallback; | ||||
| typedef std::function<void(uint16_t packetId)> OnPublishUserCallback; | ||||
|  | ||||
| // internal callbacks | ||||
| typedef std::function<void(bool sessionPresent, uint8_t connectReturnCode)> OnConnAckInternalCallback; | ||||
| typedef std::function<void()> OnPingRespInternalCallback; | ||||
| typedef std::function<void(uint16_t packetId, char status)> OnSubAckInternalCallback; | ||||
| typedef std::function<void(uint16_t packetId)> OnUnsubAckInternalCallback; | ||||
| typedef std::function<void(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId)> OnMessageInternalCallback; | ||||
| typedef std::function<void(uint16_t packetId, uint8_t qos)> OnPublishInternalCallback; | ||||
| typedef std::function<void(uint16_t packetId)> OnPubRelInternalCallback; | ||||
| typedef std::function<void(uint16_t packetId)> OnPubAckInternalCallback; | ||||
| typedef std::function<void(uint16_t packetId)> OnPubRecInternalCallback; | ||||
| typedef std::function<void(uint16_t packetId)> OnPubCompInternalCallback; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,15 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| enum class AsyncMqttClientDisconnectReason : int8_t { | ||||
|   TCP_DISCONNECTED = 0, | ||||
|  | ||||
|   MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1, | ||||
|   MQTT_IDENTIFIER_REJECTED = 2, | ||||
|   MQTT_SERVER_UNAVAILABLE = 3, | ||||
|   MQTT_MALFORMED_CREDENTIALS = 4, | ||||
|   MQTT_NOT_AUTHORIZED = 5, | ||||
|  | ||||
|   ESP8266_NOT_ENOUGH_SPACE = 6, | ||||
|  | ||||
|   TLS_BAD_FINGERPRINT = 7 | ||||
| }; | ||||
| @@ -1,57 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| constexpr struct { | ||||
|   const uint8_t RESERVED    = 0; | ||||
|   const uint8_t CONNECT     = 1; | ||||
|   const uint8_t CONNACK     = 2; | ||||
|   const uint8_t PUBLISH     = 3; | ||||
|   const uint8_t PUBACK      = 4; | ||||
|   const uint8_t PUBREC      = 5; | ||||
|   const uint8_t PUBREL      = 6; | ||||
|   const uint8_t PUBCOMP     = 7; | ||||
|   const uint8_t SUBSCRIBE   = 8; | ||||
|   const uint8_t SUBACK      = 9; | ||||
|   const uint8_t UNSUBSCRIBE = 10; | ||||
|   const uint8_t UNSUBACK    = 11; | ||||
|   const uint8_t PINGREQ     = 12; | ||||
|   const uint8_t PINGRESP    = 13; | ||||
|   const uint8_t DISCONNECT  = 14; | ||||
|   const uint8_t RESERVED2   = 1; | ||||
| } PacketType; | ||||
|  | ||||
| constexpr struct { | ||||
|   const uint8_t CONNECT_RESERVED     = 0x00; | ||||
|   const uint8_t CONNACK_RESERVED     = 0x00; | ||||
|   const uint8_t PUBLISH_DUP          = 0x08; | ||||
|   const uint8_t PUBLISH_QOS0         = 0x00; | ||||
|   const uint8_t PUBLISH_QOS1         = 0x02; | ||||
|   const uint8_t PUBLISH_QOS2         = 0x04; | ||||
|   const uint8_t PUBLISH_QOSRESERVED  = 0x06; | ||||
|   const uint8_t PUBLISH_RETAIN       = 0x01; | ||||
|   const uint8_t PUBACK_RESERVED      = 0x00; | ||||
|   const uint8_t PUBREC_RESERVED      = 0x00; | ||||
|   const uint8_t PUBREL_RESERVED      = 0x02; | ||||
|   const uint8_t PUBCOMP_RESERVED     = 0x00; | ||||
|   const uint8_t SUBSCRIBE_RESERVED   = 0x02; | ||||
|   const uint8_t SUBACK_RESERVED      = 0x00; | ||||
|   const uint8_t UNSUBSCRIBE_RESERVED = 0x02; | ||||
|   const uint8_t UNSUBACK_RESERVED    = 0x00; | ||||
|   const uint8_t PINGREQ_RESERVED     = 0x00; | ||||
|   const uint8_t PINGRESP_RESERVED    = 0x00; | ||||
|   const uint8_t DISCONNECT_RESERVED  = 0x00; | ||||
|   const uint8_t RESERVED2_RESERVED   = 0x00; | ||||
| } HeaderFlag; | ||||
|  | ||||
| constexpr struct { | ||||
|   const uint8_t USERNAME      = 0x80; | ||||
|   const uint8_t PASSWORD      = 0x40; | ||||
|   const uint8_t WILL_RETAIN   = 0x20; | ||||
|   const uint8_t WILL_QOS0     = 0x00; | ||||
|   const uint8_t WILL_QOS1     = 0x08; | ||||
|   const uint8_t WILL_QOS2     = 0x10; | ||||
|   const uint8_t WILL          = 0x04; | ||||
|   const uint8_t CLEAN_SESSION = 0x02; | ||||
|   const uint8_t RESERVED      = 0x00; | ||||
| } ConnectFlag; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,38 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class Helpers { | ||||
|  public: | ||||
|   static uint32_t decodeRemainingLength(char* bytes) { | ||||
|     uint32_t multiplier = 1; | ||||
|     uint32_t value = 0; | ||||
|     uint8_t currentByte = 0; | ||||
|     uint8_t encodedByte; | ||||
|     do { | ||||
|       encodedByte = bytes[currentByte++]; | ||||
|       value += (encodedByte & 127) * multiplier; | ||||
|       multiplier *= 128; | ||||
|     } while ((encodedByte & 128) != 0); | ||||
|  | ||||
|     return value; | ||||
|   } | ||||
|  | ||||
|   static uint8_t encodeRemainingLength(uint32_t remainingLength, char* destination) { | ||||
|     uint8_t currentByte = 0; | ||||
|     uint8_t bytesNeeded = 0; | ||||
|  | ||||
|     do { | ||||
|       uint8_t encodedByte = remainingLength % 128; | ||||
|       remainingLength /= 128; | ||||
|       if (remainingLength > 0) { | ||||
|         encodedByte = encodedByte | 128; | ||||
|       } | ||||
|  | ||||
|       destination[currentByte++] = encodedByte; | ||||
|       bytesNeeded++; | ||||
|     } while (remainingLength > 0); | ||||
|  | ||||
|     return bytesNeeded; | ||||
|   } | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,7 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| struct AsyncMqttClientMessageProperties { | ||||
|   uint8_t qos; | ||||
|   bool dup; | ||||
|   bool retain; | ||||
| }; | ||||
| @@ -1,30 +0,0 @@ | ||||
| #include "ConnAckPacket.hpp" | ||||
|  | ||||
| using AsyncMqttClientInternals::ConnAckPacket; | ||||
|  | ||||
| ConnAckPacket::ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback) | ||||
| : _parsingInformation(parsingInformation) | ||||
| , _callback(callback) | ||||
| , _bytePosition(0) | ||||
| , _sessionPresent(false) | ||||
| , _connectReturnCode(0) { | ||||
| } | ||||
|  | ||||
| ConnAckPacket::~ConnAckPacket() { | ||||
| } | ||||
|  | ||||
| void ConnAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   char currentByte = data[(*currentBytePosition)++]; | ||||
|   if (_bytePosition++ == 0) { | ||||
|     _sessionPresent = (currentByte << 7) >> 7; | ||||
|   } else { | ||||
|     _connectReturnCode = currentByte; | ||||
|     _parsingInformation->bufferState = BufferState::NONE; | ||||
|     _callback(_sessionPresent, _connectReturnCode); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ConnAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   (void)data; | ||||
|   (void)currentBytePosition; | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "Packet.hpp" | ||||
| #include "../ParsingInformation.hpp" | ||||
| #include "../Callbacks.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class ConnAckPacket : public Packet { | ||||
|  public: | ||||
|   explicit ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback); | ||||
|   ~ConnAckPacket(); | ||||
|  | ||||
|   void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | ||||
|   void parsePayload(char* data, size_t len, size_t* currentBytePosition); | ||||
|  | ||||
|  private: | ||||
|   ParsingInformation* _parsingInformation; | ||||
|   OnConnAckInternalCallback _callback; | ||||
|  | ||||
|   uint8_t _bytePosition; | ||||
|   bool _sessionPresent; | ||||
|   uint8_t _connectReturnCode; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,11 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class Packet { | ||||
|  public: | ||||
|   virtual ~Packet() {} | ||||
|  | ||||
|   virtual void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) = 0; | ||||
|   virtual void parsePayload(char* data, size_t len, size_t* currentBytePosition) = 0; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,21 +0,0 @@ | ||||
| #include "PingRespPacket.hpp" | ||||
|  | ||||
| using AsyncMqttClientInternals::PingRespPacket; | ||||
|  | ||||
| PingRespPacket::PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback) | ||||
| : _parsingInformation(parsingInformation) | ||||
| , _callback(callback) { | ||||
| } | ||||
|  | ||||
| PingRespPacket::~PingRespPacket() { | ||||
| } | ||||
|  | ||||
| void PingRespPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   (void)data; | ||||
|   (void)currentBytePosition; | ||||
| } | ||||
|  | ||||
| void PingRespPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   (void)data; | ||||
|   (void)currentBytePosition; | ||||
| } | ||||
| @@ -1,21 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "Packet.hpp" | ||||
| #include "../ParsingInformation.hpp" | ||||
| #include "../Callbacks.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class PingRespPacket : public Packet { | ||||
|  public: | ||||
|   explicit PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback); | ||||
|   ~PingRespPacket(); | ||||
|  | ||||
|   void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | ||||
|   void parsePayload(char* data, size_t len, size_t* currentBytePosition); | ||||
|  | ||||
|  private: | ||||
|   ParsingInformation* _parsingInformation; | ||||
|   OnPingRespInternalCallback _callback; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,30 +0,0 @@ | ||||
| #include "PubAckPacket.hpp" | ||||
|  | ||||
| using AsyncMqttClientInternals::PubAckPacket; | ||||
|  | ||||
| PubAckPacket::PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback) | ||||
| : _parsingInformation(parsingInformation) | ||||
| , _callback(callback) | ||||
| , _bytePosition(0) | ||||
| , _packetIdMsb(0) | ||||
| , _packetId(0) { | ||||
| } | ||||
|  | ||||
| PubAckPacket::~PubAckPacket() { | ||||
| } | ||||
|  | ||||
| void PubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   char currentByte = data[(*currentBytePosition)++]; | ||||
|   if (_bytePosition++ == 0) { | ||||
|     _packetIdMsb = currentByte; | ||||
|   } else { | ||||
|     _packetId = currentByte | _packetIdMsb << 8; | ||||
|     _parsingInformation->bufferState = BufferState::NONE; | ||||
|     _callback(_packetId); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void PubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   (void)data; | ||||
|   (void)currentBytePosition; | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "Packet.hpp" | ||||
| #include "../ParsingInformation.hpp" | ||||
| #include "../Callbacks.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class PubAckPacket : public Packet { | ||||
|  public: | ||||
|   explicit PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback); | ||||
|   ~PubAckPacket(); | ||||
|  | ||||
|   void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | ||||
|   void parsePayload(char* data, size_t len, size_t* currentBytePosition); | ||||
|  | ||||
|  private: | ||||
|   ParsingInformation* _parsingInformation; | ||||
|   OnPubAckInternalCallback _callback; | ||||
|  | ||||
|   uint8_t _bytePosition; | ||||
|   char _packetIdMsb; | ||||
|   uint16_t _packetId; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,30 +0,0 @@ | ||||
| #include "PubCompPacket.hpp" | ||||
|  | ||||
| using AsyncMqttClientInternals::PubCompPacket; | ||||
|  | ||||
| PubCompPacket::PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback) | ||||
| : _parsingInformation(parsingInformation) | ||||
| , _callback(callback) | ||||
| , _bytePosition(0) | ||||
| , _packetIdMsb(0) | ||||
| , _packetId(0) { | ||||
| } | ||||
|  | ||||
| PubCompPacket::~PubCompPacket() { | ||||
| } | ||||
|  | ||||
| void PubCompPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   char currentByte = data[(*currentBytePosition)++]; | ||||
|   if (_bytePosition++ == 0) { | ||||
|     _packetIdMsb = currentByte; | ||||
|   } else { | ||||
|     _packetId = currentByte | _packetIdMsb << 8; | ||||
|     _parsingInformation->bufferState = BufferState::NONE; | ||||
|     _callback(_packetId); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void PubCompPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   (void)data; | ||||
|   (void)currentBytePosition; | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "Packet.hpp" | ||||
| #include "../ParsingInformation.hpp" | ||||
| #include "../Callbacks.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class PubCompPacket : public Packet { | ||||
|  public: | ||||
|   explicit PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback); | ||||
|   ~PubCompPacket(); | ||||
|  | ||||
|   void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | ||||
|   void parsePayload(char* data, size_t len, size_t* currentBytePosition); | ||||
|  | ||||
|  private: | ||||
|   ParsingInformation* _parsingInformation; | ||||
|   OnPubCompInternalCallback _callback; | ||||
|  | ||||
|   uint8_t _bytePosition; | ||||
|   char _packetIdMsb; | ||||
|   uint16_t _packetId; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,30 +0,0 @@ | ||||
| #include "PubRecPacket.hpp" | ||||
|  | ||||
| using AsyncMqttClientInternals::PubRecPacket; | ||||
|  | ||||
| PubRecPacket::PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback) | ||||
| : _parsingInformation(parsingInformation) | ||||
| , _callback(callback) | ||||
| , _bytePosition(0) | ||||
| , _packetIdMsb(0) | ||||
| , _packetId(0) { | ||||
| } | ||||
|  | ||||
| PubRecPacket::~PubRecPacket() { | ||||
| } | ||||
|  | ||||
| void PubRecPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   char currentByte = data[(*currentBytePosition)++]; | ||||
|   if (_bytePosition++ == 0) { | ||||
|     _packetIdMsb = currentByte; | ||||
|   } else { | ||||
|     _packetId = currentByte | _packetIdMsb << 8; | ||||
|     _parsingInformation->bufferState = BufferState::NONE; | ||||
|     _callback(_packetId); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void PubRecPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   (void)data; | ||||
|   (void)currentBytePosition; | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "Packet.hpp" | ||||
| #include "../ParsingInformation.hpp" | ||||
| #include "../Callbacks.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class PubRecPacket : public Packet { | ||||
|  public: | ||||
|   explicit PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback); | ||||
|   ~PubRecPacket(); | ||||
|  | ||||
|   void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | ||||
|   void parsePayload(char* data, size_t len, size_t* currentBytePosition); | ||||
|  | ||||
|  private: | ||||
|   ParsingInformation* _parsingInformation; | ||||
|   OnPubRecInternalCallback _callback; | ||||
|  | ||||
|   uint8_t _bytePosition; | ||||
|   char _packetIdMsb; | ||||
|   uint16_t _packetId; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,30 +0,0 @@ | ||||
| #include "PubRelPacket.hpp" | ||||
|  | ||||
| using AsyncMqttClientInternals::PubRelPacket; | ||||
|  | ||||
| PubRelPacket::PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback) | ||||
| : _parsingInformation(parsingInformation) | ||||
| , _callback(callback) | ||||
| , _bytePosition(0) | ||||
| , _packetIdMsb(0) | ||||
| , _packetId(0) { | ||||
| } | ||||
|  | ||||
| PubRelPacket::~PubRelPacket() { | ||||
| } | ||||
|  | ||||
| void PubRelPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   char currentByte = data[(*currentBytePosition)++]; | ||||
|   if (_bytePosition++ == 0) { | ||||
|     _packetIdMsb = currentByte; | ||||
|   } else { | ||||
|     _packetId = currentByte | _packetIdMsb << 8; | ||||
|     _parsingInformation->bufferState = BufferState::NONE; | ||||
|     _callback(_packetId); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void PubRelPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   (void)data; | ||||
|   (void)currentBytePosition; | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "Packet.hpp" | ||||
| #include "../ParsingInformation.hpp" | ||||
| #include "../Callbacks.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class PubRelPacket : public Packet { | ||||
|  public: | ||||
|   explicit PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback); | ||||
|   ~PubRelPacket(); | ||||
|  | ||||
|   void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | ||||
|   void parsePayload(char* data, size_t len, size_t* currentBytePosition); | ||||
|  | ||||
|  private: | ||||
|   ParsingInformation* _parsingInformation; | ||||
|   OnPubRelInternalCallback _callback; | ||||
|  | ||||
|   uint8_t _bytePosition; | ||||
|   char _packetIdMsb; | ||||
|   uint16_t _packetId; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,91 +0,0 @@ | ||||
| #include "PublishPacket.hpp" | ||||
|  | ||||
| using AsyncMqttClientInternals::PublishPacket; | ||||
|  | ||||
| PublishPacket::PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback) | ||||
| : _parsingInformation(parsingInformation) | ||||
| , _dataCallback(dataCallback) | ||||
| , _completeCallback(completeCallback) | ||||
| , _dup(false) | ||||
| , _qos(0) | ||||
| , _retain(0) | ||||
| , _bytePosition(0) | ||||
| , _topicLengthMsb(0) | ||||
| , _topicLength(0) | ||||
| , _ignore(false) | ||||
| , _packetIdMsb(0) | ||||
| , _packetId(0) | ||||
| , _payloadLength(0) | ||||
| , _payloadBytesRead(0) { | ||||
|     _dup = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_DUP; | ||||
|     _retain = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_RETAIN; | ||||
|     char qosMasked = _parsingInformation->packetFlags & 0x06; | ||||
|     switch (qosMasked) { | ||||
|       case HeaderFlag.PUBLISH_QOS0: | ||||
|         _qos = 0; | ||||
|         break; | ||||
|       case HeaderFlag.PUBLISH_QOS1: | ||||
|         _qos = 1; | ||||
|         break; | ||||
|       case HeaderFlag.PUBLISH_QOS2: | ||||
|         _qos = 2; | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| PublishPacket::~PublishPacket() { | ||||
| } | ||||
|  | ||||
| void PublishPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   char currentByte = data[(*currentBytePosition)++]; | ||||
|   if (_bytePosition == 0) { | ||||
|     _topicLengthMsb = currentByte; | ||||
|   } else if (_bytePosition == 1) { | ||||
|     _topicLength = currentByte | _topicLengthMsb << 8; | ||||
|     if (_topicLength > _parsingInformation->maxTopicLength) { | ||||
|       _ignore = true; | ||||
|     } else { | ||||
|       _parsingInformation->topicBuffer[_topicLength] = '\0'; | ||||
|     } | ||||
|   } else if (_bytePosition >= 2 && _bytePosition < 2 + _topicLength) { | ||||
|     // Starting from here, _ignore might be true | ||||
|     if (!_ignore) _parsingInformation->topicBuffer[_bytePosition - 2] = currentByte; | ||||
|     if (_bytePosition == 2 + _topicLength - 1 && _qos == 0) { | ||||
|       _preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1)); | ||||
|       return; | ||||
|     } | ||||
|   } else if (_bytePosition == 2 + _topicLength) { | ||||
|     _packetIdMsb = currentByte; | ||||
|   } else { | ||||
|     _packetId = currentByte | _packetIdMsb << 8; | ||||
|     _preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1)); | ||||
|   } | ||||
|   _bytePosition++; | ||||
| } | ||||
|  | ||||
| void PublishPacket::_preparePayloadHandling(uint32_t payloadLength) { | ||||
|   _payloadLength = payloadLength; | ||||
|   if (payloadLength == 0) { | ||||
|     _parsingInformation->bufferState = BufferState::NONE; | ||||
|     if (!_ignore) { | ||||
|       _dataCallback(_parsingInformation->topicBuffer, nullptr, _qos, _dup, _retain, 0, 0, 0, _packetId); | ||||
|       _completeCallback(_packetId, _qos); | ||||
|     } | ||||
|   } else { | ||||
|     _parsingInformation->bufferState = BufferState::PAYLOAD; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void PublishPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   size_t remainToRead = len - (*currentBytePosition); | ||||
|   if (_payloadBytesRead + remainToRead > _payloadLength) remainToRead = _payloadLength - _payloadBytesRead; | ||||
|  | ||||
|   if (!_ignore) _dataCallback(_parsingInformation->topicBuffer, data + (*currentBytePosition), _qos, _dup, _retain, remainToRead, _payloadBytesRead, _payloadLength, _packetId); | ||||
|   _payloadBytesRead += remainToRead; | ||||
|   (*currentBytePosition) += remainToRead; | ||||
|  | ||||
|   if (_payloadBytesRead == _payloadLength) { | ||||
|     _parsingInformation->bufferState = BufferState::NONE; | ||||
|     if (!_ignore) _completeCallback(_packetId, _qos); | ||||
|   } | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "Packet.hpp" | ||||
| #include "../Flags.hpp" | ||||
| #include "../ParsingInformation.hpp" | ||||
| #include "../Callbacks.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class PublishPacket : public Packet { | ||||
|  public: | ||||
|   explicit PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback); | ||||
|   ~PublishPacket(); | ||||
|  | ||||
|   void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | ||||
|   void parsePayload(char* data, size_t len, size_t* currentBytePosition); | ||||
|  | ||||
|  private: | ||||
|   ParsingInformation* _parsingInformation; | ||||
|   OnMessageInternalCallback _dataCallback; | ||||
|   OnPublishInternalCallback _completeCallback; | ||||
|  | ||||
|   void _preparePayloadHandling(uint32_t payloadLength); | ||||
|  | ||||
|   bool _dup; | ||||
|   uint8_t _qos; | ||||
|   bool _retain; | ||||
|  | ||||
|   uint8_t _bytePosition; | ||||
|   char _topicLengthMsb; | ||||
|   uint16_t _topicLength; | ||||
|   bool _ignore; | ||||
|   char _packetIdMsb; | ||||
|   uint16_t _packetId; | ||||
|   uint32_t _payloadLength; | ||||
|   uint32_t _payloadBytesRead; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,46 +0,0 @@ | ||||
| #include "SubAckPacket.hpp" | ||||
|  | ||||
| using AsyncMqttClientInternals::SubAckPacket; | ||||
|  | ||||
| SubAckPacket::SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback) | ||||
| : _parsingInformation(parsingInformation) | ||||
| , _callback(callback) | ||||
| , _bytePosition(0) | ||||
| , _packetIdMsb(0) | ||||
| , _packetId(0) { | ||||
| } | ||||
|  | ||||
| SubAckPacket::~SubAckPacket() { | ||||
| } | ||||
|  | ||||
| void SubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   char currentByte = data[(*currentBytePosition)++]; | ||||
|   if (_bytePosition++ == 0) { | ||||
|     _packetIdMsb = currentByte; | ||||
|   } else { | ||||
|     _packetId = currentByte | _packetIdMsb << 8; | ||||
|     _parsingInformation->bufferState = BufferState::PAYLOAD; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void SubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   char status = data[(*currentBytePosition)++]; | ||||
|  | ||||
|   /* switch (status) { | ||||
|     case 0: | ||||
|       Serial.println("Success QoS 0"); | ||||
|       break; | ||||
|     case 1: | ||||
|       Serial.println("Success QoS 1"); | ||||
|       break; | ||||
|     case 2: | ||||
|       Serial.println("Success QoS 2"); | ||||
|       break; | ||||
|     case 0x80: | ||||
|       Serial.println("Failure"); | ||||
|       break; | ||||
|   } */ | ||||
|  | ||||
|   _parsingInformation->bufferState = BufferState::NONE; | ||||
|   _callback(_packetId, status); | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "Packet.hpp" | ||||
| #include "../ParsingInformation.hpp" | ||||
| #include "../Callbacks.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class SubAckPacket : public Packet { | ||||
|  public: | ||||
|   explicit SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback); | ||||
|   ~SubAckPacket(); | ||||
|  | ||||
|   void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | ||||
|   void parsePayload(char* data, size_t len, size_t* currentBytePosition); | ||||
|  | ||||
|  private: | ||||
|   ParsingInformation* _parsingInformation; | ||||
|   OnSubAckInternalCallback _callback; | ||||
|  | ||||
|   uint8_t _bytePosition; | ||||
|   char _packetIdMsb; | ||||
|   uint16_t _packetId; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,30 +0,0 @@ | ||||
| #include "UnsubAckPacket.hpp" | ||||
|  | ||||
| using AsyncMqttClientInternals::UnsubAckPacket; | ||||
|  | ||||
| UnsubAckPacket::UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback) | ||||
| : _parsingInformation(parsingInformation) | ||||
| , _callback(callback) | ||||
| , _bytePosition(0) | ||||
| , _packetIdMsb(0) | ||||
| , _packetId(0) { | ||||
| } | ||||
|  | ||||
| UnsubAckPacket::~UnsubAckPacket() { | ||||
| } | ||||
|  | ||||
| void UnsubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   char currentByte = data[(*currentBytePosition)++]; | ||||
|   if (_bytePosition++ == 0) { | ||||
|     _packetIdMsb = currentByte; | ||||
|   } else { | ||||
|     _packetId = currentByte | _packetIdMsb << 8; | ||||
|     _parsingInformation->bufferState = BufferState::NONE; | ||||
|     _callback(_packetId); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void UnsubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | ||||
|   (void)data; | ||||
|   (void)currentBytePosition; | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "Packet.hpp" | ||||
| #include "../ParsingInformation.hpp" | ||||
| #include "../Callbacks.hpp" | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| class UnsubAckPacket : public Packet { | ||||
|  public: | ||||
|   explicit UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback); | ||||
|   ~UnsubAckPacket(); | ||||
|  | ||||
|   void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | ||||
|   void parsePayload(char* data, size_t len, size_t* currentBytePosition); | ||||
|  | ||||
|  private: | ||||
|   ParsingInformation* _parsingInformation; | ||||
|   OnUnsubAckInternalCallback _callback; | ||||
|  | ||||
|   uint8_t _bytePosition; | ||||
|   char _packetIdMsb; | ||||
|   uint16_t _packetId; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,21 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| enum class BufferState : uint8_t { | ||||
|   NONE = 0, | ||||
|   REMAINING_LENGTH = 2, | ||||
|   VARIABLE_HEADER = 3, | ||||
|   PAYLOAD = 4 | ||||
| }; | ||||
|  | ||||
| struct ParsingInformation { | ||||
|   BufferState bufferState; | ||||
|  | ||||
|   uint16_t maxTopicLength; | ||||
|   char* topicBuffer; | ||||
|  | ||||
|   uint8_t packetType; | ||||
|   uint16_t packetFlags; | ||||
|   uint32_t remainingLength; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,13 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace AsyncMqttClientInternals { | ||||
| struct PendingPubRel { | ||||
|   uint16_t packetId; | ||||
| }; | ||||
|  | ||||
| struct PendingAck { | ||||
|   uint8_t packetType; | ||||
|   uint8_t headerFlag; | ||||
|   uint16_t packetId; | ||||
| }; | ||||
| }  // namespace AsyncMqttClientInternals | ||||
| @@ -1,21 +0,0 @@ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2015 Marvin Roger | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
| @@ -1,18 +0,0 @@ | ||||
| Async MQTT client for ESP8266 and ESP32 (Github: https://github.com/marvinroger/async-mqtt-client) | ||||
| ============================= | ||||
|  | ||||
| [](https://travis-ci.org/marvinroger/async-mqtt-client) | ||||
|  | ||||
| An Arduino for ESP8266 and ESP32 asynchronous [MQTT](http://mqtt.org/) client implementation, built on [me-no-dev/ESPAsyncTCP (ESP8266)](https://github.com/me-no-dev/ESPAsyncTCP) | [me-no-dev/AsyncTCP (ESP32)](https://github.com/me-no-dev/AsyncTCP) . | ||||
| ## Features | ||||
|  | ||||
| * Compliant with the 3.1.1 version of the protocol | ||||
| * Fully asynchronous | ||||
| * Subscribe at QoS 0, 1 and 2 | ||||
| * Publish at QoS 0, 1 and 2 | ||||
| * SSL/TLS support | ||||
| * Available in the [PlatformIO registry](http://platformio.org/lib/show/346/AsyncMqttClient) | ||||
|  | ||||
| ## Requirements, installation and usage | ||||
|  | ||||
| The project is documented in the [/docs folder](docs). | ||||
							
								
								
									
										153
									
								
								wled00/udp.cpp
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								wled00/udp.cpp
									
									
									
									
									
								
							| @@ -424,7 +424,7 @@ void realtimeLock(uint32_t timeoutMs, byte md) | ||||
|     } | ||||
|     // if strip is off (bri==0) and not already in RTM | ||||
|     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; | ||||
|  | ||||
|   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(); | ||||
| } | ||||
|  | ||||
| void exitRealtime() { | ||||
|   if (!realtimeMode) return; | ||||
|   if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE; | ||||
|   strip.setBrightness(scaledBri(bri), true); | ||||
|   strip.setBrightness(bri, true); | ||||
|   realtimeTimeout = 0; // cancel realtime mode immediately | ||||
|   realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately | ||||
|   realtimeIP[0] = 0; | ||||
| @@ -565,93 +565,82 @@ void handleNotifications() | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!receiveDirect) return; | ||||
|   if (receiveDirect) { | ||||
|     //TPM2.NET | ||||
|     if (udpIn[0] == 0x9c) { | ||||
|       //WARNING: this code assumes that the final TMP2.NET payload is evenly distributed if using multiple packets (ie. frame size is constant) | ||||
|       //if the number of LEDs in your installation doesn't allow that, please include padding bytes at the end of the last packet | ||||
|       byte tpmType = udpIn[1]; | ||||
|       if (tpmType == 0xaa) { //TPM2.NET polling, expect answer | ||||
|         sendTPM2Ack(); return; | ||||
|       } | ||||
|       if (tpmType != 0xda) return; //return if notTPM2.NET data | ||||
|  | ||||
|   //TPM2.NET | ||||
|   if (udpIn[0] == 0x9c) | ||||
|   { | ||||
|     //WARNING: this code assumes that the final TMP2.NET payload is evenly distributed if using multiple packets (ie. frame size is constant) | ||||
|     //if the number of LEDs in your installation doesn't allow that, please include padding bytes at the end of the last packet | ||||
|     byte tpmType = udpIn[1]; | ||||
|     if (tpmType == 0xaa) { //TPM2.NET polling, expect answer | ||||
|       sendTPM2Ack(); return; | ||||
|       realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); | ||||
|       realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET); | ||||
|       if (realtimeOverride) return; | ||||
|  | ||||
|       tpmPacketCount++; //increment the packet count | ||||
|       if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet | ||||
|       byte packetNum = udpIn[4]; //starts with 1! | ||||
|       byte numPackets = udpIn[5]; | ||||
|  | ||||
|       unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED | ||||
|       unsigned totalLen = strip.getLengthTotal(); | ||||
|       for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) { | ||||
|         setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); | ||||
|       } | ||||
|       if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received | ||||
|         tpmPacketCount = 0; | ||||
|         if (useMainSegmentOnly) strip.trigger(); | ||||
|         else                    strip.show(); | ||||
|       } | ||||
|       return; | ||||
|     } | ||||
|     if (tpmType != 0xda) return; //return if notTPM2.NET data | ||||
|  | ||||
|     realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); | ||||
|     realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET); | ||||
|     if (realtimeOverride) return; | ||||
|     //UDP realtime: 1 warls 2 drgb 3 drgbw 4 dnrgb 5 dnrgbw | ||||
|     if (udpIn[0] > 0 && udpIn[0] < 6) { | ||||
|       realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); | ||||
|       DEBUG_PRINTLN(realtimeIP); | ||||
|       if (packetSize < 2) return; | ||||
|  | ||||
|     tpmPacketCount++; //increment the packet count | ||||
|     if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet | ||||
|     byte packetNum = udpIn[4]; //starts with 1! | ||||
|     byte numPackets = udpIn[5]; | ||||
|       if (udpIn[1] == 0) { | ||||
|         realtimeTimeout = 0; // cancel realtime mode immediately | ||||
|         return; | ||||
|       } else { | ||||
|         realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); | ||||
|       } | ||||
|       if (realtimeOverride) return; | ||||
|  | ||||
|     unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED | ||||
|     unsigned totalLen = strip.getLengthTotal(); | ||||
|     for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) { | ||||
|       setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); | ||||
|     } | ||||
|     if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received | ||||
|       tpmPacketCount = 0; | ||||
|       unsigned totalLen = strip.getLengthTotal(); | ||||
|       if (udpIn[0] == 1 && packetSize > 5) { //warls | ||||
|         for (size_t i = 2; i < packetSize -3; i += 4) { | ||||
|           setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0); | ||||
|         } | ||||
|       } else if (udpIn[0] == 2 && packetSize > 4) { //drgb | ||||
|         for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) | ||||
|           { | ||||
|             setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); | ||||
|           } | ||||
|       } else if (udpIn[0] == 3 && packetSize > 6) { //drgbw | ||||
|           for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++) { | ||||
|             setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); | ||||
|           } | ||||
|       } else if (udpIn[0] == 4 && packetSize > 7) { //dnrgb | ||||
|         unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); | ||||
|         for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++) { | ||||
|           setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); | ||||
|         } | ||||
|       } else if (udpIn[0] == 5 && packetSize > 8) { //dnrgbw | ||||
|         unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); | ||||
|         for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++) { | ||||
|           setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); | ||||
|         } | ||||
|       } | ||||
|       if (useMainSegmentOnly) strip.trigger(); | ||||
|       else                    strip.show(); | ||||
|     } | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   //UDP realtime: 1 warls 2 drgb 3 drgbw 4 dnrgb 5 dnrgbw | ||||
|   if (udpIn[0] > 0 && udpIn[0] < 6) | ||||
|   { | ||||
|     realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); | ||||
|     DEBUG_PRINTLN(realtimeIP); | ||||
|     if (packetSize < 2) return; | ||||
|  | ||||
|     if (udpIn[1] == 0) { | ||||
|       realtimeTimeout = 0; // cancel realtime mode immediately | ||||
|       return; | ||||
|     } else { | ||||
|       realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); | ||||
|     } | ||||
|     if (realtimeOverride) return; | ||||
|  | ||||
|     unsigned totalLen = strip.getLengthTotal(); | ||||
|     if (udpIn[0] == 1 && packetSize > 5) //warls | ||||
|     { | ||||
|       for (size_t i = 2; i < packetSize -3; i += 4) | ||||
|       { | ||||
|         setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0); | ||||
|       } | ||||
|     } else if (udpIn[0] == 2 && packetSize > 4) //drgb | ||||
|     { | ||||
|       for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) | ||||
|       { | ||||
|         setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); | ||||
|       } | ||||
|     } else if (udpIn[0] == 3 && packetSize > 6) //drgbw | ||||
|     { | ||||
|       for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++) | ||||
|       { | ||||
|         setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); | ||||
|       } | ||||
|     } else if (udpIn[0] == 4 && packetSize > 7) //dnrgb | ||||
|     { | ||||
|       unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); | ||||
|       for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++) | ||||
|       { | ||||
|         setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); | ||||
|       } | ||||
|     } else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw | ||||
|     { | ||||
|       unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); | ||||
|       for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++) | ||||
|       { | ||||
|         setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); | ||||
|       } | ||||
|     } | ||||
|     if (useMainSegmentOnly) strip.trigger(); | ||||
|     else                    strip.show(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // API over UDP | ||||
| @@ -669,6 +658,8 @@ void handleNotifications() | ||||
|     } | ||||
|     releaseJSONBufferLock(); | ||||
|   } | ||||
|  | ||||
|   UsermodManager::onUdpPacket(udpIn, packetSize); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -68,6 +68,10 @@ bool UsermodManager::onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t | ||||
|   return false; | ||||
| } | ||||
| #endif | ||||
| bool UsermodManager::onUdpPacket(uint8_t* payload, size_t len) { | ||||
|   for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) if ((*mod)->onUdpPacket(payload, len)) return true; | ||||
|   return false; | ||||
| } | ||||
| void UsermodManager::onUpdateBegin(bool init) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onUpdateBegin(init); } // notify usermods that update is to begin | ||||
| void UsermodManager::onStateChange(uint8_t mode) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onStateChange(mode); } // notify usermods that WLED state changed | ||||
|  | ||||
|   | ||||
							
								
								
									
										151
									
								
								wled00/util.cpp
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								wled00/util.cpp
									
									
									
									
									
								
							| @@ -1,6 +1,16 @@ | ||||
| #include "wled.h" | ||||
| #include "fcn_declare.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 | ||||
| @@ -706,6 +716,147 @@ void *realloc_malloc(void *ptr, size_t size) { | ||||
| } | ||||
| #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_INTERVAL_MILLIS 120000  // time limit between crashes: 120 seconds (2 minutes) | ||||
| #define BOOTLOOP_THRESHOLD       5     // number of consecutive crashes to trigger bootloop detection | ||||
| #define BOOTLOOP_ACTION_RESTORE  0     // default action: restore config from /bkp.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) | ||||
|  | ||||
| // Platform-agnostic abstraction | ||||
| enum class ResetReason { | ||||
|   Power, | ||||
|   Software, | ||||
|   Crash, | ||||
|   Brownout | ||||
| }; | ||||
|  | ||||
| #ifdef ESP8266 | ||||
| // Place variables in RTC memory via references, since RTC memory is not exposed via the linker in the Non-OS SDK | ||||
| // Use an offset of 32 as there's some hints that the first 128 bytes of "user" memory are used by the OTA system | ||||
| // Ref: https://github.com/esp8266/Arduino/blob/78d0d0aceacc1553f45ad8154592b0af22d1eede/cores/esp8266/Esp.cpp#L168 | ||||
| static volatile uint32_t& bl_last_boottime = *(RTC_USER_MEM + 32); | ||||
| static volatile uint32_t& bl_crashcounter = *(RTC_USER_MEM + 33); | ||||
| static volatile uint32_t& bl_actiontracker = *(RTC_USER_MEM + 34); | ||||
|  | ||||
| static inline ResetReason rebootReason() { | ||||
|   uint32_t resetReason = system_get_rst_info()->reason; | ||||
|   if (resetReason == REASON_EXCEPTION_RST | ||||
|       || resetReason == REASON_WDT_RST | ||||
|       || resetReason == REASON_SOFT_WDT_RST) | ||||
|       return ResetReason::Crash; | ||||
|   if (resetReason == REASON_SOFT_RESTART) | ||||
|     return ResetReason::Software; | ||||
|   return ResetReason::Power; | ||||
| } | ||||
|  | ||||
| static inline uint32_t getRtcMillis() { return system_get_rtc_time() / 160; };  // rtc ticks ~160000Hz | ||||
|  | ||||
| #else | ||||
| // 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; | ||||
|  | ||||
| static inline ResetReason rebootReason() { | ||||
|   esp_reset_reason_t reason = esp_reset_reason(); | ||||
|   if (reason == ESP_RST_BROWNOUT) return ResetReason::Brownout; | ||||
|   if (reason == ESP_RST_SW) return ResetReason::Software; | ||||
|   if (reason == ESP_RST_PANIC || reason == ESP_RST_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_TASK_WDT) return ResetReason::Crash; | ||||
|   return ResetReason::Power; | ||||
| } | ||||
|  | ||||
| #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) | ||||
| static inline uint32_t getRtcMillis() { return esp_rtc_get_time_us() / 1000; } | ||||
| #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0) | ||||
| static inline uint32_t getRtcMillis() { return rtc_time_slowclk_to_us(rtc_time_get(), rtc_clk_slow_freq_get_hz()) / 1000; } | ||||
| #endif | ||||
|  | ||||
| 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() { | ||||
|   uint32_t rtctime = getRtcMillis(); | ||||
|   bool result = false; | ||||
|  | ||||
|   switch(rebootReason()) { | ||||
|     case ResetReason::Power: | ||||
|       bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler) | ||||
|       // fall through | ||||
|     case ResetReason::Software: | ||||
|       // no crash detected, reset counter | ||||
|       bl_crashcounter = 0; | ||||
|       break; | ||||
|  | ||||
|     case ResetReason::Crash: | ||||
|     { | ||||
|       DEBUG_PRINTLN(F("crash detected!")); | ||||
|       uint32_t rebootinterval = rtctime - bl_last_boottime; | ||||
|       if (rebootinterval < BOOTLOOP_INTERVAL_MILLIS) { | ||||
|         bl_crashcounter++; | ||||
|         if (bl_crashcounter >= BOOTLOOP_THRESHOLD) { | ||||
|           DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!")); | ||||
|           bl_crashcounter = 0; | ||||
|           result = true; | ||||
|         } | ||||
|       } else { | ||||
|         // Reset counter on long intervals to track only consecutive short-interval crashes | ||||
|         bl_crashcounter = 0; | ||||
|         // TODO: crash reporting goes here | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ResetReason::Brownout: | ||||
|       // crash due to brownout can't be detected unless using flash memory to store bootloop variables | ||||
|       DEBUG_PRINTLN(F("brownout detected")); | ||||
|       //restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all) | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   bl_last_boottime = rtctime; // store current runtime for next reboot | ||||
|  | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| void handleBootLoop() { | ||||
|   DEBUG_PRINTF_P(PSTR("checking for bootloop: time %d, counter %d, action %d\n"), bl_last_boottime, bl_crashcounter, bl_actiontracker); | ||||
|   if (!detectBootLoop()) return; // no bootloop detected | ||||
|  | ||||
|   switch(bl_actiontracker) { | ||||
|     case BOOTLOOP_ACTION_RESTORE: | ||||
|       restoreConfig(); | ||||
|       ++bl_actiontracker; | ||||
|       break; | ||||
|     case BOOTLOOP_ACTION_RESET: | ||||
|       resetConfig(); | ||||
|       ++bl_actiontracker; | ||||
|       break; | ||||
|     case BOOTLOOP_ACTION_OTA: | ||||
| #ifndef ESP8266 | ||||
|       if(Update.canRollBack()) { | ||||
|         DEBUG_PRINTLN(F("Swapping boot partition...")); | ||||
|         Update.rollBack(); // swap boot partition | ||||
|       } | ||||
|       ++bl_actiontracker; | ||||
|       break; | ||||
| #else | ||||
|       // fall through | ||||
| #endif | ||||
|     case BOOTLOOP_ACTION_DUMP: | ||||
|       dumpFilesToSerial(); | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   ESP.restart(); // restart cleanly and don't wait for another crash | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * 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 | ||||
|   | ||||
| @@ -190,12 +190,10 @@ void WLED::loop() | ||||
|     doInitBusses = false; | ||||
|     DEBUG_PRINTLN(F("Re-init busses.")); | ||||
|     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 | ||||
|     BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005 | ||||
|     if (aligned) strip.makeAutoSegments(); | ||||
|     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; | ||||
|   } | ||||
|   if (loadLedmap >= 0) { | ||||
| @@ -410,6 +408,9 @@ void WLED::setup() | ||||
|     DEBUGFS_PRINTLN(F("FS failed!")); | ||||
|     errorFlag = ERR_FS_BEGIN; | ||||
|   } | ||||
|  | ||||
|   handleBootLoop(); // check for bootloop and take action (requires WLED_FS) | ||||
|  | ||||
| #ifdef WLED_ADD_EEPROM_SUPPORT | ||||
|   else deEEP(); | ||||
| #else | ||||
| @@ -425,6 +426,11 @@ void WLED::setup() | ||||
|   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 | ||||
|  | ||||
|   if(!verifyConfig()) { | ||||
|     if(!restoreConfig()) { | ||||
|       resetConfig(); | ||||
|     } | ||||
|   } | ||||
|   DEBUG_PRINTLN(F("Reading config")); | ||||
|   bool needsCfgSave = deserializeConfigFromFS(); | ||||
|   DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); | ||||
|   | ||||
| @@ -155,7 +155,7 @@ | ||||
|  | ||||
| #include "src/dependencies/e131/ESPAsyncE131.h" | ||||
| #ifndef WLED_DISABLE_MQTT | ||||
| #include "src/dependencies/async-mqtt-client/AsyncMqttClient.h" | ||||
| #include <AsyncMqttClient.h> | ||||
| #endif | ||||
|  | ||||
| #define ARDUINOJSON_DECODE_UNICODE 0 | ||||
| @@ -194,6 +194,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>; | ||||
| #include "fcn_declare.h" | ||||
| #include "NodeStruct.h" | ||||
| #include "pin_manager.h" | ||||
| #include "colors.h" | ||||
| #include "bus_manager.h" | ||||
| #include "FX.h" | ||||
|  | ||||
| @@ -625,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 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          useRainbowWheel          _INIT(false);  // use "rainbow" color wheel instead of "spectrum" color wheel | ||||
|  | ||||
| // nightlight | ||||
| WLED_GLOBAL bool nightlightActive _INIT(false); | ||||
| @@ -734,10 +734,10 @@ WLED_GLOBAL bool receiveNotificationPalette    _INIT(true);       // apply palet | ||||
| WLED_GLOBAL bool receiveSegmentOptions         _INIT(false);      // apply segment options | ||||
| WLED_GLOBAL bool receiveSegmentBounds          _INIT(false);      // apply segment bounds (start, stop, offset) | ||||
| WLED_GLOBAL bool receiveDirect _INIT(true);                       // receive UDP/Hyperion realtime | ||||
| WLED_GLOBAL bool notifyDirect _INIT(false);                       // send notification if change via UI or HTTP API | ||||
| WLED_GLOBAL bool notifyButton _INIT(false);                       // send if updated by button or infrared remote | ||||
| WLED_GLOBAL bool notifyDirect _INIT(true);                        // send notification if change via UI or HTTP API | ||||
| WLED_GLOBAL bool notifyButton _INIT(true);                        // send if updated by button or infrared remote | ||||
| WLED_GLOBAL bool notifyAlexa  _INIT(false);                       // send notification if updated via Alexa | ||||
| WLED_GLOBAL bool notifyHue    _INIT(true);                        // send notification if Hue light changes | ||||
| WLED_GLOBAL bool notifyHue    _INIT(false);                       // send notification if Hue light changes | ||||
| #endif | ||||
|  | ||||
| // effects | ||||
|   | ||||
| @@ -411,6 +411,9 @@ void initServer() | ||||
|       serveMessage(request, 500, F("Update failed!"), F("Please check your file and retry!"), 254); | ||||
|     } else { | ||||
|       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; | ||||
|     } | ||||
|   },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){ | ||||
| @@ -429,8 +432,9 @@ void initServer() | ||||
|       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 | ||||
|       strip.suspend(); | ||||
|       #ifdef ESP8266 | ||||
|       backupConfig(); // backup current config in case the update ends badly | ||||
|       strip.resetSegments();  // free as much memory as you can | ||||
|       #ifdef ESP8266 | ||||
|       Update.runAsync(true); | ||||
|       #endif | ||||
|       Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); | ||||
|   | ||||
| @@ -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>()) { | ||||
|     JsonArray pins = obj[key].as<JsonArray>(); | ||||
|     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) | ||||
| 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 | ||||
|   if (i2c_sda > -1 && i2c_scl > -1) { | ||||
|     settingsScript.printf_P(PSTR(",%d,%d"), i2c_sda, i2c_scl); | ||||
| @@ -382,7 +400,6 @@ void getSettingsJS(byte subPage, Print& settingsScript) | ||||
|     printSetFormValue(settingsScript,PSTR("TL"),nightlightDelayMinsDefault); | ||||
|     printSetFormValue(settingsScript,PSTR("TW"),nightlightMode); | ||||
|     printSetFormIndex(settingsScript,PSTR("PB"),paletteBlend); | ||||
|     printSetFormCheckbox(settingsScript,PSTR("RW"),useRainbowWheel); | ||||
|     printSetFormValue(settingsScript,PSTR("RL"),rlyPin); | ||||
|     printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde); | ||||
|     printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain); | ||||
| @@ -595,7 +612,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) | ||||
|     printSetFormCheckbox(settingsScript,PSTR("AO"),aOtaEnabled); | ||||
|     printSetFormCheckbox(settingsScript,PSTR("SU"),otaSameSubnet); | ||||
|     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); | ||||
|     settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription); | ||||
|     //hide settings if not compiled | ||||
| @@ -657,16 +674,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) | ||||
|   if (subPage == SUBPAGE_UPDATE) // update | ||||
|   { | ||||
|     char tmp_buf[128]; | ||||
|     snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s<br>%s<br>(%s build %d)"), | ||||
|       versionString, | ||||
|       releaseString, | ||||
|     #if defined(ARDUINO_ARCH_ESP32) | ||||
|       ESP.getChipModel(), | ||||
|     #else | ||||
|       "esp8266", | ||||
|     #endif | ||||
|       VERSION); | ||||
|  | ||||
|     fillWLEDVersion(tmp_buf,sizeof(tmp_buf)); | ||||
|     printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf); | ||||
|     #ifndef ARDUINO_ARCH_ESP32 | ||||
|     settingsScript.print(F("toggle('rev');"));  // hide revert button on ESP8266 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user