mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'dev' into optolink
This commit is contained in:
		
							
								
								
									
										15
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							| @@ -7,11 +7,16 @@ | ||||
| - [ ] Bugfix (non-breaking change which fixes an issue) | ||||
| - [ ] New feature (non-breaking change which adds functionality) | ||||
| - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) | ||||
| - [ ] Code quality improvements to existing code or addition of tests | ||||
| - [ ] Other | ||||
|  | ||||
| **Related issue or feature (if applicable):** fixes <link to issue> | ||||
| **Related issue or feature (if applicable):** | ||||
|  | ||||
| **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> | ||||
| - fixes <link to issue> | ||||
|  | ||||
| **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** | ||||
|  | ||||
| - esphome/esphome-docs#<esphome-docs PR number goes here> | ||||
|  | ||||
| ## Test Environment | ||||
|  | ||||
| @@ -23,12 +28,6 @@ | ||||
| - [ ] RTL87xx | ||||
|  | ||||
| ## Example entry for `config.yaml`: | ||||
| <!-- | ||||
|   Supplying a configuration snippet, makes it easier for a maintainer to test | ||||
|   your PR. Furthermore, for new integrations, it gives an impression of how | ||||
|   the configuration would look like. | ||||
|   Note: Remove this section if this PR does not have an example entry. | ||||
| --> | ||||
|  | ||||
| ```yaml | ||||
| # Example config.yaml | ||||
|   | ||||
							
								
								
									
										10
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -46,7 +46,10 @@ runs: | ||||
|  | ||||
|     - name: Build and push to ghcr by digest | ||||
|       id: build-ghcr | ||||
|       uses: docker/build-push-action@v6.4.1 | ||||
|       uses: docker/build-push-action@v6.9.0 | ||||
|       env: | ||||
|         DOCKER_BUILD_SUMMARY: false | ||||
|         DOCKER_BUILD_RECORD_UPLOAD: false | ||||
|       with: | ||||
|         context: . | ||||
|         file: ./docker/Dockerfile | ||||
| @@ -69,7 +72,10 @@ runs: | ||||
|  | ||||
|     - name: Build and push to dockerhub by digest | ||||
|       id: build-dockerhub | ||||
|       uses: docker/build-push-action@v6.4.1 | ||||
|       uses: docker/build-push-action@v6.9.0 | ||||
|       env: | ||||
|         DOCKER_BUILD_SUMMARY: false | ||||
|         DOCKER_BUILD_RECORD_UPLOAD: false | ||||
|       with: | ||||
|         context: . | ||||
|         file: ./docker/Dockerfile | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,12 +17,12 @@ runs: | ||||
|   steps: | ||||
|     - name: Set up Python ${{ inputs.python-version }} | ||||
|       id: python | ||||
|       uses: actions/setup-python@v5.1.1 | ||||
|       uses: actions/setup-python@v5.3.0 | ||||
|       with: | ||||
|         python-version: ${{ inputs.python-version }} | ||||
|     - name: Restore Python virtual environment | ||||
|       id: cache-venv | ||||
|       uses: actions/cache/restore@v4.0.2 | ||||
|       uses: actions/cache/restore@v4.1.2 | ||||
|       with: | ||||
|         path: venv | ||||
|         # yamllint disable-line rule:line-length | ||||
|   | ||||
							
								
								
									
										7
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @@ -13,6 +13,13 @@ updates: | ||||
|     schedule: | ||||
|       interval: daily | ||||
|     open-pull-requests-limit: 10 | ||||
|     groups: | ||||
|       docker-actions: | ||||
|         applies-to: version-updates | ||||
|         patterns: | ||||
|           - "docker/setup-qemu-action" | ||||
|           - "docker/login-action" | ||||
|           - "docker/setup-buildx-action" | ||||
|   - package-ecosystem: github-actions | ||||
|     directory: "/.github/actions/build-image" | ||||
|     schedule: | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,7 +23,7 @@ jobs: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.3.0 | ||||
|         with: | ||||
|           python-version: "3.11" | ||||
|  | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -42,13 +42,13 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.7 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.3.0 | ||||
|         with: | ||||
|           python-version: "3.9" | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3.4.0 | ||||
|         uses: docker/setup-buildx-action@v3.7.1 | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v3.1.0 | ||||
|         uses: docker/setup-qemu-action@v3.2.0 | ||||
|  | ||||
|       - name: Set TAG | ||||
|         run: | | ||||
|   | ||||
							
								
								
									
										23
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,6 +9,7 @@ on: | ||||
|     paths: | ||||
|       - "**" | ||||
|       - "!.github/workflows/*.yml" | ||||
|       - "!.github/actions/build-image/*" | ||||
|       - ".github/workflows/ci.yml" | ||||
|       - "!.yamllint" | ||||
|       - "!.github/dependabot.yml" | ||||
| @@ -40,12 +41,12 @@ jobs: | ||||
|         run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT | ||||
|       - name: Set up Python ${{ env.DEFAULT_PYTHON }} | ||||
|         id: python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.3.0 | ||||
|         with: | ||||
|           python-version: ${{ env.DEFAULT_PYTHON }} | ||||
|       - name: Restore Python virtual environment | ||||
|         id: cache-venv | ||||
|         uses: actions/cache@v4.0.2 | ||||
|         uses: actions/cache@v4.1.2 | ||||
|         with: | ||||
|           path: venv | ||||
|           # yamllint disable-line rule:line-length | ||||
| @@ -301,20 +302,22 @@ jobs: | ||||
|  | ||||
|       - name: Cache platformio | ||||
|         if: github.ref == 'refs/heads/dev' | ||||
|         uses: actions/cache@v4.0.2 | ||||
|         uses: actions/cache@v4.1.2 | ||||
|         with: | ||||
|           path: ~/.platformio | ||||
|           key: platformio-${{ matrix.pio_cache_key }} | ||||
|  | ||||
|       - name: Cache platformio | ||||
|         if: github.ref != 'refs/heads/dev' | ||||
|         uses: actions/cache/restore@v4.0.2 | ||||
|         uses: actions/cache/restore@v4.1.2 | ||||
|         with: | ||||
|           path: ~/.platformio | ||||
|           key: platformio-${{ matrix.pio_cache_key }} | ||||
|  | ||||
|       - name: Install clang-tidy | ||||
|         run: sudo apt-get install clang-tidy-14 | ||||
|         run: | | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install clang-tidy-14 | ||||
|  | ||||
|       - name: Register problem matchers | ||||
|         run: | | ||||
| @@ -396,7 +399,9 @@ jobs: | ||||
|         file: ${{ fromJson(needs.list-components.outputs.components) }} | ||||
|     steps: | ||||
|       - name: Install dependencies | ||||
|         run: sudo apt-get install libsodium-dev libsdl2-dev | ||||
|         run: | | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install libsdl2-dev | ||||
|  | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.7 | ||||
| @@ -450,7 +455,9 @@ jobs: | ||||
|         run: echo ${{ matrix.components }} | ||||
|  | ||||
|       - name: Install dependencies | ||||
|         run: sudo apt-get install libsodium-dev libsdl2-dev | ||||
|         run: | | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install libsdl2-dev | ||||
|  | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.7 | ||||
| @@ -468,6 +475,8 @@ jobs: | ||||
|       - name: Compile config | ||||
|         run: | | ||||
|           . venv/bin/activate | ||||
|           mkdir build_cache | ||||
|           export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache | ||||
|           for component in ${{ matrix.components }}; do | ||||
|             ./script/test_build_components -e compile -c $component | ||||
|           done | ||||
|   | ||||
							
								
								
									
										91
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| # For most projects, this workflow file will not need changing; you simply need | ||||
| # to commit it to your repository. | ||||
| # | ||||
| # You may wish to alter this file to override the set of languages analyzed, | ||||
| # or to provide custom queries or build logic. | ||||
| # | ||||
| # ******** NOTE ******** | ||||
| # We have attempted to detect the languages in your repository. Please check | ||||
| # the `language` matrix defined below to confirm you have the correct set of | ||||
| # supported CodeQL languages. | ||||
| # | ||||
| name: "CodeQL Advanced" | ||||
|  | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|   schedule: | ||||
|     - cron: "30 18 * * 4" | ||||
|  | ||||
| jobs: | ||||
|   analyze: | ||||
|     name: Analyze (${{ matrix.language }}) | ||||
|     # Runner size impacts CodeQL analysis time. To learn more, please see: | ||||
|     #   - https://gh.io/recommended-hardware-resources-for-running-codeql | ||||
|     #   - https://gh.io/supported-runners-and-hardware-resources | ||||
|     #   - https://gh.io/using-larger-runners (GitHub.com only) | ||||
|     # Consider using larger runners or machines with greater resources for possible analysis time improvements. | ||||
|     runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} | ||||
|     permissions: | ||||
|       # required for all workflows | ||||
|       security-events: write | ||||
|  | ||||
|       # required to fetch internal or private CodeQL packs | ||||
|       packages: read | ||||
|  | ||||
|       # only required for workflows in private repositories | ||||
|       actions: read | ||||
|       contents: read | ||||
|  | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         include: | ||||
|           # - language: c-cpp | ||||
|           #   build-mode: autobuild | ||||
|           - language: python | ||||
|             build-mode: none | ||||
|             # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' | ||||
|             # Use `c-cpp` to analyze code written in C, C++ or both | ||||
|             # Use 'java-kotlin' to analyze code written in Java, Kotlin or both | ||||
|             # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both | ||||
|             # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, | ||||
|             # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. | ||||
|             # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how | ||||
|             # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v4 | ||||
|  | ||||
|       # Initializes the CodeQL tools for scanning. | ||||
|       - name: Initialize CodeQL | ||||
|         uses: github/codeql-action/init@v3 | ||||
|         with: | ||||
|           languages: ${{ matrix.language }} | ||||
|           build-mode: ${{ matrix.build-mode }} | ||||
|           # If you wish to specify custom queries, you can do so here or in a config file. | ||||
|           # By default, queries listed here will override any specified in a config file. | ||||
|           # Prefix the list here with "+" to use these queries and those in the config file. | ||||
|  | ||||
|           # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs | ||||
|           # queries: security-extended,security-and-quality | ||||
|  | ||||
|       # If the analyze step fails for one of the languages you are analyzing with | ||||
|       # "We were unable to automatically build your code", modify the matrix above | ||||
|       # to set the build mode to "manual" for that language. Then modify this step | ||||
|       # to build your code. | ||||
|       # ℹ️ Command-line programs to run using the OS shell. | ||||
|       # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun | ||||
|       - if: matrix.build-mode == 'manual' | ||||
|         shell: bash | ||||
|         run: | | ||||
|           echo 'If you are using a "manual" build mode for one or more of the' \ | ||||
|             'languages you are analyzing, replace this with the commands to build' \ | ||||
|             'your code, for example:' | ||||
|           echo '  make bootstrap' | ||||
|           echo '  make release' | ||||
|           exit 1 | ||||
|  | ||||
|       - name: Perform CodeQL Analysis | ||||
|         uses: github/codeql-action/analyze@v3 | ||||
|         with: | ||||
|           category: "/language:${{matrix.language}}" | ||||
							
								
								
									
										22
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -53,7 +53,7 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.7 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.3.0 | ||||
|         with: | ||||
|           python-version: "3.x" | ||||
|       - name: Set up python environment | ||||
| @@ -65,7 +65,7 @@ jobs: | ||||
|           pip3 install build | ||||
|           python3 -m build | ||||
|       - name: Publish | ||||
|         uses: pypa/gh-action-pypi-publish@v1.9.0 | ||||
|         uses: pypa/gh-action-pypi-publish@v1.10.3 | ||||
|  | ||||
|   deploy-docker: | ||||
|     name: Build ESPHome ${{ matrix.platform }} | ||||
| @@ -85,23 +85,23 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.7 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.3.0 | ||||
|         with: | ||||
|           python-version: "3.9" | ||||
|  | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3.4.0 | ||||
|         uses: docker/setup-buildx-action@v3.7.1 | ||||
|       - name: Set up QEMU | ||||
|         if: matrix.platform != 'linux/amd64' | ||||
|         uses: docker/setup-qemu-action@v3.1.0 | ||||
|         uses: docker/setup-qemu-action@v3.2.0 | ||||
|  | ||||
|       - name: Log in to docker hub | ||||
|         uses: docker/login-action@v3.2.0 | ||||
|         uses: docker/login-action@v3.3.0 | ||||
|         with: | ||||
|           username: ${{ secrets.DOCKER_USER }} | ||||
|           password: ${{ secrets.DOCKER_PASSWORD }} | ||||
|       - name: Log in to the GitHub container registry | ||||
|         uses: docker/login-action@v3.2.0 | ||||
|         uses: docker/login-action@v3.3.0 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
| @@ -141,7 +141,7 @@ jobs: | ||||
|           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT | ||||
|  | ||||
|       - name: Upload digests | ||||
|         uses: actions/upload-artifact@v4.3.4 | ||||
|         uses: actions/upload-artifact@v4.4.3 | ||||
|         with: | ||||
|           name: digests-${{ steps.sanitize.outputs.name }} | ||||
|           path: /tmp/digests | ||||
| @@ -184,17 +184,17 @@ jobs: | ||||
|           merge-multiple: true | ||||
|  | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3.4.0 | ||||
|         uses: docker/setup-buildx-action@v3.7.1 | ||||
|  | ||||
|       - name: Log in to docker hub | ||||
|         if: matrix.registry == 'dockerhub' | ||||
|         uses: docker/login-action@v3.2.0 | ||||
|         uses: docker/login-action@v3.3.0 | ||||
|         with: | ||||
|           username: ${{ secrets.DOCKER_USER }} | ||||
|           password: ${{ secrets.DOCKER_PASSWORD }} | ||||
|       - name: Log in to the GitHub container registry | ||||
|         if: matrix.registry == 'ghcr' | ||||
|         uses: docker/login-action@v3.2.0 | ||||
|         uses: docker/login-action@v3.3.0 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							| @@ -22,7 +22,7 @@ jobs: | ||||
|           path: lib/home-assistant | ||||
|  | ||||
|       - name: Setup Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.3.0 | ||||
|         with: | ||||
|           python-version: 3.12 | ||||
|  | ||||
| @@ -36,7 +36,7 @@ jobs: | ||||
|           python ./script/sync-device_class.py | ||||
|  | ||||
|       - name: Commit changes | ||||
|         uses: peter-evans/create-pull-request@v6.1.0 | ||||
|         uses: peter-evans/create-pull-request@v7.0.5 | ||||
|         with: | ||||
|           commit-message: "Synchronise Device Classes from Home Assistant" | ||||
|           committer: esphomebot <esphome@nabucasa.com> | ||||
|   | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -138,3 +138,5 @@ sdkconfig.* | ||||
| .tests/ | ||||
|  | ||||
| /components | ||||
| /managed_components | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,15 @@ | ||||
| # See https://pre-commit.com for more information | ||||
| # See https://pre-commit.com/hooks.html for more hooks | ||||
| repos: | ||||
|   - repo: https://github.com/astral-sh/ruff-pre-commit | ||||
|     # Ruff version. | ||||
|     rev: v0.5.4 | ||||
|     hooks: | ||||
|       # Run the linter. | ||||
|       - id: ruff | ||||
|         args: [--fix] | ||||
|       # Run the formatter. | ||||
|       - id: ruff-format | ||||
|   - repo: https://github.com/psf/black-pre-commit-mirror | ||||
|     rev: 24.4.2 | ||||
|     hooks: | ||||
|   | ||||
							
								
								
									
										45
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -24,6 +24,7 @@ esphome/components/ade7953_i2c/* @angelnu | ||||
| esphome/components/ade7953_spi/* @angelnu | ||||
| esphome/components/ads1118/* @solomondg1 | ||||
| esphome/components/ags10/* @mak-42 | ||||
| esphome/components/aic3204/* @kbx81 | ||||
| esphome/components/airthings_ble/* @jeromelaban | ||||
| esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau | ||||
| esphome/components/airthings_wave_mini/* @ncareau | ||||
| @@ -37,6 +38,7 @@ esphome/components/am43/sensor/* @buxtronix | ||||
| esphome/components/analog_threshold/* @ianchi | ||||
| esphome/components/animation/* @syndlex | ||||
| esphome/components/anova/* @buxtronix | ||||
| esphome/components/apds9306/* @aodrenah | ||||
| esphome/components/api/* @OttoWinter | ||||
| esphome/components/as5600/* @ammmze | ||||
| esphome/components/as5600/sensor/* @ammmze | ||||
| @@ -45,6 +47,10 @@ esphome/components/async_tcp/* @OttoWinter | ||||
| esphome/components/at581x/* @X-Ryl669 | ||||
| esphome/components/atc_mithermometer/* @ahpohl | ||||
| esphome/components/atm90e26/* @danieltwagner | ||||
| esphome/components/atm90e32/* @circuitsetup @descipher | ||||
| esphome/components/audio/* @kahrendt | ||||
| esphome/components/audio_dac/* @kbx81 | ||||
| esphome/components/axs15231/* @clydebarrow | ||||
| esphome/components/b_parasite/* @rbaron | ||||
| esphome/components/ballu/* @bazuchan | ||||
| esphome/components/bang_bang/* @OttoWinter | ||||
| @@ -56,15 +62,21 @@ esphome/components/beken_spi_led_strip/* @Mat931 | ||||
| esphome/components/bh1750/* @OttoWinter | ||||
| esphome/components/binary_sensor/* @esphome/core | ||||
| esphome/components/bk72xx/* @kuba2k2 | ||||
| esphome/components/bl0906/* @athom-tech @jesserockz @tarontop | ||||
| esphome/components/bl0939/* @ziceva | ||||
| esphome/components/bl0940/* @tobias- | ||||
| esphome/components/bl0942/* @dbuezas | ||||
| esphome/components/bl0942/* @dbuezas @dwmw2 | ||||
| esphome/components/ble_client/* @buxtronix @clydebarrow | ||||
| esphome/components/bluetooth_proxy/* @jesserockz | ||||
| esphome/components/bme280_base/* @esphome/core | ||||
| esphome/components/bme280_spi/* @apbodrov | ||||
| esphome/components/bme680_bsec/* @trvrnrth | ||||
| esphome/components/bme68x_bsec2/* @kbx81 @neffs | ||||
| esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs | ||||
| esphome/components/bmi160/* @flaviut | ||||
| esphome/components/bmp280_base/* @ademuri | ||||
| esphome/components/bmp280_i2c/* @ademuri | ||||
| esphome/components/bmp280_spi/* @ademuri | ||||
| esphome/components/bmp3xx/* @latonita | ||||
| esphome/components/bmp3xx_base/* @latonita @martgras | ||||
| esphome/components/bmp3xx_i2c/* @latonita | ||||
| @@ -78,6 +90,7 @@ esphome/components/cap1188/* @mreditor97 | ||||
| esphome/components/captive_portal/* @OttoWinter | ||||
| esphome/components/ccs811/* @habbie | ||||
| esphome/components/cd74hc4067/* @asoehlke | ||||
| esphome/components/ch422g/* @clydebarrow @jesterret | ||||
| esphome/components/climate/* @esphome/core | ||||
| esphome/components/climate_ir/* @glmnet | ||||
| esphome/components/color_temperature/* @jesserockz | ||||
| @@ -143,6 +156,7 @@ esphome/components/ft63x6/* @gpambrozio | ||||
| esphome/components/gcja5/* @gcormier | ||||
| esphome/components/gdk101/* @Szewcson | ||||
| esphome/components/globals/* @esphome/core | ||||
| esphome/components/gp2y1010au0f/* @zry98 | ||||
| esphome/components/gp8403/* @jesserockz | ||||
| esphome/components/gpio/* @esphome/core | ||||
| esphome/components/gpio/one_wire/* @ssieb | ||||
| @@ -150,6 +164,7 @@ esphome/components/gps/* @coogle | ||||
| esphome/components/graph/* @synco | ||||
| esphome/components/graphical_display_menu/* @MrMDavidson | ||||
| esphome/components/gree/* @orestismers | ||||
| esphome/components/grove_gas_mc_v2/* @YorkshireIoT | ||||
| esphome/components/grove_tb6612fng/* @max246 | ||||
| esphome/components/growatt_solar/* @leeuwte | ||||
| esphome/components/gt911/* @clydebarrow @jesserockz | ||||
| @@ -157,6 +172,7 @@ esphome/components/haier/* @paveldn | ||||
| esphome/components/haier/binary_sensor/* @paveldn | ||||
| esphome/components/haier/button/* @paveldn | ||||
| esphome/components/haier/sensor/* @paveldn | ||||
| esphome/components/haier/switch/* @paveldn | ||||
| esphome/components/haier/text_sensor/* @paveldn | ||||
| esphome/components/havells_solar/* @sourabhjaiswal | ||||
| esphome/components/hbridge/fan/* @WeekendWarrior | ||||
| @@ -165,7 +181,10 @@ esphome/components/he60r/* @clydebarrow | ||||
| esphome/components/heatpumpir/* @rob-deutsch | ||||
| esphome/components/hitachi_ac424/* @sourabhjaiswal | ||||
| esphome/components/hm3301/* @freekode | ||||
| esphome/components/homeassistant/* @OttoWinter | ||||
| esphome/components/hmac_md5/* @dwmw2 | ||||
| esphome/components/homeassistant/* @OttoWinter @esphome/core | ||||
| esphome/components/homeassistant/number/* @landonr | ||||
| esphome/components/homeassistant/switch/* @Links2004 | ||||
| esphome/components/honeywell_hih_i2c/* @Benichou34 | ||||
| esphome/components/honeywellabp/* @RubyBailey | ||||
| esphome/components/honeywellabp2_i2c/* @jpfaff | ||||
| @@ -179,10 +198,11 @@ esphome/components/htu31d/* @betterengineering | ||||
| esphome/components/hydreon_rgxx/* @functionpointer | ||||
| esphome/components/hyt271/* @Philippe12 | ||||
| esphome/components/i2c/* @esphome/core | ||||
| esphome/components/i2c_device/* @gabest11 | ||||
| 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/i2s_audio/speaker/* @jesserockz @kahrendt | ||||
| esphome/components/iaqcore/* @yozik04 | ||||
| esphome/components/ili9xxx/* @clydebarrow @nielsnl68 | ||||
| esphome/components/improv_base/* @esphome/core | ||||
| @@ -215,8 +235,12 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz | ||||
| esphome/components/lock/* @esphome/core | ||||
| esphome/components/logger/* @esphome/core | ||||
| esphome/components/ltr390/* @latonita @sjtrny | ||||
| esphome/components/ltr501/* @latonita | ||||
| esphome/components/ltr_als_ps/* @latonita | ||||
| esphome/components/lvgl/* @clydebarrow | ||||
| esphome/components/m5stack_8angle/* @rnauber | ||||
| esphome/components/matrix_keypad/* @ssieb | ||||
| esphome/components/max17043/* @blacknell | ||||
| esphome/components/max31865/* @DAVe3283 | ||||
| esphome/components/max44009/* @berfenger | ||||
| esphome/components/max6956/* @looping40 | ||||
| @@ -263,6 +287,7 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt | ||||
| esphome/components/mpl3115a2/* @kbickar | ||||
| esphome/components/mpu6886/* @fabaff | ||||
| esphome/components/ms8607/* @e28eta | ||||
| esphome/components/nau7802/* @cujomalainey | ||||
| esphome/components/network/* @esphome/core | ||||
| esphome/components/nextion/* @edwardtfn @senexcrenshaw | ||||
| esphome/components/nextion/binary_sensor/* @senexcrenshaw | ||||
| @@ -271,6 +296,7 @@ esphome/components/nextion/switch/* @senexcrenshaw | ||||
| esphome/components/nextion/text_sensor/* @senexcrenshaw | ||||
| esphome/components/nfc/* @jesserockz @kbx81 | ||||
| esphome/components/noblex/* @AGalfra | ||||
| esphome/components/npi19/* @bakerkj | ||||
| esphome/components/number/* @esphome/core | ||||
| esphome/components/one_wire/* @ssieb | ||||
| esphome/components/optolink/* @j0ta29 | ||||
| @@ -281,6 +307,8 @@ esphome/components/optolink/sensor/* @j0ta29 | ||||
| esphome/components/optolink/switch/* @j0ta29 | ||||
| esphome/components/optolink/text/* @j0ta29 | ||||
| esphome/components/optolink/text_sensor/* @j0ta29 | ||||
| esphome/components/online_image/* @guillempages | ||||
| esphome/components/opentherm/* @olegtarasov | ||||
| esphome/components/ota/* @esphome/core | ||||
| esphome/components/output/* @esphome/core | ||||
| esphome/components/pca6416a/* @Mat931 | ||||
| @@ -308,7 +336,7 @@ esphome/components/pvvx_mithermometer/* @pasiz | ||||
| esphome/components/pylontech/* @functionpointer | ||||
| esphome/components/qmp6988/* @andrewpc | ||||
| esphome/components/qr_code/* @wjtje | ||||
| esphome/components/qspi_amoled/* @clydebarrow | ||||
| esphome/components/qspi_dbi/* @clydebarrow | ||||
| esphome/components/qwiic_pir/* @kahrendt | ||||
| esphome/components/radon_eye_ble/* @jeffeb3 | ||||
| esphome/components/radon_eye_rd200/* @jeffeb3 | ||||
| @@ -357,7 +385,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/speaker/* @jesserockz @kahrendt | ||||
| esphome/components/spi/* @clydebarrow @esphome/core | ||||
| esphome/components/spi_device/* @clydebarrow | ||||
| esphome/components/spi_led_strip/* @clydebarrow | ||||
| @@ -381,15 +409,19 @@ esphome/components/st7701s/* @clydebarrow | ||||
| esphome/components/st7735/* @SenexCrenshaw | ||||
| esphome/components/st7789v/* @kbx81 | ||||
| esphome/components/st7920/* @marsjan155 | ||||
| esphome/components/statsd/* @Links2004 | ||||
| esphome/components/substitutions/* @esphome/core | ||||
| esphome/components/sun/* @OttoWinter | ||||
| esphome/components/sun_gtil2/* @Mat931 | ||||
| esphome/components/switch/* @esphome/core | ||||
| esphome/components/t6615/* @tylermenezes | ||||
| esphome/components/tc74/* @sethgirvan | ||||
| esphome/components/tca9548a/* @andreashergert1984 | ||||
| esphome/components/tca9555/* @mobrembski | ||||
| esphome/components/tcl112/* @glmnet | ||||
| esphome/components/tee501/* @Stock-M | ||||
| esphome/components/teleinfo/* @0hax | ||||
| esphome/components/tem3200/* @bakerkj | ||||
| esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar | ||||
| esphome/components/template/datetime/* @rfdarter | ||||
| esphome/components/template/event/* @nohat | ||||
| @@ -420,6 +452,7 @@ esphome/components/tuya/switch/* @jesserockz | ||||
| esphome/components/tuya/text_sensor/* @dentra | ||||
| esphome/components/uart/* @esphome/core | ||||
| esphome/components/uart/button/* @ssieb | ||||
| esphome/components/udp/* @clydebarrow | ||||
| esphome/components/ufire_ec/* @pvizeli | ||||
| esphome/components/ufire_ise/* @pvizeli | ||||
| esphome/components/ultrasonic/* @OttoWinter | ||||
| @@ -432,6 +465,7 @@ esphome/components/veml7700/* @latonita | ||||
| esphome/components/version/* @esphome/core | ||||
| esphome/components/voice_assistant/* @jesserockz | ||||
| esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 | ||||
| esphome/components/watchdog/* @oarcher | ||||
| esphome/components/waveshare_epaper/* @clydebarrow | ||||
| esphome/components/web_server_base/* @OttoWinter | ||||
| esphome/components/web_server_idf/* @dentra | ||||
| @@ -454,6 +488,7 @@ esphome/components/wl_134/* @hobbypunk90 | ||||
| esphome/components/x9c/* @EtienneMD | ||||
| esphome/components/xgzp68xx/* @gcormier | ||||
| esphome/components/xiaomi_hhccjcy10/* @fariouche | ||||
| esphome/components/xiaomi_lywsd02mmc/* @juanluss31 | ||||
| esphome/components/xiaomi_lywsd03mmc/* @ahpohl | ||||
| esphome/components/xiaomi_mhoc303/* @drug123 | ||||
| esphome/components/xiaomi_mhoc401/* @vevsvevs | ||||
|   | ||||
| @@ -7,3 +7,5 @@ | ||||
| For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues). | ||||
|  | ||||
| For feature requests, please see [feature requests](https://github.com/esphome/feature-requests/issues). | ||||
|  | ||||
| [](https://www.openhomefoundation.org/) | ||||
|   | ||||
| @@ -33,9 +33,9 @@ RUN \ | ||||
|         python3-venv=3.11.2-1+b1 \ | ||||
|         python3-wheel=0.38.4-2 \ | ||||
|         iputils-ping=3:20221126-1 \ | ||||
|         git=1:2.39.2-1.1 \ | ||||
|         curl=7.88.1-10+deb12u6 \ | ||||
|         openssh-client=1:9.2p1-2+deb12u2 \ | ||||
|         git=1:2.39.5-0+deb12u1 \ | ||||
|         curl=7.88.1-10+deb12u7 \ | ||||
|         openssh-client=1:9.2p1-2+deb12u3 \ | ||||
|         python3-cffi=1.15.1-5 \ | ||||
|         libcairo2=1.16.0-7 \ | ||||
|         libmagic1=1:5.44-3 \ | ||||
| @@ -49,7 +49,7 @@ RUN \ | ||||
|                 zlib1g-dev=1:1.2.13.dfsg-1 \ | ||||
|                 libjpeg-dev=1:2.1.5-2 \ | ||||
|                 libfreetype-dev=2.12.1+dfsg-5+deb12u3 \ | ||||
|                 libssl-dev=3.0.13-1~deb12u1 \ | ||||
|                 libssl-dev=3.0.14-1~deb12u2 \ | ||||
|                 libffi-dev=3.4.4-1 \ | ||||
|                 libopenjp2-7=2.5.0-2 \ | ||||
|                 libtiff6=4.5.0-6+deb12u1 \ | ||||
| @@ -86,7 +86,7 @@ RUN \ | ||||
|     pip3 install \ | ||||
|     --break-system-packages --no-cache-dir \ | ||||
|     # Keep platformio version in sync with requirements.txt | ||||
|     platformio==6.1.15 \ | ||||
|     platformio==6.1.16 \ | ||||
|     # Change some platformio settings | ||||
|     && platformio settings set enable_telemetry No \ | ||||
|     && platformio settings set check_platformio_interval 1000000 \ | ||||
| @@ -96,14 +96,19 @@ RUN \ | ||||
| # First install requirements to leverage caching when requirements don't change | ||||
| # tmpfs is for https://github.com/rust-lang/cargo/issues/8719 | ||||
|  | ||||
| COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / | ||||
| COPY requirements.txt requirements_optional.txt / | ||||
| RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ | ||||
|         export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ | ||||
|         curl -L https://www.piwheels.org/cp311/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl -o /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ | ||||
|         && pip3 install --break-system-packages --no-cache-dir /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ | ||||
|         && rm /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ | ||||
|         && export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ | ||||
|     fi; \ | ||||
|     CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \ | ||||
|     pip3 install \ | ||||
|     --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini --libraries | ||||
|     --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt | ||||
|  | ||||
| COPY script/platformio_install_deps.py platformio.ini / | ||||
| RUN /platformio_install_deps.py /platformio.ini --libraries | ||||
|  | ||||
| # Avoid unsafe git error when container user and file config volume permissions don't match | ||||
| RUN git config --system --add safe.directory '*' | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| #!/bin/bash | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| # If /cache is mounted, use that as PIO's coredir | ||||
| # otherwise use path in /config (so that PIO packages aren't downloaded on each compile) | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| # PYTHON_ARGCOMPLETE_OK | ||||
| import argparse | ||||
| from datetime import datetime | ||||
| import functools | ||||
| import logging | ||||
| import os | ||||
| import re | ||||
| import sys | ||||
| import time | ||||
| from datetime import datetime | ||||
|  | ||||
| import argcomplete | ||||
|  | ||||
| @@ -38,15 +38,15 @@ from esphome.const import ( | ||||
|     SECRETS_FILES, | ||||
| ) | ||||
| from esphome.core import CORE, EsphomeError, coroutine | ||||
| from esphome.helpers import indent, is_ip_address | ||||
| from esphome.helpers import indent, is_ip_address, get_bool_env | ||||
| from esphome.log import Fore, color, setup_log | ||||
| from esphome.util import ( | ||||
|     get_serial_ports, | ||||
|     list_yaml_files, | ||||
|     run_external_command, | ||||
|     run_external_process, | ||||
|     safe_print, | ||||
|     list_yaml_files, | ||||
|     get_serial_ports, | ||||
| ) | ||||
| from esphome.log import color, setup_log, Fore | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -116,6 +116,7 @@ def get_port_type(port): | ||||
|  | ||||
| def run_miniterm(config, port): | ||||
|     import serial | ||||
|  | ||||
|     from esphome import platformio_api | ||||
|  | ||||
|     if CONF_LOGGER not in config: | ||||
| @@ -596,9 +597,10 @@ def command_update_all(args): | ||||
|  | ||||
|  | ||||
| def command_idedata(args, config): | ||||
|     from esphome import platformio_api | ||||
|     import json | ||||
|  | ||||
|     from esphome import platformio_api | ||||
|  | ||||
|     logging.disable(logging.INFO) | ||||
|     logging.disable(logging.WARNING) | ||||
|  | ||||
| @@ -729,7 +731,11 @@ POST_CONFIG_ACTIONS = { | ||||
| def parse_args(argv): | ||||
|     options_parser = argparse.ArgumentParser(add_help=False) | ||||
|     options_parser.add_argument( | ||||
|         "-v", "--verbose", help="Enable verbose ESPHome logs.", action="store_true" | ||||
|         "-v", | ||||
|         "--verbose", | ||||
|         help="Enable verbose ESPHome logs.", | ||||
|         action="store_true", | ||||
|         default=get_bool_env("ESPHOME_VERBOSE"), | ||||
|     ) | ||||
|     options_parser.add_argument( | ||||
|         "-q", "--quiet", help="Disable all ESPHome logs.", action="store_true" | ||||
| @@ -747,7 +753,14 @@ def parse_args(argv): | ||||
|     ) | ||||
|  | ||||
|     parser = argparse.ArgumentParser( | ||||
|         description=f"ESPHome v{const.__version__}", parents=[options_parser] | ||||
|         description=f"ESPHome {const.__version__}", parents=[options_parser] | ||||
|     ) | ||||
|  | ||||
|     parser.add_argument( | ||||
|         "--version", | ||||
|         action="version", | ||||
|         version=f"Version: {const.__version__}", | ||||
|         help="Print the ESPHome version and exit.", | ||||
|     ) | ||||
|  | ||||
|     mqtt_options = argparse.ArgumentParser(add_help=False) | ||||
| @@ -948,67 +961,6 @@ def parse_args(argv): | ||||
|     # a deprecation warning). | ||||
|     arguments = argv[1:] | ||||
|  | ||||
|     # On Python 3.9+ we can simply set exit_on_error=False in the constructor | ||||
|     def _raise(x): | ||||
|         raise argparse.ArgumentError(None, x) | ||||
|  | ||||
|     # First, try new-style parsing, but don't exit in case of failure | ||||
|     try: | ||||
|         # duplicate parser so that we can use the original one to raise errors later on | ||||
|         current_parser = argparse.ArgumentParser(add_help=False, parents=[parser]) | ||||
|         current_parser.set_defaults(deprecated_argv_suggestion=None) | ||||
|         current_parser.error = _raise | ||||
|         return current_parser.parse_args(arguments) | ||||
|     except argparse.ArgumentError: | ||||
|         pass | ||||
|  | ||||
|     # Second, try compat parsing and rearrange the command-line if it succeeds | ||||
|     # Disable argparse's built-in help option and add it manually to prevent this | ||||
|     # parser from printing the help messagefor the old format when invoked with -h. | ||||
|     compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False) | ||||
|     compat_parser.add_argument("-h", "--help", action="store_true") | ||||
|     compat_parser.add_argument("configuration", nargs="*") | ||||
|     compat_parser.add_argument( | ||||
|         "command", | ||||
|         choices=[ | ||||
|             "config", | ||||
|             "compile", | ||||
|             "upload", | ||||
|             "logs", | ||||
|             "run", | ||||
|             "clean-mqtt", | ||||
|             "wizard", | ||||
|             "mqtt-fingerprint", | ||||
|             "version", | ||||
|             "clean", | ||||
|             "dashboard", | ||||
|             "vscode", | ||||
|             "update-all", | ||||
|         ], | ||||
|     ) | ||||
|  | ||||
|     try: | ||||
|         compat_parser.error = _raise | ||||
|         result, unparsed = compat_parser.parse_known_args(argv[1:]) | ||||
|         last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration) | ||||
|         unparsed = [ | ||||
|             "--device" if arg in ("--upload-port", "--serial-port") else arg | ||||
|             for arg in unparsed | ||||
|         ] | ||||
|         arguments = ( | ||||
|             arguments[0:last_option] | ||||
|             + [result.command] | ||||
|             + result.configuration | ||||
|             + unparsed | ||||
|         ) | ||||
|         deprecated_argv_suggestion = arguments | ||||
|     except argparse.ArgumentError: | ||||
|         # old-style parsing failed, don't suggest any argument | ||||
|         deprecated_argv_suggestion = None | ||||
|  | ||||
|     # Finally, run the new-style parser again with the possibly swapped arguments, | ||||
|     # and let it error out if the command is unparsable. | ||||
|     parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion) | ||||
|     argcomplete.autocomplete(parser) | ||||
|     return parser.parse_args(arguments) | ||||
|  | ||||
| @@ -1023,20 +975,6 @@ def run_esphome(argv): | ||||
|         # Show timestamp for dashboard access logs | ||||
|         args.command == "dashboard", | ||||
|     ) | ||||
|     if args.deprecated_argv_suggestion is not None and args.command != "vscode": | ||||
|         _LOGGER.warning( | ||||
|             "Calling ESPHome with the configuration before the command is deprecated " | ||||
|             "and will be removed in the future. " | ||||
|         ) | ||||
|         _LOGGER.warning("Please instead use:") | ||||
|         _LOGGER.warning("   esphome %s", " ".join(args.deprecated_argv_suggestion)) | ||||
|  | ||||
|     if sys.version_info < (3, 8, 0): | ||||
|         _LOGGER.error( | ||||
|             "You're running ESPHome with Python <3.8. ESPHome is no longer compatible " | ||||
|             "with this Python version. Please reinstall ESPHome with Python 3.8+" | ||||
|         ) | ||||
|         return 1 | ||||
|  | ||||
|     if args.command in PRE_CONFIG_ACTIONS: | ||||
|         try: | ||||
|   | ||||
| @@ -1,16 +1,18 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ALL, | ||||
|     CONF_ANY, | ||||
|     CONF_AUTOMATION_ID, | ||||
|     CONF_CONDITION, | ||||
|     CONF_COUNT, | ||||
|     CONF_ELSE, | ||||
|     CONF_ID, | ||||
|     CONF_THEN, | ||||
|     CONF_TIME, | ||||
|     CONF_TIMEOUT, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_TYPE_ID, | ||||
|     CONF_TIME, | ||||
|     CONF_UPDATE_INTERVAL, | ||||
| ) | ||||
| from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor | ||||
| @@ -73,6 +75,13 @@ def validate_potentially_and_condition(value): | ||||
|     return validate_condition(value) | ||||
|  | ||||
|  | ||||
| def validate_potentially_or_condition(value): | ||||
|     if isinstance(value, list): | ||||
|         with cv.remove_prepend_path(["or"]): | ||||
|             return validate_condition({"or": value}) | ||||
|     return validate_condition(value) | ||||
|  | ||||
|  | ||||
| DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) | ||||
| LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) | ||||
| IfAction = cg.esphome_ns.class_("IfAction", Action) | ||||
| @@ -166,6 +175,18 @@ async def or_condition_to_code(config, condition_id, template_arg, args): | ||||
|     return cg.new_Pvariable(condition_id, template_arg, conditions) | ||||
|  | ||||
|  | ||||
| @register_condition("all", AndCondition, validate_condition_list) | ||||
| async def all_condition_to_code(config, condition_id, template_arg, args): | ||||
|     conditions = await build_condition_list(config, template_arg, args) | ||||
|     return cg.new_Pvariable(condition_id, template_arg, conditions) | ||||
|  | ||||
|  | ||||
| @register_condition("any", OrCondition, validate_condition_list) | ||||
| async def any_condition_to_code(config, condition_id, template_arg, args): | ||||
|     conditions = await build_condition_list(config, template_arg, args) | ||||
|     return cg.new_Pvariable(condition_id, template_arg, conditions) | ||||
|  | ||||
|  | ||||
| @register_condition("not", NotCondition, validate_potentially_and_condition) | ||||
| async def not_condition_to_code(config, condition_id, template_arg, args): | ||||
|     condition = await build_condition(config, template_arg, args) | ||||
| @@ -223,15 +244,21 @@ async def delay_action_to_code(config, action_id, template_arg, args): | ||||
|     IfAction, | ||||
|     cv.All( | ||||
|         { | ||||
|             cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
|             cv.Exclusive( | ||||
|                 CONF_CONDITION, CONF_CONDITION | ||||
|             ): validate_potentially_and_condition, | ||||
|             cv.Exclusive(CONF_ANY, CONF_CONDITION): validate_potentially_or_condition, | ||||
|             cv.Exclusive(CONF_ALL, CONF_CONDITION): validate_potentially_and_condition, | ||||
|             cv.Optional(CONF_THEN): validate_action_list, | ||||
|             cv.Optional(CONF_ELSE): validate_action_list, | ||||
|         }, | ||||
|         cv.has_at_least_one_key(CONF_THEN, CONF_ELSE), | ||||
|         cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL), | ||||
|     ), | ||||
| ) | ||||
| async def if_action_to_code(config, action_id, template_arg, args): | ||||
|     conditions = await build_condition(config[CONF_CONDITION], template_arg, args) | ||||
|     cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION)) | ||||
|     conditions = await build_condition(config[cond_conf], template_arg, args) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||
|     if CONF_THEN in config: | ||||
|         actions = await build_action_list(config[CONF_THEN], template_arg, args) | ||||
|   | ||||
| @@ -8,55 +8,78 @@ | ||||
| # want to break suddenly due to a rename (this file will get backports for features). | ||||
|  | ||||
| # pylint: disable=unused-import | ||||
| from esphome.cpp_generator import (  # noqa | ||||
| from esphome.cpp_generator import (  # noqa: F401 | ||||
|     ArrayInitializer, | ||||
|     Expression, | ||||
|     LineComment, | ||||
|     MockObj, | ||||
|     MockObjClass, | ||||
|     Pvariable, | ||||
|     RawExpression, | ||||
|     RawStatement, | ||||
|     TemplateArguments, | ||||
|     StructInitializer, | ||||
|     ArrayInitializer, | ||||
|     safe_exp, | ||||
|     Statement, | ||||
|     LineComment, | ||||
|     progmem_array, | ||||
|     static_const_array, | ||||
|     statement, | ||||
|     variable, | ||||
|     with_local_variable, | ||||
|     new_variable, | ||||
|     Pvariable, | ||||
|     new_Pvariable, | ||||
|     StructInitializer, | ||||
|     TemplateArguments, | ||||
|     add, | ||||
|     add_global, | ||||
|     add_library, | ||||
|     add_build_flag, | ||||
|     add_define, | ||||
|     add_global, | ||||
|     add_library, | ||||
|     add_platformio_option, | ||||
|     get_variable, | ||||
|     get_variable_with_full_id, | ||||
|     process_lambda, | ||||
|     is_template, | ||||
|     new_Pvariable, | ||||
|     new_variable, | ||||
|     process_lambda, | ||||
|     progmem_array, | ||||
|     safe_exp, | ||||
|     statement, | ||||
|     static_const_array, | ||||
|     templatable, | ||||
|     MockObj, | ||||
|     MockObjClass, | ||||
|     variable, | ||||
|     with_local_variable, | ||||
| ) | ||||
| from esphome.cpp_helpers import (  # noqa | ||||
|     gpio_pin_expression, | ||||
|     register_component, | ||||
| from esphome.cpp_helpers import (  # noqa: F401 | ||||
|     build_registry_entry, | ||||
|     build_registry_list, | ||||
|     extract_registry_entry_config, | ||||
|     register_parented, | ||||
|     gpio_pin_expression, | ||||
|     past_safe_mode, | ||||
|     register_component, | ||||
|     register_parented, | ||||
| ) | ||||
| from esphome.cpp_types import (  # noqa | ||||
|     global_ns, | ||||
|     void, | ||||
|     nullptr, | ||||
|     float_, | ||||
|     double, | ||||
| from esphome.cpp_types import (  # noqa: F401 | ||||
|     NAN, | ||||
|     App, | ||||
|     Application, | ||||
|     Component, | ||||
|     ComponentPtr, | ||||
|     Controller, | ||||
|     EntityBase, | ||||
|     EntityCategory, | ||||
|     ESPTime, | ||||
|     GPIOPin, | ||||
|     InternalGPIOPin, | ||||
|     JsonObject, | ||||
|     JsonObjectConst, | ||||
|     Parented, | ||||
|     PollingComponent, | ||||
|     arduino_json_ns, | ||||
|     bool_, | ||||
|     const_char_ptr, | ||||
|     double, | ||||
|     esphome_ns, | ||||
|     float_, | ||||
|     global_ns, | ||||
|     gpio_Flags, | ||||
|     int16, | ||||
|     int32, | ||||
|     int64, | ||||
|     int_, | ||||
|     nullptr, | ||||
|     optional, | ||||
|     size_t, | ||||
|     std_ns, | ||||
|     std_shared_ptr, | ||||
|     std_string, | ||||
| @@ -66,28 +89,5 @@ from esphome.cpp_types import (  # noqa | ||||
|     uint16, | ||||
|     uint32, | ||||
|     uint64, | ||||
|     int16, | ||||
|     int32, | ||||
|     int64, | ||||
|     size_t, | ||||
|     const_char_ptr, | ||||
|     NAN, | ||||
|     esphome_ns, | ||||
|     App, | ||||
|     EntityBase, | ||||
|     Component, | ||||
|     ComponentPtr, | ||||
|     PollingComponent, | ||||
|     Application, | ||||
|     optional, | ||||
|     arduino_json_ns, | ||||
|     JsonObject, | ||||
|     JsonObjectConst, | ||||
|     Controller, | ||||
|     GPIOPin, | ||||
|     InternalGPIOPin, | ||||
|     gpio_Flags, | ||||
|     EntityCategory, | ||||
|     Parented, | ||||
|     ESPTime, | ||||
|     void, | ||||
| ) | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import esphome.config_validation as cv | ||||
|  | ||||
| CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( | ||||
| CONFIG_SCHEMA = cv.invalid( | ||||
|     "The ade7953 sensor component has been renamed to ade7953_i2c." | ||||
| ) | ||||
|   | ||||
| @@ -60,7 +60,7 @@ bool AdE7953Spi::ade_read_16(uint16_t reg, uint16_t *value) { | ||||
|   this->write_byte16(reg); | ||||
|   this->transfer_byte(0x80); | ||||
|   uint8_t recv[2]; | ||||
|   this->read_array(recv, 4); | ||||
|   this->read_array(recv, 2); | ||||
|   *value = encode_uint16(recv[0], recv[1]); | ||||
|   this->disable(); | ||||
|   return false; | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/aic3204/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/aic3204/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										173
									
								
								esphome/components/aic3204/aic3204.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								esphome/components/aic3204/aic3204.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| #include "aic3204.h" | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace aic3204 { | ||||
|  | ||||
| static const char *const TAG = "aic3204"; | ||||
|  | ||||
| #define ERROR_CHECK(err, msg) \ | ||||
|   if (!(err)) { \ | ||||
|     ESP_LOGE(TAG, msg); \ | ||||
|     this->mark_failed(); \ | ||||
|     return; \ | ||||
|   } | ||||
|  | ||||
| void AIC3204::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up AIC3204..."); | ||||
|  | ||||
|   // Set register page to 0 | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed"); | ||||
|   // Initiate SW reset (PLL is powered off as part of reset) | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_SW_RST, 0x01), "Software reset failed"); | ||||
|   // *** Program clock settings *** | ||||
|   // Default is CODEC_CLKIN is from MCLK pin. Don't need to change this. | ||||
|   // MDAC*NDAC*FOSR*48Khz = mClk (24.576 MHz when the XMOS is expecting 48kHz audio) | ||||
|   // (See page 51 of https://www.ti.com/lit/ml/slaa557/slaa557.pdf) | ||||
|   // We do need MDAC*DOSR/32 >= the resource compute level for the processing block | ||||
|   // So here 2*128/32 = 8, which is equal to processing block 1 's resource compute | ||||
|   // See page 5 of https://www.ti.com/lit/an/slaa404c/slaa404c.pdf for the workflow | ||||
|   // for determining these settings. | ||||
|  | ||||
|   // Power up NDAC and set to 2 | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_NDAC, 0x82), "Set NDAC failed"); | ||||
|   // Power up MDAC and set to 2 | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_MDAC, 0x82), "Set MDAC failed"); | ||||
|   // Program DOSR = 128 | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_DOSR, 0x80), "Set DOSR failed"); | ||||
|   // Set Audio Interface Config: I2S, 32 bits, DOUT always driving | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_CODEC_IF, 0x30), "Set CODEC_IF failed"); | ||||
|   // For I2S Firmware only, set SCLK/MFP3 pin as Audio Data In | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_SCLK_MFP3, 0x02), "Set SCLK/MFP3 failed"); | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_AUDIO_IF_4, 0x01), "Set AUDIO_IF_4 failed"); | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_AUDIO_IF_5, 0x01), "Set AUDIO_IF_5 failed"); | ||||
|   // Program the DAC processing block to be used - PRB_P1 | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_DAC_SIG_PROC, 0x01), "Set DAC_SIG_PROC failed"); | ||||
|  | ||||
|   // *** Select Page 1 *** | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x01), "Set page 1 failed"); | ||||
|   // Enable the internal AVDD_LDO: | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_LDO_CTRL, 0x09), "Set LDO_CTRL failed"); | ||||
|   // *** Program Analog Blocks *** | ||||
|   // Disable Internal Crude AVdd in presence of external AVdd supply or before powering up internal AVdd LDO | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_PWR_CFG, 0x08), "Set PWR_CFG failed"); | ||||
|   // Enable Master Analog Power Control | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_LDO_CTRL, 0x01), "Set LDO_CTRL failed"); | ||||
|   // Page 125: Common mode control register, set d6 to 1 to make the full chip common mode = 0.75 v | ||||
|   // We are using the internal AVdd regulator with a nominal output of 1.72 V (see LDO_CTRL_REGISTER on page 123) | ||||
|   // Page 86 says to only set the common mode voltage to 0.9 v if AVdd >= 1.8... but it isn't on our hardware | ||||
|   // We also adjust the HPL and HPR gains to -2dB gian later in this config flow compensate (see page 47) | ||||
|   // (All pages refer to the TLV320AIC3204 Application Reference Guide) | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_CM_CTRL, 0x40), "Set CM_CTRL failed"); | ||||
|   // *** Set PowerTune Modes *** | ||||
|   // Set the Left & Right DAC PowerTune mode to PTM_P3/4. Use Class-AB driver. | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_PLAY_CFG1, 0x00), "Set PLAY_CFG1 failed"); | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_PLAY_CFG2, 0x00), "Set PLAY_CFG2 failed"); | ||||
|   // Set the REF charging time to 40ms | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_REF_STARTUP, 0x01), "Set REF_STARTUP failed"); | ||||
|   // HP soft stepping settings for optimal pop performance at power up | ||||
|   // Rpop used is 6k with N = 6 and soft step = 20usec. This should work with 47uF coupling | ||||
|   // capacitor. Can try N=5,6 or 7 time constants as well. Trade-off delay vs “pop” sound. | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_HP_START, 0x25), "Set HP_START failed"); | ||||
|   // Route Left DAC to HPL | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_HPL_ROUTE, 0x08), "Set HPL_ROUTE failed"); | ||||
|   // Route Right DAC to HPR | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_HPR_ROUTE, 0x08), "Set HPR_ROUTE failed"); | ||||
|   // Route Left DAC to LOL | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_LOL_ROUTE, 0x08), "Set LOL_ROUTE failed"); | ||||
|   // Route Right DAC to LOR | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_LOR_ROUTE, 0x08), "Set LOR_ROUTE failed"); | ||||
|  | ||||
|   // Unmute HPL and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register) | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_HPL_GAIN, 0x3e), "Set HPL_GAIN failed"); | ||||
|   // Unmute HPR and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register) | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_HPR_GAIN, 0x3e), "Set HPR_GAIN failed"); | ||||
|   // Unmute LOL and set gain to 0dB | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_LOL_DRV_GAIN, 0x00), "Set LOL_DRV_GAIN failed"); | ||||
|   // Unmute LOR and set gain to 0dB | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_LOR_DRV_GAIN, 0x00), "Set LOR_DRV_GAIN failed"); | ||||
|  | ||||
|   // Power up HPL and HPR, LOL and LOR drivers | ||||
|   ERROR_CHECK(this->write_byte(AIC3204_OP_PWR_CTRL, 0x3C), "Set OP_PWR_CTRL failed"); | ||||
|  | ||||
|   // Wait for 2.5 sec for soft stepping to take effect before attempting power-up | ||||
|   this->set_timeout(2500, [this]() { | ||||
|     // *** Power Up DAC *** | ||||
|     // Select Page 0 | ||||
|     ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set PAGE_CTRL failed"); | ||||
|     // Power up the Left and Right DAC Channels. Route Left data to Left DAC and Right data to Right DAC. | ||||
|     // DAC Vol control soft step 1 step per DAC word clock. | ||||
|     ERROR_CHECK(this->write_byte(AIC3204_DAC_CH_SET1, 0xd4), "Set DAC_CH_SET1 failed"); | ||||
|     // Set left and right DAC digital volume control | ||||
|     ERROR_CHECK(this->write_volume_(), "Set volume failed"); | ||||
|     // Unmute left and right channels | ||||
|     ERROR_CHECK(this->write_mute_(), "Set mute failed"); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| void AIC3204::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "AIC3204:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|  | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, "Communication with AIC3204 failed"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool AIC3204::set_mute_off() { | ||||
|   this->is_muted_ = false; | ||||
|   return this->write_mute_(); | ||||
| } | ||||
|  | ||||
| bool AIC3204::set_mute_on() { | ||||
|   this->is_muted_ = true; | ||||
|   return this->write_mute_(); | ||||
| } | ||||
|  | ||||
| bool AIC3204::set_auto_mute_mode(uint8_t auto_mute_mode) { | ||||
|   this->auto_mute_mode_ = auto_mute_mode & 0x07; | ||||
|   ESP_LOGVV(TAG, "Setting auto_mute_mode to 0x%.2x", this->auto_mute_mode_); | ||||
|   return this->write_mute_(); | ||||
| } | ||||
|  | ||||
| bool AIC3204::set_volume(float volume) { | ||||
|   this->volume_ = clamp<float>(volume, 0.0, 1.0); | ||||
|   return this->write_volume_(); | ||||
| } | ||||
|  | ||||
| bool AIC3204::is_muted() { return this->is_muted_; } | ||||
|  | ||||
| float AIC3204::volume() { return this->volume_; } | ||||
|  | ||||
| bool AIC3204::write_mute_() { | ||||
|   uint8_t mute_mode_byte = this->auto_mute_mode_ << 4;  // auto-mute control is bits 4-6 | ||||
|   mute_mode_byte |= this->is_muted_ ? 0x0c : 0x00;      // mute bits are 2-3 | ||||
|   if (!this->write_byte(AIC3204_PAGE_CTRL, 0x00) || !this->write_byte(AIC3204_DAC_CH_SET2, mute_mode_byte)) { | ||||
|     ESP_LOGE(TAG, "Writing mute modes failed"); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool AIC3204::write_volume_() { | ||||
|   const int8_t dvc_min_byte = -127; | ||||
|   const int8_t dvc_max_byte = 48; | ||||
|  | ||||
|   int8_t volume_byte = dvc_min_byte + (this->volume_ * (dvc_max_byte - dvc_min_byte)); | ||||
|   volume_byte = clamp<int8_t>(volume_byte, dvc_min_byte, dvc_max_byte); | ||||
|  | ||||
|   ESP_LOGVV(TAG, "Setting volume to 0x%.2x", volume_byte & 0xFF); | ||||
|  | ||||
|   if ((!this->write_byte(AIC3204_PAGE_CTRL, 0x00)) || (!this->write_byte(AIC3204_DACL_VOL_D, volume_byte)) || | ||||
|       (!this->write_byte(AIC3204_DACR_VOL_D, volume_byte))) { | ||||
|     ESP_LOGE(TAG, "Writing volume failed"); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace aic3204 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										88
									
								
								esphome/components/aic3204/aic3204.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								esphome/components/aic3204/aic3204.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/audio_dac/audio_dac.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace aic3204 { | ||||
|  | ||||
| // TLV320AIC3204 Register Addresses | ||||
| // Page 0 | ||||
| static const uint8_t AIC3204_PAGE_CTRL = 0x00;     // Register 0  - Page Control | ||||
| static const uint8_t AIC3204_SW_RST = 0x01;        // Register 1  - Software Reset | ||||
| static const uint8_t AIC3204_CLK_PLL1 = 0x04;      // Register 4  - Clock Setting Register 1, Multiplexers | ||||
| static const uint8_t AIC3204_CLK_PLL2 = 0x05;      // Register 5  - Clock Setting Register 2, P and R values | ||||
| static const uint8_t AIC3204_CLK_PLL3 = 0x06;      // Register 6  - Clock Setting Register 3, J values | ||||
| static const uint8_t AIC3204_NDAC = 0x0B;          // Register 11 - NDAC Divider Value | ||||
| static const uint8_t AIC3204_MDAC = 0x0C;          // Register 12 - MDAC Divider Value | ||||
| static const uint8_t AIC3204_DOSR = 0x0E;          // Register 14 - DOSR Divider Value (LS Byte) | ||||
| static const uint8_t AIC3204_NADC = 0x12;          // Register 18 - NADC Divider Value | ||||
| static const uint8_t AIC3204_MADC = 0x13;          // Register 19 - MADC Divider Value | ||||
| static const uint8_t AIC3204_AOSR = 0x14;          // Register 20 - AOSR Divider Value | ||||
| static const uint8_t AIC3204_CODEC_IF = 0x1B;      // Register 27 - CODEC Interface Control | ||||
| static const uint8_t AIC3204_AUDIO_IF_4 = 0x1F;    // Register 31 - Audio Interface Setting Register 4 | ||||
| static const uint8_t AIC3204_AUDIO_IF_5 = 0x20;    // Register 32 - Audio Interface Setting Register 5 | ||||
| static const uint8_t AIC3204_SCLK_MFP3 = 0x38;     // Register 56 - SCLK/MFP3 Function Control | ||||
| static const uint8_t AIC3204_DAC_SIG_PROC = 0x3C;  // Register 60 - DAC Sig Processing Block Control | ||||
| static const uint8_t AIC3204_ADC_SIG_PROC = 0x3D;  // Register 61 - ADC Sig Processing Block Control | ||||
| static const uint8_t AIC3204_DAC_CH_SET1 = 0x3F;   // Register 63 - DAC Channel Setup 1 | ||||
| static const uint8_t AIC3204_DAC_CH_SET2 = 0x40;   // Register 64 - DAC Channel Setup 2 | ||||
| static const uint8_t AIC3204_DACL_VOL_D = 0x41;    // Register 65 - DAC Left Digital Vol Control | ||||
| static const uint8_t AIC3204_DACR_VOL_D = 0x42;    // Register 66 - DAC Right Digital Vol Control | ||||
| static const uint8_t AIC3204_DRC_ENABLE = 0x44; | ||||
| static const uint8_t AIC3204_ADC_CH_SET = 0x51;    // Register 81 - ADC Channel Setup | ||||
| static const uint8_t AIC3204_ADC_FGA_MUTE = 0x52;  // Register 82 - ADC Fine Gain Adjust/Mute | ||||
|  | ||||
| // Page 1 | ||||
| static const uint8_t AIC3204_PWR_CFG = 0x01;       // Register 1  - Power Config | ||||
| static const uint8_t AIC3204_LDO_CTRL = 0x02;      // Register 2  - LDO Control | ||||
| static const uint8_t AIC3204_PLAY_CFG1 = 0x03;     // Register 3  - Playback Config 1 | ||||
| static const uint8_t AIC3204_PLAY_CFG2 = 0x04;     // Register 4  - Playback Config 2 | ||||
| static const uint8_t AIC3204_OP_PWR_CTRL = 0x09;   // Register 9  - Output Driver Power Control | ||||
| static const uint8_t AIC3204_CM_CTRL = 0x0A;       // Register 10 - Common Mode Control | ||||
| static const uint8_t AIC3204_HPL_ROUTE = 0x0C;     // Register 12 - HPL Routing Select | ||||
| static const uint8_t AIC3204_HPR_ROUTE = 0x0D;     // Register 13 - HPR Routing Select | ||||
| static const uint8_t AIC3204_LOL_ROUTE = 0x0E;     // Register 14 - LOL Routing Selection | ||||
| static const uint8_t AIC3204_LOR_ROUTE = 0x0F;     // Register 15 - LOR Routing Selection | ||||
| static const uint8_t AIC3204_HPL_GAIN = 0x10;      // Register 16 - HPL Driver Gain | ||||
| static const uint8_t AIC3204_HPR_GAIN = 0x11;      // Register 17 - HPR Driver Gain | ||||
| static const uint8_t AIC3204_LOL_DRV_GAIN = 0x12;  // Register 18 - LOL Driver Gain Setting | ||||
| static const uint8_t AIC3204_LOR_DRV_GAIN = 0x13;  // Register 19 - LOR Driver Gain Setting | ||||
| static const uint8_t AIC3204_HP_START = 0x14;      // Register 20 - Headphone Driver Startup | ||||
| static const uint8_t AIC3204_LPGA_P_ROUTE = 0x34;  // Register 52 - Left PGA Positive Input Route | ||||
| static const uint8_t AIC3204_LPGA_N_ROUTE = 0x36;  // Register 54 - Left PGA Negative Input Route | ||||
| static const uint8_t AIC3204_RPGA_P_ROUTE = 0x37;  // Register 55 - Right PGA Positive Input Route | ||||
| static const uint8_t AIC3204_RPGA_N_ROUTE = 0x39;  // Register 57 - Right PGA Negative Input Route | ||||
| static const uint8_t AIC3204_LPGA_VOL = 0x3B;      // Register 59 - Left PGA Volume | ||||
| static const uint8_t AIC3204_RPGA_VOL = 0x3C;      // Register 60 - Right PGA Volume | ||||
| static const uint8_t AIC3204_ADC_PTM = 0x3D;       // Register 61 - ADC Power Tune Config | ||||
| static const uint8_t AIC3204_AN_IN_CHRG = 0x47;    // Register 71 - Analog Input Quick Charging Config | ||||
| static const uint8_t AIC3204_REF_STARTUP = 0x7B;   // Register 123 - Reference Power Up Config | ||||
|  | ||||
| class AIC3204 : public audio_dac::AudioDac, public Component, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   bool set_mute_off() override; | ||||
|   bool set_mute_on() override; | ||||
|   bool set_auto_mute_mode(uint8_t auto_mute_mode); | ||||
|   bool set_volume(float volume) override; | ||||
|  | ||||
|   bool is_muted() override; | ||||
|   float volume() override; | ||||
|  | ||||
|  protected: | ||||
|   bool write_mute_(); | ||||
|   bool write_volume_(); | ||||
|  | ||||
|   uint8_t auto_mute_mode_{0}; | ||||
|   float volume_{0}; | ||||
| }; | ||||
|  | ||||
| }  // namespace aic3204 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										52
									
								
								esphome/components/aic3204/audio_dac.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								esphome/components/aic3204/audio_dac.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| from esphome import automation | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import i2c | ||||
| from esphome.components.audio_dac import AudioDac | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ID, CONF_MODE | ||||
|  | ||||
| CODEOWNERS = ["@kbx81"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| aic3204_ns = cg.esphome_ns.namespace("aic3204") | ||||
| AIC3204 = aic3204_ns.class_("AIC3204", AudioDac, cg.Component, i2c.I2CDevice) | ||||
|  | ||||
| SetAutoMuteAction = aic3204_ns.class_("SetAutoMuteAction", automation.Action) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(AIC3204), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(i2c.i2c_device_schema(0x18)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| SET_AUTO_MUTE_ACTION_SCHEMA = cv.maybe_simple_value( | ||||
|     { | ||||
|         cv.GenerateID(): cv.use_id(AIC3204), | ||||
|         cv.Required(CONF_MODE): cv.templatable(cv.int_range(max=7, min=0)), | ||||
|     }, | ||||
|     key=CONF_MODE, | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "aic3204.set_auto_mute_mode", SetAutoMuteAction, SET_AUTO_MUTE_ACTION_SCHEMA | ||||
| ) | ||||
| async def aic3204_set_volume_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) | ||||
|  | ||||
|     template_ = await cg.templatable(config.get(CONF_MODE), args, int) | ||||
|     cg.add(var.set_auto_mute_mode(template_)) | ||||
|  | ||||
|     return var | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
							
								
								
									
										23
									
								
								esphome/components/aic3204/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/aic3204/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "aic3204.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace aic3204 { | ||||
|  | ||||
| template<typename... Ts> class SetAutoMuteAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit SetAutoMuteAction(AIC3204 *aic3204) : aic3204_(aic3204) {} | ||||
|  | ||||
|   TEMPLATABLE_VALUE(uint8_t, auto_mute_mode) | ||||
|  | ||||
|   void play(Ts... x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); } | ||||
|  | ||||
|  protected: | ||||
|   AIC3204 *aic3204_; | ||||
| }; | ||||
|  | ||||
| }  // namespace aic3204 | ||||
| }  // namespace esphome | ||||
| @@ -14,8 +14,6 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) { | ||||
|     ESP_LOGD(TAG, "version = %d", value->version); | ||||
|  | ||||
|     if (value->version == 1) { | ||||
|       ESP_LOGD(TAG, "ambient light = %d", value->ambientLight); | ||||
|  | ||||
|       if (this->humidity_sensor_ != nullptr) { | ||||
|         this->humidity_sensor_->publish_state(value->humidity / 2.0f); | ||||
|       } | ||||
| @@ -43,6 +41,10 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) { | ||||
|       if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { | ||||
|         this->tvoc_sensor_->publish_state(value->voc); | ||||
|       } | ||||
|  | ||||
|       if (this->illuminance_sensor_ != nullptr) { | ||||
|         this->illuminance_sensor_->publish_state(value->ambientLight); | ||||
|       } | ||||
|     } else { | ||||
|       ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); | ||||
|     } | ||||
| @@ -68,6 +70,7 @@ void AirthingsWavePlus::dump_config() { | ||||
|   LOG_SENSOR("  ", "Radon", this->radon_sensor_); | ||||
|   LOG_SENSOR("  ", "Radon Long Term", this->radon_long_term_sensor_); | ||||
|   LOG_SENSOR("  ", "CO2", this->co2_sensor_); | ||||
|   LOG_SENSOR("  ", "Illuminance", this->illuminance_sensor_); | ||||
| } | ||||
|  | ||||
| AirthingsWavePlus::AirthingsWavePlus() { | ||||
|   | ||||
| @@ -22,6 +22,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { | ||||
|   void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } | ||||
|   void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } | ||||
|   void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } | ||||
|   void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; } | ||||
|  | ||||
|  protected: | ||||
|   bool is_valid_radon_value_(uint16_t radon); | ||||
| @@ -32,6 +33,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { | ||||
|   sensor::Sensor *radon_sensor_{nullptr}; | ||||
|   sensor::Sensor *radon_long_term_sensor_{nullptr}; | ||||
|   sensor::Sensor *co2_sensor_{nullptr}; | ||||
|   sensor::Sensor *illuminance_sensor_{nullptr}; | ||||
|  | ||||
|   struct WavePlusReadings { | ||||
|     uint8_t version; | ||||
|   | ||||
| @@ -12,6 +12,9 @@ from esphome.const import ( | ||||
|     CONF_CO2, | ||||
|     UNIT_BECQUEREL_PER_CUBIC_METER, | ||||
|     UNIT_PARTS_PER_MILLION, | ||||
|     CONF_ILLUMINANCE, | ||||
|     UNIT_LUX, | ||||
|     DEVICE_CLASS_ILLUMINANCE, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = airthings_wave_base.DEPENDENCIES | ||||
| @@ -45,6 +48,12 @@ CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend( | ||||
|             device_class=DEVICE_CLASS_CARBON_DIOXIDE, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|         cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_LUX, | ||||
|             accuracy_decimals=0, | ||||
|             device_class=DEVICE_CLASS_ILLUMINANCE, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| @@ -62,3 +71,6 @@ async def to_code(config): | ||||
|     if config_co2 := config.get(CONF_CO2): | ||||
|         sens = await sensor.new_sensor(config_co2) | ||||
|         cg.add(var.set_co2(sens)) | ||||
|     if config_illuminance := config.get(CONF_ILLUMINANCE): | ||||
|         sens = await sensor.new_sensor(config_illuminance) | ||||
|         cg.add(var.set_illuminance(sens)) | ||||
|   | ||||
| @@ -1,16 +1,17 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import web_server | ||||
| from esphome import automation | ||||
| from esphome.automation import maybe_simple_id | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import mqtt, web_server | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_CODE, | ||||
|     CONF_ID, | ||||
|     CONF_MQTT_ID, | ||||
|     CONF_ON_STATE, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_CODE, | ||||
|     CONF_WEB_SERVER_ID, | ||||
|     CONF_WEB_SERVER, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.cpp_helpers import setup_entity | ||||
|  | ||||
| CODEOWNERS = ["@grahambrown11", "@hwstar"] | ||||
| @@ -77,67 +78,72 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_( | ||||
|     "AlarmControlPanelCondition", automation.Condition | ||||
| ) | ||||
|  | ||||
| ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | ||||
|     web_server.WEBSERVER_SORTING_SCHEMA | ||||
| ).extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(AlarmControlPanel), | ||||
|         cv.Optional(CONF_ON_STATE): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_ARMING): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_PENDING): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_DISARMED): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_CLEARED): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_CHIME): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_READY): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger), | ||||
|             } | ||||
|         ), | ||||
|     } | ||||
| ALARM_CONTROL_PANEL_SCHEMA = ( | ||||
|     cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) | ||||
|     .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(AlarmControlPanel), | ||||
|             cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( | ||||
|                 mqtt.MQTTAlarmControlPanelComponent | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_STATE): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_ARMING): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_PENDING): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_DISARMED): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_CLEARED): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_CHIME): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_READY): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger), | ||||
|                 } | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
| ) | ||||
|  | ||||
| ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id( | ||||
| @@ -189,9 +195,11 @@ async def setup_alarm_control_panel_core_(var, config): | ||||
|     for conf in config.get(CONF_ON_READY, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|     if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: | ||||
|         web_server_ = await cg.get_variable(webserver_id) | ||||
|         web_server.add_entity_to_sorting_list(web_server_, var, config) | ||||
|     if web_server_config := config.get(CONF_WEB_SERVER): | ||||
|         await web_server.add_entity_config(var, web_server_config) | ||||
|     if mqtt_id := config.get(CONF_MQTT_ID): | ||||
|         mqtt_ = cg.new_Pvariable(mqtt_id, var) | ||||
|         await mqtt.register_mqtt_component(mqtt_, config) | ||||
|  | ||||
|  | ||||
| async def register_alarm_control_panel(var, config): | ||||
|   | ||||
| @@ -1,26 +1,26 @@ | ||||
| import logging | ||||
|  | ||||
| from esphome import automation, core | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import font | ||||
| import esphome.components.image as espImage | ||||
| from esphome.components.image import ( | ||||
|     CONF_USE_TRANSPARENCY, | ||||
|     LOCAL_SCHEMA, | ||||
|     WEB_SCHEMA, | ||||
|     SOURCE_WEB, | ||||
|     SOURCE_LOCAL, | ||||
|     SOURCE_WEB, | ||||
|     WEB_SCHEMA, | ||||
| ) | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
| from esphome.const import ( | ||||
|     CONF_FILE, | ||||
|     CONF_ID, | ||||
|     CONF_PATH, | ||||
|     CONF_RAW_DATA_ID, | ||||
|     CONF_REPEAT, | ||||
|     CONF_RESIZE, | ||||
|     CONF_TYPE, | ||||
|     CONF_SOURCE, | ||||
|     CONF_PATH, | ||||
|     CONF_TYPE, | ||||
|     CONF_URL, | ||||
| ) | ||||
| from esphome.core import CORE, HexInt | ||||
| @@ -172,6 +172,9 @@ async def to_code(config): | ||||
|         path = CORE.relative_config_path(conf_file[CONF_PATH]) | ||||
|     elif conf_file[CONF_SOURCE] == SOURCE_WEB: | ||||
|         path = espImage.compute_local_image_path(conf_file).as_posix() | ||||
|     else: | ||||
|         raise core.EsphomeError(f"Unknown animation source: {conf_file[CONF_SOURCE]}") | ||||
|  | ||||
|     try: | ||||
|         image = Image.open(path) | ||||
|     except Exception as e: | ||||
| @@ -183,13 +186,12 @@ async def to_code(config): | ||||
|         new_width_max, new_height_max = config[CONF_RESIZE] | ||||
|         ratio = min(new_width_max / width, new_height_max / height) | ||||
|         width, height = int(width * ratio), int(height * ratio) | ||||
|     else: | ||||
|         if width > 500 or height > 500: | ||||
|             _LOGGER.warning( | ||||
|                 'The image "%s" you requested is very big. Please consider' | ||||
|                 " using the resize parameter.", | ||||
|                 path, | ||||
|             ) | ||||
|     elif width > 500 or height > 500: | ||||
|         _LOGGER.warning( | ||||
|             'The image "%s" you requested is very big. Please consider' | ||||
|             " using the resize parameter.", | ||||
|             path, | ||||
|         ) | ||||
|  | ||||
|     transparent = config[CONF_USE_TRANSPARENCY] | ||||
|  | ||||
| @@ -269,7 +271,8 @@ async def to_code(config): | ||||
|                 pos += 1 | ||||
|  | ||||
|     elif config[CONF_TYPE] in ["RGB565", "TRANSPARENT_IMAGE"]: | ||||
|         data = [0 for _ in range(height * width * 2 * frames)] | ||||
|         bytes_per_pixel = 3 if transparent else 2 | ||||
|         data = [0 for _ in range(height * width * bytes_per_pixel * frames)] | ||||
|         pos = 0 | ||||
|         for frameIndex in range(frames): | ||||
|             image.seek(frameIndex) | ||||
| @@ -286,17 +289,13 @@ async def to_code(config): | ||||
|                 G = g >> 2 | ||||
|                 B = b >> 3 | ||||
|                 rgb = (R << 11) | (G << 5) | B | ||||
|  | ||||
|                 if transparent: | ||||
|                     if rgb == 0x0020: | ||||
|                         rgb = 0 | ||||
|                     if a < 0x80: | ||||
|                         rgb = 0x0020 | ||||
|  | ||||
|                 data[pos] = rgb >> 8 | ||||
|                 pos += 1 | ||||
|                 data[pos] = rgb & 0xFF | ||||
|                 pos += 1 | ||||
|                 if transparent: | ||||
|                     data[pos] = a | ||||
|                     pos += 1 | ||||
|  | ||||
|     elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]: | ||||
|         width8 = ((width + 7) // 8) * 8 | ||||
| @@ -306,6 +305,8 @@ async def to_code(config): | ||||
|             if transparent: | ||||
|                 alpha = image.split()[-1] | ||||
|                 has_alpha = alpha.getextrema()[0] < 0xFF | ||||
|             else: | ||||
|                 has_alpha = False | ||||
|             frame = image.convert("1", dither=Image.Dither.NONE) | ||||
|             if CONF_RESIZE in config: | ||||
|                 frame = frame.resize([width, height]) | ||||
|   | ||||
| @@ -62,7 +62,7 @@ void Animation::set_frame(int frame) { | ||||
| } | ||||
|  | ||||
| void Animation::update_data_start_() { | ||||
|   const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_; | ||||
|   const uint32_t image_size = this->get_width_stride() * this->height_; | ||||
|   this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										4
									
								
								esphome/components/apds9306/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								esphome/components/apds9306/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| # Based on this datasheet: | ||||
| # https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf | ||||
|  | ||||
| CODEOWNERS = ["@aodrenah"] | ||||
							
								
								
									
										151
									
								
								esphome/components/apds9306/apds9306.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								esphome/components/apds9306/apds9306.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| // Based on this datasheet: | ||||
| // https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf | ||||
|  | ||||
| #include "apds9306.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace apds9306 { | ||||
|  | ||||
| static const char *const TAG = "apds9306"; | ||||
|  | ||||
| enum {  // APDS9306 registers | ||||
|   APDS9306_MAIN_CTRL = 0x00, | ||||
|   APDS9306_ALS_MEAS_RATE = 0x04, | ||||
|   APDS9306_ALS_GAIN = 0x05, | ||||
|   APDS9306_PART_ID = 0x06, | ||||
|   APDS9306_MAIN_STATUS = 0x07, | ||||
|   APDS9306_CLEAR_DATA_0 = 0x0A,  // LSB | ||||
|   APDS9306_CLEAR_DATA_1 = 0x0B, | ||||
|   APDS9306_CLEAR_DATA_2 = 0x0C,  // MSB | ||||
|   APDS9306_ALS_DATA_0 = 0x0D,    // LSB | ||||
|   APDS9306_ALS_DATA_1 = 0x0E, | ||||
|   APDS9306_ALS_DATA_2 = 0x0F,  // MSB | ||||
|   APDS9306_INT_CFG = 0x19, | ||||
|   APDS9306_INT_PERSISTENCE = 0x1A, | ||||
|   APDS9306_ALS_THRES_UP_0 = 0x21,  // LSB | ||||
|   APDS9306_ALS_THRES_UP_1 = 0x22, | ||||
|   APDS9306_ALS_THRES_UP_2 = 0x23,   // MSB | ||||
|   APDS9306_ALS_THRES_LOW_0 = 0x24,  // LSB | ||||
|   APDS9306_ALS_THRES_LOW_1 = 0x25, | ||||
|   APDS9306_ALS_THRES_LOW_2 = 0x26,  // MSB | ||||
|   APDS9306_ALS_THRES_VAR = 0x27 | ||||
| }; | ||||
|  | ||||
| #define APDS9306_ERROR_CHECK(func, error) \ | ||||
|   if (!(func)) { \ | ||||
|     ESP_LOGE(TAG, error); \ | ||||
|     this->mark_failed(); \ | ||||
|     return; \ | ||||
|   } | ||||
| #define APDS9306_WARNING_CHECK(func, warning) \ | ||||
|   if (!(func)) { \ | ||||
|     ESP_LOGW(TAG, warning); \ | ||||
|     this->status_set_warning(); \ | ||||
|     return; \ | ||||
|   } | ||||
| #define APDS9306_WRITE_BYTE(reg, value) \ | ||||
|   ESP_LOGV(TAG, "Writing 0x%02x to 0x%02x", value, reg); \ | ||||
|   if (!this->write_byte(reg, value)) { \ | ||||
|     ESP_LOGE(TAG, "Failed writing 0x%02x to 0x%02x", value, reg); \ | ||||
|     this->mark_failed(); \ | ||||
|     return; \ | ||||
|   } | ||||
|  | ||||
| void APDS9306::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up APDS9306..."); | ||||
|  | ||||
|   uint8_t id; | ||||
|   if (!this->read_byte(APDS9306_PART_ID, &id)) {  // Part ID register | ||||
|     this->error_code_ = COMMUNICATION_FAILED; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (id != 0xB1 && id != 0xB3) {  // 0xB1 for APDS9306 0xB3 for APDS9306-065 | ||||
|     this->error_code_ = WRONG_ID; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // ALS resolution and measurement, see datasheet or init.py for options | ||||
|   uint8_t als_meas_rate = ((this->bit_width_ & 0x07) << 4) | (this->measurement_rate_ & 0x07); | ||||
|   APDS9306_WRITE_BYTE(APDS9306_ALS_MEAS_RATE, als_meas_rate); | ||||
|  | ||||
|   // ALS gain, see datasheet or init.py for options | ||||
|   uint8_t als_gain = (this->gain_ & 0x07); | ||||
|   APDS9306_WRITE_BYTE(APDS9306_ALS_GAIN, als_gain); | ||||
|  | ||||
|   // Set to standby mode | ||||
|   APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00); | ||||
|  | ||||
|   // Check for data, clear main status | ||||
|   uint8_t status; | ||||
|   APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed."); | ||||
|  | ||||
|   // Set to active mode | ||||
|   APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02); | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "APDS9306 setup complete"); | ||||
| } | ||||
|  | ||||
| void APDS9306::dump_config() { | ||||
|   LOG_SENSOR("", "APDS9306", this); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|  | ||||
|   if (this->is_failed()) { | ||||
|     switch (this->error_code_) { | ||||
|       case COMMUNICATION_FAILED: | ||||
|         ESP_LOGE(TAG, "Communication with APDS9306 failed!"); | ||||
|         break; | ||||
|       case WRONG_ID: | ||||
|         ESP_LOGE(TAG, "APDS9306 has invalid id!"); | ||||
|         break; | ||||
|       default: | ||||
|         ESP_LOGE(TAG, "Setting up APDS9306 registers failed!"); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "  Gain: %u", AMBIENT_LIGHT_GAIN_VALUES[this->gain_]); | ||||
|   ESP_LOGCONFIG(TAG, "  Measurement rate: %u", MEASUREMENT_RATE_VALUES[this->measurement_rate_]); | ||||
|   ESP_LOGCONFIG(TAG, "  Measurement Resolution/Bit width: %d", MEASUREMENT_BIT_WIDTH_VALUES[this->bit_width_]); | ||||
|  | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| void APDS9306::update() { | ||||
|   // Check for new data | ||||
|   uint8_t status; | ||||
|   APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed."); | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
|  | ||||
|   if (!(status &= 0b00001000)) {  // No new data | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Set to standby mode | ||||
|   APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00); | ||||
|  | ||||
|   // Clear MAIN STATUS | ||||
|   APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed."); | ||||
|  | ||||
|   uint8_t als_data[3]; | ||||
|   APDS9306_WARNING_CHECK(this->read_bytes(APDS9306_ALS_DATA_0, als_data, 3), "Reading ALS data has failed."); | ||||
|  | ||||
|   // Set to active mode | ||||
|   APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02); | ||||
|  | ||||
|   uint32_t light_level = 0x00 | encode_uint24(als_data[2], als_data[1], als_data[0]); | ||||
|  | ||||
|   float lux = ((float) light_level / AMBIENT_LIGHT_GAIN_VALUES[this->gain_]) * | ||||
|               (100.0f / MEASUREMENT_RATE_VALUES[this->measurement_rate_]); | ||||
|  | ||||
|   ESP_LOGD(TAG, "Got illuminance=%.1flx from", lux); | ||||
|   this->publish_state(lux); | ||||
| } | ||||
|  | ||||
| }  // namespace apds9306 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										66
									
								
								esphome/components/apds9306/apds9306.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/apds9306/apds9306.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| // Based on this datasheet: | ||||
| // https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/core/component.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace apds9306 { | ||||
|  | ||||
| enum MeasurementBitWidth : uint8_t { | ||||
|   MEASUREMENT_BIT_WIDTH_20 = 0, | ||||
|   MEASUREMENT_BIT_WIDTH_19 = 1, | ||||
|   MEASUREMENT_BIT_WIDTH_18 = 2, | ||||
|   MEASUREMENT_BIT_WIDTH_17 = 3, | ||||
|   MEASUREMENT_BIT_WIDTH_16 = 4, | ||||
|   MEASUREMENT_BIT_WIDTH_13 = 5, | ||||
| }; | ||||
| static const uint8_t MEASUREMENT_BIT_WIDTH_VALUES[] = {20, 19, 18, 17, 16, 13}; | ||||
|  | ||||
| enum MeasurementRate : uint8_t { | ||||
|   MEASUREMENT_RATE_25 = 0, | ||||
|   MEASUREMENT_RATE_50 = 1, | ||||
|   MEASUREMENT_RATE_100 = 2, | ||||
|   MEASUREMENT_RATE_200 = 3, | ||||
|   MEASUREMENT_RATE_500 = 4, | ||||
|   MEASUREMENT_RATE_1000 = 5, | ||||
|   MEASUREMENT_RATE_2000 = 6, | ||||
| }; | ||||
| static const uint16_t MEASUREMENT_RATE_VALUES[] = {25, 50, 100, 200, 500, 1000, 2000}; | ||||
|  | ||||
| enum AmbientLightGain : uint8_t { | ||||
|   AMBIENT_LIGHT_GAIN_1 = 0, | ||||
|   AMBIENT_LIGHT_GAIN_3 = 1, | ||||
|   AMBIENT_LIGHT_GAIN_6 = 2, | ||||
|   AMBIENT_LIGHT_GAIN_9 = 3, | ||||
|   AMBIENT_LIGHT_GAIN_18 = 4, | ||||
| }; | ||||
| static const uint8_t AMBIENT_LIGHT_GAIN_VALUES[] = {1, 3, 6, 9, 18}; | ||||
|  | ||||
| class APDS9306 : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   float get_setup_priority() const override { return setup_priority::BUS; } | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|   void set_bit_width(MeasurementBitWidth bit_width) { this->bit_width_ = bit_width; } | ||||
|   void set_measurement_rate(MeasurementRate measurement_rate) { this->measurement_rate_ = measurement_rate; } | ||||
|   void set_ambient_light_gain(AmbientLightGain gain) { this->gain_ = gain; } | ||||
|  | ||||
|  protected: | ||||
|   enum ErrorCode { | ||||
|     NONE = 0, | ||||
|     COMMUNICATION_FAILED, | ||||
|     WRONG_ID, | ||||
|   } error_code_{NONE}; | ||||
|  | ||||
|   MeasurementBitWidth bit_width_; | ||||
|   MeasurementRate measurement_rate_; | ||||
|   AmbientLightGain gain_; | ||||
| }; | ||||
|  | ||||
| }  // namespace apds9306 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										95
									
								
								esphome/components/apds9306/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								esphome/components/apds9306/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| # Based on this datasheet: | ||||
| # https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import ( | ||||
|     CONF_GAIN, | ||||
|     DEVICE_CLASS_ILLUMINANCE, | ||||
|     ICON_LIGHTBULB, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_LUX, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| CONF_APDS9306_ID = "apds9306_id" | ||||
| CONF_BIT_WIDTH = "bit_width" | ||||
| CONF_MEASUREMENT_RATE = "measurement_rate" | ||||
|  | ||||
| apds9306_ns = cg.esphome_ns.namespace("apds9306") | ||||
| APDS9306 = apds9306_ns.class_( | ||||
|     "APDS9306", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| MeasurementBitWidth = apds9306_ns.enum("MeasurementBitWidth") | ||||
| MeasurementRate = apds9306_ns.enum("MeasurementRate") | ||||
| AmbientLightGain = apds9306_ns.enum("AmbientLightGain") | ||||
|  | ||||
| MEASUREMENT_BIT_WIDTHS = { | ||||
|     20: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_20, | ||||
|     19: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_19, | ||||
|     18: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_18, | ||||
|     17: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_17, | ||||
|     16: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_16, | ||||
|     13: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_13, | ||||
| } | ||||
|  | ||||
| MEASUREMENT_RATES = { | ||||
|     25: MeasurementRate.MEASUREMENT_RATE_25, | ||||
|     50: MeasurementRate.MEASUREMENT_RATE_50, | ||||
|     100: MeasurementRate.MEASUREMENT_RATE_100, | ||||
|     200: MeasurementRate.MEASUREMENT_RATE_200, | ||||
|     500: MeasurementRate.MEASUREMENT_RATE_500, | ||||
|     1000: MeasurementRate.MEASUREMENT_RATE_1000, | ||||
|     2000: MeasurementRate.MEASUREMENT_RATE_2000, | ||||
| } | ||||
|  | ||||
| AMBIENT_LIGHT_GAINS = { | ||||
|     1: AmbientLightGain.AMBIENT_LIGHT_GAIN_1, | ||||
|     3: AmbientLightGain.AMBIENT_LIGHT_GAIN_3, | ||||
|     6: AmbientLightGain.AMBIENT_LIGHT_GAIN_6, | ||||
|     9: AmbientLightGain.AMBIENT_LIGHT_GAIN_9, | ||||
|     18: AmbientLightGain.AMBIENT_LIGHT_GAIN_18, | ||||
| } | ||||
|  | ||||
|  | ||||
| def _validate_measurement_rate(value): | ||||
|     value = cv.positive_time_period_milliseconds(value) | ||||
|     return cv.enum(MEASUREMENT_RATES, int=True)(value.total_milliseconds) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema( | ||||
|         APDS9306, | ||||
|         unit_of_measurement=UNIT_LUX, | ||||
|         accuracy_decimals=1, | ||||
|         device_class=DEVICE_CLASS_ILLUMINANCE, | ||||
|         state_class=STATE_CLASS_MEASUREMENT, | ||||
|         icon=ICON_LIGHTBULB, | ||||
|     ) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Optional(CONF_GAIN, default="1"): cv.enum(AMBIENT_LIGHT_GAINS, int=True), | ||||
|             cv.Optional(CONF_BIT_WIDTH, default="18"): cv.enum( | ||||
|                 MEASUREMENT_BIT_WIDTHS, int=True | ||||
|             ), | ||||
|             cv.Optional( | ||||
|                 CONF_MEASUREMENT_RATE, default="100ms" | ||||
|             ): _validate_measurement_rate, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x52)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await sensor.new_sensor(config) | ||||
|     await cg.register_component(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_bit_width(config[CONF_BIT_WIDTH])) | ||||
|     cg.add(var.set_measurement_rate(config[CONF_MEASUREMENT_RATE])) | ||||
|     cg.add(var.set_ambient_light_gain(config[CONF_GAIN])) | ||||
| @@ -1,25 +1,27 @@ | ||||
| import base64 | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.automation import Condition | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ACTION, | ||||
|     CONF_ACTIONS, | ||||
|     CONF_DATA, | ||||
|     CONF_DATA_TEMPLATE, | ||||
|     CONF_EVENT, | ||||
|     CONF_ID, | ||||
|     CONF_KEY, | ||||
|     CONF_ON_CLIENT_CONNECTED, | ||||
|     CONF_ON_CLIENT_DISCONNECTED, | ||||
|     CONF_PASSWORD, | ||||
|     CONF_PORT, | ||||
|     CONF_REBOOT_TIMEOUT, | ||||
|     CONF_SERVICE, | ||||
|     CONF_VARIABLES, | ||||
|     CONF_SERVICES, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_EVENT, | ||||
|     CONF_TAG, | ||||
|     CONF_ON_CLIENT_CONNECTED, | ||||
|     CONF_ON_CLIENT_DISCONNECTED, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_VARIABLES, | ||||
| ) | ||||
| from esphome.core import coroutine_with_priority | ||||
|  | ||||
| @@ -63,40 +65,51 @@ def validate_encryption_key(value): | ||||
|     return value | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
| ACTIONS_SCHEMA = automation.validate_automation( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(APIServer), | ||||
|         cv.Optional(CONF_PORT, default=6053): cv.port, | ||||
|         cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, | ||||
|         cv.Optional( | ||||
|             CONF_REBOOT_TIMEOUT, default="15min" | ||||
|         ): cv.positive_time_period_milliseconds, | ||||
|         cv.Optional(CONF_SERVICES): automation.validate_automation( | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), | ||||
|         cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.valid_name, | ||||
|         cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.valid_name, | ||||
|         cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), | ||||
|                 cv.Required(CONF_SERVICE): cv.valid_name, | ||||
|                 cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||
|                     { | ||||
|                         cv.validate_id_name: cv.one_of( | ||||
|                             *SERVICE_ARG_NATIVE_TYPES, lower=True | ||||
|                         ), | ||||
|                     } | ||||
|                 ), | ||||
|                 cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ENCRYPTION): cv.Schema( | ||||
|             { | ||||
|                 cv.Required(CONF_KEY): validate_encryption_key, | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( | ||||
|             single=True | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( | ||||
|             single=True | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|     }, | ||||
|     cv.All( | ||||
|         cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION), | ||||
|         cv.rename_key(CONF_SERVICE, CONF_ACTION), | ||||
|     ), | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(APIServer), | ||||
|             cv.Optional(CONF_PORT, default=6053): cv.port, | ||||
|             cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, | ||||
|             cv.Optional( | ||||
|                 CONF_REBOOT_TIMEOUT, default="15min" | ||||
|             ): cv.positive_time_period_milliseconds, | ||||
|             cv.Exclusive( | ||||
|                 CONF_SERVICES, group_of_exclusion=CONF_ACTIONS | ||||
|             ): ACTIONS_SCHEMA, | ||||
|             cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, | ||||
|             cv.Optional(CONF_ENCRYPTION): cv.Schema( | ||||
|                 { | ||||
|                     cv.Required(CONF_KEY): validate_encryption_key, | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( | ||||
|                 single=True | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( | ||||
|                 single=True | ||||
|             ), | ||||
|         } | ||||
|     ).extend(cv.COMPONENT_SCHEMA), | ||||
|     cv.rename_key(CONF_SERVICES, CONF_ACTIONS), | ||||
| ) | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(40.0) | ||||
| @@ -108,7 +121,7 @@ async def to_code(config): | ||||
|     cg.add(var.set_password(config[CONF_PASSWORD])) | ||||
|     cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) | ||||
|  | ||||
|     for conf in config.get(CONF_SERVICES, []): | ||||
|     for conf in config.get(CONF_ACTIONS, []): | ||||
|         template_args = [] | ||||
|         func_args = [] | ||||
|         service_arg_names = [] | ||||
| @@ -119,7 +132,7 @@ async def to_code(config): | ||||
|             service_arg_names.append(name) | ||||
|         templ = cg.TemplateArguments(*template_args) | ||||
|         trigger = cg.new_Pvariable( | ||||
|             conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names | ||||
|             conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names | ||||
|         ) | ||||
|         cg.add(var.register_user_service(trigger)) | ||||
|         await automation.build_automation(trigger, func_args, conf) | ||||
| @@ -142,7 +155,7 @@ async def to_code(config): | ||||
|         decoded = base64.b64decode(encryption_config[CONF_KEY]) | ||||
|         cg.add(var.set_noise_psk(list(decoded))) | ||||
|         cg.add_define("USE_API_NOISE") | ||||
|         cg.add_library("esphome/noise-c", "0.1.4") | ||||
|         cg.add_library("esphome/noise-c", "0.1.6") | ||||
|     else: | ||||
|         cg.add_define("USE_API_PLAINTEXT") | ||||
|  | ||||
| @@ -152,28 +165,43 @@ async def to_code(config): | ||||
|  | ||||
| KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)}) | ||||
|  | ||||
| HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.use_id(APIServer), | ||||
|         cv.Required(CONF_SERVICE): cv.templatable(cv.string), | ||||
|         cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, | ||||
|         cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, | ||||
|         cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||
|             {cv.string: cv.returning_lambda} | ||||
|         ), | ||||
|     } | ||||
|  | ||||
| HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(APIServer), | ||||
|             cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.templatable( | ||||
|                 cv.string | ||||
|             ), | ||||
|             cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.templatable( | ||||
|                 cv.string | ||||
|             ), | ||||
|             cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, | ||||
|             cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, | ||||
|             cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||
|                 {cv.string: cv.returning_lambda} | ||||
|             ), | ||||
|         } | ||||
|     ), | ||||
|     cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION), | ||||
|     cv.rename_key(CONF_SERVICE, CONF_ACTION), | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "homeassistant.action", | ||||
|     HomeAssistantServiceCallAction, | ||||
|     HOMEASSISTANT_ACTION_ACTION_SCHEMA, | ||||
| ) | ||||
| @automation.register_action( | ||||
|     "homeassistant.service", | ||||
|     HomeAssistantServiceCallAction, | ||||
|     HOMEASSISTANT_SERVICE_ACTION_SCHEMA, | ||||
|     HOMEASSISTANT_ACTION_ACTION_SCHEMA, | ||||
| ) | ||||
| async def homeassistant_service_to_code(config, action_id, template_arg, args): | ||||
|     serv = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, serv, False) | ||||
|     templ = await cg.templatable(config[CONF_SERVICE], args, None) | ||||
|     templ = await cg.templatable(config[CONF_ACTION], args, None) | ||||
|     cg.add(var.set_service(templ)) | ||||
|     for key, value in config[CONF_DATA].items(): | ||||
|         templ = await cg.templatable(value, args, None) | ||||
|   | ||||
| @@ -62,6 +62,8 @@ service APIConnection { | ||||
|   rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} | ||||
|  | ||||
|   rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} | ||||
|   rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {} | ||||
|   rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {} | ||||
|  | ||||
|   rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} | ||||
| } | ||||
| @@ -686,6 +688,7 @@ message SubscribeHomeAssistantStateResponse { | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   string entity_id = 1; | ||||
|   string attribute = 2; | ||||
|   bool once = 3; | ||||
| } | ||||
|  | ||||
| message HomeAssistantStateResponse { | ||||
| @@ -1106,6 +1109,19 @@ enum MediaPlayerCommand { | ||||
|   MEDIA_PLAYER_COMMAND_MUTE = 3; | ||||
|   MEDIA_PLAYER_COMMAND_UNMUTE = 4; | ||||
| } | ||||
| enum MediaPlayerFormatPurpose { | ||||
|   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0; | ||||
|   MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1; | ||||
| } | ||||
| message MediaPlayerSupportedFormat { | ||||
|   option (ifdef) = "USE_MEDIA_PLAYER"; | ||||
|  | ||||
|   string format = 1; | ||||
|   uint32 sample_rate = 2; | ||||
|   uint32 num_channels = 3; | ||||
|   MediaPlayerFormatPurpose purpose = 4; | ||||
|   uint32 sample_bytes = 5; | ||||
| } | ||||
| message ListEntitiesMediaPlayerResponse { | ||||
|   option (id) = 63; | ||||
|   option (source) = SOURCE_SERVER; | ||||
| @@ -1121,6 +1137,8 @@ message ListEntitiesMediaPlayerResponse { | ||||
|   EntityCategory entity_category = 7; | ||||
|  | ||||
|   bool supports_pause = 8; | ||||
|  | ||||
|   repeated MediaPlayerSupportedFormat supported_formats = 9; | ||||
| } | ||||
| message MediaPlayerStateResponse { | ||||
|   option (id) = 64; | ||||
| @@ -1538,6 +1556,53 @@ message VoiceAssistantTimerEventResponse { | ||||
|   bool is_active = 6; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantAnnounceRequest { | ||||
|   option (id) = 119; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   string media_id = 1; | ||||
|   string text = 2; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantAnnounceFinished { | ||||
|   option (id) = 120; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   bool success = 1; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantWakeWord { | ||||
|   string id = 1; | ||||
|   string wake_word = 2; | ||||
|   repeated string trained_languages = 3; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantConfigurationRequest { | ||||
|   option (id) = 121; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantConfigurationResponse { | ||||
|   option (id) = 122; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   repeated VoiceAssistantWakeWord available_wake_words = 1; | ||||
|   repeated string active_wake_words = 2; | ||||
|   uint32 max_active_wake_words = 3; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantSetConfiguration { | ||||
|   option (id) = 123; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   repeated string active_wake_words = 1; | ||||
| } | ||||
|  | ||||
| // ==================== ALARM CONTROL PANEL ==================== | ||||
| enum AlarmControlPanelState { | ||||
|   ALARM_STATE_DISARMED = 0; | ||||
| @@ -1872,6 +1937,11 @@ message UpdateStateResponse { | ||||
|   string release_summary = 9; | ||||
|   string release_url = 10; | ||||
| } | ||||
| enum UpdateCommand { | ||||
|   UPDATE_COMMAND_NONE = 0; | ||||
|   UPDATE_COMMAND_UPDATE = 1; | ||||
|   UPDATE_COMMAND_CHECK = 2; | ||||
| } | ||||
| message UpdateCommandRequest { | ||||
|   option (id) = 118; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
| @@ -1879,5 +1949,5 @@ message UpdateCommandRequest { | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool install = 2; | ||||
|   UpdateCommand command = 2; | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "api_connection.h" | ||||
| #ifdef USE_API | ||||
| #include <cerrno> | ||||
| #include <cinttypes> | ||||
| #include <utility> | ||||
| @@ -179,6 +180,7 @@ void APIConnection::loop() { | ||||
|       SubscribeHomeAssistantStateResponse resp; | ||||
|       resp.entity_id = it.entity_id; | ||||
|       resp.attribute = it.attribute.value(); | ||||
|       resp.once = it.once; | ||||
|       if (this->send_subscribe_home_assistant_state_response(resp)) { | ||||
|         state_subs_at_++; | ||||
|       } | ||||
| @@ -1025,6 +1027,16 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play | ||||
|   auto traits = media_player->get_traits(); | ||||
|   msg.supports_pause = traits.get_supports_pause(); | ||||
|  | ||||
|   for (auto &supported_format : traits.get_supported_formats()) { | ||||
|     MediaPlayerSupportedFormat media_format; | ||||
|     media_format.format = supported_format.format; | ||||
|     media_format.sample_rate = supported_format.sample_rate; | ||||
|     media_format.num_channels = supported_format.num_channels; | ||||
|     media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose); | ||||
|     media_format.sample_bytes = supported_format.sample_bytes; | ||||
|     msg.supported_formats.push_back(media_format); | ||||
|   } | ||||
|  | ||||
|   return this->send_list_entities_media_player_response(msg); | ||||
| } | ||||
| void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | ||||
| @@ -1203,6 +1215,52 @@ void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistant | ||||
|   } | ||||
| }; | ||||
|  | ||||
| void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) { | ||||
|   if (voice_assistant::global_voice_assistant != nullptr) { | ||||
|     if (voice_assistant::global_voice_assistant->get_api_connection() != this) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     voice_assistant::global_voice_assistant->on_announce(msg); | ||||
|   } | ||||
| } | ||||
|  | ||||
| VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration( | ||||
|     const VoiceAssistantConfigurationRequest &msg) { | ||||
|   VoiceAssistantConfigurationResponse resp; | ||||
|   if (voice_assistant::global_voice_assistant != nullptr) { | ||||
|     if (voice_assistant::global_voice_assistant->get_api_connection() != this) { | ||||
|       return resp; | ||||
|     } | ||||
|  | ||||
|     auto &config = voice_assistant::global_voice_assistant->get_configuration(); | ||||
|     for (auto &wake_word : config.available_wake_words) { | ||||
|       VoiceAssistantWakeWord resp_wake_word; | ||||
|       resp_wake_word.id = wake_word.id; | ||||
|       resp_wake_word.wake_word = wake_word.wake_word; | ||||
|       for (const auto &lang : wake_word.trained_languages) { | ||||
|         resp_wake_word.trained_languages.push_back(lang); | ||||
|       } | ||||
|       resp.available_wake_words.push_back(std::move(resp_wake_word)); | ||||
|     } | ||||
|     for (auto &wake_word_id : config.active_wake_words) { | ||||
|       resp.active_wake_words.push_back(wake_word_id); | ||||
|     } | ||||
|     resp.max_active_wake_words = config.max_active_wake_words; | ||||
|   } | ||||
|   return resp; | ||||
| } | ||||
|  | ||||
| void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { | ||||
|   if (voice_assistant::global_voice_assistant != nullptr) { | ||||
|     if (voice_assistant::global_voice_assistant->get_api_connection() != this) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); | ||||
|   } | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| @@ -1328,7 +1386,20 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) { | ||||
|   if (update == nullptr) | ||||
|     return; | ||||
|  | ||||
|   update->perform(); | ||||
|   switch (msg.command) { | ||||
|     case enums::UPDATE_COMMAND_UPDATE: | ||||
|       update->perform(); | ||||
|       break; | ||||
|     case enums::UPDATE_COMMAND_CHECK: | ||||
|       update->check(); | ||||
|       break; | ||||
|     case enums::UPDATE_COMMAND_NONE: | ||||
|       ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled. Check client is sending the correct command"); | ||||
|       break; | ||||
|     default: | ||||
|       ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command); | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @@ -1498,3 +1569,4 @@ void APIConnection::on_fatal_error() { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #ifdef USE_API | ||||
| #include "api_frame_helper.h" | ||||
| #include "api_pb2.h" | ||||
| #include "api_pb2_service.h" | ||||
| #include "api_server.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| @@ -151,6 +152,10 @@ class APIConnection : public APIServerConnection { | ||||
|   void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; | ||||
|   void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; | ||||
|   void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; | ||||
|   void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override; | ||||
|   VoiceAssistantConfigurationResponse voice_assistant_get_configuration( | ||||
|       const VoiceAssistantConfigurationRequest &msg) override; | ||||
|   void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| @@ -264,3 +269,4 @@ class APIConnection : public APIServerConnection { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| #include "api_frame_helper.h" | ||||
|  | ||||
| #ifdef USE_API | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| @@ -1028,3 +1028,4 @@ APIError APIPlaintextFrameHelper::shutdown(int how) { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| #include <vector> | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| #ifdef USE_API | ||||
| #ifdef USE_API_NOISE | ||||
| #include "noise/protocol.h" | ||||
| #endif | ||||
| @@ -190,3 +190,4 @@ class APIPlaintextFrameHelper : public APIFrameHelper { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -387,6 +387,18 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me | ||||
| } | ||||
| #endif | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| template<> const char *proto_enum_to_string<enums::MediaPlayerFormatPurpose>(enums::MediaPlayerFormatPurpose value) { | ||||
|   switch (value) { | ||||
|     case enums::MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT: | ||||
|       return "MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT"; | ||||
|     case enums::MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT: | ||||
|       return "MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT"; | ||||
|     default: | ||||
|       return "UNKNOWN"; | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| template<> | ||||
| const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) { | ||||
|   switch (value) { | ||||
| @@ -567,6 +579,20 @@ template<> const char *proto_enum_to_string<enums::ValveOperation>(enums::ValveO | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| template<> const char *proto_enum_to_string<enums::UpdateCommand>(enums::UpdateCommand value) { | ||||
|   switch (value) { | ||||
|     case enums::UPDATE_COMMAND_NONE: | ||||
|       return "UPDATE_COMMAND_NONE"; | ||||
|     case enums::UPDATE_COMMAND_UPDATE: | ||||
|       return "UPDATE_COMMAND_UPDATE"; | ||||
|     case enums::UPDATE_COMMAND_CHECK: | ||||
|       return "UPDATE_COMMAND_CHECK"; | ||||
|     default: | ||||
|       return "UNKNOWN"; | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 2: { | ||||
| @@ -3095,6 +3121,16 @@ void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const { | ||||
|   out.append("SubscribeHomeAssistantStatesRequest {}"); | ||||
| } | ||||
| #endif | ||||
| bool SubscribeHomeAssistantStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 3: { | ||||
|       this->once = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
| @@ -3112,6 +3148,7 @@ bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, Proto | ||||
| void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(1, this->entity_id); | ||||
|   buffer.encode_string(2, this->attribute); | ||||
|   buffer.encode_bool(3, this->once); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { | ||||
| @@ -3124,6 +3161,10 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { | ||||
|   out.append("  attribute: "); | ||||
|   out.append("'").append(this->attribute).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  once: "); | ||||
|   out.append(YESNO(this->once)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -5094,6 +5135,74 @@ void ButtonCommandRequest::dump_to(std::string &out) const { | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 2: { | ||||
|       this->sample_rate = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     case 3: { | ||||
|       this->num_channels = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     case 4: { | ||||
|       this->purpose = value.as_enum<enums::MediaPlayerFormatPurpose>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 5: { | ||||
|       this->sample_bytes = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool MediaPlayerSupportedFormat::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->format = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(1, this->format); | ||||
|   buffer.encode_uint32(2, this->sample_rate); | ||||
|   buffer.encode_uint32(3, this->num_channels); | ||||
|   buffer.encode_enum<enums::MediaPlayerFormatPurpose>(4, this->purpose); | ||||
|   buffer.encode_uint32(5, this->sample_bytes); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void MediaPlayerSupportedFormat::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("MediaPlayerSupportedFormat {\n"); | ||||
|   out.append("  format: "); | ||||
|   out.append("'").append(this->format).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  sample_rate: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->sample_rate); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  num_channels: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->num_channels); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  purpose: "); | ||||
|   out.append(proto_enum_to_string<enums::MediaPlayerFormatPurpose>(this->purpose)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  sample_bytes: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->sample_bytes); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 6: { | ||||
| @@ -5130,6 +5239,10 @@ bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLeng | ||||
|       this->icon = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 9: { | ||||
|       this->supported_formats.push_back(value.as_message<MediaPlayerSupportedFormat>()); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -5153,6 +5266,9 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(6, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||
|   buffer.encode_bool(8, this->supports_pause); | ||||
|   for (auto &it : this->supported_formats) { | ||||
|     buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true); | ||||
|   } | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | ||||
| @@ -5190,6 +5306,12 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | ||||
|   out.append("  supports_pause: "); | ||||
|   out.append(YESNO(this->supports_pause)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   for (const auto &it : this->supported_formats) { | ||||
|     out.append("  supported_formats: "); | ||||
|     it.dump_to(out); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -6949,6 +7071,193 @@ void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->media_id = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->text = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantAnnounceRequest::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(1, this->media_id); | ||||
|   buffer.encode_string(2, this->text); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantAnnounceRequest {\n"); | ||||
|   out.append("  media_id: "); | ||||
|   out.append("'").append(this->media_id).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  text: "); | ||||
|   out.append("'").append(this->text).append("'"); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantAnnounceFinished::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->success = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantAnnounceFinished {\n"); | ||||
|   out.append("  success: "); | ||||
|   out.append(YESNO(this->success)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->id = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->wake_word = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 3: { | ||||
|       this->trained_languages.push_back(value.as_string()); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(1, this->id); | ||||
|   buffer.encode_string(2, this->wake_word); | ||||
|   for (auto &it : this->trained_languages) { | ||||
|     buffer.encode_string(3, it, true); | ||||
|   } | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantWakeWord::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantWakeWord {\n"); | ||||
|   out.append("  id: "); | ||||
|   out.append("'").append(this->id).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  wake_word: "); | ||||
|   out.append("'").append(this->wake_word).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   for (const auto &it : this->trained_languages) { | ||||
|     out.append("  trained_languages: "); | ||||
|     out.append("'").append(it).append("'"); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| void VoiceAssistantConfigurationRequest::encode(ProtoWriteBuffer buffer) const {} | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { | ||||
|   out.append("VoiceAssistantConfigurationRequest {}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 3: { | ||||
|       this->max_active_wake_words = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool VoiceAssistantConfigurationResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->available_wake_words.push_back(value.as_message<VoiceAssistantWakeWord>()); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->active_wake_words.push_back(value.as_string()); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   for (auto &it : this->available_wake_words) { | ||||
|     buffer.encode_message<VoiceAssistantWakeWord>(1, it, true); | ||||
|   } | ||||
|   for (auto &it : this->active_wake_words) { | ||||
|     buffer.encode_string(2, it, true); | ||||
|   } | ||||
|   buffer.encode_uint32(3, this->max_active_wake_words); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantConfigurationResponse {\n"); | ||||
|   for (const auto &it : this->available_wake_words) { | ||||
|     out.append("  available_wake_words: "); | ||||
|     it.dump_to(out); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|  | ||||
|   for (const auto &it : this->active_wake_words) { | ||||
|     out.append("  active_wake_words: "); | ||||
|     out.append("'").append(it).append("'"); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|  | ||||
|   out.append("  max_active_wake_words: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->max_active_wake_words); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->active_wake_words.push_back(value.as_string()); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantSetConfiguration::encode(ProtoWriteBuffer buffer) const { | ||||
|   for (auto &it : this->active_wake_words) { | ||||
|     buffer.encode_string(1, it, true); | ||||
|   } | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantSetConfiguration::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantSetConfiguration {\n"); | ||||
|   for (const auto &it : this->active_wake_words) { | ||||
|     out.append("  active_wake_words: "); | ||||
|     out.append("'").append(it).append("'"); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 6: { | ||||
| @@ -8596,7 +8905,7 @@ void UpdateStateResponse::dump_to(std::string &out) const { | ||||
| bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 2: { | ||||
|       this->install = value.as_bool(); | ||||
|       this->command = value.as_enum<enums::UpdateCommand>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
| @@ -8615,7 +8924,7 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { | ||||
| } | ||||
| void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_fixed32(1, this->key); | ||||
|   buffer.encode_bool(2, this->install); | ||||
|   buffer.encode_enum<enums::UpdateCommand>(2, this->command); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void UpdateCommandRequest::dump_to(std::string &out) const { | ||||
| @@ -8626,8 +8935,8 @@ void UpdateCommandRequest::dump_to(std::string &out) const { | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  install: "); | ||||
|   out.append(YESNO(this->install)); | ||||
|   out.append("  command: "); | ||||
|   out.append(proto_enum_to_string<enums::UpdateCommand>(this->command)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
|   | ||||
| @@ -156,6 +156,10 @@ enum MediaPlayerCommand : uint32_t { | ||||
|   MEDIA_PLAYER_COMMAND_MUTE = 3, | ||||
|   MEDIA_PLAYER_COMMAND_UNMUTE = 4, | ||||
| }; | ||||
| enum MediaPlayerFormatPurpose : uint32_t { | ||||
|   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0, | ||||
|   MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1, | ||||
| }; | ||||
| enum BluetoothDeviceRequestType : uint32_t { | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, | ||||
| @@ -227,6 +231,11 @@ enum ValveOperation : uint32_t { | ||||
|   VALVE_OPERATION_IS_OPENING = 1, | ||||
|   VALVE_OPERATION_IS_CLOSING = 2, | ||||
| }; | ||||
| enum UpdateCommand : uint32_t { | ||||
|   UPDATE_COMMAND_NONE = 0, | ||||
|   UPDATE_COMMAND_UPDATE = 1, | ||||
|   UPDATE_COMMAND_CHECK = 2, | ||||
| }; | ||||
|  | ||||
| }  // namespace enums | ||||
|  | ||||
| @@ -831,6 +840,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage { | ||||
|  public: | ||||
|   std::string entity_id{}; | ||||
|   std::string attribute{}; | ||||
|   bool once{false}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -838,6 +848,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage { | ||||
|  | ||||
|  protected: | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class HomeAssistantStateResponse : public ProtoMessage { | ||||
|  public: | ||||
| @@ -1260,6 +1271,22 @@ class ButtonCommandRequest : public ProtoMessage { | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
| }; | ||||
| class MediaPlayerSupportedFormat : public ProtoMessage { | ||||
|  public: | ||||
|   std::string format{}; | ||||
|   uint32_t sample_rate{0}; | ||||
|   uint32_t num_channels{0}; | ||||
|   enums::MediaPlayerFormatPurpose purpose{}; | ||||
|   uint32_t sample_bytes{0}; | ||||
|   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; | ||||
| }; | ||||
| class ListEntitiesMediaPlayerResponse : public ProtoMessage { | ||||
|  public: | ||||
|   std::string object_id{}; | ||||
| @@ -1270,6 +1297,7 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage { | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   bool supports_pause{false}; | ||||
|   std::vector<MediaPlayerSupportedFormat> supported_formats{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -1798,6 +1826,76 @@ class VoiceAssistantTimerEventResponse : public ProtoMessage { | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class VoiceAssistantAnnounceRequest : public ProtoMessage { | ||||
|  public: | ||||
|   std::string media_id{}; | ||||
|   std::string text{}; | ||||
|   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 VoiceAssistantAnnounceFinished : public ProtoMessage { | ||||
|  public: | ||||
|   bool success{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 VoiceAssistantWakeWord : public ProtoMessage { | ||||
|  public: | ||||
|   std::string id{}; | ||||
|   std::string wake_word{}; | ||||
|   std::vector<std::string> trained_languages{}; | ||||
|   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 VoiceAssistantConfigurationRequest : 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 VoiceAssistantConfigurationResponse : public ProtoMessage { | ||||
|  public: | ||||
|   std::vector<VoiceAssistantWakeWord> available_wake_words{}; | ||||
|   std::vector<std::string> active_wake_words{}; | ||||
|   uint32_t max_active_wake_words{0}; | ||||
|   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; | ||||
| }; | ||||
| class VoiceAssistantSetConfiguration : public ProtoMessage { | ||||
|  public: | ||||
|   std::vector<std::string> active_wake_words{}; | ||||
|   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 ListEntitiesAlarmControlPanelResponse : public ProtoMessage { | ||||
|  public: | ||||
|   std::string object_id{}; | ||||
| @@ -2175,7 +2273,7 @@ class UpdateStateResponse : public ProtoMessage { | ||||
| class UpdateCommandRequest : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   bool install{false}; | ||||
|   enums::UpdateCommand command{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
|   | ||||
| @@ -486,6 +486,29 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| bool APIServerConnectionBase::send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_voice_assistant_announce_finished: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<VoiceAssistantAnnounceFinished>(msg, 120); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| bool APIServerConnectionBase::send_voice_assistant_configuration_response( | ||||
|     const VoiceAssistantConfigurationResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_voice_assistant_configuration_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<VoiceAssistantConfigurationResponse>(msg, 122); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( | ||||
|     const ListEntitiesAlarmControlPanelResponse &msg) { | ||||
| @@ -1135,6 +1158,39 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_update_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 119: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       VoiceAssistantAnnounceRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_announce_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 121: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       VoiceAssistantConfigurationRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_configuration_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 123: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       VoiceAssistantSetConfiguration msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_set_configuration(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
| @@ -1625,6 +1681,35 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo | ||||
|   this->subscribe_voice_assistant(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg); | ||||
|   if (!this->send_voice_assistant_configuration_response(ret)) { | ||||
|     this->on_fatal_error(); | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->voice_assistant_set_configuration(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|   | ||||
| @@ -247,6 +247,21 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg); | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){}; | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); | ||||
| #endif | ||||
| @@ -419,6 +434,13 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual VoiceAssistantConfigurationResponse voice_assistant_get_configuration( | ||||
|       const VoiceAssistantConfigurationRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; | ||||
| #endif | ||||
| @@ -520,6 +542,12 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; | ||||
| #endif | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "api_server.h" | ||||
| #ifdef USE_API | ||||
| #include <cerrno> | ||||
| #include "api_connection.h" | ||||
| #include "esphome/components/network/util.h" | ||||
| @@ -359,8 +360,18 @@ void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<s | ||||
|       .entity_id = std::move(entity_id), | ||||
|       .attribute = std::move(attribute), | ||||
|       .callback = std::move(f), | ||||
|       .once = false, | ||||
|   }); | ||||
| } | ||||
| void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute, | ||||
|                                          std::function<void(std::string)> f) { | ||||
|   this->state_subs_.push_back(HomeAssistantStateSubscription{ | ||||
|       .entity_id = std::move(entity_id), | ||||
|       .attribute = std::move(attribute), | ||||
|       .callback = std::move(f), | ||||
|       .once = true, | ||||
|   }); | ||||
| }; | ||||
| const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const { | ||||
|   return this->state_subs_; | ||||
| } | ||||
| @@ -393,3 +404,4 @@ void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #ifdef USE_API | ||||
| #include "api_noise_context.h" | ||||
| #include "api_pb2.h" | ||||
| #include "api_pb2_service.h" | ||||
| @@ -7,7 +9,6 @@ | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/controller.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "list_entities.h" | ||||
| #include "subscribe_state.h" | ||||
| @@ -112,10 +113,13 @@ class APIServer : public Component, public Controller { | ||||
|     std::string entity_id; | ||||
|     optional<std::string> attribute; | ||||
|     std::function<void(std::string)> callback; | ||||
|     bool once; | ||||
|   }; | ||||
|  | ||||
|   void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute, | ||||
|                                       std::function<void(std::string)> f); | ||||
|   void get_home_assistant_state(std::string entity_id, optional<std::string> attribute, | ||||
|                                 std::function<void(std::string)> f); | ||||
|   const std::vector<HomeAssistantStateSubscription> &get_state_subs() const; | ||||
|   const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; } | ||||
|  | ||||
| @@ -150,3 +154,4 @@ template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <map> | ||||
| #include "user_services.h" | ||||
| #include "api_server.h" | ||||
|  | ||||
| #ifdef USE_API | ||||
| #include "user_services.h" | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| @@ -216,3 +216,4 @@ class CustomAPIDevice { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "api_server.h" | ||||
| #ifdef USE_API | ||||
| #include "api_pb2.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "api_pb2.h" | ||||
| #include "api_server.h" | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -81,3 +81,4 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "list_entities.h" | ||||
| #ifdef USE_API | ||||
| #include "api_connection.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/log.h" | ||||
| @@ -104,3 +105,4 @@ bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #ifdef USE_API | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/component_iterator.h" | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| @@ -87,3 +87,4 @@ class ListEntitiesIterator : public ComponentIterator { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "subscribe_state.h" | ||||
| #ifdef USE_API | ||||
| #include "api_connection.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| @@ -84,3 +85,4 @@ InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(clie | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #ifdef USE_API | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/component_iterator.h" | ||||
| #include "esphome/core/controller.h" | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| @@ -82,3 +82,4 @@ class InitialStateIterator : public ComponentIterator { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| # Dummy integration to allow relying on AsyncTCP | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.const import ( | ||||
|     PLATFORM_BK72XX, | ||||
|     PLATFORM_ESP32, | ||||
|     PLATFORM_ESP8266, | ||||
|     PLATFORM_BK72XX, | ||||
|     PLATFORM_RTL87XX, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
|  | ||||
| CODEOWNERS = ["@OttoWinter"] | ||||
|  | ||||
| @@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All( | ||||
| async def to_code(config): | ||||
|     if CORE.is_esp32 or CORE.is_libretiny: | ||||
|         # https://github.com/esphome/AsyncTCP/blob/master/library.json | ||||
|         cg.add_library("esphome/AsyncTCP-esphome", "2.1.3") | ||||
|         cg.add_library("esphome/AsyncTCP-esphome", "2.1.4") | ||||
|     elif CORE.is_esp8266: | ||||
|         # https://github.com/esphome/ESPAsyncTCP | ||||
|         cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") | ||||
|   | ||||
| @@ -1,34 +1,34 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, spi | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_REACTIVE_POWER, | ||||
|     CONF_VOLTAGE, | ||||
|     CONF_CURRENT, | ||||
|     CONF_FORWARD_ACTIVE_ENERGY, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_ID, | ||||
|     CONF_LINE_FREQUENCY, | ||||
|     CONF_POWER, | ||||
|     CONF_POWER_FACTOR, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_FORWARD_ACTIVE_ENERGY, | ||||
|     CONF_REACTIVE_POWER, | ||||
|     CONF_REVERSE_ACTIVE_ENERGY, | ||||
|     CONF_VOLTAGE, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_POWER_FACTOR, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ICON_LIGHTBULB, | ||||
|     ICON_CURRENT_AC, | ||||
|     ICON_LIGHTBULB, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     STATE_CLASS_TOTAL_INCREASING, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_HERTZ, | ||||
|     UNIT_VOLT, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_WATT, | ||||
|     UNIT_VOLT_AMPS_REACTIVE, | ||||
|     UNIT_WATT, | ||||
|     UNIT_WATT_HOURS, | ||||
| ) | ||||
|  | ||||
| CONF_LINE_FREQUENCY = "line_frequency" | ||||
| CONF_METER_CONSTANT = "meter_constant" | ||||
| CONF_PL_CONST = "pl_const" | ||||
| CONF_GAIN_PGA = "gain_pga" | ||||
|   | ||||
| @@ -0,0 +1,7 @@ | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| CODEOWNERS = ["@circuitsetup", "@descipher"] | ||||
|  | ||||
| atm90e32_ns = cg.esphome_ns.namespace("atm90e32") | ||||
|  | ||||
| CONF_ATM90E32_ID = "atm90e32_id" | ||||
|   | ||||
| @@ -132,10 +132,77 @@ void ATM90E32Component::update() { | ||||
|   this->status_clear_warning(); | ||||
| } | ||||
|  | ||||
| void ATM90E32Component::restore_calibrations_() { | ||||
|   if (enable_offset_calibration_) { | ||||
|     this->pref_.load(&this->offset_phase_); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| void ATM90E32Component::run_offset_calibrations() { | ||||
|   // Run the calibrations and | ||||
|   // Setup voltage and current calibration offsets for PHASE A | ||||
|   this->offset_phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA); | ||||
|   this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_);  // C Voltage offset | ||||
|   this->offset_phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA); | ||||
|   this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_);  // C Current offset | ||||
|   // Setup voltage and current calibration offsets for PHASE B | ||||
|   this->offset_phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB); | ||||
|   this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_);  // C Voltage offset | ||||
|   this->offset_phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB); | ||||
|   this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_);  // C Current offset | ||||
|   // Setup voltage and current calibration offsets for PHASE C | ||||
|   this->offset_phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC); | ||||
|   this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_);  // C Voltage offset | ||||
|   this->offset_phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC); | ||||
|   this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_);  // C Current offset | ||||
|   this->pref_.save(&this->offset_phase_); | ||||
|   ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_, | ||||
|            this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_); | ||||
|   ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_, | ||||
|            this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_); | ||||
| } | ||||
|  | ||||
| void ATM90E32Component::clear_offset_calibrations() { | ||||
|   // Clear the calibrations and | ||||
|   this->offset_phase_[PHASEA].voltage_offset_ = 0; | ||||
|   this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_);  // C Voltage offset | ||||
|   this->offset_phase_[PHASEA].current_offset_ = 0; | ||||
|   this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_);  // C Current offset | ||||
|   this->offset_phase_[PHASEB].voltage_offset_ = 0; | ||||
|   this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_);  // C Voltage offset | ||||
|   this->offset_phase_[PHASEB].current_offset_ = 0; | ||||
|   this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_);  // C Current offset | ||||
|   this->offset_phase_[PHASEC].voltage_offset_ = 0; | ||||
|   this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_);  // C Voltage offset | ||||
|   this->offset_phase_[PHASEC].current_offset_ = 0; | ||||
|   this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_; | ||||
|   this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_);  // C Current offset | ||||
|   this->pref_.save(&this->offset_phase_); | ||||
|   ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_, | ||||
|            this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_); | ||||
|   ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_, | ||||
|            this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_); | ||||
| } | ||||
|  | ||||
| void ATM90E32Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component..."); | ||||
|   this->spi_setup(); | ||||
|  | ||||
|   if (this->enable_offset_calibration_) { | ||||
|     uint32_t hash = fnv1_hash(App.get_friendly_name()); | ||||
|     this->pref_ = global_preferences->make_preference<Calibration[3]>(hash, true); | ||||
|     this->restore_calibrations_(); | ||||
|   } | ||||
|   uint16_t mmode0 = 0x87;  // 3P4W 50Hz | ||||
|   if (line_freq_ == 60) { | ||||
|     mmode0 |= 1 << 12;  // sets 12th bit to 1, 60Hz | ||||
| @@ -167,27 +234,12 @@ void ATM90E32Component::setup() { | ||||
|   this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C);       // All Reactive Startup Power Threshold - 50% | ||||
|   this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE);       // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750 | ||||
|   this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE);       // Each phase Reactive Phase Threshold - 10% | ||||
|   // Setup voltage and current calibration offsets for PHASE A | ||||
|   this->phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA); | ||||
|   this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_);  // A Voltage offset | ||||
|   this->phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA); | ||||
|   this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_);  // A Current offset | ||||
|   // Setup voltage and current gain for PHASE A | ||||
|   this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_);  // A Voltage rms gain | ||||
|   this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_);       // A line current gain | ||||
|   // Setup voltage and current calibration offsets for PHASE B | ||||
|   this->phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB); | ||||
|   this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_);  // B Voltage offset | ||||
|   this->phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB); | ||||
|   this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_);  // B Current offset | ||||
|   // Setup voltage and current gain for PHASE B | ||||
|   this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_);  // B Voltage rms gain | ||||
|   this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_);       // B line current gain | ||||
|   // Setup voltage and current calibration offsets for PHASE C | ||||
|   this->phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC); | ||||
|   this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_);  // C Voltage offset | ||||
|   this->phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC); | ||||
|   this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_);  // C Current offset | ||||
|   // Setup voltage and current gain for PHASE C | ||||
|   this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_);  // C Voltage rms gain | ||||
|   this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_);       // C line current gain | ||||
|   | ||||
| @@ -1,9 +1,12 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "atm90e32_reg.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/spi/spi.h" | ||||
| #include "atm90e32_reg.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/preferences.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace atm90e32 { | ||||
| @@ -20,7 +23,6 @@ class ATM90E32Component : public PollingComponent, | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void update() override; | ||||
|  | ||||
|   void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; } | ||||
|   void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; } | ||||
|   void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; } | ||||
| @@ -48,9 +50,11 @@ class ATM90E32Component : public PollingComponent, | ||||
|   void set_line_freq(int freq) { line_freq_ = freq; } | ||||
|   void set_current_phases(int phases) { current_phases_ = phases; } | ||||
|   void set_pga_gain(uint16_t gain) { pga_gain_ = gain; } | ||||
|   void run_offset_calibrations(); | ||||
|   void clear_offset_calibrations(); | ||||
|   void set_enable_offset_calibration(bool flag) { enable_offset_calibration_ = flag; } | ||||
|   uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/); | ||||
|   uint16_t calibrate_current_offset_phase(uint8_t /*phase*/); | ||||
|  | ||||
|   int32_t last_periodic_millis = millis(); | ||||
|  | ||||
|  protected: | ||||
| @@ -83,10 +87,11 @@ class ATM90E32Component : public PollingComponent, | ||||
|   float get_chip_temperature_(); | ||||
|   bool get_publish_interval_flag_() { return publish_interval_flag_; }; | ||||
|   void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; }; | ||||
|   void restore_calibrations_(); | ||||
|  | ||||
|   struct ATM90E32Phase { | ||||
|     uint16_t voltage_gain_{7305}; | ||||
|     uint16_t ct_gain_{27961}; | ||||
|     uint16_t voltage_gain_{0}; | ||||
|     uint16_t ct_gain_{0}; | ||||
|     uint16_t voltage_offset_{0}; | ||||
|     uint16_t current_offset_{0}; | ||||
|     float voltage_{0}; | ||||
| @@ -114,13 +119,21 @@ class ATM90E32Component : public PollingComponent, | ||||
|     uint32_t cumulative_reverse_active_energy_{0}; | ||||
|   } phase_[3]; | ||||
|  | ||||
|   struct Calibration { | ||||
|     uint16_t voltage_offset_{0}; | ||||
|     uint16_t current_offset_{0}; | ||||
|   } offset_phase_[3]; | ||||
|  | ||||
|   ESPPreferenceObject pref_; | ||||
|  | ||||
|   sensor::Sensor *freq_sensor_{nullptr}; | ||||
|   sensor::Sensor *chip_temperature_sensor_{nullptr}; | ||||
|   uint16_t pga_gain_{0x15}; | ||||
|   int line_freq_{60}; | ||||
|   int current_phases_{3}; | ||||
|   bool publish_interval_flag_{true}; | ||||
|   bool publish_interval_flag_{false}; | ||||
|   bool peak_current_signed_{false}; | ||||
|   bool enable_offset_calibration_{false}; | ||||
| }; | ||||
|  | ||||
| }  // namespace atm90e32 | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cinttypes> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace atm90e32 { | ||||
|  | ||||
|   | ||||
							
								
								
									
										43
									
								
								esphome/components/atm90e32/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/atm90e32/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import button | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_CHIP, ICON_SCALE | ||||
|  | ||||
| from .. import atm90e32_ns | ||||
| from ..sensor import ATM90E32Component | ||||
|  | ||||
| CONF_RUN_OFFSET_CALIBRATION = "run_offset_calibration" | ||||
| CONF_CLEAR_OFFSET_CALIBRATION = "clear_offset_calibration" | ||||
|  | ||||
| ATM90E32CalibrationButton = atm90e32_ns.class_( | ||||
|     "ATM90E32CalibrationButton", | ||||
|     button.Button, | ||||
| ) | ||||
| ATM90E32ClearCalibrationButton = atm90e32_ns.class_( | ||||
|     "ATM90E32ClearCalibrationButton", | ||||
|     button.Button, | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = { | ||||
|     cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component), | ||||
|     cv.Optional(CONF_RUN_OFFSET_CALIBRATION): button.button_schema( | ||||
|         ATM90E32CalibrationButton, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_SCALE, | ||||
|     ), | ||||
|     cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): button.button_schema( | ||||
|         ATM90E32ClearCalibrationButton, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_CHIP, | ||||
|     ), | ||||
| } | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     if run_offset := config.get(CONF_RUN_OFFSET_CALIBRATION): | ||||
|         b = await button.new_button(run_offset) | ||||
|         await cg.register_parented(b, parent) | ||||
|     if clear_offset := config.get(CONF_CLEAR_OFFSET_CALIBRATION): | ||||
|         b = await button.new_button(clear_offset) | ||||
|         await cg.register_parented(b, parent) | ||||
							
								
								
									
										20
									
								
								esphome/components/atm90e32/button/atm90e32_button.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/atm90e32/button/atm90e32_button.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| #include "atm90e32_button.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace atm90e32 { | ||||
|  | ||||
| static const char *const TAG = "atm90e32.button"; | ||||
|  | ||||
| void ATM90E32CalibrationButton::press_action() { | ||||
|   ESP_LOGI(TAG, "Running offset calibrations, Note: CTs and ACVs must be 0 during this process..."); | ||||
|   this->parent_->run_offset_calibrations(); | ||||
| } | ||||
|  | ||||
| void ATM90E32ClearCalibrationButton::press_action() { | ||||
|   ESP_LOGI(TAG, "Offset calibrations cleared."); | ||||
|   this->parent_->clear_offset_calibrations(); | ||||
| } | ||||
|  | ||||
| }  // namespace atm90e32 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										27
									
								
								esphome/components/atm90e32/button/atm90e32_button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/atm90e32/button/atm90e32_button.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/atm90e32/atm90e32.h" | ||||
| #include "esphome/components/button/button.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace atm90e32 { | ||||
|  | ||||
| class ATM90E32CalibrationButton : public button::Button, public Parented<ATM90E32Component> { | ||||
|  public: | ||||
|   ATM90E32CalibrationButton() = default; | ||||
|  | ||||
|  protected: | ||||
|   void press_action() override; | ||||
| }; | ||||
|  | ||||
| class ATM90E32ClearCalibrationButton : public button::Button, public Parented<ATM90E32Component> { | ||||
|  public: | ||||
|   ATM90E32ClearCalibrationButton() = default; | ||||
|  | ||||
|  protected: | ||||
|   void press_action() override; | ||||
| }; | ||||
|  | ||||
| }  // namespace atm90e32 | ||||
| }  // namespace esphome | ||||
| @@ -1,21 +1,22 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, spi | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_REACTIVE_POWER, | ||||
|     CONF_VOLTAGE, | ||||
|     CONF_APPARENT_POWER, | ||||
|     CONF_CURRENT, | ||||
|     CONF_FORWARD_ACTIVE_ENERGY, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_ID, | ||||
|     CONF_LINE_FREQUENCY, | ||||
|     CONF_PHASE_A, | ||||
|     CONF_PHASE_ANGLE, | ||||
|     CONF_PHASE_B, | ||||
|     CONF_PHASE_C, | ||||
|     CONF_PHASE_ANGLE, | ||||
|     CONF_POWER, | ||||
|     CONF_POWER_FACTOR, | ||||
|     CONF_APPARENT_POWER, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_FORWARD_ACTIVE_ENERGY, | ||||
|     CONF_REACTIVE_POWER, | ||||
|     CONF_REVERSE_ACTIVE_ENERGY, | ||||
|     CONF_VOLTAGE, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_POWER, | ||||
| @@ -23,13 +24,13 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     ICON_LIGHTBULB, | ||||
|     ICON_CURRENT_AC, | ||||
|     ICON_LIGHTBULB, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     STATE_CLASS_TOTAL_INCREASING, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_DEGREES, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_DEGREES, | ||||
|     UNIT_HERTZ, | ||||
|     UNIT_VOLT, | ||||
|     UNIT_VOLT_AMPS_REACTIVE, | ||||
| @@ -37,7 +38,8 @@ from esphome.const import ( | ||||
|     UNIT_WATT_HOURS, | ||||
| ) | ||||
|  | ||||
| CONF_LINE_FREQUENCY = "line_frequency" | ||||
| from . import atm90e32_ns | ||||
|  | ||||
| CONF_CHIP_TEMPERATURE = "chip_temperature" | ||||
| CONF_GAIN_PGA = "gain_pga" | ||||
| CONF_CURRENT_PHASES = "current_phases" | ||||
| @@ -46,6 +48,7 @@ CONF_GAIN_CT = "gain_ct" | ||||
| CONF_HARMONIC_POWER = "harmonic_power" | ||||
| CONF_PEAK_CURRENT = "peak_current" | ||||
| CONF_PEAK_CURRENT_SIGNED = "peak_current_signed" | ||||
| CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration" | ||||
| UNIT_DEG = "degrees" | ||||
| LINE_FREQS = { | ||||
|     "50HZ": 50, | ||||
| @@ -61,7 +64,6 @@ PGA_GAINS = { | ||||
|     "4X": 0x2A, | ||||
| } | ||||
|  | ||||
| atm90e32_ns = cg.esphome_ns.namespace("atm90e32") | ||||
| ATM90E32Component = atm90e32_ns.class_( | ||||
|     "ATM90E32Component", cg.PollingComponent, spi.SPIDevice | ||||
| ) | ||||
| @@ -164,6 +166,7 @@ CONFIG_SCHEMA = ( | ||||
|             ), | ||||
|             cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True), | ||||
|             cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean, | ||||
|             cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| @@ -227,3 +230,4 @@ async def to_code(config): | ||||
|     cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES])) | ||||
|     cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) | ||||
|     cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED])) | ||||
|     cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION])) | ||||
|   | ||||
							
								
								
									
										9
									
								
								esphome/components/audio/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								esphome/components/audio/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
|  | ||||
| CODEOWNERS = ["@kahrendt"] | ||||
| audio_ns = cg.esphome_ns.namespace("audio") | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema({}), | ||||
| ) | ||||
							
								
								
									
										21
									
								
								esphome/components/audio/audio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								esphome/components/audio/audio.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <stddef.h> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace audio { | ||||
|  | ||||
| struct AudioStreamInfo { | ||||
|   bool operator==(const AudioStreamInfo &rhs) const { | ||||
|     return (channels == rhs.channels) && (bits_per_sample == rhs.bits_per_sample) && (sample_rate == rhs.sample_rate); | ||||
|   } | ||||
|   bool operator!=(const AudioStreamInfo &rhs) const { return !operator==(rhs); } | ||||
|   size_t get_bytes_per_sample() const { return bits_per_sample / 8; } | ||||
|   uint8_t channels = 1; | ||||
|   uint8_t bits_per_sample = 16; | ||||
|   uint32_t sample_rate = 16000; | ||||
| }; | ||||
|  | ||||
| }  // namespace audio | ||||
| }  // namespace esphome | ||||
							
								
								
									
										57
									
								
								esphome/components/audio_dac/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/audio_dac/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| from esphome import automation | ||||
| from esphome.automation import maybe_simple_id | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ID, CONF_VOLUME | ||||
| from esphome.core import coroutine_with_priority | ||||
|  | ||||
| CODEOWNERS = ["@kbx81"] | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| audio_dac_ns = cg.esphome_ns.namespace("audio_dac") | ||||
| AudioDac = audio_dac_ns.class_("AudioDac") | ||||
|  | ||||
| MuteOffAction = audio_dac_ns.class_("MuteOffAction", automation.Action) | ||||
| MuteOnAction = audio_dac_ns.class_("MuteOnAction", automation.Action) | ||||
| SetVolumeAction = audio_dac_ns.class_("SetVolumeAction", automation.Action) | ||||
|  | ||||
|  | ||||
| MUTE_ACTION_SCHEMA = maybe_simple_id( | ||||
|     { | ||||
|         cv.GenerateID(): cv.use_id(AudioDac), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| SET_VOLUME_ACTION_SCHEMA = cv.maybe_simple_value( | ||||
|     { | ||||
|         cv.GenerateID(): cv.use_id(AudioDac), | ||||
|         cv.Required(CONF_VOLUME): cv.templatable(cv.percentage), | ||||
|     }, | ||||
|     key=CONF_VOLUME, | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action("audio_dac.mute_off", MuteOffAction, MUTE_ACTION_SCHEMA) | ||||
| @automation.register_action("audio_dac.mute_on", MuteOnAction, MUTE_ACTION_SCHEMA) | ||||
| async def audio_dac_mute_action_to_code(config, action_id, template_arg, args): | ||||
|     paren = await cg.get_variable(config[CONF_ID]) | ||||
|     return cg.new_Pvariable(action_id, template_arg, paren) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "audio_dac.set_volume", SetVolumeAction, SET_VOLUME_ACTION_SCHEMA | ||||
| ) | ||||
| async def audio_dac_set_volume_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) | ||||
|  | ||||
|     template_ = await cg.templatable(config.get(CONF_VOLUME), args, float) | ||||
|     cg.add(var.set_volume(template_)) | ||||
|  | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(100.0) | ||||
| async def to_code(config): | ||||
|     cg.add_define("USE_AUDIO_DAC") | ||||
|     cg.add_global(audio_dac_ns.using) | ||||
							
								
								
									
										23
									
								
								esphome/components/audio_dac/audio_dac.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/audio_dac/audio_dac.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace audio_dac { | ||||
|  | ||||
| class AudioDac { | ||||
|  public: | ||||
|   virtual bool set_mute_off() = 0; | ||||
|   virtual bool set_mute_on() = 0; | ||||
|   virtual bool set_volume(float volume) = 0; | ||||
|  | ||||
|   virtual bool is_muted() = 0; | ||||
|   virtual float volume() = 0; | ||||
|  | ||||
|  protected: | ||||
|   bool is_muted_{false}; | ||||
| }; | ||||
|  | ||||
| }  // namespace audio_dac | ||||
| }  // namespace esphome | ||||
							
								
								
									
										43
									
								
								esphome/components/audio_dac/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/audio_dac/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "audio_dac.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace audio_dac { | ||||
|  | ||||
| template<typename... Ts> class MuteOffAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {} | ||||
|  | ||||
|   void play(Ts... x) override { this->audio_dac_->set_mute_off(); } | ||||
|  | ||||
|  protected: | ||||
|   AudioDac *audio_dac_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class MuteOnAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {} | ||||
|  | ||||
|   void play(Ts... x) override { this->audio_dac_->set_mute_on(); } | ||||
|  | ||||
|  protected: | ||||
|   AudioDac *audio_dac_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class SetVolumeAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit SetVolumeAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {} | ||||
|  | ||||
|   TEMPLATABLE_VALUE(float, volume) | ||||
|  | ||||
|   void play(Ts... x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); } | ||||
|  | ||||
|  protected: | ||||
|   AudioDac *audio_dac_; | ||||
| }; | ||||
|  | ||||
| }  // namespace audio_dac | ||||
| }  // namespace esphome | ||||
							
								
								
									
										6
									
								
								esphome/components/axs15231/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								esphome/components/axs15231/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| CODEOWNERS = ["@clydebarrow"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| axs15231_ns = cg.esphome_ns.namespace("axs15231") | ||||
							
								
								
									
										36
									
								
								esphome/components/axs15231/touchscreen/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/axs15231/touchscreen/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| from esphome import pins | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import i2c, touchscreen | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN | ||||
|  | ||||
| from .. import axs15231_ns | ||||
|  | ||||
| AXS15231Touchscreen = axs15231_ns.class_( | ||||
|     "AXS15231Touchscreen", | ||||
|     touchscreen.Touchscreen, | ||||
|     i2c.I2CDevice, | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     touchscreen.touchscreen_schema("50ms") | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(AXS15231Touchscreen), | ||||
|             cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, | ||||
|             cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, | ||||
|         } | ||||
|     ) | ||||
|     .extend(i2c.i2c_device_schema(0x3B)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await touchscreen.register_touchscreen(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     if interrupt_pin := config.get(CONF_INTERRUPT_PIN): | ||||
|         cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) | ||||
|     if reset_pin := config.get(CONF_RESET_PIN): | ||||
|         cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin))) | ||||
| @@ -0,0 +1,64 @@ | ||||
| #include "axs15231_touchscreen.h" | ||||
|  | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace axs15231 { | ||||
|  | ||||
| static const char *const TAG = "ax15231.touchscreen"; | ||||
|  | ||||
| constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a, 0x0, 0x0, 0x0, 0x8}; | ||||
|  | ||||
| #define ERROR_CHECK(err) \ | ||||
|   if ((err) != i2c::ERROR_OK) { \ | ||||
|     this->status_set_warning("Failed to communicate"); \ | ||||
|     return; \ | ||||
|   } | ||||
|  | ||||
| void AXS15231Touchscreen::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up AXS15231 Touchscreen..."); | ||||
|   if (this->reset_pin_ != nullptr) { | ||||
|     this->reset_pin_->setup(); | ||||
|     this->reset_pin_->digital_write(false); | ||||
|     delay(5); | ||||
|     this->reset_pin_->digital_write(true); | ||||
|     delay(10); | ||||
|   } | ||||
|   if (this->interrupt_pin_ != nullptr) { | ||||
|     this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT); | ||||
|     this->interrupt_pin_->setup(); | ||||
|     this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); | ||||
|   } | ||||
|   this->x_raw_max_ = this->display_->get_native_width(); | ||||
|   this->y_raw_max_ = this->display_->get_native_height(); | ||||
|   ESP_LOGCONFIG(TAG, "AXS15231 Touchscreen setup complete"); | ||||
| } | ||||
|  | ||||
| void AXS15231Touchscreen::update_touches() { | ||||
|   i2c::ErrorCode err; | ||||
|   uint8_t data[8]{}; | ||||
|  | ||||
|   err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD), false); | ||||
|   ERROR_CHECK(err); | ||||
|   err = this->read(data, sizeof(data)); | ||||
|   ERROR_CHECK(err); | ||||
|   this->status_clear_warning(); | ||||
|   if (data[0] != 0)  // no touches | ||||
|     return; | ||||
|   uint16_t x = encode_uint16(data[2] & 0xF, data[3]); | ||||
|   uint16_t y = encode_uint16(data[4] & 0xF, data[5]); | ||||
|   this->add_raw_touch_position_(0, x, y); | ||||
| } | ||||
|  | ||||
| void AXS15231Touchscreen::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "AXS15231 Touchscreen:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   LOG_PIN("  Interrupt Pin: ", this->interrupt_pin_); | ||||
|   LOG_PIN("  Reset Pin: ", this->reset_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  Width: %d", this->x_raw_max_); | ||||
|   ESP_LOGCONFIG(TAG, "  Height: %d", this->y_raw_max_); | ||||
| } | ||||
|  | ||||
| }  // namespace axs15231 | ||||
| }  // namespace esphome | ||||
| @@ -0,0 +1,27 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/components/touchscreen/touchscreen.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace axs15231 { | ||||
|  | ||||
| class AXS15231Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } | ||||
|   void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } | ||||
|  | ||||
|  protected: | ||||
|   void update_touches() override; | ||||
|  | ||||
|   InternalGPIOPin *interrupt_pin_{}; | ||||
|   GPIOPin *reset_pin_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace axs15231 | ||||
| }  // namespace esphome | ||||
| @@ -157,8 +157,11 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) { | ||||
|     default: | ||||
|       trig = nullptr; | ||||
|   } | ||||
|   assert(trig != nullptr); | ||||
|   trig->trigger(); | ||||
|   if (trig != nullptr) { | ||||
|     trig->trigger(); | ||||
|   } else { | ||||
|     ESP_LOGW(TAG, "trig not set - unsupported action"); | ||||
|   } | ||||
|   this->action = action; | ||||
|   this->prev_trigger_ = trig; | ||||
|   this->publish_state(); | ||||
|   | ||||
| @@ -13,8 +13,10 @@ float bedjet_temp_to_f(const uint8_t temp) { | ||||
|  | ||||
| /** Cleans up the packet before sending. */ | ||||
| BedjetPacket *BedjetCodec::clean_packet_() { | ||||
|   // So far no commands require more than 2 bytes of data. | ||||
|   assert(this->packet_.data_length <= 2); | ||||
|   // So far no commands require more than 2 bytes of data | ||||
|   if (this->packet_.data_length > 2) { | ||||
|     ESP_LOGW(TAG, "Packet may be malformed"); | ||||
|   } | ||||
|   for (int i = this->packet_.data_length; i < 2; i++) { | ||||
|     this->packet_.data[i] = '\0'; | ||||
|   } | ||||
|   | ||||
| @@ -90,7 +90,7 @@ struct BedjetStatusPacket { | ||||
|     int unused_6 : 1;       // 0x4 | ||||
|     bool is_dual_zone : 1;  /// Is part of a Dual Zone configuration | ||||
|     int unused_7 : 1;       // 0x1 | ||||
|   } dual_zone_flags; | ||||
|   } dual_zone_flags;        // NOLINT(clang-diagnostic-unaligned-access) | ||||
|  | ||||
|   uint8_t unused_4 : 8;  // Unknown 23-24 = 0x1310 | ||||
|   uint8_t unused_5 : 8;  // Unknown 23-24 = 0x1310 | ||||
|   | ||||
| @@ -18,10 +18,11 @@ class BinaryLightOutput : public light::LightOutput { | ||||
|   void write_state(light::LightState *state) override { | ||||
|     bool binary; | ||||
|     state->current_values_as_binary(&binary); | ||||
|     if (binary) | ||||
|     if (binary) { | ||||
|       this->output_->turn_on(); | ||||
|     else | ||||
|     } else { | ||||
|       this->output_->turn_off(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   | ||||
| @@ -1,10 +1,8 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.cpp_generator import MockObjClass | ||||
| from esphome.cpp_helpers import setup_entity | ||||
| from esphome import automation, core | ||||
| from esphome.automation import Condition, maybe_simple_id | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import mqtt, web_server | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_DELAY, | ||||
|     CONF_DEVICE_CLASS, | ||||
| @@ -16,6 +14,7 @@ from esphome.const import ( | ||||
|     CONF_INVERTED, | ||||
|     CONF_MAX_LENGTH, | ||||
|     CONF_MIN_LENGTH, | ||||
|     CONF_MQTT_ID, | ||||
|     CONF_ON_CLICK, | ||||
|     CONF_ON_DOUBLE_CLICK, | ||||
|     CONF_ON_MULTI_CLICK, | ||||
| @@ -26,8 +25,7 @@ from esphome.const import ( | ||||
|     CONF_STATE, | ||||
|     CONF_TIMING, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_MQTT_ID, | ||||
|     CONF_WEB_SERVER_ID, | ||||
|     CONF_WEB_SERVER, | ||||
|     DEVICE_CLASS_BATTERY, | ||||
|     DEVICE_CLASS_BATTERY_CHARGING, | ||||
|     DEVICE_CLASS_CARBON_MONOXIDE, | ||||
| @@ -59,6 +57,8 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_WINDOW, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.cpp_generator import MockObjClass | ||||
| from esphome.cpp_helpers import setup_entity | ||||
| from esphome.util import Registry | ||||
|  | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| @@ -543,9 +543,8 @@ async def setup_binary_sensor_core_(var, config): | ||||
|         mqtt_ = cg.new_Pvariable(mqtt_id, var) | ||||
|         await mqtt.register_mqtt_component(mqtt_, config) | ||||
|  | ||||
|     if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: | ||||
|         web_server_ = await cg.get_variable(webserver_id) | ||||
|         web_server.add_entity_to_sorting_list(web_server_, var, config) | ||||
|     if web_server_config := config.get(CONF_WEB_SERVER): | ||||
|         await web_server.add_entity_config(var, web_server_config) | ||||
|  | ||||
|  | ||||
| async def register_binary_sensor(var, config): | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/bl0906/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bl0906/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@athom-tech", "@tarontop", "@jesserockz"] | ||||
							
								
								
									
										238
									
								
								esphome/components/bl0906/bl0906.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								esphome/components/bl0906/bl0906.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,238 @@ | ||||
| #include "bl0906.h" | ||||
| #include "constants.h" | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0906 { | ||||
|  | ||||
| static const char *const TAG = "bl0906"; | ||||
|  | ||||
| constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||
|  | ||||
| constexpr int32_t to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||
|  | ||||
| // The SUM byte is (Addr+Data_L+Data_M+Data_H)&0xFF negated; | ||||
| constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data) { | ||||
|   return (address + data->l + data->m + data->h) ^ 0xFF; | ||||
| } | ||||
|  | ||||
| void BL0906::loop() { | ||||
|   if (this->current_channel_ == UINT8_MAX) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   while (this->available()) | ||||
|     this->flush(); | ||||
|  | ||||
|   if (this->current_channel_ == 0) { | ||||
|     // Temperature | ||||
|     this->read_data_(BL0906_TEMPERATURE, BL0906_TREF, this->temperature_sensor_); | ||||
|   } else if (this->current_channel_ == 1) { | ||||
|     this->read_data_(BL0906_I_1_RMS, BL0906_IREF, this->current_1_sensor_); | ||||
|     this->read_data_(BL0906_WATT_1, BL0906_PREF, this->power_1_sensor_); | ||||
|     this->read_data_(BL0906_CF_1_CNT, BL0906_EREF, this->energy_1_sensor_); | ||||
|   } else if (this->current_channel_ == 2) { | ||||
|     this->read_data_(BL0906_I_2_RMS, BL0906_IREF, this->current_2_sensor_); | ||||
|     this->read_data_(BL0906_WATT_2, BL0906_PREF, this->power_2_sensor_); | ||||
|     this->read_data_(BL0906_CF_2_CNT, BL0906_EREF, this->energy_2_sensor_); | ||||
|   } else if (this->current_channel_ == 3) { | ||||
|     this->read_data_(BL0906_I_3_RMS, BL0906_IREF, this->current_3_sensor_); | ||||
|     this->read_data_(BL0906_WATT_3, BL0906_PREF, this->power_3_sensor_); | ||||
|     this->read_data_(BL0906_CF_3_CNT, BL0906_EREF, this->energy_3_sensor_); | ||||
|   } else if (this->current_channel_ == 4) { | ||||
|     this->read_data_(BL0906_I_4_RMS, BL0906_IREF, this->current_4_sensor_); | ||||
|     this->read_data_(BL0906_WATT_4, BL0906_PREF, this->power_4_sensor_); | ||||
|     this->read_data_(BL0906_CF_4_CNT, BL0906_EREF, this->energy_4_sensor_); | ||||
|   } else if (this->current_channel_ == 5) { | ||||
|     this->read_data_(BL0906_I_5_RMS, BL0906_IREF, this->current_5_sensor_); | ||||
|     this->read_data_(BL0906_WATT_5, BL0906_PREF, this->power_5_sensor_); | ||||
|     this->read_data_(BL0906_CF_5_CNT, BL0906_EREF, this->energy_5_sensor_); | ||||
|   } else if (this->current_channel_ == 6) { | ||||
|     this->read_data_(BL0906_I_6_RMS, BL0906_IREF, this->current_6_sensor_); | ||||
|     this->read_data_(BL0906_WATT_6, BL0906_PREF, this->power_6_sensor_); | ||||
|     this->read_data_(BL0906_CF_6_CNT, BL0906_EREF, this->energy_6_sensor_); | ||||
|   } else if (this->current_channel_ == UINT8_MAX - 2) { | ||||
|     // Frequency | ||||
|     this->read_data_(BL0906_FREQUENCY, BL0906_FREF, frequency_sensor_); | ||||
|     // Voltage | ||||
|     this->read_data_(BL0906_V_RMS, BL0906_UREF, voltage_sensor_); | ||||
|   } else if (this->current_channel_ == UINT8_MAX - 1) { | ||||
|     // Total power | ||||
|     this->read_data_(BL0906_WATT_SUM, BL0906_WATT, this->total_power_sensor_); | ||||
|     // Total Energy | ||||
|     this->read_data_(BL0906_CF_SUM_CNT, BL0906_CF, this->total_energy_sensor_); | ||||
|   } else { | ||||
|     this->current_channel_ = UINT8_MAX - 2;  // Go to frequency and voltage | ||||
|     return; | ||||
|   } | ||||
|   this->current_channel_++; | ||||
|   this->handle_actions_(); | ||||
| } | ||||
|  | ||||
| void BL0906::setup() { | ||||
|   while (this->available()) | ||||
|     this->flush(); | ||||
|   this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE)); | ||||
|   // Calibration (1: register address; 2: value before calibration; 3: value after calibration) | ||||
|   this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0);  // Calibration current_1 | ||||
|   this->bias_correction_(BL0906_RMSOS_2, 0.01500, 0); | ||||
|   this->bias_correction_(BL0906_RMSOS_3, 0.01400, 0); | ||||
|   this->bias_correction_(BL0906_RMSOS_4, 0.01300, 0); | ||||
|   this->bias_correction_(BL0906_RMSOS_5, 0.01200, 0); | ||||
|   this->bias_correction_(BL0906_RMSOS_6, 0.01200, 0);  // Calibration current_6 | ||||
|  | ||||
|   this->write_array(USR_WRPROT_ONLYREAD, sizeof(USR_WRPROT_ONLYREAD)); | ||||
| } | ||||
|  | ||||
| void BL0906::update() { this->current_channel_ = 0; } | ||||
|  | ||||
| size_t BL0906::enqueue_action_(ActionCallbackFuncPtr function) { | ||||
|   this->action_queue_.push_back(function); | ||||
|   return this->action_queue_.size(); | ||||
| } | ||||
|  | ||||
| void BL0906::handle_actions_() { | ||||
|   if (this->action_queue_.empty()) { | ||||
|     return; | ||||
|   } | ||||
|   ActionCallbackFuncPtr ptr_func = nullptr; | ||||
|   for (int i = 0; i < this->action_queue_.size(); i++) { | ||||
|     ptr_func = this->action_queue_[i]; | ||||
|     if (ptr_func) { | ||||
|       ESP_LOGI(TAG, "HandleActionCallback[%d]...", i); | ||||
|       (this->*ptr_func)(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   while (this->available()) { | ||||
|     this->read(); | ||||
|   } | ||||
|  | ||||
|   this->action_queue_.clear(); | ||||
| } | ||||
|  | ||||
| // Reset energy | ||||
| void BL0906::reset_energy_() { | ||||
|   this->write_array(BL0906_INIT[0], 6); | ||||
|   delay(1); | ||||
|   this->flush(); | ||||
|  | ||||
|   ESP_LOGW(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_INIT[0][0], BL0906_INIT[0][1], BL0906_INIT[0][2], | ||||
|            BL0906_INIT[0][3], BL0906_INIT[0][4], BL0906_INIT[0][5]); | ||||
| } | ||||
|  | ||||
| // Read data | ||||
| void BL0906::read_data_(const uint8_t address, const float reference, sensor::Sensor *sensor) { | ||||
|   if (sensor == nullptr) { | ||||
|     return; | ||||
|   } | ||||
|   DataPacket buffer; | ||||
|   ube24_t data_u24; | ||||
|   sbe24_t data_s24; | ||||
|   float value = 0; | ||||
|  | ||||
|   bool signed_result = reference == BL0906_TREF || reference == BL0906_WATT || reference == BL0906_PREF; | ||||
|  | ||||
|   this->write_byte(BL0906_READ_COMMAND); | ||||
|   this->write_byte(address); | ||||
|   if (this->read_array((uint8_t *) &buffer, sizeof(buffer) - 1)) { | ||||
|     if (bl0906_checksum(address, &buffer) == buffer.checksum) { | ||||
|       if (signed_result) { | ||||
|         data_s24.l = buffer.l; | ||||
|         data_s24.m = buffer.m; | ||||
|         data_s24.h = buffer.h; | ||||
|       } else { | ||||
|         data_u24.l = buffer.l; | ||||
|         data_u24.m = buffer.m; | ||||
|         data_u24.h = buffer.h; | ||||
|       } | ||||
|     } else { | ||||
|       ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); | ||||
|       while (read() >= 0) | ||||
|         ; | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|   // Power | ||||
|   if (reference == BL0906_PREF) { | ||||
|     value = (float) to_int32_t(data_s24) * reference; | ||||
|   } | ||||
|  | ||||
|   // Total power | ||||
|   if (reference == BL0906_WATT) { | ||||
|     value = (float) to_int32_t(data_s24) * reference; | ||||
|   } | ||||
|  | ||||
|   // Voltage, current, power, total power | ||||
|   if (reference == BL0906_UREF || reference == BL0906_IREF || reference == BL0906_EREF || reference == BL0906_CF) { | ||||
|     value = (float) to_uint32_t(data_u24) * reference; | ||||
|   } | ||||
|  | ||||
|   // Frequency | ||||
|   if (reference == BL0906_FREF) { | ||||
|     value = reference / (float) to_uint32_t(data_u24); | ||||
|   } | ||||
|   // Chip temperature | ||||
|   if (reference == BL0906_TREF) { | ||||
|     value = (float) to_int32_t(data_s24); | ||||
|     value = (value - 64) * 12.5 / 59 - 40; | ||||
|   } | ||||
|   sensor->publish_state(value); | ||||
| } | ||||
|  | ||||
| // RMS offset correction | ||||
| void BL0906::bias_correction_(uint8_t address, float measurements, float correction) { | ||||
|   DataPacket data; | ||||
|   float ki = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097;  // Current coefficient | ||||
|   float i_rms0 = measurements * ki; | ||||
|   float i_rms = correction * ki; | ||||
|   int32_t value = (i_rms * i_rms - i_rms0 * i_rms0) / 256; | ||||
|   data.l = value << 24 >> 24; | ||||
|   data.m = value << 16 >> 24; | ||||
|   if (value < 0) { | ||||
|     data.h = (value << 8 >> 24) | 0b10000000; | ||||
|   } | ||||
|   data.address = bl0906_checksum(address, &data); | ||||
|   ESP_LOGV(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_WRITE_COMMAND, address, data.l, data.m, data.h, data.address); | ||||
|   this->write_byte(BL0906_WRITE_COMMAND); | ||||
|   this->write_byte(address); | ||||
|   this->write_byte(data.l); | ||||
|   this->write_byte(data.m); | ||||
|   this->write_byte(data.h); | ||||
|   this->write_byte(data.address); | ||||
| } | ||||
|  | ||||
| void BL0906::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "BL0906:"); | ||||
|   LOG_SENSOR("  ", "Voltage", this->voltage_sensor_); | ||||
|  | ||||
|   LOG_SENSOR("  ", "Current1", this->current_1_sensor_); | ||||
|   LOG_SENSOR("  ", "Current2", this->current_2_sensor_); | ||||
|   LOG_SENSOR("  ", "Current3", this->current_3_sensor_); | ||||
|   LOG_SENSOR("  ", "Current4", this->current_4_sensor_); | ||||
|   LOG_SENSOR("  ", "Current5", this->current_5_sensor_); | ||||
|   LOG_SENSOR("  ", "Current6", this->current_6_sensor_); | ||||
|  | ||||
|   LOG_SENSOR("  ", "Power1", this->power_1_sensor_); | ||||
|   LOG_SENSOR("  ", "Power2", this->power_2_sensor_); | ||||
|   LOG_SENSOR("  ", "Power3", this->power_3_sensor_); | ||||
|   LOG_SENSOR("  ", "Power4", this->power_4_sensor_); | ||||
|   LOG_SENSOR("  ", "Power5", this->power_5_sensor_); | ||||
|   LOG_SENSOR("  ", "Power6", this->power_6_sensor_); | ||||
|  | ||||
|   LOG_SENSOR("  ", "Energy1", this->energy_1_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy2", this->energy_2_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy3", this->energy_3_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy4", this->energy_4_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy5", this->energy_5_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy6", this->energy_6_sensor_); | ||||
|  | ||||
|   LOG_SENSOR("  ", "Total Power", this->total_power_sensor_); | ||||
|   LOG_SENSOR("  ", "Total Energy", this->total_energy_sensor_); | ||||
|   LOG_SENSOR("  ", "Frequency", this->frequency_sensor_); | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||
| } | ||||
|  | ||||
| }  // namespace bl0906 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										96
									
								
								esphome/components/bl0906/bl0906.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								esphome/components/bl0906/bl0906.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/datatypes.h" | ||||
|  | ||||
| // https://www.belling.com.cn/media/file_object/bel_product/BL0906/datasheet/BL0906_V1.02_cn.pdf | ||||
| // https://www.belling.com.cn/media/file_object/bel_product/BL0906/guide/BL0906%20APP%20Note_V1.02.pdf | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0906 { | ||||
|  | ||||
| struct DataPacket {  // NOLINT(altera-struct-pack-align) | ||||
|   uint8_t l{0}; | ||||
|   uint8_t m{0}; | ||||
|   uint8_t h{0}; | ||||
|   uint8_t checksum;  // checksum | ||||
|   uint8_t address; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| struct ube24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||
|   uint8_t l{0}; | ||||
|   uint8_t m{0}; | ||||
|   uint8_t h{0}; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| struct sbe24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||
|   uint8_t l{0}; | ||||
|   uint8_t m{0}; | ||||
|   int8_t h{0}; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| template<typename... Ts> class ResetEnergyAction; | ||||
|  | ||||
| class BL0906; | ||||
|  | ||||
| using ActionCallbackFuncPtr = void (BL0906::*)(); | ||||
|  | ||||
| class BL0906 : public PollingComponent, public uart::UARTDevice { | ||||
|   SUB_SENSOR(voltage) | ||||
|   SUB_SENSOR(current_1) | ||||
|   SUB_SENSOR(current_2) | ||||
|   SUB_SENSOR(current_3) | ||||
|   SUB_SENSOR(current_4) | ||||
|   SUB_SENSOR(current_5) | ||||
|   SUB_SENSOR(current_6) | ||||
|   SUB_SENSOR(power_1) | ||||
|   SUB_SENSOR(power_2) | ||||
|   SUB_SENSOR(power_3) | ||||
|   SUB_SENSOR(power_4) | ||||
|   SUB_SENSOR(power_5) | ||||
|   SUB_SENSOR(power_6) | ||||
|   SUB_SENSOR(total_power) | ||||
|   SUB_SENSOR(energy_1) | ||||
|   SUB_SENSOR(energy_2) | ||||
|   SUB_SENSOR(energy_3) | ||||
|   SUB_SENSOR(energy_4) | ||||
|   SUB_SENSOR(energy_5) | ||||
|   SUB_SENSOR(energy_6) | ||||
|   SUB_SENSOR(total_energy) | ||||
|   SUB_SENSOR(frequency) | ||||
|   SUB_SENSOR(temperature) | ||||
|  | ||||
|  public: | ||||
|   void loop() override; | ||||
|  | ||||
|   void update() override; | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   template<typename... Ts> friend class ResetEnergyAction; | ||||
|  | ||||
|   void reset_energy_(); | ||||
|  | ||||
|   void read_data_(uint8_t address, float reference, sensor::Sensor *sensor); | ||||
|  | ||||
|   void bias_correction_(uint8_t address, float measurements, float correction); | ||||
|  | ||||
|   uint8_t current_channel_{0}; | ||||
|   size_t enqueue_action_(ActionCallbackFuncPtr function); | ||||
|   void handle_actions_(); | ||||
|  | ||||
|  private: | ||||
|   std::vector<ActionCallbackFuncPtr> action_queue_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class ResetEnergyAction : public Action<Ts...>, public Parented<BL0906> { | ||||
|  public: | ||||
|   void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); } | ||||
| }; | ||||
|  | ||||
| }  // namespace bl0906 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										4
									
								
								esphome/components/bl0906/const.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								esphome/components/bl0906/const.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| # const.py | ||||
| ICON_ENERGY = "mdi:lightning-bolt" | ||||
| ICON_FREQUENCY = "mdi:cosine-wave" | ||||
| ICON_VOLTAGE = "mdi:sine-wave" | ||||
							
								
								
									
										122
									
								
								esphome/components/bl0906/constants.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								esphome/components/bl0906/constants.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| #pragma once | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0906 { | ||||
|  | ||||
| // Total power conversion | ||||
| static const float BL0906_WATT = 16 * 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / | ||||
|                                  (40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000); | ||||
| // Total Energy conversion | ||||
| static const float BL0906_CF = 16 * 4194304 * 0.032768 * 16 / | ||||
|                                (3600000 * 16 * | ||||
|                                 (40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / | ||||
|                                  (1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000)))); | ||||
| // Frequency conversion | ||||
| static const float BL0906_FREF = 10000000; | ||||
| // Temperature conversion | ||||
| static const float BL0906_TREF = 12.5 / 59 - 40; | ||||
| // Current conversion | ||||
| static const float BL0906_IREF = 1.097 / (12875 * 1 * (5.1 + 5.1) * 1000 / 2000); | ||||
| // Voltage conversion | ||||
| static const float BL0906_UREF = 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / (13162 * 1 * 100 * 1000); | ||||
| // Power conversion | ||||
| static const float BL0906_PREF = 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / | ||||
|                                  (40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000); | ||||
| // Energy conversion | ||||
| static const float BL0906_EREF = 4194304 * 0.032768 * 16 / | ||||
|                                  (3600000 * 16 * | ||||
|                                   (40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / | ||||
|                                    (1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000)))); | ||||
| // Current coefficient | ||||
| static const float BL0906_KI = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097; | ||||
| // Power coefficient | ||||
| static const float BL0906_KP = 40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / 1.097 / 1.097 / | ||||
|                                (20000 + 20000 + 20000 + 20000 + 20000); | ||||
|  | ||||
| static const uint8_t USR_WRPROT_WITABLE[6] = {0xCA, 0x9E, 0x55, 0x55, 0x00, 0xB7}; | ||||
| static const uint8_t USR_WRPROT_ONLYREAD[6] = {0xCA, 0x9E, 0x00, 0x00, 0x00, 0x61}; | ||||
|  | ||||
| static const uint8_t BL0906_READ_COMMAND = 0x35; | ||||
| static const uint8_t BL0906_WRITE_COMMAND = 0xCA; | ||||
|  | ||||
| // Register address | ||||
| // Voltage | ||||
| static const uint8_t BL0906_V_RMS = 0x16; | ||||
|  | ||||
| // Total power | ||||
| static const uint8_t BL0906_WATT_SUM = 0X2C; | ||||
|  | ||||
| // Current1~6 | ||||
| static const uint8_t BL0906_I_1_RMS = 0x0D;  // current_1 | ||||
| static const uint8_t BL0906_I_2_RMS = 0x0E; | ||||
| static const uint8_t BL0906_I_3_RMS = 0x0F; | ||||
| static const uint8_t BL0906_I_4_RMS = 0x10; | ||||
| static const uint8_t BL0906_I_5_RMS = 0x13; | ||||
| static const uint8_t BL0906_I_6_RMS = 0x14;  // current_6 | ||||
|  | ||||
| // Power1~6 | ||||
| static const uint8_t BL0906_WATT_1 = 0X23;  // power_1 | ||||
| static const uint8_t BL0906_WATT_2 = 0X24; | ||||
| static const uint8_t BL0906_WATT_3 = 0X25; | ||||
| static const uint8_t BL0906_WATT_4 = 0X26; | ||||
| static const uint8_t BL0906_WATT_5 = 0X29; | ||||
| static const uint8_t BL0906_WATT_6 = 0X2A;  // power_6 | ||||
|  | ||||
| // Active pulse count, unsigned | ||||
| static const uint8_t BL0906_CF_1_CNT = 0X30;  // Channel_1 | ||||
| static const uint8_t BL0906_CF_2_CNT = 0X31; | ||||
| static const uint8_t BL0906_CF_3_CNT = 0X32; | ||||
| static const uint8_t BL0906_CF_4_CNT = 0X33; | ||||
| static const uint8_t BL0906_CF_5_CNT = 0X36; | ||||
| static const uint8_t BL0906_CF_6_CNT = 0X37;  // Channel_6 | ||||
|  | ||||
| // Total active pulse count, unsigned | ||||
| static const uint8_t BL0906_CF_SUM_CNT = 0X39; | ||||
|  | ||||
| // Voltage frequency cycle | ||||
| static const uint8_t BL0906_FREQUENCY = 0X4E; | ||||
|  | ||||
| // Internal temperature | ||||
| static const uint8_t BL0906_TEMPERATURE = 0X5E; | ||||
|  | ||||
| // Calibration register | ||||
| // RMS gain adjustment register | ||||
| static const uint8_t BL0906_RMSGN_1 = 0x6D;  // Channel_1 | ||||
| static const uint8_t BL0906_RMSGN_2 = 0x6E; | ||||
| static const uint8_t BL0906_RMSGN_3 = 0x6F; | ||||
| static const uint8_t BL0906_RMSGN_4 = 0x70; | ||||
| static const uint8_t BL0906_RMSGN_5 = 0x73; | ||||
| static const uint8_t BL0906_RMSGN_6 = 0x74;  // Channel_6 | ||||
|  | ||||
| // RMS offset correction register | ||||
| static const uint8_t BL0906_RMSOS_1 = 0x78;  // Channel_1 | ||||
| static const uint8_t BL0906_RMSOS_2 = 0x79; | ||||
| static const uint8_t BL0906_RMSOS_3 = 0x7A; | ||||
| static const uint8_t BL0906_RMSOS_4 = 0x7B; | ||||
| static const uint8_t BL0906_RMSOS_5 = 0x7E; | ||||
| static const uint8_t BL0906_RMSOS_6 = 0x7F;  // Channel_6 | ||||
|  | ||||
| // Active power gain adjustment register | ||||
| static const uint8_t BL0906_WATTGN_1 = 0xB7;  // Channel_1 | ||||
| static const uint8_t BL0906_WATTGN_2 = 0xB8; | ||||
| static const uint8_t BL0906_WATTGN_3 = 0xB9; | ||||
| static const uint8_t BL0906_WATTGN_4 = 0xBA; | ||||
| static const uint8_t BL0906_WATTGN_5 = 0xBD; | ||||
| static const uint8_t BL0906_WATTGN_6 = 0xBE;  // Channel_6 | ||||
|  | ||||
| // User write protection setting register, | ||||
| // You must first write 0x5555 to the write protection setting register before writing to other registers. | ||||
| static const uint8_t BL0906_USR_WRPROT = 0x9E; | ||||
|  | ||||
| // Reset Register | ||||
| static const uint8_t BL0906_SOFT_RESET = 0x9F; | ||||
|  | ||||
| const uint8_t BL0906_INIT[2][6] = { | ||||
|     // Reset to default | ||||
|     {BL0906_WRITE_COMMAND, BL0906_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x52}, | ||||
|     // Enable User Operation Write | ||||
|     {BL0906_WRITE_COMMAND, BL0906_USR_WRPROT, 0x55, 0x55, 0x00, 0xB7}}; | ||||
|  | ||||
| }  // namespace bl0906 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										185
									
								
								esphome/components/bl0906/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								esphome/components/bl0906/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| from esphome import automation | ||||
| from esphome.automation import maybe_simple_id | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import sensor, uart | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_CHANNEL, | ||||
|     CONF_CURRENT, | ||||
|     CONF_ENERGY, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_ID, | ||||
|     CONF_NAME, | ||||
|     CONF_POWER, | ||||
|     CONF_TEMPERATURE, | ||||
|     CONF_TOTAL_POWER, | ||||
|     CONF_VOLTAGE, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_FREQUENCY, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ICON_CURRENT_AC, | ||||
|     ICON_POWER, | ||||
|     ICON_THERMOMETER, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     STATE_CLASS_TOTAL_INCREASING, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_HERTZ, | ||||
|     UNIT_KILOWATT_HOURS, | ||||
|     UNIT_VOLT, | ||||
|     UNIT_WATT, | ||||
| ) | ||||
|  | ||||
| # Import ICONS not included in esphome's const.py, from the local components const.py | ||||
| from .const import ICON_ENERGY, ICON_FREQUENCY, ICON_VOLTAGE | ||||
|  | ||||
| DEPENDENCIES = ["uart"] | ||||
| AUTO_LOAD = ["bl0906"] | ||||
| CONF_TOTAL_ENERGY = "total_energy" | ||||
|  | ||||
| bl0906_ns = cg.esphome_ns.namespace("bl0906") | ||||
| BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice) | ||||
| ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BL0906), | ||||
|             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||
|                 icon=ICON_FREQUENCY, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_FREQUENCY, | ||||
|                 unit_of_measurement=UNIT_HERTZ, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 icon=ICON_THERMOMETER, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|                 unit_of_measurement=UNIT_CELSIUS, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||
|                 icon=ICON_VOLTAGE, | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_VOLTAGE, | ||||
|                 unit_of_measurement=UNIT_VOLT, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema( | ||||
|                 icon=ICON_POWER, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_POWER, | ||||
|                 unit_of_measurement=UNIT_WATT, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_TOTAL_ENERGY): sensor.sensor_schema( | ||||
|                 icon=ICON_ENERGY, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend( | ||||
|         cv.Schema( | ||||
|             { | ||||
|                 cv.Optional(f"{CONF_CHANNEL}_{i + 1}"): cv.Schema( | ||||
|                     { | ||||
|                         cv.Optional(CONF_CURRENT): cv.maybe_simple_value( | ||||
|                             sensor.sensor_schema( | ||||
|                                 icon=ICON_CURRENT_AC, | ||||
|                                 accuracy_decimals=3, | ||||
|                                 device_class=DEVICE_CLASS_CURRENT, | ||||
|                                 unit_of_measurement=UNIT_AMPERE, | ||||
|                                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                             ), | ||||
|                             key=CONF_NAME, | ||||
|                         ), | ||||
|                         cv.Optional(CONF_POWER): cv.maybe_simple_value( | ||||
|                             sensor.sensor_schema( | ||||
|                                 icon=ICON_POWER, | ||||
|                                 accuracy_decimals=0, | ||||
|                                 device_class=DEVICE_CLASS_POWER, | ||||
|                                 unit_of_measurement=UNIT_WATT, | ||||
|                                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                             ), | ||||
|                             key=CONF_NAME, | ||||
|                         ), | ||||
|                         cv.Optional(CONF_ENERGY): cv.maybe_simple_value( | ||||
|                             sensor.sensor_schema( | ||||
|                                 icon=ICON_ENERGY, | ||||
|                                 accuracy_decimals=3, | ||||
|                                 device_class=DEVICE_CLASS_ENERGY, | ||||
|                                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||
|                             ), | ||||
|                             key=CONF_NAME, | ||||
|                         ), | ||||
|                     } | ||||
|                 ) | ||||
|                 for i in range(6) | ||||
|             } | ||||
|         ) | ||||
|     ) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| ) | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||
|     "bl0906", baud_rate=19200, require_tx=True, require_rx=True | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "bl0906.reset_energy", | ||||
|     ResetEnergyAction, | ||||
|     maybe_simple_id( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(BL0906), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| async def reset_energy_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     await cg.register_parented(var, config[CONF_ID]) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await uart.register_uart_device(var, config) | ||||
|     if frequency_config := config.get(CONF_FREQUENCY): | ||||
|         sens = await sensor.new_sensor(frequency_config) | ||||
|         cg.add(var.set_frequency_sensor(sens)) | ||||
|     if temperature_config := config.get(CONF_TEMPERATURE): | ||||
|         sens = await sensor.new_sensor(temperature_config) | ||||
|         cg.add(var.set_temperature_sensor(sens)) | ||||
|     if voltage_config := config.get(CONF_VOLTAGE): | ||||
|         sens = await sensor.new_sensor(voltage_config) | ||||
|         cg.add(var.set_voltage_sensor(sens)) | ||||
|  | ||||
|     for i in range(6): | ||||
|         if channel_config := config.get(f"{CONF_CHANNEL}_{i + 1}"): | ||||
|             if current_config := channel_config.get(CONF_CURRENT): | ||||
|                 sens = await sensor.new_sensor(current_config) | ||||
|                 cg.add(getattr(var, f"set_current_{i + 1}_sensor")(sens)) | ||||
|             if power_config := channel_config.get(CONF_POWER): | ||||
|                 sens = await sensor.new_sensor(power_config) | ||||
|                 cg.add(getattr(var, f"set_power_{i + 1}_sensor")(sens)) | ||||
|             if energy_config := channel_config.get(CONF_ENERGY): | ||||
|                 sens = await sensor.new_sensor(energy_config) | ||||
|                 cg.add(getattr(var, f"set_energy_{i + 1}_sensor")(sens)) | ||||
|  | ||||
|     if total_power_config := config.get(CONF_TOTAL_POWER): | ||||
|         sens = await sensor.new_sensor(total_power_config) | ||||
|         cg.add(var.set_total_power_sensor(sens)) | ||||
|  | ||||
|     if total_energy_config := config.get(CONF_TOTAL_ENERGY): | ||||
|         sens = await sensor.new_sensor(total_energy_config) | ||||
|         cg.add(var.set_total_energy_sensor(sens)) | ||||
| @@ -1 +1 @@ | ||||
| CODEOWNERS = ["@dbuezas"] | ||||
| CODEOWNERS = ["@dbuezas", "@dwmw2"] | ||||
|   | ||||
| @@ -2,6 +2,8 @@ | ||||
| #include "esphome/core/log.h" | ||||
| #include <cinttypes> | ||||
|  | ||||
| // Datasheet: https://www.belling.com.cn/media/file_object/bel_product/BL0942/datasheet/BL0942_V1.06_en.pdf | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0942 { | ||||
|  | ||||
| @@ -12,43 +14,64 @@ static const uint8_t BL0942_FULL_PACKET = 0xAA; | ||||
| static const uint8_t BL0942_PACKET_HEADER = 0x55; | ||||
|  | ||||
| static const uint8_t BL0942_WRITE_COMMAND = 0xA8; | ||||
| static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10; | ||||
| static const uint8_t BL0942_REG_MODE = 0x18; | ||||
| static const uint8_t BL0942_REG_SOFT_RESET = 0x19; | ||||
| static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; | ||||
|  | ||||
| static const uint8_t BL0942_REG_I_RMSOS = 0x12; | ||||
| static const uint8_t BL0942_REG_WA_CREEP = 0x14; | ||||
| static const uint8_t BL0942_REG_I_FAST_RMS_TH = 0x15; | ||||
| static const uint8_t BL0942_REG_I_FAST_RMS_CYC = 0x16; | ||||
| static const uint8_t BL0942_REG_FREQ_CYC = 0x17; | ||||
| static const uint8_t BL0942_REG_OT_FUNX = 0x18; | ||||
| static const uint8_t BL0942_REG_MODE = 0x19; | ||||
| static const uint8_t BL0942_REG_SOFT_RESET = 0x1C; | ||||
| static const uint8_t BL0942_REG_USR_WRPROT = 0x1D; | ||||
| static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; | ||||
|  | ||||
| // TODO: Confirm insialisation works as intended | ||||
| const uint8_t BL0942_INIT[5][6] = { | ||||
|     // Reset to default | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, | ||||
|     // Enable User Operation Write | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, | ||||
|     // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, | ||||
|     // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, | ||||
|     // 0x181C = Half cycle, Fast RMS threshold 6172 | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; | ||||
| static const uint32_t BL0942_REG_MODE_RESV = 0x03; | ||||
| static const uint32_t BL0942_REG_MODE_CF_EN = 0x04; | ||||
| static const uint32_t BL0942_REG_MODE_RMS_UPDATE_SEL = 0x08; | ||||
| static const uint32_t BL0942_REG_MODE_FAST_RMS_SEL = 0x10; | ||||
| static const uint32_t BL0942_REG_MODE_AC_FREQ_SEL = 0x20; | ||||
| static const uint32_t BL0942_REG_MODE_CF_CNT_CLR_SEL = 0x40; | ||||
| static const uint32_t BL0942_REG_MODE_CF_CNT_ADD_SEL = 0x80; | ||||
| static const uint32_t BL0942_REG_MODE_UART_RATE_19200 = 0x200; | ||||
| static const uint32_t BL0942_REG_MODE_UART_RATE_38400 = 0x300; | ||||
| static const uint32_t BL0942_REG_MODE_DEFAULT = | ||||
|     BL0942_REG_MODE_RESV | BL0942_REG_MODE_CF_EN | BL0942_REG_MODE_CF_CNT_ADD_SEL; | ||||
|  | ||||
| static const uint32_t BL0942_REG_SOFT_RESET_MAGIC = 0x5a5a5a; | ||||
| static const uint32_t BL0942_REG_USR_WRPROT_MAGIC = 0x55; | ||||
|  | ||||
| // 23-byte packet, 11 bits per byte, 2400 baud: about 105ms | ||||
| static const uint32_t PKT_TIMEOUT_MS = 200; | ||||
|  | ||||
| void BL0942::loop() { | ||||
|   DataPacket buffer; | ||||
|   if (!this->available()) { | ||||
|   int avail = this->available(); | ||||
|  | ||||
|   if (!avail) { | ||||
|     return; | ||||
|   } | ||||
|   if (read_array((uint8_t *) &buffer, sizeof(buffer))) { | ||||
|     if (validate_checksum(&buffer)) { | ||||
|       received_package_(&buffer); | ||||
|   if (avail < sizeof(buffer)) { | ||||
|     if (!this->rx_start_) { | ||||
|       this->rx_start_ = millis(); | ||||
|     } else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) { | ||||
|       ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail); | ||||
|       this->read_array((uint8_t *) &buffer, avail); | ||||
|       this->rx_start_ = 0; | ||||
|     } | ||||
|   } else { | ||||
|     ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); | ||||
|     while (read() >= 0) | ||||
|       ; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (this->read_array((uint8_t *) &buffer, sizeof(buffer))) { | ||||
|     if (this->validate_checksum_(&buffer)) { | ||||
|       this->received_package_(&buffer); | ||||
|     } | ||||
|   } | ||||
|   this->rx_start_ = 0; | ||||
| } | ||||
|  | ||||
| bool BL0942::validate_checksum(DataPacket *data) { | ||||
|   uint8_t checksum = BL0942_READ_COMMAND; | ||||
| bool BL0942::validate_checksum_(DataPacket *data) { | ||||
|   uint8_t checksum = BL0942_READ_COMMAND | this->address_; | ||||
|   // Whole package but checksum | ||||
|   uint8_t *raw = (uint8_t *) data; | ||||
|   for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { | ||||
| @@ -61,17 +84,73 @@ bool BL0942::validate_checksum(DataPacket *data) { | ||||
|   return checksum == data->checksum; | ||||
| } | ||||
|  | ||||
| void BL0942::update() { | ||||
| void BL0942::write_reg_(uint8_t reg, uint32_t val) { | ||||
|   uint8_t pkt[6]; | ||||
|  | ||||
|   this->flush(); | ||||
|   this->write_byte(BL0942_READ_COMMAND); | ||||
|   pkt[0] = BL0942_WRITE_COMMAND | this->address_; | ||||
|   pkt[1] = reg; | ||||
|   pkt[2] = (val & 0xff); | ||||
|   pkt[3] = (val >> 8) & 0xff; | ||||
|   pkt[4] = (val >> 16) & 0xff; | ||||
|   pkt[5] = (pkt[0] + pkt[1] + pkt[2] + pkt[3] + pkt[4]) ^ 0xff; | ||||
|   this->write_array(pkt, 6); | ||||
|   delay(1); | ||||
| } | ||||
|  | ||||
| int BL0942::read_reg_(uint8_t reg) { | ||||
|   union { | ||||
|     uint8_t b[4]; | ||||
|     uint32_le_t le32; | ||||
|   } resp; | ||||
|  | ||||
|   this->write_byte(BL0942_READ_COMMAND | this->address_); | ||||
|   this->write_byte(reg); | ||||
|   this->flush(); | ||||
|   if (this->read_array(resp.b, 4) && | ||||
|       resp.b[3] == | ||||
|           (uint8_t) ((BL0942_READ_COMMAND + this->address_ + reg + resp.b[0] + resp.b[1] + resp.b[2]) ^ 0xff)) { | ||||
|     resp.b[3] = 0; | ||||
|     return resp.le32; | ||||
|   } | ||||
|   return -1; | ||||
| } | ||||
|  | ||||
| void BL0942::update() { | ||||
|   this->write_byte(BL0942_READ_COMMAND | this->address_); | ||||
|   this->write_byte(BL0942_FULL_PACKET); | ||||
| } | ||||
|  | ||||
| void BL0942::setup() { | ||||
|   for (auto *i : BL0942_INIT) { | ||||
|     this->write_array(i, 6); | ||||
|     delay(1); | ||||
|   // If either current or voltage references are set explicitly by the user, | ||||
|   // calculate the power reference from it unless that is also explicitly set. | ||||
|   if ((this->current_reference_set_ || this->voltage_reference_set_) && !this->power_reference_set_) { | ||||
|     this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0 / 305978.0) / 73989.0; | ||||
|     this->power_reference_set_ = true; | ||||
|   } | ||||
|  | ||||
|   // Similarly for energy reference, if the power reference was set by the user | ||||
|   // either implicitly or explicitly. | ||||
|   if (this->power_reference_set_ && !this->energy_reference_set_) { | ||||
|     this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4; | ||||
|     this->energy_reference_set_ = true; | ||||
|   } | ||||
|  | ||||
|   this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC); | ||||
|   if (this->reset_) | ||||
|     this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC); | ||||
|  | ||||
|   uint32_t mode = BL0942_REG_MODE_DEFAULT; | ||||
|   mode |= BL0942_REG_MODE_RMS_UPDATE_SEL; /* 800ms refresh time */ | ||||
|   if (this->line_freq_ == LINE_FREQUENCY_60HZ) | ||||
|     mode |= BL0942_REG_MODE_AC_FREQ_SEL; | ||||
|   this->write_reg_(BL0942_REG_MODE, mode); | ||||
|  | ||||
|   this->write_reg_(BL0942_REG_USR_WRPROT, 0); | ||||
|  | ||||
|   if (this->read_reg_(BL0942_REG_MODE) != mode) | ||||
|     this->status_set_warning("BL0942 setup failed!"); | ||||
|  | ||||
|   this->flush(); | ||||
| } | ||||
|  | ||||
| @@ -82,10 +161,17 @@ void BL0942::received_package_(DataPacket *data) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // cf_cnt is only 24 bits, so track overflows | ||||
|   uint32_t cf_cnt = (uint24_t) data->cf_cnt; | ||||
|   cf_cnt |= this->prev_cf_cnt_ & 0xff000000; | ||||
|   if (cf_cnt < this->prev_cf_cnt_) { | ||||
|     cf_cnt += 0x1000000; | ||||
|   } | ||||
|   this->prev_cf_cnt_ = cf_cnt; | ||||
|  | ||||
|   float v_rms = (uint24_t) data->v_rms / voltage_reference_; | ||||
|   float i_rms = (uint24_t) data->i_rms / current_reference_; | ||||
|   float watt = (int24_t) data->watt / power_reference_; | ||||
|   uint32_t cf_cnt = (uint24_t) data->cf_cnt; | ||||
|   float total_energy_consumption = cf_cnt / energy_reference_; | ||||
|   float frequency = 1000000.0f / data->frequency; | ||||
|  | ||||
| @@ -104,18 +190,25 @@ void BL0942::received_package_(DataPacket *data) { | ||||
|   if (frequency_sensor_ != nullptr) { | ||||
|     frequency_sensor_->publish_state(frequency); | ||||
|   } | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
|   ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms, | ||||
|            watt, cf_cnt, total_energy_consumption, frequency, data->status); | ||||
| } | ||||
|  | ||||
| void BL0942::dump_config() {  // NOLINT(readability-function-cognitive-complexity) | ||||
|   ESP_LOGCONFIG(TAG, "BL0942:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Reset: %s", TRUEFALSE(this->reset_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Address: %d", this->address_); | ||||
|   ESP_LOGCONFIG(TAG, "  Nominal line frequency: %d Hz", this->line_freq_); | ||||
|   ESP_LOGCONFIG(TAG, "  Current reference: %f", this->current_reference_); | ||||
|   ESP_LOGCONFIG(TAG, "  Energy reference: %f", this->energy_reference_); | ||||
|   ESP_LOGCONFIG(TAG, "  Power reference: %f", this->power_reference_); | ||||
|   ESP_LOGCONFIG(TAG, "  Voltage reference: %f", this->voltage_reference_); | ||||
|   LOG_SENSOR("", "Voltage", this->voltage_sensor_); | ||||
|   LOG_SENSOR("", "Current", this->current_sensor_); | ||||
|   LOG_SENSOR("", "Power", this->power_sensor_); | ||||
|   LOG_SENSOR("", "Energy", this->energy_sensor_); | ||||
|   LOG_SENSOR("", "frequency", this->frequency_sensor_); | ||||
|   LOG_SENSOR("", "Frequency", this->frequency_sensor_); | ||||
| } | ||||
|  | ||||
| }  // namespace bl0942 | ||||
|   | ||||
| @@ -8,6 +8,57 @@ | ||||
| namespace esphome { | ||||
| namespace bl0942 { | ||||
|  | ||||
| // The BL0942 IC is "calibration-free", which means that it doesn't care | ||||
| // at all about calibration, and that's left to software. It measures a | ||||
| // voltage differential on its IP/IN pins which linearly proportional to | ||||
| // the current flow, and another on its VP pin which is proportional to | ||||
| // the line voltage. It never knows the actual calibration; the values | ||||
| // it reports are solely in terms of those inputs. | ||||
| // | ||||
| // The datasheet refers to the input voltages as I(A) and V(V), both | ||||
| // in millivolts. It measures them against a reference voltage Vref, | ||||
| // which is typically 1.218V (but that absolute value is meaningless | ||||
| // without the actual calibration anyway). | ||||
| // | ||||
| // The reported I_RMS value is 305978 I(A)/Vref, and the reported V_RMS | ||||
| // value is 73989 V(V)/Vref. So we can calibrate those by applying a | ||||
| // simple meter with a resistive load. | ||||
| // | ||||
| // The chip also measures the phase difference between voltage and | ||||
| // current, and uses it to calculate the power factor (cos φ). It | ||||
| // reports the WATT value of 3537 * I_RMS * V_RMS * cos φ). | ||||
| // | ||||
| // It also integrates total energy based on the WATT value. The time for | ||||
| // one CF_CNT pulse is 1638.4*256 / WATT. | ||||
| // | ||||
| // So... how do we calibrate that? | ||||
| // | ||||
| // Using a simple resistive load and an external meter, we can measure | ||||
| // the true voltage and current for a given V_RMS and I_RMS reading, | ||||
| // to calculate BL0942_UREF and BL0942_IREF. Those are in units of | ||||
| // "305978 counts per amp" or "73989 counts per volt" respectively. | ||||
| // | ||||
| // We can derive BL0942_PREF from those. Let's eliminate the weird | ||||
| // factors and express the calibration in plain counts per volt/amp: | ||||
| // UREF1 = UREF/73989, IREF1 = IREF/305978. | ||||
| // | ||||
| // Next... the true power in Watts is V * I * cos φ, so that's equal | ||||
| // to WATT/3537 * IREF1 * UREF1. Which means | ||||
| // BL0942_PREF = BL0942_UREF * BL0942_IREF * 3537 / 305978 / 73989. | ||||
| // | ||||
| // Finally the accumulated energy. The period of a CF_CNT count is | ||||
| // 1638.4*256 / WATT seconds, or 419230.4 / WATT seconds. Which means | ||||
| // the energy represented by a CN_CNT pulse is 419230.4 WATT-seconds. | ||||
| // Factoring in the calibration, that's 419230.4 / BL0942_PREF actual | ||||
| // Watt-seconds (or Joules, as the physicists like to call them). | ||||
| // | ||||
| // But we're not being physicists today; we we're being engineers, so | ||||
| // we want to convert to kWh instead. Which we do by dividing by 1000 | ||||
| // and then by 3600, so the energy in kWh is | ||||
| // CF_CNT * 419230.4 / BL0942_PREF / 3600000 | ||||
| // | ||||
| // Which makes BL0952_EREF = BL0942_PREF * 3600000 / 419430.4 | ||||
|  | ||||
| static const float BL0942_PREF = 596;              // taken from tasmota | ||||
| static const float BL0942_UREF = 15873.35944299;   // should be 73989/1.218 | ||||
| static const float BL0942_IREF = 251213.46469622;  // 305978/1.218 | ||||
| @@ -28,6 +79,11 @@ struct DataPacket { | ||||
|   uint8_t checksum; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| enum LineFrequency : uint8_t { | ||||
|   LINE_FREQUENCY_50HZ = 50, | ||||
|   LINE_FREQUENCY_60HZ = 60, | ||||
| }; | ||||
|  | ||||
| class BL0942 : public PollingComponent, public uart::UARTDevice { | ||||
|  public: | ||||
|   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } | ||||
| @@ -35,9 +91,27 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { | ||||
|   void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } | ||||
|   void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } | ||||
|   void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } | ||||
|   void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; } | ||||
|   void set_address(uint8_t address) { this->address_ = address; } | ||||
|   void set_reset(bool reset) { this->reset_ = reset; } | ||||
|   void set_current_reference(float current_ref) { | ||||
|     this->current_reference_ = current_ref; | ||||
|     this->current_reference_set_ = true; | ||||
|   } | ||||
|   void set_energy_reference(float energy_ref) { | ||||
|     this->energy_reference_ = energy_ref; | ||||
|     this->energy_reference_set_ = true; | ||||
|   } | ||||
|   void set_power_reference(float power_ref) { | ||||
|     this->power_reference_ = power_ref; | ||||
|     this->power_reference_set_ = true; | ||||
|   } | ||||
|   void set_voltage_reference(float voltage_ref) { | ||||
|     this->voltage_reference_ = voltage_ref; | ||||
|     this->voltage_reference_set_ = true; | ||||
|   } | ||||
|  | ||||
|   void loop() override; | ||||
|  | ||||
|   void update() override; | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
| @@ -53,15 +127,25 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { | ||||
|  | ||||
|   // Divide by this to turn into Watt | ||||
|   float power_reference_ = BL0942_PREF; | ||||
|   bool power_reference_set_ = false; | ||||
|   // Divide by this to turn into Volt | ||||
|   float voltage_reference_ = BL0942_UREF; | ||||
|   bool voltage_reference_set_ = false; | ||||
|   // Divide by this to turn into Ampere | ||||
|   float current_reference_ = BL0942_IREF; | ||||
|   bool current_reference_set_ = false; | ||||
|   // Divide by this to turn into kWh | ||||
|   float energy_reference_ = BL0942_EREF; | ||||
|   bool energy_reference_set_ = false; | ||||
|   uint8_t address_ = 0; | ||||
|   bool reset_ = false; | ||||
|   LineFrequency line_freq_ = LINE_FREQUENCY_50HZ; | ||||
|   uint32_t rx_start_ = 0; | ||||
|   uint32_t prev_cf_cnt_ = 0; | ||||
|  | ||||
|   static bool validate_checksum(DataPacket *data); | ||||
|  | ||||
|   bool validate_checksum_(DataPacket *data); | ||||
|   int read_reg_(uint8_t reg); | ||||
|   void write_reg_(uint8_t reg, uint32_t val); | ||||
|   void received_package_(DataPacket *data); | ||||
| }; | ||||
| }  // namespace bl0942 | ||||
|   | ||||
| @@ -1,32 +1,46 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, uart | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ADDRESS, | ||||
|     CONF_CURRENT, | ||||
|     CONF_ENERGY, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_ID, | ||||
|     CONF_LINE_FREQUENCY, | ||||
|     CONF_POWER, | ||||
|     CONF_VOLTAGE, | ||||
|     CONF_FREQUENCY, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_FREQUENCY, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     DEVICE_CLASS_FREQUENCY, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     STATE_CLASS_TOTAL_INCREASING, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_HERTZ, | ||||
|     UNIT_KILOWATT_HOURS, | ||||
|     UNIT_VOLT, | ||||
|     UNIT_WATT, | ||||
|     UNIT_HERTZ, | ||||
|     STATE_CLASS_TOTAL_INCREASING, | ||||
| ) | ||||
|  | ||||
| CONF_CURRENT_REFERENCE = "current_reference" | ||||
| CONF_ENERGY_REFERENCE = "energy_reference" | ||||
| CONF_POWER_REFERENCE = "power_reference" | ||||
| CONF_RESET = "reset" | ||||
| CONF_VOLTAGE_REFERENCE = "voltage_reference" | ||||
|  | ||||
| DEPENDENCIES = ["uart"] | ||||
|  | ||||
| bl0942_ns = cg.esphome_ns.namespace("bl0942") | ||||
| BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice) | ||||
|  | ||||
| LineFrequency = bl0942_ns.enum("LineFrequency") | ||||
| LINE_FREQS = { | ||||
|     50: LineFrequency.LINE_FREQUENCY_50HZ, | ||||
|     60: LineFrequency.LINE_FREQUENCY_60HZ, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
| @@ -45,22 +59,35 @@ CONFIG_SCHEMA = ( | ||||
|             ), | ||||
|             cv.Optional(CONF_POWER): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_WATT, | ||||
|                 accuracy_decimals=0, | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_POWER, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_ENERGY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                 accuracy_decimals=0, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||
|             ), | ||||
|             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_HERTZ, | ||||
|                 accuracy_decimals=0, | ||||
|                 accuracy_decimals=2, | ||||
|                 device_class=DEVICE_CLASS_FREQUENCY, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_LINE_FREQUENCY, default="50HZ"): cv.All( | ||||
|                 cv.frequency, | ||||
|                 cv.enum( | ||||
|                     LINE_FREQS, | ||||
|                     int=True, | ||||
|                 ), | ||||
|             ), | ||||
|             cv.Optional(CONF_ADDRESS, default=0): cv.int_range(min=0, max=3), | ||||
|             cv.Optional(CONF_RESET, default=True): cv.boolean, | ||||
|             cv.Optional(CONF_CURRENT_REFERENCE): cv.float_, | ||||
|             cv.Optional(CONF_ENERGY_REFERENCE): cv.float_, | ||||
|             cv.Optional(CONF_POWER_REFERENCE): cv.float_, | ||||
|             cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| @@ -88,3 +115,14 @@ async def to_code(config): | ||||
|     if frequency_config := config.get(CONF_FREQUENCY): | ||||
|         sens = await sensor.new_sensor(frequency_config) | ||||
|         cg.add(var.set_frequency_sensor(sens)) | ||||
|     cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) | ||||
|     cg.add(var.set_address(config[CONF_ADDRESS])) | ||||
|     cg.add(var.set_reset(config[CONF_RESET])) | ||||
|     if (current_reference := config.get(CONF_CURRENT_REFERENCE, None)) is not None: | ||||
|         cg.add(var.set_current_reference(current_reference)) | ||||
|     if (voltage_reference := config.get(CONF_VOLTAGE_REFERENCE, None)) is not None: | ||||
|         cg.add(var.set_voltage_reference(voltage_reference)) | ||||
|     if (power_reference := config.get(CONF_POWER_REFERENCE, None)) is not None: | ||||
|         cg.add(var.set_power_reference(power_reference)) | ||||
|     if (energy_reference := config.get(CONF_ENERGY_REFERENCE, None)) is not None: | ||||
|         cg.add(var.set_energy_reference(energy_reference)) | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.automation import maybe_simple_id | ||||
| from esphome.components import esp32_ble_tracker, esp32_ble_client | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import esp32_ble_client, esp32_ble_tracker | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_CHARACTERISTIC_UUID, | ||||
|     CONF_ID, | ||||
| @@ -13,7 +14,6 @@ from esphome.const import ( | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_VALUE, | ||||
| ) | ||||
| from esphome import automation | ||||
|  | ||||
| AUTO_LOAD = ["esp32_ble_client"] | ||||
| CODEOWNERS = ["@buxtronix", "@clydebarrow"] | ||||
| @@ -65,9 +65,7 @@ CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" | ||||
| CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" | ||||
| CONF_AUTO_CONNECT = "auto_connect" | ||||
|  | ||||
| # Espressif platformio framework is built with MAX_BLE_CONN to 3, so | ||||
| # enforce this in yaml checks. | ||||
| MULTI_CONF = 3 | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import ble_client, esp32_ble_tracker, output | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID | ||||
|  | ||||
| from .. import ble_client_ns | ||||
|   | ||||
| @@ -1,17 +1,18 @@ | ||||
| from esphome import automation | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import ble_client, esp32_ble_tracker, sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, ble_client, esp32_ble_tracker | ||||
| from esphome.const import ( | ||||
|     CONF_CHARACTERISTIC_UUID, | ||||
|     CONF_LAMBDA, | ||||
|     CONF_SERVICE_UUID, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_TYPE, | ||||
|     CONF_SERVICE_UUID, | ||||
|     DEVICE_CLASS_SIGNAL_STRENGTH, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_DECIBEL_MILLIWATT, | ||||
| ) | ||||
| from esphome import automation | ||||
|  | ||||
| from .. import ble_client_ns | ||||
|  | ||||
| DEPENDENCIES = ["ble_client"] | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import ble_client, switch | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import switch, ble_client | ||||
| from esphome.const import ICON_BLUETOOTH | ||||
|  | ||||
| from .. import ble_client_ns | ||||
|  | ||||
| BLEClientSwitch = ble_client_ns.class_( | ||||
|   | ||||
| @@ -1,13 +1,14 @@ | ||||
| from esphome import automation | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import ble_client, esp32_ble_tracker, text_sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import text_sensor, ble_client, esp32_ble_tracker | ||||
| from esphome.const import ( | ||||
|     CONF_CHARACTERISTIC_UUID, | ||||
|     CONF_ID, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_SERVICE_UUID, | ||||
|     CONF_TRIGGER_ID, | ||||
| ) | ||||
| from esphome import automation | ||||
|  | ||||
| from .. import ble_client_ns | ||||
|  | ||||
| DEPENDENCIES = ["ble_client"] | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import binary_sensor, esp32_ble_tracker | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_MAC_ADDRESS, | ||||
|     CONF_SERVICE_UUID, | ||||
|     CONF_IBEACON_MAJOR, | ||||
|     CONF_IBEACON_MINOR, | ||||
|     CONF_IBEACON_UUID, | ||||
|     CONF_MAC_ADDRESS, | ||||
|     CONF_MIN_RSSI, | ||||
|     CONF_SERVICE_UUID, | ||||
|     CONF_TIMEOUT, | ||||
| ) | ||||
|  | ||||
| @@ -41,7 +41,7 @@ CONFIG_SCHEMA = cv.All( | ||||
|             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||
|             cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, | ||||
|             cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, | ||||
|             cv.Optional(CONF_IBEACON_UUID): cv.uuid, | ||||
|             cv.Optional(CONF_IBEACON_UUID): esp32_ble_tracker.bt_uuid, | ||||
|             cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period, | ||||
|             cv.Optional(CONF_MIN_RSSI): cv.All( | ||||
|                 cv.decibel, cv.int_range(min=-100, max=-30) | ||||
| @@ -83,7 +83,7 @@ async def to_code(config): | ||||
|             cg.add(var.set_service_uuid128(uuid128)) | ||||
|  | ||||
|     if ibeacon_uuid := config.get(CONF_IBEACON_UUID): | ||||
|         ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid)) | ||||
|         ibeacon_uuid = esp32_ble_tracker.as_reversed_hex_array(ibeacon_uuid) | ||||
|         cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) | ||||
|  | ||||
|         if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None: | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import esp32_ble_tracker, sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, esp32_ble_tracker | ||||
| from esphome.const import ( | ||||
|     CONF_IBEACON_MAJOR, | ||||
|     CONF_IBEACON_MINOR, | ||||
|     CONF_IBEACON_UUID, | ||||
|     CONF_SERVICE_UUID, | ||||
|     CONF_MAC_ADDRESS, | ||||
|     CONF_SERVICE_UUID, | ||||
|     DEVICE_CLASS_SIGNAL_STRENGTH, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_DECIBEL_MILLIWATT, | ||||
| @@ -45,7 +45,7 @@ CONFIG_SCHEMA = cv.All( | ||||
|             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||
|             cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, | ||||
|             cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, | ||||
|             cv.Optional(CONF_IBEACON_UUID): cv.uuid, | ||||
|             cv.Optional(CONF_IBEACON_UUID): esp32_ble_tracker.bt_uuid, | ||||
|         } | ||||
|     ) | ||||
|     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||||
| @@ -79,7 +79,7 @@ async def to_code(config): | ||||
|             cg.add(var.set_service_uuid128(uuid128)) | ||||
|  | ||||
|     if ibeacon_uuid := config.get(CONF_IBEACON_UUID): | ||||
|         ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid)) | ||||
|         ibeacon_uuid = esp32_ble_tracker.as_reversed_hex_array(ibeacon_uuid) | ||||
|         cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) | ||||
|  | ||||
|         if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None: | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import esp32_ble_tracker, text_sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import text_sensor, esp32_ble_tracker | ||||
|  | ||||
| DEPENDENCIES = ["esp32_ble_tracker"] | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| from esphome.components import esp32_ble_tracker, esp32_ble_client | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
| from esphome.const import CONF_ACTIVE, CONF_ID | ||||
| from esphome.components import esp32_ble_client, esp32_ble_tracker | ||||
| from esphome.components.esp32 import add_idf_sdkconfig_option | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ACTIVE, CONF_ID | ||||
|  | ||||
| AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"] | ||||
| DEPENDENCIES = ["api", "esp32"] | ||||
|   | ||||
| @@ -54,6 +54,9 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p | ||||
|     } | ||||
|  | ||||
|     resp.advertisements.push_back(std::move(adv)); | ||||
|  | ||||
|     ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0], | ||||
|              result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi); | ||||
|   } | ||||
|   ESP_LOGV(TAG, "Proxying %d packets", count); | ||||
|   this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp); | ||||
| @@ -87,6 +90,8 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi | ||||
| void BluetoothProxy::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Active: %s", YESNO(this->active_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Connections: %d", this->connections_.size()); | ||||
|   ESP_LOGCONFIG(TAG, "  Raw advertisements: %s", YESNO(this->raw_advertisements_)); | ||||
| } | ||||
|  | ||||
| int BluetoothProxy::get_bluetooth_connections_free() { | ||||
|   | ||||
							
								
								
									
										196
									
								
								esphome/components/bme68x_bsec2/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								esphome/components/bme68x_bsec2/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | ||||
| import hashlib | ||||
| from pathlib import Path | ||||
|  | ||||
| from esphome import core, external_files | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_MODEL, | ||||
|     CONF_RAW_DATA_ID, | ||||
|     CONF_SAMPLE_RATE, | ||||
|     CONF_TEMPERATURE_OFFSET, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@neffs", "@kbx81"] | ||||
|  | ||||
| DOMAIN = "bme68x_bsec2" | ||||
|  | ||||
| BSEC2_LIBRARY_VERSION = "v1.8.2610" | ||||
|  | ||||
| CONF_ALGORITHM_OUTPUT = "algorithm_output" | ||||
| CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id" | ||||
| CONF_IAQ_MODE = "iaq_mode" | ||||
| CONF_OPERATING_AGE = "operating_age" | ||||
| CONF_STATE_SAVE_INTERVAL = "state_save_interval" | ||||
| CONF_SUPPLY_VOLTAGE = "supply_voltage" | ||||
|  | ||||
| bme68x_bsec2_ns = cg.esphome_ns.namespace("bme68x_bsec2") | ||||
| BME68xBSEC2Component = bme68x_bsec2_ns.class_("BME68xBSEC2Component", cg.Component) | ||||
|  | ||||
|  | ||||
| MODEL_OPTIONS = ["bme680", "bme688"] | ||||
|  | ||||
| AlgorithmOutput = bme68x_bsec2_ns.enum("AlgorithmOutput") | ||||
| ALGORITHM_OUTPUT_OPTIONS = { | ||||
|     "classification": AlgorithmOutput.ALGORITHM_OUTPUT_CLASSIFICATION, | ||||
|     "regression": AlgorithmOutput.ALGORITHM_OUTPUT_REGRESSION, | ||||
| } | ||||
|  | ||||
| OperatingAge = bme68x_bsec2_ns.enum("OperatingAge") | ||||
| OPERATING_AGE_OPTIONS = { | ||||
|     "4d": OperatingAge.OPERATING_AGE_4D, | ||||
|     "28d": OperatingAge.OPERATING_AGE_28D, | ||||
| } | ||||
|  | ||||
| SampleRate = bme68x_bsec2_ns.enum("SampleRate") | ||||
| SAMPLE_RATE_OPTIONS = { | ||||
|     "LP": SampleRate.SAMPLE_RATE_LP, | ||||
|     "ULP": SampleRate.SAMPLE_RATE_ULP, | ||||
| } | ||||
|  | ||||
| Voltage = bme68x_bsec2_ns.enum("Voltage") | ||||
| VOLTAGE_OPTIONS = { | ||||
|     "1.8V": Voltage.VOLTAGE_1_8V, | ||||
|     "3.3V": Voltage.VOLTAGE_3_3V, | ||||
| } | ||||
|  | ||||
| ALGORITHM_OUTPUT_FILE_NAME = { | ||||
|     "classification": "sel", | ||||
|     "regression": "reg", | ||||
| } | ||||
|  | ||||
| SAMPLE_RATE_FILE_NAME = { | ||||
|     "LP": "3s", | ||||
|     "ULP": "300s", | ||||
| } | ||||
|  | ||||
| VOLTAGE_FILE_NAME = { | ||||
|     "1.8V": "18v", | ||||
|     "3.3V": "33v", | ||||
| } | ||||
|  | ||||
|  | ||||
| def _compute_local_file_path(url: str) -> Path: | ||||
|     h = hashlib.new("sha256") | ||||
|     h.update(url.encode()) | ||||
|     key = h.hexdigest()[:8] | ||||
|     base_dir = external_files.compute_local_file_dir(DOMAIN) | ||||
|     return base_dir / key | ||||
|  | ||||
|  | ||||
| def _compute_url(config: dict) -> str: | ||||
|     model = config.get(CONF_MODEL) | ||||
|     operating_age = config.get(CONF_OPERATING_AGE) | ||||
|     sample_rate = SAMPLE_RATE_FILE_NAME[config.get(CONF_SAMPLE_RATE)] | ||||
|     volts = VOLTAGE_FILE_NAME[config.get(CONF_SUPPLY_VOLTAGE)] | ||||
|     if model == "bme688": | ||||
|         algo = ALGORITHM_OUTPUT_FILE_NAME[ | ||||
|             config.get(CONF_ALGORITHM_OUTPUT, "classification") | ||||
|         ] | ||||
|         filename = "bsec_selectivity" | ||||
|     else: | ||||
|         algo = "iaq" | ||||
|         filename = "bsec_iaq" | ||||
|     return f"https://raw.githubusercontent.com/boschsensortec/Bosch-BSEC2-Library/{BSEC2_LIBRARY_VERSION}/src/config/{model}/{model}_{algo}_{volts}_{sample_rate}_{operating_age}/{filename}.txt" | ||||
|  | ||||
|  | ||||
| def download_bme68x_blob(config): | ||||
|     url = _compute_url(config) | ||||
|     path = _compute_local_file_path(url) | ||||
|     external_files.download_content(url, path) | ||||
|  | ||||
|     return config | ||||
|  | ||||
|  | ||||
| def validate_bme68x(config): | ||||
|     if CONF_ALGORITHM_OUTPUT not in config: | ||||
|         return config | ||||
|  | ||||
|     if config[CONF_MODEL] != "bme688": | ||||
|         raise cv.Invalid(f"{CONF_ALGORITHM_OUTPUT} is only valid for BME688") | ||||
|  | ||||
|     if config[CONF_ALGORITHM_OUTPUT] == "regression" and ( | ||||
|         config[CONF_OPERATING_AGE] != "4d" | ||||
|         or config[CONF_SAMPLE_RATE] != "ULP" | ||||
|         or config[CONF_SUPPLY_VOLTAGE] != "1.8V" | ||||
|     ): | ||||
|         raise cv.Invalid( | ||||
|             f" To use '{CONF_ALGORITHM_OUTPUT}: regression', {CONF_OPERATING_AGE} must be '4d', {CONF_SAMPLE_RATE} must be 'ULP' and {CONF_SUPPLY_VOLTAGE} must be '1.8V'" | ||||
|         ) | ||||
|     return config | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA_BASE = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BME68xBSEC2Component), | ||||
|             cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||
|             cv.Required(CONF_MODEL): cv.one_of(*MODEL_OPTIONS, lower=True), | ||||
|             cv.Optional(CONF_ALGORITHM_OUTPUT): cv.enum( | ||||
|                 ALGORITHM_OUTPUT_OPTIONS, lower=True | ||||
|             ), | ||||
|             cv.Optional(CONF_OPERATING_AGE, default="28d"): cv.enum( | ||||
|                 OPERATING_AGE_OPTIONS, lower=True | ||||
|             ), | ||||
|             cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( | ||||
|                 SAMPLE_RATE_OPTIONS, upper=True | ||||
|             ), | ||||
|             cv.Optional(CONF_SUPPLY_VOLTAGE, default="3.3V"): cv.enum( | ||||
|                 VOLTAGE_OPTIONS, upper=True | ||||
|             ), | ||||
|             cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature, | ||||
|             cv.Optional( | ||||
|                 CONF_STATE_SAVE_INTERVAL, default="6hours" | ||||
|             ): cv.positive_time_period_minutes, | ||||
|         }, | ||||
|     ) | ||||
|     .add_extra(cv.only_with_arduino) | ||||
|     .add_extra(validate_bme68x) | ||||
|     .add_extra(download_bme68x_blob) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code_base(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     if algo_output := config.get(CONF_ALGORITHM_OUTPUT): | ||||
|         cg.add(var.set_algorithm_output(algo_output)) | ||||
|     cg.add(var.set_operating_age(config[CONF_OPERATING_AGE])) | ||||
|     cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) | ||||
|     cg.add(var.set_voltage(config[CONF_SUPPLY_VOLTAGE])) | ||||
|     cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) | ||||
|     cg.add( | ||||
|         var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) | ||||
|     ) | ||||
|  | ||||
|     path = _compute_local_file_path(_compute_url(config)) | ||||
|  | ||||
|     try: | ||||
|         with open(path, encoding="utf-8") as f: | ||||
|             bsec2_iaq_config = f.read() | ||||
|     except Exception as e: | ||||
|         raise core.EsphomeError(f"Could not open binary configuration file {path}: {e}") | ||||
|  | ||||
|     # Convert retrieved BSEC2 config to an array of ints | ||||
|     rhs = [int(x) for x in bsec2_iaq_config.split(",")] | ||||
|     # Create an array which will reside in program memory and configure the sensor instance to use it | ||||
|     bsec2_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||
|     cg.add(var.set_bsec2_configuration(bsec2_arr, len(rhs))) | ||||
|  | ||||
|     # Although this component does not use SPI, the BSEC2 library requires the SPI library | ||||
|     cg.add_library("SPI", None) | ||||
|     cg.add_library( | ||||
|         "BME68x Sensor library", | ||||
|         "1.1.40407", | ||||
|     ) | ||||
|     cg.add_library( | ||||
|         "BSEC2 Software Library", | ||||
|         None, | ||||
|         f"https://github.com/boschsensortec/Bosch-BSEC2-Library.git#{BSEC2_LIBRARY_VERSION}", | ||||
|     ) | ||||
|  | ||||
|     cg.add_define("USE_BSEC2") | ||||
|  | ||||
|     return var | ||||
							
								
								
									
										523
									
								
								esphome/components/bme68x_bsec2/bme68x_bsec2.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										523
									
								
								esphome/components/bme68x_bsec2/bme68x_bsec2.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,523 @@ | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef USE_BSEC2 | ||||
| #include "bme68x_bsec2.h" | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bme68x_bsec2 { | ||||
|  | ||||
| #define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression") | ||||
| #define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days") | ||||
| #define BME68X_BSEC2_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP")) | ||||
| #define BME68X_BSEC2_VOLTAGE_LOG(v) (v == VOLTAGE_3_3V ? "3.3V" : "1.8V") | ||||
|  | ||||
| static const char *const TAG = "bme68x_bsec2.sensor"; | ||||
|  | ||||
| static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; | ||||
|  | ||||
| void BME68xBSEC2Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up BME68X via BSEC2..."); | ||||
|  | ||||
|   this->bsec_status_ = bsec_init_m(&this->bsec_instance_); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     this->mark_failed(); | ||||
|     ESP_LOGE(TAG, "bsec_init_m failed: status %d", this->bsec_status_); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   bsec_get_version_m(&this->bsec_instance_, &this->version_); | ||||
|  | ||||
|   this->bme68x_status_ = bme68x_init(&this->bme68x_); | ||||
|   if (this->bme68x_status_ != BME68X_OK) { | ||||
|     this->mark_failed(); | ||||
|     ESP_LOGE(TAG, "bme68x_init failed: status %d", this->bme68x_status_); | ||||
|     return; | ||||
|   } | ||||
|   if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) { | ||||
|     this->set_config_(this->bsec2_configuration_, this->bsec2_configuration_length_); | ||||
|     if (this->bsec_status_ != BSEC_OK) { | ||||
|       this->mark_failed(); | ||||
|       ESP_LOGE(TAG, "bsec_set_configuration_m failed: status %d", this->bsec_status_); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   this->update_subscription_(); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     this->mark_failed(); | ||||
|     ESP_LOGE(TAG, "bsec_update_subscription_m failed: status %d", this->bsec_status_); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->load_state_(); | ||||
| } | ||||
|  | ||||
| void BME68xBSEC2Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "BME68X via BSEC2:"); | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "  BSEC2 version: %d.%d.%d.%d", this->version_.major, this->version_.minor, | ||||
|                 this->version_.major_bugfix, this->version_.minor_bugfix); | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "  BSEC2 configuration blob:"); | ||||
|   ESP_LOGCONFIG(TAG, "    Configured: %s", YESNO(this->bsec2_blob_configured_)); | ||||
|   if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) { | ||||
|     ESP_LOGCONFIG(TAG, "    Size: %" PRIu32, this->bsec2_configuration_length_); | ||||
|   } | ||||
|  | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, "Communication failed (BSEC2 status: %d, BME68X status: %d)", this->bsec_status_, | ||||
|              this->bme68x_status_); | ||||
|   } | ||||
|  | ||||
|   if (this->algorithm_output_ != ALGORITHM_OUTPUT_IAQ) { | ||||
|     ESP_LOGCONFIG(TAG, "  Algorithm output: %s", BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(this->algorithm_output_)); | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  Operating age: %s", BME68X_BSEC2_OPERATING_AGE_LOG(this->operating_age_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->sample_rate_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Voltage: %s", BME68X_BSEC2_VOLTAGE_LOG(this->voltage_)); | ||||
|   ESP_LOGCONFIG(TAG, "  State save interval: %ims", this->state_save_interval_ms_); | ||||
|   ESP_LOGCONFIG(TAG, "  Temperature offset: %.2f", this->temperature_offset_); | ||||
|  | ||||
| #ifdef USE_SENSOR | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||
|   ESP_LOGCONFIG(TAG, "    Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->temperature_sample_rate_)); | ||||
|   LOG_SENSOR("  ", "Pressure", this->pressure_sensor_); | ||||
|   ESP_LOGCONFIG(TAG, "    Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->pressure_sample_rate_)); | ||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_sensor_); | ||||
|   ESP_LOGCONFIG(TAG, "    Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->humidity_sample_rate_)); | ||||
|   LOG_SENSOR("  ", "Gas resistance", this->gas_resistance_sensor_); | ||||
|   LOG_SENSOR("  ", "CO2 equivalent", this->co2_equivalent_sensor_); | ||||
|   LOG_SENSOR("  ", "Breath VOC equivalent", this->breath_voc_equivalent_sensor_); | ||||
|   LOG_SENSOR("  ", "IAQ", this->iaq_sensor_); | ||||
|   LOG_SENSOR("  ", "IAQ static", this->iaq_static_sensor_); | ||||
|   LOG_SENSOR("  ", "Numeric IAQ accuracy", this->iaq_accuracy_sensor_); | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   LOG_TEXT_SENSOR("  ", "IAQ accuracy", this->iaq_accuracy_text_sensor_); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| float BME68xBSEC2Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| void BME68xBSEC2Component::loop() { | ||||
|   this->run_(); | ||||
|  | ||||
|   if (this->bsec_status_ < BSEC_OK || this->bme68x_status_ < BME68X_OK) { | ||||
|     this->status_set_error(); | ||||
|   } else { | ||||
|     this->status_clear_error(); | ||||
|   } | ||||
|   if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) { | ||||
|     this->status_set_warning(); | ||||
|   } else { | ||||
|     this->status_clear_warning(); | ||||
|   } | ||||
|   // Process a single action from the queue. These are primarily sensor state publishes | ||||
|   // that in totality take too long to send in a single call. | ||||
|   if (this->queue_.size()) { | ||||
|     auto action = std::move(this->queue_.front()); | ||||
|     this->queue_.pop(); | ||||
|     action(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) { | ||||
|   if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) { | ||||
|     ESP_LOGE(TAG, "Configuration is larger than BSEC_MAX_PROPERTY_BLOB_SIZE"); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   uint8_t work_buffer[BSEC_MAX_PROPERTY_BLOB_SIZE]; | ||||
|   this->bsec_status_ = bsec_set_configuration_m(&this->bsec_instance_, config, len, work_buffer, sizeof(work_buffer)); | ||||
|   if (this->bsec_status_ == BSEC_OK) { | ||||
|     this->bsec2_blob_configured_ = true; | ||||
|   } | ||||
| } | ||||
|  | ||||
| float BME68xBSEC2Component::calc_sensor_sample_rate_(SampleRate sample_rate) { | ||||
|   if (sample_rate == SAMPLE_RATE_DEFAULT) { | ||||
|     sample_rate = this->sample_rate_; | ||||
|   } | ||||
|   return sample_rate == SAMPLE_RATE_ULP ? BSEC_SAMPLE_RATE_ULP : BSEC_SAMPLE_RATE_LP; | ||||
| } | ||||
|  | ||||
| void BME68xBSEC2Component::update_subscription_() { | ||||
|   bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS]; | ||||
|   uint8_t num_virtual_sensors = 0; | ||||
| #ifdef USE_SENSOR | ||||
|   if (this->iaq_sensor_) { | ||||
|     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_IAQ; | ||||
|     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||
|     num_virtual_sensors++; | ||||
|   } | ||||
|  | ||||
|   if (this->iaq_static_sensor_) { | ||||
|     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_STATIC_IAQ; | ||||
|     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||
|     num_virtual_sensors++; | ||||
|   } | ||||
|  | ||||
|   if (this->co2_equivalent_sensor_) { | ||||
|     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT; | ||||
|     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||
|     num_virtual_sensors++; | ||||
|   } | ||||
|  | ||||
|   if (this->breath_voc_equivalent_sensor_) { | ||||
|     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT; | ||||
|     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||
|     num_virtual_sensors++; | ||||
|   } | ||||
|  | ||||
|   if (this->pressure_sensor_) { | ||||
|     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE; | ||||
|     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->pressure_sample_rate_); | ||||
|     num_virtual_sensors++; | ||||
|   } | ||||
|  | ||||
|   if (this->gas_resistance_sensor_) { | ||||
|     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS; | ||||
|     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||
|     num_virtual_sensors++; | ||||
|   } | ||||
|  | ||||
|   if (this->temperature_sensor_) { | ||||
|     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE; | ||||
|     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->temperature_sample_rate_); | ||||
|     num_virtual_sensors++; | ||||
|   } | ||||
|  | ||||
|   if (this->humidity_sensor_) { | ||||
|     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY; | ||||
|     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->humidity_sample_rate_); | ||||
|     num_virtual_sensors++; | ||||
|   } | ||||
| #endif | ||||
|   bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR]; | ||||
|   uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; | ||||
|   this->bsec_status_ = bsec_update_subscription_m(&this->bsec_instance_, virtual_sensors, num_virtual_sensors, | ||||
|                                                   sensor_settings, &num_sensor_settings); | ||||
| } | ||||
|  | ||||
| void BME68xBSEC2Component::run_() { | ||||
|   int64_t curr_time_ns = this->get_time_ns_(); | ||||
|   if (curr_time_ns < this->next_call_ns_) { | ||||
|     return; | ||||
|   } | ||||
|   this->op_mode_ = this->bsec_settings_.op_mode; | ||||
|   uint8_t status; | ||||
|  | ||||
|   ESP_LOGV(TAG, "Performing sensor run"); | ||||
|  | ||||
|   struct bme68x_conf bme68x_conf; | ||||
|   this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_); | ||||
|   if (this->bsec_status_ < BSEC_OK) { | ||||
|     ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_); | ||||
|     return; | ||||
|   } | ||||
|   this->next_call_ns_ = this->bsec_settings_.next_call; | ||||
|  | ||||
|   if (this->bsec_settings_.trigger_measurement) { | ||||
|     bme68x_get_conf(&bme68x_conf, &this->bme68x_); | ||||
|  | ||||
|     bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling; | ||||
|     bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling; | ||||
|     bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling; | ||||
|     bme68x_set_conf(&bme68x_conf, &this->bme68x_); | ||||
|  | ||||
|     switch (this->bsec_settings_.op_mode) { | ||||
|       case BME68X_FORCED_MODE: | ||||
|         this->bme68x_heatr_conf_.enable = BME68X_ENABLE; | ||||
|         this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature; | ||||
|         this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration; | ||||
|  | ||||
|         status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_); | ||||
|         status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); | ||||
|         status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_); | ||||
|         this->op_mode_ = BME68X_FORCED_MODE; | ||||
|         this->sleep_mode_ = false; | ||||
|         ESP_LOGV(TAG, "Using forced mode"); | ||||
|  | ||||
|         break; | ||||
|       case BME68X_PARALLEL_MODE: | ||||
|         if (this->op_mode_ != this->bsec_settings_.op_mode) { | ||||
|           this->bme68x_heatr_conf_.enable = BME68X_ENABLE; | ||||
|           this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile; | ||||
|           this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile; | ||||
|           this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len; | ||||
|           this->bme68x_heatr_conf_.shared_heatr_dur = | ||||
|               BSEC_TOTAL_HEAT_DUR - | ||||
|               (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000)); | ||||
|  | ||||
|           status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); | ||||
|  | ||||
|           status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_); | ||||
|           this->op_mode_ = BME68X_PARALLEL_MODE; | ||||
|           this->sleep_mode_ = false; | ||||
|           ESP_LOGV(TAG, "Using parallel mode"); | ||||
|         } | ||||
|         break; | ||||
|       case BME68X_SLEEP_MODE: | ||||
|         if (!this->sleep_mode_) { | ||||
|           bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_); | ||||
|           this->sleep_mode_ = true; | ||||
|           ESP_LOGV(TAG, "Using sleep mode"); | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     uint32_t meas_dur = 0; | ||||
|     meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_); | ||||
|     ESP_LOGV(TAG, "Queueing read in %uus", meas_dur); | ||||
|     this->set_timeout("read", meas_dur / 1000, [this, curr_time_ns]() { this->read_(curr_time_ns); }); | ||||
|   } else { | ||||
|     ESP_LOGV(TAG, "Measurement not required"); | ||||
|     this->read_(curr_time_ns); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void BME68xBSEC2Component::read_(int64_t trigger_time_ns) { | ||||
|   ESP_LOGV(TAG, "Reading data"); | ||||
|  | ||||
|   if (this->bsec_settings_.trigger_measurement) { | ||||
|     uint8_t current_op_mode; | ||||
|     this->bme68x_status_ = bme68x_get_op_mode(¤t_op_mode, &this->bme68x_); | ||||
|  | ||||
|     if (current_op_mode == BME68X_SLEEP_MODE) { | ||||
|       ESP_LOGV(TAG, "Still in sleep mode, doing nothing"); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (!this->bsec_settings_.process_data) { | ||||
|     ESP_LOGV(TAG, "Data processing not required"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   struct bme68x_data data[3]; | ||||
|   uint8_t nFields = 0; | ||||
|   this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_); | ||||
|  | ||||
|   if (this->bme68x_status_ != BME68X_OK) { | ||||
|     ESP_LOGW(TAG, "Failed to get sensor data (BME68X error code %d)", this->bme68x_status_); | ||||
|     return; | ||||
|   } | ||||
|   if (nFields < 1) { | ||||
|     ESP_LOGD(TAG, "BME68X did not provide new data"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   for (uint8_t i = 0; i < nFields; i++) { | ||||
|     bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR];  // Temperature, Pressure, Humidity & Gas Resistance | ||||
|     uint8_t num_inputs = 0; | ||||
|  | ||||
|     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_TEMPERATURE)) { | ||||
|       inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE; | ||||
|       inputs[num_inputs].signal = data[i].temperature; | ||||
|       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|       num_inputs++; | ||||
|     } | ||||
|     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HEATSOURCE)) { | ||||
|       inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE; | ||||
|       inputs[num_inputs].signal = this->temperature_offset_; | ||||
|       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|       num_inputs++; | ||||
|     } | ||||
|     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HUMIDITY)) { | ||||
|       inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY; | ||||
|       inputs[num_inputs].signal = data[i].humidity; | ||||
|       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|       num_inputs++; | ||||
|     } | ||||
|     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PRESSURE)) { | ||||
|       inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE; | ||||
|       inputs[num_inputs].signal = data[i].pressure; | ||||
|       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|       num_inputs++; | ||||
|     } | ||||
|     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_GASRESISTOR)) { | ||||
|       if (data[i].status & BME68X_GASM_VALID_MSK) { | ||||
|         inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR; | ||||
|         inputs[num_inputs].signal = data[i].gas_resistance; | ||||
|         inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|         num_inputs++; | ||||
|       } else { | ||||
|         ESP_LOGD(TAG, "BME68X did not report gas data"); | ||||
|       } | ||||
|     } | ||||
|     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PROFILE_PART) && | ||||
|         (data[i].status & BME68X_GASM_VALID_MSK)) { | ||||
|       inputs[num_inputs].sensor_id = BSEC_INPUT_PROFILE_PART; | ||||
|       inputs[num_inputs].signal = (this->op_mode_ == BME68X_FORCED_MODE) ? 0 : data[i].gas_index; | ||||
|       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|       num_inputs++; | ||||
|     } | ||||
|  | ||||
|     if (num_inputs < 1) { | ||||
|       ESP_LOGD(TAG, "No signal inputs available for BSEC2"); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     bsec_output_t outputs[BSEC_NUMBER_OUTPUTS]; | ||||
|     uint8_t num_outputs = BSEC_NUMBER_OUTPUTS; | ||||
|     this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs); | ||||
|     if (this->bsec_status_ != BSEC_OK) { | ||||
|       ESP_LOGW(TAG, "BSEC2 failed to process signals (BSEC2 error code %d)", this->bsec_status_); | ||||
|       return; | ||||
|     } | ||||
|     if (num_outputs < 1) { | ||||
|       ESP_LOGD(TAG, "No signal outputs provided by BSEC2"); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this->publish_(outputs, num_outputs); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void BME68xBSEC2Component::publish_(const bsec_output_t *outputs, uint8_t num_outputs) { | ||||
|   ESP_LOGV(TAG, "Publishing sensor states"); | ||||
|   bool update_accuracy = false; | ||||
|   uint8_t max_accuracy = 0; | ||||
|   for (uint8_t i = 0; i < num_outputs; i++) { | ||||
|     float signal = outputs[i].signal; | ||||
|     switch (outputs[i].sensor_id) { | ||||
|       case BSEC_OUTPUT_IAQ: | ||||
|         max_accuracy = std::max(outputs[i].accuracy, max_accuracy); | ||||
|         update_accuracy = true; | ||||
| #ifdef USE_SENSOR | ||||
|         this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); }); | ||||
| #endif | ||||
|         break; | ||||
|       case BSEC_OUTPUT_STATIC_IAQ: | ||||
|         max_accuracy = std::max(outputs[i].accuracy, max_accuracy); | ||||
|         update_accuracy = true; | ||||
| #ifdef USE_SENSOR | ||||
|         this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_static_sensor_, signal); }); | ||||
| #endif | ||||
|         break; | ||||
|       case BSEC_OUTPUT_CO2_EQUIVALENT: | ||||
| #ifdef USE_SENSOR | ||||
|         this->queue_push_([this, signal]() { this->publish_sensor_(this->co2_equivalent_sensor_, signal); }); | ||||
| #endif | ||||
|         break; | ||||
|       case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT: | ||||
| #ifdef USE_SENSOR | ||||
|         this->queue_push_([this, signal]() { this->publish_sensor_(this->breath_voc_equivalent_sensor_, signal); }); | ||||
| #endif | ||||
|         break; | ||||
|       case BSEC_OUTPUT_RAW_PRESSURE: | ||||
| #ifdef USE_SENSOR | ||||
|         this->queue_push_([this, signal]() { this->publish_sensor_(this->pressure_sensor_, signal / 100.0f); }); | ||||
| #endif | ||||
|         break; | ||||
|       case BSEC_OUTPUT_RAW_GAS: | ||||
| #ifdef USE_SENSOR | ||||
|         this->queue_push_([this, signal]() { this->publish_sensor_(this->gas_resistance_sensor_, signal); }); | ||||
| #endif | ||||
|         break; | ||||
|       case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: | ||||
| #ifdef USE_SENSOR | ||||
|         this->queue_push_([this, signal]() { this->publish_sensor_(this->temperature_sensor_, signal); }); | ||||
| #endif | ||||
|         break; | ||||
|       case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: | ||||
| #ifdef USE_SENSOR | ||||
|         this->queue_push_([this, signal]() { this->publish_sensor_(this->humidity_sensor_, signal); }); | ||||
| #endif | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|   if (update_accuracy) { | ||||
| #ifdef USE_SENSOR | ||||
|     this->queue_push_( | ||||
|         [this, max_accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, max_accuracy, true); }); | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|     this->queue_push_([this, max_accuracy]() { | ||||
|       this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[max_accuracy]); | ||||
|     }); | ||||
| #endif | ||||
|     // Queue up an opportunity to save state | ||||
|     this->queue_push_([this, max_accuracy]() { this->save_state_(max_accuracy); }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| int64_t BME68xBSEC2Component::get_time_ns_() { | ||||
|   int64_t time_ms = millis(); | ||||
|   if (this->last_time_ms_ > time_ms) { | ||||
|     this->millis_overflow_counter_++; | ||||
|   } | ||||
|   this->last_time_ms_ = time_ms; | ||||
|  | ||||
|   return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000); | ||||
| } | ||||
|  | ||||
| #ifdef USE_SENSOR | ||||
| void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value, bool change_only) { | ||||
|   if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) { | ||||
|     return; | ||||
|   } | ||||
|   sensor->publish_state(value); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) { | ||||
|   if (!sensor || (sensor->has_state() && sensor->state == value)) { | ||||
|     return; | ||||
|   } | ||||
|   sensor->publish_state(value); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void BME68xBSEC2Component::load_state_() { | ||||
|   uint32_t hash = this->get_hash(); | ||||
|   this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true); | ||||
|  | ||||
|   uint8_t state[BSEC_MAX_STATE_BLOB_SIZE]; | ||||
|   if (this->bsec_state_.load(&state)) { | ||||
|     ESP_LOGV(TAG, "Loading state"); | ||||
|     uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE]; | ||||
|     this->bsec_status_ = | ||||
|         bsec_set_state_m(&this->bsec_instance_, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer)); | ||||
|     if (this->bsec_status_ != BSEC_OK) { | ||||
|       ESP_LOGW(TAG, "Failed to load state (BSEC2 error code %d)", this->bsec_status_); | ||||
|     } | ||||
|     ESP_LOGI(TAG, "Loaded state"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void BME68xBSEC2Component::save_state_(uint8_t accuracy) { | ||||
|   if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGV(TAG, "Saving state"); | ||||
|  | ||||
|   uint8_t state[BSEC_MAX_STATE_BLOB_SIZE]; | ||||
|   uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE]; | ||||
|   uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE; | ||||
|  | ||||
|   this->bsec_status_ = bsec_get_state_m(&this->bsec_instance_, 0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, | ||||
|                                         BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     ESP_LOGW(TAG, "Failed fetch state for save (BSEC2 error code %d)", this->bsec_status_); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->bsec_state_.save(&state)) { | ||||
|     ESP_LOGW(TAG, "Failed to save state"); | ||||
|     return; | ||||
|   } | ||||
|   this->last_state_save_ms_ = millis(); | ||||
|  | ||||
|   ESP_LOGI(TAG, "Saved state"); | ||||
| } | ||||
|  | ||||
| }  // namespace bme68x_bsec2 | ||||
| }  // namespace esphome | ||||
| #endif | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user