Compare commits
	
		
			39 Commits
		
	
	
		
			copilot/fi
			...
			v0.15.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | faadb67eb0 | ||
|   | a111a2e7a1 | ||
|   | 32864d8986 | ||
|   | d7bebc2659 | ||
|   | af410ae2d0 | ||
|   | 1891cc816f | ||
|   | 9a4073e606 | ||
|   | b78229d1e2 | ||
|   | 71b242874f | ||
|   | 9328e6faca | ||
|   | bbacc2daae | ||
|   | 3e22f9cabb | ||
|   | 685ad83d4b | ||
|   | 62ddb18a1a | ||
|   | 6a12378475 | ||
|   | d26b3108da | ||
|   | c89e4576b4 | ||
|   | bc79f44a26 | ||
|   | 7ece14ff3f | ||
|   | 0b3643132b | ||
|   | a5693fbf8d | ||
|   | 5c5b70f52b | ||
|   | ae97e388a6 | ||
|   | 37cddcaacc | ||
|   | a1b332fc78 | ||
|   | 86d7c24513 | ||
|   | b28add3b8b | ||
|   | 5fd3a513a4 | ||
|   | b98a8a10b0 | ||
|   | 2bee2793ef | ||
|   | 2f6fa66f4d | ||
|   | 5d38acd787 | ||
|   | e607fcb5c5 | ||
|   | 8a18555ae4 | ||
|   | beb709dc8f | ||
|   | 7a58c69a80 | ||
|   | 1082c85789 | ||
|   | 568d2edd96 | ||
|   | d2d56ebbd2 | 
| @@ -2,7 +2,12 @@ | ||||
|  | ||||
| # [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6 | ||||
| ARG VARIANT="3" | ||||
| FROM mcr.microsoft.com/devcontainers/python:0-${VARIANT} | ||||
| FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} | ||||
|  | ||||
| # [Option] Install Node.js | ||||
| ARG INSTALL_NODE="true" | ||||
| ARG NODE_VERSION="lts/*" | ||||
| RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi | ||||
|  | ||||
| # [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. | ||||
| # COPY requirements.txt /tmp/pip-tmp/ | ||||
|   | ||||
| @@ -5,7 +5,10 @@ | ||||
| 		"context": "..", | ||||
| 		"args": {  | ||||
| 			// Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9 | ||||
| 			"VARIANT": "3" | ||||
| 			"VARIANT": "3", | ||||
| 			// Options | ||||
| 			"INSTALL_NODE": "true", | ||||
| 			"NODE_VERSION": "lts/*" | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| @@ -24,35 +27,34 @@ | ||||
| 	// risk to running the build directly on the host. | ||||
| 	// "runArgs": ["--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "--group-add", "dialout"], | ||||
|  | ||||
| 	"customizations": { | ||||
| 		"vscode": { | ||||
| 			"settings": {  | ||||
| 				"terminal.integrated.shell.linux": "/bin/bash", | ||||
| 				"python.pythonPath": "/usr/local/bin/python", | ||||
| 				"python.linting.enabled": true, | ||||
| 				"python.linting.pylintEnabled": true, | ||||
| 				"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", | ||||
| 				"python.formatting.blackPath": "/usr/local/py-utils/bin/black", | ||||
| 				"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", | ||||
| 				"python.linting.banditPath": "/usr/local/py-utils/bin/bandit", | ||||
| 				"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", | ||||
| 				"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", | ||||
| 				"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", | ||||
| 				"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", | ||||
| 				"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" | ||||
| 			}, | ||||
| 			"extensions": [ | ||||
| 				"ms-python.python", | ||||
| 				"platformio.platformio-ide" | ||||
| 			] | ||||
| 		} | ||||
| 	// Set *default* container specific settings.json values on container create. | ||||
| 	"settings": {  | ||||
| 		"terminal.integrated.shell.linux": "/bin/bash", | ||||
| 		"python.pythonPath": "/usr/local/bin/python", | ||||
| 		"python.linting.enabled": true, | ||||
| 		"python.linting.pylintEnabled": true, | ||||
| 		"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", | ||||
| 		"python.formatting.blackPath": "/usr/local/py-utils/bin/black", | ||||
| 		"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", | ||||
| 		"python.linting.banditPath": "/usr/local/py-utils/bin/bandit", | ||||
| 		"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", | ||||
| 		"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", | ||||
| 		"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", | ||||
| 		"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", | ||||
| 		"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" | ||||
| 	}, | ||||
|  | ||||
| 	// Add the IDs of extensions you want installed when the container is created. | ||||
| 	"extensions": [ | ||||
| 		"ms-python.python", | ||||
| 		"platformio.platformio-ide" | ||||
| 	], | ||||
|  | ||||
| 	// Use 'forwardPorts' to make a list of ports inside the container available locally. | ||||
| 	// "forwardPorts": [], | ||||
|  | ||||
| 	// Use 'postCreateCommand' to run commands after the container is created. | ||||
| 	"postCreateCommand": "bash -i -c 'nvm install && npm ci'", | ||||
| 	"postCreateCommand": "npm install", | ||||
|  | ||||
| 	// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. | ||||
| 	"remoteUser": "vscode" | ||||
|   | ||||
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,2 @@ | ||||
| github: [Aircoookie,blazoncek,DedeHai,lost-hope,willmmiles] | ||||
| github: [Aircoookie,blazoncek] | ||||
| custom: ['https://paypal.me/Aircoookie','https://paypal.me/blazoncek'] | ||||
| thanks_dev: u/gh/netmindz | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug.yml
									
									
									
									
										vendored
									
									
								
							| @@ -80,7 +80,7 @@ body: | ||||
|     id: terms | ||||
|     attributes: | ||||
|       label: Code of Conduct | ||||
|       description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/wled-dev/WLED/blob/main/CODE_OF_CONDUCT.md) | ||||
|       description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Aircoookie/WLED/blob/master/CODE_OF_CONDUCT.md) | ||||
|       options: | ||||
|         - label: I agree to follow this project's Code of Conduct | ||||
|           required: true | ||||
|   | ||||
							
								
								
									
										138
									
								
								.github/copilot-instructions.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										138
									
								
								.github/copilot-instructions.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,138 +0,0 @@ | ||||
| # 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. | ||||
							
								
								
									
										11
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -26,7 +26,7 @@ jobs: | ||||
|  | ||||
|  | ||||
|   build: | ||||
|     name: Build Environments | ||||
|     name: Build Enviornments | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: get_default_envs | ||||
|     strategy: | ||||
| @@ -38,12 +38,8 @@ jobs: | ||||
|     - name: Set up Node.js | ||||
|       uses: actions/setup-node@v4 | ||||
|       with: | ||||
|         node-version-file: '.nvmrc' | ||||
|         cache: 'npm' | ||||
|     - run: | | ||||
|         npm ci | ||||
|         VERSION=`date +%y%m%d0` | ||||
|         sed -i -r -e "s/define VERSION .+/define VERSION $VERSION/" wled00/wled.h | ||||
|     - run: npm ci | ||||
|     - name: Cache PlatformIO | ||||
|       uses: actions/cache@v4 | ||||
|       with: | ||||
| @@ -60,7 +56,6 @@ jobs: | ||||
|           cache: 'pip' | ||||
|     - name: Install PlatformIO | ||||
|       run: pip install -r requirements.txt | ||||
|  | ||||
|     - name: Build firmware | ||||
|       run: pio run -e ${{ matrix.environment }} | ||||
|     - uses: actions/upload-artifact@v4 | ||||
| @@ -79,7 +74,7 @@ jobs: | ||||
|       - name: Use Node.js | ||||
|         uses: actions/setup-node@v4 | ||||
|         with: | ||||
|           node-version-file: '.nvmrc' | ||||
|           node-version: '20.x' | ||||
|           cache: 'npm' | ||||
|       - run: npm ci | ||||
|       - run: npm test | ||||
|   | ||||
							
								
								
									
										49
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										49
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,49 +0,0 @@ | ||||
|  | ||||
| name: Deploy Nightly | ||||
| on: | ||||
|   # This can be used to automatically publish nightlies at UTC nighttime | ||||
|   schedule: | ||||
|     - cron: '0 2 * * *' # run at 2 AM UTC | ||||
|   # This can be used to allow manually triggering nightlies from the web interface | ||||
|   workflow_dispatch: | ||||
|  | ||||
| jobs: | ||||
|   wled_build: | ||||
|     uses: ./.github/workflows/build.yml | ||||
|   nightly: | ||||
|     name: Deploy nightly | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: wled_build | ||||
|     steps: | ||||
|       - name: Download artifacts | ||||
|         uses: actions/download-artifact@v4 | ||||
|         with: | ||||
|           merge-multiple: true | ||||
|       - name: Show Files | ||||
|         run: ls -la | ||||
|       - name: "✏️ Generate release changelog" | ||||
|         id: changelog | ||||
|         uses: janheinrichmerker/action-github-changelog-generator@v2.3 | ||||
|         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: | ||||
|           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|         with: | ||||
|           tag_name: nightly | ||||
|           name: 'Nightly Release $$' | ||||
|           prerelease: true | ||||
|           body: ${{ steps.changelog.outputs.changelog }} | ||||
|           files: | | ||||
|             *.bin | ||||
|             *.bin.gz | ||||
|       - name: Repository Dispatch | ||||
|         uses: peter-evans/repository-dispatch@v3 | ||||
|         with: | ||||
|           repository: wled/WLED-WebInstaller | ||||
|           event-type: release-nightly | ||||
|           token: ${{ secrets.PAT_PUBLIC }} | ||||
							
								
								
									
										38
									
								
								.github/workflows/pr-merge.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								.github/workflows/pr-merge.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -1,38 +0,0 @@ | ||||
|     name: Notify Discord on PR Merge | ||||
|     on: | ||||
|       workflow_dispatch: | ||||
|       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 | ||||
|           uses: actions-cool/check-user-permission@v2 | ||||
|           with: | ||||
|             require: write | ||||
|             username: ${{ github.triggering_actor }} | ||||
|           env: | ||||
|             GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|         - name: Check User Permission | ||||
|           if: steps.checkAccess.outputs.require-result == 'false' | ||||
|           run: | | ||||
|             echo "${{ github.triggering_actor }} does not have permissions on this repo." | ||||
|             echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}" | ||||
|             echo "Job originally triggered by ${{ github.actor }}" | ||||
|             exit 1 | ||||
|         - name: Send Discord notification | ||||
|           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: | | ||||
|             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 }} | ||||
							
								
								
									
										10
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,19 +18,9 @@ jobs: | ||||
|     - uses: actions/download-artifact@v4 | ||||
|       with: | ||||
|         merge-multiple: true | ||||
|     - name: "✏️ Generate release changelog" | ||||
|       id: changelog | ||||
|       uses: janheinrichmerker/action-github-changelog-generator@v2.3 | ||||
|       with: | ||||
|           token: ${{ secrets.GITHUB_TOKEN }}  | ||||
|           sinceTag: v0.15.0 | ||||
|           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: | ||||
|         body: ${{ steps.changelog.outputs.changelog }} | ||||
|         draft: True | ||||
|         files: | | ||||
|           *.bin | ||||
|   | ||||
							
								
								
									
										13
									
								
								.github/workflows/test.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.github/workflows/test.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -1,13 +0,0 @@ | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|  | ||||
| jobs: | ||||
|   dispatch: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Repository Dispatch | ||||
|         uses: peter-evans/repository-dispatch@v3 | ||||
|         with: | ||||
|           repository: wled/WLED-WebInstaller | ||||
|           event-type: release-nightly | ||||
|           token: ${{ secrets.PAT_PUBLIC }} | ||||
							
								
								
									
										74
									
								
								.github/workflows/usermods.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										74
									
								
								.github/workflows/usermods.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,74 +0,0 @@ | ||||
| name: Usermod CI | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     paths: | ||||
|       - usermods/** | ||||
|       - .github/workflows/usermods.yml | ||||
|   pull_request: | ||||
|     paths: | ||||
|       - usermods/** | ||||
|      | ||||
| jobs: | ||||
|  | ||||
|   get_usermod_envs: | ||||
|     name: Gather Usermods | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|     - uses: actions/checkout@v4 | ||||
|     - uses: actions/setup-python@v5 | ||||
|       with: | ||||
|         python-version: '3.12' | ||||
|         cache: 'pip' | ||||
|     - name: Install PlatformIO | ||||
|       run: pip install -r requirements.txt | ||||
|     - name: Get default environments | ||||
|       id: envs | ||||
|       run: | | ||||
|         echo "usermods=$(find usermods/ -name library.json | xargs dirname | xargs -n 1 basename | jq -R | grep -v PWM_fan | grep -v BME68X_v2| grep -v pixels_dice_tray | jq --slurp -c)" >> $GITHUB_OUTPUT | ||||
|     outputs: | ||||
|       usermods: ${{ steps.envs.outputs.usermods }} | ||||
|  | ||||
|  | ||||
|   build: | ||||
|     name: Build Enviornments | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: get_usermod_envs | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         usermod: ${{ fromJSON(needs.get_usermod_envs.outputs.usermods) }} | ||||
|         environment: [usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp32s3] | ||||
|     steps: | ||||
|     - uses: actions/checkout@v4 | ||||
|     - name: Set up Node.js | ||||
|       uses: actions/setup-node@v4 | ||||
|       with: | ||||
|         node-version-file: '.nvmrc' | ||||
|         cache: 'npm' | ||||
|     - run: npm ci | ||||
|     - name: Cache PlatformIO | ||||
|       uses: actions/cache@v4 | ||||
|       with: | ||||
|         path: | | ||||
|               ~/.platformio/.cache | ||||
|               ~/.buildcache | ||||
|               build_output | ||||
|         key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }} | ||||
|         restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}- | ||||
|     - name: Set up Python | ||||
|       uses: actions/setup-python@v5 | ||||
|       with: | ||||
|           python-version: '3.12' | ||||
|           cache: 'pip' | ||||
|     - name: Install PlatformIO | ||||
|       run: pip install -r requirements.txt | ||||
|     - name: Add usermods environment | ||||
|       run: | | ||||
|         cp -v usermods/platformio_override.usermods.ini platformio_override.ini | ||||
|         echo >> platformio_override.ini | ||||
|         echo  "custom_usermods = ${{ matrix.usermod }}" >> platformio_override.ini | ||||
|         cat platformio_override.ini | ||||
|  | ||||
|     - name: Build firmware | ||||
|       run: pio run -e ${{ matrix.environment }}     | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -15,7 +15,6 @@ wled-update.sh | ||||
|  | ||||
| /build_output/ | ||||
| /node_modules/ | ||||
| /logs/ | ||||
|  | ||||
| /wled00/extLibs | ||||
| /wled00/LittleFS | ||||
|   | ||||
							
								
								
									
										20
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,23 @@ | ||||
| ## WLED changelog | ||||
|  | ||||
| #### Build 2412100 | ||||
| -   WLED 0.15.0 release | ||||
| -   Usermod BME280: Fix "Unit of Measurement" for temperature | ||||
| -   WiFi reconnect bugfix (@blazoncek) | ||||
|  | ||||
| #### Build 2411250 | ||||
| -   WLED 0.15.0-rc1 release | ||||
| -   Add support for esp32S3_wroom2 (#4243 by @softhack007) | ||||
| -   Fix mixed LED SK6812 and ws2812b booloop (#4301 by @willmmiles) | ||||
| -   Improved FPS calculation (by DedeHai) | ||||
| -   Fix crashes when using HTTP API within MQTT (#4269 by @willmmiles) | ||||
| -   Fix array overflow in exploding_fireworks (#4120 by @willmmiles) | ||||
| -   Fix MQTT topic buffer length (#4293 by @WouterGritter) | ||||
| -   Fix SparkFunDMX fix for possible array bounds violation in DMX.write (by @softhack007) | ||||
| -   Allow TV Simulator on single LED segments (by @softhack007) | ||||
| -   Fix WLED_RELEASE_NAME (by @netmindz) | ||||
|  | ||||
|  | ||||
| #### Build 2410270 | ||||
| -   WLED 0.15.0-b7 release | ||||
| -   Re-license the WLED project from MIT to EUPL (#4194 by @Aircoookie) | ||||
| @@ -173,7 +191,7 @@ | ||||
| -   v0.15.0-b2 | ||||
| -   WS2805 support (RGB + WW + CW, 600kbps) | ||||
| -   Unified PSRAM use | ||||
| -   NeoPixelBus v2.7.9 (for future WS2805 support) | ||||
| -   NeoPixelBus v2.7.9 | ||||
| -   Ubiquitous PSRAM mode for all variants of ESP32 | ||||
| -   SSD1309_64 I2C Support for FLD Usermod (#3836 by @THATDONFC) | ||||
| -   Palette cycling fix (add support for `{"seg":[{"pal":"X~Y~"}]}` or `{"seg":[{"pal":"X~Yr"}]}`) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ A good description helps us to review and understand your proposed changes. For | ||||
|  | ||||
| ### Target branch for pull requests | ||||
|  | ||||
| Please make all PRs against the `main` branch. | ||||
| Please make all PRs against the `0_15` branch. | ||||
|  | ||||
| ### Updating your code | ||||
| While the PR is open - and under review by maintainers - you may be asked to modify your PR source code. | ||||
| @@ -27,7 +27,7 @@ Github will pick up the changes so your PR stays up-to-date. | ||||
| > For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push. | ||||
|  | ||||
|  | ||||
| You can find a collection of very useful tips and tricks here: https://github.com/wled-dev/WLED/wiki/How-to-properly-submit-a-PR | ||||
| You can find a collection of very useful tips and tricks here: https://github.com/Aircoookie/WLED/wiki/How-to-properly-submit-a-PR | ||||
|  | ||||
|  | ||||
| ### Code style | ||||
| @@ -105,4 +105,4 @@ Good: | ||||
|  | ||||
| There is no hard character limit for a comment within a line, | ||||
| though as a rule of thumb consider wrapping after 120 characters. | ||||
| Inline comments are OK if they describe that line only and are not exceedingly wide. | ||||
| Inline comments are OK if they describe that line only and are not exceedingly wide. | ||||
| @@ -1,47 +0,0 @@ | ||||
| { | ||||
|     "build": { | ||||
|       "arduino": { | ||||
|         "ldscript": "esp32s3_out.ld", | ||||
|         "memory_type": "qio_qspi" | ||||
|       }, | ||||
|       "core": "esp32", | ||||
|       "extra_flags": [ | ||||
|         "-DBOARD_HAS_PSRAM", | ||||
|         "-DARDUINO_LOLIN_S3_MINI", | ||||
|         "-DARDUINO_USB_MODE=1" | ||||
|       ], | ||||
|       "f_cpu": "240000000L", | ||||
|       "f_flash": "80000000L", | ||||
|       "flash_mode": "qio", | ||||
|       "hwids": [ | ||||
|         [ | ||||
|           "0x303A", | ||||
|           "0x8167" | ||||
|         ] | ||||
|       ], | ||||
|       "mcu": "esp32s3", | ||||
|       "variant": "lolin_s3_mini" | ||||
|     }, | ||||
|     "connectivity": [ | ||||
|       "bluetooth", | ||||
|       "wifi" | ||||
|     ], | ||||
|     "debug": { | ||||
|       "openocd_target": "esp32s3.cfg" | ||||
|     }, | ||||
|     "frameworks": [ | ||||
|       "arduino", | ||||
|       "espidf" | ||||
|     ], | ||||
|     "name": "WEMOS LOLIN S3 Mini", | ||||
|     "upload": { | ||||
|       "flash_size": "4MB", | ||||
|       "maximum_ram_size": 327680, | ||||
|       "maximum_size": 4194304, | ||||
|       "require_upload_port": true, | ||||
|       "speed": 460800 | ||||
|     }, | ||||
|     "url": "https://www.wemos.cc/en/latest/s3/index.html", | ||||
|     "vendor": "WEMOS" | ||||
| } | ||||
|    | ||||
| @@ -1,504 +0,0 @@ | ||||
| /* esp8266_waveform imported from platform source code | ||||
|    Modified for WLED to work around a fault in the NMI handling, | ||||
|    which can result in the system locking up and hard WDT crashes. | ||||
|  | ||||
|    Imported from https://github.com/esp8266/Arduino/blob/7e0d20e2b9034994f573a236364e0aef17fd66de/cores/esp8266/core_esp8266_waveform_phase.cpp | ||||
| */ | ||||
|  | ||||
|  | ||||
| /* | ||||
|   esp8266_waveform - General purpose waveform generation and control, | ||||
|                      supporting outputs on all pins in parallel. | ||||
|  | ||||
|   Copyright (c) 2018 Earle F. Philhower, III.  All rights reserved. | ||||
|   Copyright (c) 2020 Dirk O. Kaar. | ||||
|  | ||||
|   The core idea is to have a programmable waveform generator with a unique | ||||
|   high and low period (defined in microseconds or CPU clock cycles).  TIMER1 is | ||||
|   set to 1-shot mode and is always loaded with the time until the next edge | ||||
|   of any live waveforms. | ||||
|  | ||||
|   Up to one waveform generator per pin supported. | ||||
|  | ||||
|   Each waveform generator is synchronized to the ESP clock cycle counter, not the | ||||
|   timer.  This allows for removing interrupt jitter and delay as the counter | ||||
|   always increments once per 80MHz clock.  Changes to a waveform are | ||||
|   contiguous and only take effect on the next waveform transition, | ||||
|   allowing for smooth transitions. | ||||
|  | ||||
|   This replaces older tone(), analogWrite(), and the Servo classes. | ||||
|  | ||||
|   Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() | ||||
|   clock cycle time, or an interval measured in clock cycles, but not TIMER1 | ||||
|   cycles (which may be 2 CPU clock cycles @ 160MHz). | ||||
|  | ||||
|   This library is free software; you can redistribute it and/or | ||||
|   modify it under the terms of the GNU Lesser General Public | ||||
|   License as published by the Free Software Foundation; either | ||||
|   version 2.1 of the License, or (at your option) any later version. | ||||
|  | ||||
|   This library is distributed in the hope that it will be useful, | ||||
|   but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|   Lesser General Public License for more details. | ||||
|  | ||||
|   You should have received a copy of the GNU Lesser General Public | ||||
|   License along with this library; if not, write to the Free Software | ||||
|   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||||
| */ | ||||
|  | ||||
| #include "core_esp8266_waveform.h" | ||||
| #include <Arduino.h> | ||||
| #include "debug.h" | ||||
| #include "ets_sys.h" | ||||
| #include <atomic> | ||||
|  | ||||
|  | ||||
| // ----- @willmmiles begin patch ----- | ||||
| // Linker magic | ||||
| extern "C" void usePWMFixedNMI(void) {}; | ||||
|  | ||||
| // NMI crash workaround | ||||
| // Sometimes the NMI fails to return, stalling the CPU.  When this happens, | ||||
| // the next NMI gets a return address /inside the NMI handler function/. | ||||
| // We work around this by caching the last NMI return address, and restoring | ||||
| // the epc3 and eps3 registers to the previous values if the observed epc3 | ||||
| // happens to be pointing to the _NMILevelVector function. | ||||
| extern "C" void _NMILevelVector(); | ||||
| extern "C" void _UserExceptionVector_1(); // the next function after _NMILevelVector | ||||
| static inline IRAM_ATTR void nmiCrashWorkaround() { | ||||
|   static uintptr_t epc3_backup, eps3_backup; | ||||
|  | ||||
|   uintptr_t epc3, eps3; | ||||
|   __asm__ __volatile__("rsr %0,epc3; rsr %1,eps3":"=a"(epc3),"=a" (eps3)); | ||||
|   if ((epc3 < (uintptr_t) &_NMILevelVector) || (epc3 >= (uintptr_t) &_UserExceptionVector_1)) { | ||||
|     // Address is good; save backup | ||||
|     epc3_backup = epc3; | ||||
|     eps3_backup = eps3; | ||||
|   } else { | ||||
|     // Address is inside the NMI handler -- restore from backup | ||||
|     __asm__ __volatile__("wsr %0,epc3; wsr %1,eps3"::"a"(epc3_backup),"a"(eps3_backup)); | ||||
|   } | ||||
| } | ||||
| // ----- @willmmiles end patch ----- | ||||
|  | ||||
|  | ||||
| // No-op calls to override the PWM implementation | ||||
| extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; } | ||||
| extern "C" IRAM_ATTR bool _stopPWM_weak(int pin) { (void) pin; return false; } | ||||
| extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; } | ||||
|  | ||||
|  | ||||
| // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. | ||||
| constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160; | ||||
| // Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz | ||||
| constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); | ||||
| // Maximum servicing time for any single IRQ | ||||
| constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); | ||||
| // The latency between in-ISR rearming of the timer and the earliest firing | ||||
| constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2); | ||||
| // The SDK and hardware take some time to actually get to our NMI code | ||||
| constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ? | ||||
|   microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); | ||||
|  | ||||
| // for INFINITE, the NMI proceeds on the waveform without expiry deadline. | ||||
| // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. | ||||
| // for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES. | ||||
| // for UPDATEPHASE, the NMI recomputes the target timings | ||||
| // for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY. | ||||
| enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, UPDATEPHASE = 3, INIT = 4}; | ||||
|  | ||||
| // Waveform generator can create tones, PWM, and servos | ||||
| typedef struct { | ||||
|   uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. | ||||
|   uint32_t endDutyCcy;    // ESP clock cycle when going from duty to off | ||||
|   int32_t dutyCcys;       // Set next off cycle at low->high to maintain phase | ||||
|   int32_t adjDutyCcys;    // Temporary correction for next period | ||||
|   int32_t periodCcys;     // Set next phase cycle at low->high to maintain phase | ||||
|   uint32_t expiryCcy;     // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count | ||||
|   WaveformMode mode; | ||||
|   bool autoPwm;           // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings | ||||
| } Waveform; | ||||
|  | ||||
| namespace { | ||||
|  | ||||
|   static struct { | ||||
|     Waveform pins[17];             // State of all possible pins | ||||
|     uint32_t states = 0;           // Is the pin high or low, updated in NMI so no access outside the NMI code | ||||
|     uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code | ||||
|  | ||||
|     // Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine | ||||
|     int32_t toSetBits = 0;     // Message to the NMI handler to start/modify exactly one waveform | ||||
|     int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation | ||||
|  | ||||
|     // toSetBits temporaries | ||||
|     // cheaper than packing them in every Waveform, since we permit only one use at a time | ||||
|     uint32_t phaseCcy;      // positive phase offset ccy count   | ||||
|     int8_t alignPhase;      // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin | ||||
|  | ||||
|     uint32_t(*timer1CB)() = nullptr; | ||||
|  | ||||
|     bool timer1Running = false; | ||||
|  | ||||
|     uint32_t nextEventCcy; | ||||
|   } waveform; | ||||
|  | ||||
| } | ||||
|  | ||||
| // Interrupt on/off control | ||||
| static IRAM_ATTR void timer1Interrupt(); | ||||
|  | ||||
| // Non-speed critical bits | ||||
| #pragma GCC optimize ("Os") | ||||
|  | ||||
| static void initTimer() { | ||||
|   timer1_disable(); | ||||
|   ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); | ||||
|   ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); | ||||
|   timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); | ||||
|   waveform.timer1Running = true; | ||||
|   timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste | ||||
| } | ||||
|  | ||||
| static void IRAM_ATTR deinitTimer() { | ||||
|   ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); | ||||
|   timer1_disable(); | ||||
|   timer1_isr_init(); | ||||
|   waveform.timer1Running = false; | ||||
| } | ||||
|  | ||||
| extern "C" { | ||||
|  | ||||
| // Set a callback.  Pass in NULL to stop it | ||||
| void setTimer1Callback_weak(uint32_t (*fn)()) { | ||||
|   waveform.timer1CB = fn; | ||||
|   std::atomic_thread_fence(std::memory_order_acq_rel); | ||||
|   if (!waveform.timer1Running && fn) { | ||||
|     initTimer(); | ||||
|   } else if (waveform.timer1Running && !fn && !waveform.enabled) { | ||||
|     deinitTimer(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Start up a waveform on a pin, or change the current one.  Will change to the new | ||||
| // waveform smoothly on next low->high transition.  For immediate change, stopWaveform() | ||||
| // first, then it will immediately begin. | ||||
| int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, | ||||
|   uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) { | ||||
|   uint32_t periodCcys = highCcys + lowCcys; | ||||
|   if (periodCcys < MAXIRQTICKSCCYS) { | ||||
|     if (!highCcys) { | ||||
|       periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; | ||||
|     } | ||||
|     else if (!lowCcys) { | ||||
|       highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; | ||||
|     } | ||||
|   } | ||||
|   // sanity checks, including mixed signed/unsigned arithmetic safety | ||||
|   if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || | ||||
|     static_cast<int32_t>(periodCcys) <= 0 || | ||||
|     static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) { | ||||
|     return false; | ||||
|   } | ||||
|   Waveform& wave = waveform.pins[pin]; | ||||
|   wave.dutyCcys = highCcys; | ||||
|   wave.adjDutyCcys = 0; | ||||
|   wave.periodCcys = periodCcys; | ||||
|   wave.autoPwm = autoPwm; | ||||
|   waveform.alignPhase = (alignPhase < 0) ? -1 : alignPhase; | ||||
|   waveform.phaseCcy = phaseOffsetCcys; | ||||
|  | ||||
|   std::atomic_thread_fence(std::memory_order_acquire); | ||||
|   const uint32_t pinBit = 1UL << pin; | ||||
|   if (!(waveform.enabled & pinBit)) { | ||||
|     // wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR | ||||
|     wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count | ||||
|     wave.mode = WaveformMode::INIT; | ||||
|     if (!wave.dutyCcys) { | ||||
|       // If initially at zero duty cycle, force GPIO off | ||||
|       if (pin == 16) { | ||||
|         GP16O = 0; | ||||
|       } | ||||
|       else { | ||||
|         GPOC = pinBit; | ||||
|       } | ||||
|     } | ||||
|     std::atomic_thread_fence(std::memory_order_release); | ||||
|     waveform.toSetBits = 1UL << pin; | ||||
|     std::atomic_thread_fence(std::memory_order_release); | ||||
|     if (!waveform.timer1Running) { | ||||
|       initTimer(); | ||||
|     } | ||||
|     else if (T1V > IRQLATENCYCCYS) { | ||||
|       // Must not interfere if Timer is due shortly | ||||
|       timer1_write(IRQLATENCYCCYS); | ||||
|     } | ||||
|   } | ||||
|   else { | ||||
|     wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI | ||||
|     std::atomic_thread_fence(std::memory_order_release); | ||||
|     if (runTimeCcys) { | ||||
|       wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count | ||||
|       wave.mode = WaveformMode::UPDATEEXPIRY; | ||||
|       std::atomic_thread_fence(std::memory_order_release); | ||||
|       waveform.toSetBits = 1UL << pin; | ||||
|     } else if (alignPhase >= 0) { | ||||
|       // @willmmiles new feature | ||||
|       wave.mode = WaveformMode::UPDATEPHASE; // recalculate start | ||||
|       std::atomic_thread_fence(std::memory_order_release); | ||||
|       waveform.toSetBits = 1UL << pin; | ||||
|     } | ||||
|   } | ||||
|   std::atomic_thread_fence(std::memory_order_acq_rel); | ||||
|   while (waveform.toSetBits) { | ||||
|     esp_yield(); // Wait for waveform to update | ||||
|     std::atomic_thread_fence(std::memory_order_acquire); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| // Stops a waveform on a pin | ||||
| IRAM_ATTR int stopWaveform_weak(uint8_t pin) { | ||||
|   // Can't possibly need to stop anything if there is no timer active | ||||
|   if (!waveform.timer1Running) { | ||||
|     return false; | ||||
|   } | ||||
|   // If user sends in a pin >16 but <32, this will always point to a 0 bit | ||||
|   // If they send >=32, then the shift will result in 0 and it will also return false | ||||
|   std::atomic_thread_fence(std::memory_order_acquire); | ||||
|   const uint32_t pinBit = 1UL << pin; | ||||
|   if (waveform.enabled & pinBit) { | ||||
|     waveform.toDisableBits = 1UL << pin; | ||||
|     std::atomic_thread_fence(std::memory_order_release); | ||||
|     // Must not interfere if Timer is due shortly | ||||
|     if (T1V > IRQLATENCYCCYS) { | ||||
|       timer1_write(IRQLATENCYCCYS); | ||||
|     } | ||||
|     while (waveform.toDisableBits) { | ||||
|       /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ | ||||
|       std::atomic_thread_fence(std::memory_order_acquire); | ||||
|     } | ||||
|   } | ||||
|   if (!waveform.enabled && !waveform.timer1CB) { | ||||
|     deinitTimer(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }; | ||||
|  | ||||
| // Speed critical bits | ||||
| #pragma GCC optimize ("O2") | ||||
|  | ||||
| // For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. | ||||
| // Using constexpr makes sure that the CPU clock frequency is compile-time fixed. | ||||
| static inline IRAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) { | ||||
|   if (ISCPUFREQ160MHZ) { | ||||
|     return isCPU2X ? ccys : (ccys >> 1); | ||||
|   } | ||||
|   else { | ||||
|     return isCPU2X ? (ccys << 1) : ccys; | ||||
|   } | ||||
| } | ||||
|  | ||||
| static IRAM_ATTR void timer1Interrupt() { | ||||
|   const uint32_t isrStartCcy = ESP.getCycleCount(); | ||||
|   //int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; | ||||
|  | ||||
|   // ----- @willmmiles begin patch ----- | ||||
|   nmiCrashWorkaround(); | ||||
|   // ----- @willmmiles end patch ----- | ||||
|  | ||||
|   const bool isCPU2X = CPU2X & 1; | ||||
|   if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) { | ||||
|     // Handle enable/disable requests from main app. | ||||
|     waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off | ||||
|     // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) | ||||
|     waveform.toDisableBits = 0; | ||||
|   } | ||||
|  | ||||
|   if (waveform.toSetBits) { | ||||
|     const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1; | ||||
|     Waveform& wave = waveform.pins[toSetPin]; | ||||
|     switch (wave.mode) { | ||||
|     case WaveformMode::INIT: | ||||
|       waveform.states &= ~waveform.toSetBits; // Clear the state of any just started | ||||
|       if (waveform.alignPhase >= 0 && waveform.enabled & (1UL << waveform.alignPhase)) { | ||||
|         wave.nextPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X); | ||||
|       } | ||||
|       else { | ||||
|         wave.nextPeriodCcy = waveform.nextEventCcy; | ||||
|       } | ||||
|       if (!wave.expiryCcy) { | ||||
|         wave.mode = WaveformMode::INFINITE; | ||||
|         break; | ||||
|       } | ||||
|       // fall through | ||||
|     case WaveformMode::UPDATEEXPIRY: | ||||
|       // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count | ||||
|       wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X); | ||||
|       wave.mode = WaveformMode::EXPIRES; | ||||
|       break; | ||||
|     // @willmmiles new feature | ||||
|     case WaveformMode::UPDATEPHASE: | ||||
|       // in WaveformMode::UPDATEPHASE, we recalculate the targets | ||||
|       if ((waveform.alignPhase >= 0) && (waveform.enabled & (1UL << waveform.alignPhase))) { | ||||
|         // Compute phase shift to realign with target | ||||
|         auto const newPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X); | ||||
|         auto const period = scaleCcys(wave.periodCcys, isCPU2X); | ||||
|         auto shift = ((static_cast<int32_t> (newPeriodCcy - wave.nextPeriodCcy) + period/2) % period) - (period/2); | ||||
|         wave.nextPeriodCcy += static_cast<uint32_t>(shift); | ||||
|         if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) { | ||||
|           wave.endDutyCcy = wave.nextPeriodCcy; | ||||
|         } | ||||
|       } | ||||
|     default: | ||||
|       break; | ||||
|     } | ||||
|     waveform.toSetBits = 0; | ||||
|   } | ||||
|  | ||||
|   // Exit the loop if the next event, if any, is sufficiently distant. | ||||
|   const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; | ||||
|   uint32_t busyPins = waveform.enabled; | ||||
|   waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; | ||||
|  | ||||
|   uint32_t now = ESP.getCycleCount(); | ||||
|   uint32_t isrNextEventCcy = now; | ||||
|   while (busyPins) { | ||||
|     if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) { | ||||
|       waveform.nextEventCcy = isrNextEventCcy; | ||||
|       break; | ||||
|     } | ||||
|     isrNextEventCcy = waveform.nextEventCcy; | ||||
|     uint32_t loopPins = busyPins; | ||||
|     while (loopPins) { | ||||
|       const int pin = __builtin_ffsl(loopPins) - 1; | ||||
|       const uint32_t pinBit = 1UL << pin; | ||||
|       loopPins ^= pinBit; | ||||
|  | ||||
|       Waveform& wave = waveform.pins[pin]; | ||||
|  | ||||
| /* @willmmiles - wtf?  We don't want to accumulate drift | ||||
|       if (clockDrift) { | ||||
|         wave.endDutyCcy += clockDrift; | ||||
|         wave.nextPeriodCcy += clockDrift; | ||||
|         wave.expiryCcy += clockDrift; | ||||
|       } | ||||
| */           | ||||
|  | ||||
|       uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy; | ||||
|       if (WaveformMode::EXPIRES == wave.mode && | ||||
|         static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 && | ||||
|         static_cast<int32_t>(now - wave.expiryCcy) >= 0) { | ||||
|         // Disable any waveforms that are done | ||||
|         waveform.enabled ^= pinBit; | ||||
|         busyPins ^= pinBit; | ||||
|       } | ||||
|       else { | ||||
|         const int32_t overshootCcys = now - waveNextEventCcy; | ||||
|         if (overshootCcys >= 0) { | ||||
|           const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X); | ||||
|           if (waveform.states & pinBit) { | ||||
|             // active configuration and forward are 100% duty | ||||
|             if (wave.periodCcys == wave.dutyCcys) { | ||||
|               wave.nextPeriodCcy += periodCcys; | ||||
|               wave.endDutyCcy = wave.nextPeriodCcy; | ||||
|             } | ||||
|             else { | ||||
|               if (wave.autoPwm) { | ||||
|                 wave.adjDutyCcys += overshootCcys; | ||||
|               } | ||||
|               waveform.states ^= pinBit; | ||||
|               if (16 == pin) { | ||||
|                 GP16O = 0; | ||||
|               } | ||||
|               else { | ||||
|                 GPOC = pinBit; | ||||
|               } | ||||
|             } | ||||
|             waveNextEventCcy = wave.nextPeriodCcy; | ||||
|           } | ||||
|           else { | ||||
|             wave.nextPeriodCcy += periodCcys; | ||||
|             if (!wave.dutyCcys) { | ||||
|               wave.endDutyCcy = wave.nextPeriodCcy; | ||||
|             } | ||||
|             else { | ||||
|               int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X); | ||||
|               if (dutyCcys <= wave.adjDutyCcys) { | ||||
|                 dutyCcys >>= 1; | ||||
|                 wave.adjDutyCcys -= dutyCcys; | ||||
|               } | ||||
|               else if (wave.adjDutyCcys) { | ||||
|                 dutyCcys -= wave.adjDutyCcys; | ||||
|                 wave.adjDutyCcys = 0; | ||||
|               } | ||||
|               wave.endDutyCcy = now + dutyCcys; | ||||
|               if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) { | ||||
|                 wave.endDutyCcy = wave.nextPeriodCcy; | ||||
|               } | ||||
|               waveform.states |= pinBit; | ||||
|               if (16 == pin) { | ||||
|                 GP16O = 1; | ||||
|               } | ||||
|               else { | ||||
|                 GPOS = pinBit; | ||||
|               } | ||||
|             } | ||||
|             waveNextEventCcy = wave.endDutyCcy; | ||||
|           } | ||||
|  | ||||
|           if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) { | ||||
|             waveNextEventCcy = wave.expiryCcy; | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) { | ||||
|           busyPins ^= pinBit; | ||||
|           if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) { | ||||
|             waveform.nextEventCcy = waveNextEventCcy; | ||||
|           } | ||||
|         } | ||||
|         else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) { | ||||
|           isrNextEventCcy = waveNextEventCcy; | ||||
|         } | ||||
|       } | ||||
|       now = ESP.getCycleCount(); | ||||
|     } | ||||
|     //clockDrift = 0; | ||||
|   } | ||||
|  | ||||
|   int32_t callbackCcys = 0; | ||||
|   if (waveform.timer1CB) { | ||||
|     callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X); | ||||
|   } | ||||
|   now = ESP.getCycleCount(); | ||||
|   int32_t nextEventCcys = waveform.nextEventCcy - now; | ||||
|   // Account for unknown duration of timer1CB(). | ||||
|   if (waveform.timer1CB && nextEventCcys > callbackCcys) { | ||||
|     waveform.nextEventCcy = now + callbackCcys; | ||||
|     nextEventCcys = callbackCcys; | ||||
|   } | ||||
|  | ||||
|   // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. | ||||
|   int32_t deltaIrqCcys = DELTAIRQCCYS; | ||||
|   int32_t irqLatencyCcys = IRQLATENCYCCYS; | ||||
|   if (isCPU2X) { | ||||
|     nextEventCcys >>= 1; | ||||
|     deltaIrqCcys >>= 1; | ||||
|     irqLatencyCcys >>= 1; | ||||
|   } | ||||
|  | ||||
|   // Firing timer too soon, the NMI occurs before ISR has returned. | ||||
|   if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) { | ||||
|     waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS; | ||||
|     nextEventCcys = irqLatencyCcys; | ||||
|   } | ||||
|   else { | ||||
|     nextEventCcys -= deltaIrqCcys; | ||||
|   } | ||||
|  | ||||
|   // Register access is fast and edge IRQ was configured before. | ||||
|   T1L = nextEventCcys; | ||||
| } | ||||
							
								
								
									
										717
									
								
								lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										717
									
								
								lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,717 @@ | ||||
| /* esp8266_waveform imported from platform source code | ||||
|    Modified for WLED to work around a fault in the NMI handling, | ||||
|    which can result in the system locking up and hard WDT crashes. | ||||
|  | ||||
|    Imported from https://github.com/esp8266/Arduino/blob/7e0d20e2b9034994f573a236364e0aef17fd66de/cores/esp8266/core_esp8266_waveform_pwm.cpp | ||||
| */ | ||||
|  | ||||
| /* | ||||
|   esp8266_waveform - General purpose waveform generation and control, | ||||
|                      supporting outputs on all pins in parallel. | ||||
|  | ||||
|   Copyright (c) 2018 Earle F. Philhower, III.  All rights reserved. | ||||
|  | ||||
|   The core idea is to have a programmable waveform generator with a unique | ||||
|   high and low period (defined in microseconds or CPU clock cycles).  TIMER1 | ||||
|   is set to 1-shot mode and is always loaded with the time until the next | ||||
|   edge of any live waveforms. | ||||
|  | ||||
|   Up to one waveform generator per pin supported. | ||||
|  | ||||
|   Each waveform generator is synchronized to the ESP clock cycle counter, not | ||||
|   the timer.  This allows for removing interrupt jitter and delay as the | ||||
|   counter always increments once per 80MHz clock.  Changes to a waveform are | ||||
|   contiguous and only take effect on the next waveform transition, | ||||
|   allowing for smooth transitions. | ||||
|  | ||||
|   This replaces older tone(), analogWrite(), and the Servo classes. | ||||
|  | ||||
|   Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() | ||||
|   clock cycle count, or an interval measured in CPU clock cycles, but not | ||||
|   TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz). | ||||
|  | ||||
|   This library is free software; you can redistribute it and/or | ||||
|   modify it under the terms of the GNU Lesser General Public | ||||
|   License as published by the Free Software Foundation; either | ||||
|   version 2.1 of the License, or (at your option) any later version. | ||||
|  | ||||
|   This library is distributed in the hope that it will be useful, | ||||
|   but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|   Lesser General Public License for more details. | ||||
|  | ||||
|   You should have received a copy of the GNU Lesser General Public | ||||
|   License along with this library; if not, write to the Free Software | ||||
|   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||||
| */ | ||||
|  | ||||
|  | ||||
| #include <Arduino.h> | ||||
| #include <coredecls.h> | ||||
| #include "ets_sys.h" | ||||
| #include "core_esp8266_waveform.h" | ||||
| #include "user_interface.h" | ||||
|  | ||||
| extern "C" { | ||||
|  | ||||
| // Linker magic | ||||
| void usePWMFixedNMI() {}; | ||||
|  | ||||
| // Maximum delay between IRQs | ||||
| #define MAXIRQUS (10000) | ||||
|  | ||||
| // Waveform generator can create tones, PWM, and servos | ||||
| typedef struct { | ||||
|   uint32_t nextServiceCycle;   // ESP cycle timer when a transition required | ||||
|   uint32_t expiryCycle;        // For time-limited waveform, the cycle when this waveform must stop | ||||
|   uint32_t timeHighCycles;     // Actual running waveform period (adjusted using desiredCycles) | ||||
|   uint32_t timeLowCycles;      // | ||||
|   uint32_t desiredHighCycles;  // Ideal waveform period to drive the error signal | ||||
|   uint32_t desiredLowCycles;   // | ||||
|   uint32_t lastEdge;           // Cycle when this generator last changed | ||||
| } Waveform; | ||||
|  | ||||
| class WVFState { | ||||
| public: | ||||
|   Waveform waveform[17];        // State of all possible pins | ||||
|   uint32_t waveformState = 0;   // Is the pin high or low, updated in NMI so no access outside the NMI code | ||||
|   uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code | ||||
|  | ||||
|   // Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine | ||||
|   uint32_t waveformToEnable = 0;  // Message to the NMI handler to start a waveform on a inactive pin | ||||
|   uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation | ||||
|  | ||||
|   uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI | ||||
|   uint32_t waveformNewHigh = 0; | ||||
|   uint32_t waveformNewLow = 0; | ||||
|  | ||||
|   uint32_t (*timer1CB)() = NULL; | ||||
|  | ||||
|   // Optimize the NMI inner loop by keeping track of the min and max GPIO that we | ||||
|   // are generating.  In the common case (1 PWM) these may be the same pin and | ||||
|   // we can avoid looking at the other pins. | ||||
|   uint16_t startPin = 0; | ||||
|   uint16_t endPin = 0; | ||||
| }; | ||||
| static WVFState wvfState; | ||||
|  | ||||
|  | ||||
| // Ensure everything is read/written to RAM | ||||
| #define MEMBARRIER() { __asm__ volatile("" ::: "memory"); } | ||||
|  | ||||
| // Non-speed critical bits | ||||
| #pragma GCC optimize ("Os") | ||||
|  | ||||
| // Interrupt on/off control | ||||
| static IRAM_ATTR void timer1Interrupt(); | ||||
| static bool timerRunning = false; | ||||
|  | ||||
| static __attribute__((noinline)) void initTimer() { | ||||
|   if (!timerRunning) { | ||||
|     timer1_disable(); | ||||
|     ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); | ||||
|     ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); | ||||
|     timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); | ||||
|     timerRunning = true; | ||||
|     timer1_write(microsecondsToClockCycles(10)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| static IRAM_ATTR void forceTimerInterrupt() { | ||||
|   if (T1L > microsecondsToClockCycles(10)) { | ||||
|     T1L = microsecondsToClockCycles(10); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // PWM implementation using special purpose state machine | ||||
| // | ||||
| // Keep an ordered list of pins with the delta in cycles between each | ||||
| // element, with a terminal entry making up the remainder of the PWM | ||||
| // period.  With this method sum(all deltas) == PWM period clock cycles. | ||||
| // | ||||
| // At t=0 set all pins high and set the timeout for the 1st edge. | ||||
| // On interrupt, if we're at the last element reset to t=0 state | ||||
| // Otherwise, clear that pin down and set delay for next element | ||||
| // and so forth. | ||||
|  | ||||
| constexpr int maxPWMs = 8; | ||||
|  | ||||
| // PWM machine state | ||||
| typedef struct PWMState { | ||||
|   uint32_t mask; // Bitmask of active pins | ||||
|   uint32_t cnt;  // How many entries | ||||
|   uint32_t idx;  // Where the state machine is along the list | ||||
|   uint8_t  pin[maxPWMs + 1]; | ||||
|   uint32_t delta[maxPWMs + 1]; | ||||
|   uint32_t nextServiceCycle;  // Clock cycle for next step | ||||
|   struct PWMState *pwmUpdate; // Set by main code, cleared by ISR | ||||
| } PWMState; | ||||
|  | ||||
| static PWMState pwmState; | ||||
| static uint32_t _pwmFreq = 1000; | ||||
| static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq; | ||||
|  | ||||
|  | ||||
| // If there are no more scheduled activities, shut down Timer 1. | ||||
| // Otherwise, do nothing. | ||||
| static IRAM_ATTR void disableIdleTimer() { | ||||
|  if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { | ||||
|     ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); | ||||
|     timer1_disable(); | ||||
|     timer1_isr_init(); | ||||
|     timerRunning = false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Notify the NMI that a new PWM state is available through the mailbox. | ||||
| // Wait for mailbox to be emptied (either busy or delay() as needed) | ||||
| static IRAM_ATTR void _notifyPWM(PWMState *p, bool idle) { | ||||
|   p->pwmUpdate = nullptr; | ||||
|   pwmState.pwmUpdate = p; | ||||
|   MEMBARRIER(); | ||||
|   forceTimerInterrupt(); | ||||
|   while (pwmState.pwmUpdate) { | ||||
|     if (idle) { | ||||
|       esp_yield(); | ||||
|     } | ||||
|     MEMBARRIER(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range); | ||||
|  | ||||
|  | ||||
| // Called when analogWriteFreq() changed to update the PWM total period | ||||
| //extern void _setPWMFreq_weak(uint32_t freq) __attribute__((weak));  | ||||
| void _setPWMFreq_weak(uint32_t freq) { | ||||
|   _pwmFreq = freq; | ||||
|  | ||||
|   // Convert frequency into clock cycles | ||||
|   uint32_t cc = microsecondsToClockCycles(1000000UL) / freq; | ||||
|  | ||||
|   // Simple static adjustment to bring period closer to requested due to overhead | ||||
|   // Empirically determined as a constant PWM delay and a function of the number of PWMs | ||||
| #if F_CPU == 80000000 | ||||
|   cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110; | ||||
| #else | ||||
|   cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75; | ||||
| #endif | ||||
|  | ||||
|   if (cc == _pwmPeriod) { | ||||
|     return; // No change | ||||
|   } | ||||
|  | ||||
|   _pwmPeriod = cc; | ||||
|  | ||||
|   if (pwmState.cnt) { | ||||
|     PWMState p;  // The working copy since we can't edit the one in use | ||||
|     p.mask = 0; | ||||
|     p.cnt = 0; | ||||
|     for (uint32_t i = 0; i < pwmState.cnt; i++) { | ||||
|       auto pin = pwmState.pin[i]; | ||||
|       _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles); | ||||
|     } | ||||
|     // Update and wait for mailbox to be emptied | ||||
|     initTimer(); | ||||
|     _notifyPWM(&p, true); | ||||
|     disableIdleTimer(); | ||||
|   } | ||||
| } | ||||
| /* | ||||
| static void _setPWMFreq_bound(uint32_t freq) __attribute__((weakref("_setPWMFreq_weak"))); | ||||
| void _setPWMFreq(uint32_t freq) {  | ||||
|   _setPWMFreq_bound(freq); | ||||
| } | ||||
| */ | ||||
|  | ||||
| // Helper routine to remove an entry from the state machine | ||||
| // and clean up any marked-off entries | ||||
| static void _cleanAndRemovePWM(PWMState *p, int pin) { | ||||
|   uint32_t leftover = 0; | ||||
|   uint32_t in, out; | ||||
|   for (in = 0, out = 0; in < p->cnt; in++) { | ||||
|     if ((p->pin[in] != pin) && (p->mask & (1<<p->pin[in]))) { | ||||
|         p->pin[out] = p->pin[in]; | ||||
|         p->delta[out] = p->delta[in] + leftover; | ||||
|         leftover = 0; | ||||
|         out++; | ||||
|     } else { | ||||
|         leftover += p->delta[in]; | ||||
|         p->mask &= ~(1<<p->pin[in]); | ||||
|     } | ||||
|   } | ||||
|   p->cnt = out; | ||||
|   // Final pin is never used: p->pin[out] = 0xff; | ||||
|   p->delta[out] = p->delta[in] + leftover; | ||||
| } | ||||
|  | ||||
|  | ||||
| // Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%)) | ||||
| //extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak)); | ||||
| IRAM_ATTR bool _stopPWM_weak(uint8_t pin) { | ||||
|   if (!((1<<pin) & pwmState.mask)) { | ||||
|     return false; // Pin not actually active | ||||
|   } | ||||
|  | ||||
|   PWMState p;  // The working copy since we can't edit the one in use | ||||
|   p = pwmState; | ||||
|  | ||||
|   // In _stopPWM we just clear the mask but keep everything else | ||||
|   // untouched to save IRAM.  The main startPWM will handle cleanup. | ||||
|   p.mask &= ~(1<<pin); | ||||
|   if (!p.mask) { | ||||
|     // If all have been stopped, then turn PWM off completely | ||||
|     p.cnt = 0; | ||||
|   } | ||||
|  | ||||
|   // Update and wait for mailbox to be emptied, no delay (could be in ISR) | ||||
|   _notifyPWM(&p, false); | ||||
|   // Possibly shut down the timer completely if we're done | ||||
|   disableIdleTimer(); | ||||
|   return true; | ||||
| } | ||||
| /* | ||||
| static bool _stopPWM_bound(uint8_t pin) __attribute__((weakref("_stopPWM_weak"))); | ||||
| IRAM_ATTR bool _stopPWM(uint8_t pin) { | ||||
|   return _stopPWM_bound(pin); | ||||
| } | ||||
| */ | ||||
|  | ||||
| static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) { | ||||
|   // Stash the val and range so we can re-evaluate the fraction | ||||
|   // should the user change PWM frequency.  This allows us to | ||||
|   // give as great a precision as possible.  We know by construction | ||||
|   // that the waveform for this pin will be inactive so we can borrow | ||||
|   // memory from that structure. | ||||
|   wvfState.waveform[pin].desiredHighCycles = val;  // Numerator == high | ||||
|   wvfState.waveform[pin].desiredLowCycles = range; // Denominator == low | ||||
|  | ||||
|   uint32_t cc = (_pwmPeriod * val) / range; | ||||
|  | ||||
|   // Clip to sane values in the case we go from OK to not-OK when adjusting frequencies | ||||
|   if (cc == 0) { | ||||
|     cc = 1; | ||||
|   } else if (cc >= _pwmPeriod) { | ||||
|     cc = _pwmPeriod - 1; | ||||
|   } | ||||
|  | ||||
|   if (p.cnt == 0) { | ||||
|     // Starting up from scratch, special case 1st element and PWM period | ||||
|     p.pin[0] = pin; | ||||
|     p.delta[0] = cc; | ||||
|    // Final pin is never used: p.pin[1] = 0xff; | ||||
|     p.delta[1] = _pwmPeriod - cc; | ||||
|   } else { | ||||
|     uint32_t ttl = 0; | ||||
|     uint32_t i; | ||||
|     // Skip along until we're at the spot to insert | ||||
|     for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) { | ||||
|       ttl += p.delta[i]; | ||||
|     } | ||||
|     // Shift everything out by one to make space for new edge | ||||
|     for (int32_t j = p.cnt; j >= (int)i; j--) { | ||||
|       p.pin[j + 1] = p.pin[j]; | ||||
|       p.delta[j + 1] = p.delta[j]; | ||||
|     } | ||||
|     int off = cc - ttl; // The delta from the last edge to the one we're inserting | ||||
|     p.pin[i] = pin; | ||||
|     p.delta[i] = off; // Add the delta to this new pin | ||||
|     p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant | ||||
|   } | ||||
|   p.cnt++; | ||||
|   p.mask |= 1<<pin; | ||||
| } | ||||
|  | ||||
| // Called by analogWrite(1...99%) to set the PWM duty in clock cycles | ||||
| //extern bool _setPWM_weak(int pin, uint32_t val, uint32_t range) __attribute__((weak)); | ||||
| bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { | ||||
|   stopWaveform(pin); | ||||
|   PWMState p;  // Working copy | ||||
|   p = pwmState; | ||||
|   // Get rid of any entries for this pin | ||||
|   _cleanAndRemovePWM(&p, pin); | ||||
|   // And add it to the list, in order | ||||
|   if (p.cnt >= maxPWMs) { | ||||
|     return false; // No space left | ||||
|   } | ||||
|  | ||||
|   // Sanity check for all-on/off | ||||
|   uint32_t cc = (_pwmPeriod * val) / range; | ||||
|   if ((cc == 0) || (cc >= _pwmPeriod)) { | ||||
|     digitalWrite(pin, cc ? HIGH : LOW); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   _addPWMtoList(p, pin, val, range); | ||||
|  | ||||
|   // Set mailbox and wait for ISR to copy it over | ||||
|   initTimer(); | ||||
|   _notifyPWM(&p, true); | ||||
|   disableIdleTimer(); | ||||
|  | ||||
|   // Potentially recalculate the PWM period if we've added another pin | ||||
|   _setPWMFreq(_pwmFreq); | ||||
|  | ||||
|   return true; | ||||
| } | ||||
| /* | ||||
| static bool _setPWM_bound(int pin, uint32_t val, uint32_t range) __attribute__((weakref("_setPWM_weak"))); | ||||
| bool _setPWM(int pin, uint32_t val, uint32_t range) { | ||||
|   return _setPWM_bound(pin, val, range); | ||||
| } | ||||
| */ | ||||
|  | ||||
| // Start up a waveform on a pin, or change the current one.  Will change to the new | ||||
| // waveform smoothly on next low->high transition.  For immediate change, stopWaveform() | ||||
| // first, then it will immediately begin. | ||||
| //extern int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm)  __attribute__((weak)); | ||||
| int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, | ||||
|                              int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { | ||||
|   (void) alignPhase; | ||||
|   (void) phaseOffsetUS; | ||||
|   (void) autoPwm; | ||||
|  | ||||
|    if ((pin > 16) || isFlashInterfacePin(pin) || (timeHighCycles == 0)) { | ||||
|     return false; | ||||
|   } | ||||
|   Waveform *wave = &wvfState.waveform[pin]; | ||||
|   wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0; | ||||
|   if (runTimeCycles && !wave->expiryCycle) { | ||||
|     wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it | ||||
|   } | ||||
|  | ||||
|   _stopPWM(pin); // Make sure there's no PWM live here | ||||
|  | ||||
|   uint32_t mask = 1<<pin; | ||||
|   MEMBARRIER(); | ||||
|   if (wvfState.waveformEnabled & mask) { | ||||
|     // Make sure no waveform changes are waiting to be applied | ||||
|     while (wvfState.waveformToChange) { | ||||
|       esp_yield(); // Wait for waveform to update | ||||
|       MEMBARRIER(); | ||||
|     } | ||||
|     wvfState.waveformNewHigh = timeHighCycles; | ||||
|     wvfState.waveformNewLow = timeLowCycles; | ||||
|     MEMBARRIER(); | ||||
|     wvfState.waveformToChange = mask; | ||||
|     // The waveform will be updated some time in the future on the next period for the signal | ||||
|   } else { //  if (!(wvfState.waveformEnabled & mask)) { | ||||
|     wave->timeHighCycles = timeHighCycles; | ||||
|     wave->desiredHighCycles = timeHighCycles; | ||||
|     wave->timeLowCycles = timeLowCycles; | ||||
|     wave->desiredLowCycles = timeLowCycles; | ||||
|     wave->lastEdge = 0; | ||||
|     wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1); | ||||
|     wvfState.waveformToEnable |= mask; | ||||
|     MEMBARRIER(); | ||||
|     initTimer(); | ||||
|     forceTimerInterrupt(); | ||||
|     while (wvfState.waveformToEnable) { | ||||
|       esp_yield(); // Wait for waveform to update | ||||
|       MEMBARRIER(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
| /* | ||||
| static int startWaveformClockCycles_bound(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weakref("startWaveformClockCycles_weak"))); | ||||
| int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { | ||||
|   return startWaveformClockCycles_bound(pin, timeHighCycles, timeLowCycles, runTimeCycles, alignPhase, phaseOffsetUS, autoPwm); | ||||
| } | ||||
|  | ||||
|  | ||||
| // This version falls-thru to the proper startWaveformClockCycles call and is invariant across waveform generators | ||||
| int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS, | ||||
|                   int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { | ||||
|   return startWaveformClockCycles_bound(pin, | ||||
|     microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), | ||||
|     microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm); | ||||
| } | ||||
| */ | ||||
|  | ||||
| // Set a callback.  Pass in NULL to stop it | ||||
| //extern void setTimer1Callback_weak(uint32_t (*fn)()) __attribute__((weak)); | ||||
| void setTimer1Callback_weak(uint32_t (*fn)()) { | ||||
|   wvfState.timer1CB = fn; | ||||
|   if (fn) { | ||||
|     initTimer(); | ||||
|     forceTimerInterrupt(); | ||||
|   } | ||||
|   disableIdleTimer(); | ||||
| } | ||||
| /* | ||||
| static void setTimer1Callback_bound(uint32_t (*fn)()) __attribute__((weakref("setTimer1Callback_weak"))); | ||||
| void setTimer1Callback(uint32_t (*fn)()) { | ||||
|   setTimer1Callback_bound(fn); | ||||
| } | ||||
| */ | ||||
|  | ||||
| // Stops a waveform on a pin | ||||
| //extern int stopWaveform_weak(uint8_t pin) __attribute__((weak)); | ||||
| IRAM_ATTR int stopWaveform_weak(uint8_t pin) { | ||||
|   // Can't possibly need to stop anything if there is no timer active | ||||
|   if (!timerRunning) { | ||||
|     return false; | ||||
|   } | ||||
|   // If user sends in a pin >16 but <32, this will always point to a 0 bit | ||||
|   // If they send >=32, then the shift will result in 0 and it will also return false | ||||
|   uint32_t mask = 1<<pin; | ||||
|   if (wvfState.waveformEnabled & mask) { | ||||
|     wvfState.waveformToDisable = mask; | ||||
|     // Cancel any pending updates for this waveform, too. | ||||
|     if (wvfState.waveformToChange & mask) { | ||||
|         wvfState.waveformToChange = 0; | ||||
|     } | ||||
|     forceTimerInterrupt(); | ||||
|     while (wvfState.waveformToDisable) { | ||||
|       MEMBARRIER(); // If it wasn't written yet, it has to be by now | ||||
|       /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ | ||||
|     } | ||||
|   } | ||||
|   disableIdleTimer(); | ||||
|   return true; | ||||
| } | ||||
| /* | ||||
| static int stopWaveform_bound(uint8_t pin) __attribute__((weakref("stopWaveform_weak"))); | ||||
| IRAM_ATTR int stopWaveform(uint8_t pin) { | ||||
|   return stopWaveform_bound(pin); | ||||
| } | ||||
| */ | ||||
|  | ||||
| // Speed critical bits | ||||
| #pragma GCC optimize ("O2") | ||||
|  | ||||
| // Normally would not want two copies like this, but due to different | ||||
| // optimization levels the inline attribute gets lost if we try the | ||||
| // other version. | ||||
| static inline IRAM_ATTR uint32_t GetCycleCountIRQ() { | ||||
|   uint32_t ccount; | ||||
|   __asm__ __volatile__("rsr %0,ccount":"=a"(ccount)); | ||||
|   return ccount; | ||||
| } | ||||
|  | ||||
| // Find the earliest cycle as compared to right now | ||||
| static inline IRAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) { | ||||
|     uint32_t now = GetCycleCountIRQ(); | ||||
|     int32_t da = a - now; | ||||
|     int32_t db = b - now; | ||||
|     return (da < db) ? a : b; | ||||
| } | ||||
|  | ||||
| // ----- @willmmiles begin patch ----- | ||||
| // NMI crash workaround | ||||
| // Sometimes the NMI fails to return, stalling the CPU.  When this happens, | ||||
| // the next NMI gets a return address /inside the NMI handler function/. | ||||
| // We work around this by caching the last NMI return address, and restoring | ||||
| // the epc3 and eps3 registers to the previous values if the observed epc3 | ||||
| // happens to be pointing to the _NMILevelVector function. | ||||
| extern void _NMILevelVector(); | ||||
| extern void _UserExceptionVector_1(); // the next function after _NMILevelVector | ||||
| static inline IRAM_ATTR void nmiCrashWorkaround() { | ||||
|   static uintptr_t epc3_backup, eps3_backup; | ||||
|  | ||||
|   uintptr_t epc3, eps3; | ||||
|   __asm__ __volatile__("rsr %0,epc3; rsr %1,eps3":"=a"(epc3),"=a" (eps3)); | ||||
|   if ((epc3 < (uintptr_t) &_NMILevelVector) || (epc3 >= (uintptr_t) &_UserExceptionVector_1)) { | ||||
|     // Address is good; save backup | ||||
|     epc3_backup = epc3; | ||||
|     eps3_backup = eps3; | ||||
|   } else { | ||||
|     // Address is inside the NMI handler -- restore from backup | ||||
|     __asm__ __volatile__("wsr %0,epc3; wsr %1,eps3"::"a"(epc3_backup),"a"(eps3_backup)); | ||||
|   } | ||||
| } | ||||
| // ----- @willmmiles end patch ----- | ||||
|  | ||||
|  | ||||
| // The SDK and hardware take some time to actually get to our NMI code, so | ||||
| // decrement the next IRQ's timer value by a bit so we can actually catch the | ||||
| // real CPU cycle counter we want for the waveforms. | ||||
|  | ||||
| // The SDK also sometimes is running at a different speed the the Arduino core | ||||
| // so the ESP cycle counter is actually running at a variable speed. | ||||
| // adjust(x) takes care of adjusting a delta clock cycle amount accordingly. | ||||
| #if F_CPU == 80000000 | ||||
|   #define DELTAIRQ (microsecondsToClockCycles(9)/4) | ||||
|   #define adjust(x) ((x) << (turbo ? 1 : 0)) | ||||
| #else | ||||
|   #define DELTAIRQ (microsecondsToClockCycles(9)/8) | ||||
|   #define adjust(x) ((x) >> 0) | ||||
| #endif | ||||
|  | ||||
| // When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage | ||||
| #define MINIRQTIME microsecondsToClockCycles(6) | ||||
|  | ||||
| static IRAM_ATTR void timer1Interrupt() { | ||||
|   // ----- @willmmiles begin patch ----- | ||||
|   nmiCrashWorkaround(); | ||||
|   // ----- @willmmiles end patch ----- | ||||
|  | ||||
|   // Flag if the core is at 160 MHz, for use by adjust() | ||||
|   bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false; | ||||
|  | ||||
|   uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS); | ||||
|   uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14); | ||||
|  | ||||
|   if (wvfState.waveformToEnable || wvfState.waveformToDisable) { | ||||
|     // Handle enable/disable requests from main app | ||||
|     wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off | ||||
|     wvfState.waveformState &= ~wvfState.waveformToEnable;  // And clear the state of any just started | ||||
|     wvfState.waveformToEnable = 0; | ||||
|     wvfState.waveformToDisable = 0; | ||||
|     // No mem barrier.  Globals must be written to RAM on ISR exit. | ||||
|     // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) | ||||
|     wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1; | ||||
|     // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) | ||||
|     wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); | ||||
|   } else if (!pwmState.cnt && pwmState.pwmUpdate) { | ||||
|     // Start up the PWM generator by copying from the mailbox | ||||
|     pwmState.cnt = 1; | ||||
|     pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 | ||||
|     pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! | ||||
|     // No need for mem barrier here.  Global must be written by IRQ exit | ||||
|   } | ||||
|  | ||||
|   bool done = false; | ||||
|   if (wvfState.waveformEnabled || pwmState.cnt) { | ||||
|     do { | ||||
|       nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS); | ||||
|  | ||||
|       // PWM state machine implementation | ||||
|       if (pwmState.cnt) { | ||||
|         int32_t cyclesToGo; | ||||
|         do { | ||||
|             cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ(); | ||||
|             if (cyclesToGo < 0) { | ||||
|                 if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new | ||||
|                   if (pwmState.pwmUpdate) { | ||||
|                     // Do the memory copy from temp to global and clear mailbox | ||||
|                     pwmState = *(PWMState*)pwmState.pwmUpdate; | ||||
|                   } | ||||
|                   GPOS = pwmState.mask; // Set all active pins high | ||||
|                   if (pwmState.mask & (1<<16)) { | ||||
|                     GP16O = 1; | ||||
|                   } | ||||
|                   pwmState.idx = 0; | ||||
|                 } else { | ||||
|                   do { | ||||
|                     // Drop the pin at this edge | ||||
|                     if (pwmState.mask & (1<<pwmState.pin[pwmState.idx])) { | ||||
|                       GPOC = 1<<pwmState.pin[pwmState.idx]; | ||||
|                       if (pwmState.pin[pwmState.idx] == 16) { | ||||
|                         GP16O = 0; | ||||
|                       } | ||||
|                     } | ||||
|                     pwmState.idx++; | ||||
|                     // Any other pins at this same PWM value will have delta==0, drop them too. | ||||
|                   } while (pwmState.delta[pwmState.idx] == 0); | ||||
|                 } | ||||
|                 // Preserve duty cycle over PWM period by using now+xxx instead of += delta | ||||
|                 cyclesToGo = adjust(pwmState.delta[pwmState.idx]); | ||||
|                 pwmState.nextServiceCycle = GetCycleCountIRQ() + cyclesToGo; | ||||
|             } | ||||
|             nextEventCycle = earliest(nextEventCycle, pwmState.nextServiceCycle); | ||||
|         } while (pwmState.cnt && (cyclesToGo < 100)); | ||||
|       } | ||||
|  | ||||
|       for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) { | ||||
|         uint32_t mask = 1<<i; | ||||
|  | ||||
|         // If it's not on, ignore! | ||||
|         if (!(wvfState.waveformEnabled & mask)) { | ||||
|           continue; | ||||
|         } | ||||
|  | ||||
|         Waveform *wave = &wvfState.waveform[i]; | ||||
|         uint32_t now = GetCycleCountIRQ(); | ||||
|  | ||||
|         // Disable any waveforms that are done | ||||
|         if (wave->expiryCycle) { | ||||
|           int32_t expiryToGo = wave->expiryCycle - now; | ||||
|           if (expiryToGo < 0) { | ||||
|               // Done, remove! | ||||
|               if (i == 16) { | ||||
|                 GP16O = 0; | ||||
|               }  | ||||
|               GPOC = mask; | ||||
|               wvfState.waveformEnabled &= ~mask; | ||||
|               continue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Check for toggles | ||||
|         int32_t cyclesToGo = wave->nextServiceCycle - now; | ||||
|         if (cyclesToGo < 0) { | ||||
|           uint32_t nextEdgeCycles; | ||||
|           uint32_t desired = 0; | ||||
|           uint32_t *timeToUpdate; | ||||
|           wvfState.waveformState ^= mask; | ||||
|           if (wvfState.waveformState & mask) { | ||||
|             if (i == 16) { | ||||
|               GP16O = 1; | ||||
|             } | ||||
|             GPOS = mask; | ||||
|  | ||||
|             if (wvfState.waveformToChange & mask) { | ||||
|               // Copy over next full-cycle timings | ||||
|               wave->timeHighCycles = wvfState.waveformNewHigh; | ||||
|               wave->desiredHighCycles = wvfState.waveformNewHigh; | ||||
|               wave->timeLowCycles = wvfState.waveformNewLow; | ||||
|               wave->desiredLowCycles = wvfState.waveformNewLow; | ||||
|               wave->lastEdge = 0; | ||||
|               wvfState.waveformToChange = 0; | ||||
|             } | ||||
|             if (wave->lastEdge) { | ||||
|               desired = wave->desiredLowCycles; | ||||
|               timeToUpdate = &wave->timeLowCycles; | ||||
|             } | ||||
|             nextEdgeCycles = wave->timeHighCycles; | ||||
|           } else { | ||||
|             if (i == 16) { | ||||
|               GP16O = 0; | ||||
|             } | ||||
|             GPOC = mask; | ||||
|             desired = wave->desiredHighCycles; | ||||
|             timeToUpdate = &wave->timeHighCycles; | ||||
|             nextEdgeCycles = wave->timeLowCycles; | ||||
|           } | ||||
|           if (desired) { | ||||
|             desired = adjust(desired); | ||||
|             int32_t err = desired - (now - wave->lastEdge); | ||||
|             if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal | ||||
|                 err /= 2; | ||||
|                 *timeToUpdate += err; | ||||
|             } | ||||
|           } | ||||
|           nextEdgeCycles = adjust(nextEdgeCycles); | ||||
|           wave->nextServiceCycle = now + nextEdgeCycles; | ||||
|           wave->lastEdge = now; | ||||
|         } | ||||
|         nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle); | ||||
|       } | ||||
|  | ||||
|       // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur | ||||
|       uint32_t now = GetCycleCountIRQ(); | ||||
|       int32_t cycleDeltaNextEvent = nextEventCycle - now; | ||||
|       int32_t cyclesLeftTimeout = timeoutCycle - now; | ||||
|       done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0); | ||||
|     } while (!done); | ||||
|   } // if (wvfState.waveformEnabled) | ||||
|  | ||||
|   if (wvfState.timer1CB) { | ||||
|     nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB()); | ||||
|   } | ||||
|  | ||||
|   int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ(); | ||||
|  | ||||
|   if (nextEventCycles < MINIRQTIME) { | ||||
|     nextEventCycles = MINIRQTIME; | ||||
|   } | ||||
|   nextEventCycles -= DELTAIRQ; | ||||
|  | ||||
|   // Do it here instead of global function to save time and because we know it's edge-IRQ | ||||
|   T1L = nextEventCycles >> (turbo ? 1 : 0); | ||||
| } | ||||
|  | ||||
| }; | ||||
							
								
								
									
										1907
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1907
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "wled", | ||||
|   "version": "0.16.0-alpha", | ||||
|   "version": "0.15.0", | ||||
|   "description": "Tools for WLED project", | ||||
|   "main": "tools/cdata.js", | ||||
|   "directories": { | ||||
| @@ -14,21 +14,21 @@ | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git+https://github.com/wled-dev/WLED.git" | ||||
|     "url": "git+https://github.com/Aircoookie/WLED.git" | ||||
|   }, | ||||
|   "author": "", | ||||
|   "license": "ISC", | ||||
|   "bugs": { | ||||
|     "url": "https://github.com/wled-dev/WLED/issues" | ||||
|     "url": "https://github.com/Aircoookie/WLED/issues" | ||||
|   }, | ||||
|   "homepage": "https://github.com/wled-dev/WLED#readme", | ||||
|   "homepage": "https://github.com/Aircoookie/WLED#readme", | ||||
|   "dependencies": { | ||||
|     "clean-css": "^5.3.3", | ||||
|     "html-minifier-terser": "^7.2.0", | ||||
|     "web-resource-inliner": "^7.0.0", | ||||
|     "nodemon": "^3.1.9" | ||||
|     "inliner": "^1.13.1", | ||||
|     "nodemon": "^3.1.7" | ||||
|   }, | ||||
|   "engines": { | ||||
|     "node": ">=20.0.0" | ||||
|   } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,21 +1,3 @@ | ||||
| Import("env") | ||||
| import shutil | ||||
| Import('env') | ||||
|  | ||||
| node_ex = shutil.which("node") | ||||
| # Check if Node.js is installed and present in PATH if it failed, abort the build | ||||
| if node_ex is None: | ||||
|     print('\x1b[0;31;43m' + 'Node.js is not installed or missing from PATH html css js will not be processed check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m') | ||||
|     exitCode = env.Execute("null") | ||||
|     exit(exitCode) | ||||
| else: | ||||
|     # Install the necessary node packages for the pre-build asset bundling script | ||||
|     print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m') | ||||
|     env.Execute("npm ci") | ||||
|  | ||||
|     # Call the bundling script | ||||
|     exitCode = env.Execute("npm run build") | ||||
|  | ||||
|     # If it failed, abort the build | ||||
|     if (exitCode): | ||||
|       print('\x1b[0;31;43m' + 'npm run build fails check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m') | ||||
|       exit(exitCode) | ||||
| env.Execute("npm run build") | ||||
| @@ -1,107 +0,0 @@ | ||||
| Import('env') | ||||
| from collections import deque | ||||
| from pathlib import Path   # For OS-agnostic path manipulation | ||||
| from click import secho | ||||
| from SCons.Script import Exit | ||||
| from platformio.builder.tools.piolib import LibBuilderBase | ||||
|  | ||||
| usermod_dir = Path(env["PROJECT_DIR"]).resolve() / "usermods" | ||||
|  | ||||
| # Utility functions | ||||
| def find_usermod(mod: str) -> Path: | ||||
|   """Locate this library in the usermods folder. | ||||
|      We do this to avoid needing to rename a bunch of folders; | ||||
|      this could be removed later | ||||
|   """ | ||||
|   # Check name match | ||||
|   mp = usermod_dir / mod | ||||
|   if mp.exists(): | ||||
|     return mp | ||||
|   mp = usermod_dir / f"{mod}_v2" | ||||
|   if mp.exists(): | ||||
|     return mp | ||||
|   mp = usermod_dir / f"usermod_v2_{mod}" | ||||
|   if mp.exists(): | ||||
|     return mp | ||||
|   raise RuntimeError(f"Couldn't locate module {mod} in usermods directory!") | ||||
|  | ||||
| def is_wled_module(dep: LibBuilderBase) -> bool: | ||||
|   """Returns true if the specified library is a wled module | ||||
|   """ | ||||
|   return usermod_dir in Path(dep.src_dir).parents or str(dep.name).startswith("wled-") | ||||
|  | ||||
| ## Script starts here | ||||
| # Process usermod option | ||||
| usermods = env.GetProjectOption("custom_usermods","") | ||||
|  | ||||
| # Handle "all usermods" case | ||||
| if usermods == '*': | ||||
|   usermods = [f.name for f in usermod_dir.iterdir() if f.is_dir() and f.joinpath('library.json').exists()] | ||||
| else: | ||||
|   usermods = usermods.split() | ||||
|  | ||||
| if usermods: | ||||
|   # Inject usermods in to project lib_deps | ||||
|   symlinks = [f"symlink://{find_usermod(mod).resolve()}" for mod in usermods] | ||||
|   env.GetProjectConfig().set("env:" + env['PIOENV'], 'lib_deps', env.GetProjectOption('lib_deps') + symlinks) | ||||
|  | ||||
| # Utility function for assembling usermod include paths | ||||
| def cached_add_includes(dep, dep_cache: set, includes: deque): | ||||
|   """ Add dep's include paths to includes if it's not in the cache """ | ||||
|   if dep not in dep_cache: | ||||
|     dep_cache.add(dep) | ||||
|     for include in dep.get_include_dirs(): | ||||
|       if include not in includes: | ||||
|         includes.appendleft(include) | ||||
|       if usermod_dir not in Path(dep.src_dir).parents: | ||||
|         # Recurse, but only for NON-usermods | ||||
|         for subdep in dep.depbuilders: | ||||
|           cached_add_includes(subdep, dep_cache, includes) | ||||
|  | ||||
| # Monkey-patch ConfigureProjectLibBuilder to mark up the dependencies | ||||
| # Save the old value | ||||
| old_ConfigureProjectLibBuilder = env.ConfigureProjectLibBuilder | ||||
|  | ||||
| # Our new wrapper | ||||
| def wrapped_ConfigureProjectLibBuilder(xenv): | ||||
|   # Call the wrapped function | ||||
|   result = old_ConfigureProjectLibBuilder.clone(xenv)() | ||||
|  | ||||
|   # Fix up include paths | ||||
|   # In PlatformIO >=6.1.17, this could be done prior to ConfigureProjectLibBuilder | ||||
|   wled_dir = xenv["PROJECT_SRC_DIR"] | ||||
|   # Build a list of dependency include dirs | ||||
|   # TODO: Find out if this is the order that PlatformIO/SCons puts them in?? | ||||
|   processed_deps = set() | ||||
|   extra_include_dirs = deque()  # Deque used for fast prepend | ||||
|   for dep in result.depbuilders: | ||||
|      cached_add_includes(dep, processed_deps, extra_include_dirs) | ||||
|  | ||||
|   wled_deps = [dep for dep in result.depbuilders if is_wled_module(dep)] | ||||
|  | ||||
|   broken_usermods = [] | ||||
|   for dep in wled_deps: | ||||
|     # Add the wled folder to the include path | ||||
|     dep.env.PrependUnique(CPPPATH=str(wled_dir)) | ||||
|     # Add WLED's own dependencies | ||||
|     for dir in extra_include_dirs: | ||||
|       dep.env.PrependUnique(CPPPATH=str(dir)) | ||||
|     # Enforce that libArchive is not set; we must link them directly to the executable | ||||
|     if dep.lib_archive: | ||||
|       broken_usermods.append(dep) | ||||
|  | ||||
|   if broken_usermods: | ||||
|     broken_usermods = [usermod.name for usermod in broken_usermods] | ||||
|     secho( | ||||
|       f"ERROR: libArchive=false is missing on usermod(s) {' '.join(broken_usermods)} -- modules will not compile in correctly", | ||||
|       fg="red", | ||||
|       err=True) | ||||
|     Exit(1) | ||||
|  | ||||
|   # Save the depbuilders list for later validation | ||||
|   xenv.Replace(WLED_MODULES=wled_deps) | ||||
|  | ||||
|   return result | ||||
|  | ||||
| # Apply the wrapper | ||||
| env.AddMethod(wrapped_ConfigureProjectLibBuilder, "ConfigureProjectLibBuilder") | ||||
| @@ -1,80 +0,0 @@ | ||||
| import re | ||||
| from pathlib import Path   # For OS-agnostic path manipulation | ||||
| from typing import Iterable | ||||
| from click import secho | ||||
| from SCons.Script import Action, Exit | ||||
| from platformio.builder.tools.piolib import LibBuilderBase | ||||
|  | ||||
|  | ||||
| def is_wled_module(env, dep: LibBuilderBase) -> bool: | ||||
|   """Returns true if the specified library is a wled module | ||||
|   """ | ||||
|   usermod_dir = Path(env["PROJECT_DIR"]).resolve() / "usermods" | ||||
|   return usermod_dir in Path(dep.src_dir).parents or str(dep.name).startswith("wled-") | ||||
|  | ||||
|  | ||||
| def read_lines(p: Path): | ||||
|     """ Read in the contents of a file for analysis """ | ||||
|     with p.open("r", encoding="utf-8", errors="ignore") as f: | ||||
|         return f.readlines() | ||||
|  | ||||
|  | ||||
| def check_map_file_objects(map_file: list[str], dirs: Iterable[str]) -> set[str]: | ||||
|     """ Identify which dirs contributed to the final build | ||||
|  | ||||
|         Returns the (sub)set of dirs that are found in the output ELF | ||||
|     """ | ||||
|     # Pattern to match symbols in object directories | ||||
|     # Join directories into alternation | ||||
|     usermod_dir_regex = "|".join([re.escape(dir) for dir in dirs]) | ||||
|     # Matches nonzero address, any size, and any path in a matching directory | ||||
|     object_path_regex = re.compile(r"0x0*[1-9a-f][0-9a-f]*\s+0x[0-9a-f]+\s+\S+[/\\](" + usermod_dir_regex + r")[/\\]\S+\.o") | ||||
|  | ||||
|     found = set() | ||||
|     for line in map_file: | ||||
|         matches = object_path_regex.findall(line) | ||||
|         for m in matches: | ||||
|             found.add(m) | ||||
|     return found | ||||
|  | ||||
|  | ||||
| def count_usermod_objects(map_file: list[str]) -> int: | ||||
|     """ Returns the number of usermod objects in the usermod list """ | ||||
|     # Count the number of entries in the usermods table section | ||||
|     return len([x for x in map_file if ".dtors.tbl.usermods.1" in x]) | ||||
|  | ||||
|  | ||||
| def validate_map_file(source, target, env): | ||||
|     """ Validate that all modules appear in the output build """ | ||||
|     build_dir = Path(env.subst("$BUILD_DIR")) | ||||
|     map_file_path = build_dir /  env.subst("${PROGNAME}.map") | ||||
|  | ||||
|     if not map_file_path.exists(): | ||||
|         secho(f"ERROR: Map file not found: {map_file_path}", fg="red", err=True) | ||||
|         Exit(1) | ||||
|  | ||||
|     # Identify the WLED module builders, set by load_usermods.py | ||||
|     module_lib_builders = env['WLED_MODULES'] | ||||
|  | ||||
|     # Extract the values we care about | ||||
|     modules = {Path(builder.build_dir).name: builder.name for builder in module_lib_builders} | ||||
|     secho(f"INFO: {len(modules)} libraries linked as WLED optional/user modules") | ||||
|  | ||||
|     # Now parse the map file | ||||
|     map_file_contents = read_lines(map_file_path) | ||||
|     usermod_object_count = count_usermod_objects(map_file_contents) | ||||
|     secho(f"INFO: {usermod_object_count} usermod object entries") | ||||
|  | ||||
|     confirmed_modules = check_map_file_objects(map_file_contents, modules.keys()) | ||||
|     missing_modules = [modname for mdir, modname in modules.items() if mdir not in confirmed_modules] | ||||
|     if missing_modules: | ||||
|         secho( | ||||
|             f"ERROR: No object files from {missing_modules} found in linked output!", | ||||
|             fg="red", | ||||
|             err=True) | ||||
|         Exit(1) | ||||
|     return None | ||||
|  | ||||
| Import("env") | ||||
| env.Append(LINKFLAGS=[env.subst("-Wl,--Map=${BUILD_DIR}/${PROGNAME}.map")]) | ||||
| env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", Action(validate_map_file, cmdstr='Checking linked optional modules (usermods) in map file')) | ||||
							
								
								
									
										210
									
								
								platformio.ini
									
									
									
									
									
								
							
							
						
						
									
										210
									
								
								platformio.ini
									
									
									
									
									
								
							| @@ -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, 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 | ||||
|  | ||||
| src_dir  = ./wled00 | ||||
| data_dir = ./wled00/data | ||||
| @@ -114,9 +114,7 @@ extra_scripts = | ||||
|   post:pio-scripts/output_bins.py | ||||
|   post:pio-scripts/strip-floats.py | ||||
|   pre:pio-scripts/user_config_copy.py | ||||
|   pre:pio-scripts/load_usermods.py | ||||
|   pre:pio-scripts/build_ui.py | ||||
|   post:pio-scripts/validate_modules.py  ;; double-check the build output usermods | ||||
|   ; post:pio-scripts/obj-dump.py  ;; convenience script to create a disassembly dump of the firmware (hardcore debugging) | ||||
|  | ||||
| # ------------------------------------------------------------------------------ | ||||
| @@ -140,10 +138,9 @@ lib_compat_mode = strict | ||||
| lib_deps = | ||||
|     fastled/FastLED @ 3.6.0 | ||||
|     IRremoteESP8266 @ 2.8.2 | ||||
|     makuna/NeoPixelBus @ 2.8.3 | ||||
|     makuna/NeoPixelBus @ 2.8.0 | ||||
|     #https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta | ||||
|     https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2 | ||||
|     marvinroger/AsyncMqttClient @ 0.9.0 | ||||
|     https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 | ||||
|   # for I2C interface | ||||
|     ;Wire | ||||
|   # ESP-NOW library | ||||
| @@ -160,18 +157,25 @@ lib_deps = | ||||
|     ;adafruit/Adafruit BMP280 Library @ 2.1.0 | ||||
|     ;adafruit/Adafruit CCS811 Library @ 1.0.4 | ||||
|     ;adafruit/Adafruit Si7021 Library @ 1.4.0 | ||||
|   #For ADS1115 sensor uncomment following | ||||
|     ;adafruit/Adafruit BusIO @ 1.13.2 | ||||
|     ;adafruit/Adafruit ADS1X15 @ 2.4.0 | ||||
|   #For MAX1704x Lipo Monitor / Fuel Gauge uncomment following | ||||
|     ; https://github.com/adafruit/Adafruit_BusIO @ 1.14.5 | ||||
|     ; https://github.com/adafruit/Adafruit_MAX1704X @ 1.0.2 | ||||
|   #For MPU6050 IMU uncomment follwoing | ||||
|     ;electroniccats/MPU6050 @1.0.1 | ||||
|   # For -D USERMOD_ANIMARTRIX | ||||
|   # CC BY-NC 3.0 licensed effects by Stefan Petrick, include this usermod only if you accept the terms! | ||||
|     ;https://github.com/netmindz/animartrix.git#18bf17389e57c69f11bc8d04ebe1d215422c7fb7 | ||||
|   # SHT85 | ||||
|     ;robtillaart/SHT85@~0.3.3 | ||||
|   # Audioreactive usermod | ||||
|     ;kosme/arduinoFFT @ 2.0.1 | ||||
|  | ||||
| extra_scripts = ${scripts_defaults.extra_scripts} | ||||
|  | ||||
| [esp8266] | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = | ||||
|   -DESP8266 | ||||
|   -DFP_IN_IROM | ||||
| @@ -231,110 +235,104 @@ lib_deps_compat = | ||||
|   IRremoteESP8266 @ 2.8.2 | ||||
|   makuna/NeoPixelBus @ 2.7.9 | ||||
|   https://github.com/blazoncek/QuickESPNow.git#optional-debug | ||||
|   https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0 | ||||
|   https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 | ||||
|  | ||||
| [esp32_all_variants] | ||||
| lib_deps = | ||||
|   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 = ${esp32_idf_V4.platform} | ||||
| platform_packages = | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${esp32_idf_V4.build_flags} | ||||
| lib_deps = ${esp32_idf_V4.lib_deps} | ||||
|  | ||||
| #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 | ||||
| build_flags = -g | ||||
|   -DARDUINO_ARCH_ESP32 | ||||
|   #-DCONFIG_LITTLEFS_FOR_IDF_3_2 | ||||
|   -D CONFIG_ASYNC_TCP_USE_WDT=0 | ||||
|   #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x | ||||
|   -D LOROL_LITTLEFS | ||||
|   ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 | ||||
| tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv | ||||
| default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv | ||||
| extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv | ||||
| big_partitions = tools/WLED_ESP32_4MB_256KB_FS.csv     ;; 1.8MB firmware, 256KB filesystem, coredump support | ||||
| large_partitions = tools/WLED_ESP32_8MB.csv | ||||
| extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv | ||||
|  | ||||
| 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) | ||||
| AR_lib_deps =  ;; for pre-usermod-library platformio_override compatibility | ||||
|  | ||||
| lib_deps = | ||||
|   https://github.com/lorol/LITTLEFS.git | ||||
|   https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 | ||||
|   ${env.lib_deps} | ||||
| # additional build flags for audioreactive | ||||
| AR_build_flags = -D USERMOD_AUDIOREACTIVE  | ||||
|   -D sqrt_internal=sqrtf ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster) | ||||
| AR_lib_deps = kosme/arduinoFFT @ 2.0.1 | ||||
|  | ||||
| [esp32_idf_V4] | ||||
| ;; build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 | ||||
| ;; 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. | ||||
| ;; | ||||
| ;; 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. | ||||
|  | ||||
| ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) | ||||
| platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.02/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.9 with IPv6 support, based on IDF 4.4.4 | ||||
| build_unflags = ${common.build_unflags} | ||||
| platform = espressif32@ ~6.3.2 | ||||
| platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0    ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) | ||||
| build_flags = -g | ||||
|   -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one | ||||
|   -DARDUINO_ARCH_ESP32 -DESP32 | ||||
|   ${esp32_all_variants.build_flags} | ||||
|   -D WLED_ENABLE_DMX_INPUT | ||||
|   -D CONFIG_ASYNC_TCP_USE_WDT=0 | ||||
|   -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 | ||||
| lib_deps = | ||||
|   ${esp32_all_variants.lib_deps} | ||||
|   https://github.com/someweisguy/esp_dmx.git#47db25d | ||||
|   https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 | ||||
|   ${env.lib_deps} | ||||
|  | ||||
| [esp32s2] | ||||
| ;; generic definitions for all ESP32-S2 boards | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| build_unflags = ${common.build_unflags} | ||||
| platform = espressif32@ ~6.3.2 | ||||
| platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0    ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) | ||||
| build_flags = -g | ||||
|   -DARDUINO_ARCH_ESP32 | ||||
|   -DARDUINO_ARCH_ESP32S2 | ||||
|   -DCONFIG_IDF_TARGET_ESP32S2=1 | ||||
|   -D CONFIG_ASYNC_TCP_USE_WDT=0 | ||||
|   -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 | ||||
|   -DCO | ||||
|   -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_idf_V4.build_flags} | ||||
| lib_deps = | ||||
|   ${esp32_idf_V4.lib_deps} | ||||
| board_build.partitions = ${esp32.default_partitions}   ;; default partioning for 4MB Flash - can be overridden in build envs | ||||
|   https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 | ||||
|   ${env.lib_deps} | ||||
|  | ||||
| [esp32c3] | ||||
| ;; generic definitions for all ESP32-C3 boards | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| build_unflags = ${common.build_unflags} | ||||
| platform = espressif32@ ~6.3.2 | ||||
| platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0    ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) | ||||
| build_flags = -g | ||||
|   -DARDUINO_ARCH_ESP32 | ||||
|   -DARDUINO_ARCH_ESP32C3 | ||||
|   -DCONFIG_IDF_TARGET_ESP32C3=1 | ||||
|   -D CONFIG_ASYNC_TCP_USE_WDT=0 | ||||
|   -DCO | ||||
|   -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_idf_V4.build_flags} | ||||
| 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 | ||||
|   https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 | ||||
|   ${env.lib_deps} | ||||
|  | ||||
| [esp32s3] | ||||
| ;; generic definitions for all ESP32-S3 boards | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| build_unflags = ${common.build_unflags} | ||||
| platform = espressif32@ ~6.3.2 | ||||
| platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0    ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) | ||||
| build_flags = -g | ||||
|   -DESP32 | ||||
|   -DARDUINO_ARCH_ESP32 | ||||
|   -DARDUINO_ARCH_ESP32S3 | ||||
|   -DCONFIG_IDF_TARGET_ESP32S3=1 | ||||
|   -D CONFIG_ASYNC_TCP_USE_WDT=0 | ||||
|   -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0 | ||||
|   -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_idf_V4.build_flags} | ||||
| lib_deps = | ||||
|   ${esp32_idf_V4.lib_deps} | ||||
| board_build.partitions = ${esp32.large_partitions}   ;; default partioning for 8MB flash - can be overridden in build envs | ||||
|   https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 | ||||
|   ${env.lib_deps} | ||||
|  | ||||
|  | ||||
| # ------------------------------------------------------------------------------ | ||||
| @@ -348,7 +346,6 @@ platform_packages = ${common.platform_packages} | ||||
| board_build.ldscript = ${common.ldscript_4m1m} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM2D | ||||
| lib_deps = ${esp8266.lib_deps} | ||||
| monitor_filters = esp8266_exception_decoder | ||||
|  | ||||
| @@ -358,15 +355,13 @@ extends = env:nodemcuv2 | ||||
| platform = ${esp8266.platform_compat} | ||||
| platform_packages = ${esp8266.platform_packages_compat} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM2D | ||||
| ;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 | ||||
|  | ||||
| [env:nodemcuv2_160] | ||||
| extends = env:nodemcuv2 | ||||
| board_build.f_cpu = 160000000L | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM2D | ||||
| custom_usermods = audioreactive | ||||
|   -D USERMOD_AUDIOREACTIVE | ||||
|  | ||||
| [env:esp8266_2m] | ||||
| board = esp_wroom_02 | ||||
| @@ -375,8 +370,6 @@ platform_packages = ${common.platform_packages} | ||||
| board_build.ldscript = ${common.ldscript_2m512k} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\" | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM2D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM1D | ||||
| lib_deps = ${esp8266.lib_deps} | ||||
|  | ||||
| [env:esp8266_2m_compat] | ||||
| @@ -385,16 +378,12 @@ extends = env:esp8266_2m | ||||
| platform = ${esp8266.platform_compat} | ||||
| platform_packages = ${esp8266.platform_packages_compat} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM1D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM2D | ||||
|  | ||||
| [env:esp8266_2m_160] | ||||
| extends = env:esp8266_2m | ||||
| board_build.f_cpu = 160000000L | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\" | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM1D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM2D | ||||
| custom_usermods = audioreactive | ||||
|   -D USERMOD_AUDIOREACTIVE | ||||
|  | ||||
| [env:esp01_1m_full] | ||||
| board = esp01_1m | ||||
| @@ -404,8 +393,6 @@ board_build.ldscript = ${common.ldscript_1m128k} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA | ||||
|   ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM1D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM2D | ||||
| lib_deps = ${esp8266.lib_deps} | ||||
|  | ||||
| [env:esp01_1m_full_compat] | ||||
| @@ -414,37 +401,35 @@ extends = env:esp01_1m_full | ||||
| platform = ${esp8266.platform_compat} | ||||
| platform_packages = ${esp8266.platform_packages_compat} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM1D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM2D | ||||
|  | ||||
| [env:esp01_1m_full_160] | ||||
| extends = env:esp01_1m_full | ||||
| board_build.f_cpu = 160000000L | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01_160\" -D WLED_DISABLE_OTA | ||||
|   -D USERMOD_AUDIOREACTIVE | ||||
|   ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM1D | ||||
|   -D WLED_DISABLE_PARTICLESYSTEM2D | ||||
| custom_usermods = audioreactive | ||||
|  | ||||
| [env:esp32dev] | ||||
| board = esp32dev | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| platform = ${esp32.platform} | ||||
| platform_packages = ${esp32.platform_packages} | ||||
| 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} | ||||
| build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET | ||||
|   ${esp32.AR_build_flags} | ||||
| lib_deps = ${esp32.lib_deps} | ||||
|   ${esp32.AR_lib_deps} | ||||
| monitor_filters = esp32_exception_decoder | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
| board_build.flash_mode = dio | ||||
|  | ||||
| [env:esp32dev_8M] | ||||
| board = esp32dev | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| custom_usermods = audioreactive | ||||
| platform_packages = ${esp32_idf_V4.platform_packages} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET | ||||
|   ${esp32.AR_build_flags} | ||||
| lib_deps = ${esp32_idf_V4.lib_deps} | ||||
|   ${esp32.AR_lib_deps} | ||||
| monitor_filters = esp32_exception_decoder | ||||
| board_build.partitions = ${esp32.large_partitions} | ||||
| board_upload.flash_size = 8MB | ||||
| @@ -455,10 +440,12 @@ board_upload.maximum_size = 8388608 | ||||
| [env:esp32dev_16M] | ||||
| board = esp32dev | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| custom_usermods = audioreactive | ||||
| platform_packages = ${esp32_idf_V4.platform_packages} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET | ||||
|   ${esp32.AR_build_flags} | ||||
| lib_deps = ${esp32_idf_V4.lib_deps} | ||||
|   ${esp32.AR_lib_deps} | ||||
| monitor_filters = esp32_exception_decoder | ||||
| board_build.partitions = ${esp32.extreme_partitions} | ||||
| board_upload.flash_size = 16MB | ||||
| @@ -466,34 +453,53 @@ 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} | ||||
| ;build_unflags = ${common.build_unflags} | ||||
| ;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_audioreactive\" #-D WLED_DISABLE_BROWNOUT_DET | ||||
| ;  ${esp32.AR_build_flags} | ||||
| ;lib_deps = ${esp32.lib_deps} | ||||
| ;  ${esp32.AR_lib_deps} | ||||
| ;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_idf_V4.platform} | ||||
| platform = ${esp32.platform} | ||||
| platform_packages = ${esp32.platform_packages} | ||||
| upload_speed = 921600 | ||||
| custom_usermods = audioreactive | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 | ||||
| ;  -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only | ||||
|   ${esp32.AR_build_flags} | ||||
| lib_deps = ${esp32.lib_deps} | ||||
|   ${esp32.AR_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} | ||||
| platform_packages = ${esp32_idf_V4.platform_packages} | ||||
| board = ttgo-t7-v14-mini32 | ||||
| board_build.f_flash = 80000000L | ||||
| board_build.flash_mode = qio | ||||
| board_build.partitions = ${esp32.extended_partitions} | ||||
| custom_usermods = audioreactive | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\" | ||||
|   -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html | ||||
|   -D DATA_PINS=25 | ||||
|   ${esp32.AR_build_flags} | ||||
| lib_deps = ${esp32_idf_V4.lib_deps} | ||||
|    | ||||
|   ${esp32.AR_lib_deps} | ||||
|  | ||||
| [env:esp32c3dev] | ||||
| extends = esp32c3 | ||||
| platform = ${esp32c3.platform} | ||||
| platform_packages = ${esp32c3.platform_packages} | ||||
| framework = arduino | ||||
| board = esp32-c3-devkitm-1 | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
| @@ -511,15 +517,17 @@ lib_deps = ${esp32c3.lib_deps} | ||||
| board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support | ||||
| board_build.arduino.memory_type = qio_opi     ;; use with PSRAM: 8MB or 16MB | ||||
| platform = ${esp32s3.platform} | ||||
| platform_packages = ${esp32s3.platform_packages} | ||||
| upload_speed = 921600 | ||||
| custom_usermods = audioreactive | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\" | ||||
|   -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 | ||||
|   ;-D ARDUINO_USB_CDC_ON_BOOT=0  ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip | ||||
|   -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1      ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") | ||||
|   -DBOARD_HAS_PSRAM | ||||
|   ${esp32.AR_build_flags} | ||||
| lib_deps = ${esp32s3.lib_deps} | ||||
|   ${esp32.AR_lib_deps} | ||||
| board_build.partitions = ${esp32.extreme_partitions} | ||||
| board_upload.flash_size = 16MB | ||||
| board_upload.maximum_size = 16777216 | ||||
| @@ -532,15 +540,17 @@ monitor_filters = esp32_exception_decoder | ||||
| board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support | ||||
| board_build.arduino.memory_type = qio_opi     ;; use with PSRAM: 8MB or 16MB | ||||
| platform = ${esp32s3.platform} | ||||
| platform_packages = ${esp32s3.platform_packages} | ||||
| upload_speed = 921600 | ||||
| custom_usermods = audioreactive | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\" | ||||
|   -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 | ||||
|   ;-D ARDUINO_USB_CDC_ON_BOOT=0  ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip | ||||
|   -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1      ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") | ||||
|   -DBOARD_HAS_PSRAM | ||||
|   ${esp32.AR_build_flags} | ||||
| lib_deps = ${esp32s3.lib_deps} | ||||
|   ${esp32.AR_lib_deps} | ||||
| board_build.partitions = ${esp32.large_partitions} | ||||
| board_build.f_flash = 80000000L | ||||
| board_build.flash_mode = qio | ||||
| @@ -550,10 +560,10 @@ monitor_filters = esp32_exception_decoder | ||||
| ;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 | ||||
| ;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) | ||||
| platform = ${esp32s3.platform} | ||||
| platform_packages = ${esp32s3.platform_packages} | ||||
| board = esp32s3camlcd ;; this is the only standard board with "opi_opi" | ||||
| board_build.arduino.memory_type = opi_opi | ||||
| upload_speed = 921600 | ||||
| custom_usermods = audioreactive | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\" | ||||
|   -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 | ||||
| @@ -563,8 +573,10 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= | ||||
|   -D LEDPIN=38 -D DATA_PINS=38 ;; buildin WS2812b LED | ||||
|   -D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1 | ||||
|   -D WLED_DEBUG | ||||
|   ${esp32.AR_build_flags} | ||||
|   -D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4  ;; I2S mic | ||||
| lib_deps = ${esp32s3.lib_deps} | ||||
|   ${esp32.AR_lib_deps} | ||||
|  | ||||
| board_build.partitions = ${esp32.extreme_partitions} | ||||
| board_upload.flash_size = 16MB | ||||
| @@ -575,15 +587,17 @@ monitor_filters = esp32_exception_decoder | ||||
| ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) | ||||
| board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM  | ||||
| platform = ${esp32s3.platform} | ||||
| platform_packages = ${esp32s3.platform_packages} | ||||
| upload_speed = 921600 | ||||
| custom_usermods = audioreactive | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" | ||||
|   -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1      ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") | ||||
|   -DBOARD_HAS_PSRAM | ||||
|   -DLOLIN_WIFI_FIX ; seems to work much better with this | ||||
|   -D WLED_WATCHDOG_TIMEOUT=0 | ||||
|   ${esp32.AR_build_flags} | ||||
| lib_deps = ${esp32s3.lib_deps} | ||||
|   ${esp32.AR_lib_deps} | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
| board_build.f_flash = 80000000L | ||||
| board_build.flash_mode = qio | ||||
| @@ -591,11 +605,11 @@ monitor_filters = esp32_exception_decoder | ||||
|  | ||||
| [env:lolin_s2_mini] | ||||
| platform = ${esp32s2.platform} | ||||
| platform_packages = ${esp32s2.platform_packages} | ||||
| board = lolin_s2_mini | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
| board_build.flash_mode = qio | ||||
| board_build.f_flash = 80000000L | ||||
| custom_usermods = audioreactive | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\" | ||||
|   -DARDUINO_USB_CDC_ON_BOOT=1 | ||||
| @@ -604,6 +618,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= | ||||
|   -DBOARD_HAS_PSRAM | ||||
|   -DLOLIN_WIFI_FIX ; seems to work much better with this | ||||
|   -D WLED_WATCHDOG_TIMEOUT=0 | ||||
|   -D CONFIG_ASYNC_TCP_USE_WDT=0 | ||||
|   -D DATA_PINS=16 | ||||
|   -D HW_PIN_SCL=35 | ||||
|   -D HW_PIN_SDA=33 | ||||
| @@ -611,17 +626,6 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= | ||||
|   -D HW_PIN_DATASPI=11 | ||||
|   -D HW_PIN_MISOSPI=9 | ||||
| ;  -D STATUSLED=15 | ||||
|   ${esp32.AR_build_flags} | ||||
| lib_deps = ${esp32s2.lib_deps} | ||||
|  | ||||
|  | ||||
| [env:usermods] | ||||
| board = esp32dev | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_USERMODS\" | ||||
|   -DTOUCH_CS=9 | ||||
| lib_deps = ${esp32_idf_V4.lib_deps} | ||||
| monitor_filters = esp32_exception_decoder | ||||
| board_build.flash_mode = dio | ||||
| custom_usermods = *   ; Expands to all usermods in usermods folder | ||||
| board_build.partitions = ${esp32.extreme_partitions}  ; We're gonna need a bigger boat | ||||
|   ${esp32.AR_lib_deps} | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| # Please visit documentation: https://docs.platformio.org/page/projectconf.html | ||||
|  | ||||
| [platformio] | ||||
| default_envs = WLED_generic8266_1M, esp32dev_V4_dio80  # put the name(s) of your own build environment here. You can define as many as you need | ||||
| default_envs = WLED_tasmota_1M  # define as many as you need | ||||
|  | ||||
| #---------- | ||||
| # SAMPLE | ||||
| @@ -28,12 +28,13 @@ 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 | ||||
| ;  ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE | ||||
| ;  ${esp32.AR_lib_deps} ;; used for USERMOD_AUDIOREACTIVE | ||||
| ;  bitbank2/PNGdec@^1.0.1 ;; used for POV display uncomment following | ||||
|  | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} | ||||
| ; | ||||
| ; *** To use the below defines/overrides, copy and paste each onto its own line just below build_flags in the section above. | ||||
| ; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above. | ||||
| ;  | ||||
| ; Set a release name that may be used to distinguish required binary for flashing | ||||
| ;   -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\" | ||||
| @@ -140,8 +141,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} | ||||
| ;   -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering) | ||||
| ; | ||||
| ; Use Audioreactive usermod and configure I2S microphone | ||||
| ;   ${esp32.AR_build_flags} ;; default flags required to properly configure ArduinoFFT | ||||
| ;   ;; don't forget to add ArduinoFFT to your libs_deps: ${esp32.AR_lib_deps} | ||||
| ;   -D USERMOD_AUDIOREACTIVE | ||||
| ;   -D AUDIOPIN=-1 | ||||
| ;   -D DMTYPE=1     # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM | ||||
| ;   -D I2S_SDPIN=36 | ||||
| @@ -157,22 +157,17 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} | ||||
| ;   -D USERMOD_POV_DISPLAY | ||||
| ; Use built-in or custom LED as a status indicator (assumes LED is connected to GPIO16) | ||||
| ;   -D STATUSLED=16 | ||||
| ; | ||||
| ;    | ||||
| ; set the name of the module - make sure there is a quote-backslash-quote before the name and a backslash-quote-quote after the name | ||||
| ;   -D SERVERNAME="\"WLED\"" | ||||
| ; | ||||
| ;    | ||||
| ; set the number of LEDs | ||||
| ;   -D PIXEL_COUNTS=30 | ||||
| ;   -D DEFAULT_LED_COUNT=30 | ||||
| ; or this for multiple outputs | ||||
| ;   -D PIXEL_COUNTS=30,30 | ||||
| ; | ||||
| ; set the default LED type | ||||
| ;   -D LED_TYPES=22    # see const.h (TYPE_xxxx) | ||||
| ; or this for multiple outputs | ||||
| ;   -D LED_TYPES=TYPE_SK6812_RGBW,TYPE_WS2812_RGB | ||||
| ; | ||||
| ; set default color order of your led strip | ||||
| ;   -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB | ||||
| ;   -D DEFAULT_LED_TYPE=22    # see const.h (TYPE_xxxx) | ||||
| ; | ||||
| ; set milliampere limit when using ESP power pin (or inadequate PSU) to power LEDs | ||||
| ;   -D ABL_MILLIAMPS_DEFAULT=850 | ||||
| @@ -181,6 +176,9 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} | ||||
| ; enable IR by setting remote type | ||||
| ;   -D IRTYPE=0   # 0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote | ||||
| ;    | ||||
| ; set default color order of your led strip | ||||
| ;   -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB | ||||
| ; | ||||
| ; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues) | ||||
| ;   -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue   # needed only for classic ESP32 rev.1 | ||||
| ; | ||||
| @@ -238,13 +236,14 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=1 -D WLE | ||||
| lib_deps = ${esp8266.lib_deps} | ||||
|  | ||||
| [env:esp32dev_qio80] | ||||
| extends = env:esp32dev  # we want to extend the existing esp32dev environment (and define only updated options) | ||||
| board = esp32dev | ||||
| platform = ${esp32.platform} | ||||
| platform_packages = ${esp32.platform_packages} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32.build_flags} #-D WLED_DISABLE_BROWNOUT_DET | ||||
|   ${esp32.AR_build_flags} ;; optional - includes USERMOD_AUDIOREACTIVE | ||||
| lib_deps = ${esp32.lib_deps} | ||||
|   ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE | ||||
| monitor_filters = esp32_exception_decoder | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
| board_build.f_flash = 80000000L | ||||
| board_build.flash_mode = qio | ||||
|  | ||||
| @@ -252,25 +251,26 @@ board_build.flash_mode = qio | ||||
| ;; experimental ESP32 env using ESP-IDF V4.4.x | ||||
| ;; Warning: this build environment is not stable!! | ||||
| ;; please erase your device before installing. | ||||
| extends = esp32_idf_V4  # based on newer "esp-idf V4" platform environment | ||||
| board = esp32dev | ||||
| platform = ${esp32_idf_V4.platform} | ||||
| platform_packages = ${esp32_idf_V4.platform_packages} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags}  ${esp32_idf_V4.build_flags} #-D WLED_DISABLE_BROWNOUT_DET | ||||
|   ${esp32.AR_build_flags} ;; includes USERMOD_AUDIOREACTIVE | ||||
| lib_deps = ${esp32_idf_V4.lib_deps} | ||||
|   ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE | ||||
| monitor_filters = esp32_exception_decoder | ||||
| board_build.partitions = ${esp32.default_partitions}  ;; if you get errors about "out of program space", change this to ${esp32.extended_partitions} or even ${esp32.big_partitions} | ||||
| board_build.partitions = ${esp32_idf_V4.default_partitions} | ||||
| board_build.f_flash = 80000000L | ||||
| board_build.flash_mode = dio | ||||
|  | ||||
| [env:esp32s2_saola] | ||||
| extends = esp32s2 | ||||
| board = esp32-s2-saola-1 | ||||
| platform = ${esp32s2.platform} | ||||
| platform_packages = ${esp32s2.platform_packages} | ||||
| framework = arduino | ||||
| board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv | ||||
| board_build.flash_mode = qio | ||||
| upload_speed = 460800 | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32s2.build_flags} | ||||
|   ;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work | ||||
|   -DARDUINO_USB_CDC_ON_BOOT=1 | ||||
| @@ -279,7 +279,7 @@ lib_deps = ${esp32s2.lib_deps} | ||||
| [env:esp32s3dev_8MB_PSRAM_qspi] | ||||
| ;; ESP32-TinyS3 development board, with 8MB FLASH and PSRAM (memory_type: qio_qspi) | ||||
| extends = env:esp32s3dev_8MB_PSRAM_opi | ||||
| ;board = um_tinys3 ;    -> needs workaround from https://github.com/wled-dev/WLED/pull/2905#issuecomment-1328049860 | ||||
| ;board = um_tinys3 ;    -> needs workaround from https://github.com/Aircoookie/WLED/pull/2905#issuecomment-1328049860 | ||||
| board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support | ||||
| board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or  4MB | ||||
|  | ||||
| @@ -307,7 +307,7 @@ platform = ${common.platform_wled_default} | ||||
| platform_packages = ${common.platform_packages} | ||||
| board_build.ldscript = ${common.ldscript_4m1m} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_USE_SHOJO_PCB ;; NB: WLED_USE_SHOJO_PCB is not used anywhere in the source code. Not sure why its needed. | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_USE_SHOJO_PCB | ||||
| lib_deps = ${esp8266.lib_deps} | ||||
|  | ||||
| [env:d1_mini_debug] | ||||
| @@ -362,48 +362,35 @@ board_upload.flash_size = 2MB | ||||
| board_upload.maximum_size = 2097152 | ||||
|  | ||||
| [env:wemos_shield_esp32] | ||||
| extends = esp32              ;; use default esp32 platform | ||||
| board = esp32dev | ||||
| platform = ${esp32.platform} | ||||
| platform_packages = ${esp32.platform_packages} | ||||
| upload_speed = 460800 | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32.build_flags} | ||||
|   -D WLED_RELEASE_NAME=\"ESP32_wemos_shield\" | ||||
|   -D DATA_PINS=16 | ||||
|   -D RLYPIN=19 | ||||
|   -D BTNPIN=17 | ||||
|   -D IRPIN=18 | ||||
|   -UWLED_USE_MY_CONFIG | ||||
|   -D UWLED_USE_MY_CONFIG | ||||
|   -D USERMOD_DALLASTEMPERATURE | ||||
|   -D USERMOD_FOUR_LINE_DISPLAY | ||||
|   -D TEMPERATURE_PIN=23 | ||||
|   ${esp32.AR_build_flags} ;; includes USERMOD_AUDIOREACTIVE | ||||
|   -D USERMOD_AUDIOREACTIVE | ||||
| lib_deps = ${esp32.lib_deps} | ||||
|   OneWire@~2.3.5          ;; needed for USERMOD_DALLASTEMPERATURE | ||||
|   olikraus/U8g2 @ ^2.28.8 ;; needed for USERMOD_FOUR_LINE_DISPLAY | ||||
|   ${esp32.AR_lib_deps}    ;; needed for USERMOD_AUDIOREACTIVE | ||||
|   OneWire@~2.3.5 | ||||
|   olikraus/U8g2 @ ^2.28.8 | ||||
|   https://github.com/blazoncek/arduinoFFT.git | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
|  | ||||
| [env:esp32_pico-D4] | ||||
| extends = esp32              ;; use default esp32 platform | ||||
| board = pico32               ;; pico32-D4 is different from the standard esp32dev | ||||
|                              ;; hardware details from https://github.com/srg74/WLED-ESP32-pico | ||||
| build_flags = ${common.build_flags} ${esp32.build_flags} | ||||
|   -D WLED_RELEASE_NAME=\"pico32-D4\" -D SERVERNAME='"WLED-pico32"' | ||||
|   -D WLED_DISABLE_ADALIGHT   ;; no serial-to-USB chip on this board - better to disable serial protocols | ||||
|   -D DATA_PINS=2,18          ;; LED pins | ||||
|   -D RLYPIN=19 -D BTNPIN=0 -D IRPIN=-1 ;; no default pin for IR | ||||
|   ${esp32.AR_build_flags}    ;; include USERMOD_AUDIOREACTIVE | ||||
|   -D UM_AUDIOREACTIVE_ENABLE ;; enable AR by default | ||||
|   ;; Audioreactive settings for on-board microphone (ICS-43432) | ||||
|   -D SR_DMTYPE=1 -D I2S_SDPIN=25 -D I2S_WSPIN=15 -D I2S_CKPIN=14 | ||||
|   -D SR_SQUELCH=5 -D SR_GAIN=30 | ||||
| lib_deps = ${esp32.lib_deps} | ||||
|   ${esp32.AR_lib_deps}       ;; needed for USERMOD_AUDIOREACTIVE | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
| board_build.f_flash = 80000000L | ||||
|  | ||||
| [env:m5atom] | ||||
| extends = env:esp32dev  # we want to extend the existing esp32dev environment (and define only updated options) | ||||
| board = esp32dev | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp32.build_flags} -D DATA_PINS=27 -D BTNPIN=39 | ||||
| lib_deps = ${esp32.lib_deps} | ||||
| platform = ${esp32.platform} | ||||
| platform_packages = ${esp32.platform_packages} | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
|  | ||||
| [env:sp501e] | ||||
| board = esp_wroom_02 | ||||
| @@ -426,7 +413,7 @@ platform_packages = ${common.platform_packages} | ||||
| board_build.ldscript = ${common.ldscript_2m512k} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5 | ||||
|                                             -D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 | ||||
|                                             -D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 | ||||
| lib_deps = ${esp8266.lib_deps} | ||||
|  | ||||
| [env:Athom_15w_RGBCW]        ;15w bulb | ||||
| @@ -436,7 +423,7 @@ platform_packages = ${common.platform_packages} | ||||
| board_build.ldscript = ${common.ldscript_2m512k} | ||||
| build_unflags = ${common.build_unflags} | ||||
| build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13 | ||||
|                                             -D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT | ||||
|                                             -D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT | ||||
| lib_deps = ${esp8266.lib_deps} | ||||
|  | ||||
| [env:Athom_3Pin_Controller]        ;small controller with only data | ||||
| @@ -502,15 +489,17 @@ lib_deps = ${esp8266.lib_deps} | ||||
| # EleksTube-IPS | ||||
| # ------------------------------------------------------------------------------ | ||||
| [env:elekstube_ips] | ||||
| extends = esp32              ;; use default esp32 platform | ||||
| board = esp32dev | ||||
| platform = ${esp32.platform} | ||||
| platform_packages = ${esp32.platform_packages} | ||||
| upload_speed = 921600 | ||||
| custom_usermods = ${env:esp32dev.custom_usermods} RTC EleksTube_IPS | ||||
| build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED | ||||
|   -D USERMOD_RTC | ||||
|   -D USERMOD_ELEKSTUBE_IPS | ||||
|   -D DATA_PINS=12 | ||||
|   -D RLYPIN=27 | ||||
|   -D BTNPIN=34 | ||||
|   -D PIXEL_COUNTS=6 | ||||
|   -D DEFAULT_LED_COUNT=6 | ||||
|   # Display config | ||||
|   -D ST7789_DRIVER | ||||
|   -D TFT_WIDTH=135 | ||||
| @@ -524,14 +513,7 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOU | ||||
|   -D SPI_FREQUENCY=40000000 | ||||
|   -D USER_SETUP_LOADED | ||||
| monitor_filters = esp32_exception_decoder | ||||
|  | ||||
| # ------------------------------------------------------------------------------ | ||||
| # Usermod examples | ||||
| # ------------------------------------------------------------------------------ | ||||
|  | ||||
| # 433MHz RF remote example for esp32dev | ||||
| [env:esp32dev_usermod_RF433] | ||||
| extends = env:esp32dev | ||||
| build_flags = ${env:esp32dev.build_flags} -D USERMOD_RF433 | ||||
| lib_deps = ${env:esp32dev.lib_deps} | ||||
|   sui77/rc-switch @ 2.6.4 | ||||
| lib_deps = | ||||
|   ${esp32.lib_deps} | ||||
|   TFT_eSPI @ ^2.3.70 | ||||
| board_build.partitions = ${esp32.default_partitions} | ||||
|   | ||||
							
								
								
									
										17
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								readme.md
									
									
									
									
									
								
							| @@ -1,20 +1,18 @@ | ||||
| <p align="center"> | ||||
|   <img src="/images/wled_logo_akemi.png"> | ||||
|   <a href="https://github.com/wled-dev/WLED/releases"><img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a> | ||||
|   <a href="https://raw.githubusercontent.com/wled-dev/WLED/main/LICENSE"><img src="https://img.shields.io/github/license/wled-dev/wled?color=blue&style=flat-square"></a> | ||||
|   <a href="https://github.com/Aircoookie/WLED/releases"><img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a> | ||||
|   <a href="https://raw.githubusercontent.com/Aircoookie/WLED/master/LICENSE"><img src="https://img.shields.io/github/license/Aircoookie/wled?color=blue&style=flat-square"></a> | ||||
|   <a href="https://wled.discourse.group"><img src="https://img.shields.io/discourse/topics?colorB=blue&label=forum&server=https%3A%2F%2Fwled.discourse.group%2F&style=flat-square"></a> | ||||
|   <a href="https://discord.gg/QAh7wJHrRM"><img src="https://img.shields.io/discord/473448917040758787.svg?colorB=blue&label=discord&style=flat-square"></a> | ||||
|   <a href="https://kno.wled.ge"><img src="https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square"></a> | ||||
|   <a href="https://github.com/Aircoookie/WLED-App"><img src="https://img.shields.io/badge/app-wled-blue.svg?style=flat-square"></a> | ||||
|   <a href="https://gitpod.io/#https://github.com/wled-dev/WLED"><img src="https://img.shields.io/badge/Gitpod-ready--to--code-blue?style=flat-square&logo=gitpod"></a> | ||||
|   <a href="https://gitpod.io/#https://github.com/Aircoookie/WLED"><img src="https://img.shields.io/badge/Gitpod-ready--to--code-blue?style=flat-square&logo=gitpod"></a> | ||||
|  | ||||
|   </p> | ||||
|  | ||||
| # Welcome to WLED! ✨ | ||||
| # Welcome to my project 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) | ||||
| A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! | ||||
|  | ||||
| ## ⚙️ Features | ||||
| - WS2812FX library with more than 100 special effects   | ||||
| @@ -23,7 +21,7 @@ Originally created by [Aircoookie](https://github.com/Aircoookie) | ||||
| - Segments to set different effects and colors to user defined parts of the LED string   | ||||
| - Settings page - configuration via the network   | ||||
| - Access Point and station mode - automatic failsafe AP   | ||||
| - [Up to 10 LED outputs](https://kno.wled.ge/features/multi-strip/#esp32) per instance | ||||
| - Up to 10 LED outputs per instance | ||||
| - Support for RGBW strips   | ||||
| - Up to 250 user presets to save and load colors/effects easily, supports cycling through them.   | ||||
| - Presets can be used to automatically execute API calls   | ||||
| @@ -34,7 +32,7 @@ Originally created by [Aircoookie](https://github.com/Aircoookie) | ||||
| - 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=ca.cgagnier.wlednativeandroid) and [iOS](https://apps.apple.com/gb/app/wled-native/id6446207239) | ||||
| - 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) | ||||
| - JSON and HTTP request APIs   | ||||
| - MQTT    | ||||
| - E1.31, Art-Net, DDP and TPM2.net | ||||
| @@ -65,7 +63,6 @@ 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! | ||||
|  | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| platformio>=6.1.17 | ||||
| platformio | ||||
|   | ||||
| @@ -1,26 +1,28 @@ | ||||
| # | ||||
| # This file is autogenerated by pip-compile with Python 3.11 | ||||
| # This file is autogenerated by pip-compile with Python 3.12 | ||||
| # by the following command: | ||||
| # | ||||
| #    pip-compile requirements.in | ||||
| #    pip-compile | ||||
| # | ||||
| ajsonrpc==1.2.0 | ||||
|     # via platformio | ||||
| anyio==4.8.0 | ||||
| anyio==4.6.0 | ||||
|     # via starlette | ||||
| bottle==0.13.2 | ||||
| bottle==0.13.1 | ||||
|     # via platformio | ||||
| certifi==2025.1.31 | ||||
| certifi==2024.8.30 | ||||
|     # via requests | ||||
| charset-normalizer==3.4.1 | ||||
| charset-normalizer==3.3.2 | ||||
|     # via requests | ||||
| click==8.1.8 | ||||
| click==8.1.7 | ||||
|     # via | ||||
|     #   platformio | ||||
|     #   uvicorn | ||||
| colorama==0.4.6 | ||||
|     # via platformio | ||||
| h11==0.16.0 | ||||
|     # via | ||||
|     #   click | ||||
|     #   platformio | ||||
| h11==0.14.0 | ||||
|     # via | ||||
|     #   uvicorn | ||||
|     #   wsproto | ||||
| @@ -28,31 +30,29 @@ idna==3.10 | ||||
|     # via | ||||
|     #   anyio | ||||
|     #   requests | ||||
| marshmallow==3.26.1 | ||||
| marshmallow==3.22.0 | ||||
|     # via platformio | ||||
| packaging==24.2 | ||||
| packaging==24.1 | ||||
|     # via marshmallow | ||||
| platformio==6.1.17 | ||||
| platformio==6.1.16 | ||||
|     # via -r requirements.in | ||||
| pyelftools==0.32 | ||||
| pyelftools==0.31 | ||||
|     # via platformio | ||||
| pyserial==3.5 | ||||
|     # via platformio | ||||
| requests==2.32.4 | ||||
| requests==2.32.3 | ||||
|     # via platformio | ||||
| semantic-version==2.10.0 | ||||
|     # via platformio | ||||
| sniffio==1.3.1 | ||||
|     # via anyio | ||||
| starlette==0.45.3 | ||||
| starlette==0.39.1 | ||||
|     # via platformio | ||||
| tabulate==0.9.0 | ||||
|     # via platformio | ||||
| typing-extensions==4.12.2 | ||||
|     # via anyio | ||||
| urllib3==2.5.0 | ||||
| urllib3==2.2.3 | ||||
|     # via requests | ||||
| uvicorn==0.34.0 | ||||
| uvicorn==0.30.6 | ||||
|     # via platformio | ||||
| wsproto==1.2.0 | ||||
|     # via platformio | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -17,7 +17,7 @@ | ||||
|  | ||||
| const fs = require("node:fs"); | ||||
| const path = require("path"); | ||||
| const inline = require("web-resource-inliner"); | ||||
| const inliner = require("inliner"); | ||||
| const zlib = require("node:zlib"); | ||||
| const CleanCSS = require("clean-css"); | ||||
| const minifyHtml = require("html-minifier-terser").minify; | ||||
| @@ -89,7 +89,7 @@ function adoptVersionAndRepo(html) { | ||||
|     repoUrl = repoUrl.replace(/^git\+/, ""); | ||||
|     repoUrl = repoUrl.replace(/\.git$/, ""); | ||||
|     html = html.replaceAll("https://github.com/atuline/WLED", repoUrl); | ||||
|     html = html.replaceAll("https://github.com/wled-dev/WLED", repoUrl); | ||||
|     html = html.replaceAll("https://github.com/Aircoookie/WLED", repoUrl); | ||||
|   } | ||||
|   let version = packageJson.version; | ||||
|   if (version) { | ||||
| @@ -128,26 +128,21 @@ async function minify(str, type = "plain") { | ||||
|  | ||||
| async function writeHtmlGzipped(sourceFile, resultFile, page) { | ||||
|   console.info("Reading " + sourceFile); | ||||
|   inline.html({ | ||||
|     fileContent: fs.readFileSync(sourceFile, "utf8"), | ||||
|     relativeTo: path.dirname(sourceFile), | ||||
|     strict: true, | ||||
|   }, | ||||
|     async function (error, html) { | ||||
|       if (error) throw error; | ||||
|   new inliner(sourceFile, async function (error, html) { | ||||
|     if (error) throw error; | ||||
|  | ||||
|       html = adoptVersionAndRepo(html); | ||||
|       const originalLength = html.length; | ||||
|       html = await minify(html, "html-minify"); | ||||
|       const result = zlib.gzipSync(html, { level: zlib.constants.Z_BEST_COMPRESSION }); | ||||
|       console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes"); | ||||
|       const array = hexdump(result); | ||||
|       let src = singleHeader; | ||||
|       src += `const uint16_t PAGE_${page}_L = ${result.length};\n`; | ||||
|       src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`; | ||||
|       console.info("Writing " + resultFile); | ||||
|       fs.writeFileSync(resultFile, src); | ||||
|     }); | ||||
|     html = adoptVersionAndRepo(html); | ||||
|     const originalLength = html.length; | ||||
|     html = await minify(html, "html-minify"); | ||||
|     const result = zlib.gzipSync(html, { level: zlib.constants.Z_BEST_COMPRESSION }); | ||||
|     console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes"); | ||||
|     const array = hexdump(result); | ||||
|     let src = singleHeader; | ||||
|     src += `const uint16_t PAGE_${page}_L = ${result.length};\n`; | ||||
|     src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`; | ||||
|     console.info("Writing " + resultFile); | ||||
|     fs.writeFileSync(resultFile, src); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| async function specToChunk(srcDir, s) { | ||||
|   | ||||
| @@ -27,7 +27,6 @@ read -a JSON_TINY_TARGETS <<< $(replicate "json/nodes") | ||||
| read -a JSON_SMALL_TARGETS <<< $(replicate "json/info") | ||||
| read -a JSON_LARGE_TARGETS <<< $(replicate "json/si") | ||||
| read -a JSON_LARGER_TARGETS <<< $(replicate "json/fxdata") | ||||
| read -a INDEX_TARGETS <<< $(replicate "") | ||||
|  | ||||
| # Expand target URLS to full arguments for curl | ||||
| TARGETS=(${TARGET_STR[@]}) | ||||
|   | ||||
							
								
								
									
										286
									
								
								tools/wled-tools
									
									
									
									
									
								
							
							
						
						
									
										286
									
								
								tools/wled-tools
									
									
									
									
									
								
							| @@ -1,286 +0,0 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| # WLED Tools | ||||
| # A utility for managing WLED devices in a local network | ||||
| # https://github.com/wled/WLED | ||||
|  | ||||
| # Color Definitions | ||||
| GREEN="\e[32m" | ||||
| RED="\e[31m" | ||||
| BLUE="\e[34m" | ||||
| YELLOW="\e[33m" | ||||
| RESET="\e[0m" | ||||
|  | ||||
| # Logging function | ||||
| log() { | ||||
|     local category="$1" | ||||
|     local color="$2" | ||||
|     local text="$3" | ||||
|  | ||||
|     if [ "$quiet" = true ]; then | ||||
|         return | ||||
|     fi | ||||
|  | ||||
|     if [ -t 1 ]; then  # Check if output is a terminal | ||||
|         echo -e "${color}[${category}]${RESET} ${text}" | ||||
|     else | ||||
|         echo "[${category}] ${text}" | ||||
|     fi | ||||
| } | ||||
|  | ||||
| # Generic curl handler function | ||||
| curl_handler() { | ||||
|     local command="$1" | ||||
|     local hostname="$2" | ||||
|  | ||||
|     response=$($command -w "%{http_code}" -o /dev/null) | ||||
|     curl_exit_code=$? | ||||
|  | ||||
|     if [ "$response" -ge 200 ] && [ "$response" -lt 300 ]; then | ||||
|         return 0 | ||||
|     elif [ $curl_exit_code -ne 0 ]; then | ||||
|         log "ERROR" "$RED" "Connection error during request to $hostname (curl exit code: $curl_exit_code)." | ||||
|         return 1 | ||||
|     elif [ "$response" -ge 400 ]; then | ||||
|         log "ERROR" "$RED" "Server error during request to $hostname (HTTP status code: $response)." | ||||
|         return 2 | ||||
|     else | ||||
|         log "ERROR" "$RED" "Unexpected response from $hostname (HTTP status code: $response)." | ||||
|         return 3 | ||||
|     fi | ||||
| } | ||||
|  | ||||
| # Print help message | ||||
| show_help() { | ||||
|     cat << EOF | ||||
| Usage: wled-tools.sh [OPTIONS] COMMAND [ARGS...] | ||||
|  | ||||
| Options: | ||||
|   -h, --help              Show this help message and exit. | ||||
|   -t, --target <IP/Host>  Specify a single WLED device by IP address or hostname. | ||||
|   -D, --discover          Discover multiple WLED devices using mDNS. | ||||
|   -d, --directory <Path>  Specify a directory for saving backups (default: working directory). | ||||
|   -f, --firmware <File>   Specify the firmware file for updating devices. | ||||
|   -q, --quiet             Suppress logging output (also makes discover output hostnames only). | ||||
|  | ||||
| Commands: | ||||
|   backup      Backup the current state of a WLED device or multiple discovered devices. | ||||
|   update      Update the firmware of a WLED device or multiple discovered devices. | ||||
|   discover    Discover WLED devices using mDNS and list their IP addresses and names. | ||||
|  | ||||
| Examples: | ||||
|   # Discover all WLED devices on the network | ||||
|   ./wled-tools discover | ||||
|  | ||||
|   # Backup a specific WLED device | ||||
|   ./wled-tools -t 192.168.1.100 backup | ||||
|  | ||||
|   # Backup all discovered WLED devices to a specific directory | ||||
|   ./wled-tools -D -d /path/to/backups backup | ||||
|  | ||||
|   # Update firmware on all discovered WLED devices | ||||
|   ./wled-tools -D -f /path/to/firmware.bin update | ||||
|  | ||||
| EOF | ||||
| } | ||||
|  | ||||
| # Discover devices using mDNS | ||||
| discover_devices() {   | ||||
|     if ! command -v avahi-browse &> /dev/null; then   | ||||
|         log "ERROR" "$RED" "'avahi-browse' is required but not installed, please install avahi-utils using your preferred package manager." | ||||
|         exit 1   | ||||
|     fi   | ||||
|  | ||||
|     # Map avahi responses to strings seperated by 0x1F (unit separator) | ||||
|     mapfile -t raw_devices < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7"\x1F"$8"\x1F"$9}')   | ||||
|  | ||||
|     local devices_array=()   | ||||
|     for device in "${raw_devices[@]}"; do   | ||||
|         IFS=$'\x1F' read -r hostname address port <<< "$device"   | ||||
|         devices_array+=("$hostname" "$address" "$port")   | ||||
|     done   | ||||
|  | ||||
|     echo "${devices_array[@]}"   | ||||
| }   | ||||
|  | ||||
| # Backup one device | ||||
| backup_one() { | ||||
|     local hostname="$1" | ||||
|     local address="$2" | ||||
|     local port="$3" | ||||
|  | ||||
|     log "INFO" "$YELLOW" "Backing up device config/presets: $hostname ($address:$port)" | ||||
|  | ||||
|     mkdir -p "$backup_dir" | ||||
|  | ||||
|     local cfg_url="http://$address:$port/cfg.json" | ||||
|     local presets_url="http://$address:$port/presets.json" | ||||
|     local cfg_dest="${backup_dir}/${hostname}.cfg.json" | ||||
|     local presets_dest="${backup_dir}/${hostname}.presets.json" | ||||
|  | ||||
|     # Write to ".tmp" files first, then move when success, to ensure we don't write partial files | ||||
|     local curl_command_cfg="curl -s "$cfg_url" -o "$cfg_dest.tmp"" | ||||
|     local curl_command_presets="curl -s "$presets_url" -o "$presets_dest.tmp"" | ||||
|  | ||||
|     if ! curl_handler "$curl_command_cfg" "$hostname"; then   | ||||
|         log "ERROR" "$RED" "Failed to backup configuration for $hostname"   | ||||
|         rm -f "$cfg_dest.tmp"   | ||||
|         return 1   | ||||
|     fi   | ||||
|      | ||||
|     if ! curl_handler "$curl_command_presets" "$hostname"; then   | ||||
|         log "ERROR" "$RED" "Failed to backup presets for $hostname"   | ||||
|         rm -f "$presets_dest.tmp"   | ||||
|         return 1   | ||||
|     fi  | ||||
|  | ||||
|     mv "$cfg_dest.tmp" "$cfg_dest" | ||||
|     mv "$presets_dest.tmp" "$presets_dest" | ||||
|     log "INFO" "$GREEN" "Successfully backed up config and presets for $hostname" | ||||
|     return 0 | ||||
| } | ||||
|  | ||||
| # Update one device | ||||
| update_one() { | ||||
|     local hostname="$1" | ||||
|     local address="$2" | ||||
|     local port="$3" | ||||
|     local firmware="$4" | ||||
|  | ||||
|     log "INFO" "$YELLOW" "Starting firmware update for device: $hostname ($address:$port)" | ||||
|  | ||||
|     local url="http://$address:$port/update" | ||||
|     local curl_command="curl -s -X POST -F "file=@$firmware" "$url"" | ||||
|  | ||||
|     if ! curl_handler "$curl_command" "$hostname"; then | ||||
|         log "ERROR" "$RED" "Failed to update firmware for $hostname" | ||||
|         return 1 | ||||
|     fi | ||||
|      | ||||
|     log "INFO" "$GREEN" "Successfully initiated firmware update for $hostname" | ||||
|     return 0 | ||||
| } | ||||
|  | ||||
| # Command-line arguments processing | ||||
| command="" | ||||
| target="" | ||||
| discover=false | ||||
| quiet=false | ||||
| backup_dir="./" | ||||
| firmware_file="" | ||||
|  | ||||
| if [ $# -eq 0 ]; then | ||||
|     show_help | ||||
|     exit 0 | ||||
| fi | ||||
|  | ||||
| while [[ $# -gt 0 ]]; do | ||||
|     case "$1" in | ||||
|         -h|--help) | ||||
|             show_help | ||||
|             exit 0 | ||||
|             ;; | ||||
|         -t|--target) | ||||
|             if [ -z "$2" ] || [[ "$2" == -* ]]; then | ||||
|                 log "ERROR" "$RED" "The --target option requires an argument." | ||||
|                 exit 1 | ||||
|             fi | ||||
|             target="$2" | ||||
|             shift 2 | ||||
|             ;; | ||||
|         -D|--discover) | ||||
|             discover=true | ||||
|             shift | ||||
|             ;; | ||||
|         -d|--directory) | ||||
|             if [ -z "$2" ] || [[ "$2" == -* ]]; then | ||||
|                 log "ERROR" "$RED" "The --directory option requires an argument." | ||||
|                 exit 1 | ||||
|             fi | ||||
|             backup_dir="$2" | ||||
|             shift 2 | ||||
|             ;; | ||||
|         -f|--firmware) | ||||
|             if [ -z "$2" ] || [[ "$2" == -* ]]; then | ||||
|                 log "ERROR" "$RED" "The --firmware option requires an argument." | ||||
|                 exit 1 | ||||
|             fi | ||||
|             firmware_file="$2" | ||||
|             shift 2 | ||||
|             ;; | ||||
|         -q|--quiet) | ||||
|             quiet=true | ||||
|             shift | ||||
|             ;; | ||||
|         backup|update|discover) | ||||
|             command="$1" | ||||
|             shift | ||||
|             ;; | ||||
|         *) | ||||
|             log "ERROR" "$RED" "Unknown argument: $1" | ||||
|             exit 1 | ||||
|             ;; | ||||
|     esac | ||||
| done | ||||
|  | ||||
| # Execute the appropriate command | ||||
| case "$command" in | ||||
|     discover) | ||||
|         read -ra devices <<< "$(discover_devices)" | ||||
|         for ((i=0; i<${#devices[@]}; i+=3)); do | ||||
|             hostname="${devices[$i]}" | ||||
|             address="${devices[$i+1]}" | ||||
|             port="${devices[$i+2]}" | ||||
|  | ||||
|             if [ "$quiet" = true ]; then | ||||
|                 echo "$hostname" | ||||
|             else | ||||
|                 log "INFO" "$BLUE" "Discovered device: Hostname=$hostname, Address=$address, Port=$port" | ||||
|             fi | ||||
|         done | ||||
|         ;; | ||||
|     backup) | ||||
|         if [ -n "$target" ]; then | ||||
|             # Assume target is both the hostname and address, with port 80 | ||||
|             backup_one "$target" "$target" "80" | ||||
|         elif [ "$discover" = true ]; then | ||||
|             read -ra devices <<< "$(discover_devices)" | ||||
|             for ((i=0; i<${#devices[@]}; i+=3)); do | ||||
|                 hostname="${devices[$i]}" | ||||
|                 address="${devices[$i+1]}" | ||||
|                 port="${devices[$i+2]}" | ||||
|                 backup_one "$hostname" "$address" "$port" | ||||
|             done | ||||
|         else | ||||
|             log "ERROR" "$RED" "No target specified. Use --target or --discover." | ||||
|             exit 1 | ||||
|         fi | ||||
|         ;; | ||||
|     update) | ||||
|         # Validate firmware before proceeding | ||||
|         if [ -z "$firmware_file" ] || [ ! -f "$firmware_file" ]; then | ||||
|             log "ERROR" "$RED" "Please provide a file in --firmware that exists" | ||||
|             exit 1 | ||||
|         fi | ||||
|          | ||||
|         if [ -n "$target" ]; then | ||||
|             # Assume target is both the hostname and address, with port 80 | ||||
|             update_one "$target" "$target" "80" "$firmware_file" | ||||
|         elif [ "$discover" = true ]; then | ||||
|             read -ra devices <<< "$(discover_devices)" | ||||
|             for ((i=0; i<${#devices[@]}; i+=3)); do | ||||
|                 hostname="${devices[$i]}" | ||||
|                 address="${devices[$i+1]}" | ||||
|                 port="${devices[$i+2]}" | ||||
|                 update_one "$hostname" "$address" "$port" "$firmware_file" | ||||
|             done | ||||
|         else | ||||
|             log "ERROR" "$RED" "No target specified. Use --target or --discover." | ||||
|             exit 1 | ||||
|         fi | ||||
|         ;; | ||||
|     *) | ||||
|         show_help | ||||
|         exit 1 | ||||
|         ;; | ||||
| esac | ||||
| @@ -1,8 +0,0 @@ | ||||
| { | ||||
|   "name": "ADS1115_v2", | ||||
|   "build": { "libArchive": false }, | ||||
|   "dependencies": { | ||||
|     "Adafruit BusIO": "https://github.com/adafruit/Adafruit_BusIO#1.13.2", | ||||
|     "Adafruit ADS1X15": "https://github.com/adafruit/Adafruit_ADS1X15#2.4.0" | ||||
|   } | ||||
| } | ||||
| @@ -6,5 +6,5 @@ Configuration is performed via the Usermod menu. There are no parameters to set | ||||
|  | ||||
| ## Installation  | ||||
|  | ||||
| Add 'ADS1115' to `custom_usermods` in your platformio environment. | ||||
|  | ||||
| Add the build flag `-D USERMOD_ADS1115` to your platformio environment. | ||||
| Uncomment libraries with comment `#For ADS1115 sensor uncomment following` | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #include <Adafruit_ADS1X15.h> | ||||
| #include <math.h> | ||||
| @@ -250,7 +252,4 @@ class ADS1115Usermod : public Usermod { | ||||
|         int16_t results = ads.getLastConversionResults(); | ||||
|         readings[activeChannel] = ads.computeVolts(results); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static ADS1115Usermod ads1115_v2; | ||||
| REGISTER_USERMOD(ads1115_v2); | ||||
| }; | ||||
| @@ -22,9 +22,15 @@ Dependencies, These must be added under `lib_deps` in your `platform.ini` (or `p | ||||
|  | ||||
| # Compiling | ||||
|  | ||||
| To enable, add 'AHT10' to `custom_usermods` in your platformio encrionment  (e.g. in `platformio_override.ini`) | ||||
| To enable, compile with `USERMOD_AHT10` defined  (e.g. in `platformio_override.ini`) | ||||
| ```ini | ||||
| [env:aht10_example] | ||||
| extends = env:esp32dev | ||||
| custom_usermods = ${env:esp32dev.custom_usermods} AHT10 | ||||
| build_flags = | ||||
|   ${common.build_flags} ${esp32.build_flags} | ||||
|   -D USERMOD_AHT10 | ||||
|   ; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal | ||||
| lib_deps =  | ||||
|   ${esp32.lib_deps} | ||||
|   enjoyneering/AHT10@~1.1.0 | ||||
| ``` | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "name": "AHT10_v2", | ||||
|   "build": { "libArchive": false },   | ||||
|   "dependencies": { | ||||
|     "enjoyneering/AHT10":"~1.1.0" | ||||
|   } | ||||
| } | ||||
| @@ -2,4 +2,8 @@ | ||||
| extends = env:esp32dev | ||||
| build_flags = | ||||
|   ${common.build_flags} ${esp32.build_flags} | ||||
|   -D USERMOD_AHT10 | ||||
|   ; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal | ||||
| lib_deps =  | ||||
|   ${esp32.lib_deps} | ||||
|   enjoyneering/AHT10@~1.1.0 | ||||
| @@ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #include <AHT10.h> | ||||
| 
 | ||||
| @@ -52,6 +54,12 @@ private: | ||||
|     _lastTemperature = 0; | ||||
|   } | ||||
| 
 | ||||
|   ~UsermodAHT10() | ||||
|   { | ||||
|     delete _aht; | ||||
|     _aht = nullptr; | ||||
|   } | ||||
| 
 | ||||
| #ifndef WLED_DISABLE_MQTT | ||||
|   void mqttInitialize() | ||||
|   { | ||||
| @@ -314,15 +322,6 @@ public: | ||||
|     _initDone = true; | ||||
|     return configComplete; | ||||
|   } | ||||
| 
 | ||||
|   ~UsermodAHT10() | ||||
|   { | ||||
|     delete _aht; | ||||
|     _aht = nullptr; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const char UsermodAHT10::_name[] PROGMEM = "AHTxx"; | ||||
| 
 | ||||
| static UsermodAHT10 aht10_v2; | ||||
| REGISTER_USERMOD(aht10_v2); | ||||
| const char UsermodAHT10::_name[] PROGMEM = "AHTxx"; | ||||
| @@ -1,3 +1,4 @@ | ||||
| #pragma once | ||||
| #include "wled.h" | ||||
| 
 | ||||
| /*
 | ||||
| @@ -102,9 +103,9 @@ private: | ||||
|     void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) { | ||||
|         uint32_t ms = time.ms % 1000; | ||||
|         uint8_t b0 = (cos8_t(ms * 64 / 1000) - 128) * 2; | ||||
|         setPixelColor(secondLed, scale32(secondColor, b0)); | ||||
|         setPixelColor(secondLed, gamma32(scale32(secondColor, b0))); | ||||
|         uint8_t b1 = (sin8_t(ms * 64 / 1000) - 128) * 2; | ||||
|         setPixelColor(inc(secondLed, 1, secondsSegment), scale32(secondColor, b1)); | ||||
|         setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1))); | ||||
|     } | ||||
| 
 | ||||
|     static inline uint32_t qadd32(uint32_t c1, uint32_t c2) { | ||||
| @@ -191,7 +192,7 @@ public: | ||||
|             // for (uint16_t i = 1; i < secondsTrail + 1; ++i) {
 | ||||
|             //     uint16_t trailLed = dec(secondLed, i, secondsSegment);
 | ||||
|             //     uint8_t trailBright = 255 / (secondsTrail + 1) * (secondsTrail - i + 1);
 | ||||
|             //     setPixelColor(trailLed, scale32(secondColor, trailBright));
 | ||||
|             //     setPixelColor(trailLed, gamma32(scale32(secondColor, trailBright)));
 | ||||
|             // }
 | ||||
|         } | ||||
| 
 | ||||
| @@ -253,7 +254,3 @@ public: | ||||
|         return USERMOD_ID_ANALOG_CLOCK; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| static AnalogClockUsermod analog_clock; | ||||
| REGISTER_USERMOD(analog_clock); | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|   "name": "Analog_Clock", | ||||
|   "build": { "libArchive": false } | ||||
| } | ||||
| @@ -7,6 +7,7 @@ | ||||
|  *  | ||||
|  * See the accompanying README.md file for more info. | ||||
|  */ | ||||
| #pragma once | ||||
| #include "wled.h" | ||||
| 
 | ||||
| class Animated_Staircase : public Usermod { | ||||
| @@ -561,7 +562,3 @@ const char Animated_Staircase::_bottomEcho_pin[]            PROGMEM = "bottomEch | ||||
| const char Animated_Staircase::_topEchoCm[]                 PROGMEM = "top-dist-cm"; | ||||
| const char Animated_Staircase::_bottomEchoCm[]              PROGMEM = "bottom-dist-cm"; | ||||
| const char Animated_Staircase::_togglePower[]               PROGMEM = "toggle-on-off"; | ||||
| 
 | ||||
| 
 | ||||
| static Animated_Staircase animated_staircase; | ||||
| REGISTER_USERMOD(animated_staircase); | ||||
| @@ -1,5 +1,4 @@ | ||||
| # Usermod Animated Staircase | ||||
|  | ||||
| This usermod makes your staircase look cool by illuminating it with an animation. It uses | ||||
| PIR or ultrasonic sensors at the top and bottom of your stairs to: | ||||
|  | ||||
| @@ -12,15 +11,14 @@ The Animated Staircase can be controlled by the WLED API. Change settings such a | ||||
| speed, on/off time and distance by sending an HTTP request, see below. | ||||
|  | ||||
| ## WLED integration | ||||
|  | ||||
| To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/). | ||||
|  | ||||
| Before compiling, you have to make the following modifications: | ||||
|  | ||||
| Edit your environment in `platformio_override.ini` | ||||
|  | ||||
| 1. Open `platformio_override.ini` | ||||
| 2. add `Animated_Staircase` to the `custom_usermods` line for your environment | ||||
| Edit `usermods_list.cpp`: | ||||
| 1. Open `wled00/usermods_list.cpp` | ||||
| 2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file | ||||
| 3. add `UsermodManager::add(new Animated_Staircase());` to the end of the `void registerUsermods()` function. | ||||
|  | ||||
| You can configure usermod using the Usermods settings page. | ||||
| Please enter GPIO pins for PIR or ultrasonic sensors (trigger and echo). | ||||
| @@ -28,10 +26,10 @@ If you use PIR sensor enter -1 for echo pin. | ||||
| Maximum distance for ultrasonic sensor can be configured as the time needed for an echo (see below). | ||||
|  | ||||
| ## Hardware installation | ||||
|  | ||||
| 1. Attach the LED strip to each step of the stairs. | ||||
| 2. Connect the ESP8266 pin D4 or ESP32 pin D2 to the first LED data pin at the bottom step. | ||||
| 3. Connect the data-out pin at the end of each strip per step to the data-in pin on the next step, creating one large virtual LED strip. | ||||
| 3. Connect the data-out pin at the end of each strip per step to the data-in pin on the  | ||||
|    next step, creating one large virtual LED strip. | ||||
| 4. Mount sensors of choice at the bottom and top of the stairs and connect them to the ESP. | ||||
| 5. To make sure all LEDs get enough power and have your staircase lighted evenly, power each | ||||
|    step from one side, using at least AWG14 or 2.5mm^2 cable. Don't connect them serial as you | ||||
| @@ -40,23 +38,24 @@ Maximum distance for ultrasonic sensor can be configured as the time needed for | ||||
| You _may_ need to use 10k pull-down resistors on the selected PIR pins, depending on the sensor. | ||||
|  | ||||
| ## WLED configuration | ||||
|  | ||||
| 1. In the WLED UI, configure a segment for each step. The lowest step of the stairs is the lowest segment id. | ||||
| 2. Save your segments into a preset. | ||||
| 3. Ideally, add the preset in the config > LED setup menu to the "apply preset **n** at boot" setting. | ||||
| 1. In the WLED UI, configure a segment for each step. The lowest step of the stairs is the  | ||||
|    lowest segment id.  | ||||
| 2. Save your segments into a preset.  | ||||
| 3. Ideally, add the preset in the config > LED setup menu to the "apply  | ||||
|    preset **n** at boot" setting. | ||||
|  | ||||
| ## Changing behavior through API | ||||
|  | ||||
| The Staircase settings can be changed through the WLED JSON api. | ||||
|  | ||||
| **NOTE:** We are using [curl](https://curl.se/) to send HTTP POSTs to the WLED API. | ||||
| If you're using Windows and want to use the curl commands, replace the `\` with a `^` | ||||
| or remove them and put everything on one line. | ||||
|  | ||||
|  | ||||
| | Setting          | Description                                                   | Default | | ||||
| |------------------|---------------------------------------------------------------|---------| | ||||
| | enabled          | Enable or disable the usermod                                 | true    | | ||||
| | bottom-sensor    | Manually trigger a down to up animation via API               | false   | | ||||
| | bottom-sensor    | Manually trigger a down to up animation via API               | false   |  | ||||
| | top-sensor       | Manually trigger an up to down animation via API              | false   | | ||||
|  | ||||
|  | ||||
| @@ -76,7 +75,6 @@ The staircase settings and sensor states are inside the WLED "state" element: | ||||
| ``` | ||||
|  | ||||
| ### Enable/disable the usermod | ||||
|  | ||||
| By disabling the usermod you will be able to keep the LED's on, independent from the sensor | ||||
| activity. This enables you to play with the lights without the usermod switching them on or off. | ||||
|  | ||||
| @@ -93,7 +91,6 @@ To enable the usermod again, use `"enabled":true`. | ||||
| Alternatively you can use _Usermod_ Settings page where you can change other parameters as well. | ||||
|  | ||||
| ### Changing animation parameters and detection range of the ultrasonic HC-SR04 sensor | ||||
|  | ||||
| Using _Usermod_ Settings page you can define different usermod parameters, including sensor pins, delay between segment activation etc. | ||||
|  | ||||
| When an ultrasonic sensor is enabled you can enter maximum detection distance in centimeters separately for top and bottom sensors. | ||||
| @@ -103,7 +100,6 @@ distances creates delays in the WLED software, _might_ introduce timing hiccups | ||||
| a less responsive web interface. It is therefore advised to keep the detection distance as short as possible. | ||||
|  | ||||
| ### Animation triggering through the API | ||||
|  | ||||
| In addition to activation by one of the stair sensors, you can also trigger the animation manually | ||||
| via the API. To simulate triggering the bottom sensor, use: | ||||
|  | ||||
| @@ -120,19 +116,15 @@ curl -X POST -H "Content-Type: application/json" \ | ||||
|      -d '{"staircase":{"top-sensor":true}}' \ | ||||
|      xxx.xxx.xxx.xxx/json/state | ||||
| ``` | ||||
|  | ||||
| **MQTT** | ||||
| You can publish a message with either `up` or `down` on topic `/swipe` to trigger animation. | ||||
| You can also use `on` or `off` for enabling or disabling the usermod. | ||||
|  | ||||
| Have fun with this usermod | ||||
|  | ||||
| `www.rolfje.com` | ||||
| Have fun with this usermod.<br/> | ||||
| www.rolfje.com | ||||
|  | ||||
| Modifications @blazoncek | ||||
|  | ||||
| ## Change log | ||||
|  | ||||
| 2021-04 | ||||
|  | ||||
| - Adaptation for runtime configuration. | ||||
| * Adaptation for runtime configuration. | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|   "name": "Animated_Staircase", | ||||
|   "build": { "libArchive": false } | ||||
| } | ||||
| @@ -1,186 +0,0 @@ | ||||
| // force the compiler to show a warning to confirm that this file is included | ||||
| #warning **** Included USERMOD_BH1750 **** | ||||
|  | ||||
| #include "wled.h" | ||||
| #include "BH1750_v2.h" | ||||
|  | ||||
| #ifdef WLED_DISABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
|  | ||||
| static bool checkBoundSensor(float newValue, float prevValue, float maxDiff) | ||||
| { | ||||
|   return isnan(prevValue) || newValue <= prevValue - maxDiff || newValue >= prevValue + maxDiff || (newValue == 0.0 && prevValue > 0.0); | ||||
| } | ||||
|  | ||||
| void Usermod_BH1750::_mqttInitialize() | ||||
| { | ||||
|   mqttLuminanceTopic = String(mqttDeviceTopic) + F("/brightness"); | ||||
|  | ||||
|   if (HomeAssistantDiscovery) _createMqttSensor(F("Brightness"), mqttLuminanceTopic, F("Illuminance"), F(" lx")); | ||||
| } | ||||
|  | ||||
| // Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. | ||||
| void Usermod_BH1750::_createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) | ||||
| { | ||||
|   String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config"); | ||||
|    | ||||
|   StaticJsonDocument<600> doc; | ||||
|    | ||||
|   doc[F("name")] = String(serverDescription) + " " + name; | ||||
|   doc[F("state_topic")] = topic; | ||||
|   doc[F("unique_id")] = String(mqttClientID) + name; | ||||
|   if (unitOfMeasurement != "") | ||||
|     doc[F("unit_of_measurement")] = unitOfMeasurement; | ||||
|   if (deviceClass != "") | ||||
|     doc[F("device_class")] = deviceClass; | ||||
|   doc[F("expire_after")] = 1800; | ||||
|  | ||||
|   JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device | ||||
|   device[F("name")] = serverDescription; | ||||
|   device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); | ||||
|   device[F("manufacturer")] = F(WLED_BRAND); | ||||
|   device[F("model")] = F(WLED_PRODUCT_NAME); | ||||
|   device[F("sw_version")] = versionString; | ||||
|  | ||||
|   String temp; | ||||
|   serializeJson(doc, temp); | ||||
|   DEBUG_PRINTLN(t); | ||||
|   DEBUG_PRINTLN(temp); | ||||
|  | ||||
|   mqtt->publish(t.c_str(), 0, true, temp.c_str()); | ||||
| } | ||||
|  | ||||
| void Usermod_BH1750::setup() | ||||
| { | ||||
|   if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; } | ||||
|   sensorFound = lightMeter.begin(); | ||||
|   initDone = true; | ||||
| } | ||||
|  | ||||
| void Usermod_BH1750::loop() | ||||
| { | ||||
|   if ((!enabled) || strip.isUpdating()) | ||||
|     return; | ||||
|  | ||||
|   unsigned long now = millis(); | ||||
|  | ||||
|   // check to see if we are due for taking a measurement | ||||
|   // lastMeasurement will not be updated until the conversion | ||||
|   // is complete the the reading is finished | ||||
|   if (now - lastMeasurement < minReadingInterval) | ||||
|   { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   bool shouldUpdate = now - lastSend > maxReadingInterval; | ||||
|  | ||||
|   float lux = lightMeter.readLightLevel(); | ||||
|   lastMeasurement = millis(); | ||||
|   getLuminanceComplete = true; | ||||
|  | ||||
|   if (shouldUpdate || checkBoundSensor(lux, lastLux, offset)) | ||||
|   { | ||||
|     lastLux = lux; | ||||
|     lastSend = millis(); | ||||
|  | ||||
|     if (WLED_MQTT_CONNECTED) | ||||
|     { | ||||
|       if (!mqttInitialized) | ||||
|         { | ||||
|           _mqttInitialize(); | ||||
|           mqttInitialized = true; | ||||
|         } | ||||
|       mqtt->publish(mqttLuminanceTopic.c_str(), 0, true, String(lux).c_str()); | ||||
|       DEBUG_PRINTLN(F("Brightness: ") + String(lux) + F("lx")); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       DEBUG_PRINTLN(F("Missing MQTT connection. Not publishing data")); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| void Usermod_BH1750::addToJsonInfo(JsonObject &root) | ||||
| { | ||||
|   JsonObject user = root[F("u")]; | ||||
|   if (user.isNull()) | ||||
|     user = root.createNestedObject(F("u")); | ||||
|  | ||||
|   JsonArray lux_json = user.createNestedArray(F("Luminance")); | ||||
|   if (!enabled) { | ||||
|     lux_json.add(F("disabled")); | ||||
|   } else if (!sensorFound) { | ||||
|       // if no sensor  | ||||
|       lux_json.add(F("BH1750 ")); | ||||
|       lux_json.add(F("Not Found")); | ||||
|   } else if (!getLuminanceComplete) { | ||||
|     // if we haven't read the sensor yet, let the user know | ||||
|       // that we are still waiting for the first measurement | ||||
|       lux_json.add((USERMOD_BH1750_FIRST_MEASUREMENT_AT - millis()) / 1000); | ||||
|       lux_json.add(F(" sec until read")); | ||||
|       return; | ||||
|   } else { | ||||
|     lux_json.add(lastLux); | ||||
|     lux_json.add(F(" lx")); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // (called from set.cpp) stores persistent properties to cfg.json | ||||
| void Usermod_BH1750::addToConfig(JsonObject &root) | ||||
| { | ||||
|   // we add JSON object. | ||||
|   JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname | ||||
|   top[FPSTR(_enabled)] = enabled; | ||||
|   top[FPSTR(_maxReadInterval)] = maxReadingInterval; | ||||
|   top[FPSTR(_minReadInterval)] = minReadingInterval; | ||||
|   top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; | ||||
|   top[FPSTR(_offset)] = offset; | ||||
|  | ||||
|   DEBUG_PRINTLN(F("BH1750 config saved.")); | ||||
| } | ||||
|  | ||||
| // called before setup() to populate properties from values stored in cfg.json | ||||
| bool Usermod_BH1750::readFromConfig(JsonObject &root) | ||||
| { | ||||
|   // we look for JSON object. | ||||
|   JsonObject top = root[FPSTR(_name)]; | ||||
|   if (top.isNull()) | ||||
|   { | ||||
|     DEBUG_PRINT(FPSTR(_name)); | ||||
|     DEBUG_PRINT(F("BH1750")); | ||||
|     DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); | ||||
|     return false; | ||||
|   } | ||||
|   bool configComplete = !top.isNull(); | ||||
|  | ||||
|   configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled, false); | ||||
|   configComplete &= getJsonValue(top[FPSTR(_maxReadInterval)], maxReadingInterval, 10000); //ms | ||||
|   configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms | ||||
|   configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); | ||||
|   configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1); | ||||
|  | ||||
|   DEBUG_PRINT(FPSTR(_name)); | ||||
|   if (!initDone) { | ||||
|     DEBUG_PRINTLN(F(" config loaded.")); | ||||
|   } else { | ||||
|     DEBUG_PRINTLN(F(" config (re)loaded.")); | ||||
|   } | ||||
|  | ||||
|   return configComplete; | ||||
|    | ||||
| } | ||||
|  | ||||
|  | ||||
| // strings to reduce flash memory usage (used more than twice) | ||||
| const char Usermod_BH1750::_name[] PROGMEM = "BH1750"; | ||||
| const char Usermod_BH1750::_enabled[] PROGMEM = "enabled"; | ||||
| const char Usermod_BH1750::_maxReadInterval[] PROGMEM = "max-read-interval-ms"; | ||||
| const char Usermod_BH1750::_minReadInterval[] PROGMEM = "min-read-interval-ms"; | ||||
| const char Usermod_BH1750::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscoveryLux"; | ||||
| const char Usermod_BH1750::_offset[] PROGMEM = "offset-lx"; | ||||
|  | ||||
|  | ||||
| static Usermod_BH1750 bh1750_v2; | ||||
| REGISTER_USERMOD(bh1750_v2); | ||||
| @@ -1,92 +0,0 @@ | ||||
|  | ||||
| #pragma once | ||||
| #include "wled.h" | ||||
| #include <BH1750.h> | ||||
|  | ||||
| #ifdef WLED_DISABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
|  | ||||
| // the max frequency to check photoresistor, 10 seconds | ||||
| #ifndef USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL | ||||
| #define USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL 10000 | ||||
| #endif | ||||
|  | ||||
| // the min frequency to check photoresistor, 500 ms | ||||
| #ifndef USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL | ||||
| #define USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL 500 | ||||
| #endif | ||||
|  | ||||
| // how many seconds after boot to take first measurement, 10 seconds | ||||
| #ifndef USERMOD_BH1750_FIRST_MEASUREMENT_AT | ||||
| #define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000 | ||||
| #endif | ||||
|  | ||||
| // only report if difference grater than offset value | ||||
| #ifndef USERMOD_BH1750_OFFSET_VALUE | ||||
| #define USERMOD_BH1750_OFFSET_VALUE 1 | ||||
| #endif | ||||
|  | ||||
| class Usermod_BH1750 : public Usermod | ||||
| { | ||||
| private: | ||||
|   int8_t offset = USERMOD_BH1750_OFFSET_VALUE; | ||||
|  | ||||
|   unsigned long maxReadingInterval = USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL; | ||||
|   unsigned long minReadingInterval = USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL; | ||||
|   unsigned long lastMeasurement = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT); | ||||
|   unsigned long lastSend = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT); | ||||
|   // flag to indicate we have finished the first readLightLevel call | ||||
|   // allows this library to report to the user how long until the first | ||||
|   // measurement | ||||
|   bool getLuminanceComplete = false; | ||||
|  | ||||
|   // flag set at startup | ||||
|   bool enabled = true; | ||||
|  | ||||
|   // strings to reduce flash memory usage (used more than twice) | ||||
|   static const char _name[]; | ||||
|   static const char _enabled[]; | ||||
|   static const char _maxReadInterval[]; | ||||
|   static const char _minReadInterval[]; | ||||
|   static const char _offset[]; | ||||
|   static const char _HomeAssistantDiscovery[]; | ||||
|  | ||||
|   bool initDone = false; | ||||
|   bool sensorFound = false; | ||||
|  | ||||
|   // Home Assistant and MQTT   | ||||
|   String mqttLuminanceTopic; | ||||
|   bool mqttInitialized = false; | ||||
|   bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages | ||||
|  | ||||
|   BH1750 lightMeter; | ||||
|   float lastLux = -1000; | ||||
|  | ||||
|   // set up Home Assistant discovery entries | ||||
|   void _mqttInitialize(); | ||||
|  | ||||
|   // Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. | ||||
|   void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement); | ||||
|  | ||||
| public: | ||||
|   void setup(); | ||||
|   void loop(); | ||||
|   inline float getIlluminance()  { | ||||
|     return (float)lastLux; | ||||
|   } | ||||
|  | ||||
|   void addToJsonInfo(JsonObject &root); | ||||
|  | ||||
|   // (called from set.cpp) stores persistent properties to cfg.json | ||||
|   void addToConfig(JsonObject &root); | ||||
|  | ||||
|   // called before setup() to populate properties from values stored in cfg.json | ||||
|   bool readFromConfig(JsonObject &root); | ||||
|  | ||||
|   inline uint16_t getId() | ||||
|   { | ||||
|     return USERMOD_ID_BH1750; | ||||
|   } | ||||
|  | ||||
| }; | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "name": "BH1750_v2", | ||||
|   "build": { "libArchive": false }, | ||||
|   "dependencies": { | ||||
|     "claws/BH1750":"^1.2.0" | ||||
|   } | ||||
| } | ||||
| @@ -4,40 +4,43 @@ This usermod will read from an ambient light sensor like the BH1750. | ||||
| The luminance is displayed in both the Info section of the web UI, as well as published to the `/luminance` MQTT topic if enabled. | ||||
|  | ||||
| ## Dependencies | ||||
|  | ||||
| - Libraries | ||||
|   - `claws/BH1750 @^1.2.0` | ||||
|   - This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`). | ||||
| - Data is published over MQTT - make sure you've enabled the MQTT sync interface. | ||||
|  | ||||
| ## Compilation | ||||
|  | ||||
| To enable, compile with `BH1750` in `custom_usermods` (e.g. in `platformio_override.ini`) | ||||
| To enable, compile with `USERMOD_BH1750` defined  (e.g. in `platformio_override.ini`) | ||||
| ```ini | ||||
| [env:usermod_BH1750_d1_mini] | ||||
| extends = env:d1_mini | ||||
| build_flags = | ||||
|     ${common.build_flags_esp8266} | ||||
|     -D USERMOD_BH1750 | ||||
| lib_deps =  | ||||
|     ${esp8266.lib_deps} | ||||
|     claws/BH1750 @ ^1.2.0 | ||||
| ``` | ||||
|  | ||||
| ### Configuration Options | ||||
|  | ||||
| The following settings can be set at compile-time but are configurable on the usermod menu (except First Measurement time): | ||||
|  | ||||
| - `USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL` - the max number of milliseconds between measurements, defaults to 10000ms | ||||
| - `USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL` - the min number of milliseconds between measurements, defaults to 500ms | ||||
| - `USERMOD_BH1750_OFFSET_VALUE` - the offset value to report on, defaults to 1 | ||||
| - `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10000 ms | ||||
| *   `USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL` - the max number of milliseconds between measurements, defaults to 10000ms | ||||
| *   `USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL` - the min number of milliseconds between measurements, defaults to 500ms | ||||
| *   `USERMOD_BH1750_OFFSET_VALUE` - the offset value to report on, defaults to 1 | ||||
| *   `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10000 ms | ||||
|  | ||||
| In addition, the Usermod screen allows you to: | ||||
|  | ||||
| - enable/disable the usermod | ||||
| - Enable Home Assistant Discovery of usermod | ||||
| - Configure the SCL/SDA pins | ||||
|  | ||||
| ## API | ||||
|  | ||||
| The following method is available to interact with the usermod from other code modules: | ||||
|  | ||||
| - `getIlluminance` read the brightness from the sensor | ||||
|  | ||||
| ## Change Log | ||||
|  | ||||
| Jul 2022 | ||||
|  | ||||
| - Added Home Assistant Discovery | ||||
| - Implemented PinManager to register pins | ||||
| - Made pins configurable in usermod menu | ||||
|   | ||||
							
								
								
									
										252
									
								
								usermods/BH1750_v2/usermod_bh1750.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								usermods/BH1750_v2/usermod_bh1750.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,252 @@ | ||||
| // force the compiler to show a warning to confirm that this file is included | ||||
| #warning **** Included USERMOD_BH1750 **** | ||||
|  | ||||
| #ifndef WLED_ENABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "wled.h" | ||||
| #include <BH1750.h> | ||||
|  | ||||
| // the max frequency to check photoresistor, 10 seconds | ||||
| #ifndef USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL | ||||
| #define USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL 10000 | ||||
| #endif | ||||
|  | ||||
| // the min frequency to check photoresistor, 500 ms | ||||
| #ifndef USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL | ||||
| #define USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL 500 | ||||
| #endif | ||||
|  | ||||
| // how many seconds after boot to take first measurement, 10 seconds | ||||
| #ifndef USERMOD_BH1750_FIRST_MEASUREMENT_AT | ||||
| #define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000 | ||||
| #endif | ||||
|  | ||||
| // only report if difference grater than offset value | ||||
| #ifndef USERMOD_BH1750_OFFSET_VALUE | ||||
| #define USERMOD_BH1750_OFFSET_VALUE 1 | ||||
| #endif | ||||
|  | ||||
| class Usermod_BH1750 : public Usermod | ||||
| { | ||||
| private: | ||||
|   int8_t offset = USERMOD_BH1750_OFFSET_VALUE; | ||||
|  | ||||
|   unsigned long maxReadingInterval = USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL; | ||||
|   unsigned long minReadingInterval = USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL; | ||||
|   unsigned long lastMeasurement = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT); | ||||
|   unsigned long lastSend = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT); | ||||
|   // flag to indicate we have finished the first readLightLevel call | ||||
|   // allows this library to report to the user how long until the first | ||||
|   // measurement | ||||
|   bool getLuminanceComplete = false; | ||||
|  | ||||
|   // flag set at startup | ||||
|   bool enabled = true; | ||||
|  | ||||
|   // strings to reduce flash memory usage (used more than twice) | ||||
|   static const char _name[]; | ||||
|   static const char _enabled[]; | ||||
|   static const char _maxReadInterval[]; | ||||
|   static const char _minReadInterval[]; | ||||
|   static const char _offset[]; | ||||
|   static const char _HomeAssistantDiscovery[]; | ||||
|  | ||||
|   bool initDone = false; | ||||
|   bool sensorFound = false; | ||||
|  | ||||
|   // Home Assistant and MQTT   | ||||
|   String mqttLuminanceTopic; | ||||
|   bool mqttInitialized = false; | ||||
|   bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages | ||||
|  | ||||
|   BH1750 lightMeter; | ||||
|   float lastLux = -1000; | ||||
|  | ||||
|   bool checkBoundSensor(float newValue, float prevValue, float maxDiff) | ||||
|   { | ||||
|     return isnan(prevValue) || newValue <= prevValue - maxDiff || newValue >= prevValue + maxDiff || (newValue == 0.0 && prevValue > 0.0); | ||||
|   } | ||||
|    | ||||
|   // set up Home Assistant discovery entries | ||||
|   void _mqttInitialize() | ||||
|   { | ||||
|     mqttLuminanceTopic = String(mqttDeviceTopic) + F("/brightness"); | ||||
|  | ||||
|     if (HomeAssistantDiscovery) _createMqttSensor(F("Brightness"), mqttLuminanceTopic, F("Illuminance"), F(" lx")); | ||||
|   } | ||||
|  | ||||
|   // Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. | ||||
|   void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) | ||||
|   { | ||||
|     String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config"); | ||||
|      | ||||
|     StaticJsonDocument<600> doc; | ||||
|      | ||||
|     doc[F("name")] = String(serverDescription) + " " + name; | ||||
|     doc[F("state_topic")] = topic; | ||||
|     doc[F("unique_id")] = String(mqttClientID) + name; | ||||
|     if (unitOfMeasurement != "") | ||||
|       doc[F("unit_of_measurement")] = unitOfMeasurement; | ||||
|     if (deviceClass != "") | ||||
|       doc[F("device_class")] = deviceClass; | ||||
|     doc[F("expire_after")] = 1800; | ||||
|  | ||||
|     JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device | ||||
|     device[F("name")] = serverDescription; | ||||
|     device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); | ||||
|     device[F("manufacturer")] = F(WLED_BRAND); | ||||
|     device[F("model")] = F(WLED_PRODUCT_NAME); | ||||
|     device[F("sw_version")] = versionString; | ||||
|  | ||||
|     String temp; | ||||
|     serializeJson(doc, temp); | ||||
|     DEBUG_PRINTLN(t); | ||||
|     DEBUG_PRINTLN(temp); | ||||
|  | ||||
|     mqtt->publish(t.c_str(), 0, true, temp.c_str()); | ||||
|   } | ||||
|  | ||||
| public: | ||||
|   void setup() | ||||
|   { | ||||
|     if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; } | ||||
|     sensorFound = lightMeter.begin(); | ||||
|     initDone = true; | ||||
|   } | ||||
|  | ||||
|   void loop() | ||||
|   { | ||||
|     if ((!enabled) || strip.isUpdating()) | ||||
|       return; | ||||
|  | ||||
|     unsigned long now = millis(); | ||||
|  | ||||
|     // check to see if we are due for taking a measurement | ||||
|     // lastMeasurement will not be updated until the conversion | ||||
|     // is complete the the reading is finished | ||||
|     if (now - lastMeasurement < minReadingInterval) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     bool shouldUpdate = now - lastSend > maxReadingInterval; | ||||
|  | ||||
|     float lux = lightMeter.readLightLevel(); | ||||
|     lastMeasurement = millis(); | ||||
|     getLuminanceComplete = true; | ||||
|  | ||||
|     if (shouldUpdate || checkBoundSensor(lux, lastLux, offset)) | ||||
|     { | ||||
|       lastLux = lux; | ||||
|       lastSend = millis(); | ||||
| #ifndef WLED_DISABLE_MQTT | ||||
|       if (WLED_MQTT_CONNECTED) | ||||
|       { | ||||
|         if (!mqttInitialized) | ||||
|           { | ||||
|             _mqttInitialize(); | ||||
|             mqttInitialized = true; | ||||
|           } | ||||
|         mqtt->publish(mqttLuminanceTopic.c_str(), 0, true, String(lux).c_str()); | ||||
|         DEBUG_PRINTLN(F("Brightness: ") + String(lux) + F("lx")); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         DEBUG_PRINTLN(F("Missing MQTT connection. Not publishing data")); | ||||
|       } | ||||
| #endif | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   inline float getIlluminance() { | ||||
|     return (float)lastLux; | ||||
|   } | ||||
|  | ||||
|   void addToJsonInfo(JsonObject &root) | ||||
|   { | ||||
|     JsonObject user = root[F("u")]; | ||||
|     if (user.isNull()) | ||||
|       user = root.createNestedObject(F("u")); | ||||
|  | ||||
|     JsonArray lux_json = user.createNestedArray(F("Luminance")); | ||||
|     if (!enabled) { | ||||
|       lux_json.add(F("disabled")); | ||||
|     } else if (!sensorFound) { | ||||
|         // if no sensor  | ||||
|         lux_json.add(F("BH1750 ")); | ||||
|         lux_json.add(F("Not Found")); | ||||
|     } else if (!getLuminanceComplete) { | ||||
|       // if we haven't read the sensor yet, let the user know | ||||
|         // that we are still waiting for the first measurement | ||||
|         lux_json.add((USERMOD_BH1750_FIRST_MEASUREMENT_AT - millis()) / 1000); | ||||
|         lux_json.add(F(" sec until read")); | ||||
|         return; | ||||
|     } else { | ||||
|       lux_json.add(lastLux); | ||||
|       lux_json.add(F(" lx")); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // (called from set.cpp) stores persistent properties to cfg.json | ||||
|   void addToConfig(JsonObject &root) | ||||
|   { | ||||
|     // we add JSON object. | ||||
|     JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname | ||||
|     top[FPSTR(_enabled)] = enabled; | ||||
|     top[FPSTR(_maxReadInterval)] = maxReadingInterval; | ||||
|     top[FPSTR(_minReadInterval)] = minReadingInterval; | ||||
|     top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; | ||||
|     top[FPSTR(_offset)] = offset; | ||||
|  | ||||
|     DEBUG_PRINTLN(F("BH1750 config saved.")); | ||||
|   } | ||||
|  | ||||
|   // called before setup() to populate properties from values stored in cfg.json | ||||
|   bool readFromConfig(JsonObject &root) | ||||
|   { | ||||
|     // we look for JSON object. | ||||
|     JsonObject top = root[FPSTR(_name)]; | ||||
|     if (top.isNull()) | ||||
|     { | ||||
|       DEBUG_PRINT(FPSTR(_name)); | ||||
|       DEBUG_PRINT(F("BH1750")); | ||||
|       DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); | ||||
|       return false; | ||||
|     } | ||||
|     bool configComplete = !top.isNull(); | ||||
|  | ||||
|     configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled, false); | ||||
|     configComplete &= getJsonValue(top[FPSTR(_maxReadInterval)], maxReadingInterval, 10000); //ms | ||||
|     configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms | ||||
|     configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); | ||||
|     configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1); | ||||
|  | ||||
|     DEBUG_PRINT(FPSTR(_name)); | ||||
|     if (!initDone) { | ||||
|       DEBUG_PRINTLN(F(" config loaded.")); | ||||
|     } else { | ||||
|       DEBUG_PRINTLN(F(" config (re)loaded.")); | ||||
|     } | ||||
|  | ||||
|     return configComplete; | ||||
|      | ||||
|   } | ||||
|  | ||||
|   uint16_t getId() | ||||
|   { | ||||
|     return USERMOD_ID_BH1750; | ||||
|   } | ||||
|  | ||||
| }; | ||||
|  | ||||
| // strings to reduce flash memory usage (used more than twice) | ||||
| const char Usermod_BH1750::_name[] PROGMEM = "BH1750"; | ||||
| const char Usermod_BH1750::_enabled[] PROGMEM = "enabled"; | ||||
| const char Usermod_BH1750::_maxReadInterval[] PROGMEM = "max-read-interval-ms"; | ||||
| const char Usermod_BH1750::_minReadInterval[] PROGMEM = "min-read-interval-ms"; | ||||
| const char Usermod_BH1750::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscoveryLux"; | ||||
| const char Usermod_BH1750::_offset[] PROGMEM = "offset-lx"; | ||||
| @@ -22,6 +22,7 @@ Dependencies | ||||
| - Libraries | ||||
|   - `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280)) | ||||
|   - `Wire` | ||||
|   - These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). | ||||
| - Data is published over MQTT - make sure you've enabled the MQTT sync interface. | ||||
| - This usermod also writes to serial (GPIO1 on ESP8266). Please make sure nothing else is listening to the serial TX pin or your board will get confused by log messages! | ||||
|  | ||||
| @@ -39,11 +40,17 @@ Methods also exist to read the read/calculated values from other WLED modules th | ||||
|  | ||||
| # Compiling | ||||
|  | ||||
| To enable, add `BME280_v2` to your `custom_usermods`  (e.g. in `platformio_override.ini`) | ||||
| To enable, compile with `USERMOD_BME280` defined  (e.g. in `platformio_override.ini`) | ||||
| ```ini | ||||
| [env:usermod_bme280_d1_mini] | ||||
| extends = env:d1_mini | ||||
| custom_usermods = ${env:d1_mini.custom_usermods} BME280_v2 | ||||
| build_flags = | ||||
|   ${common.build_flags_esp8266} | ||||
|   -D USERMOD_BME280 | ||||
| lib_deps =  | ||||
|   ${esp8266.lib_deps} | ||||
|   BME280@~3.0.0 | ||||
|   Wire | ||||
| ``` | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "name": "BME280_v2", | ||||
|   "build": { "libArchive": false }, | ||||
|   "dependencies": { | ||||
|     "finitespace/BME280":"~3.0.0" | ||||
|   } | ||||
| } | ||||
| @@ -1,15 +1,17 @@ | ||||
| // force the compiler to show a warning to confirm that this file is included
 | ||||
| #warning **** Included USERMOD_BME280 version 2.0 **** | ||||
| 
 | ||||
| #ifndef WLED_ENABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #include <Arduino.h> | ||||
| #include <BME280I2C.h>               // BME280 sensor | ||||
| #include <EnvironmentCalculations.h> // BME280 extended measurements | ||||
| 
 | ||||
| #ifdef WLED_DISABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
| 
 | ||||
| class UsermodBME280 : public Usermod | ||||
| { | ||||
| private: | ||||
| @@ -239,7 +241,7 @@ public: | ||||
|         // from the UI and values read from sensor, then publish to broker
 | ||||
|         if (temperature != lastTemperature || PublishAlways) | ||||
|         { | ||||
|           publishMqtt("temperature", String(temperature, (unsigned) TemperatureDecimals).c_str()); | ||||
|           publishMqtt("temperature", String(temperature, TemperatureDecimals).c_str()); | ||||
|         } | ||||
| 
 | ||||
|         lastTemperature = temperature; // Update last sensor temperature for next loop
 | ||||
| @@ -252,17 +254,17 @@ public: | ||||
| 
 | ||||
|           if (humidity != lastHumidity || PublishAlways) | ||||
|           { | ||||
|             publishMqtt("humidity", String(humidity, (unsigned) HumidityDecimals).c_str()); | ||||
|             publishMqtt("humidity", String(humidity, HumidityDecimals).c_str()); | ||||
|           } | ||||
| 
 | ||||
|           if (heatIndex != lastHeatIndex || PublishAlways) | ||||
|           { | ||||
|             publishMqtt("heat_index", String(heatIndex, (unsigned) TemperatureDecimals).c_str()); | ||||
|             publishMqtt("heat_index", String(heatIndex, TemperatureDecimals).c_str()); | ||||
|           } | ||||
| 
 | ||||
|           if (dewPoint != lastDewPoint || PublishAlways) | ||||
|           { | ||||
|             publishMqtt("dew_point", String(dewPoint, (unsigned) TemperatureDecimals).c_str()); | ||||
|             publishMqtt("dew_point", String(dewPoint, TemperatureDecimals).c_str()); | ||||
|           } | ||||
| 
 | ||||
|           lastHumidity = humidity; | ||||
| @@ -279,7 +281,7 @@ public: | ||||
| 
 | ||||
|         if (pressure != lastPressure || PublishAlways) | ||||
|         { | ||||
|           publishMqtt("pressure", String(pressure, (unsigned) PressureDecimals).c_str()); | ||||
|           publishMqtt("pressure", String(pressure, PressureDecimals).c_str()); | ||||
|         } | ||||
| 
 | ||||
|         lastPressure = pressure; | ||||
| @@ -442,6 +444,7 @@ public: | ||||
|     configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false); | ||||
|     configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true); | ||||
|     configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false); | ||||
|     tempScale = UseCelsius ? "°C" : "°F"; | ||||
| 
 | ||||
|     DEBUG_PRINT(FPSTR(_name)); | ||||
|     if (!initDone) { | ||||
| @@ -477,7 +480,3 @@ public: | ||||
| 
 | ||||
| const char UsermodBME280::_name[]                      PROGMEM = "BME280/BMP280"; | ||||
| const char UsermodBME280::_enabled[]                   PROGMEM = "enabled"; | ||||
| 
 | ||||
| 
 | ||||
| static UsermodBME280 bme280_v2; | ||||
| REGISTER_USERMOD(bme280_v2); | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,70 +1,65 @@ | ||||
| # Usermod BME68X | ||||
|  | ||||
| This usermod was developed for a BME680/BME68X sensor. The BME68X is not compatible with the BME280/BMP280 chip. It has its own library. The original 'BSEC Software Library' from Bosch was used to develop the code. The measured values are displayed on the WLED info page. | ||||
| This usermod was developed for a BME680/BME68X sensor. The BME68X is not compatible with the BME280/BMP280 chip. It has its own library. The original 'BSEC Software Library' from Bosch was used to develop the code. The measured values are displayed on the WLED info page.  | ||||
|  | ||||
| <p align="center"><img src="pics/pic1.png" style="width:60%;"></p> | ||||
|  | ||||
| In addition, the values are published on MQTT if this is active. The topic used for this is: 'wled/[MQTT Client ID]'. The Client ID is set in the WLED MQTT settings. | ||||
|  | ||||
| <p align="center"><img src="pics/pic2.png"></p> | ||||
|  | ||||
| If you use HomeAssistance discovery, the device tree for HomeAssistance is created.  This is published under the topic 'homeassistant/sensor/[MQTT Client ID]' via MQTT. | ||||
|  | ||||
| <p align="center"><img src="pics/pic3.png"></p> | ||||
|  | ||||
| A device with the following sensors appears in HomeAssistant. Please note that MQTT must be activated in HomeAssistant. | ||||
|  | ||||
| <p align="center"><img src="pics/pic4.png" style="width:60%;"></p> | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| ## Features | ||||
| Raw sensor types | ||||
|  | ||||
| Sensor		Accuracy	Scale		Range | ||||
| ----------------------------- | ||||
| 	Sensor		Accuracy	Scale		Range | ||||
|  	-------------------------------------------------------------------------------------------------- | ||||
| 	Temperature	+/- 1.0		°C/°F		-40 to 85 °C | ||||
| 	Humidity	+/- 3 		%		0 to 100 % | ||||
| 	Pressure	+/- 1 		hPa		300 to 1100 hPa | ||||
| 	Gas Resistance			Ohm | ||||
|  | ||||
| Temperature	+/- 1.0		°C/°F		-40 to 85 °C | ||||
| Humidity	+/- 3 		%		0 to 100 % | ||||
| Pressure	+/- 1 		hPa		300 to 1100 hPa | ||||
| Gas Resistance			Ohm | ||||
| The BSEC Library calculates the following values via the gas resistance | ||||
|  | ||||
| Sensor		Accuracy	Scale		Range | ||||
| ----------------------------- | ||||
| 	Sensor		Accuracy	Scale		Range | ||||
|  	-------------------------------------------------------------------------------------------------- | ||||
| 	IAQ 						value between 0 and 500 | ||||
| 	Static IAQ 					same as IAQ but for permanently installed devices | ||||
| 	CO2 				PPM | ||||
| 	VOC 				PPM | ||||
| 	Gas-Percentage 			% | ||||
|  | ||||
|  | ||||
| IAQ 						value between 0 and 500 | ||||
| Static IAQ 					same as IAQ but for permanently installed devices | ||||
| CO2 				PPM | ||||
| VOC 				PPM | ||||
| Gas-Percentage 			% | ||||
| In addition the usermod calculates | ||||
|  | ||||
| Sensor		Accuracy	Scale		Range | ||||
| ----------------------------- | ||||
|  | ||||
| Absolute humidity	 	g/m³ | ||||
| Dew point 			°C/°F | ||||
| 	Sensor		Accuracy	Scale		Range | ||||
|  	-------------------------------------------------------------------------------------------------- | ||||
| 	Absolute humidity	 	g/m³ | ||||
| 	Dew point 			°C/°F | ||||
|  | ||||
| ### IAQ (Indoor Air Quality) | ||||
|  | ||||
| The IAQ is divided into the following value groups. | ||||
|  | ||||
| The IAQ is divided into the following value groups.  | ||||
| <p align="center"><img src="pics/pic5.png"></p> | ||||
|  | ||||
| For more detailed information, please consult the enclosed Bosch product description (BME680.pdf). | ||||
|  | ||||
|  | ||||
| ## Calibration of the device | ||||
|  | ||||
| The gas sensor of the BME68X must be calibrated. This differs from the BME280, which does not require any calibration. | ||||
| The gas sensor of the BME68X must be calibrated. This differs from the BME280, which does not require any calibration.  | ||||
| There is a range of additional information for this, which the driver also provides. These values can be found in HomeAssistant under Diagnostics. | ||||
|  | ||||
| - **STABILIZATION_STATUS**: Gas sensor stabilization status [boolean] Indicates initial stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization is finished (1). | ||||
| - **RUN_IN_STATUS**: 	Gas sensor run-in status [boolean] Indicates power-on stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization is finished (1) | ||||
|  | ||||
| Furthermore, all GAS based values have their own accuracy value. These have the following meaning: | ||||
| Furthermore, all GAS based values have their own accuracy value. These have the following meaning:  | ||||
|  | ||||
| - **Accuracy = 0** 	means the sensor is being stabilized (this can take a while on the first run) | ||||
| - **Accuracy = 1**	means that the previous measured values show too few differences and cannot be used for calibration. If the sensor is at accuracy 1 for too long, you must ensure that the ambient air is chaning. Opening the windows is fine. Or sometimes it is sufficient to breathe on the sensor for approx. 5 minutes. | ||||
| - **Accuracy = 0** 	means the sensor is being stabilized (this can take a while on the first run)  | ||||
| - **Accuracy = 1**	means that the previous measured values show too few differences and cannot be used for calibration. If the sensor is at accuracy 1 for too long, you must ensure that the ambient air is chaning. Opening the windows is fine. Or sometimes it is sufficient to breathe on the sensor for approx. 5 minutes.  | ||||
| - **Accuracy = 2**	means the sensor is currently calibrating. | ||||
| - **Accuracy = 3**	means that the sensor has been successfully calibrated. Once accuracy 3 is reached, the calibration data is automatically written to the file system. This calibration data will be used again at the next start and will speed up the calibration. | ||||
|  | ||||
| @@ -72,29 +67,28 @@ The IAQ index is therefore only meaningful if IAQ Accuracy = 3. In addition to t | ||||
|  | ||||
| Reasonably reliable values are therefore only achieved when accuracy displays the value 3. | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Settings | ||||
|  | ||||
| The settings of the usermods are set in the usermod section of wled. | ||||
|  | ||||
| The settings of the usermods are set in the usermod section of wled.  | ||||
| <p align="center"><img src="pics/pic6.png"></p> | ||||
|  | ||||
| The possible settings are | ||||
|  | ||||
| - **Enable:**			Enables / disables the usermod | ||||
| - **I2C address:**		I2C address of the sensor. You can choose between 0X77 & 0X76. The default is 0x77. | ||||
| - **Interval:**			Specifies the interval of seconds at which the usermod should be executed. The default is every second. | ||||
| - **Pub Chages Only:**		If this item is active, the values are only published if they have changed since the last publication. | ||||
| - **Pub Accuracy:**		The Accuracy values associated with the gas values are also published. | ||||
| - **Pub Calib State:**		If this item is active, STABILIZATION_STATUS& RUN_IN_STATUS are also published. | ||||
| - **Interval:**			Specifies the interval of seconds at which the usermod should be executed. The default is every second.  | ||||
| - **Pub Chages Only:**		If this item is active, the values are only published if they have changed since the last publication.  | ||||
| - **Pub Accuracy:**		The Accuracy values associated with the gas values are also published.  | ||||
| - **Pub Calib State:**		If this item is active, STABILIZATION_STATUS& RUN_IN_STATUS are also published.  | ||||
| - **Temp Scale:**		Here you can choose between °C and °F. | ||||
| - **Temp Offset:**		The temperature offset is always set in °C. It must be converted for Fahrenheit. | ||||
| - **HA Discovery:**		If this item is active, the HomeAssistant sensor tree is created. | ||||
| - **Temp Offset:**		The temperature offset is always set in °C. It must be converted for Fahrenheit.  | ||||
| - **HA Discovery:**		If this item is active, the HomeAssistant sensor tree is created.  | ||||
| - **Pause While WLED Active:**	If WLED has many LEDs to calculate, the computing power may no longer be sufficient to calculate the LEDs and read the sensor data. The LEDs then hang for a few microseconds, which can be seen. If this point is active, no sensor data is fetched as long as WLED is running. | ||||
| - **Del Calibration Hist:**	If a check mark is set here, the calibration file saved in the file system is deleted when the settings are saved. | ||||
| - **Del Calibration Hist:**	If a check mark is set here, the calibration file saved in the file system is deleted when the settings are saved.  | ||||
|  | ||||
| ### Sensors | ||||
|  | ||||
| Applies to all sensors. The number of decimal places is set here. If the sensor is set to -1, it will no longer be published. In addition, the IAQ values can be activated here in verbal form. | ||||
| Applies to all sensors. The number of decimal places is set here. If the sensor is set to -1, it will no longer be published. In addition, the IAQ values can be activated here in verbal form.  | ||||
|  | ||||
| It is recommended to use the Static IAQ for the IAQ values. This is recommended by Bosch for statically placed devices. | ||||
|  | ||||
| @@ -105,9 +99,8 @@ Data is published over MQTT - make sure you've enabled the MQTT sync interface. | ||||
| In addition to outputting via MQTT, you can read the values from the Info Screen on the dashboard page of the device's web interface. | ||||
|  | ||||
| Methods also exist to read the read/calculated values from other WLED modules through code. | ||||
|  | ||||
| - getTemperature();	The scale °C/°F is depended to the settings | ||||
| - getHumidity(); | ||||
| - getHumidity();	 | ||||
| - getPressure(); | ||||
| - getGasResistance(); | ||||
| - getAbsoluteHumidity(); | ||||
| @@ -125,36 +118,32 @@ Methods also exist to read the read/calculated values from other WLED modules th | ||||
| - getStabStatus(); | ||||
| - getRunInStatus(); | ||||
|  | ||||
| ## Compilation | ||||
|  | ||||
| To enable, compile with `BME68X` in `custom_usermods` (e.g. in `platformio_override.ini`) | ||||
| ## Compiling | ||||
|  | ||||
| Example: | ||||
| To enable, compile with `USERMOD_BME68X` defined (e.g. in `platformio_override.ini`) and add the `BSEC Software Library` to the lib_deps. | ||||
|  | ||||
| ```[env:esp32_mySpecial] | ||||
| extends = env:esp32dev | ||||
| custom_usermods = ${env:esp32dev.custom_usermods} BME68X | ||||
| ``` | ||||
| [env:esp32-BME680] | ||||
| board = 		esp32dev | ||||
| platform = 		${esp32.platform} | ||||
| platform_packages = 	${esp32.platform_packages} | ||||
| lib_deps = 		${esp32.lib_deps} | ||||
|            		boschsensortec/BSEC Software Library @ ^1.8.1492      	; USERMOD: BME680                                           | ||||
| build_unflags = 	${common.build_unflags} | ||||
| build_flags = 		${common.build_flags_esp32}  | ||||
|               		-D USERMOD_BME68X                      			; USERMOD: BME680 | ||||
| ``` | ||||
|  | ||||
| ## Revision History | ||||
|  | ||||
| ### Version 1.0.0 | ||||
|  | ||||
| - First version of the BME68X_v user module | ||||
|  | ||||
| ### Version 1.0.1 | ||||
|  | ||||
| - Rebased to WELD Version 0.15 | ||||
| - Reworked some default settings | ||||
| - A problem with the default settings has been fixed | ||||
|  | ||||
| ### Version 1.0.2 | ||||
|  | ||||
| * Rebased to WELD Version 0.16 | ||||
| * Fixed: Solved compilation problems related to some macro naming interferences. | ||||
|  | ||||
| ## Known problems | ||||
|  | ||||
| - MQTT goes online at device start. Shortly afterwards it goes offline and takes quite a while until it goes online again. The problem does not come from this user module, but from the WLED core. | ||||
| - If you save the settings often, WLED can get stuck. | ||||
| - If many LEDS are connected to WLED, reading the sensor can cause a small but noticeable hang. The "Pause While WLED Active" option was introduced as a workaround. | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "name": "BME68X", | ||||
|   "build": { "libArchive": false }, | ||||
|   "dependencies": { | ||||
|     "boschsensortec/BSEC Software Library":"^1.8.1492" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										1114
									
								
								usermods/BME68X_v2/usermod_bme68x.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1114
									
								
								usermods/BME68X_v2/usermod_bme68x.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|   "name": "Battery", | ||||
|   "build": { "libArchive": false } | ||||
| } | ||||
| @@ -23,7 +23,9 @@ Enables battery level monitoring of your project. | ||||
|  | ||||
| ## 🎈 Installation | ||||
|  | ||||
| In `platformio_override.ini` (or `platformio.ini`)<br>Under: `custom_usermods =`, add the line: `Battery`<br><br>[Example: platformio_override.ini](assets/installation_platformio_override_ini.png) | | ||||
| | **Option 1** | **Option 2** | | ||||
| |--------------|--------------| | ||||
| | In `wled00/my_config.h`<br>Add the line: `#define USERMOD_BATTERY`<br><br>[Example: my_config.h](assets/installation_my_config_h.png) | In `platformio_override.ini` (or `platformio.ini`)<br>Under: `build_flags =`, add the line: `-D USERMOD_BATTERY`<br><br>[Example: platformio_override.ini](assets/installation_platformio_override_ini.png) | | ||||
|  | ||||
| <br><br> | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #include "battery_defaults.h" | ||||
| #include "UMBattery.h" | ||||
| @@ -855,7 +857,3 @@ const char UsermodBattery::_preset[]        PROGMEM = "preset"; | ||||
| const char UsermodBattery::_duration[]      PROGMEM = "duration"; | ||||
| const char UsermodBattery::_init[]          PROGMEM = "init"; | ||||
| const char UsermodBattery::_haDiscovery[]   PROGMEM = "HA-discovery"; | ||||
| 
 | ||||
| 
 | ||||
| static UsermodBattery battery; | ||||
| REGISTER_USERMOD(battery); | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|   "name": "Cronixie", | ||||
|   "build": { "libArchive": false } | ||||
| } | ||||
| @@ -4,5 +4,5 @@ This usermod supports driving the Cronixie M and L clock kits by Diamex. | ||||
|  | ||||
| ## Installation  | ||||
|  | ||||
| Compile and upload after adding `Cronixie` to `custom_usermods` of your PlatformIO environment.   | ||||
| Compile and upload after adding `-D USERMOD_CRONIXIE` to `build_flags` of your PlatformIO environment.   | ||||
| Make sure the Auto Brightness Limiter is enabled at 420mA (!) and configure 60 WS281x LEDs. | ||||
| @@ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| 
 | ||||
| class UsermodCronixie : public Usermod { | ||||
| @@ -247,7 +249,7 @@ class UsermodCronixie : public Usermod { | ||||
|          | ||||
|         if (backlight && _digitOut[i] <11) | ||||
|         { | ||||
|           uint32_t col = strip.getSegment(0).colors[1]; | ||||
|           uint32_t col = gamma32(strip.getSegment(0).colors[1]); | ||||
|           for (uint16_t j=o; j< o+10; j++) { | ||||
|             if (j != excl) strip.setPixelColor(j, col); | ||||
|           } | ||||
| @@ -297,7 +299,4 @@ class UsermodCronixie : public Usermod { | ||||
|     { | ||||
|       return USERMOD_ID_CRONIXIE; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static UsermodCronixie cronixie; | ||||
| REGISTER_USERMOD(cronixie); | ||||
| }; | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "name": "DHT", | ||||
|   "build": { "libArchive": false}, | ||||
|   "dependencies": { | ||||
|     "DHT_nonblocking":"https://github.com/alwynallan/DHT_nonblocking" | ||||
|   } | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| ; Options | ||||
| ; ------- | ||||
| ; USERMOD_DHT                      - define this to have this user mod included wled00\usermods_list.cpp | ||||
| ; USERMOD_DHT_DHTTYPE              - DHT model: 11, 21, 22 for DHT11, DHT21, or DHT22, defaults to 22/DHT22 | ||||
| ; USERMOD_DHT_PIN                  - pin to which DTH is connected, defaults to Q2 pin on QuinLed Dig-Uno's board | ||||
| ; USERMOD_DHT_CELSIUS              - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported | ||||
| @@ -10,11 +11,13 @@ | ||||
|  | ||||
| [env:d1_mini_usermod_dht_C] | ||||
| extends = env:d1_mini | ||||
| custom_usermods = ${env:d1_mini.custom_usermods} DHT | ||||
| build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT_CELSIUS | ||||
| build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS | ||||
| lib_deps = ${env:d1_mini.lib_deps} | ||||
|     https://github.com/alwynallan/DHT_nonblocking | ||||
|  | ||||
| [env:custom32_LEDPIN_16_usermod_dht_C] | ||||
| extends = env:custom32_LEDPIN_16 | ||||
| custom_usermods = ${env:custom32_LEDPIN_16.custom_usermods} DHT | ||||
| build_flags = ${env:custom32_LEDPIN_16.build_flags} -D USERMOD_DHT_CELSIUS -D USERMOD_DHT_STATS | ||||
| build_flags = ${env:custom32_LEDPIN_16.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS -D USERMOD_DHT_STATS | ||||
| lib_deps = ${env.lib_deps} | ||||
|     https://github.com/alwynallan/DHT_nonblocking | ||||
|  | ||||
|   | ||||
| @@ -15,6 +15,7 @@ Copy the example `platformio_override.ini` to the root directory.  This file sho | ||||
|  | ||||
| ### Define Your Options | ||||
|  | ||||
| * `USERMOD_DHT`                      - define this to include this user mod wled00\usermods_list.cpp | ||||
| * `USERMOD_DHT_DHTTYPE`              - DHT model: 11, 21, 22 for DHT11, DHT21, or DHT22, defaults to 22/DHT22 | ||||
| * `USERMOD_DHT_PIN`                  - pin to which DTH is connected, defaults to Q2 pin on QuinLed Dig-Uno's board | ||||
| * `USERMOD_DHT_CELSIUS`              - define this to report temperatures in degrees Celsius, otherwise Fahrenheit will be reported | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #ifdef WLED_DISABLE_MQTT | ||||
| #ifndef WLED_ENABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
| 
 | ||||
| @@ -243,7 +245,3 @@ class UsermodDHT : public Usermod { | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| static UsermodDHT dht; | ||||
| REGISTER_USERMOD(dht); | ||||
| @@ -1,5 +0,0 @@ | ||||
| { | ||||
|   "name": "EXAMPLE", | ||||
|   "build": { "libArchive": false }, | ||||
|   "dependencies": {} | ||||
| } | ||||
| @@ -4,6 +4,7 @@ In this usermod file you can find the documentation on how to take advantage of | ||||
| 
 | ||||
| ## Installation  | ||||
| 
 | ||||
| Add `EXAMPLE` to `custom_usermods` in your PlatformIO environment and compile! | ||||
| Copy `usermod_v2_example.h` to the wled00 directory.   | ||||
| Uncomment the corresponding lines in `usermods_list.cpp` and compile!   | ||||
| _(You shouldn't need to actually install this, it does nothing useful)_ | ||||
| 
 | ||||
| @@ -1,8 +1,10 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| 
 | ||||
| /*
 | ||||
|  * Usermods allow you to add own functionality to WLED more easily | ||||
|  * See: https://github.com/wled-dev/WLED/wiki/Add-own-functionality
 | ||||
|  * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
 | ||||
|  *  | ||||
|  * This is an example for a v2 usermod. | ||||
|  * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. | ||||
| @@ -402,6 +404,3 @@ void MyExampleUsermod::publishMqtt(const char* state, bool retain) | ||||
|   } | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static MyExampleUsermod example_usermod; | ||||
| REGISTER_USERMOD(example_usermod); | ||||
| @@ -1,8 +0,0 @@ | ||||
| { | ||||
|   "name:": "EleksTube_IPS", | ||||
|   "build": { "libArchive": false }, | ||||
|   "dependencies": { | ||||
|     "TFT_eSPI" : "2.5.33" | ||||
|   }  | ||||
| } | ||||
| # Seems to add 300kb to the RAM requirement??? | ||||
| @@ -1,3 +1,4 @@ | ||||
| #pragma once | ||||
| #include "TFTs.h" | ||||
| #include "wled.h" | ||||
| 
 | ||||
| @@ -155,7 +156,3 @@ class ElekstubeIPSUsermod : public Usermod { | ||||
| const char ElekstubeIPSUsermod::_name[]         PROGMEM = "EleksTubeIPS"; | ||||
| const char ElekstubeIPSUsermod::_tubeSeg[]      PROGMEM = "tubeSegment"; | ||||
| const char ElekstubeIPSUsermod::_digitOffset[]  PROGMEM = "digitOffset"; | ||||
| 
 | ||||
| 
 | ||||
| static ElekstubeIPSUsermod elekstube_ips; | ||||
| REGISTER_USERMOD(elekstube_ips); | ||||
| @@ -1,12 +1,11 @@ | ||||
| #ifndef WLED_ENABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
|  | ||||
| #include "wled.h" | ||||
| #include <Arduino.h> | ||||
| #include <U8x8lib.h> // from https://github.com/olikraus/u8g2/ | ||||
| #include <DallasTemperature.h> //Dallastemperature sensor | ||||
|  | ||||
| #ifdef WLED_DISABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
|  | ||||
| //The SCL and SDA pins are defined here.  | ||||
| //Lolin32 boards use SCL=5 SDA=4  | ||||
| #define U8X8_PIN_SCL 5 | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| #ifndef WLED_ENABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
|  | ||||
| #include "wled.h" | ||||
| #include <Arduino.h> | ||||
| #include <U8x8lib.h> // from https://github.com/olikraus/u8g2/ | ||||
| #include <Wire.h> | ||||
| #include <BME280I2C.h> //BME280 sensor | ||||
|  | ||||
| #ifdef WLED_DISABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
|  | ||||
| void UpdateBME280Data(); | ||||
|  | ||||
| #define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit  | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|   "name": "Fix_unreachable_netservices_v2", | ||||
|   "platforms": ["espressif8266"] | ||||
| } | ||||
| @@ -30,6 +30,41 @@ The usermod supports the following state changes: | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| 1. Add `Fix_unreachable_netservices` to `custom_usermods` in your PlatformIO environment. | ||||
| 1. Copy the file `usermod_Fix_unreachable_netservices.h` to the `wled00` directory. | ||||
| 2. Register the usermod by adding `#include "usermod_Fix_unreachable_netservices.h"` in the top and `registerUsermod(new FixUnreachableNetServices());` in the bottom of `usermods_list.cpp`. | ||||
|  | ||||
| Example **usermods_list.cpp**: | ||||
|  | ||||
| ```cpp | ||||
| #include "wled.h" | ||||
| /* | ||||
|  * Register your v2 usermods here! | ||||
|  *   (for v1 usermods using just usermod.cpp, you can ignore this file) | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  * Add/uncomment your usermod filename here (and once more below) | ||||
|  * || || || | ||||
|  * \/ \/ \/ | ||||
|  */ | ||||
| //#include "usermod_v2_example.h" | ||||
| //#include "usermod_temperature.h" | ||||
| //#include "usermod_v2_empty.h" | ||||
| #include  "usermod_Fix_unreachable_netservices.h" | ||||
|  | ||||
| void registerUsermods() | ||||
| { | ||||
|   /* | ||||
|    * Add your usermod class name here | ||||
|    * || || || | ||||
|    * \/ \/ \/ | ||||
|    */ | ||||
|   //UsermodManager::add(new MyExampleUsermod()); | ||||
|   //UsermodManager::add(new UsermodTemperature()); | ||||
|   //UsermodManager::add(new UsermodRenameMe()); | ||||
|   UsermodManager::add(new FixUnreachableNetServices()); | ||||
|  | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Hopefully I can help someone with that - @gegu | ||||
|   | ||||
| @@ -1,4 +1,12 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #if defined(ESP32) | ||||
| #warning "Usermod FixUnreachableNetServices works only with ESP8266 builds" | ||||
| class FixUnreachableNetServices : public Usermod | ||||
| { | ||||
| }; | ||||
| #endif | ||||
| 
 | ||||
| #if defined(ESP8266) | ||||
| #include <ping.h> | ||||
| @@ -8,7 +16,7 @@ | ||||
|  * By this procedure the net services of WLED remains accessible in some problematic WLAN environments. | ||||
|  *  | ||||
|  * Usermods allow you to add own functionality to WLED more easily | ||||
|  * See: https://github.com/wled-dev/WLED/wiki/Add-own-functionality
 | ||||
|  * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
 | ||||
|  *  | ||||
|  * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. | ||||
|  * Multiple v2 usermods can be added to one compilation easily. | ||||
| @@ -160,11 +168,4 @@ Delay <input type=\"number\" min=\"5\" max=\"300\" value=\""; | ||||
|     return USERMOD_ID_FIXNETSERVICES; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| static FixUnreachableNetServices fix_unreachable_net_services; | ||||
| REGISTER_USERMOD(fix_unreachable_net_services); | ||||
| 
 | ||||
| #else /* !ESP8266 */ | ||||
| #warning "Usermod FixUnreachableNetServices works only with ESP8266 builds" | ||||
| #endif | ||||
| 
 | ||||
| @@ -22,6 +22,13 @@ The following settings can be configured in the Usermod Menu: | ||||
| - **MqttPublishAlways**: Publish always, regardless if there is a change. | ||||
| - **MqttHomeAssistantDiscovery**: Enable Home Assistant discovery. | ||||
|  | ||||
| ## Dependencies | ||||
|  | ||||
| These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). | ||||
|  | ||||
| - Libraries | ||||
|   - `wollewald/INA226_WE@~1.2.9` (by [wollewald](https://registry.platformio.org/libraries/wollewald/INA226_WE)) | ||||
|   - `Wire` | ||||
|  | ||||
| ## Understanding Samples and Conversion Times | ||||
|  | ||||
| @@ -55,12 +62,16 @@ For detailed programming information and register configurations, refer to the [ | ||||
|  | ||||
| ## Compiling | ||||
|  | ||||
| To enable, compile with `INA226` in `custom_usermods` (e.g. in `platformio_override.ini`). | ||||
| To enable, compile with `USERMOD_INA226` defined (e.g. in `platformio_override.ini`). | ||||
|  | ||||
| ```ini | ||||
| [env:ina226_example] | ||||
| extends = env:esp32dev | ||||
| custom_usermods = ${env:esp32dev.custom_usermods} INA226 | ||||
| build_flags = ${env:esp32dev.build_flags} | ||||
| build_flags = | ||||
|   ${common.build_flags} ${esp32.build_flags} | ||||
|   -D USERMOD_INA226 | ||||
|   ; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal | ||||
| lib_deps =  | ||||
|   ${esp32.lib_deps} | ||||
|   wollewald/INA226_WE@~1.2.9 | ||||
| ``` | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "name": "INA226_v2", | ||||
|   "build": { "libArchive": false }, | ||||
|   "dependencies": { | ||||
|     "wollewald/INA226_WE":"~1.2.9" | ||||
|   } | ||||
| } | ||||
| @@ -1,6 +1,9 @@ | ||||
| [env:ina226_example] | ||||
| extends = env:esp32dev | ||||
| custom_usermods = ${env:esp32dev.custom_usermods} INA226_v2 | ||||
| build_flags = | ||||
|   ${env:esp32dev.build_flags} | ||||
|   ${common.build_flags} ${esp32.build_flags} | ||||
|   -D USERMOD_INA226 | ||||
|   ; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal | ||||
| lib_deps =  | ||||
|   ${esp32.lib_deps} | ||||
|   wollewald/INA226_WE@~1.2.9 | ||||
| @@ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #include <INA226_WE.h> | ||||
| 
 | ||||
| @@ -208,6 +210,12 @@ private: | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     ~UsermodINA226() | ||||
|     { | ||||
|         delete _ina226; | ||||
|         _ina226 = nullptr; | ||||
|     } | ||||
| 
 | ||||
| #ifndef WLED_DISABLE_MQTT | ||||
|     void mqttInitialize() | ||||
|     { | ||||
| @@ -543,17 +551,6 @@ public: | ||||
|         _initDone = true; | ||||
|         return configComplete; | ||||
|     } | ||||
| 
 | ||||
|     ~UsermodINA226() | ||||
|     { | ||||
|         delete _ina226; | ||||
|         _ina226 = nullptr; | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| const char UsermodINA226::_name[] PROGMEM = "INA226"; | ||||
| 
 | ||||
| 
 | ||||
| static UsermodINA226 ina226_v2; | ||||
| REGISTER_USERMOD(ina226_v2); | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|   "name": "Internal_Temperature_v2", | ||||
|   "build": { "libArchive": false } | ||||
| } | ||||
| @@ -23,7 +23,8 @@ | ||||
|  | ||||
|  | ||||
| ## Installation | ||||
| - Add `Internal_Temperature` to `custom_usermods` in your `platformio.ini` (or `platformio_override.ini`). | ||||
| - Add a build flag `-D USERMOD_INTERNAL_TEMPERATURE` to your `platformio.ini` (or `platformio_override.ini`). | ||||
|  | ||||
|  | ||||
| ## 📝 Change Log | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| 
 | ||||
| class InternalTemperatureUsermod : public Usermod | ||||
| @@ -48,7 +50,7 @@ public: | ||||
| #else                                    // ESP32 ESP32S3 and ESP32C3
 | ||||
|     temperature = roundf(temperatureRead() * 10) / 10; | ||||
| #endif | ||||
|  if(presetToActivate != 0){ | ||||
| 
 | ||||
|     // Check if temperature has exceeded the activation threshold
 | ||||
|     if (temperature >= activationThreshold) { | ||||
|       // Update the state flag if not already set
 | ||||
| @@ -56,7 +58,7 @@ public: | ||||
|         isAboveThreshold = true; | ||||
|         } | ||||
|       // Check if a 'high temperature' preset is configured and it's not already active
 | ||||
|       if (currentPreset != presetToActivate) { | ||||
|       if (presetToActivate != 0 && currentPreset != presetToActivate) { | ||||
|         // If a playlist is active, store it for reactivation later
 | ||||
|         if (currentPlaylist > 0) { | ||||
|           previousPlaylist = currentPlaylist; | ||||
| @@ -99,7 +101,6 @@ public: | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  } | ||||
| 
 | ||||
| #ifndef WLED_DISABLE_MQTT | ||||
|     if (WLED_MQTT_CONNECTED) | ||||
| @@ -191,7 +192,4 @@ void InternalTemperatureUsermod::publishMqtt(const char *state, bool retain) | ||||
|     mqtt->publish(subuf, 0, retain, state); | ||||
|   } | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static InternalTemperatureUsermod internal_temperature_v2; | ||||
| REGISTER_USERMOD(internal_temperature_v2); | ||||
| } | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "name": "LD2410_v2", | ||||
|   "build": { "libArchive": false }, | ||||
|   "dependencies": { | ||||
|     "ncmreynolds/ld2410":"^0.1.3" | ||||
|   } | ||||
| } | ||||
| @@ -10,15 +10,21 @@ The movement and presence state are displayed in both the Info section of the we | ||||
| ## Dependencies | ||||
| - Libraries | ||||
|   - `ncmreynolds/ld2410@^0.1.3` | ||||
|   - This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`). | ||||
| - Data is published over MQTT - make sure you've enabled the MQTT sync interface. | ||||
|  | ||||
| ## Compilation | ||||
|  | ||||
| To enable, compile with `LD2140` in `custom_usermods` (e.g. in `platformio_override.ini`) | ||||
| To enable, compile with `USERMOD_LD2410` defined  (e.g. in `platformio_override.ini`) | ||||
| ```ini | ||||
| [env:usermod_USERMOD_LD2410_esp32dev] | ||||
| extends = env:esp32dev | ||||
| custom_usermods = ${env:esp32dev.custom_usermods} LD2140_v2 | ||||
| build_flags = | ||||
|     ${common.build_flags_esp32} | ||||
|     -D USERMOD_LD2410 | ||||
| lib_deps =  | ||||
|     ${esp32.lib_deps} | ||||
|     ncmreynolds/ld2410@^0.1.3 | ||||
| ``` | ||||
|  | ||||
| ### Configuration Options | ||||
|   | ||||
| @@ -1,10 +1,14 @@ | ||||
| #include "wled.h" | ||||
| #include <ld2410.h> | ||||
| #warning **** Included USERMOD_LD2410 **** | ||||
| 
 | ||||
| #ifdef WLED_DISABLE_MQTT | ||||
| #ifndef WLED_ENABLE_MQTT | ||||
| #error "This user mod requires MQTT to be enabled." | ||||
| #endif | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #include <ld2410.h> | ||||
| 
 | ||||
| class LD2410Usermod : public Usermod { | ||||
| 
 | ||||
|   private: | ||||
| @@ -231,7 +235,3 @@ void LD2410Usermod::publishMqtt(const char* topic, const char* state, bool retai | ||||
|   } | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static LD2410Usermod ld2410_v2; | ||||
| REGISTER_USERMOD(ld2410_v2); | ||||
| @@ -2,14 +2,13 @@ | ||||
| This usermod will obtain readings from a Light Dependent Resistor (LDR) and will turn on/off specific presets based on those readings. This is useful for exterior lighting situations where you want the lights to only be on when it is dark out. | ||||
|  | ||||
| # Installation | ||||
| Add "LDR_Dusk_Dawn" to your platformio.ini environment's custom_usermods and build. | ||||
| Add "-D USERMOD_LDR_DUSK_DAWN" to your platformio.ini [common] build_flags and build. | ||||
|  | ||||
| Example: | ||||
| ``` | ||||
| [env:usermod_LDR_Dusk_Dawn_esp32dev] | ||||
| extends = env:esp32dev | ||||
| custom_usermods = ${env:esp32dev.custom_usermods}  | ||||
|   LDR_Dusk_Dawn   # Enable LDR Dusk Dawn Usermod | ||||
| [common] | ||||
| build_flags = | ||||
|   -D USERMOD_LDR_DUSK_DAWN   # Enable LDR Dusk Dawn Usermod | ||||
| ``` | ||||
|  | ||||
| # Usermod Settings | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|   "name": "LDR_Dusk_Dawn_v2", | ||||
|   "build": { "libArchive": false } | ||||
| } | ||||
| @@ -1,3 +1,4 @@ | ||||
| #pragma once | ||||
| #include "wled.h" | ||||
| 
 | ||||
| #ifndef ARDUINO_ARCH_ESP32 | ||||
| @@ -150,7 +151,3 @@ class LDR_Dusk_Dawn_v2 : public Usermod { | ||||
| }; | ||||
| 
 | ||||
| const char LDR_Dusk_Dawn_v2::_name[]    PROGMEM = "LDR_Dusk_Dawn_v2"; | ||||
| 
 | ||||
| 
 | ||||
| static LDR_Dusk_Dawn_v2 ldr_dusk_dawn_v2; | ||||
| REGISTER_USERMOD(ldr_dusk_dawn_v2); | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "name": "MAX17048_v2", | ||||
|   "build": { "libArchive": false}, | ||||
|   "dependencies": { | ||||
|     "Adafruit_MAX1704X":"https://github.com/adafruit/Adafruit_MAX1704X#1.0.2" | ||||
|   } | ||||
| } | ||||
| @@ -5,16 +5,26 @@ This usermod reads information from an Adafruit MAX17048  and outputs the follow | ||||
|  | ||||
|  | ||||
| ## Dependencies | ||||
| Libraries: | ||||
| - `Adafruit_BusIO@~1.14.5` (by [adafruit](https://github.com/adafruit/Adafruit_BusIO)) | ||||
| - `Adafruit_MAX1704X@~1.0.2` (by [adafruit](https://github.com/adafruit/Adafruit_MAX1704X)) | ||||
|  | ||||
| These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). | ||||
| Data is published over MQTT - make sure you've enabled the MQTT sync interface. | ||||
|  | ||||
| ## Compilation | ||||
|  | ||||
| Add "MAX17048_v2" to your platformio.ini environment's custom_usermods and build. | ||||
| To enable, compile with `USERMOD_MAX17048` define in the build_flags (e.g. in `platformio.ini` or `platformio_override.ini`) such as in the example below: | ||||
| ```ini | ||||
| [env:usermod_max17048_d1_mini] | ||||
| extends = env:d1_mini | ||||
| custom_usermods = ${env:d1_mini.custom_usermods} MAX17048_v2 | ||||
| build_flags = | ||||
|   ${common.build_flags_esp8266} | ||||
|   -D USERMOD_MAX17048 | ||||
| lib_deps =  | ||||
|   ${esp8266.lib_deps} | ||||
|   https://github.com/adafruit/Adafruit_BusIO @ 1.14.5 | ||||
|   https://github.com/adafruit/Adafruit_MAX1704X @ 1.0.2 | ||||
| ``` | ||||
|  | ||||
| ### Configuration Options | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| // force the compiler to show a warning to confirm that this file is included
 | ||||
| #warning **** Included USERMOD_MAX17048 V2.0 **** | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #include "Adafruit_MAX1704X.h" | ||||
| 
 | ||||
| @@ -35,8 +37,8 @@ class  Usermod_MAX17048 : public Usermod { | ||||
|     unsigned long lastSend = UINT32_MAX - (USERMOD_MAX17048_MAX_MONITOR_INTERVAL - USERMOD_MAX17048_FIRST_MONITOR_AT); | ||||
| 
 | ||||
| 
 | ||||
|     unsigned VoltageDecimals = 3;  // Number of decimal places in published voltage values
 | ||||
|     unsigned PercentDecimals = 1;  // Number of decimal places in published percent values
 | ||||
|     uint8_t  VoltageDecimals = 3;  // Number of decimal places in published voltage values
 | ||||
|     uint8_t  PercentDecimals = 1;    // Number of decimal places in published percent values
 | ||||
| 
 | ||||
|     // string that are used multiple time (this will save some flash memory)
 | ||||
|     static const char _name[]; | ||||
| @@ -277,7 +279,3 @@ const char Usermod_MAX17048::_enabled[] PROGMEM = "enabled"; | ||||
| const char Usermod_MAX17048::_maxReadInterval[] PROGMEM = "max-read-interval-ms"; | ||||
| const char Usermod_MAX17048::_minReadInterval[] PROGMEM = "min-read-interval-ms"; | ||||
| const char Usermod_MAX17048::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscovery"; | ||||
| 
 | ||||
| 
 | ||||
| static Usermod_MAX17048 max17048_v2; | ||||
| REGISTER_USERMOD(max17048_v2); | ||||
| @@ -1,5 +0,0 @@ | ||||
| { | ||||
|   "name": "MY9291", | ||||
|   "build": { "libArchive": false }, | ||||
|   "platforms": ["espressif8266"] | ||||
| } | ||||
| @@ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| #include "MY92xx.h" | ||||
| 
 | ||||
| @@ -40,7 +42,4 @@ class MY9291Usermod : public Usermod { | ||||
|     uint16_t getId() { | ||||
|       return USERMOD_ID_MY9291; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static MY9291Usermod my9291; | ||||
| REGISTER_USERMOD(my9291); | ||||
| }; | ||||
| @@ -42,7 +42,7 @@ | ||||
|  *  | ||||
|  *  | ||||
|  * Usermods allow you to add own functionality to WLED more easily | ||||
|  * See: https://github.com/wled-dev/WLED/wiki/Add-own-functionality | ||||
|  * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality | ||||
|  *  | ||||
|  * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. | ||||
|  * Multiple v2 usermods can be added to one compilation easily. | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|   "name": "PIR_sensor_switch", | ||||
|   "build": { "libArchive": false } | ||||
| } | ||||
| @@ -5,7 +5,7 @@ This usermod-v2 modification allows the connection of a PIR sensor to switch on | ||||
| _Story:_ | ||||
|  | ||||
| I use the PIR Sensor to automatically turn on the WLED analog clock in my home office room when I am there. | ||||
| The LED strip is switched [using a relay](https://kno.wled.ge/features/relay-control/) to keep the power consumption low when it is switched off. | ||||
| The LED strip is switched [using a relay](https://github.com/Aircoookie/WLED/wiki/Control-a-relay-with-WLED) to keep the power consumption low when it is switched off. | ||||
|  | ||||
| ## Web interface | ||||
|  | ||||
| @@ -25,7 +25,7 @@ You can also use usermod's off timer instead of sensor's. In such case rotate th | ||||
|  | ||||
| **NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionally `-D PIR_SENSOR_PIN=16` to override default pin. You can also change the default off time by adding `-D PIR_SENSOR_OFF_SEC=30`. | ||||
|  | ||||
| ## API to enable/disable the PIR sensor from outside. For example from another usermod | ||||
| ## API to enable/disable the PIR sensor from outside. For example from another usermod: | ||||
|  | ||||
| To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available. | ||||
|  | ||||
| @@ -33,16 +33,15 @@ When the PIR sensor state changes an MQTT message is broadcasted with topic `wle | ||||
| Usermod can also be configured to send just the MQTT message but not change WLED state using settings page as well as responding to motion only at night | ||||
| (assuming NTP and latitude/longitude are set to determine sunrise/sunset times). | ||||
|  | ||||
| ### There are two options to get access to the usermod instance | ||||
| ### There are two options to get access to the usermod instance: | ||||
|  | ||||
| _1._ Include `usermod_PIR_sensor_switch.h` **before** you include other usermods in `usermods_list.cpp' | ||||
| 1. Include `usermod_PIR_sensor_switch.h` **before** you include other usermods in `usermods_list.cpp' | ||||
|  | ||||
| or | ||||
|  | ||||
| _2._ Use `#include "usermod_PIR_sensor_switch.h"` at the top of the `usermod.h` where you need it. | ||||
| 2. Use `#include "usermod_PIR_sensor_switch.h"` at the top of the `usermod.h` where you need it. | ||||
|  | ||||
| **Example usermod.h :** | ||||
|  | ||||
| ```cpp | ||||
| #include "wled.h" | ||||
|  | ||||
| @@ -80,30 +79,25 @@ Usermod can be configured via the Usermods settings page. | ||||
| * `override` - override PIR input when WLED state is changed using UI | ||||
| * `domoticz-idx` - Domoticz virtual switch ID (used with MQTT `domoticz/in`) | ||||
|  | ||||
|  | ||||
| Have fun - @gegu & @blazoncek | ||||
|  | ||||
| ## Change log | ||||
|  | ||||
| 2021-04 | ||||
|  | ||||
| * Adaptation for runtime configuration. | ||||
|  | ||||
| 2021-11 | ||||
|  | ||||
| * Added information about dynamic configuration options | ||||
| * Added option to temporary enable/disable usermod from WLED UI (Info dialog) | ||||
|  | ||||
| 2022-11 | ||||
|  | ||||
| * Added compile time option for off timer. | ||||
| * Added Home Assistant autodiscovery MQTT broadcast. | ||||
| * Updated info on compiling. | ||||
|  | ||||
| 2023-?? | ||||
|  | ||||
| * Override option | ||||
| * Domoticz virtual switch ID (used with MQTT `domoticz/in`) | ||||
|  | ||||
| 2024-02 | ||||
|  | ||||
| * Added compile time option to expand number of PIR sensors (they are logically ORed) `-D PIR_SENSOR_MAX_SENSORS=3` | ||||
| * Added compile time option to expand number of PIR sensors (they are logically ORed) `-D PIR_SENSOR_MAX_SENSORS=3` | ||||
| @@ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "wled.h" | ||||
| 
 | ||||
| #ifndef PIR_SENSOR_PIN | ||||
| @@ -24,7 +26,7 @@ | ||||
|  * Maintained by: @blazoncek | ||||
|  *  | ||||
|  * Usermods allow you to add own functionality to WLED more easily | ||||
|  * See: https://github.com/wled-dev/WLED/wiki/Add-own-functionality
 | ||||
|  * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
 | ||||
|  *  | ||||
|  * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. | ||||
|  * Multiple v2 usermods can be added to one compilation easily. | ||||
| @@ -569,7 +571,3 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) | ||||
|   // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
 | ||||
|   return !(pins.isNull() || pins.size() != PIR_SENSOR_MAX_SENSORS); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static PIRsensorSwitch pir_sensor_switch; | ||||
| REGISTER_USERMOD(pir_sensor_switch); | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "name": "PWM_fan", | ||||
|   "build": { | ||||
|     "libArchive": false, | ||||
|     "extraScript": "setup_deps.py" | ||||
|   } | ||||
| } | ||||
| @@ -11,8 +11,8 @@ If the _tachometer_ is supported, the current speed (in RPM) will be displayed o | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| Add the `PWM_fan` to `custom_usermods` in your `platformio.ini` (or `platformio_override.ini`) | ||||
| You will also need `Temperature` or `sht`. | ||||
| Add the compile-time option `-D USERMOD_PWM_FAN` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_PWM_FAN` in `myconfig.h`. | ||||
| You will also need `-D USERMOD_DALLASTEMPERATURE`. | ||||
|  | ||||
| ### Define Your Options | ||||
|  | ||||
| @@ -40,9 +40,6 @@ If the fan speed is unlocked, it will revert to temperature controlled speed on | ||||
| ## Change Log | ||||
|  | ||||
| 2021-10 | ||||
|  | ||||
| * First public release | ||||
|  | ||||
| 2022-05 | ||||
|  | ||||
| * Added JSON API call to allow changing of speed | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user