mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-01 07:31:51 +00:00 
			
		
		
		
	Compare commits
	
		
			308 Commits
		
	
	
		
			2023.3.0b3
			...
			2023.5.0b1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c7c9c49f4e | ||
|  | 2c160a8a25 | ||
|  | cc576cf1a9 | ||
|  | 97a71482a9 | ||
|  | 8822b6c808 | ||
|  | 5099595aee | ||
|  | 39a650ee54 | ||
|  | b19c7d462b | ||
|  | a8b821c213 | ||
|  | 535003014b | ||
|  | 3c05ae4e1a | ||
|  | c835b67bac | ||
|  | 8c32941428 | ||
|  | b5dac00dcb | ||
|  | ffdc721c79 | ||
|  | 0828a9fc11 | ||
|  | 4b664b6f09 | ||
|  | 679633245d | ||
|  | ce8a77c765 | ||
|  | 2f78c4acfa | ||
|  | 72f6841aac | ||
|  | 10bd9b14fc | ||
|  | 3498aade85 | ||
|  | c4539e10fb | ||
|  | 2b3052e9d7 | ||
|  | e725e15f7a | ||
|  | 4d1113e265 | ||
|  | 52352ac27a | ||
|  | f60b2b754d | ||
|  | 4a3f9712b2 | ||
|  | fb094fca0f | ||
|  | de10b356cf | ||
|  | bd6d6caa8a | ||
|  | 1c4af08ed3 | ||
|  | c97d361b6c | ||
|  | 379b1d84dd | ||
|  | c13e20643b | ||
|  | 76b6fcf554 | ||
|  | 57e909e790 | ||
|  | d6f7876e68 | ||
|  | 56e0923c22 | ||
|  | f4b98f5e32 | ||
|  | 2d56b70a36 | ||
|  | 980cfaf295 | ||
|  | c2a43c733a | ||
|  | 568e65a6ab | ||
|  | 59d6b3afa0 | ||
|  | b89c04b928 | ||
|  | 12090657bb | ||
|  | 4e21cf0bdd | ||
|  | ba4ef72d56 | ||
|  | f3e6a4314f | ||
|  | e14ce3d950 | ||
|  | 70aa38f5bd | ||
|  | 55ec082628 | ||
|  | 6f27126c8d | ||
|  | ee21a91313 | ||
|  | c5efaa1c00 | ||
|  | 6476357596 | ||
|  | 77f71acbc8 | ||
|  | 4a08a5413d | ||
|  | e3d89cc6b6 | ||
|  | 64afb07e91 | ||
|  | f639f7c280 | ||
|  | 986dd2ddd2 | ||
|  | 7abdb5d046 | ||
|  | 0f1e186189 | ||
|  | 96d208e0d8 | ||
|  | 38ed38864e | ||
|  | a12ba7bd38 | ||
|  | 4a177e3931 | ||
|  | bef5b38d49 | ||
|  | 0a95f116fc | ||
|  | 327cd662b4 | ||
|  | bb05ba3d00 | ||
|  | c0ad5d1d16 | ||
|  | 4c39631428 | ||
|  | afc2b3b74f | ||
|  | 19fc1417ae | ||
|  | e2fefa51f5 | ||
|  | f668d5617f | ||
|  | 47c4ff15d6 | ||
|  | ccf1bdc0b4 | ||
|  | e993fcf80c | ||
|  | 1bdc30a09e | ||
|  | 0f7e34e7ec | ||
|  | f56e89597f | ||
|  | 9460fb28c4 | ||
|  | 7207b9734f | ||
|  | 2be703b329 | ||
|  | 4cea74ef3b | ||
|  | 3be3267d06 | ||
|  | 98db604dba | ||
|  | ebf6f8c6de | ||
|  | 2ebacad398 | ||
|  | 53c59cf675 | ||
|  | 9da261cb39 | ||
|  | 3a587ea0d4 | ||
|  | 8a60919e1f | ||
|  | 382dcddf12 | ||
|  | 6b67acbeb5 | ||
|  | 7b0fca6824 | ||
|  | 47555d314a | ||
|  | 0643b71908 | ||
|  | afc848bf22 | ||
|  | 9fbbcd6d8a | ||
|  | cc1eb648f9 | ||
|  | 04a139fe3d | ||
|  | 1a86167a47 | ||
|  | 614ed7fd0c | ||
|  | 4eb69d6af5 | ||
|  | 0547f2a931 | ||
|  | b5fbe0b145 | ||
|  | 443c3c2a56 | ||
|  | d75daa9644 | ||
|  | 7963abb27a | ||
|  | 0b9e8fda34 | ||
|  | a3cacc0c8b | ||
|  | 5a4840f641 | ||
|  | 3d7d689040 | ||
|  | b60c08dd28 | ||
|  | 80bc567c31 | ||
|  | 888ac2e180 | ||
|  | 421ebcc8b2 | ||
|  | b56fa8c50a | ||
|  | 42401775e1 | ||
|  | 9c9bc58c16 | ||
|  | fbc129cccc | ||
|  | 99638190cb | ||
|  | d78e9e6aa8 | ||
|  | 878155a03d | ||
|  | 9922eb83e2 | ||
|  | 79f861f012 | ||
|  | 4faa9d109e | ||
|  | 28534ecc61 | ||
|  | 616e0a21d8 | ||
|  | a546ffd490 | ||
|  | b5d0aede38 | ||
|  | a014d853a4 | ||
|  | c4ddf7697d | ||
|  | a2931b6774 | ||
|  | 3ac7bf3761 | ||
|  | 922344811f | ||
|  | cb2fcaa9b1 | ||
|  | 1f50bd0649 | ||
|  | e4b2de5c68 | ||
|  | f862b479e7 | ||
|  | 358c59bd8d | ||
|  | 74fe135c9c | ||
|  | 8d3896172d | ||
|  | 9d9725144d | ||
|  | 06f83bf1c0 | ||
|  | c2756d57d8 | ||
|  | 56504692af | ||
|  | e542e75b9e | ||
|  | 806e43c34c | ||
|  | 29e7d00894 | ||
|  | 9ee661c1e4 | ||
|  | 36c0e2416d | ||
|  | e4ba3ff1db | ||
|  | be69b49880 | ||
|  | cc317d27f5 | ||
|  | c16709ed95 | ||
|  | e13eaf6706 | ||
|  | a1eb3b8475 | ||
|  | d52e425ba2 | ||
|  | dd8dc1ef1d | ||
|  | bc427de16a | ||
|  | db5988bbe1 | ||
|  | a3875af4b4 | ||
|  | d70e7da0ef | ||
|  | d42f35de5d | ||
|  | cd57469e06 | ||
|  | d98d6ff45f | ||
|  | 14e38f0469 | ||
|  | f0f6d3f1cd | ||
|  | 0b383542da | ||
|  | b2cec10601 | ||
|  | 48658d5a55 | ||
|  | 5207ca1d52 | ||
|  | 7196fb8e82 | ||
|  | 48ada2eebb | ||
|  | 0de5808ed2 | ||
|  | ebc544e4b4 | ||
|  | a31fb3c987 | ||
|  | dfc7cd7f5d | ||
|  | a8bb2a42a1 | ||
|  | 3d4c0e6667 | ||
|  | 25fb288016 | ||
|  | 1b8b8cdd11 | ||
|  | e6737479f7 | ||
|  | 7f75832bf1 | ||
|  | 33339e3bd8 | ||
|  | c298c1166f | ||
|  | c3d9eef01f | ||
|  | 5ffdc66864 | ||
|  | 2f50e18eb5 | ||
|  | 9922c1503a | ||
|  | fce99d4b17 | ||
|  | 11567085d8 | ||
|  | 83f8e84247 | ||
|  | 215107e8ea | ||
|  | d3f2b93c42 | ||
|  | 11eb5cb0fa | ||
|  | 9a7af97b2d | ||
|  | 5e11469f50 | ||
|  | 0c7a3d1fff | ||
|  | 8a705bf4b0 | ||
|  | ee7102fcd1 | ||
|  | a44e38300b | ||
|  | b00e20c29f | ||
|  | d642aeba0f | ||
|  | 6a6aee510d | ||
|  | ea17a92dbc | ||
|  | 32a0a60480 | ||
|  | 5a56644702 | ||
|  | 29113808ee | ||
|  | 6471361715 | ||
|  | 01687a9d57 | ||
|  | 801fbf44c5 | ||
|  | ba1416cc0e | ||
|  | afc1c83af4 | ||
|  | da056866ff | ||
|  | 336c2d34e6 | ||
|  | f3a969d35c | ||
|  | c12dd77c64 | ||
|  | 4f138c600b | ||
|  | c037e95861 | ||
|  | 2e1b35959f | ||
|  | 7f46d9e0f9 | ||
|  | 069b5f81a0 | ||
|  | 3a36d0b13f | ||
|  | f0760e99b7 | ||
|  | 18fecf8c09 | ||
|  | 414cf1b333 | ||
|  | d10f891f51 | ||
|  | b5927322e6 | ||
|  | 1cf4107e1c | ||
|  | c12408326c | ||
|  | 4434e59e5a | ||
|  | 45180d98f6 | ||
|  | 44494ad18e | ||
|  | 1447536906 | ||
|  | 27ec517084 | ||
|  | ce1f034bac | ||
|  | f1f96f16e9 | ||
|  | 4af4649e23 | ||
|  | 8bcddef39d | ||
|  | 4ac96ccea2 | ||
|  | 3c5de77ae9 | ||
|  | a2925b1d37 | ||
|  | 73748e9e20 | ||
|  | 75c9823899 | ||
|  | c8c0bd3351 | ||
|  | e1cdeb7c8f | ||
|  | 7f97f42552 | ||
|  | aa7f3569ec | ||
|  | 2d0a08442e | ||
|  | d2380756b2 | ||
|  | 925e3cb6c9 | ||
|  | 6757acba56 | ||
|  | 5cc91cdd95 | ||
|  | 2b41886819 | ||
|  | 72c6efd6a0 | ||
|  | a1f1804112 | ||
|  | a8b1ceb4e9 | ||
|  | 4fb0f7f8c6 | ||
|  | 958cadeca8 | ||
|  | 00f2655f1a | ||
|  | 074f5029eb | ||
|  | 1691976587 | ||
|  | 60e6b4d21e | ||
|  | 5750591df2 | ||
|  | a75da54455 | ||
|  | de7f6c3f5f | ||
|  | 4245480656 | ||
|  | 1824c8131e | ||
|  | 4e9606d2e0 | ||
|  | 78500fa933 | ||
|  | 9c69b98a49 | ||
|  | e6d8ef98d3 | ||
|  | 3f1af1690b | ||
|  | 84374b6b1e | ||
|  | 391316c9b5 | ||
|  | 705c62ebd7 | ||
|  | 7209dd8bae | ||
|  | ab736c89bb | ||
|  | 6911639617 | ||
|  | b9720d0715 | ||
|  | 47b3267ed4 | ||
|  | e16ba2adb5 | ||
|  | 0a19b1e32c | ||
|  | bae9a950c0 | ||
|  | 72b2943332 | ||
|  | 4ec0ef7548 | ||
|  | 25bc6761f6 | ||
|  | 81b6562c25 | ||
|  | ae74189fc2 | ||
|  | 9e516efe10 | ||
|  | 366e29439e | ||
|  | 1c9c700d7f | ||
|  | b2e6b9d31f | ||
|  | 7623f63846 | ||
|  | 2bfaf9dce3 | ||
|  | 5c2c1560bb | ||
|  | f7096ab78e | ||
|  | 98f8feb625 | ||
|  | 9944ca414e | 
| @@ -4,53 +4,60 @@ | ||||
|   "postCreateCommand": [ | ||||
|     "script/devcontainer-post-create" | ||||
|   ], | ||||
|   "containerEnv": { | ||||
|     "DEVCONTAINER": "1" | ||||
|   }, | ||||
|   "runArgs": [ | ||||
|     "--privileged", | ||||
|     "-e", | ||||
|     "ESPHOME_DASHBOARD_USE_PING=1" | ||||
|   ], | ||||
|   "appPort": 6052, | ||||
|   "extensions": [ | ||||
|     // python | ||||
|     "ms-python.python", | ||||
|     "visualstudioexptteam.vscodeintellicode", | ||||
|     // yaml | ||||
|     "redhat.vscode-yaml", | ||||
|     // cpp | ||||
|     "ms-vscode.cpptools", | ||||
|     // editorconfig | ||||
|     "editorconfig.editorconfig", | ||||
|   ], | ||||
|   "settings": { | ||||
|     "python.languageServer": "Pylance", | ||||
|     "python.pythonPath": "/usr/bin/python3", | ||||
|     "python.linting.pylintEnabled": true, | ||||
|     "python.linting.enabled": true, | ||||
|     "python.formatting.provider": "black", | ||||
|     "editor.formatOnPaste": false, | ||||
|     "editor.formatOnSave": true, | ||||
|     "editor.formatOnType": true, | ||||
|     "files.trimTrailingWhitespace": true, | ||||
|     "terminal.integrated.defaultProfile.linux": "bash", | ||||
|     "yaml.customTags": [ | ||||
|       "!secret scalar", | ||||
|       "!lambda scalar", | ||||
|       "!include_dir_named scalar", | ||||
|       "!include_dir_list scalar", | ||||
|       "!include_dir_merge_list scalar", | ||||
|       "!include_dir_merge_named scalar" | ||||
|     ], | ||||
|     "files.exclude": { | ||||
|       "**/.git": true, | ||||
|       "**/.DS_Store": true, | ||||
|       "**/*.pyc": { | ||||
|         "when": "$(basename).py" | ||||
|       }, | ||||
|       "**/__pycache__": true | ||||
|     }, | ||||
|     "files.associations": { | ||||
|       "**/.vscode/*.json": "jsonc" | ||||
|     }, | ||||
|     "C_Cpp.clang_format_path": "/usr/bin/clang-format-11", | ||||
|   "customizations": { | ||||
|     "vscode": { | ||||
|       "extensions": [ | ||||
|         // python | ||||
|         "ms-python.python", | ||||
|         "visualstudioexptteam.vscodeintellicode", | ||||
|         // yaml | ||||
|         "redhat.vscode-yaml", | ||||
|         // cpp | ||||
|         "ms-vscode.cpptools", | ||||
|         // editorconfig | ||||
|         "editorconfig.editorconfig", | ||||
|       ], | ||||
|       "settings": { | ||||
|         "python.languageServer": "Pylance", | ||||
|         "python.pythonPath": "/usr/bin/python3", | ||||
|         "python.linting.pylintEnabled": true, | ||||
|         "python.linting.enabled": true, | ||||
|         "python.formatting.provider": "black", | ||||
|         "editor.formatOnPaste": false, | ||||
|         "editor.formatOnSave": true, | ||||
|         "editor.formatOnType": true, | ||||
|         "files.trimTrailingWhitespace": true, | ||||
|         "terminal.integrated.defaultProfile.linux": "bash", | ||||
|         "yaml.customTags": [ | ||||
|           "!secret scalar", | ||||
|           "!lambda scalar", | ||||
|           "!include_dir_named scalar", | ||||
|           "!include_dir_list scalar", | ||||
|           "!include_dir_merge_list scalar", | ||||
|           "!include_dir_merge_named scalar" | ||||
|         ], | ||||
|         "files.exclude": { | ||||
|           "**/.git": true, | ||||
|           "**/.DS_Store": true, | ||||
|           "**/*.pyc": { | ||||
|             "when": "$(basename).py" | ||||
|           }, | ||||
|           "**/__pycache__": true | ||||
|         }, | ||||
|         "files.associations": { | ||||
|           "**/.vscode/*.json": "jsonc" | ||||
|         }, | ||||
|         "C_Cpp.clang_format_path": "/usr/bin/clang-format-13" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,6 +11,7 @@ on: | ||||
|       - ".github/workflows/**" | ||||
|       - "requirements*.txt" | ||||
|       - "platformio.ini" | ||||
|       - "script/platformio_install_deps.py" | ||||
|  | ||||
|   pull_request: | ||||
|     paths: | ||||
| @@ -18,6 +19,7 @@ on: | ||||
|       - ".github/workflows/**" | ||||
|       - "requirements*.txt" | ||||
|       - "platformio.ini" | ||||
|       - "script/platformio_install_deps.py" | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|   | ||||
							
								
								
									
										7
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,6 +23,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       max-parallel: 5 | ||||
|       matrix: | ||||
|         include: | ||||
|           - id: ci-custom | ||||
| @@ -41,6 +42,10 @@ jobs: | ||||
|             file: tests/test3.yaml | ||||
|             name: Test tests/test3.yaml | ||||
|             pio_cache_key: test3 | ||||
|           - id: test | ||||
|             file: tests/test3.1.yaml | ||||
|             name: Test tests/test3.1.yaml | ||||
|             pio_cache_key: test3.1 | ||||
|           - id: test | ||||
|             file: tests/test4.yaml | ||||
|             name: Test tests/test4.yaml | ||||
| @@ -129,7 +134,7 @@ jobs: | ||||
|       - name: Install clang tools | ||||
|         run: | | ||||
|           sudo apt-get install \ | ||||
|               clang-format-11 \ | ||||
|               clang-format-13 \ | ||||
|               clang-tidy-11 | ||||
|         if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format' | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -117,7 +117,7 @@ jobs: | ||||
|             --suffix "${{ matrix.image.suffix }}" | ||||
|  | ||||
|       - name: Build and push | ||||
|         uses: docker/build-push-action@v3 | ||||
|         uses: docker/build-push-action@v4 | ||||
|         with: | ||||
|           context: . | ||||
|           file: ./docker/Dockerfile | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,7 +18,7 @@ jobs: | ||||
|   stale: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/stale@v7 | ||||
|       - uses: actions/stale@v8 | ||||
|         with: | ||||
|           days-before-pr-stale: 90 | ||||
|           days-before-pr-close: 7 | ||||
| @@ -38,7 +38,7 @@ jobs: | ||||
|   close-issues: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/stale@v7 | ||||
|       - uses: actions/stale@v8 | ||||
|         with: | ||||
|           days-before-pr-stale: -1 | ||||
|           days-before-pr-close: -1 | ||||
|   | ||||
							
								
								
									
										60
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| --- | ||||
| name: Synchronise Device Classes from Home Assistant | ||||
|  | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|   schedule: | ||||
|     - cron: '45 6 * * *' | ||||
|  | ||||
| permissions: | ||||
|   contents: write | ||||
|   pull-requests: write | ||||
|  | ||||
| jobs: | ||||
|   sync: | ||||
|     name: Sync Device Classes | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|  | ||||
|       - name: Checkout Home Assistant | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           repository: home-assistant/core | ||||
|           path: lib/home-assistant | ||||
|  | ||||
|       - name: Setup Python | ||||
|         uses: actions/setup-python@v4 | ||||
|         with: | ||||
|           python-version: 3.11 | ||||
|  | ||||
|       - name: Install Home Assistant | ||||
|         run: | | ||||
|           python -m pip install --upgrade pip | ||||
|           pip install -e lib/home-assistant | ||||
|  | ||||
|       - name: Sync | ||||
|         run: | | ||||
|           python ./script/sync-device_class.py | ||||
|  | ||||
|       - name: Get PR template | ||||
|         id: pr-template-body | ||||
|         run: | | ||||
|           body=$(cat .github/PULL_REQUEST_TEMPLATE.md) | ||||
|           delimiter="$(openssl rand -hex 8)" | ||||
|           echo "body<<$delimiter" >> $GITHUB_OUTPUT | ||||
|           echo "$body" >> $GITHUB_OUTPUT | ||||
|           echo "$delimiter" >> $GITHUB_OUTPUT | ||||
|  | ||||
|       - name: Commit changes | ||||
|         uses: peter-evans/create-pull-request@v5 | ||||
|         with: | ||||
|           commit-message: "Synchronise Device Classes from Home Assistant" | ||||
|           committer: esphomebot <esphome@nabucasa.com> | ||||
|           author: esphomebot <esphome@nabucasa.com> | ||||
|           branch: sync/device-classes/ | ||||
|           branch-suffix: timestamp | ||||
|           delete-branch: true | ||||
|           title: "Synchronise Device Classes from Home Assistant" | ||||
|           body: ${{ steps.pr-template-body.outputs.body }} | ||||
| @@ -2,8 +2,8 @@ | ||||
| # See https://pre-commit.com for more information | ||||
| # See https://pre-commit.com/hooks.html for more hooks | ||||
| repos: | ||||
|   - repo: https://github.com/ambv/black | ||||
|     rev: 23.1.0 | ||||
|   - repo: https://github.com/psf/black | ||||
|     rev: 23.3.0 | ||||
|     hooks: | ||||
|       - id: black | ||||
|         args: | ||||
| @@ -27,7 +27,7 @@ repos: | ||||
|           - --branch=release | ||||
|           - --branch=beta | ||||
|   - repo: https://github.com/asottile/pyupgrade | ||||
|     rev: v3.3.1 | ||||
|     rev: v3.3.2 | ||||
|     hooks: | ||||
|       - id: pyupgrade | ||||
|         args: [--py39-plus] | ||||
|   | ||||
							
								
								
									
										15
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							| @@ -2,15 +2,24 @@ | ||||
|   "version": "2.0.0", | ||||
|   "tasks": [ | ||||
|     { | ||||
|       "label": "run", | ||||
|       "label": "Run Dashboard", | ||||
|       "type": "shell", | ||||
|       "command": "python3 -m esphome dashboard config/", | ||||
|       "command": "${command:python.interpreterPath}", | ||||
|       "args": [ | ||||
|         "-m", | ||||
|         "esphome", | ||||
|         "dashboard", | ||||
|         "config/" | ||||
|       ], | ||||
|       "problemMatcher": [] | ||||
|     }, | ||||
|     { | ||||
|       "label": "clang-tidy", | ||||
|       "type": "shell", | ||||
|       "command": "./script/clang-tidy", | ||||
|       "command": "${command:python.interpreterPath}", | ||||
|       "args": [ | ||||
|         "./script/clang-tidy" | ||||
|       ], | ||||
|       "problemMatcher": [ | ||||
|         { | ||||
|           "owner": "clang-tidy", | ||||
|   | ||||
							
								
								
									
										17
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -21,6 +21,7 @@ esphome/components/airthings_wave_mini/* @ncareau | ||||
| esphome/components/airthings_wave_plus/* @jeromelaban | ||||
| esphome/components/am43/* @buxtronix | ||||
| esphome/components/am43/cover/* @buxtronix | ||||
| esphome/components/am43/sensor/* @buxtronix | ||||
| esphome/components/analog_threshold/* @ianchi | ||||
| esphome/components/animation/* @syndlex | ||||
| esphome/components/anova/* @buxtronix | ||||
| @@ -83,6 +84,7 @@ esphome/components/esp32_ble_server/* @jesserockz | ||||
| esphome/components/esp32_camera_web_server/* @ayufan | ||||
| esphome/components/esp32_can/* @Sympatron | ||||
| esphome/components/esp32_improv/* @jesserockz | ||||
| esphome/components/esp32_rmt_led_strip/* @jesserockz | ||||
| esphome/components/esp8266/* @esphome/core | ||||
| esphome/components/ethernet_info/* @gtjadsonsantos | ||||
| esphome/components/exposure_notifications/* @OttoWinter | ||||
| @@ -94,6 +96,7 @@ esphome/components/feedback/* @ianchi | ||||
| esphome/components/fingerprint_grow/* @OnFreund @loongyh | ||||
| esphome/components/fs3000/* @kahrendt | ||||
| esphome/components/globals/* @esphome/core | ||||
| esphome/components/gp8403/* @jesserockz | ||||
| esphome/components/gpio/* @esphome/core | ||||
| esphome/components/gps/* @coogle | ||||
| esphome/components/graph/* @synco | ||||
| @@ -106,11 +109,16 @@ esphome/components/heatpumpir/* @rob-deutsch | ||||
| esphome/components/hitachi_ac424/* @sourabhjaiswal | ||||
| esphome/components/homeassistant/* @OttoWinter | ||||
| esphome/components/honeywellabp/* @RubyBailey | ||||
| esphome/components/host/* @esphome/core | ||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||
| esphome/components/hte501/* @Stock-M | ||||
| esphome/components/hydreon_rgxx/* @functionpointer | ||||
| esphome/components/hyt271/* @Philippe12 | ||||
| esphome/components/i2c/* @esphome/core | ||||
| esphome/components/i2s_audio/* @jesserockz | ||||
| esphome/components/i2s_audio/media_player/* @jesserockz | ||||
| esphome/components/i2s_audio/microphone/* @jesserockz | ||||
| esphome/components/i2s_audio/speaker/* @jesserockz | ||||
| esphome/components/ili9xxx/* @nielsnl68 | ||||
| esphome/components/improv_base/* @esphome/core | ||||
| esphome/components/improv_serial/* @esphome/core | ||||
| @@ -136,6 +144,7 @@ esphome/components/ltr390/* @sjtrny | ||||
| esphome/components/matrix_keypad/* @ssieb | ||||
| esphome/components/max31865/* @DAVe3283 | ||||
| esphome/components/max44009/* @berfenger | ||||
| esphome/components/max6956/* @looping40 | ||||
| esphome/components/max7219digit/* @rspaargaren | ||||
| esphome/components/max9611/* @mckaymatthew | ||||
| esphome/components/mcp23008/* @jesserockz | ||||
| @@ -154,11 +163,14 @@ esphome/components/mcp9808/* @k7hpn | ||||
| esphome/components/md5/* @esphome/core | ||||
| esphome/components/mdns/* @esphome/core | ||||
| esphome/components/media_player/* @jesserockz | ||||
| esphome/components/microphone/* @jesserockz | ||||
| esphome/components/mics_4514/* @jesserockz | ||||
| esphome/components/midea/* @dudanov | ||||
| esphome/components/midea_ir/* @dudanov | ||||
| esphome/components/mitsubishi/* @RubyBailey | ||||
| esphome/components/mlx90393/* @functionpointer | ||||
| esphome/components/mlx90614/* @jesserockz | ||||
| esphome/components/mmc5603/* @benhoff | ||||
| esphome/components/modbus_controller/* @martgras | ||||
| esphome/components/modbus_controller/binary_sensor/* @martgras | ||||
| esphome/components/modbus_controller/number/* @martgras | ||||
| @@ -182,6 +194,7 @@ esphome/components/nfc/* @jesserockz | ||||
| esphome/components/number/* @esphome/core | ||||
| esphome/components/ota/* @esphome/core | ||||
| esphome/components/output/* @esphome/core | ||||
| esphome/components/pca6416a/* @Mat931 | ||||
| esphome/components/pca9554/* @hwstar | ||||
| esphome/components/pcf85063/* @brogon | ||||
| esphome/components/pid/* @OttoWinter | ||||
| @@ -228,7 +241,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet | ||||
| esphome/components/sigma_delta_output/* @Cat-Ion | ||||
| esphome/components/sim800l/* @glmnet | ||||
| esphome/components/sm10bit_base/* @Cossid | ||||
| esphome/components/sm2135/* @BoukeHaarsma23 | ||||
| esphome/components/sm2135/* @BoukeHaarsma23 @dd32 @matika77 | ||||
| esphome/components/sm2235/* @Cossid | ||||
| esphome/components/sm2335/* @Cossid | ||||
| esphome/components/sml/* @alengwenus | ||||
| @@ -236,6 +249,7 @@ esphome/components/smt100/* @piechade | ||||
| esphome/components/sn74hc165/* @jesserockz | ||||
| esphome/components/socket/* @esphome/core | ||||
| esphome/components/sonoff_d1/* @anatoly-savchenkov | ||||
| esphome/components/speaker/* @jesserockz | ||||
| esphome/components/spi/* @esphome/core | ||||
| esphome/components/sprinkler/* @kbx81 | ||||
| esphome/components/sps30/* @martgras | ||||
| @@ -286,6 +300,7 @@ esphome/components/ufire_ise/* @pvizeli | ||||
| esphome/components/ultrasonic/* @OttoWinter | ||||
| esphome/components/vbus/* @ssieb | ||||
| esphome/components/version/* @esphome/core | ||||
| esphome/components/voice_assistant/* @jesserockz | ||||
| esphome/components/wake_on_lan/* @willwill2will54 | ||||
| esphome/components/web_server_base/* @OttoWinter | ||||
| esphome/components/whirlpool/* @glmnet | ||||
|   | ||||
| @@ -24,8 +24,9 @@ RUN \ | ||||
|         python3-setuptools=52.0.0-4 \ | ||||
|         python3-pil=8.1.2+dfsg-0.3+deb11u1 \ | ||||
|         python3-cryptography=3.3.2-1 \ | ||||
|         python3-venv=3.9.2-3 \ | ||||
|         iputils-ping=3:20210202-1 \ | ||||
|         git=1:2.30.2-1 \ | ||||
|         git=1:2.30.2-1+deb11u2 \ | ||||
|         curl=7.74.0-1.3+deb11u7 \ | ||||
|         openssh-client=1:8.4p1-5+deb11u1 \ | ||||
|     && rm -rf \ | ||||
| @@ -59,10 +60,10 @@ RUN \ | ||||
|  | ||||
|  | ||||
| # First install requirements to leverage caching when requirements don't change | ||||
| COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / | ||||
| COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / | ||||
| RUN \ | ||||
|     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini | ||||
|     && /platformio_install_deps.py /platformio.ini --libraries | ||||
|  | ||||
|  | ||||
| # ======================= docker-type image ======================= | ||||
| @@ -135,7 +136,7 @@ RUN \ | ||||
|     apt-get update \ | ||||
|     # Use pinned versions so that we get updates with build caching | ||||
|     && apt-get install -y --no-install-recommends \ | ||||
|         clang-format-11=1:11.0.1-2 \ | ||||
|         clang-format-13=1:13.0.1-6~deb11u1 \ | ||||
|         clang-tidy-11=1:11.0.1-2 \ | ||||
|         patch=2.7.6-7 \ | ||||
|         software-properties-common=0.96.20.2-2.1 \ | ||||
|   | ||||
| @@ -1,30 +0,0 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # This script is used in the docker containers to preinstall | ||||
| # all platformio libraries in the global storage | ||||
|  | ||||
| import configparser | ||||
| import subprocess | ||||
| import sys | ||||
|  | ||||
| config = configparser.ConfigParser(inline_comment_prefixes=(';', )) | ||||
| config.read(sys.argv[1]) | ||||
|  | ||||
| libs = [] | ||||
| # Extract from every lib_deps key in all sections | ||||
| for section in config.sections(): | ||||
|     conf = config[section] | ||||
|     if "lib_deps" not in conf: | ||||
|         continue | ||||
|     for lib_dep in conf["lib_deps"].splitlines(): | ||||
|         if not lib_dep: | ||||
|             # Empty line or comment | ||||
|             continue | ||||
|         if lib_dep.startswith("${"): | ||||
|             # Extending from another section | ||||
|             continue | ||||
|         if "@" not in lib_dep: | ||||
|             # No version pinned, this is an internal lib | ||||
|             continue | ||||
|         libs.append(lib_dep) | ||||
|  | ||||
| subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) | ||||
| @@ -152,6 +152,8 @@ def run_miniterm(config, port): | ||||
|         _LOGGER.error("Could not connect to serial port %s", port) | ||||
|         return 1 | ||||
|  | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def wrap_to_code(name, comp): | ||||
|     coro = coroutine(comp.to_code) | ||||
|   | ||||
| @@ -1 +1,118 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.const import CONF_INPUT | ||||
|  | ||||
| from esphome.core import CORE | ||||
| from esphome.components.esp32 import get_esp32_variant | ||||
| from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32C3, | ||||
|     VARIANT_ESP32H2, | ||||
|     VARIANT_ESP32S2, | ||||
|     VARIANT_ESP32S3, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
|  | ||||
| ATTENUATION_MODES = { | ||||
|     "0db": cg.global_ns.ADC_ATTEN_DB_0, | ||||
|     "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, | ||||
|     "6db": cg.global_ns.ADC_ATTEN_DB_6, | ||||
|     "11db": cg.global_ns.ADC_ATTEN_DB_11, | ||||
|     "auto": "auto", | ||||
| } | ||||
|  | ||||
| adc1_channel_t = cg.global_ns.enum("adc1_channel_t") | ||||
|  | ||||
| # From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h | ||||
| # pin to adc1 channel mapping | ||||
| ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { | ||||
|     VARIANT_ESP32: { | ||||
|         36: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         37: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         38: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         39: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         32: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|         33: adc1_channel_t.ADC1_CHANNEL_5, | ||||
|         34: adc1_channel_t.ADC1_CHANNEL_6, | ||||
|         35: adc1_channel_t.ADC1_CHANNEL_7, | ||||
|     }, | ||||
|     VARIANT_ESP32S2: { | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|         6: adc1_channel_t.ADC1_CHANNEL_5, | ||||
|         7: adc1_channel_t.ADC1_CHANNEL_6, | ||||
|         8: adc1_channel_t.ADC1_CHANNEL_7, | ||||
|         9: adc1_channel_t.ADC1_CHANNEL_8, | ||||
|         10: adc1_channel_t.ADC1_CHANNEL_9, | ||||
|     }, | ||||
|     VARIANT_ESP32S3: { | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|         6: adc1_channel_t.ADC1_CHANNEL_5, | ||||
|         7: adc1_channel_t.ADC1_CHANNEL_6, | ||||
|         8: adc1_channel_t.ADC1_CHANNEL_7, | ||||
|         9: adc1_channel_t.ADC1_CHANNEL_8, | ||||
|         10: adc1_channel_t.ADC1_CHANNEL_9, | ||||
|     }, | ||||
|     VARIANT_ESP32C3: { | ||||
|         0: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|     }, | ||||
|     VARIANT_ESP32H2: { | ||||
|         0: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|     }, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_adc_pin(value): | ||||
|     if str(value).upper() == "VCC": | ||||
|         return cv.only_on_esp8266("VCC") | ||||
|  | ||||
|     if str(value).upper() == "TEMPERATURE": | ||||
|         return cv.only_on_rp2040("TEMPERATURE") | ||||
|  | ||||
|     if CORE.is_esp32: | ||||
|         value = pins.internal_gpio_input_pin_number(value) | ||||
|         variant = get_esp32_variant() | ||||
|         if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: | ||||
|             raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") | ||||
|  | ||||
|         if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: | ||||
|             raise cv.Invalid(f"{variant} doesn't support ADC on this pin") | ||||
|         return pins.internal_gpio_input_pin_schema(value) | ||||
|  | ||||
|     if CORE.is_esp8266: | ||||
|         from esphome.components.esp8266.gpio import CONF_ANALOG | ||||
|  | ||||
|         value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( | ||||
|             value | ||||
|         ) | ||||
|  | ||||
|         if value != 17:  # A0 | ||||
|             raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") | ||||
|         return pins.gpio_pin_schema( | ||||
|             {CONF_ANALOG: True, CONF_INPUT: True}, internal=True | ||||
|         )(value) | ||||
|  | ||||
|     if CORE.is_rp2040: | ||||
|         value = pins.internal_gpio_input_pin_number(value) | ||||
|         if value not in (26, 27, 28, 29): | ||||
|             raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.") | ||||
|         return pins.internal_gpio_input_pin_schema(value) | ||||
|  | ||||
|     raise NotImplementedError | ||||
|   | ||||
| @@ -1,133 +1,27 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.components import sensor, voltage_sampler | ||||
| from esphome.components.esp32 import get_esp32_variant | ||||
| from esphome.const import ( | ||||
|     CONF_ATTENUATION, | ||||
|     CONF_RAW, | ||||
|     CONF_ID, | ||||
|     CONF_INPUT, | ||||
|     CONF_NUMBER, | ||||
|     CONF_PIN, | ||||
|     CONF_RAW, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_VOLT, | ||||
| ) | ||||
| from esphome.core import CORE | ||||
| from esphome.components.esp32 import get_esp32_variant | ||||
| from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32C3, | ||||
|     VARIANT_ESP32H2, | ||||
|     VARIANT_ESP32S2, | ||||
|     VARIANT_ESP32S3, | ||||
|  | ||||
| from . import ( | ||||
|     ATTENUATION_MODES, | ||||
|     ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, | ||||
|     validate_adc_pin, | ||||
| ) | ||||
|  | ||||
|  | ||||
| AUTO_LOAD = ["voltage_sampler"] | ||||
|  | ||||
| ATTENUATION_MODES = { | ||||
|     "0db": cg.global_ns.ADC_ATTEN_DB_0, | ||||
|     "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, | ||||
|     "6db": cg.global_ns.ADC_ATTEN_DB_6, | ||||
|     "11db": cg.global_ns.ADC_ATTEN_DB_11, | ||||
|     "auto": "auto", | ||||
| } | ||||
|  | ||||
| adc1_channel_t = cg.global_ns.enum("adc1_channel_t") | ||||
|  | ||||
| # From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h | ||||
| # pin to adc1 channel mapping | ||||
| ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { | ||||
|     VARIANT_ESP32: { | ||||
|         36: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         37: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         38: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         39: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         32: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|         33: adc1_channel_t.ADC1_CHANNEL_5, | ||||
|         34: adc1_channel_t.ADC1_CHANNEL_6, | ||||
|         35: adc1_channel_t.ADC1_CHANNEL_7, | ||||
|     }, | ||||
|     VARIANT_ESP32S2: { | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|         6: adc1_channel_t.ADC1_CHANNEL_5, | ||||
|         7: adc1_channel_t.ADC1_CHANNEL_6, | ||||
|         8: adc1_channel_t.ADC1_CHANNEL_7, | ||||
|         9: adc1_channel_t.ADC1_CHANNEL_8, | ||||
|         10: adc1_channel_t.ADC1_CHANNEL_9, | ||||
|     }, | ||||
|     VARIANT_ESP32S3: { | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|         6: adc1_channel_t.ADC1_CHANNEL_5, | ||||
|         7: adc1_channel_t.ADC1_CHANNEL_6, | ||||
|         8: adc1_channel_t.ADC1_CHANNEL_7, | ||||
|         9: adc1_channel_t.ADC1_CHANNEL_8, | ||||
|         10: adc1_channel_t.ADC1_CHANNEL_9, | ||||
|     }, | ||||
|     VARIANT_ESP32C3: { | ||||
|         0: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|     }, | ||||
|     VARIANT_ESP32H2: { | ||||
|         0: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|     }, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_adc_pin(value): | ||||
|     if str(value).upper() == "VCC": | ||||
|         return cv.only_on_esp8266("VCC") | ||||
|  | ||||
|     if str(value).upper() == "TEMPERATURE": | ||||
|         return cv.only_on_rp2040("TEMPERATURE") | ||||
|  | ||||
|     if CORE.is_esp32: | ||||
|         value = pins.internal_gpio_input_pin_number(value) | ||||
|         variant = get_esp32_variant() | ||||
|         if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: | ||||
|             raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") | ||||
|  | ||||
|         if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: | ||||
|             raise cv.Invalid(f"{variant} doesn't support ADC on this pin") | ||||
|         return pins.internal_gpio_input_pin_schema(value) | ||||
|  | ||||
|     if CORE.is_esp8266: | ||||
|         from esphome.components.esp8266.gpio import CONF_ANALOG | ||||
|  | ||||
|         value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( | ||||
|             value | ||||
|         ) | ||||
|  | ||||
|         if value != 17:  # A0 | ||||
|             raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") | ||||
|         return pins.gpio_pin_schema( | ||||
|             {CONF_ANALOG: True, CONF_INPUT: True}, internal=True | ||||
|         )(value) | ||||
|  | ||||
|     if CORE.is_rp2040: | ||||
|         value = pins.internal_gpio_input_pin_number(value) | ||||
|         if value not in (26, 27, 28, 29): | ||||
|             raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.") | ||||
|         return pins.internal_gpio_input_pin_schema(value) | ||||
|  | ||||
|     raise NotImplementedError | ||||
|  | ||||
|  | ||||
| def validate_config(config): | ||||
|     if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": | ||||
|   | ||||
| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@buxtronix"] | ||||
|   | ||||
| @@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN | ||||
|  | ||||
| CODEOWNERS = ["@buxtronix"] | ||||
| DEPENDENCIES = ["ble_client"] | ||||
| AUTO_LOAD = ["am43", "sensor"] | ||||
| AUTO_LOAD = ["am43"] | ||||
|  | ||||
| CONF_INVERT_POSITION = "invert_position" | ||||
|  | ||||
| @@ -27,10 +27,10 @@ CONFIG_SCHEMA = ( | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     cg.add(var.set_pin(config[CONF_PIN])) | ||||
|     cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield cover.register_cover(var, config) | ||||
|     yield ble_client.register_ble_node(var, config) | ||||
|     await cg.register_component(var, config) | ||||
|     await cover.register_cover(var, config) | ||||
|     await ble_client.register_ble_node(var, config) | ||||
|   | ||||
| @@ -40,6 +40,7 @@ void Am43Component::loop() { | ||||
|  | ||||
| CoverTraits Am43Component::get_traits() { | ||||
|   auto traits = CoverTraits(); | ||||
|   traits.set_supports_stop(true); | ||||
|   traits.set_supports_position(true); | ||||
|   traits.set_supports_tilt(false); | ||||
|   traits.set_is_assumed_state(false); | ||||
| @@ -65,7 +66,7 @@ void Am43Component::control(const CoverCall &call) { | ||||
|  | ||||
|     if (this->invert_position_) | ||||
|       pos = 1 - pos; | ||||
|     auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); | ||||
|     auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t) (pos * 100)); | ||||
|     auto status = | ||||
|         esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, | ||||
|                                  packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); | ||||
|   | ||||
| @@ -11,6 +11,7 @@ from esphome.const import ( | ||||
|     UNIT_PERCENT, | ||||
| ) | ||||
| 
 | ||||
| AUTO_LOAD = ["am43"] | ||||
| CODEOWNERS = ["@buxtronix"] | ||||
| 
 | ||||
| am43_ns = cg.esphome_ns.namespace("am43") | ||||
| @@ -38,15 +39,15 @@ CONFIG_SCHEMA = ( | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def to_code(config): | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield ble_client.register_ble_node(var, config) | ||||
|     await cg.register_component(var, config) | ||||
|     await ble_client.register_ble_node(var, config) | ||||
| 
 | ||||
|     if CONF_BATTERY_LEVEL in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||
|         sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||
|         cg.add(var.set_battery(sens)) | ||||
| 
 | ||||
|     if CONF_ILLUMINANCE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) | ||||
|         sens = await sensor.new_sensor(config[CONF_ILLUMINANCE]) | ||||
|         cg.add(var.set_illuminance(sens)) | ||||
| @@ -1,6 +1,6 @@ | ||||
| #include "am43.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "am43_sensor.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
| 
 | ||||
| #ifdef USE_ESP32 | ||||
| 
 | ||||
| @@ -76,8 +76,6 @@ async def to_code(config): | ||||
|         pos = 0 | ||||
|         for frameIndex in range(frames): | ||||
|             image.seek(frameIndex) | ||||
|             if CONF_RESIZE in config: | ||||
|                 image.thumbnail(config[CONF_RESIZE]) | ||||
|             frame = image.convert("RGB") | ||||
|             if CONF_RESIZE in config: | ||||
|                 frame = frame.resize([width, height]) | ||||
|   | ||||
| @@ -53,6 +53,9 @@ service APIConnection { | ||||
|   rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {} | ||||
|   rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {} | ||||
|   rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {} | ||||
|   rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} | ||||
|  | ||||
|   rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -208,6 +211,8 @@ message DeviceInfoResponse { | ||||
|   string manufacturer = 12; | ||||
|  | ||||
|   string friendly_name = 13; | ||||
|  | ||||
|   uint32 voice_assistant_version = 14; | ||||
| } | ||||
|  | ||||
| message ListEntitiesRequest { | ||||
| @@ -283,6 +288,7 @@ message ListEntitiesCoverResponse { | ||||
|   bool disabled_by_default = 9; | ||||
|   string icon = 10; | ||||
|   EntityCategory entity_category = 11; | ||||
|   bool supports_stop = 12; | ||||
| } | ||||
|  | ||||
| enum LegacyCoverState { | ||||
| @@ -856,8 +862,7 @@ message ClimateStateResponse { | ||||
|   float target_temperature = 4; | ||||
|   float target_temperature_low = 5; | ||||
|   float target_temperature_high = 6; | ||||
|   // For older peers, equal to preset == CLIMATE_PRESET_AWAY | ||||
|   bool legacy_away = 7; | ||||
|   bool unused_legacy_away = 7; | ||||
|   ClimateAction action = 8; | ||||
|   ClimateFanMode fan_mode = 9; | ||||
|   ClimateSwingMode swing_mode = 10; | ||||
| @@ -880,9 +885,8 @@ message ClimateCommandRequest { | ||||
|   float target_temperature_low = 7; | ||||
|   bool has_target_temperature_high = 8; | ||||
|   float target_temperature_high = 9; | ||||
|   // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset | ||||
|   bool has_legacy_away = 10; | ||||
|   bool legacy_away = 11; | ||||
|   bool unused_has_legacy_away = 10; | ||||
|   bool unused_legacy_away = 11; | ||||
|   bool has_fan_mode = 12; | ||||
|   ClimateFanMode fan_mode = 13; | ||||
|   bool has_swing_mode = 14; | ||||
| @@ -1125,6 +1129,7 @@ message MediaPlayerCommandRequest { | ||||
| message SubscribeBluetoothLEAdvertisementsRequest { | ||||
|   option (id) = 66; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||
| } | ||||
|  | ||||
| message BluetoothServiceData { | ||||
| @@ -1156,6 +1161,7 @@ enum BluetoothDeviceRequestType { | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3; | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4; | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5; | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6; | ||||
| } | ||||
|  | ||||
| message BluetoothDeviceRequest { | ||||
| @@ -1359,3 +1365,71 @@ message BluetoothDeviceUnpairingResponse { | ||||
|   bool success = 2; | ||||
|   int32 error = 3; | ||||
| } | ||||
|  | ||||
| message UnsubscribeBluetoothLEAdvertisementsRequest { | ||||
|   option (id) = 87; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||
| } | ||||
|  | ||||
| message BluetoothDeviceClearCacheResponse { | ||||
|   option (id) = 88; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||
|  | ||||
|   uint64 address = 1; | ||||
|   bool success = 2; | ||||
|   int32 error = 3; | ||||
| } | ||||
|  | ||||
| // ==================== PUSH TO TALK ==================== | ||||
| message SubscribeVoiceAssistantRequest { | ||||
|   option (id) = 89; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   bool subscribe = 1; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantRequest { | ||||
|   option (id) = 90; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   bool start = 1; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantResponse { | ||||
|   option (id) = 91; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   uint32 port = 1; | ||||
|   bool error = 2; | ||||
| } | ||||
|  | ||||
| enum VoiceAssistantEvent { | ||||
|   VOICE_ASSISTANT_ERROR = 0; | ||||
|   VOICE_ASSISTANT_RUN_START = 1; | ||||
|   VOICE_ASSISTANT_RUN_END = 2; | ||||
|   VOICE_ASSISTANT_STT_START = 3; | ||||
|   VOICE_ASSISTANT_STT_END = 4; | ||||
|   VOICE_ASSISTANT_INTENT_START = 5; | ||||
|   VOICE_ASSISTANT_INTENT_END = 6; | ||||
|   VOICE_ASSISTANT_TTS_START = 7; | ||||
|   VOICE_ASSISTANT_TTS_END = 8; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantEventData { | ||||
|   string name = 1; | ||||
|   string value = 2; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantEventResponse { | ||||
|   option (id) = 92; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   VoiceAssistantEvent event_type = 1; | ||||
|   repeated VoiceAssistantEventData data = 2; | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "api_connection.h" | ||||
| #include <cerrno> | ||||
| #include <cinttypes> | ||||
| #include "esphome/components/network/util.h" | ||||
| #include "esphome/core/entity_base.h" | ||||
| #include "esphome/core/hal.h" | ||||
| @@ -15,6 +16,9 @@ | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| #include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #include "esphome/components/voice_assistant/voice_assistant.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
| @@ -180,7 +184,8 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ | ||||
|   ListEntitiesBinarySensorResponse msg; | ||||
|   msg.object_id = binary_sensor->get_object_id(); | ||||
|   msg.key = binary_sensor->get_object_id_hash(); | ||||
|   msg.name = binary_sensor->get_name(); | ||||
|   if (binary_sensor->has_own_name()) | ||||
|     msg.name = binary_sensor->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor); | ||||
|   msg.device_class = binary_sensor->get_device_class(); | ||||
|   msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); | ||||
| @@ -212,7 +217,8 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { | ||||
|   ListEntitiesCoverResponse msg; | ||||
|   msg.key = cover->get_object_id_hash(); | ||||
|   msg.object_id = cover->get_object_id(); | ||||
|   msg.name = cover->get_name(); | ||||
|   if (cover->has_own_name()) | ||||
|     msg.name = cover->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("cover", cover); | ||||
|   msg.assumed_state = traits.get_is_assumed_state(); | ||||
|   msg.supports_position = traits.get_supports_position(); | ||||
| @@ -275,7 +281,8 @@ bool APIConnection::send_fan_info(fan::Fan *fan) { | ||||
|   ListEntitiesFanResponse msg; | ||||
|   msg.key = fan->get_object_id_hash(); | ||||
|   msg.object_id = fan->get_object_id(); | ||||
|   msg.name = fan->get_name(); | ||||
|   if (fan->has_own_name()) | ||||
|     msg.name = fan->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("fan", fan); | ||||
|   msg.supports_oscillation = traits.supports_oscillation(); | ||||
|   msg.supports_speed = traits.supports_speed(); | ||||
| @@ -337,7 +344,8 @@ bool APIConnection::send_light_info(light::LightState *light) { | ||||
|   ListEntitiesLightResponse msg; | ||||
|   msg.key = light->get_object_id_hash(); | ||||
|   msg.object_id = light->get_object_id(); | ||||
|   msg.name = light->get_name(); | ||||
|   if (light->has_own_name()) | ||||
|     msg.name = light->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("light", light); | ||||
|  | ||||
|   msg.disabled_by_default = light->is_disabled_by_default(); | ||||
| @@ -418,7 +426,8 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { | ||||
|   ListEntitiesSensorResponse msg; | ||||
|   msg.key = sensor->get_object_id_hash(); | ||||
|   msg.object_id = sensor->get_object_id(); | ||||
|   msg.name = sensor->get_name(); | ||||
|   if (sensor->has_own_name()) | ||||
|     msg.name = sensor->get_name(); | ||||
|   msg.unique_id = sensor->unique_id(); | ||||
|   if (msg.unique_id.empty()) | ||||
|     msg.unique_id = get_default_unique_id("sensor", sensor); | ||||
| @@ -448,7 +457,8 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { | ||||
|   ListEntitiesSwitchResponse msg; | ||||
|   msg.key = a_switch->get_object_id_hash(); | ||||
|   msg.object_id = a_switch->get_object_id(); | ||||
|   msg.name = a_switch->get_name(); | ||||
|   if (a_switch->has_own_name()) | ||||
|     msg.name = a_switch->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("switch", a_switch); | ||||
|   msg.icon = a_switch->get_icon(); | ||||
|   msg.assumed_state = a_switch->assumed_state(); | ||||
| @@ -520,7 +530,6 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { | ||||
|     resp.custom_fan_mode = climate->custom_fan_mode.value(); | ||||
|   if (traits.get_supports_presets() && climate->preset.has_value()) { | ||||
|     resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value()); | ||||
|     resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY; | ||||
|   } | ||||
|   if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) | ||||
|     resp.custom_preset = climate->custom_preset.value(); | ||||
| @@ -533,7 +542,8 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { | ||||
|   ListEntitiesClimateResponse msg; | ||||
|   msg.key = climate->get_object_id_hash(); | ||||
|   msg.object_id = climate->get_object_id(); | ||||
|   msg.name = climate->get_name(); | ||||
|   if (climate->has_own_name()) | ||||
|     msg.name = climate->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("climate", climate); | ||||
|  | ||||
|   msg.disabled_by_default = climate->is_disabled_by_default(); | ||||
| @@ -580,8 +590,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { | ||||
|     call.set_target_temperature_low(msg.target_temperature_low); | ||||
|   if (msg.has_target_temperature_high) | ||||
|     call.set_target_temperature_high(msg.target_temperature_high); | ||||
|   if (msg.has_legacy_away) | ||||
|     call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME); | ||||
|   if (msg.has_fan_mode) | ||||
|     call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode)); | ||||
|   if (msg.has_custom_fan_mode) | ||||
| @@ -611,7 +619,8 @@ bool APIConnection::send_number_info(number::Number *number) { | ||||
|   ListEntitiesNumberResponse msg; | ||||
|   msg.key = number->get_object_id_hash(); | ||||
|   msg.object_id = number->get_object_id(); | ||||
|   msg.name = number->get_name(); | ||||
|   if (number->has_own_name()) | ||||
|     msg.name = number->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("number", number); | ||||
|   msg.icon = number->get_icon(); | ||||
|   msg.disabled_by_default = number->is_disabled_by_default(); | ||||
| @@ -652,7 +661,8 @@ bool APIConnection::send_select_info(select::Select *select) { | ||||
|   ListEntitiesSelectResponse msg; | ||||
|   msg.key = select->get_object_id_hash(); | ||||
|   msg.object_id = select->get_object_id(); | ||||
|   msg.name = select->get_name(); | ||||
|   if (select->has_own_name()) | ||||
|     msg.name = select->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("select", select); | ||||
|   msg.icon = select->get_icon(); | ||||
|   msg.disabled_by_default = select->is_disabled_by_default(); | ||||
| @@ -679,7 +689,8 @@ bool APIConnection::send_button_info(button::Button *button) { | ||||
|   ListEntitiesButtonResponse msg; | ||||
|   msg.key = button->get_object_id_hash(); | ||||
|   msg.object_id = button->get_object_id(); | ||||
|   msg.name = button->get_name(); | ||||
|   if (button->has_own_name()) | ||||
|     msg.name = button->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("button", button); | ||||
|   msg.icon = button->get_icon(); | ||||
|   msg.disabled_by_default = button->is_disabled_by_default(); | ||||
| @@ -710,7 +721,8 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) { | ||||
|   ListEntitiesLockResponse msg; | ||||
|   msg.key = a_lock->get_object_id_hash(); | ||||
|   msg.object_id = a_lock->get_object_id(); | ||||
|   msg.name = a_lock->get_name(); | ||||
|   if (a_lock->has_own_name()) | ||||
|     msg.name = a_lock->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("lock", a_lock); | ||||
|   msg.icon = a_lock->get_icon(); | ||||
|   msg.assumed_state = a_lock->traits.get_assumed_state(); | ||||
| @@ -755,7 +767,8 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play | ||||
|   ListEntitiesMediaPlayerResponse msg; | ||||
|   msg.key = media_player->get_object_id_hash(); | ||||
|   msg.object_id = media_player->get_object_id(); | ||||
|   msg.name = media_player->get_name(); | ||||
|   if (media_player->has_own_name()) | ||||
|     msg.name = media_player->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("media_player", media_player); | ||||
|   msg.icon = media_player->get_icon(); | ||||
|   msg.disabled_by_default = media_player->is_disabled_by_default(); | ||||
| @@ -799,7 +812,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | ||||
|   ListEntitiesCameraResponse msg; | ||||
|   msg.key = camera->get_object_id_hash(); | ||||
|   msg.object_id = camera->get_object_id(); | ||||
|   msg.name = camera->get_name(); | ||||
|   if (camera->has_own_name()) | ||||
|     msg.name = camera->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("camera", camera); | ||||
|   msg.disabled_by_default = camera->is_disabled_by_default(); | ||||
|   msg.icon = camera->get_icon(); | ||||
| @@ -879,6 +893,30 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_ | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| bool APIConnection::request_voice_assistant(bool start) { | ||||
|   if (!this->voice_assistant_subscription_) | ||||
|     return false; | ||||
|   VoiceAssistantRequest msg; | ||||
|   msg.start = start; | ||||
|   return this->send_voice_assistant_request(msg); | ||||
| } | ||||
| void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) { | ||||
|   if (voice_assistant::global_voice_assistant != nullptr) { | ||||
|     struct sockaddr_storage storage; | ||||
|     socklen_t len = sizeof(storage); | ||||
|     this->helper_->getpeername((struct sockaddr *) &storage, &len); | ||||
|     voice_assistant::global_voice_assistant->start(&storage, msg.port); | ||||
|   } | ||||
| }; | ||||
| void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) { | ||||
|   if (voice_assistant::global_voice_assistant != nullptr) { | ||||
|     voice_assistant::global_voice_assistant->on_event(msg); | ||||
|   } | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| bool APIConnection::send_log_message(int level, const char *tag, const char *line) { | ||||
|   if (this->log_subscription_ < level) | ||||
|     return false; | ||||
| @@ -898,12 +936,12 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { | ||||
|   this->helper_->set_log_info(client_info_); | ||||
|   this->client_api_version_major_ = msg.api_version_major; | ||||
|   this->client_api_version_minor_ = msg.api_version_minor; | ||||
|   ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(), | ||||
|   ESP_LOGV(TAG, "Hello from client: '%s' | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(), | ||||
|            this->client_api_version_major_, this->client_api_version_minor_); | ||||
|  | ||||
|   HelloResponse resp; | ||||
|   resp.api_version_major = 1; | ||||
|   resp.api_version_minor = 7; | ||||
|   resp.api_version_minor = 8; | ||||
|   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; | ||||
|   resp.name = App.get_name(); | ||||
|  | ||||
| @@ -940,6 +978,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | ||||
|   resp.manufacturer = "Espressif"; | ||||
| #elif defined(USE_RP2040) | ||||
|   resp.manufacturer = "Raspberry Pi"; | ||||
| #elif defined(USE_HOST) | ||||
|   resp.manufacturer = "Host"; | ||||
| #endif | ||||
|   resp.model = ESPHOME_BOARD; | ||||
| #ifdef USE_DEEP_SLEEP | ||||
| @@ -953,7 +993,12 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | ||||
|   resp.webserver_port = USE_WEBSERVER_PORT; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 4 : 1; | ||||
|   resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() | ||||
|                                      ? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION | ||||
|                                      : bluetooth_proxy::PASSIVE_ONLY_VERSION; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version(); | ||||
| #endif | ||||
|   return resp; | ||||
| } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| #include "api_server.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| @@ -97,6 +98,12 @@ class APIConnection : public APIServerConnection { | ||||
|     this->send_homeassistant_service_response(call); | ||||
|   } | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { | ||||
|     this->bluetooth_le_advertisement_subscription_ = true; | ||||
|   } | ||||
|   void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override { | ||||
|     this->bluetooth_le_advertisement_subscription_ = false; | ||||
|   } | ||||
|   bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg); | ||||
|  | ||||
|   void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; | ||||
| @@ -117,6 +124,15 @@ class APIConnection : public APIServerConnection { | ||||
|   } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override { | ||||
|     this->voice_assistant_subscription_ = msg.subscribe; | ||||
|   } | ||||
|   bool request_voice_assistant(bool start); | ||||
|   void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; | ||||
|   void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; | ||||
| #endif | ||||
|  | ||||
|   void on_disconnect_response(const DisconnectResponse &value) override; | ||||
|   void on_ping_response(const PingResponse &value) override { | ||||
|     // we initiated ping | ||||
| @@ -150,9 +166,7 @@ class APIConnection : public APIServerConnection { | ||||
|     return {}; | ||||
|   } | ||||
|   void execute_service(const ExecuteServiceRequest &msg) override; | ||||
|   void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { | ||||
|     this->bluetooth_le_advertisement_subscription_ = true; | ||||
|   } | ||||
|  | ||||
|   bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } | ||||
|   bool is_connection_setup() override { | ||||
|     return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); | ||||
| @@ -197,7 +211,12 @@ class APIConnection : public APIServerConnection { | ||||
|   uint32_t last_traffic_; | ||||
|   bool sent_ping_{false}; | ||||
|   bool service_call_subscription_{false}; | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool bluetooth_le_advertisement_subscription_{false}; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   bool voice_assistant_subscription_{false}; | ||||
| #endif | ||||
|   bool next_close_ = false; | ||||
|   APIServer *parent_; | ||||
|   InitialStateIterator initial_state_iterator_; | ||||
|   | ||||
| @@ -295,7 +295,7 @@ APIError APINoiseFrameHelper::state_action_() { | ||||
|     if (aerr != APIError::OK) | ||||
|       return aerr; | ||||
|     // ignore contents, may be used in future for flags | ||||
|     prologue_.push_back((uint8_t)(frame.msg.size() >> 8)); | ||||
|     prologue_.push_back((uint8_t) (frame.msg.size() >> 8)); | ||||
|     prologue_.push_back((uint8_t) frame.msg.size()); | ||||
|     prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end()); | ||||
|  | ||||
| @@ -492,9 +492,9 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload | ||||
|   // tmpbuf[1], tmpbuf[2] to be set later | ||||
|   const uint8_t msg_offset = 3; | ||||
|   const uint8_t payload_offset = msg_offset + 4; | ||||
|   tmpbuf[msg_offset + 0] = (uint8_t)(type >> 8);  // type | ||||
|   tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8);  // type | ||||
|   tmpbuf[msg_offset + 1] = (uint8_t) type; | ||||
|   tmpbuf[msg_offset + 2] = (uint8_t)(payload_len >> 8);  // data_len | ||||
|   tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8);  // data_len | ||||
|   tmpbuf[msg_offset + 3] = (uint8_t) payload_len; | ||||
|   // copy data | ||||
|   std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]); | ||||
| @@ -512,7 +512,7 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload | ||||
|   } | ||||
|  | ||||
|   size_t total_len = 3 + mbuf.size; | ||||
|   tmpbuf[1] = (uint8_t)(mbuf.size >> 8); | ||||
|   tmpbuf[1] = (uint8_t) (mbuf.size >> 8); | ||||
|   tmpbuf[2] = (uint8_t) mbuf.size; | ||||
|  | ||||
|   struct iovec iov; | ||||
| @@ -610,7 +610,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | ||||
| APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) { | ||||
|   uint8_t header[3]; | ||||
|   header[0] = 0x01;  // indicator | ||||
|   header[1] = (uint8_t)(len >> 8); | ||||
|   header[1] = (uint8_t) (len >> 8); | ||||
|   header[2] = (uint8_t) len; | ||||
|  | ||||
|   struct iovec iov[2]; | ||||
|   | ||||
| @@ -10,8 +10,8 @@ | ||||
| #include "noise/protocol.h" | ||||
| #endif | ||||
|  | ||||
| #include "esphome/components/socket/socket.h" | ||||
| #include "api_noise_context.h" | ||||
| #include "esphome/components/socket/socket.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
| @@ -67,6 +67,7 @@ class APIFrameHelper { | ||||
|   virtual bool can_write_without_blocking() = 0; | ||||
|   virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0; | ||||
|   virtual std::string getpeername() = 0; | ||||
|   virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; | ||||
|   virtual APIError close() = 0; | ||||
|   virtual APIError shutdown(int how) = 0; | ||||
|   // Give this helper a name for logging | ||||
| @@ -84,7 +85,10 @@ class APINoiseFrameHelper : public APIFrameHelper { | ||||
|   APIError read_packet(ReadPacketBuffer *buffer) override; | ||||
|   bool can_write_without_blocking() override; | ||||
|   APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; | ||||
|   std::string getpeername() override { return socket_->getpeername(); } | ||||
|   std::string getpeername() override { return this->socket_->getpeername(); } | ||||
|   int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { | ||||
|     return this->socket_->getpeername(addr, addrlen); | ||||
|   } | ||||
|   APIError close() override; | ||||
|   APIError shutdown(int how) override; | ||||
|   // Give this helper a name for logging | ||||
| @@ -144,7 +148,10 @@ class APIPlaintextFrameHelper : public APIFrameHelper { | ||||
|   APIError read_packet(ReadPacketBuffer *buffer) override; | ||||
|   bool can_write_without_blocking() override; | ||||
|   APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; | ||||
|   std::string getpeername() override { return socket_->getpeername(); } | ||||
|   std::string getpeername() override { return this->socket_->getpeername(); } | ||||
|   int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { | ||||
|     return this->socket_->getpeername(addr, addrlen); | ||||
|   } | ||||
|   APIError close() override; | ||||
|   APIError shutdown(int how) override; | ||||
|   // Give this helper a name for logging | ||||
|   | ||||
| @@ -400,6 +400,34 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet | ||||
|       return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE"; | ||||
|     case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: | ||||
|       return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE"; | ||||
|     case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: | ||||
|       return "BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE"; | ||||
|     default: | ||||
|       return "UNKNOWN"; | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::VoiceAssistantEvent value) { | ||||
|   switch (value) { | ||||
|     case enums::VOICE_ASSISTANT_ERROR: | ||||
|       return "VOICE_ASSISTANT_ERROR"; | ||||
|     case enums::VOICE_ASSISTANT_RUN_START: | ||||
|       return "VOICE_ASSISTANT_RUN_START"; | ||||
|     case enums::VOICE_ASSISTANT_RUN_END: | ||||
|       return "VOICE_ASSISTANT_RUN_END"; | ||||
|     case enums::VOICE_ASSISTANT_STT_START: | ||||
|       return "VOICE_ASSISTANT_STT_START"; | ||||
|     case enums::VOICE_ASSISTANT_STT_END: | ||||
|       return "VOICE_ASSISTANT_STT_END"; | ||||
|     case enums::VOICE_ASSISTANT_INTENT_START: | ||||
|       return "VOICE_ASSISTANT_INTENT_START"; | ||||
|     case enums::VOICE_ASSISTANT_INTENT_END: | ||||
|       return "VOICE_ASSISTANT_INTENT_END"; | ||||
|     case enums::VOICE_ASSISTANT_TTS_START: | ||||
|       return "VOICE_ASSISTANT_TTS_START"; | ||||
|     case enums::VOICE_ASSISTANT_TTS_END: | ||||
|       return "VOICE_ASSISTANT_TTS_END"; | ||||
|     default: | ||||
|       return "UNKNOWN"; | ||||
|   } | ||||
| @@ -592,6 +620,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|       this->bluetooth_proxy_version = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     case 14: { | ||||
|       this->voice_assistant_version = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -652,6 +684,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint32(11, this->bluetooth_proxy_version); | ||||
|   buffer.encode_string(12, this->manufacturer); | ||||
|   buffer.encode_string(13, this->friendly_name); | ||||
|   buffer.encode_uint32(14, this->voice_assistant_version); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | ||||
| @@ -710,6 +743,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const { | ||||
|   out.append("  friendly_name: "); | ||||
|   out.append("'").append(this->friendly_name).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  voice_assistant_version: "); | ||||
|   sprintf(buffer, "%u", this->voice_assistant_version); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -903,6 +941,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 12: { | ||||
|       this->supports_stop = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -955,6 +997,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(9, this->disabled_by_default); | ||||
|   buffer.encode_string(10, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); | ||||
|   buffer.encode_bool(12, this->supports_stop); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||
| @@ -1004,6 +1047,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  supports_stop: "); | ||||
|   out.append(YESNO(this->supports_stop)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -3611,7 +3658,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|       return true; | ||||
|     } | ||||
|     case 7: { | ||||
|       this->legacy_away = value.as_bool(); | ||||
|       this->unused_legacy_away = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 8: { | ||||
| @@ -3681,7 +3728,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_float(4, this->target_temperature); | ||||
|   buffer.encode_float(5, this->target_temperature_low); | ||||
|   buffer.encode_float(6, this->target_temperature_high); | ||||
|   buffer.encode_bool(7, this->legacy_away); | ||||
|   buffer.encode_bool(7, this->unused_legacy_away); | ||||
|   buffer.encode_enum<enums::ClimateAction>(8, this->action); | ||||
|   buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode); | ||||
|   buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode); | ||||
| @@ -3722,8 +3769,8 @@ void ClimateStateResponse::dump_to(std::string &out) const { | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  legacy_away: "); | ||||
|   out.append(YESNO(this->legacy_away)); | ||||
|   out.append("  unused_legacy_away: "); | ||||
|   out.append(YESNO(this->unused_legacy_away)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  action: "); | ||||
| @@ -3775,11 +3822,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) | ||||
|       return true; | ||||
|     } | ||||
|     case 10: { | ||||
|       this->has_legacy_away = value.as_bool(); | ||||
|       this->unused_has_legacy_away = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 11: { | ||||
|       this->legacy_away = value.as_bool(); | ||||
|       this->unused_legacy_away = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 12: { | ||||
| @@ -3864,8 +3911,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_float(7, this->target_temperature_low); | ||||
|   buffer.encode_bool(8, this->has_target_temperature_high); | ||||
|   buffer.encode_float(9, this->target_temperature_high); | ||||
|   buffer.encode_bool(10, this->has_legacy_away); | ||||
|   buffer.encode_bool(11, this->legacy_away); | ||||
|   buffer.encode_bool(10, this->unused_has_legacy_away); | ||||
|   buffer.encode_bool(11, this->unused_legacy_away); | ||||
|   buffer.encode_bool(12, this->has_fan_mode); | ||||
|   buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode); | ||||
|   buffer.encode_bool(14, this->has_swing_mode); | ||||
| @@ -3921,12 +3968,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const { | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  has_legacy_away: "); | ||||
|   out.append(YESNO(this->has_legacy_away)); | ||||
|   out.append("  unused_has_legacy_away: "); | ||||
|   out.append(YESNO(this->unused_has_legacy_away)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  legacy_away: "); | ||||
|   out.append(YESNO(this->legacy_away)); | ||||
|   out.append("  unused_legacy_away: "); | ||||
|   out.append(YESNO(this->unused_legacy_away)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  has_fan_mode: "); | ||||
| @@ -6060,6 +6107,204 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| void UnsubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {} | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { | ||||
|   out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}"); | ||||
| } | ||||
| #endif | ||||
| bool BluetoothDeviceClearCacheResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->address = value.as_uint64(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->success = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 3: { | ||||
|       this->error = value.as_int32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint64(1, this->address); | ||||
|   buffer.encode_bool(2, this->success); | ||||
|   buffer.encode_int32(3, this->error); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("BluetoothDeviceClearCacheResponse {\n"); | ||||
|   out.append("  address: "); | ||||
|   sprintf(buffer, "%llu", this->address); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  success: "); | ||||
|   out.append(YESNO(this->success)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  error: "); | ||||
|   sprintf(buffer, "%d", this->error); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->subscribe = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->subscribe); } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SubscribeVoiceAssistantRequest {\n"); | ||||
|   out.append("  subscribe: "); | ||||
|   out.append(YESNO(this->subscribe)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->start = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantRequest::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantRequest {\n"); | ||||
|   out.append("  start: "); | ||||
|   out.append(YESNO(this->start)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->port = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->error = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint32(1, this->port); | ||||
|   buffer.encode_bool(2, this->error); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantResponse::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantResponse {\n"); | ||||
|   out.append("  port: "); | ||||
|   sprintf(buffer, "%u", this->port); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  error: "); | ||||
|   out.append(YESNO(this->error)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->name = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->value = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantEventData::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(1, this->name); | ||||
|   buffer.encode_string(2, this->value); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantEventData::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantEventData {\n"); | ||||
|   out.append("  name: "); | ||||
|   out.append("'").append(this->name).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  value: "); | ||||
|   out.append("'").append(this->value).append("'"); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->event_type = value.as_enum<enums::VoiceAssistantEvent>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool VoiceAssistantEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 2: { | ||||
|       this->data.push_back(value.as_message<VoiceAssistantEventData>()); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantEventResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_enum<enums::VoiceAssistantEvent>(1, this->event_type); | ||||
|   for (auto &it : this->data) { | ||||
|     buffer.encode_message<VoiceAssistantEventData>(2, it, true); | ||||
|   } | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantEventResponse::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantEventResponse {\n"); | ||||
|   out.append("  event_type: "); | ||||
|   out.append(proto_enum_to_string<enums::VoiceAssistantEvent>(this->event_type)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   for (const auto &it : this->data) { | ||||
|     out.append("  data: "); | ||||
|     it.dump_to(out); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -163,6 +163,18 @@ enum BluetoothDeviceRequestType : uint32_t { | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3, | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4, | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5, | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6, | ||||
| }; | ||||
| enum VoiceAssistantEvent : uint32_t { | ||||
|   VOICE_ASSISTANT_ERROR = 0, | ||||
|   VOICE_ASSISTANT_RUN_START = 1, | ||||
|   VOICE_ASSISTANT_RUN_END = 2, | ||||
|   VOICE_ASSISTANT_STT_START = 3, | ||||
|   VOICE_ASSISTANT_STT_END = 4, | ||||
|   VOICE_ASSISTANT_INTENT_START = 5, | ||||
|   VOICE_ASSISTANT_INTENT_END = 6, | ||||
|   VOICE_ASSISTANT_TTS_START = 7, | ||||
|   VOICE_ASSISTANT_TTS_END = 8, | ||||
| }; | ||||
|  | ||||
| }  // namespace enums | ||||
| @@ -278,6 +290,7 @@ class DeviceInfoResponse : public ProtoMessage { | ||||
|   uint32_t bluetooth_proxy_version{0}; | ||||
|   std::string manufacturer{}; | ||||
|   std::string friendly_name{}; | ||||
|   uint32_t voice_assistant_version{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -362,6 +375,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { | ||||
|   bool disabled_by_default{false}; | ||||
|   std::string icon{}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   bool supports_stop{false}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -945,7 +959,7 @@ class ClimateStateResponse : public ProtoMessage { | ||||
|   float target_temperature{0.0f}; | ||||
|   float target_temperature_low{0.0f}; | ||||
|   float target_temperature_high{0.0f}; | ||||
|   bool legacy_away{false}; | ||||
|   bool unused_legacy_away{false}; | ||||
|   enums::ClimateAction action{}; | ||||
|   enums::ClimateFanMode fan_mode{}; | ||||
|   enums::ClimateSwingMode swing_mode{}; | ||||
| @@ -973,8 +987,8 @@ class ClimateCommandRequest : public ProtoMessage { | ||||
|   float target_temperature_low{0.0f}; | ||||
|   bool has_target_temperature_high{false}; | ||||
|   float target_temperature_high{0.0f}; | ||||
|   bool has_legacy_away{false}; | ||||
|   bool legacy_away{false}; | ||||
|   bool unused_has_legacy_away{false}; | ||||
|   bool unused_legacy_away{false}; | ||||
|   bool has_fan_mode{false}; | ||||
|   enums::ClimateFanMode fan_mode{}; | ||||
|   bool has_swing_mode{false}; | ||||
| @@ -1554,6 +1568,87 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage { | ||||
|  protected: | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { | ||||
|  public: | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
| }; | ||||
| class BluetoothDeviceClearCacheResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint64_t address{0}; | ||||
|   bool success{false}; | ||||
|   int32_t error{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class SubscribeVoiceAssistantRequest : public ProtoMessage { | ||||
|  public: | ||||
|   bool subscribe{false}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class VoiceAssistantRequest : public ProtoMessage { | ||||
|  public: | ||||
|   bool start{false}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class VoiceAssistantResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t port{0}; | ||||
|   bool error{false}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class VoiceAssistantEventData : public ProtoMessage { | ||||
|  public: | ||||
|   std::string name{}; | ||||
|   std::string value{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
| }; | ||||
| class VoiceAssistantEventResponse : public ProtoMessage { | ||||
|  public: | ||||
|   enums::VoiceAssistantEvent event_type{}; | ||||
|   std::vector<VoiceAssistantEventData> data{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -329,6 +329,8 @@ bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayer | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_bluetooth_le_advertisement_response: %s", msg.dump().c_str()); | ||||
| @@ -441,6 +443,30 @@ bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const Blu | ||||
|   return this->send_message_<BluetoothDeviceUnpairingResponse>(msg, 86); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_bluetooth_device_clear_cache_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantRequest &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_voice_assistant_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<VoiceAssistantRequest>(msg, 90); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||
|   switch (msg_type) { | ||||
|     case 1: { | ||||
| @@ -709,12 +735,14 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       break; | ||||
|     } | ||||
|     case 66: { | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|       SubscribeBluetoothLEAdvertisementsRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_bluetooth_le_advertisements_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 68: { | ||||
| @@ -802,6 +830,50 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_bluetooth_connections_free_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 87: { | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|       UnsubscribeBluetoothLEAdvertisementsRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_unsubscribe_bluetooth_le_advertisements_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 89: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       SubscribeVoiceAssistantRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_voice_assistant_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 91: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       VoiceAssistantResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 92: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       VoiceAssistantEventResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_event_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
| @@ -1065,6 +1137,7 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma | ||||
|   this->media_player_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( | ||||
|     const SubscribeBluetoothLEAdvertisementsRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
| @@ -1077,6 +1150,7 @@ void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( | ||||
|   } | ||||
|   this->subscribe_bluetooth_le_advertisements(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
| @@ -1185,6 +1259,33 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request( | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( | ||||
|     const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->unsubscribe_bluetooth_le_advertisements(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->subscribe_voice_assistant(msg); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -154,8 +154,10 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void on_subscribe_bluetooth_le_advertisements_request( | ||||
|       const SubscribeBluetoothLEAdvertisementsRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); | ||||
| #endif | ||||
| @@ -215,6 +217,25 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void on_unsubscribe_bluetooth_le_advertisements_request( | ||||
|       const UnsubscribeBluetoothLEAdvertisementsRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   bool send_voice_assistant_request(const VoiceAssistantRequest &msg); | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){}; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; | ||||
| #endif | ||||
|  protected: | ||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
| @@ -267,7 +288,9 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0; | ||||
| #endif | ||||
| @@ -292,6 +315,12 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( | ||||
|       const SubscribeBluetoothConnectionsFreeRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; | ||||
| #endif | ||||
|  protected: | ||||
|   void on_hello_request(const HelloRequest &msg) override; | ||||
| @@ -339,7 +368,9 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; | ||||
| #endif | ||||
| @@ -364,6 +395,13 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_unsubscribe_bluetooth_le_advertisements_request( | ||||
|       const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
|   | ||||
| @@ -45,7 +45,7 @@ void APIServer::setup() { | ||||
|  | ||||
|   struct sockaddr_storage server; | ||||
|  | ||||
|   socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); | ||||
|   socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); | ||||
|   if (sl == 0) { | ||||
|     ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); | ||||
|     this->mark_failed(); | ||||
| @@ -331,6 +331,17 @@ void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, | ||||
|   } | ||||
| } | ||||
|  | ||||
| void APIServer::send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error) { | ||||
|   BluetoothDeviceClearCacheResponse call; | ||||
|   call.address = address; | ||||
|   call.success = success; | ||||
|   call.error = error; | ||||
|  | ||||
|   for (auto &client : this->clients_) { | ||||
|     client->send_bluetooth_device_clear_cache_response(call); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) { | ||||
|   BluetoothConnectionsFreeResponse call; | ||||
|   call.free = free; | ||||
| @@ -416,5 +427,21 @@ void APIServer::on_shutdown() { | ||||
|   delay(10); | ||||
| } | ||||
|  | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| bool APIServer::start_voice_assistant() { | ||||
|   for (auto &c : this->clients_) { | ||||
|     if (c->request_voice_assistant(true)) | ||||
|       return true; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| void APIServer::stop_voice_assistant() { | ||||
|   for (auto &c : this->clients_) { | ||||
|     if (c->request_voice_assistant(false)) | ||||
|       return; | ||||
|   } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -80,6 +80,7 @@ class APIServer : public Component, public Controller { | ||||
|   void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_connections_free(uint8_t free, uint8_t limit); | ||||
|   void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); | ||||
|   void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call); | ||||
| @@ -94,6 +95,11 @@ class APIServer : public Component, public Controller { | ||||
|   void request_time(); | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   bool start_voice_assistant(); | ||||
|   void stop_voice_assistant(); | ||||
| #endif | ||||
|  | ||||
|   bool is_connected() const; | ||||
|  | ||||
|   struct HomeAssistantStateSubscription { | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "proto.h" | ||||
| #include <cinttypes> | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -13,7 +14,7 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { | ||||
|     uint32_t consumed; | ||||
|     auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); | ||||
|     if (!res.has_value()) { | ||||
|       ESP_LOGV(TAG, "Invalid field start at %u", i); | ||||
|       ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
| @@ -25,12 +26,12 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { | ||||
|       case 0: {  // VarInt | ||||
|         res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); | ||||
|         if (!res.has_value()) { | ||||
|           ESP_LOGV(TAG, "Invalid VarInt at %u", i); | ||||
|           ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         if (!this->decode_varint(field_id, *res)) { | ||||
|           ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32()); | ||||
|           ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32()); | ||||
|         } | ||||
|         i += consumed; | ||||
|         break; | ||||
| @@ -38,38 +39,38 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { | ||||
|       case 2: {  // Length-delimited | ||||
|         res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); | ||||
|         if (!res.has_value()) { | ||||
|           ESP_LOGV(TAG, "Invalid Length Delimited at %u", i); | ||||
|           ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         uint32_t field_length = res->as_uint32(); | ||||
|         i += consumed; | ||||
|         if (field_length > length - i) { | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i); | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) { | ||||
|           ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id); | ||||
|           ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id); | ||||
|         } | ||||
|         i += field_length; | ||||
|         break; | ||||
|       } | ||||
|       case 5: {  // 32-bit | ||||
|         if (length - i < 4) { | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %u", i); | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]); | ||||
|         if (!this->decode_32bit(field_id, Proto32Bit(val))) { | ||||
|           ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val); | ||||
|           ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val); | ||||
|         } | ||||
|         i += 4; | ||||
|         break; | ||||
|       } | ||||
|       default: | ||||
|         ESP_LOGV(TAG, "Invalid field type at %u", i); | ||||
|         ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i); | ||||
|         error = true; | ||||
|         break; | ||||
|     } | ||||
|   | ||||
| @@ -12,7 +12,6 @@ from esphome.const import ( | ||||
|     CONF_CAPACITANCE, | ||||
| ) | ||||
|  | ||||
| AUTO_LOAD = ["sensor", "binary_sensor"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_AS3935_ID = "as3935_id" | ||||
|   | ||||
| @@ -26,9 +26,13 @@ void AS3935Component::setup() { | ||||
| void AS3935Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "AS3935:"); | ||||
|   LOG_PIN("  Interrupt Pin: ", this->irq_pin_); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   LOG_BINARY_SENSOR("  ", "Thunder alert", this->thunder_alert_binary_sensor_); | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   LOG_SENSOR("  ", "Distance", this->distance_sensor_); | ||||
|   LOG_SENSOR("  ", "Lightning energy", this->energy_sensor_); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| float AS3935Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
| @@ -44,16 +48,22 @@ void AS3935Component::loop() { | ||||
|     ESP_LOGI(TAG, "Disturber was detected - try increasing the spike rejection value!"); | ||||
|   } else if (int_value == LIGHTNING_INT) { | ||||
|     ESP_LOGI(TAG, "Lightning has been detected!"); | ||||
|     if (this->thunder_alert_binary_sensor_ != nullptr) | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|     if (this->thunder_alert_binary_sensor_ != nullptr) { | ||||
|       this->thunder_alert_binary_sensor_->publish_state(true); | ||||
|       this->set_timeout(10, [this]() { this->thunder_alert_binary_sensor_->publish_state(false); }); | ||||
|     } | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|     uint8_t distance = this->get_distance_to_storm_(); | ||||
|     if (this->distance_sensor_ != nullptr) | ||||
|       this->distance_sensor_->publish_state(distance); | ||||
|  | ||||
|     uint32_t energy = this->get_lightning_energy_(); | ||||
|     if (this->energy_sensor_ != nullptr) | ||||
|       this->energy_sensor_->publish_state(energy); | ||||
| #endif | ||||
|   } | ||||
|   this->thunder_alert_binary_sensor_->publish_state(false); | ||||
| } | ||||
|  | ||||
| void AS3935Component::write_indoor(bool indoor) { | ||||
|   | ||||
| @@ -1,9 +1,14 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #ifdef USE_SENSOR | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #endif | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace as3935 { | ||||
| @@ -52,6 +57,15 @@ enum AS3935Values { | ||||
| }; | ||||
|  | ||||
| class AS3935Component : public Component { | ||||
| #ifdef USE_SENSOR | ||||
|   SUB_SENSOR(distance) | ||||
|   SUB_SENSOR(energy) | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   SUB_BINARY_SENSOR(thunder_alert) | ||||
| #endif | ||||
|  | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
| @@ -59,11 +73,7 @@ class AS3935Component : public Component { | ||||
|   void loop() override; | ||||
|  | ||||
|   void set_irq_pin(GPIOPin *irq_pin) { irq_pin_ = irq_pin; } | ||||
|   void set_distance_sensor(sensor::Sensor *distance_sensor) { distance_sensor_ = distance_sensor; } | ||||
|   void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } | ||||
|   void set_thunder_alert_binary_sensor(binary_sensor::BinarySensor *thunder_alert_binary_sensor) { | ||||
|     thunder_alert_binary_sensor_ = thunder_alert_binary_sensor; | ||||
|   } | ||||
|  | ||||
|   void set_indoor(bool indoor) { indoor_ = indoor; } | ||||
|   void write_indoor(bool indoor); | ||||
|   void set_noise_level(uint8_t noise_level) { noise_level_ = noise_level; } | ||||
| @@ -92,9 +102,6 @@ class AS3935Component : public Component { | ||||
|  | ||||
|   virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0; | ||||
|  | ||||
|   sensor::Sensor *distance_sensor_{nullptr}; | ||||
|   sensor::Sensor *energy_sensor_{nullptr}; | ||||
|   binary_sensor::BinarySensor *thunder_alert_binary_sensor_{nullptr}; | ||||
|   GPIOPin *irq_pin_; | ||||
|  | ||||
|   bool indoor_; | ||||
|   | ||||
| @@ -18,5 +18,5 @@ async def to_code(config): | ||||
|         # https://github.com/esphome/AsyncTCP/blob/master/library.json | ||||
|         cg.add_library("esphome/AsyncTCP-esphome", "1.2.2") | ||||
|     elif CORE.is_esp8266: | ||||
|         # https://github.com/OttoWinter/ESPAsyncTCP | ||||
|         cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3") | ||||
|         # https://github.com/esphome/ESPAsyncTCP | ||||
|         cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3") | ||||
|   | ||||
| @@ -116,7 +116,7 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo | ||||
|   void update() override; | ||||
|   void dump_config() override; | ||||
|   void setup() override { this->codec_ = make_unique<BedjetCodec>(); } | ||||
|   float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } | ||||
|   float get_setup_priority() const override { return setup_priority::BLUETOOTH; } | ||||
|  | ||||
|   /** @return The BedJet's configured name, or the MAC address if not discovered yet. */ | ||||
|   std::string get_name() { | ||||
|   | ||||
| @@ -41,17 +41,9 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { | ||||
|     this->state_callback_.call(state); | ||||
|   } | ||||
| } | ||||
| std::string BinarySensor::device_class() { return ""; } | ||||
|  | ||||
| BinarySensor::BinarySensor() : state(false) {} | ||||
| void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } | ||||
| std::string BinarySensor::get_device_class() { | ||||
|   if (this->device_class_.has_value()) | ||||
|     return *this->device_class_; | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
|   return this->device_class(); | ||||
| #pragma GCC diagnostic pop | ||||
| } | ||||
|  | ||||
| void BinarySensor::add_filter(Filter *filter) { | ||||
|   filter->parent_ = this; | ||||
|   if (this->filter_list_ == nullptr) { | ||||
|   | ||||
| @@ -34,7 +34,7 @@ namespace binary_sensor { | ||||
|  * The sub classes should notify the front-end of new states via the publish_state() method which | ||||
|  * handles inverted inputs for you. | ||||
|  */ | ||||
| class BinarySensor : public EntityBase { | ||||
| class BinarySensor : public EntityBase, public EntityBase_DeviceClass { | ||||
|  public: | ||||
|   explicit BinarySensor(); | ||||
|  | ||||
| @@ -60,12 +60,6 @@ class BinarySensor : public EntityBase { | ||||
|   /// The current reported state of the binary sensor. | ||||
|   bool state; | ||||
|  | ||||
|   /// Manually set the Home Assistant device class (see binary_sensor::device_class) | ||||
|   void set_device_class(const std::string &device_class); | ||||
|  | ||||
|   /// Get the device class for this binary sensor, using the manual override if specified. | ||||
|   std::string get_device_class(); | ||||
|  | ||||
|   void add_filter(Filter *filter); | ||||
|   void add_filters(const std::vector<Filter *> &filters); | ||||
|  | ||||
| @@ -80,17 +74,8 @@ class BinarySensor : public EntityBase { | ||||
|  | ||||
|   virtual bool is_status_binary_sensor() const; | ||||
|  | ||||
|   // ========== OVERRIDE METHODS ========== | ||||
|   // (You'll only need this when creating your own custom binary sensor) | ||||
|   /** Override this to set the default device class. | ||||
|    * | ||||
|    * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) | ||||
|    */ | ||||
|   virtual std::string device_class(); | ||||
|  | ||||
|  protected: | ||||
|   CallbackManager<void(bool)> state_callback_{}; | ||||
|   optional<std::string> device_class_{};  ///< Stores the override of the device class | ||||
|   Filter *filter_list_{nullptr}; | ||||
|   bool has_state_{false}; | ||||
|   bool publish_initial_state_{false}; | ||||
|   | ||||
| @@ -16,6 +16,9 @@ void BinarySensorMap::loop() { | ||||
|     case BINARY_SENSOR_MAP_TYPE_SUM: | ||||
|       this->process_sum_(); | ||||
|       break; | ||||
|     case BINARY_SENSOR_MAP_TYPE_BAYESIAN: | ||||
|       this->process_bayesian_(); | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -23,69 +26,117 @@ void BinarySensorMap::process_group_() { | ||||
|   float total_current_value = 0.0; | ||||
|   uint8_t num_active_sensors = 0; | ||||
|   uint64_t mask = 0x00; | ||||
|   // check all binary_sensors for its state. when active add its value to total_current_value. | ||||
|   // create a bitmask for the binary_sensor status on all channels | ||||
|  | ||||
|   // - check all binary_sensors for its state | ||||
|   //  - if active, add its value to total_current_value. | ||||
|   // - creates a bitmask for the binary_sensor states on all channels | ||||
|   for (size_t i = 0; i < this->channels_.size(); i++) { | ||||
|     auto bs = this->channels_[i]; | ||||
|     if (bs.binary_sensor->state) { | ||||
|       num_active_sensors++; | ||||
|       total_current_value += bs.sensor_value; | ||||
|       mask |= 1 << i; | ||||
|       total_current_value += bs.parameters.sensor_value; | ||||
|       mask |= 1ULL << i; | ||||
|     } | ||||
|   } | ||||
|   // check if the sensor map was touched | ||||
|  | ||||
|   // potentially update state only if a binary_sensor is active | ||||
|   if (mask != 0ULL) { | ||||
|     // did the bit_mask change or is it a new sensor touch | ||||
|     // publish the average if the bitmask has changed | ||||
|     if (this->last_mask_ != mask) { | ||||
|       float publish_value = total_current_value / num_active_sensors; | ||||
|       ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value); | ||||
|       this->publish_state(publish_value); | ||||
|     } | ||||
|   } else if (this->last_mask_ != 0ULL) { | ||||
|     // is this a new sensor release | ||||
|     ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str()); | ||||
|     // no buttons are pressed and the states have changed since last run, so publish NAN | ||||
|     ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str()); | ||||
|     this->publish_state(NAN); | ||||
|   } | ||||
|  | ||||
|   this->last_mask_ = mask; | ||||
| } | ||||
|  | ||||
| void BinarySensorMap::process_sum_() { | ||||
|   float total_current_value = 0.0; | ||||
|   uint64_t mask = 0x00; | ||||
|   // check all binary_sensors for its state. when active add its value to total_current_value. | ||||
|   // create a bitmask for the binary_sensor status on all channels | ||||
|  | ||||
|   // - check all binary_sensor states | ||||
|   // - if active, add its value to total_current_value | ||||
|   // - creates a bitmask for the binary_sensor states on all channels | ||||
|   for (size_t i = 0; i < this->channels_.size(); i++) { | ||||
|     auto bs = this->channels_[i]; | ||||
|     if (bs.binary_sensor->state) { | ||||
|       total_current_value += bs.sensor_value; | ||||
|       mask |= 1 << i; | ||||
|       total_current_value += bs.parameters.sensor_value; | ||||
|       mask |= 1ULL << i; | ||||
|     } | ||||
|   } | ||||
|   // check if the sensor map was touched | ||||
|   if (mask != 0ULL) { | ||||
|     // did the bit_mask change or is it a new sensor touch | ||||
|     if (this->last_mask_ != mask) { | ||||
|       float publish_value = total_current_value; | ||||
|       ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value); | ||||
|       this->publish_state(publish_value); | ||||
|     } | ||||
|   } else if (this->last_mask_ != 0ULL) { | ||||
|     // is this a new sensor release | ||||
|     ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing 0", this->name_.c_str()); | ||||
|     this->publish_state(0.0); | ||||
|  | ||||
|   // update state only if any binary_sensor states have changed or if no state has ever been sent on boot | ||||
|   if ((this->last_mask_ != mask) || (!this->has_state())) { | ||||
|     this->publish_state(total_current_value); | ||||
|   } | ||||
|  | ||||
|   this->last_mask_ = mask; | ||||
| } | ||||
|  | ||||
| void BinarySensorMap::process_bayesian_() { | ||||
|   float posterior_probability = this->bayesian_prior_; | ||||
|   uint64_t mask = 0x00; | ||||
|  | ||||
|   // - compute the posterior probability by taking the product of the predicate probablities for each observation | ||||
|   // - create a bitmask for the binary_sensor states on all channels/observations | ||||
|   for (size_t i = 0; i < this->channels_.size(); i++) { | ||||
|     auto bs = this->channels_[i]; | ||||
|  | ||||
|     posterior_probability *= | ||||
|         this->bayesian_predicate_(bs.binary_sensor->state, posterior_probability, | ||||
|                                   bs.parameters.probabilities.given_true, bs.parameters.probabilities.given_false); | ||||
|  | ||||
|     mask |= ((uint64_t) (bs.binary_sensor->state)) << i; | ||||
|   } | ||||
|  | ||||
|   // update state only if any binary_sensor states have changed or if no state has ever been sent on boot | ||||
|   if ((this->last_mask_ != mask) || (!this->has_state())) { | ||||
|     this->publish_state(posterior_probability); | ||||
|   } | ||||
|  | ||||
|   this->last_mask_ = mask; | ||||
| } | ||||
|  | ||||
| float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, | ||||
|                                            float prob_given_false) { | ||||
|   float prob_state_source_true = prob_given_true; | ||||
|   float prob_state_source_false = prob_given_false; | ||||
|  | ||||
|   // if sensor is off, then we use the probabilities for the observation's complement | ||||
|   if (!sensor_state) { | ||||
|     prob_state_source_true = 1 - prob_given_true; | ||||
|     prob_state_source_false = 1 - prob_given_false; | ||||
|   } | ||||
|  | ||||
|   return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false); | ||||
| } | ||||
|  | ||||
| void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) { | ||||
|   BinarySensorMapChannel sensor_channel{ | ||||
|       .binary_sensor = sensor, | ||||
|       .sensor_value = value, | ||||
|       .parameters{ | ||||
|           .sensor_value = value, | ||||
|       }, | ||||
|   }; | ||||
|   this->channels_.push_back(sensor_channel); | ||||
| } | ||||
|  | ||||
| void BinarySensorMap::set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; } | ||||
|  | ||||
| void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false) { | ||||
|   BinarySensorMapChannel sensor_channel{ | ||||
|       .binary_sensor = sensor, | ||||
|       .parameters{ | ||||
|           .probabilities{ | ||||
|               .given_true = prob_given_true, | ||||
|               .given_false = prob_given_false, | ||||
|           }, | ||||
|       }, | ||||
|   }; | ||||
|   this->channels_.push_back(sensor_channel); | ||||
| } | ||||
| }  // namespace binary_sensor_map | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -12,51 +12,88 @@ namespace binary_sensor_map { | ||||
| enum BinarySensorMapType { | ||||
|   BINARY_SENSOR_MAP_TYPE_GROUP, | ||||
|   BINARY_SENSOR_MAP_TYPE_SUM, | ||||
|   BINARY_SENSOR_MAP_TYPE_BAYESIAN, | ||||
| }; | ||||
|  | ||||
| struct BinarySensorMapChannel { | ||||
|   binary_sensor::BinarySensor *binary_sensor; | ||||
|   float sensor_value; | ||||
|   union { | ||||
|     float sensor_value; | ||||
|     struct { | ||||
|       float given_true; | ||||
|       float given_false; | ||||
|     } probabilities; | ||||
|   } parameters; | ||||
| }; | ||||
|  | ||||
| /** Class to group binary_sensors to one Sensor. | ||||
| /** Class to map one or more binary_sensors to one Sensor. | ||||
|  * | ||||
|  * Each binary sensor represents a float value in the group. | ||||
|  * Each binary sensor has configured parameters that each mapping type uses to compute the single numerical result | ||||
|  */ | ||||
| class BinarySensorMap : public sensor::Sensor, public Component { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|  | ||||
|   /** | ||||
|    * The loop checks all binary_sensor states | ||||
|    * When the binary_sensor reports a true value for its state, then the float value it represents is added to the | ||||
|    * total_current_value | ||||
|    * The loop calls the configured type processing method | ||||
|    * | ||||
|    * Only when the total_current_value changed and at least one sensor reports an active state we publish the sensors | ||||
|    * average value. When the value changed and no sensors ar active we publish NAN. | ||||
|    * */ | ||||
|    * The processing method loops through all sensors and calculates the numerical result | ||||
|    * The result is only published if a binary sensor state has changed or, for some types, on initial boot | ||||
|    */ | ||||
|   void loop() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   /** Add binary_sensors to the group. | ||||
|    * Each binary_sensor represents a float value when its state is true | ||||
|  | ||||
|   /** | ||||
|    * Add binary_sensors to the group when only one parameter is needed for the configured mapping type. | ||||
|    * | ||||
|    * @param *sensor The binary sensor. | ||||
|    * @param value  The value this binary_sensor represents | ||||
|    */ | ||||
|   void add_channel(binary_sensor::BinarySensor *sensor, float value); | ||||
|   void set_sensor_type(BinarySensorMapType sensor_type); | ||||
|  | ||||
|   /** | ||||
|    * Add binary_sensors to the group when two parameters are needed for the Bayesian mapping type. | ||||
|    * | ||||
|    * @param *sensor The binary sensor. | ||||
|    * @param prob_given_true Probability this observation is on when the Bayesian event is true | ||||
|    * @param prob_given_false Probability this observation is on when the Bayesian event is false | ||||
|    */ | ||||
|   void add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false); | ||||
|  | ||||
|   void set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; } | ||||
|  | ||||
|   void set_bayesian_prior(float prior) { this->bayesian_prior_ = prior; }; | ||||
|  | ||||
|  protected: | ||||
|   std::vector<BinarySensorMapChannel> channels_{}; | ||||
|   BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP}; | ||||
|   // this gives max 64 channels per binary_sensor_map | ||||
|  | ||||
|   // this allows a max of 64 channels/observations in order to keep track of binary_sensor states | ||||
|   uint64_t last_mask_{0x00}; | ||||
|  | ||||
|   // Bayesian event prior probability before taking into account any observations | ||||
|   float bayesian_prior_{}; | ||||
|  | ||||
|   /** | ||||
|    * methods to process the types of binary_sensor_maps | ||||
|    * GROUP: process_group_() just map to a value | ||||
|    * Methods to process the binary_sensor_maps types | ||||
|    * | ||||
|    * GROUP: process_group_() averages all the values | ||||
|    * ADD: process_add_() adds all the values | ||||
|    * BAYESIAN: process_bayesian_() computes the predicate probability | ||||
|    * */ | ||||
|   void process_group_(); | ||||
|   void process_sum_(); | ||||
|   void process_bayesian_(); | ||||
|  | ||||
|   /** | ||||
|    * Computes the Bayesian predicate for a specific observation | ||||
|    * If the sensor state is false, then we use the parameters' probabilities for the observatiosn complement | ||||
|    * | ||||
|    * @param sensor_state  State of observation | ||||
|    * @param prior Prior probability before accounting for this observation | ||||
|    * @param prob_given_true Probability this observation is on when the Bayesian event is true | ||||
|    * @param prob_given_false Probability this observation is on when the Bayesian event is false | ||||
|    * */ | ||||
|   float bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, float prob_given_false); | ||||
| }; | ||||
|  | ||||
| }  // namespace binary_sensor_map | ||||
|   | ||||
| @@ -20,16 +20,29 @@ BinarySensorMap = binary_sensor_map_ns.class_( | ||||
| ) | ||||
| SensorMapType = binary_sensor_map_ns.enum("SensorMapType") | ||||
|  | ||||
| CONF_BAYESIAN = "bayesian" | ||||
| CONF_PRIOR = "prior" | ||||
| CONF_PROB_GIVEN_TRUE = "prob_given_true" | ||||
| CONF_PROB_GIVEN_FALSE = "prob_given_false" | ||||
| CONF_OBSERVATIONS = "observations" | ||||
|  | ||||
| SENSOR_MAP_TYPES = { | ||||
|     CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, | ||||
|     CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM, | ||||
|     CONF_BAYESIAN: SensorMapType.BINARY_SENSOR_MAP_TYPE_BAYESIAN, | ||||
| } | ||||
|  | ||||
| entry = { | ||||
| entry_one_parameter = { | ||||
|     cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor), | ||||
|     cv.Required(CONF_VALUE): cv.float_, | ||||
| } | ||||
|  | ||||
| entry_bayesian_parameters = { | ||||
|     cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor), | ||||
|     cv.Required(CONF_PROB_GIVEN_TRUE): cv.float_range(min=0, max=1), | ||||
|     cv.Required(CONF_PROB_GIVEN_FALSE): cv.float_range(min=0, max=1), | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.typed_schema( | ||||
|     { | ||||
|         CONF_GROUP: sensor.sensor_schema( | ||||
| @@ -39,7 +52,7 @@ CONFIG_SCHEMA = cv.typed_schema( | ||||
|         ).extend( | ||||
|             { | ||||
|                 cv.Required(CONF_CHANNELS): cv.All( | ||||
|                     cv.ensure_list(entry), cv.Length(min=1) | ||||
|                     cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
| @@ -50,7 +63,18 @@ CONFIG_SCHEMA = cv.typed_schema( | ||||
|         ).extend( | ||||
|             { | ||||
|                 cv.Required(CONF_CHANNELS): cv.All( | ||||
|                     cv.ensure_list(entry), cv.Length(min=1) | ||||
|                     cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|         CONF_BAYESIAN: sensor.sensor_schema( | ||||
|             BinarySensorMap, | ||||
|             accuracy_decimals=2, | ||||
|         ).extend( | ||||
|             { | ||||
|                 cv.Required(CONF_PRIOR): cv.float_range(min=0, max=1), | ||||
|                 cv.Required(CONF_OBSERVATIONS): cv.All( | ||||
|                     cv.ensure_list(entry_bayesian_parameters), cv.Length(min=1, max=64) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
| @@ -66,6 +90,17 @@ async def to_code(config): | ||||
|     constant = SENSOR_MAP_TYPES[config[CONF_TYPE]] | ||||
|     cg.add(var.set_sensor_type(constant)) | ||||
|  | ||||
|     for ch in config[CONF_CHANNELS]: | ||||
|         input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR]) | ||||
|         cg.add(var.add_channel(input_var, ch[CONF_VALUE])) | ||||
|     if config[CONF_TYPE] == CONF_BAYESIAN: | ||||
|         cg.add(var.set_bayesian_prior(config[CONF_PRIOR])) | ||||
|  | ||||
|         for obs in config[CONF_OBSERVATIONS]: | ||||
|             input_var = await cg.get_variable(obs[CONF_BINARY_SENSOR]) | ||||
|             cg.add( | ||||
|                 var.add_channel( | ||||
|                     input_var, obs[CONF_PROB_GIVEN_TRUE], obs[CONF_PROB_GIVEN_FALSE] | ||||
|                 ) | ||||
|             ) | ||||
|     else: | ||||
|         for ch in config[CONF_CHANNELS]: | ||||
|             input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR]) | ||||
|             cg.add(var.add_channel(input_var, ch[CONF_VALUE])) | ||||
|   | ||||
| @@ -29,8 +29,35 @@ BLEClientConnectTrigger = ble_client_ns.class_( | ||||
| BLEClientDisconnectTrigger = ble_client_ns.class_( | ||||
|     "BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef) | ||||
| ) | ||||
| BLEClientPasskeyRequestTrigger = ble_client_ns.class_( | ||||
|     "BLEClientPasskeyRequestTrigger", automation.Trigger.template(BLEClientNodeConstRef) | ||||
| ) | ||||
| BLEClientPasskeyNotificationTrigger = ble_client_ns.class_( | ||||
|     "BLEClientPasskeyNotificationTrigger", | ||||
|     automation.Trigger.template(BLEClientNodeConstRef, cg.uint32), | ||||
| ) | ||||
| BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_( | ||||
|     "BLEClientNumericComparisonRequestTrigger", | ||||
|     automation.Trigger.template(BLEClientNodeConstRef, cg.uint32), | ||||
| ) | ||||
|  | ||||
| # Actions | ||||
| BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action) | ||||
| BLEPasskeyReplyAction = ble_client_ns.class_( | ||||
|     "BLEClientPasskeyReplyAction", automation.Action | ||||
| ) | ||||
| BLENumericComparisonReplyAction = ble_client_ns.class_( | ||||
|     "BLEClientNumericComparisonReplyAction", automation.Action | ||||
| ) | ||||
| BLERemoveBondAction = ble_client_ns.class_( | ||||
|     "BLEClientRemoveBondAction", automation.Action | ||||
| ) | ||||
|  | ||||
| CONF_PASSKEY = "passkey" | ||||
| CONF_ACCEPT = "accept" | ||||
| CONF_ON_PASSKEY_REQUEST = "on_passkey_request" | ||||
| CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" | ||||
| CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" | ||||
|  | ||||
| # Espressif platformio framework is built with MAX_BLE_CONN to 3, so | ||||
| # enforce this in yaml checks. | ||||
| @@ -56,6 +83,29 @@ CONFIG_SCHEMA = ( | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_PASSKEY_REQUEST): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
|                         BLEClientPasskeyRequestTrigger | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_PASSKEY_NOTIFICATION): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
|                         BLEClientPasskeyNotificationTrigger | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional( | ||||
|                 CONF_ON_NUMERIC_COMPARISON_REQUEST | ||||
|             ): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
|                         BLEClientNumericComparisonRequestTrigger | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| @@ -85,13 +135,34 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema( | ||||
|     } | ||||
| ) | ||||
|  | ||||
| BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||
|         cv.Required(CONF_ACCEPT): cv.templatable(cv.boolean), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| BLE_PASSKEY_REPLY_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||
|         cv.Required(CONF_PASSKEY): cv.templatable(cv.int_range(min=0, max=999999)), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA | ||||
| ) | ||||
| async def ble_write_to_code(config, action_id, template_arg, args): | ||||
|     paren = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|  | ||||
|     value = config[CONF_VALUE] | ||||
|     if cg.is_template(value): | ||||
| @@ -137,6 +208,54 @@ async def ble_write_to_code(config, action_id, template_arg, args): | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.numeric_comparison_reply", | ||||
|     BLENumericComparisonReplyAction, | ||||
|     BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA, | ||||
| ) | ||||
| async def numeric_comparison_reply_to_code(config, action_id, template_arg, args): | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|  | ||||
|     accept = config[CONF_ACCEPT] | ||||
|     if cg.is_template(accept): | ||||
|         templ = await cg.templatable(accept, args, cg.bool_) | ||||
|         cg.add(var.set_value_template(templ)) | ||||
|     else: | ||||
|         cg.add(var.set_value_simple(accept)) | ||||
|  | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.passkey_reply", BLEPasskeyReplyAction, BLE_PASSKEY_REPLY_ACTION_SCHEMA | ||||
| ) | ||||
| async def passkey_reply_to_code(config, action_id, template_arg, args): | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|  | ||||
|     passkey = config[CONF_PASSKEY] | ||||
|     if cg.is_template(passkey): | ||||
|         templ = await cg.templatable(passkey, args, cg.uint32) | ||||
|         cg.add(var.set_value_template(templ)) | ||||
|     else: | ||||
|         cg.add(var.set_value_simple(passkey)) | ||||
|  | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.remove_bond", | ||||
|     BLERemoveBondAction, | ||||
|     BLE_REMOVE_BOND_ACTION_SCHEMA, | ||||
| ) | ||||
| async def remove_bond_to_code(config, action_id, template_arg, args): | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|  | ||||
|     return var | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
| @@ -148,3 +267,12 @@ async def to_code(config): | ||||
|     for conf in config.get(CONF_ON_DISCONNECT, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|     for conf in config.get(CONF_ON_PASSKEY_REQUEST, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|     for conf in config.get(CONF_ON_PASSKEY_NOTIFICATION, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf) | ||||
|     for conf in config.get(CONF_ON_NUMERIC_COMPARISON_REQUEST, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf) | ||||
|   | ||||
| @@ -37,6 +37,44 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode { | ||||
|  public: | ||||
|   explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() override {} | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||
|     if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && | ||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||
|       this->trigger(); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLEClientNode { | ||||
|  public: | ||||
|   explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() override {} | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||
|     if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && | ||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||
|       uint32_t passkey = param->ble_security.key_notif.passkey; | ||||
|       this->trigger(passkey); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, public BLEClientNode { | ||||
|  public: | ||||
|   explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() override {} | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||
|     if (event == ESP_GAP_BLE_NC_REQ_EVT && | ||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||
|       uint32_t passkey = param->ble_security.key_notif.passkey; | ||||
|       this->trigger(passkey); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class BLEWriterClientNode : public BLEClientNode { | ||||
|  public: | ||||
|   BLEWriterClientNode(BLEClient *ble_client) { | ||||
| @@ -94,6 +132,86 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ | ||||
|   std::function<std::vector<uint8_t>(Ts...)> value_template_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> { | ||||
|  public: | ||||
|   BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; } | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     uint32_t passkey; | ||||
|     if (has_simple_value_) { | ||||
|       passkey = this->value_simple_; | ||||
|     } else { | ||||
|       passkey = this->value_template_(x...); | ||||
|     } | ||||
|     if (passkey > 999999) | ||||
|       return; | ||||
|     esp_bd_addr_t remote_bda; | ||||
|     memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); | ||||
|     esp_ble_passkey_reply(remote_bda, true, passkey); | ||||
|   } | ||||
|  | ||||
|   void set_value_template(std::function<uint32_t(Ts...)> func) { | ||||
|     this->value_template_ = std::move(func); | ||||
|     has_simple_value_ = false; | ||||
|   } | ||||
|  | ||||
|   void set_value_simple(const uint32_t &value) { | ||||
|     this->value_simple_ = value; | ||||
|     has_simple_value_ = true; | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   BLEClient *parent_{nullptr}; | ||||
|   bool has_simple_value_ = true; | ||||
|   uint32_t value_simple_{0}; | ||||
|   std::function<uint32_t(Ts...)> value_template_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> { | ||||
|  public: | ||||
|   BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; } | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     esp_bd_addr_t remote_bda; | ||||
|     memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); | ||||
|     if (has_simple_value_) { | ||||
|       esp_ble_confirm_reply(remote_bda, this->value_simple_); | ||||
|     } else { | ||||
|       esp_ble_confirm_reply(remote_bda, this->value_template_(x...)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void set_value_template(std::function<bool(Ts...)> func) { | ||||
|     this->value_template_ = std::move(func); | ||||
|     has_simple_value_ = false; | ||||
|   } | ||||
|  | ||||
|   void set_value_simple(const bool &value) { | ||||
|     this->value_simple_ = value; | ||||
|     has_simple_value_ = true; | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   BLEClient *parent_{nullptr}; | ||||
|   bool has_simple_value_ = true; | ||||
|   bool value_simple_{false}; | ||||
|   std::function<bool(Ts...)> value_template_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> { | ||||
|  public: | ||||
|   BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; } | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     esp_bd_addr_t remote_bda; | ||||
|     memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); | ||||
|     esp_ble_remove_bond_device(remote_bda); | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   BLEClient *parent_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace ble_client | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -27,7 +27,7 @@ class BLEClient; | ||||
| class BLEClientNode { | ||||
|  public: | ||||
|   virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                                    esp_ble_gattc_cb_param_t *param) = 0; | ||||
|                                    esp_ble_gattc_cb_param_t *param){}; | ||||
|   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {} | ||||
|   virtual void loop() {} | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|   | ||||
| @@ -306,6 +306,13 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest | ||||
|       api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret); | ||||
|       break; | ||||
|     } | ||||
|     case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: { | ||||
|       esp_bd_addr_t address; | ||||
|       uint64_to_bd_addr(msg.address, address); | ||||
|       esp_err_t ret = esp_ble_gattc_cache_clean(address); | ||||
|       api::global_api_server->send_bluetooth_device_clear_cache(msg.address, ret == ESP_OK, ret); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -68,6 +68,14 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com | ||||
|  | ||||
| extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|  | ||||
| // Version 1: Initial version without active connections | ||||
| // Version 2: Support for active connections | ||||
| // Version 3: New connection API | ||||
| // Version 4: Pairing support | ||||
| // Version 5: Cache clear support | ||||
| static const uint32_t ACTIVE_CONNECTIONS_VERSION = 5; | ||||
| static const uint32_t PASSIVE_ONLY_VERSION = 1; | ||||
|  | ||||
| }  // namespace bluetooth_proxy | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #include "bme680.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bme680 { | ||||
| @@ -275,8 +275,8 @@ uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) { | ||||
|   var3 = var1 + (var2 / 2); | ||||
|   var4 = (var3 / (res_heat_range + 4)); | ||||
|   var5 = (131 * res_heat_val) + 65536; | ||||
|   heatr_res_x100 = (int32_t)(((var4 / var5) - 250) * 34); | ||||
|   heatr_res = (uint8_t)((heatr_res_x100 + 50) / 100); | ||||
|   heatr_res_x100 = (int32_t) (((var4 / var5) - 250) * 34); | ||||
|   heatr_res = (uint8_t) ((heatr_res_x100 + 50) / 100); | ||||
|  | ||||
|   return heatr_res; | ||||
| } | ||||
| @@ -316,7 +316,7 @@ void BME680Component::read_data_() { | ||||
|   uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4); | ||||
|   uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4); | ||||
|   uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]); | ||||
|   uint16_t raw_gas = (uint16_t)((uint32_t) data[13] * 4 | (((uint32_t) data[14]) / 64)); | ||||
|   uint16_t raw_gas = (uint16_t) ((uint32_t) data[13] * 4 | (((uint32_t) data[14]) / 64)); | ||||
|   uint8_t gas_range = data[14] & 0x0F; | ||||
|  | ||||
|   float temperature = this->calc_temperature_(raw_temperature); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c | ||||
| from esphome.components import i2c, esp32 | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| CODEOWNERS = ["@trvrnrth"] | ||||
| @@ -32,22 +32,31 @@ BME680BSECComponent = bme680_bsec_ns.class_( | ||||
|     "BME680BSECComponent", cg.Component, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(BME680BSECComponent), | ||||
|         cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature, | ||||
|         cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum( | ||||
|             IAQ_MODE_OPTIONS, upper=True | ||||
|         ), | ||||
|         cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( | ||||
|             SAMPLE_RATE_OPTIONS, upper=True | ||||
|         ), | ||||
|         cv.Optional( | ||||
|             CONF_STATE_SAVE_INTERVAL, default="6hours" | ||||
|         ): cv.positive_time_period_minutes, | ||||
|     }, | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BME680BSECComponent), | ||||
|             cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature, | ||||
|             cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum( | ||||
|                 IAQ_MODE_OPTIONS, upper=True | ||||
|             ), | ||||
|             cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( | ||||
|                 SAMPLE_RATE_OPTIONS, upper=True | ||||
|             ), | ||||
|             cv.Optional( | ||||
|                 CONF_STATE_SAVE_INTERVAL, default="6hours" | ||||
|             ): cv.positive_time_period_minutes, | ||||
|         } | ||||
|     ).extend(i2c.i2c_device_schema(0x76)), | ||||
|     cv.only_with_arduino, | ||||
| ).extend(i2c.i2c_device_schema(0x76)) | ||||
|     cv.Any( | ||||
|         cv.only_on_esp8266, | ||||
|         cv.All( | ||||
|             cv.only_on_esp32, | ||||
|             esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32]), | ||||
|         ), | ||||
|     ), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|   | ||||
| @@ -6,9 +6,6 @@ namespace button { | ||||
|  | ||||
| static const char *const TAG = "button"; | ||||
|  | ||||
| Button::Button(const std::string &name) : EntityBase(name) {} | ||||
| Button::Button() : Button("") {} | ||||
|  | ||||
| void Button::press() { | ||||
|   ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); | ||||
|   this->press_action(); | ||||
| @@ -16,8 +13,5 @@ void Button::press() { | ||||
| } | ||||
| void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); } | ||||
|  | ||||
| void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } | ||||
| std::string Button::get_device_class() { return this->device_class_; } | ||||
|  | ||||
| }  // namespace button | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -26,11 +26,8 @@ namespace button { | ||||
|  * | ||||
|  * A button is just a momentary switch that does not have a state, only a trigger. | ||||
|  */ | ||||
| class Button : public EntityBase { | ||||
| class Button : public EntityBase, public EntityBase_DeviceClass { | ||||
|  public: | ||||
|   explicit Button(); | ||||
|   explicit Button(const std::string &name); | ||||
|  | ||||
|   /** Press this button. This is called by the front-end. | ||||
|    * | ||||
|    * For implementing buttons, please override press_action. | ||||
| @@ -43,19 +40,12 @@ class Button : public EntityBase { | ||||
|    */ | ||||
|   void add_on_press_callback(std::function<void()> &&callback); | ||||
|  | ||||
|   /// Set the Home Assistant device class (see button::device_class). | ||||
|   void set_device_class(const std::string &device_class); | ||||
|  | ||||
|   /// Get the device class for this button. | ||||
|   std::string get_device_class(); | ||||
|  | ||||
|  protected: | ||||
|   /** You should implement this virtual method if you want to create your own button. | ||||
|    */ | ||||
|   virtual void press_action() = 0; | ||||
|  | ||||
|   CallbackManager<void()> press_callback_{}; | ||||
|   std::string device_class_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace button | ||||
|   | ||||
| @@ -145,8 +145,8 @@ void CCS811Component::send_env_data_() { | ||||
|   // https://github.com/adafruit/Adafruit_CCS811/blob/0990f5c620354d8bc087c4706bec091d8e6e5dfd/Adafruit_CCS811.cpp#L135-L142 | ||||
|   uint16_t hum_conv = static_cast<uint16_t>(lroundf(humidity * 512.0f + 0.5f)); | ||||
|   uint16_t temp_conv = static_cast<uint16_t>(lroundf(temperature * 512.0f + 0.5f)); | ||||
|   this->write_bytes(0x05, {(uint8_t)((hum_conv >> 8) & 0xff), (uint8_t)((hum_conv & 0xff)), | ||||
|                            (uint8_t)((temp_conv >> 8) & 0xff), (uint8_t)((temp_conv & 0xff))}); | ||||
|   this->write_bytes(0x05, {(uint8_t) ((hum_conv >> 8) & 0xff), (uint8_t) ((hum_conv & 0xff)), | ||||
|                            (uint8_t) ((temp_conv >> 8) & 0xff), (uint8_t) ((temp_conv & 0xff))}); | ||||
| } | ||||
| void CCS811Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "CCS811"); | ||||
|   | ||||
| @@ -324,6 +324,10 @@ async def setup_climate_core_(var, config): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_CONTROL, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|  | ||||
| async def register_climate(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
| @@ -339,7 +343,7 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema( | ||||
|         cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), | ||||
|         cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), | ||||
|         cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), | ||||
|         cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), | ||||
|         cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"), | ||||
|         cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable( | ||||
|             validate_climate_fan_mode | ||||
|         ), | ||||
| @@ -375,9 +379,6 @@ async def climate_control_to_code(config, action_id, template_arg, args): | ||||
|             config[CONF_TARGET_TEMPERATURE_HIGH], args, float | ||||
|         ) | ||||
|         cg.add(var.set_target_temperature_high(template_)) | ||||
|     if CONF_AWAY in config: | ||||
|         template_ = await cg.templatable(config[CONF_AWAY], args, bool) | ||||
|         cg.add(var.set_away(template_)) | ||||
|     if CONF_FAN_MODE in config: | ||||
|         template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) | ||||
|         cg.add(var.set_fan_mode(template_)) | ||||
|   | ||||
| @@ -264,25 +264,11 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_; | ||||
| const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; } | ||||
| const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } | ||||
| const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } | ||||
| optional<bool> ClimateCall::get_away() const { | ||||
|   if (!this->preset_.has_value()) | ||||
|     return {}; | ||||
|   return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY; | ||||
| } | ||||
| const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; } | ||||
| const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } | ||||
| const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; } | ||||
| const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; } | ||||
| const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; } | ||||
| ClimateCall &ClimateCall::set_away(bool away) { | ||||
|   this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; | ||||
|   return *this; | ||||
| } | ||||
| ClimateCall &ClimateCall::set_away(optional<bool> away) { | ||||
|   if (away.has_value()) | ||||
|     this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; | ||||
|   return *this; | ||||
| } | ||||
| ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) { | ||||
|   this->target_temperature_high_ = target_temperature_high; | ||||
|   return *this; | ||||
| @@ -453,12 +439,7 @@ void Climate::set_visual_temperature_step_override(float target, float current) | ||||
|   this->visual_target_temperature_step_override_ = target; | ||||
|   this->visual_current_temperature_step_override_ = current; | ||||
| } | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
| Climate::Climate(const std::string &name) : EntityBase(name) {} | ||||
| #pragma GCC diagnostic pop | ||||
|  | ||||
| Climate::Climate() : Climate("") {} | ||||
| ClimateCall Climate::make_call() { return ClimateCall(this); } | ||||
|  | ||||
| ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { | ||||
|   | ||||
| @@ -64,10 +64,6 @@ class ClimateCall { | ||||
|    * For climate devices with two point target temperature control | ||||
|    */ | ||||
|   ClimateCall &set_target_temperature_high(optional<float> target_temperature_high); | ||||
|   ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") | ||||
|   ClimateCall &set_away(bool away); | ||||
|   ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") | ||||
|   ClimateCall &set_away(optional<bool> away); | ||||
|   /// Set the fan mode of the climate device. | ||||
|   ClimateCall &set_fan_mode(ClimateFanMode fan_mode); | ||||
|   /// Set the fan mode of the climate device. | ||||
| @@ -97,8 +93,6 @@ class ClimateCall { | ||||
|   const optional<float> &get_target_temperature() const; | ||||
|   const optional<float> &get_target_temperature_low() const; | ||||
|   const optional<float> &get_target_temperature_high() const; | ||||
|   ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20") | ||||
|   optional<bool> get_away() const; | ||||
|   const optional<ClimateFanMode> &get_fan_mode() const; | ||||
|   const optional<ClimateSwingMode> &get_swing_mode() const; | ||||
|   const optional<std::string> &get_custom_fan_mode() const; | ||||
| @@ -166,11 +160,6 @@ struct ClimateDeviceRestoreState { | ||||
|  */ | ||||
| class Climate : public EntityBase { | ||||
|  public: | ||||
|   /// Construct a climate device with empty name (will be set later). | ||||
|   Climate(); | ||||
|   /// Construct a climate device with a name. | ||||
|   Climate(const std::string &name); | ||||
|  | ||||
|   /// The active mode of the climate device. | ||||
|   ClimateMode mode{CLIMATE_MODE_OFF}; | ||||
|   /// The active state of the climate device. | ||||
| @@ -189,14 +178,6 @@ class Climate : public EntityBase { | ||||
|     }; | ||||
|   }; | ||||
|  | ||||
|   /** Whether the climate device is in away mode. | ||||
|    * | ||||
|    * Away allows climate devices to have two different target temperature configs: | ||||
|    * one for normal mode and one for away mode. | ||||
|    */ | ||||
|   ESPDEPRECATED("away is deprecated, use preset instead", "v1.20") | ||||
|   bool away{false}; | ||||
|  | ||||
|   /// The active fan mode of the climate device. | ||||
|   optional<ClimateFanMode> fan_mode; | ||||
|  | ||||
|   | ||||
| @@ -117,15 +117,6 @@ class ClimateTraits { | ||||
|   bool supports_custom_preset(const std::string &custom_preset) const { | ||||
|     return supported_custom_presets_.count(custom_preset); | ||||
|   } | ||||
|   ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20") | ||||
|   void set_supports_away(bool supports) { | ||||
|     if (supports) { | ||||
|       supported_presets_.insert(CLIMATE_PRESET_AWAY); | ||||
|       supported_presets_.insert(CLIMATE_PRESET_HOME); | ||||
|     } | ||||
|   } | ||||
|   ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20") | ||||
|   bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); } | ||||
|  | ||||
|   void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); } | ||||
|   void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } | ||||
|   | ||||
| @@ -28,6 +28,7 @@ cover::CoverTraits CopyCover::get_traits() { | ||||
|   // copy traits manually so it doesn't break when new options are added | ||||
|   // but the control() method hasn't implemented them yet. | ||||
|   traits.set_is_assumed_state(base.get_is_assumed_state()); | ||||
|   traits.set_supports_stop(base.get_supports_stop()); | ||||
|   traits.set_supports_position(base.get_supports_position()); | ||||
|   traits.set_supports_tilt(base.get_supports_tilt()); | ||||
|   traits.set_supports_toggle(base.get_supports_toggle()); | ||||
|   | ||||
| @@ -14,12 +14,15 @@ from .. import copy_ns | ||||
| CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(CopySelect), | ||||
|         cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
| CONFIG_SCHEMA = ( | ||||
|     select.select_schema(CopySelect) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = cv.All( | ||||
|     inherit_property_from(CONF_ICON, CONF_SOURCE_ID), | ||||
|   | ||||
| @@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {} | ||||
| Cover::Cover() : position{COVER_OPEN} {} | ||||
|  | ||||
| CoverCall::CoverCall(Cover *parent) : parent_(parent) {} | ||||
| CoverCall &CoverCall::set_command(const char *command) { | ||||
| @@ -145,7 +145,7 @@ CoverCall &CoverCall::set_stop(bool stop) { | ||||
|   return *this; | ||||
| } | ||||
| bool CoverCall::get_stop() const { return this->stop_; } | ||||
| void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; } | ||||
|  | ||||
| CoverCall Cover::make_call() { return {this}; } | ||||
| void Cover::open() { | ||||
|   auto call = this->make_call(); | ||||
| @@ -204,18 +204,9 @@ optional<CoverRestoreState> Cover::restore_state_() { | ||||
|     return {}; | ||||
|   return recovered; | ||||
| } | ||||
| Cover::Cover() : Cover("") {} | ||||
| std::string Cover::get_device_class() { | ||||
|   if (this->device_class_override_.has_value()) | ||||
|     return *this->device_class_override_; | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
|   return this->device_class(); | ||||
| #pragma GCC diagnostic pop | ||||
| } | ||||
|  | ||||
| bool Cover::is_fully_open() const { return this->position == COVER_OPEN; } | ||||
| bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; } | ||||
| std::string Cover::device_class() { return ""; } | ||||
|  | ||||
| CoverCall CoverRestoreState::to_call(Cover *cover) { | ||||
|   auto call = cover->make_call(); | ||||
|   | ||||
| @@ -108,10 +108,9 @@ const char *cover_operation_to_str(CoverOperation op); | ||||
|  * to control all values of the cover. Also implement get_traits() to return what operations | ||||
|  * the cover supports. | ||||
|  */ | ||||
| class Cover : public EntityBase { | ||||
| class Cover : public EntityBase, public EntityBase_DeviceClass { | ||||
|  public: | ||||
|   explicit Cover(); | ||||
|   explicit Cover(const std::string &name); | ||||
|  | ||||
|   /// The current operation of the cover (idle, opening, closing). | ||||
|   CoverOperation current_operation{COVER_OPERATION_IDLE}; | ||||
| @@ -157,8 +156,6 @@ class Cover : public EntityBase { | ||||
|   void publish_state(bool save = true); | ||||
|  | ||||
|   virtual CoverTraits get_traits() = 0; | ||||
|   void set_device_class(const std::string &device_class); | ||||
|   std::string get_device_class(); | ||||
|  | ||||
|   /// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0 | ||||
|   bool is_fully_open() const; | ||||
| @@ -170,16 +167,9 @@ class Cover : public EntityBase { | ||||
|  | ||||
|   virtual void control(const CoverCall &call) = 0; | ||||
|  | ||||
|   /** Override this to set the default device class. | ||||
|    * | ||||
|    * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) | ||||
|    */ | ||||
|   virtual std::string device_class(); | ||||
|  | ||||
|   optional<CoverRestoreState> restore_state_(); | ||||
|  | ||||
|   CallbackManager<void()> state_callback_{}; | ||||
|   optional<std::string> device_class_override_{}; | ||||
|  | ||||
|   ESPPreferenceObject rtc_; | ||||
| }; | ||||
|   | ||||
| @@ -15,12 +15,15 @@ class CoverTraits { | ||||
|   void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } | ||||
|   bool get_supports_toggle() const { return this->supports_toggle_; } | ||||
|   void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; } | ||||
|   bool get_supports_stop() const { return this->supports_stop_; } | ||||
|   void set_supports_stop(bool supports_stop) { this->supports_stop_ = supports_stop; } | ||||
|  | ||||
|  protected: | ||||
|   bool is_assumed_state_{false}; | ||||
|   bool supports_position_{false}; | ||||
|   bool supports_tilt_{false}; | ||||
|   bool supports_toggle_{false}; | ||||
|   bool supports_stop_{false}; | ||||
| }; | ||||
|  | ||||
| }  // namespace cover | ||||
|   | ||||
| @@ -305,7 +305,7 @@ bool CS5460AComponent::check_status_() { | ||||
|       voltage_sensor_->publish_state(raw_voltage * voltage_multiplier_); | ||||
|  | ||||
|     if (power_sensor_ != nullptr && raw_energy != prev_raw_energy_) { | ||||
|       int32_t raw = (int32_t)(raw_energy << 8) >> 8; /* Sign-extend */ | ||||
|       int32_t raw = (int32_t) (raw_energy << 8) >> 8; /* Sign-extend */ | ||||
|       power_sensor_->publish_state(raw * power_multiplier_); | ||||
|       prev_raw_energy_ = raw_energy; | ||||
|     } | ||||
|   | ||||
| @@ -33,7 +33,10 @@ void CTClampSensor::update() { | ||||
|  | ||||
|     const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_; | ||||
|     const float rms_dc = this->sample_sum_ / this->num_samples_; | ||||
|     const float rms_ac = std::sqrt(rms_ac_dc_squared - rms_dc * rms_dc); | ||||
|     const float rms_ac_squared = rms_ac_dc_squared - rms_dc * rms_dc; | ||||
|     float rms_ac = 0; | ||||
|     if (rms_ac_squared > 0) | ||||
|       rms_ac = std::sqrt(rms_ac_squared); | ||||
|     ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac, | ||||
|              this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_); | ||||
|     this->publish_state(rms_ac); | ||||
|   | ||||
| @@ -12,6 +12,7 @@ using namespace esphome::cover; | ||||
|  | ||||
| CoverTraits CurrentBasedCover::get_traits() { | ||||
|   auto traits = CoverTraits(); | ||||
|   traits.set_supports_stop(true); | ||||
|   traits.set_supports_position(true); | ||||
|   traits.set_supports_toggle(true); | ||||
|   traits.set_is_assumed_state(false); | ||||
|   | ||||
| @@ -61,8 +61,8 @@ void DalyBmsComponent::request_data_(uint8_t data_id) { | ||||
|   request_message[9] = 0x00;     //     | | ||||
|   request_message[10] = 0x00;    //     | | ||||
|   request_message[11] = 0x00;    // Empty Data | ||||
|   request_message[12] = (uint8_t)(request_message[0] + request_message[1] + request_message[2] + | ||||
|                                   request_message[3]);  // Checksum (Lower byte of the other bytes sum) | ||||
|   request_message[12] = (uint8_t) (request_message[0] + request_message[1] + request_message[2] + | ||||
|                                    request_message[3]);  // Checksum (Lower byte of the other bytes sum) | ||||
|  | ||||
|   this->write_array(request_message, sizeof(request_message)); | ||||
|   this->flush(); | ||||
|   | ||||
| @@ -7,9 +7,10 @@ import requests | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| import esphome.final_validate as fv | ||||
| from esphome import git | ||||
| from esphome.components.packages import validate_source_shorthand | ||||
| from esphome.const import CONF_REF, CONF_WIFI | ||||
| from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT | ||||
| from esphome.wizard import wizard_file | ||||
| from esphome.yaml_util import dump | ||||
|  | ||||
| @@ -52,6 +53,17 @@ CONFIG_SCHEMA = cv.All( | ||||
|     validate_full_url, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def _final_validate(config): | ||||
|     full_config = fv.full_config.get()[CONF_ESPHOME] | ||||
|     if CONF_PROJECT not in full_config: | ||||
|         raise cv.Invalid( | ||||
|             "Dashboard import requires the `esphome` -> `project` information to be provided." | ||||
|         ) | ||||
|  | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = _final_validate | ||||
|  | ||||
| WIFI_CONFIG = """ | ||||
|  | ||||
| wifi: | ||||
|   | ||||
| @@ -1,15 +1,11 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| import esphome.final_validate as fv | ||||
| from esphome.components import logger | ||||
| from esphome.const import ( | ||||
|     CONF_BLOCK, | ||||
|     CONF_DEVICE, | ||||
|     CONF_FRAGMENTATION, | ||||
|     CONF_FREE, | ||||
|     CONF_ID, | ||||
|     CONF_LEVEL, | ||||
|     CONF_LOGGER, | ||||
|     CONF_LOOP_TIME, | ||||
| ) | ||||
|  | ||||
| @@ -21,38 +17,29 @@ debug_ns = cg.esphome_ns.namespace("debug") | ||||
| DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(DebugComponent), | ||||
|         cv.Optional(CONF_DEVICE): cv.invalid( | ||||
|             "The 'device' option has been moved to the 'debug' text_sensor component" | ||||
|         ), | ||||
|         cv.Optional(CONF_FREE): cv.invalid( | ||||
|             "The 'free' option has been moved to the 'debug' sensor component" | ||||
|         ), | ||||
|         cv.Optional(CONF_BLOCK): cv.invalid( | ||||
|             "The 'block' option has been moved to the 'debug' sensor component" | ||||
|         ), | ||||
|         cv.Optional(CONF_FRAGMENTATION): cv.invalid( | ||||
|             "The 'fragmentation' option has been moved to the 'debug' sensor component" | ||||
|         ), | ||||
|         cv.Optional(CONF_LOOP_TIME): cv.invalid( | ||||
|             "The 'loop_time' option has been moved to the 'debug' sensor component" | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.polling_component_schema("60s")) | ||||
|  | ||||
|  | ||||
| def _final_validate(_): | ||||
|     logger_conf = fv.full_config.get()[CONF_LOGGER] | ||||
|     severity = logger.LOG_LEVEL_SEVERITY.index(logger_conf[CONF_LEVEL]) | ||||
|     if severity < logger.LOG_LEVEL_SEVERITY.index("DEBUG"): | ||||
|         raise cv.Invalid( | ||||
|             "The debug component requires the logger to be at least at DEBUG level" | ||||
|         ) | ||||
|  | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = _final_validate | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(DebugComponent), | ||||
|             cv.Optional(CONF_DEVICE): cv.invalid( | ||||
|                 "The 'device' option has been moved to the 'debug' text_sensor component" | ||||
|             ), | ||||
|             cv.Optional(CONF_FREE): cv.invalid( | ||||
|                 "The 'free' option has been moved to the 'debug' sensor component" | ||||
|             ), | ||||
|             cv.Optional(CONF_BLOCK): cv.invalid( | ||||
|                 "The 'block' option has been moved to the 'debug' sensor component" | ||||
|             ), | ||||
|             cv.Optional(CONF_FRAGMENTATION): cv.invalid( | ||||
|                 "The 'fragmentation' option has been moved to the 'debug' sensor component" | ||||
|             ), | ||||
|             cv.Optional(CONF_LOOP_TIME): cv.invalid( | ||||
|                 "The 'loop_time' option has been moved to the 'debug' sensor component" | ||||
|             ), | ||||
|         } | ||||
|     ).extend(cv.polling_component_schema("60s")), | ||||
|     cv.only_on(["esp32", "esp8266"]), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|   | ||||
| @@ -37,6 +37,10 @@ static uint32_t get_free_heap() { | ||||
| } | ||||
|  | ||||
| void DebugComponent::dump_config() { | ||||
| #ifndef ESPHOME_LOG_HAS_DEBUG | ||||
|   return;  // Can't log below if debug logging is disabled | ||||
| #endif | ||||
|  | ||||
|   std::string device_info; | ||||
|   std::string reset_reason; | ||||
|   device_info.reserve(256); | ||||
|   | ||||
| @@ -72,6 +72,7 @@ class DemoCover : public cover::Cover, public Component { | ||||
|         traits.set_supports_tilt(true); | ||||
|         break; | ||||
|       case DemoCoverType::TYPE_4: | ||||
|         traits.set_supports_stop(true); | ||||
|         traits.set_is_assumed_state(true); | ||||
|         traits.set_supports_tilt(true); | ||||
|         break; | ||||
|   | ||||
| @@ -40,6 +40,7 @@ DEVICE = { | ||||
|  | ||||
| NextAction = dfplayer_ns.class_("NextAction", automation.Action) | ||||
| PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action) | ||||
| PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action) | ||||
| PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action) | ||||
| PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action) | ||||
| SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action) | ||||
| @@ -113,6 +114,25 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args): | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "dfplayer.play_mp3", | ||||
|     PlayMp3Action, | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
|             cv.Required(CONF_FILE): cv.templatable(cv.int_), | ||||
|         }, | ||||
|         key=CONF_FILE, | ||||
|     ), | ||||
| ) | ||||
| async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     await cg.register_parented(var, config[CONF_ID]) | ||||
|     template_ = await cg.templatable(config[CONF_FILE], args, float) | ||||
|     cg.add(var.set_file(template_)) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "dfplayer.play", | ||||
|     PlayFileAction, | ||||
|   | ||||
| @@ -7,10 +7,10 @@ namespace dfplayer { | ||||
| static const char *const TAG = "dfplayer"; | ||||
|  | ||||
| void DFPlayer::play_folder(uint16_t folder, uint16_t file) { | ||||
|   if (folder < 100 && file < 256) { | ||||
|   if (folder <= 10 && file <= 1000) { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file); | ||||
|   } else if (folder <= 10 && file <= 1000) { | ||||
|   } else if (folder < 100 && file < 256) { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file); | ||||
|   } else { | ||||
| @@ -19,7 +19,7 @@ void DFPlayer::play_folder(uint16_t folder, uint16_t file) { | ||||
| } | ||||
|  | ||||
| void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) { | ||||
|   uint8_t buffer[10]{0x7e, 0xff, 0x06, cmd, 0x01, (uint8_t)(argument >> 8), (uint8_t) argument, 0x00, 0x00, 0xef}; | ||||
|   uint8_t buffer[10]{0x7e, 0xff, 0x06, cmd, 0x01, (uint8_t) (argument >> 8), (uint8_t) argument, 0x00, 0x00, 0xef}; | ||||
|   uint16_t checksum = 0; | ||||
|   for (uint8_t i = 1; i < 7; i++) | ||||
|     checksum += buffer[i]; | ||||
|   | ||||
| @@ -35,6 +35,10 @@ class DFPlayer : public uart::UARTDevice, public Component { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x02); | ||||
|   } | ||||
|   void play_mp3(uint16_t file) { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x12, file); | ||||
|   } | ||||
|   void play_file(uint16_t file) { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x03, file); | ||||
| @@ -113,6 +117,16 @@ class DFPlayer : public uart::UARTDevice, public Component { | ||||
| DFPLAYER_SIMPLE_ACTION(NextAction, next) | ||||
| DFPLAYER_SIMPLE_ACTION(PreviousAction, previous) | ||||
|  | ||||
| template<typename... Ts> class PlayMp3Action : public Action<Ts...>, public Parented<DFPlayer> { | ||||
|  public: | ||||
|   TEMPLATABLE_VALUE(uint16_t, file) | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     auto file = this->file_.value(x...); | ||||
|     this->parent_->play_mp3(file); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> { | ||||
|  public: | ||||
|   TEMPLATABLE_VALUE(uint16_t, file) | ||||
|   | ||||
| @@ -32,9 +32,11 @@ void Rect::extend(Rect rect) { | ||||
|     this->h = rect.h; | ||||
|   } else { | ||||
|     if (this->x > rect.x) { | ||||
|       this->w = this->w + (this->x - rect.x); | ||||
|       this->x = rect.x; | ||||
|     } | ||||
|     if (this->y > rect.y) { | ||||
|       this->h = this->h + (this->y - rect.y); | ||||
|       this->y = rect.y; | ||||
|     } | ||||
|     if (this->x2() < rect.x2()) { | ||||
| @@ -49,29 +51,35 @@ void Rect::shrink(Rect rect) { | ||||
|   if (!this->inside(rect)) { | ||||
|     (*this) = Rect(); | ||||
|   } else { | ||||
|     if (this->x < rect.x) { | ||||
|       this->x = rect.x; | ||||
|     } | ||||
|     if (this->y < rect.y) { | ||||
|       this->y = rect.y; | ||||
|     } | ||||
|     if (this->x2() > rect.x2()) { | ||||
|       this->w = rect.x2() - this->x; | ||||
|     } | ||||
|     if (this->x < rect.x) { | ||||
|       this->w = this->w + (this->x - rect.x); | ||||
|       this->x = rect.x; | ||||
|     } | ||||
|     if (this->y2() > rect.y2()) { | ||||
|       this->h = rect.y2() - this->y; | ||||
|     } | ||||
|     if (this->y < rect.y) { | ||||
|       this->h = this->h + (this->y - rect.y); | ||||
|       this->y = rect.y; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool Rect::inside(int16_t x, int16_t y, bool absolute) {  // NOLINT | ||||
| bool Rect::equal(Rect rect) { | ||||
|   return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h); | ||||
| } | ||||
|  | ||||
| bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) {  // NOLINT | ||||
|   if (!this->is_set()) { | ||||
|     return true; | ||||
|   } | ||||
|   if (absolute) { | ||||
|     return ((x >= 0) && (x <= this->w) && (y >= 0) && (y <= this->h)); | ||||
|     return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2())); | ||||
|   } else { | ||||
|     return ((x >= this->x) && (x <= this->x2()) && (y >= this->y) && (y <= this->y2())); | ||||
|     return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -80,15 +88,16 @@ bool Rect::inside(Rect rect, bool absolute) { | ||||
|     return true; | ||||
|   } | ||||
|   if (absolute) { | ||||
|     return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0)); | ||||
|   } else { | ||||
|     return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y)); | ||||
|   } else { | ||||
|     return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Rect::info(const std::string &prefix) { | ||||
|   if (this->is_set()) { | ||||
|     ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d]", prefix.c_str(), this->x, this->y, this->w, this->h); | ||||
|     ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(), | ||||
|              this->y2()); | ||||
|   } else | ||||
|     ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str()); | ||||
| } | ||||
| @@ -273,10 +282,14 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align | ||||
|     int scan_x1, scan_y1, scan_width, scan_height; | ||||
|     glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); | ||||
|  | ||||
|     for (int glyph_x = scan_x1; glyph_x < scan_x1 + scan_width; glyph_x++) { | ||||
|       for (int glyph_y = scan_y1; glyph_y < scan_y1 + scan_height; glyph_y++) { | ||||
|         if (glyph.get_pixel(glyph_x, glyph_y)) { | ||||
|           this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); | ||||
|     { | ||||
|       const int glyph_x_max = scan_x1 + scan_width; | ||||
|       const int glyph_y_max = scan_y1 + scan_height; | ||||
|       for (int glyph_x = scan_x1; glyph_x < glyph_x_max; glyph_x++) { | ||||
|         for (int glyph_y = scan_y1; glyph_y < glyph_y_max; glyph_y++) { | ||||
|           if (glyph.get_pixel(glyph_x, glyph_y)) { | ||||
|             this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @@ -655,7 +668,7 @@ bool Animation::get_pixel(int x, int y) const { | ||||
|     return false; | ||||
|   const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; | ||||
|   const uint32_t frame_index = this->height_ * width_8 * this->current_frame_; | ||||
|   if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) | ||||
|   if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_)) | ||||
|     return false; | ||||
|   const uint32_t pos = x + y * width_8 + frame_index; | ||||
|   return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); | ||||
| @@ -664,7 +677,7 @@ Color Animation::get_color_pixel(int x, int y) const { | ||||
|   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) | ||||
|     return Color::BLACK; | ||||
|   const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; | ||||
|   if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) | ||||
|   if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_)) | ||||
|     return Color::BLACK; | ||||
|   const uint32_t pos = (x + y * this->width_ + frame_index) * 3; | ||||
|   const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | | ||||
| @@ -676,7 +689,7 @@ Color Animation::get_rgb565_pixel(int x, int y) const { | ||||
|   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) | ||||
|     return Color::BLACK; | ||||
|   const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; | ||||
|   if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) | ||||
|   if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_)) | ||||
|     return Color::BLACK; | ||||
|   const uint32_t pos = (x + y * this->width_ + frame_index) * 2; | ||||
|   uint16_t rgb565 = | ||||
| @@ -690,7 +703,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const { | ||||
|   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) | ||||
|     return Color::BLACK; | ||||
|   const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; | ||||
|   if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) | ||||
|   if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_)) | ||||
|     return Color::BLACK; | ||||
|   const uint32_t pos = (x + y * this->width_ + frame_index); | ||||
|   const uint8_t gray = progmem_read_byte(this->data_start_ + pos); | ||||
|   | ||||
| @@ -120,8 +120,9 @@ class Rect { | ||||
|   void extend(Rect rect); | ||||
|   void shrink(Rect rect); | ||||
|  | ||||
|   bool inside(Rect rect, bool absolute = false); | ||||
|   bool inside(int16_t x, int16_t y, bool absolute = false); | ||||
|   bool inside(Rect rect, bool absolute = true); | ||||
|   bool inside(int16_t test_x, int16_t test_y, bool absolute = true); | ||||
|   bool equal(Rect rect); | ||||
|   void info(const std::string &prefix = "rect info:"); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ using namespace esphome::cover; | ||||
|  | ||||
| CoverTraits EndstopCover::get_traits() { | ||||
|   auto traits = CoverTraits(); | ||||
|   traits.set_supports_stop(true); | ||||
|   traits.set_supports_position(true); | ||||
|   traits.set_supports_toggle(true); | ||||
|   traits.set_is_assumed_state(false); | ||||
|   | ||||
| @@ -168,7 +168,7 @@ void ENS210Component::update() { | ||||
|       return; | ||||
|     } | ||||
|     // Pack bytes for humidity | ||||
|     h_val_data = (uint32_t)((uint32_t) data[5] << 16 | (uint32_t) data[4] << 8 | (uint32_t) data[3]); | ||||
|     h_val_data = (uint32_t) ((uint32_t) data[5] << 16 | (uint32_t) data[4] << 8 | (uint32_t) data[3]); | ||||
|     // Extract humidity data and update the status | ||||
|     extract_measurement_(h_val_data, &humidity_data, &humidity_status); | ||||
|  | ||||
| @@ -183,7 +183,7 @@ void ENS210Component::update() { | ||||
|       return; | ||||
|     } | ||||
|     // Pack bytes for temperature | ||||
|     t_val_data = (uint32_t)((uint32_t) data[2] << 16 | (uint32_t) data[1] << 8 | (uint32_t) data[0]); | ||||
|     t_val_data = (uint32_t) ((uint32_t) data[2] << 16 | (uint32_t) data[1] << 8 | (uint32_t) data[0]); | ||||
|     // Extract temperature data and update the status | ||||
|     extract_measurement_(t_val_data, &temperature_data, &temperature_status); | ||||
|  | ||||
|   | ||||
| @@ -163,7 +163,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5) | ||||
| # The platformio/espressif32 version to use for arduino frameworks | ||||
| #  - https://github.com/platformio/platform-espressif32/releases | ||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 | ||||
| ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0) | ||||
| ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 0) | ||||
|  | ||||
| # The default/recommended esp-idf framework version | ||||
| #  - https://github.com/espressif/esp-idf/releases | ||||
| @@ -252,7 +252,7 @@ def _parse_platform_version(value): | ||||
|     try: | ||||
|         # if platform version is a valid version constraint, prefix the default package | ||||
|         cv.platformio_version_constraint(value) | ||||
|         return f"platformio/espressif32 @ {value}" | ||||
|         return f"platformio/espressif32@{value}" | ||||
|     except cv.Invalid: | ||||
|         return value | ||||
|  | ||||
| @@ -367,12 +367,12 @@ async def to_code(config): | ||||
|         cg.add_build_flag("-Wno-nonnull-compare") | ||||
|         cg.add_platformio_option( | ||||
|             "platform_packages", | ||||
|             [f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"], | ||||
|             [f"platformio/framework-espidf@{conf[CONF_SOURCE]}"], | ||||
|         ) | ||||
|         # platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years | ||||
|         # This is espressif's own published version which is more up to date. | ||||
|         cg.add_platformio_option( | ||||
|             "platform_packages", ["espressif/toolchain-esp32ulp @ 2.35.0-20220830"] | ||||
|             "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] | ||||
|         ) | ||||
|         add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) | ||||
|         add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) | ||||
| @@ -433,7 +433,7 @@ async def to_code(config): | ||||
|         cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") | ||||
|         cg.add_platformio_option( | ||||
|             "platform_packages", | ||||
|             [f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"], | ||||
|             [f"platformio/framework-arduinoespressif32@{conf[CONF_SOURCE]}"], | ||||
|         ) | ||||
|  | ||||
|         cg.add_platformio_option("board_build.partitions", "partitions.csv") | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
| #include <freertos/task.h> | ||||
| #include <esp_idf_version.h> | ||||
| #include <esp_task_wdt.h> | ||||
| #include <esp_timer.h> | ||||
| #include <soc/rtc.h> | ||||
|  | ||||
| #if ESP_IDF_VERSION_MAJOR >= 4 | ||||
| @@ -23,7 +24,7 @@ void loop(); | ||||
| namespace esphome { | ||||
|  | ||||
| void IRAM_ATTR HOT yield() { vPortYield(); } | ||||
| uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); } | ||||
| uint32_t IRAM_ATTR HOT millis() { return (uint32_t) (esp_timer_get_time() / 1000ULL); } | ||||
| void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } | ||||
| uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); } | ||||
| void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| #include "gpio.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include <cinttypes> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace esp32 { | ||||
| @@ -74,7 +75,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi | ||||
|  | ||||
| std::string ESP32InternalGPIOPin::dump_summary() const { | ||||
|   char buffer[32]; | ||||
|   snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast<uint32_t>(pin_)); | ||||
|   snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(pin_)); | ||||
|   return buffer; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "esphome/core/log.h" | ||||
| #include <nvs_flash.h> | ||||
| #include <cstring> | ||||
| #include <cinttypes> | ||||
| #include <vector> | ||||
| #include <string> | ||||
|  | ||||
| @@ -101,7 +102,7 @@ class ESP32Preferences : public ESPPreferences { | ||||
|     pref->nvs_handle = nvs_handle; | ||||
|  | ||||
|     uint32_t keyval = type; | ||||
|     pref->key = str_sprintf("%u", keyval); | ||||
|     pref->key = str_sprintf("%" PRIu32, keyval); | ||||
|  | ||||
|     return ESPPreferenceObject(pref); | ||||
|   } | ||||
|   | ||||
| @@ -9,8 +9,9 @@ CODEOWNERS = ["@jesserockz"] | ||||
| CONFLICTS_WITH = ["esp32_ble_beacon"] | ||||
|  | ||||
| CONF_BLE_ID = "ble_id" | ||||
| CONF_IO_CAPABILITY = "io_capability" | ||||
|  | ||||
| NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2] | ||||
| NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] | ||||
|  | ||||
| esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") | ||||
| ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) | ||||
| @@ -19,17 +20,28 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler") | ||||
| GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler") | ||||
| GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler") | ||||
|  | ||||
| IoCapability = esp32_ble_ns.enum("IoCapability") | ||||
| IO_CAPABILITY = { | ||||
|     "none": IoCapability.IO_CAP_NONE, | ||||
|     "keyboard_only": IoCapability.IO_CAP_IN, | ||||
|     "keyboard_display": IoCapability.IO_CAP_KBDISP, | ||||
|     "display_only": IoCapability.IO_CAP_OUT, | ||||
|     "display_yes_no": IoCapability.IO_CAP_IO, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(ESP32BLE), | ||||
|         cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( | ||||
|             IO_CAPABILITY, lower=True | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def validate_variant(_): | ||||
|     variant = get_esp32_variant() | ||||
|     if variant in NO_BLUTOOTH_VARIANTS: | ||||
|     if variant in NO_BLUETOOTH_VARIANTS: | ||||
|         raise cv.Invalid(f"{variant} does not support Bluetooth") | ||||
|  | ||||
|  | ||||
| @@ -39,6 +51,7 @@ FINAL_VALIDATE_SCHEMA = validate_variant | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) | ||||
|  | ||||
|     if CORE.using_esp_idf: | ||||
|         add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|  | ||||
| #include <esp_bt.h> | ||||
| #include <esp_bt_main.h> | ||||
| #include <esp_bt_device.h> | ||||
| #include <esp_gap_ble_api.h> | ||||
| #include <freertos/FreeRTOS.h> | ||||
| #include <freertos/FreeRTOSConfig.h> | ||||
| @@ -133,8 +134,7 @@ bool ESP32BLE::ble_setup_() { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; | ||||
|   err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); | ||||
|   err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t)); | ||||
|   if (err != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err); | ||||
|     return false; | ||||
| @@ -211,7 +211,38 @@ void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if | ||||
|  | ||||
| float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } | ||||
|  | ||||
| void ESP32BLE::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE:"); } | ||||
| void ESP32BLE::dump_config() { | ||||
|   const uint8_t *mac_address = esp_bt_dev_get_address(); | ||||
|   if (mac_address) { | ||||
|     const char *io_capability_s; | ||||
|     switch (this->io_cap_) { | ||||
|       case ESP_IO_CAP_OUT: | ||||
|         io_capability_s = "display_only"; | ||||
|         break; | ||||
|       case ESP_IO_CAP_IO: | ||||
|         io_capability_s = "display_yes_no"; | ||||
|         break; | ||||
|       case ESP_IO_CAP_IN: | ||||
|         io_capability_s = "keyboard_only"; | ||||
|         break; | ||||
|       case ESP_IO_CAP_NONE: | ||||
|         io_capability_s = "none"; | ||||
|         break; | ||||
|       case ESP_IO_CAP_KBDISP: | ||||
|         io_capability_s = "keyboard_display"; | ||||
|         break; | ||||
|       default: | ||||
|         io_capability_s = "invalid"; | ||||
|         break; | ||||
|     } | ||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE:"); | ||||
|     ESP_LOGCONFIG(TAG, "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2], | ||||
|                   mac_address[3], mac_address[4], mac_address[5]); | ||||
|     ESP_LOGCONFIG(TAG, "  IO Capability: %s", io_capability_s); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| ESP32BLE *global_ble = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|  | ||||
|   | ||||
| @@ -25,6 +25,14 @@ typedef struct { | ||||
|   uint16_t mtu; | ||||
| } conn_status_t; | ||||
|  | ||||
| enum IoCapability { | ||||
|   IO_CAP_OUT = ESP_IO_CAP_OUT, | ||||
|   IO_CAP_IO = ESP_IO_CAP_IO, | ||||
|   IO_CAP_IN = ESP_IO_CAP_IN, | ||||
|   IO_CAP_NONE = ESP_IO_CAP_NONE, | ||||
|   IO_CAP_KBDISP = ESP_IO_CAP_KBDISP, | ||||
| }; | ||||
|  | ||||
| class GAPEventHandler { | ||||
|  public: | ||||
|   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; | ||||
| @@ -44,6 +52,8 @@ class GATTsEventHandler { | ||||
|  | ||||
| class ESP32BLE : public Component { | ||||
|  public: | ||||
|   void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } | ||||
|  | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
| @@ -72,6 +82,7 @@ class ESP32BLE : public Component { | ||||
|  | ||||
|   Queue<BLEEvent> ble_events_; | ||||
|   BLEAdvertising *advertising_; | ||||
|   esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; | ||||
| }; | ||||
|  | ||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|   | ||||
| @@ -316,18 +316,18 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) { | ||||
|     case 0xD:  // int12. | ||||
|     case 0xE:  // int16. | ||||
|       if (length > 2) { | ||||
|         return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]); | ||||
|         return (float) ((int16_t) (value[1] << 8) + (int16_t) value[2]); | ||||
|       } | ||||
|       // fall through | ||||
|     case 0xF:  // int24. | ||||
|       if (length > 3) { | ||||
|         return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3])); | ||||
|         return (float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3])); | ||||
|       } | ||||
|       // fall through | ||||
|     case 0x10:  // int32. | ||||
|       if (length > 4) { | ||||
|         return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) + | ||||
|                         (int32_t)(value[4])); | ||||
|         return (float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) + | ||||
|                         (int32_t) (value[4])); | ||||
|       } | ||||
|   } | ||||
|   ESP_LOGW(TAG, "[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->connection_index_, | ||||
|   | ||||
| @@ -45,10 +45,11 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|       memset(this->remote_bda_, 0, sizeof(this->remote_bda_)); | ||||
|       this->address_str_ = ""; | ||||
|     } else { | ||||
|       this->address_str_ = str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t)(this->address_ >> 40) & 0xff, | ||||
|                                         (uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff, | ||||
|                                         (uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff, | ||||
|                                         (uint8_t)(this->address_ >> 0) & 0xff); | ||||
|       this->address_str_ = | ||||
|           str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t) (this->address_ >> 40) & 0xff, | ||||
|                        (uint8_t) (this->address_ >> 32) & 0xff, (uint8_t) (this->address_ >> 24) & 0xff, | ||||
|                        (uint8_t) (this->address_ >> 16) & 0xff, (uint8_t) (this->address_ >> 8) & 0xff, | ||||
|                        (uint8_t) (this->address_ >> 0) & 0xff); | ||||
|     } | ||||
|   } | ||||
|   std::string address_str() const { return this->address_str_; } | ||||
|   | ||||
| @@ -148,44 +148,44 @@ bool BLECharacteristic::is_failed() { | ||||
|  | ||||
| void BLECharacteristic::set_broadcast_property(bool value) { | ||||
|   if (value) { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); | ||||
|   } else { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); | ||||
|   } | ||||
| } | ||||
| void BLECharacteristic::set_indicate_property(bool value) { | ||||
|   if (value) { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); | ||||
|   } else { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); | ||||
|   } | ||||
| } | ||||
| void BLECharacteristic::set_notify_property(bool value) { | ||||
|   if (value) { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); | ||||
|   } else { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); | ||||
|   } | ||||
| } | ||||
| void BLECharacteristic::set_read_property(bool value) { | ||||
|   if (value) { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); | ||||
|   } else { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); | ||||
|   } | ||||
| } | ||||
| void BLECharacteristic::set_write_property(bool value) { | ||||
|   if (value) { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); | ||||
|   } else { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); | ||||
|   } | ||||
| } | ||||
| void BLECharacteristic::set_write_no_response_property(bool value) { | ||||
|   if (value) { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); | ||||
|   } else { | ||||
|     this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); | ||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -55,6 +55,22 @@ FRAME_SIZES = { | ||||
|     "SXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1280X1024, | ||||
|     "1600X1200": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, | ||||
|     "UXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, | ||||
|     "1920X1080": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1920X1080, | ||||
|     "FHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1920X1080, | ||||
|     "720X1280": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_720X1280, | ||||
|     "PHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_720X1280, | ||||
|     "864X1536": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_864X1536, | ||||
|     "P3MP": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_864X1536, | ||||
|     "2048X1536": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2048X1536, | ||||
|     "QXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2048X1536, | ||||
|     "2560X1440": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1440, | ||||
|     "QHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1440, | ||||
|     "2560X1600": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1600, | ||||
|     "WQXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1600, | ||||
|     "1080X1920": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1080X1920, | ||||
|     "PFHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1080X1920, | ||||
|     "2560X1920": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1920, | ||||
|     "QSXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1920, | ||||
| } | ||||
| ESP32GainControlMode = esp32_camera_ns.enum("ESP32GainControlMode") | ||||
| ENUM_GAIN_CONTROL_MODE = { | ||||
| @@ -140,7 +156,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | ||||
|             { | ||||
|                 cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, | ||||
|                 cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( | ||||
|                     cv.frequency, cv.one_of(20e6, 10e6) | ||||
|                     cv.frequency, cv.Range(min=8e6, max=20e6) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|   | ||||
| @@ -91,6 +91,30 @@ void ESP32Camera::dump_config() { | ||||
|     case FRAMESIZE_UXGA: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 1600x1200 (UXGA)"); | ||||
|       break; | ||||
|     case FRAMESIZE_FHD: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 1920x1080 (FHD)"); | ||||
|       break; | ||||
|     case FRAMESIZE_P_HD: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 720x1280 (P_HD)"); | ||||
|       break; | ||||
|     case FRAMESIZE_P_3MP: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 864x1536 (P_3MP)"); | ||||
|       break; | ||||
|     case FRAMESIZE_QXGA: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 2048x1536 (QXGA)"); | ||||
|       break; | ||||
|     case FRAMESIZE_QHD: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 2560x1440 (QHD)"); | ||||
|       break; | ||||
|     case FRAMESIZE_WQXGA: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 2560x1600 (WQXGA)"); | ||||
|       break; | ||||
|     case FRAMESIZE_P_FHD: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 1080x1920 (P_FHD)"); | ||||
|       break; | ||||
|     case FRAMESIZE_QSXGA: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 2560x1920 (QSXGA)"); | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| @@ -178,7 +202,7 @@ void ESP32Camera::loop() { | ||||
| float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| /* ---------------- constructors ---------------- */ | ||||
| ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { | ||||
| ESP32Camera::ESP32Camera() { | ||||
|   this->config_.pin_pwdn = -1; | ||||
|   this->config_.pin_reset = -1; | ||||
|   this->config_.pin_xclk = -1; | ||||
| @@ -191,7 +215,6 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { | ||||
|  | ||||
|   global_esp32_camera = this; | ||||
| } | ||||
| ESP32Camera::ESP32Camera() : ESP32Camera("") {} | ||||
|  | ||||
| /* ---------------- setters ---------------- */ | ||||
| /* set pin assignment */ | ||||
| @@ -257,6 +280,30 @@ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) { | ||||
|     case ESP32_CAMERA_SIZE_1600X1200: | ||||
|       this->config_.frame_size = FRAMESIZE_UXGA; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_1920X1080: | ||||
|       this->config_.frame_size = FRAMESIZE_FHD; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_720X1280: | ||||
|       this->config_.frame_size = FRAMESIZE_P_HD; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_864X1536: | ||||
|       this->config_.frame_size = FRAMESIZE_P_3MP; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_2048X1536: | ||||
|       this->config_.frame_size = FRAMESIZE_QXGA; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_2560X1440: | ||||
|       this->config_.frame_size = FRAMESIZE_QHD; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_2560X1600: | ||||
|       this->config_.frame_size = FRAMESIZE_WQXGA; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_1080X1920: | ||||
|       this->config_.frame_size = FRAMESIZE_P_FHD; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_2560X1920: | ||||
|       this->config_.frame_size = FRAMESIZE_QSXGA; | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; } | ||||
|   | ||||
| @@ -29,6 +29,14 @@ enum ESP32CameraFrameSize { | ||||
|   ESP32_CAMERA_SIZE_1024X768,   // XGA | ||||
|   ESP32_CAMERA_SIZE_1280X1024,  // SXGA | ||||
|   ESP32_CAMERA_SIZE_1600X1200,  // UXGA | ||||
|   ESP32_CAMERA_SIZE_1920X1080,  // FHD | ||||
|   ESP32_CAMERA_SIZE_720X1280,   // PHD | ||||
|   ESP32_CAMERA_SIZE_864X1536,   // P3MP | ||||
|   ESP32_CAMERA_SIZE_2048X1536,  // QXGA | ||||
|   ESP32_CAMERA_SIZE_2560X1440,  // QHD | ||||
|   ESP32_CAMERA_SIZE_2560X1600,  // WQXGA | ||||
|   ESP32_CAMERA_SIZE_1080X1920,  // PFHD | ||||
|   ESP32_CAMERA_SIZE_2560X1920,  // QSXGA | ||||
| }; | ||||
|  | ||||
| enum ESP32AgcGainCeiling { | ||||
| @@ -95,7 +103,6 @@ class CameraImageReader { | ||||
| /* ---------------- ESP32Camera class ---------------- */ | ||||
| class ESP32Camera : public Component, public EntityBase { | ||||
|  public: | ||||
|   ESP32Camera(const std::string &name); | ||||
|   ESP32Camera(); | ||||
|  | ||||
|   /* setters */ | ||||
|   | ||||
| @@ -22,20 +22,12 @@ ESP32ImprovComponent = esp32_improv_ns.class_( | ||||
| ) | ||||
|  | ||||
|  | ||||
| def validate_none_(value): | ||||
|     if value in ("none", "None"): | ||||
|         return None | ||||
|     if cv.boolean(value) is False: | ||||
|         return None | ||||
|     raise cv.Invalid("Must be none") | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(ESP32ImprovComponent), | ||||
|         cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer), | ||||
|         cv.Required(CONF_AUTHORIZER): cv.Any( | ||||
|             validate_none_, cv.use_id(binary_sensor.BinarySensor) | ||||
|             cv.none, cv.use_id(binary_sensor.BinarySensor) | ||||
|         ), | ||||
|         cv.Optional(CONF_STATUS_INDICATOR): cv.use_id(output.BinaryOutput), | ||||
|         cv.Optional( | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/esp32_rmt_led_strip/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/esp32_rmt_led_strip/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										207
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| #include "led_strip.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #include <esp_attr.h> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace esp32_rmt_led_strip { | ||||
|  | ||||
| static const char *const TAG = "esp32_rmt_led_strip"; | ||||
|  | ||||
| static const uint8_t RMT_CLK_DIV = 2; | ||||
|  | ||||
| void ESP32RMTLEDStripLightOutput::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip..."); | ||||
|  | ||||
|   size_t buffer_size = this->get_buffer_size_(); | ||||
|  | ||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||
|   this->buf_ = allocator.allocate(buffer_size); | ||||
|   if (this->buf_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Cannot allocate LED buffer!"); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->effect_data_ = allocator.allocate(this->num_leds_); | ||||
|   if (this->effect_data_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Cannot allocate effect data!"); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE); | ||||
|   this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8);  // 8 bits per byte, 1 rmt_item32_t per bit | ||||
|  | ||||
|   rmt_config_t config; | ||||
|   memset(&config, 0, sizeof(config)); | ||||
|   config.channel = this->channel_; | ||||
|   config.rmt_mode = RMT_MODE_TX; | ||||
|   config.gpio_num = gpio_num_t(this->pin_); | ||||
|   config.mem_block_num = 1; | ||||
|   config.clk_div = RMT_CLK_DIV; | ||||
|   config.tx_config.loop_en = false; | ||||
|   config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; | ||||
|   config.tx_config.carrier_en = false; | ||||
|   config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; | ||||
|   config.tx_config.idle_output_en = true; | ||||
|  | ||||
|   if (rmt_config(&config) != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "Cannot initialize RMT!"); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   if (rmt_driver_install(config.channel, 0, 0) != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "Cannot install RMT driver!"); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, | ||||
|                                                  uint32_t bit1_low) { | ||||
|   float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f; | ||||
|  | ||||
|   // 0-bit | ||||
|   this->bit0_.duration0 = (uint32_t) (ratio * bit0_high); | ||||
|   this->bit0_.level0 = 1; | ||||
|   this->bit0_.duration1 = (uint32_t) (ratio * bit0_low); | ||||
|   this->bit0_.level1 = 0; | ||||
|   // 1-bit | ||||
|   this->bit1_.duration0 = (uint32_t) (ratio * bit1_high); | ||||
|   this->bit1_.level0 = 1; | ||||
|   this->bit1_.duration1 = (uint32_t) (ratio * bit1_low); | ||||
|   this->bit1_.level1 = 0; | ||||
| } | ||||
|  | ||||
| void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | ||||
|   // protect from refreshing too often | ||||
|   uint32_t now = micros(); | ||||
|   if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) { | ||||
|     // try again next loop iteration, so that this change won't get lost | ||||
|     this->schedule_show(); | ||||
|     return; | ||||
|   } | ||||
|   this->last_refresh_ = now; | ||||
|   this->mark_shown_(); | ||||
|  | ||||
|   ESP_LOGVV(TAG, "Writing RGB values to bus..."); | ||||
|  | ||||
|   if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "RMT TX timeout"); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|   delayMicroseconds(50); | ||||
|  | ||||
|   size_t buffer_size = this->get_buffer_size_(); | ||||
|  | ||||
|   size_t size = 0; | ||||
|   size_t len = 0; | ||||
|   uint8_t *psrc = this->buf_; | ||||
|   rmt_item32_t *pdest = this->rmt_buf_; | ||||
|   while (size < buffer_size) { | ||||
|     uint8_t b = *psrc; | ||||
|     for (int i = 0; i < 8; i++) { | ||||
|       pdest->val = b & (1 << (7 - i)) ? this->bit1_.val : this->bit0_.val; | ||||
|       pdest++; | ||||
|       len++; | ||||
|     } | ||||
|     size++; | ||||
|     psrc++; | ||||
|   } | ||||
|  | ||||
|   if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "RMT TX error"); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|   this->status_clear_warning(); | ||||
| } | ||||
|  | ||||
| light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index) const { | ||||
|   int32_t r = 0, g = 0, b = 0; | ||||
|   switch (this->rgb_order_) { | ||||
|     case ORDER_RGB: | ||||
|       r = 0; | ||||
|       g = 1; | ||||
|       b = 2; | ||||
|       break; | ||||
|     case ORDER_RBG: | ||||
|       r = 0; | ||||
|       g = 2; | ||||
|       b = 1; | ||||
|       break; | ||||
|     case ORDER_GRB: | ||||
|       r = 1; | ||||
|       g = 0; | ||||
|       b = 2; | ||||
|       break; | ||||
|     case ORDER_GBR: | ||||
|       r = 2; | ||||
|       g = 0; | ||||
|       b = 1; | ||||
|       break; | ||||
|     case ORDER_BGR: | ||||
|       r = 2; | ||||
|       g = 1; | ||||
|       b = 0; | ||||
|       break; | ||||
|     case ORDER_BRG: | ||||
|       r = 1; | ||||
|       g = 2; | ||||
|       b = 0; | ||||
|       break; | ||||
|   } | ||||
|   uint8_t multiplier = this->is_rgbw_ ? 4 : 3; | ||||
|   return {this->buf_ + (index * multiplier) + r, | ||||
|           this->buf_ + (index * multiplier) + g, | ||||
|           this->buf_ + (index * multiplier) + b, | ||||
|           this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr, | ||||
|           &this->effect_data_[index], | ||||
|           &this->correction_}; | ||||
| } | ||||
|  | ||||
| void ESP32RMTLEDStripLightOutput::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_); | ||||
|   const char *rgb_order; | ||||
|   switch (this->rgb_order_) { | ||||
|     case ORDER_RGB: | ||||
|       rgb_order = "RGB"; | ||||
|       break; | ||||
|     case ORDER_RBG: | ||||
|       rgb_order = "RBG"; | ||||
|       break; | ||||
|     case ORDER_GRB: | ||||
|       rgb_order = "GRB"; | ||||
|       break; | ||||
|     case ORDER_GBR: | ||||
|       rgb_order = "GBR"; | ||||
|       break; | ||||
|     case ORDER_BGR: | ||||
|       rgb_order = "BGR"; | ||||
|       break; | ||||
|     case ORDER_BRG: | ||||
|       rgb_order = "BRG"; | ||||
|       break; | ||||
|     default: | ||||
|       rgb_order = "UNKNOWN"; | ||||
|       break; | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  RGB Order: %s", rgb_order); | ||||
|   ESP_LOGCONFIG(TAG, "  Max refresh rate: %u", *this->max_refresh_rate_); | ||||
|   ESP_LOGCONFIG(TAG, "  Number of LEDs: %u", this->num_leds_); | ||||
| } | ||||
|  | ||||
| float ESP32RMTLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
|  | ||||
| }  // namespace esp32_rmt_led_strip | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP32 | ||||
							
								
								
									
										87
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include "esphome/components/light/addressable_light.h" | ||||
| #include "esphome/components/light/light_output.h" | ||||
| #include "esphome/core/color.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| #include <driver/gpio.h> | ||||
| #include <driver/rmt.h> | ||||
| #include <esp_err.h> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace esp32_rmt_led_strip { | ||||
|  | ||||
| enum RGBOrder : uint8_t { | ||||
|   ORDER_RGB, | ||||
|   ORDER_RBG, | ||||
|   ORDER_GRB, | ||||
|   ORDER_GBR, | ||||
|   ORDER_BGR, | ||||
|   ORDER_BRG, | ||||
| }; | ||||
|  | ||||
| class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void write_state(light::LightState *state) override; | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|   int32_t size() const override { return this->num_leds_; } | ||||
|   light::LightTraits get_traits() override { | ||||
|     auto traits = light::LightTraits(); | ||||
|     if (this->is_rgbw_) { | ||||
|       traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::RGB_WHITE}); | ||||
|     } else { | ||||
|       traits.set_supported_color_modes({light::ColorMode::RGB}); | ||||
|     } | ||||
|     return traits; | ||||
|   } | ||||
|  | ||||
|   void set_pin(uint8_t pin) { this->pin_ = pin; } | ||||
|   void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } | ||||
|   void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } | ||||
|  | ||||
|   /// Set a maximum refresh rate in µs as some lights do not like being updated too often. | ||||
|   void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } | ||||
|  | ||||
|   void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low); | ||||
|  | ||||
|   void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } | ||||
|   void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } | ||||
|  | ||||
|   void clear_effect_data() override { | ||||
|     for (int i = 0; i < this->size(); i++) | ||||
|       this->effect_data_[i] = 0; | ||||
|   } | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   light::ESPColorView get_view_internal(int32_t index) const override; | ||||
|  | ||||
|   size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); } | ||||
|  | ||||
|   uint8_t *buf_{nullptr}; | ||||
|   uint8_t *effect_data_{nullptr}; | ||||
|   rmt_item32_t *rmt_buf_{nullptr}; | ||||
|  | ||||
|   uint8_t pin_; | ||||
|   uint16_t num_leds_; | ||||
|   bool is_rgbw_; | ||||
|  | ||||
|   rmt_item32_t bit0_, bit1_; | ||||
|   RGBOrder rgb_order_; | ||||
|   rmt_channel_t channel_; | ||||
|  | ||||
|   uint32_t last_refresh_{0}; | ||||
|   optional<uint32_t> max_refresh_rate_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace esp32_rmt_led_strip | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP32 | ||||
							
								
								
									
										151
									
								
								esphome/components/esp32_rmt_led_strip/light.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								esphome/components/esp32_rmt_led_strip/light.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| from dataclasses import dataclass | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.components import esp32, light | ||||
| from esphome.const import ( | ||||
|     CONF_CHIPSET, | ||||
|     CONF_MAX_REFRESH_RATE, | ||||
|     CONF_NUM_LEDS, | ||||
|     CONF_OUTPUT_ID, | ||||
|     CONF_PIN, | ||||
|     CONF_RGB_ORDER, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| DEPENDENCIES = ["esp32"] | ||||
|  | ||||
| esp32_rmt_led_strip_ns = cg.esphome_ns.namespace("esp32_rmt_led_strip") | ||||
| ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_( | ||||
|     "ESP32RMTLEDStripLightOutput", light.AddressableLight | ||||
| ) | ||||
|  | ||||
| rmt_channel_t = cg.global_ns.enum("rmt_channel_t") | ||||
|  | ||||
| RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder") | ||||
|  | ||||
| RGB_ORDERS = { | ||||
|     "RGB": RGBOrder.ORDER_RGB, | ||||
|     "RBG": RGBOrder.ORDER_RBG, | ||||
|     "GRB": RGBOrder.ORDER_GRB, | ||||
|     "GBR": RGBOrder.ORDER_GBR, | ||||
|     "BGR": RGBOrder.ORDER_BGR, | ||||
|     "BRG": RGBOrder.ORDER_BRG, | ||||
| } | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class LEDStripTimings: | ||||
|     bit0_high: int | ||||
|     bit0_low: int | ||||
|     bit1_high: int | ||||
|     bit1_low: int | ||||
|  | ||||
|  | ||||
| CHIPSETS = { | ||||
|     "WS2812": LEDStripTimings(400, 1000, 1000, 400), | ||||
|     "SK6812": LEDStripTimings(300, 900, 600, 600), | ||||
|     "APA106": LEDStripTimings(350, 1360, 1360, 350), | ||||
|     "SM16703": LEDStripTimings(300, 900, 1360, 350), | ||||
| } | ||||
|  | ||||
|  | ||||
| CONF_IS_RGBW = "is_rgbw" | ||||
| CONF_BIT0_HIGH = "bit0_high" | ||||
| CONF_BIT0_LOW = "bit0_low" | ||||
| CONF_BIT1_HIGH = "bit1_high" | ||||
| CONF_BIT1_LOW = "bit1_low" | ||||
| CONF_RMT_CHANNEL = "rmt_channel" | ||||
|  | ||||
| RMT_CHANNELS = { | ||||
|     esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7], | ||||
|     esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], | ||||
|     esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3], | ||||
|     esp32.const.VARIANT_ESP32C3: [0, 1], | ||||
| } | ||||
|  | ||||
|  | ||||
| def _validate_rmt_channel(value): | ||||
|     variant = esp32.get_esp32_variant() | ||||
|     if variant not in RMT_CHANNELS: | ||||
|         raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.") | ||||
|     if value not in RMT_CHANNELS[variant]: | ||||
|         raise cv.Invalid( | ||||
|             f"RMT channel {value} is not supported for ESP32 variant {variant}." | ||||
|         ) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     light.ADDRESSABLE_LIGHT_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput), | ||||
|             cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, | ||||
|             cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, | ||||
|             cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), | ||||
|             cv.Required(CONF_RMT_CHANNEL): _validate_rmt_channel, | ||||
|             cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, | ||||
|             cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), | ||||
|             cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, | ||||
|             cv.Inclusive( | ||||
|                 CONF_BIT0_HIGH, | ||||
|                 "custom", | ||||
|             ): cv.positive_time_period_microseconds, | ||||
|             cv.Inclusive( | ||||
|                 CONF_BIT0_LOW, | ||||
|                 "custom", | ||||
|             ): cv.positive_time_period_microseconds, | ||||
|             cv.Inclusive( | ||||
|                 CONF_BIT1_HIGH, | ||||
|                 "custom", | ||||
|             ): cv.positive_time_period_microseconds, | ||||
|             cv.Inclusive( | ||||
|                 CONF_BIT1_LOW, | ||||
|                 "custom", | ||||
|             ): cv.positive_time_period_microseconds, | ||||
|         } | ||||
|     ), | ||||
|     cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) | ||||
|     await light.register_light(var, config) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     cg.add(var.set_num_leds(config[CONF_NUM_LEDS])) | ||||
|     cg.add(var.set_pin(config[CONF_PIN])) | ||||
|  | ||||
|     if CONF_MAX_REFRESH_RATE in config: | ||||
|         cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) | ||||
|  | ||||
|     if CONF_CHIPSET in config: | ||||
|         chipset = CHIPSETS[config[CONF_CHIPSET]] | ||||
|         cg.add( | ||||
|             var.set_led_params( | ||||
|                 chipset.bit0_high, | ||||
|                 chipset.bit0_low, | ||||
|                 chipset.bit1_high, | ||||
|                 chipset.bit1_low, | ||||
|             ) | ||||
|         ) | ||||
|     else: | ||||
|         cg.add( | ||||
|             var.set_led_params( | ||||
|                 config[CONF_BIT0_HIGH], | ||||
|                 config[CONF_BIT0_LOW], | ||||
|                 config[CONF_BIT1_HIGH], | ||||
|                 config[CONF_BIT1_LOW], | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) | ||||
|     cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) | ||||
|  | ||||
|     cg.add( | ||||
|         var.set_rmt_channel( | ||||
|             getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}") | ||||
|         ) | ||||
|     ) | ||||
| @@ -11,6 +11,7 @@ from esphome.const import ( | ||||
|     CONF_VOLTAGE_ATTENUATION, | ||||
| ) | ||||
| from esphome.core import TimePeriod | ||||
| from esphome.components import esp32 | ||||
|  | ||||
| AUTO_LOAD = ["binary_sensor"] | ||||
| DEPENDENCIES = ["esp32"] | ||||
| @@ -50,30 +51,37 @@ VOLTAGE_ATTENUATION = { | ||||
|     "0V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(ESP32TouchComponent), | ||||
|         cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean, | ||||
|         cv.Optional( | ||||
|             CONF_IIR_FILTER, default="0ms" | ||||
|         ): cv.positive_time_period_milliseconds, | ||||
|         cv.Optional(CONF_SLEEP_DURATION, default="27306us"): cv.All( | ||||
|             cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=436906)) | ||||
|         ), | ||||
|         cv.Optional(CONF_MEASUREMENT_DURATION, default="8192us"): cv.All( | ||||
|             cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=8192)) | ||||
|         ), | ||||
|         cv.Optional(CONF_LOW_VOLTAGE_REFERENCE, default="0.5V"): validate_voltage( | ||||
|             LOW_VOLTAGE_REFERENCE | ||||
|         ), | ||||
|         cv.Optional(CONF_HIGH_VOLTAGE_REFERENCE, default="2.7V"): validate_voltage( | ||||
|             HIGH_VOLTAGE_REFERENCE | ||||
|         ), | ||||
|         cv.Optional(CONF_VOLTAGE_ATTENUATION, default="0V"): validate_voltage( | ||||
|             VOLTAGE_ATTENUATION | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ESP32TouchComponent), | ||||
|             cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean, | ||||
|             cv.Optional( | ||||
|                 CONF_IIR_FILTER, default="0ms" | ||||
|             ): cv.positive_time_period_milliseconds, | ||||
|             cv.Optional(CONF_SLEEP_DURATION, default="27306us"): cv.All( | ||||
|                 cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=436906)) | ||||
|             ), | ||||
|             cv.Optional(CONF_MEASUREMENT_DURATION, default="8192us"): cv.All( | ||||
|                 cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=8192)) | ||||
|             ), | ||||
|             cv.Optional(CONF_LOW_VOLTAGE_REFERENCE, default="0.5V"): validate_voltage( | ||||
|                 LOW_VOLTAGE_REFERENCE | ||||
|             ), | ||||
|             cv.Optional(CONF_HIGH_VOLTAGE_REFERENCE, default="2.7V"): validate_voltage( | ||||
|                 HIGH_VOLTAGE_REFERENCE | ||||
|             ), | ||||
|             cv.Optional(CONF_VOLTAGE_ATTENUATION, default="0V"): validate_voltage( | ||||
|                 VOLTAGE_ATTENUATION | ||||
|             ), | ||||
|         } | ||||
|     ).extend(cv.COMPONENT_SCHEMA), | ||||
|     esp32.only_on_variant( | ||||
|         supported=[ | ||||
|             esp32.const.VARIANT_ESP32, | ||||
|         ] | ||||
|     ), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|   | ||||
| @@ -125,7 +125,7 @@ def _parse_platform_version(value): | ||||
|     try: | ||||
|         # if platform version is a valid version constraint, prefix the default package | ||||
|         cv.platformio_version_constraint(value) | ||||
|         return f"platformio/espressif8266 @ {value}" | ||||
|         return f"platformio/espressif8266@{value}" | ||||
|     except cv.Invalid: | ||||
|         return value | ||||
|  | ||||
| @@ -181,7 +181,7 @@ async def to_code(config): | ||||
|     cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) | ||||
|     cg.add_platformio_option( | ||||
|         "platform_packages", | ||||
|         [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], | ||||
|         [f"platformio/framework-arduinoespressif8266@{conf[CONF_SOURCE]}"], | ||||
|     ) | ||||
|  | ||||
|     # Default for platformio is LWIP2_LOW_MEMORY with: | ||||
|   | ||||
| @@ -34,6 +34,7 @@ ETHERNET_TYPES = { | ||||
|     "DP83848": EthernetType.ETHERNET_TYPE_DP83848, | ||||
|     "IP101": EthernetType.ETHERNET_TYPE_IP101, | ||||
|     "JL1101": EthernetType.ETHERNET_TYPE_JL1101, | ||||
|     "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, | ||||
| } | ||||
|  | ||||
| emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") | ||||
|   | ||||
| @@ -26,8 +26,10 @@ EthernetComponent::EthernetComponent() { global_eth_component = this; } | ||||
|  | ||||
| void EthernetComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); | ||||
|   // Delay here to allow power to stabilise before Ethernet is initialised. | ||||
|   delay(300);  // NOLINT | ||||
|   if (esp_reset_reason() != ESP_RST_DEEPSLEEP) { | ||||
|     // Delay here to allow power to stabilise before Ethernet is initialized. | ||||
|     delay(300);  // NOLINT | ||||
|   } | ||||
|  | ||||
|   esp_err_t err; | ||||
|   err = esp_netif_init(); | ||||
| @@ -52,26 +54,29 @@ void EthernetComponent::setup() { | ||||
|  | ||||
|   esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); | ||||
|  | ||||
|   esp_eth_phy_t *phy; | ||||
|   switch (this->type_) { | ||||
|     case ETHERNET_TYPE_LAN8720: { | ||||
|       phy = esp_eth_phy_new_lan87xx(&phy_config); | ||||
|       this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); | ||||
|       break; | ||||
|     } | ||||
|     case ETHERNET_TYPE_RTL8201: { | ||||
|       phy = esp_eth_phy_new_rtl8201(&phy_config); | ||||
|       this->phy_ = esp_eth_phy_new_rtl8201(&phy_config); | ||||
|       break; | ||||
|     } | ||||
|     case ETHERNET_TYPE_DP83848: { | ||||
|       phy = esp_eth_phy_new_dp83848(&phy_config); | ||||
|       this->phy_ = esp_eth_phy_new_dp83848(&phy_config); | ||||
|       break; | ||||
|     } | ||||
|     case ETHERNET_TYPE_IP101: { | ||||
|       phy = esp_eth_phy_new_ip101(&phy_config); | ||||
|       this->phy_ = esp_eth_phy_new_ip101(&phy_config); | ||||
|       break; | ||||
|     } | ||||
|     case ETHERNET_TYPE_JL1101: { | ||||
|       phy = esp_eth_phy_new_jl1101(&phy_config); | ||||
|       this->phy_ = esp_eth_phy_new_jl1101(&phy_config); | ||||
|       break; | ||||
|     } | ||||
|     case ETHERNET_TYPE_KSZ8081: { | ||||
|       this->phy_ = esp_eth_phy_new_ksz8081(&phy_config); | ||||
|       break; | ||||
|     } | ||||
|     default: { | ||||
| @@ -80,7 +85,7 @@ void EthernetComponent::setup() { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); | ||||
|   esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_); | ||||
|   this->eth_handle_ = nullptr; | ||||
|   err = esp_eth_driver_install(ð_config, &this->eth_handle_); | ||||
|   ESPHL_ERROR_CHECK(err, "ETH driver install error"); | ||||
| @@ -140,7 +145,7 @@ void EthernetComponent::loop() { | ||||
| } | ||||
|  | ||||
| void EthernetComponent::dump_config() { | ||||
|   std::string eth_type; | ||||
|   const char *eth_type; | ||||
|   switch (this->type_) { | ||||
|     case ETHERNET_TYPE_LAN8720: | ||||
|       eth_type = "LAN8720"; | ||||
| @@ -158,6 +163,14 @@ void EthernetComponent::dump_config() { | ||||
|       eth_type = "IP101"; | ||||
|       break; | ||||
|  | ||||
|     case ETHERNET_TYPE_JL1101: | ||||
|       eth_type = "JL1101"; | ||||
|       break; | ||||
|  | ||||
|     case ETHERNET_TYPE_KSZ8081: | ||||
|       eth_type = "KSZ8081"; | ||||
|       break; | ||||
|  | ||||
|     default: | ||||
|       eth_type = "Unknown"; | ||||
|       break; | ||||
| @@ -170,7 +183,8 @@ void EthernetComponent::dump_config() { | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  MDC Pin: %u", this->mdc_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  MDIO Pin: %u", this->mdio_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  Type: %s", eth_type.c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  Type: %s", eth_type); | ||||
|   ESP_LOGCONFIG(TAG, "  PHY addr: %u", this->phy_addr_); | ||||
| } | ||||
|  | ||||
| float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } | ||||
| @@ -255,14 +269,22 @@ void EthernetComponent::start_connect_() { | ||||
|   if (this->manual_ip_.has_value()) { | ||||
|     if (uint32_t(this->manual_ip_->dns1) != 0) { | ||||
|       ip_addr_t d; | ||||
| #if LWIP_IPV6 | ||||
|       d.type = IPADDR_TYPE_V4; | ||||
|       d.u_addr.ip4.addr = static_cast<uint32_t>(this->manual_ip_->dns1); | ||||
| #else | ||||
|       d.addr = static_cast<uint32_t>(this->manual_ip_->dns1); | ||||
| #endif | ||||
|       dns_setserver(0, &d); | ||||
|     } | ||||
|     if (uint32_t(this->manual_ip_->dns1) != 0) { | ||||
|     if (uint32_t(this->manual_ip_->dns2) != 0) { | ||||
|       ip_addr_t d; | ||||
| #if LWIP_IPV6 | ||||
|       d.type = IPADDR_TYPE_V4; | ||||
|       d.u_addr.ip4.addr = static_cast<uint32_t>(this->manual_ip_->dns2); | ||||
| #else | ||||
|       d.addr = static_cast<uint32_t>(this->manual_ip_->dns2); | ||||
| #endif | ||||
|       dns_setserver(1, &d); | ||||
|     } | ||||
|   } else { | ||||
| @@ -289,8 +311,13 @@ void EthernetComponent::dump_connect_params_() { | ||||
|   const ip_addr_t *dns_ip1 = dns_getserver(0); | ||||
|   const ip_addr_t *dns_ip2 = dns_getserver(1); | ||||
|  | ||||
| #if LWIP_IPV6 | ||||
|   ESP_LOGCONFIG(TAG, "  DNS1: %s", network::IPAddress(dns_ip1->u_addr.ip4.addr).str().c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  DNS2: %s", network::IPAddress(dns_ip2->u_addr.ip4.addr).str().c_str()); | ||||
| #else | ||||
|   ESP_LOGCONFIG(TAG, "  DNS1: %s", network::IPAddress(dns_ip1->addr).str().c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  DNS2: %s", network::IPAddress(dns_ip2->addr).str().c_str()); | ||||
| #endif | ||||
|  | ||||
|   esp_err_t err; | ||||
|  | ||||
| @@ -330,6 +357,21 @@ std::string EthernetComponent::get_use_address() const { | ||||
|  | ||||
| void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } | ||||
|  | ||||
| bool EthernetComponent::powerdown() { | ||||
|   ESP_LOGI(TAG, "Powering down ethernet PHY"); | ||||
|   if (this->phy_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Ethernet PHY not assigned"); | ||||
|     return false; | ||||
|   } | ||||
|   this->connected_ = false; | ||||
|   this->started_ = false; | ||||
|   if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "Error powering down ethernet PHY"); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace ethernet | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -14,11 +14,13 @@ namespace esphome { | ||||
| namespace ethernet { | ||||
|  | ||||
| enum EthernetType { | ||||
|   ETHERNET_TYPE_LAN8720 = 0, | ||||
|   ETHERNET_TYPE_UNKNOWN = 0, | ||||
|   ETHERNET_TYPE_LAN8720, | ||||
|   ETHERNET_TYPE_RTL8201, | ||||
|   ETHERNET_TYPE_DP83848, | ||||
|   ETHERNET_TYPE_IP101, | ||||
|   ETHERNET_TYPE_JL1101, | ||||
|   ETHERNET_TYPE_KSZ8081, | ||||
| }; | ||||
|  | ||||
| struct ManualIP { | ||||
| @@ -43,6 +45,7 @@ class EthernetComponent : public Component { | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|   bool can_proceed() override; | ||||
|   void on_shutdown() override { powerdown(); } | ||||
|   bool is_connected(); | ||||
|  | ||||
|   void set_phy_addr(uint8_t phy_addr); | ||||
| @@ -56,6 +59,7 @@ class EthernetComponent : public Component { | ||||
|   network::IPAddress get_ip_address(); | ||||
|   std::string get_use_address() const; | ||||
|   void set_use_address(const std::string &use_address); | ||||
|   bool powerdown(); | ||||
|  | ||||
|  protected: | ||||
|   static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); | ||||
| @@ -69,7 +73,7 @@ class EthernetComponent : public Component { | ||||
|   int power_pin_{-1}; | ||||
|   uint8_t mdc_pin_{23}; | ||||
|   uint8_t mdio_pin_{18}; | ||||
|   EthernetType type_{ETHERNET_TYPE_LAN8720}; | ||||
|   EthernetType type_{ETHERNET_TYPE_UNKNOWN}; | ||||
|   emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; | ||||
|   emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; | ||||
|   optional<ManualIP> manual_ip_{}; | ||||
| @@ -80,6 +84,7 @@ class EthernetComponent : public Component { | ||||
|   uint32_t connect_begin_; | ||||
|   esp_netif_t *eth_netif_{nullptr}; | ||||
|   esp_eth_handle_t eth_handle_; | ||||
|   esp_eth_phy_t *phy_{nullptr}; | ||||
| }; | ||||
|  | ||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user