mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'dev' into jesserockz-2023-304
This commit is contained in:
		| @@ -1,7 +1,9 @@ | ||||
| { | ||||
|   "name": "ESPHome Dev", | ||||
|   "image": "ghcr.io/esphome/esphome-lint:dev", | ||||
|   "postCreateCommand": ["script/devcontainer-post-create"], | ||||
|   "postCreateCommand": [ | ||||
|     "script/devcontainer-post-create" | ||||
|   ], | ||||
|   "containerEnv": { | ||||
|     "DEVCONTAINER": "1", | ||||
|     "PIP_BREAK_SYSTEM_PACKAGES": "1", | ||||
| @@ -27,6 +29,9 @@ | ||||
|       "extensions": [ | ||||
|         // python | ||||
|         "ms-python.python", | ||||
|         "ms-python.pylint", | ||||
|         "ms-python.flake8", | ||||
|         "ms-python.black-formatter", | ||||
|         "visualstudioexptteam.vscodeintellicode", | ||||
|         // yaml | ||||
|         "redhat.vscode-yaml", | ||||
| @@ -38,9 +43,21 @@ | ||||
|       "settings": { | ||||
|         "python.languageServer": "Pylance", | ||||
|         "python.pythonPath": "/usr/bin/python3", | ||||
|         "python.linting.pylintEnabled": true, | ||||
|         "python.linting.enabled": true, | ||||
|         "python.formatting.provider": "black", | ||||
|         "pylint.args": [ | ||||
|           "--rcfile=${workspaceFolder}/pyproject.toml" | ||||
|         ], | ||||
|         "flake8.args": [ | ||||
|           "--config=${workspaceFolder}/.flake8" | ||||
|         ], | ||||
|         "black-formatter.args": [ | ||||
|           "--config", | ||||
|           "${workspaceFolder}/pyproject.toml" | ||||
|         ], | ||||
|         "[python]": { | ||||
|           // VS will say "Value is not accepted" before building the devcontainer, but the warning | ||||
|           // should go away after build is completed. | ||||
|           "editor.defaultFormatter": "ms-python.black-formatter" | ||||
|         }, | ||||
|         "editor.formatOnPaste": false, | ||||
|         "editor.formatOnSave": true, | ||||
|         "editor.formatOnType": true, | ||||
|   | ||||
| @@ -1,19 +1,3 @@ | ||||
| [metadata] | ||||
| license      = MIT | ||||
| license_file = LICENSE | ||||
| platforms = any | ||||
| description  = Make creating custom firmwares for ESP32/ESP8266 super easy. | ||||
| long_description = file: README.md | ||||
| keywords     = home, automation | ||||
| classifier = | ||||
|     Environment :: Console | ||||
|     Intended Audience :: Developers | ||||
|     Intended Audience :: End Users/Desktop | ||||
|     License :: OSI Approved :: MIT License | ||||
|     Programming Language :: C++ | ||||
|     Programming Language :: Python :: 3 | ||||
|     Topic :: Home Automation | ||||
| 
 | ||||
| [flake8] | ||||
| max-line-length = 120 | ||||
| # Following 4 for black compatibility | ||||
| @@ -37,25 +21,22 @@ max-line-length = 120 | ||||
| # D401 First line should be in imperative mood | ||||
| 
 | ||||
| ignore = | ||||
|     E501, | ||||
|     W503, | ||||
|     E203, | ||||
|     D202, | ||||
|   E501, | ||||
|   W503, | ||||
|   E203, | ||||
|   D202, | ||||
| 
 | ||||
|     D100, | ||||
|     D101, | ||||
|     D102, | ||||
|     D103, | ||||
|     D104, | ||||
|     D105, | ||||
|     D107, | ||||
|     D200, | ||||
|     D205, | ||||
|     D209, | ||||
|     D400, | ||||
|     D401, | ||||
|   D100, | ||||
|   D101, | ||||
|   D102, | ||||
|   D103, | ||||
|   D104, | ||||
|   D105, | ||||
|   D107, | ||||
|   D200, | ||||
|   D205, | ||||
|   D209, | ||||
|   D400, | ||||
|   D401, | ||||
| 
 | ||||
| exclude = api_pb2.py | ||||
| 
 | ||||
| [bdist_wheel] | ||||
| universal = 1 | ||||
							
								
								
									
										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 | ||||
|   | ||||
							
								
								
									
										40
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -34,16 +34,29 @@ runs: | ||||
|           echo $l >> $GITHUB_OUTPUT | ||||
|         done | ||||
|  | ||||
|     # set cache-to only if dev branch | ||||
|     - id: cache-to | ||||
|       shell: bash | ||||
|       run: |- | ||||
|         if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then | ||||
|           echo "value=type=gha,mode=max" >> $GITHUB_OUTPUT | ||||
|         else | ||||
|           echo "value=" >> $GITHUB_OUTPUT | ||||
|         fi | ||||
|  | ||||
|     - name: Build and push to ghcr by digest | ||||
|       id: build-ghcr | ||||
|       uses: docker/build-push-action@v5.3.0 | ||||
|       uses: docker/build-push-action@v6.9.0 | ||||
|       env: | ||||
|         DOCKER_BUILD_SUMMARY: false | ||||
|         DOCKER_BUILD_RECORD_UPLOAD: false | ||||
|       with: | ||||
|         context: . | ||||
|         file: ./docker/Dockerfile | ||||
|         platforms: ${{ inputs.platform }} | ||||
|         target: ${{ inputs.target }} | ||||
|         cache-from: type=gha | ||||
|         cache-to: type=gha,mode=max | ||||
|         cache-to: ${{ steps.cache-to.outputs.value }} | ||||
|         build-args: | | ||||
|           BASEIMGTYPE=${{ inputs.baseimg }} | ||||
|           BUILD_VERSION=${{ inputs.version }} | ||||
| @@ -57,24 +70,19 @@ runs: | ||||
|         digest="${{ steps.build-ghcr.outputs.digest }}" | ||||
|         touch "/tmp/digests/${{ inputs.target }}/ghcr/${digest#sha256:}" | ||||
|  | ||||
|     - name: Upload ghcr digest | ||||
|       uses: actions/upload-artifact@v3.1.3 | ||||
|       with: | ||||
|         name: digests-${{ inputs.target }}-ghcr | ||||
|         path: /tmp/digests/${{ inputs.target }}/ghcr/* | ||||
|         if-no-files-found: error | ||||
|         retention-days: 1 | ||||
|  | ||||
|     - name: Build and push to dockerhub by digest | ||||
|       id: build-dockerhub | ||||
|       uses: docker/build-push-action@v5.3.0 | ||||
|       uses: docker/build-push-action@v6.9.0 | ||||
|       env: | ||||
|         DOCKER_BUILD_SUMMARY: false | ||||
|         DOCKER_BUILD_RECORD_UPLOAD: false | ||||
|       with: | ||||
|         context: . | ||||
|         file: ./docker/Dockerfile | ||||
|         platforms: ${{ inputs.platform }} | ||||
|         target: ${{ inputs.target }} | ||||
|         cache-from: type=gha | ||||
|         cache-to: type=gha,mode=max | ||||
|         cache-to: ${{ steps.cache-to.outputs.value }} | ||||
|         build-args: | | ||||
|           BASEIMGTYPE=${{ inputs.baseimg }} | ||||
|           BUILD_VERSION=${{ inputs.version }} | ||||
| @@ -87,11 +95,3 @@ runs: | ||||
|         mkdir -p /tmp/digests/${{ inputs.target }}/dockerhub | ||||
|         digest="${{ steps.build-dockerhub.outputs.digest }}" | ||||
|         touch "/tmp/digests/${{ inputs.target }}/dockerhub/${digest#sha256:}" | ||||
|  | ||||
|     - name: Upload dockerhub digest | ||||
|       uses: actions/upload-artifact@v3.1.3 | ||||
|       with: | ||||
|         name: digests-${{ inputs.target }}-dockerhub | ||||
|         path: /tmp/digests/${{ inputs.target }}/dockerhub/* | ||||
|         if-no-files-found: error | ||||
|         retention-days: 1 | ||||
|   | ||||
							
								
								
									
										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.0 | ||||
|       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: | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							| @@ -21,9 +21,9 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         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" | ||||
|  | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -40,15 +40,15 @@ jobs: | ||||
|         arch: [amd64, armv7, aarch64] | ||||
|         build_type: ["ha-addon", "docker", "lint"] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.1 | ||||
|       - 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.2.0 | ||||
|         uses: docker/setup-buildx-action@v3.7.1 | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v3.0.0 | ||||
|         uses: docker/setup-qemu-action@v3.2.0 | ||||
|  | ||||
|       - name: Set TAG | ||||
|         run: | | ||||
|   | ||||
							
								
								
									
										218
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										218
									
								
								.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" | ||||
| @@ -20,7 +21,6 @@ permissions: | ||||
| env: | ||||
|   DEFAULT_PYTHON: "3.9" | ||||
|   PYUPGRADE_TARGET: "--py39-plus" | ||||
|   CLANG_FORMAT_VERSION: "13.0.1" | ||||
|  | ||||
| concurrency: | ||||
|   # yamllint disable-line rule:line-length | ||||
| @@ -35,18 +35,18 @@ jobs: | ||||
|       cache-key: ${{ steps.cache-key.outputs.key }} | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Generate cache-key | ||||
|         id: cache-key | ||||
|         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 | ||||
| @@ -67,7 +67,7 @@ jobs: | ||||
|       - common | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
| @@ -88,7 +88,7 @@ jobs: | ||||
|       - common | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
| @@ -109,7 +109,7 @@ jobs: | ||||
|       - common | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
| @@ -130,7 +130,7 @@ jobs: | ||||
|       - common | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
| @@ -151,7 +151,7 @@ jobs: | ||||
|       - common | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
| @@ -200,7 +200,7 @@ jobs: | ||||
|       - common | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
| @@ -230,7 +230,7 @@ jobs: | ||||
|       - common | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
| @@ -239,7 +239,7 @@ jobs: | ||||
|       - name: Install clang-format | ||||
|         run: | | ||||
|           . venv/bin/activate | ||||
|           pip install clang-format==${{ env.CLANG_FORMAT_VERSION }} | ||||
|           pip install clang-format -c requirements_dev.txt | ||||
|       - name: Run clang-format | ||||
|         run: | | ||||
|           . venv/bin/activate | ||||
| @@ -249,72 +249,6 @@ jobs: | ||||
|         run: script/ci-suggest-changes | ||||
|         if: always() | ||||
|  | ||||
|   compile-tests-list: | ||||
|     runs-on: ubuntu-latest | ||||
|     outputs: | ||||
|       matrix: ${{ steps.set-matrix.outputs.matrix }} | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|       - name: Find all YAML test files | ||||
|         id: set-matrix | ||||
|         run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT | ||||
|  | ||||
|   validate-tests: | ||||
|     name: Validate YAML test ${{ matrix.file }} | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: | ||||
|       - common | ||||
|       - compile-tests-list | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }} | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
|           python-version: ${{ env.DEFAULT_PYTHON }} | ||||
|           cache-key: ${{ needs.common.outputs.cache-key }} | ||||
|       - name: Run esphome config ${{ matrix.file }} | ||||
|         run: | | ||||
|           . venv/bin/activate | ||||
|           esphome config ${{ matrix.file }} | ||||
|  | ||||
|   compile-tests: | ||||
|     name: Run YAML test ${{ matrix.file }} | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: | ||||
|       - common | ||||
|       - black | ||||
|       - ci-custom | ||||
|       - clang-format | ||||
|       - flake8 | ||||
|       - pylint | ||||
|       - pytest | ||||
|       - pyupgrade | ||||
|       - compile-tests-list | ||||
|       - validate-tests | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       max-parallel: 2 | ||||
|       matrix: | ||||
|         file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }} | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
|           python-version: ${{ env.DEFAULT_PYTHON }} | ||||
|           cache-key: ${{ needs.common.outputs.cache-key }} | ||||
|       - name: Run esphome compile ${{ matrix.file }} | ||||
|         run: | | ||||
|           . venv/bin/activate | ||||
|           esphome compile ${{ matrix.file }} | ||||
|  | ||||
|   clang-tidy: | ||||
|     name: ${{ matrix.name }} | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -363,27 +297,44 @@ jobs: | ||||
|  | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
|           python-version: ${{ env.DEFAULT_PYTHON }} | ||||
|           cache-key: ${{ needs.common.outputs.cache-key }} | ||||
|  | ||||
|       - name: Cache platformio | ||||
|         uses: actions/cache@v4.0.2 | ||||
|         if: github.ref == 'refs/heads/dev' | ||||
|         uses: actions/cache@v4.1.2 | ||||
|         with: | ||||
|           path: ~/.platformio | ||||
|           # yamllint disable-line rule:line-length | ||||
|           key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} | ||||
|           key: platformio-${{ matrix.pio_cache_key }} | ||||
|  | ||||
|       - name: Cache platformio | ||||
|         if: github.ref != 'refs/heads/dev' | ||||
|         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: | | ||||
|           echo "::add-matcher::.github/workflows/matchers/gcc.json" | ||||
|           echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" | ||||
|  | ||||
|       - name: Run 'pio run --list-targets -e esp32-idf-tidy' | ||||
|         if: matrix.name == 'Run script/clang-tidy for ESP32 IDF' | ||||
|         run: | | ||||
|           . venv/bin/activate | ||||
|           mkdir -p .temp | ||||
|           pio run --list-targets -e esp32-idf-tidy | ||||
|  | ||||
|       - name: Run clang-tidy | ||||
|         run: | | ||||
|           . venv/bin/activate | ||||
| @@ -403,10 +354,11 @@ jobs: | ||||
|       - common | ||||
|     if: github.event_name == 'pull_request' | ||||
|     outputs: | ||||
|       matrix: ${{ steps.set-matrix.outputs.matrix }} | ||||
|       components: ${{ steps.list-components.outputs.components }} | ||||
|       count: ${{ steps.list-components.outputs.count }} | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|         with: | ||||
|           # Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works. | ||||
|           fetch-depth: 500 | ||||
| @@ -424,10 +376,18 @@ jobs: | ||||
|           python-version: ${{ env.DEFAULT_PYTHON }} | ||||
|           cache-key: ${{ needs.common.outputs.cache-key }} | ||||
|       - name: Find changed components | ||||
|         id: set-matrix | ||||
|         id: list-components | ||||
|         run: | | ||||
|           . venv/bin/activate | ||||
|           echo "matrix=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }} | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT | ||||
|           components=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }}) | ||||
|           output_components=$(echo "$components" | jq -R -s -c 'split("\n")[:-1] | map(select(length > 0))') | ||||
|           count=$(echo "$output_components" | jq length) | ||||
|  | ||||
|           echo "components=$output_components" >> $GITHUB_OUTPUT | ||||
|           echo "count=$count" >> $GITHUB_OUTPUT | ||||
|  | ||||
|           echo "$count Components:" | ||||
|           echo "$output_components" | jq | ||||
|  | ||||
|   test-build-components: | ||||
|     name: Component test ${{ matrix.file }} | ||||
| @@ -435,18 +395,20 @@ jobs: | ||||
|     needs: | ||||
|       - common | ||||
|       - list-components | ||||
|     if: ${{ github.event_name == 'pull_request' && needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }} | ||||
|     if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) > 0 && fromJSON(needs.list-components.outputs.count) < 100 | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       max-parallel: 2 | ||||
|       matrix: | ||||
|         file: ${{ fromJson(needs.list-components.outputs.matrix) }} | ||||
|         file: ${{ fromJson(needs.list-components.outputs.components) }} | ||||
|     steps: | ||||
|       - name: Install libsodium | ||||
|         run: sudo apt-get install libsodium-dev | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install libsdl2-dev | ||||
|  | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
| @@ -461,6 +423,68 @@ jobs: | ||||
|           . venv/bin/activate | ||||
|           ./script/test_build_components -e compile -c ${{ matrix.file }} | ||||
|  | ||||
|   test-build-components-splitter: | ||||
|     name: Split components for testing into 20 groups maximum | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: | ||||
|       - common | ||||
|       - list-components | ||||
|     if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100 | ||||
|     outputs: | ||||
|       matrix: ${{ steps.split.outputs.components }} | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Split components into 20 groups | ||||
|         id: split | ||||
|         run: | | ||||
|           components=$(echo '${{ needs.list-components.outputs.components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]') | ||||
|           echo "components=$components" >> $GITHUB_OUTPUT | ||||
|  | ||||
|   test-build-components-split: | ||||
|     name: Test split components | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: | ||||
|       - common | ||||
|       - list-components | ||||
|       - test-build-components-splitter | ||||
|     if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100 | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       max-parallel: 4 | ||||
|       matrix: | ||||
|         components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }} | ||||
|     steps: | ||||
|       - name: List components | ||||
|         run: echo ${{ matrix.components }} | ||||
|  | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install libsdl2-dev | ||||
|  | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Restore Python | ||||
|         uses: ./.github/actions/restore-python | ||||
|         with: | ||||
|           python-version: ${{ env.DEFAULT_PYTHON }} | ||||
|           cache-key: ${{ needs.common.outputs.cache-key }} | ||||
|       - name: Validate config | ||||
|         run: | | ||||
|           . venv/bin/activate | ||||
|           for component in ${{ matrix.components }}; do | ||||
|             ./script/test_build_components -e config -c $component | ||||
|           done | ||||
|       - 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 | ||||
|  | ||||
|   ci-status: | ||||
|     name: CI Status | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -473,9 +497,11 @@ jobs: | ||||
|       - pylint | ||||
|       - pytest | ||||
|       - pyupgrade | ||||
|       - compile-tests | ||||
|       - clang-tidy | ||||
|       - list-components | ||||
|       - test-build-components | ||||
|       - test-build-components-splitter | ||||
|       - test-build-components-split | ||||
|     if: always() | ||||
|     steps: | ||||
|       - name: Success | ||||
| @@ -483,4 +509,8 @@ jobs: | ||||
|         run: exit 0 | ||||
|       - name: Failure | ||||
|         if: ${{ contains(needs.*.result, 'failure') }} | ||||
|         run: exit 1 | ||||
|         env: | ||||
|           JSON_DOC: ${{ toJSON(needs) }} | ||||
|         run: | | ||||
|           echo $JSON_DOC | jq | ||||
|           exit 1 | ||||
|   | ||||
							
								
								
									
										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}}" | ||||
							
								
								
									
										90
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										90
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,14 +17,16 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     outputs: | ||||
|       tag: ${{ steps.tag.outputs.tag }} | ||||
|       branch_build: ${{ steps.tag.outputs.branch_build }} | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.1 | ||||
|       - uses: actions/checkout@v4.1.7 | ||||
|       - name: Get tag | ||||
|         id: tag | ||||
|         # yamllint disable rule:line-length | ||||
|         run: | | ||||
|           if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then | ||||
|             TAG="${GITHUB_REF#refs/tags/}" | ||||
|           if [[ "${{ github.event_name }}" = "release" ]]; then | ||||
|             TAG="${{ github.event.release.tag_name}}" | ||||
|             BRANCH_BUILD="false" | ||||
|           else | ||||
|             TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p") | ||||
|             today="$(date --utc '+%Y%m%d')" | ||||
| @@ -32,34 +34,38 @@ jobs: | ||||
|             BRANCH=${GITHUB_REF#refs/heads/} | ||||
|             if [[ "$BRANCH" != "dev" ]]; then | ||||
|               TAG="${TAG}-${BRANCH}" | ||||
|               BRANCH_BUILD="true" | ||||
|             else | ||||
|               BRANCH_BUILD="false" | ||||
|             fi | ||||
|           fi | ||||
|           echo "tag=${TAG}" >> $GITHUB_OUTPUT | ||||
|           echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT | ||||
|         # yamllint enable rule:line-length | ||||
|  | ||||
|   deploy-pypi: | ||||
|     name: Build and publish to PyPi | ||||
|     if: github.repository == 'esphome/esphome' && github.event_name == 'release' | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       id-token: write | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.1 | ||||
|       - 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 | ||||
|         env: | ||||
|           ESPHOME_NO_VENV: 1 | ||||
|         run: | | ||||
|           script/setup | ||||
|           pip install twine | ||||
|         run: script/setup | ||||
|       - name: Build | ||||
|         run: python setup.py sdist bdist_wheel | ||||
|       - name: Upload | ||||
|         env: | ||||
|           TWINE_USERNAME: __token__ | ||||
|           TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} | ||||
|         run: twine upload dist/* | ||||
|         run: |- | ||||
|           pip3 install build | ||||
|           python3 -m build | ||||
|       - name: Publish | ||||
|         uses: pypa/gh-action-pypi-publish@v1.10.3 | ||||
|  | ||||
|   deploy-docker: | ||||
|     name: Build ESPHome ${{ matrix.platform }} | ||||
| @@ -77,25 +83,25 @@ jobs: | ||||
|           - linux/arm/v7 | ||||
|           - linux/arm64 | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.1 | ||||
|       - 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.2.0 | ||||
|         uses: docker/setup-buildx-action@v3.7.1 | ||||
|       - name: Set up QEMU | ||||
|         if: matrix.platform != 'linux/amd64' | ||||
|         uses: docker/setup-qemu-action@v3.0.0 | ||||
|         uses: docker/setup-qemu-action@v3.2.0 | ||||
|  | ||||
|       - name: Log in to docker hub | ||||
|         uses: docker/login-action@v3.1.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.1.0 | ||||
|         uses: docker/login-action@v3.3.0 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
| @@ -128,6 +134,19 @@ jobs: | ||||
|           suffix: lint | ||||
|           version: ${{ needs.init.outputs.tag }} | ||||
|  | ||||
|       - name: Sanitize platform name | ||||
|         id: sanitize | ||||
|         run: | | ||||
|           echo "${{ matrix.platform }}" | sed 's|/|-|g' > /tmp/platform | ||||
|           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT | ||||
|  | ||||
|       - name: Upload digests | ||||
|         uses: actions/upload-artifact@v4.4.3 | ||||
|         with: | ||||
|           name: digests-${{ steps.sanitize.outputs.name }} | ||||
|           path: /tmp/digests | ||||
|           retention-days: 1 | ||||
|  | ||||
|   deploy-manifest: | ||||
|     name: Publish ESPHome ${{ matrix.image.title }} to ${{ matrix.registry }} | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -155,24 +174,27 @@ jobs: | ||||
|           - ghcr | ||||
|           - dockerhub | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.1 | ||||
|       - uses: actions/checkout@v4.1.7 | ||||
|  | ||||
|       - name: Download digests | ||||
|         uses: actions/download-artifact@v3.0.2 | ||||
|         uses: actions/download-artifact@v4.1.8 | ||||
|         with: | ||||
|           name: digests-${{ matrix.image.target }}-${{ matrix.registry }} | ||||
|           pattern: digests-* | ||||
|           path: /tmp/digests | ||||
|           merge-multiple: true | ||||
|  | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3.2.0 | ||||
|         uses: docker/setup-buildx-action@v3.7.1 | ||||
|  | ||||
|       - name: Log in to docker hub | ||||
|         if: matrix.registry == 'dockerhub' | ||||
|         uses: docker/login-action@v3.1.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.1.0 | ||||
|         uses: docker/login-action@v3.3.0 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
| @@ -191,28 +213,34 @@ jobs: | ||||
|           done | ||||
|  | ||||
|       - name: Create manifest list and push | ||||
|         working-directory: /tmp/digests | ||||
|         working-directory: /tmp/digests/${{ matrix.image.target }}/${{ matrix.registry }} | ||||
|         run: | | ||||
|           docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \ | ||||
|             $(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *) | ||||
|  | ||||
|   deploy-ha-addon-repo: | ||||
|     if: github.repository == 'esphome/esphome' && github.event_name == 'release' | ||||
|     if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false' | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: [deploy-manifest] | ||||
|     needs: | ||||
|       - init | ||||
|       - deploy-manifest | ||||
|     steps: | ||||
|       - name: Trigger Workflow | ||||
|         uses: actions/github-script@v7.0.1 | ||||
|         with: | ||||
|           github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} | ||||
|           script: | | ||||
|             let description = "ESPHome"; | ||||
|             if (context.eventName == "release") { | ||||
|               description = ${{ toJSON(github.event.release.body) }}; | ||||
|             } | ||||
|             github.rest.actions.createWorkflowDispatch({ | ||||
|               owner: "esphome", | ||||
|               repo: "home-assistant-addon", | ||||
|               workflow_id: "bump-version.yml", | ||||
|               ref: "main", | ||||
|               inputs: { | ||||
|                 version: "${{ github.event.release.tag_name }}", | ||||
|                 content: ${{ toJSON(github.event.release.body) }} | ||||
|                 version: "${{ needs.init.outputs.tag }}", | ||||
|                 content: description | ||||
|               } | ||||
|             }) | ||||
|   | ||||
							
								
								
									
										10
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							| @@ -13,18 +13,18 @@ jobs: | ||||
|     if: github.repository == 'esphome/esphome' | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|  | ||||
|       - name: Checkout Home Assistant | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|         with: | ||||
|           repository: home-assistant/core | ||||
|           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.11 | ||||
|           python-version: 3.12 | ||||
|  | ||||
|       - name: Install Home Assistant | ||||
|         run: | | ||||
| @@ -36,7 +36,7 @@ jobs: | ||||
|           python ./script/sync-device_class.py | ||||
|  | ||||
|       - name: Commit changes | ||||
|         uses: peter-evans/create-pull-request@v6.0.2 | ||||
|         uses: peter-evans/create-pull-request@v7.0.5 | ||||
|         with: | ||||
|           commit-message: "Synchronise Device Classes from Home Assistant" | ||||
|           committer: esphomebot <esphome@nabucasa.com> | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/yaml-lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/yaml-lint.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,7 +18,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.1 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Run yamllint | ||||
|         uses: frenck/action-yamllint@v1.5.0 | ||||
|         with: | ||||
|   | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -138,3 +138,5 @@ sdkconfig.* | ||||
| .tests/ | ||||
|  | ||||
| /components | ||||
| /managed_components | ||||
|  | ||||
|   | ||||
| @@ -2,8 +2,17 @@ | ||||
| # 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.2.0 | ||||
|     rev: 24.4.2 | ||||
|     hooks: | ||||
|       - id: black | ||||
|         args: | ||||
| @@ -27,7 +36,23 @@ repos: | ||||
|           - --branch=release | ||||
|           - --branch=beta | ||||
|   - repo: https://github.com/asottile/pyupgrade | ||||
|     rev: v3.15.1 | ||||
|     rev: v3.15.2 | ||||
|     hooks: | ||||
|       - id: pyupgrade | ||||
|         args: [--py39-plus] | ||||
|   - repo: https://github.com/adrienverge/yamllint.git | ||||
|     rev: v1.35.1 | ||||
|     hooks: | ||||
|       - id: yamllint | ||||
|   - repo: https://github.com/pre-commit/mirrors-clang-format | ||||
|     rev: v13.0.1 | ||||
|     hooks: | ||||
|       - id: clang-format | ||||
|         types_or: [c, c++] | ||||
|   - repo: local | ||||
|     hooks: | ||||
|       - id: pylint | ||||
|         name: pylint | ||||
|         entry: script/run-in-env.sh pylint | ||||
|         language: script | ||||
|         types: [python] | ||||
|   | ||||
							
								
								
									
										105
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -6,7 +6,7 @@ | ||||
| # the integration's code owner is automatically notified. | ||||
|  | ||||
| # Core Code | ||||
| setup.py @esphome/core | ||||
| pyproject.toml @esphome/core | ||||
| esphome/*.py @esphome/core | ||||
| esphome/core/* @esphome/core | ||||
|  | ||||
| @@ -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,25 +47,40 @@ 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 | ||||
| esphome/components/bedjet/* @jhansche | ||||
| esphome/components/bedjet/climate/* @jhansche | ||||
| esphome/components/bedjet/fan/* @jhansche | ||||
| esphome/components/bedjet/sensor/* @javawizard @jhansche | ||||
| 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/bmp3xx/* @martgras | ||||
| 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 | ||||
| esphome/components/bmp3xx_spi/* @latonita | ||||
| esphome/components/bmp581/* @kahrendt | ||||
| esphome/components/bp1658cj/* @Cossid | ||||
| esphome/components/bp5758d/* @Cossid | ||||
| @@ -73,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 | ||||
| @@ -89,9 +107,10 @@ esphome/components/current_based/* @djwmarcx | ||||
| esphome/components/dac7678/* @NickB1 | ||||
| esphome/components/daikin_arc/* @MagicBear | ||||
| esphome/components/daikin_brc/* @hagak | ||||
| esphome/components/dallas_temp/* @ssieb | ||||
| esphome/components/daly_bms/* @s1lvi0 | ||||
| esphome/components/dashboard_import/* @esphome/core | ||||
| esphome/components/datetime/* @rfdarter | ||||
| esphome/components/datetime/* @jesserockz @rfdarter | ||||
| esphome/components/debug/* @OttoWinter | ||||
| esphome/components/delonghi/* @grob6000 | ||||
| esphome/components/dfplayer/* @glmnet | ||||
| @@ -106,7 +125,10 @@ esphome/components/ee895/* @Stock-M | ||||
| esphome/components/ektf2232/touchscreen/* @jesserockz | ||||
| esphome/components/emc2101/* @ellull | ||||
| esphome/components/emmeti/* @E440QF | ||||
| esphome/components/ens160/* @vincentscode | ||||
| esphome/components/ens160/* @latonita | ||||
| esphome/components/ens160_base/* @latonita @vincentscode | ||||
| esphome/components/ens160_i2c/* @latonita | ||||
| esphome/components/ens160_spi/* @latonita | ||||
| esphome/components/ens210/* @itn3rd77 | ||||
| esphome/components/esp32/* @esphome/core | ||||
| esphome/components/esp32_ble/* @Rapsssito @jesserockz | ||||
| @@ -115,9 +137,11 @@ esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz | ||||
| esphome/components/esp32_camera_web_server/* @ayufan | ||||
| esphome/components/esp32_can/* @Sympatron | ||||
| esphome/components/esp32_improv/* @jesserockz | ||||
| esphome/components/esp32_rmt/* @jesserockz | ||||
| esphome/components/esp32_rmt_led_strip/* @jesserockz | ||||
| esphome/components/esp8266/* @esphome/core | ||||
| esphome/components/ethernet_info/* @gtjadsonsantos | ||||
| esphome/components/event/* @nohat | ||||
| esphome/components/exposure_notifications/* @OttoWinter | ||||
| esphome/components/ezo/* @ssieb | ||||
| esphome/components/ezo_pmp/* @carlos-sarmiento | ||||
| @@ -130,17 +154,26 @@ esphome/components/fs3000/* @kahrendt | ||||
| esphome/components/ft5x06/* @clydebarrow | ||||
| 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 | ||||
| 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 | ||||
| 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 | ||||
| esphome/components/hbridge/light/* @DotNetDann | ||||
| @@ -148,27 +181,37 @@ 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 | ||||
| esphome/components/host/* @esphome/core | ||||
| esphome/components/host/* @clydebarrow @esphome/core | ||||
| esphome/components/host/time/* @clydebarrow | ||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||
| esphome/components/hte501/* @Stock-M | ||||
| esphome/components/http_request/ota/* @oarcher | ||||
| esphome/components/http_request/update/* @jesserockz | ||||
| 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 | ||||
| esphome/components/improv_serial/* @esphome/core | ||||
| esphome/components/ina226/* @Sergio303 @latonita | ||||
| esphome/components/ina260/* @mreditor97 | ||||
| esphome/components/ina2xx_base/* @latonita | ||||
| esphome/components/ina2xx_i2c/* @latonita | ||||
| esphome/components/ina2xx_spi/* @latonita | ||||
| esphome/components/inkbird_ibsth1_mini/* @fkirill | ||||
| esphome/components/inkplate6/* @jesserockz | ||||
| esphome/components/integration/* @OttoWinter | ||||
| @@ -191,8 +234,13 @@ esphome/components/lightwaverf/* @max246 | ||||
| esphome/components/lilygo_t5_47/touchscreen/* @jesserockz | ||||
| esphome/components/lock/* @esphome/core | ||||
| esphome/components/logger/* @esphome/core | ||||
| esphome/components/ltr390/* @sjtrny | ||||
| 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 | ||||
| @@ -239,15 +287,20 @@ 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/* @senexcrenshaw | ||||
| esphome/components/nextion/* @edwardtfn @senexcrenshaw | ||||
| esphome/components/nextion/binary_sensor/* @senexcrenshaw | ||||
| esphome/components/nextion/sensor/* @senexcrenshaw | ||||
| 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/online_image/* @guillempages | ||||
| esphome/components/opentherm/* @olegtarasov | ||||
| esphome/components/ota/* @esphome/core | ||||
| esphome/components/output/* @esphome/core | ||||
| esphome/components/pca6416a/* @Mat931 | ||||
| @@ -275,7 +328,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 | ||||
| @@ -292,9 +345,10 @@ esphome/components/rp2040_pwm/* @jesserockz | ||||
| esphome/components/rpi_dpi_rgb/* @clydebarrow | ||||
| esphome/components/rtl87xx/* @kuba2k2 | ||||
| esphome/components/rtttl/* @glmnet | ||||
| esphome/components/safe_mode/* @jsuanet @paulmonigatti | ||||
| esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti | ||||
| esphome/components/scd4x/* @martgras @sjtrny | ||||
| esphome/components/script/* @esphome/core | ||||
| esphome/components/sdl/* @clydebarrow | ||||
| esphome/components/sdm_meter/* @jesserockz @polyfaces | ||||
| esphome/components/sdp3x/* @Azimath | ||||
| esphome/components/seeed_mr24hpc1/* @limengdu | ||||
| @@ -323,7 +377,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 | ||||
| @@ -347,22 +401,28 @@ 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 | ||||
| esphome/components/template/fan/* @ssieb | ||||
| esphome/components/text/* @mauritskorse | ||||
| esphome/components/thermostat/* @kbx81 | ||||
| esphome/components/time/* @OttoWinter | ||||
| esphome/components/tlc5947/* @rnauber | ||||
| esphome/components/tlc5971/* @IJIJI | ||||
| esphome/components/tm1621/* @Philippe12 | ||||
| esphome/components/tm1637/* @glmnet | ||||
| esphome/components/tm1638/* @skykingjwc | ||||
| @@ -384,26 +444,43 @@ 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 | ||||
| esphome/components/update/* @jesserockz | ||||
| esphome/components/uponor_smatrix/* @kroimon | ||||
| esphome/components/valve/* @esphome/core | ||||
| esphome/components/vbus/* @ssieb | ||||
| esphome/components/veml3235/* @kbx81 | ||||
| esphome/components/veml7700/* @latonita | ||||
| esphome/components/version/* @esphome/core | ||||
| esphome/components/voice_assistant/* @jesserockz | ||||
| esphome/components/wake_on_lan/* @willwill2will54 | ||||
| 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 | ||||
| esphome/components/weikai/* @DrCoolZic | ||||
| esphome/components/weikai_i2c/* @DrCoolZic | ||||
| esphome/components/weikai_spi/* @DrCoolZic | ||||
| esphome/components/whirlpool/* @glmnet | ||||
| esphome/components/whynter/* @aeonsablaze | ||||
| esphome/components/wiegand/* @ssieb | ||||
| esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard | ||||
| esphome/components/wk2132_i2c/* @DrCoolZic | ||||
| esphome/components/wk2132_spi/* @DrCoolZic | ||||
| esphome/components/wk2168_i2c/* @DrCoolZic | ||||
| esphome/components/wk2168_spi/* @DrCoolZic | ||||
| esphome/components/wk2204_i2c/* @DrCoolZic | ||||
| esphome/components/wk2204_spi/* @DrCoolZic | ||||
| esphome/components/wk2212_i2c/* @DrCoolZic | ||||
| esphome/components/wk2212_spi/* @DrCoolZic | ||||
| 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,29 +33,33 @@ 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+deb12u5 \ | ||||
|         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 \ | ||||
|         patch=2.7.6-7; \ | ||||
|     if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ | ||||
|         apt-get install -y --no-install-recommends \ | ||||
|           build-essential=12.9 \ | ||||
|           python3-dev=3.11.2-1+b1 \ | ||||
|           zlib1g-dev=1:1.2.13.dfsg-1 \ | ||||
|           libjpeg-dev=1:2.1.5-2 \ | ||||
|           libfreetype-dev=2.12.1+dfsg-5 \ | ||||
|           libssl-dev=3.0.11-1~deb12u2 \ | ||||
|           libffi-dev=3.4.4-1 \ | ||||
|           libopenjp2-7=2.5.0-2 \ | ||||
|           libtiff6=4.5.0-6+deb12u1 \ | ||||
|           cargo=0.66.0+ds1-1 \ | ||||
|           pkg-config=1.8.1-1 \ | ||||
|           gcc-arm-linux-gnueabihf=4:12.2.0-3; \ | ||||
|     fi; \ | ||||
|     rm -rf \ | ||||
|         patch=2.7.6-7 \ | ||||
|     && ( \ | ||||
|         ( \ | ||||
|             [ "$TARGETARCH$TARGETVARIANT" = "armv7" ] && \ | ||||
|                 apt-get install -y --no-install-recommends \ | ||||
|                 build-essential=12.9 \ | ||||
|                 python3-dev=3.11.2-1+b1 \ | ||||
|                 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.14-1~deb12u2 \ | ||||
|                 libffi-dev=3.4.4-1 \ | ||||
|                 libopenjp2-7=2.5.0-2 \ | ||||
|                 libtiff6=4.5.0-6+deb12u1 \ | ||||
|                 cargo=0.66.0+ds1-1 \ | ||||
|                 pkg-config=1.8.1-1 \ | ||||
|                 gcc-arm-linux-gnueabihf=4:12.2.0-3 \ | ||||
|         ) \ | ||||
|         || [ "$TARGETARCH$TARGETVARIANT" != "armv7" ] \ | ||||
|     ) \ | ||||
|     && rm -rf \ | ||||
|         /tmp/* \ | ||||
|         /var/{cache,log}/* \ | ||||
|         /var/lib/apt/lists/* | ||||
| @@ -81,7 +85,8 @@ RUN \ | ||||
|     fi; \ | ||||
|     pip3 install \ | ||||
|     --break-system-packages --no-cache-dir \ | ||||
|     platformio==6.1.13 \ | ||||
|     # Keep platformio version in sync with requirements.txt | ||||
|     platformio==6.1.16 \ | ||||
|     # Change some platformio settings | ||||
|     && platformio settings set enable_telemetry No \ | ||||
|     && platformio settings set check_platformio_interval 1000000 \ | ||||
| @@ -91,14 +96,22 @@ 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 '*' | ||||
|  | ||||
|  | ||||
| # ======================= docker-type image ======================= | ||||
| @@ -110,7 +123,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ | ||||
|         export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ | ||||
|   fi; \ | ||||
|   pip3 install \ | ||||
|   --break-system-packages --no-cache-dir --no-use-pep517 -e /esphome | ||||
|   --break-system-packages --no-cache-dir -e /esphome | ||||
|  | ||||
| # Settings for dashboard | ||||
| ENV USERNAME="" PASSWORD="" | ||||
| @@ -160,7 +173,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ | ||||
|         export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ | ||||
|   fi; \ | ||||
|   pip3 install \ | ||||
|   --break-system-packages --no-cache-dir --no-use-pep517 -e /esphome | ||||
|   --break-system-packages --no-cache-dir -e /esphome | ||||
|  | ||||
| # Labels | ||||
| LABEL \ | ||||
| @@ -186,8 +199,8 @@ RUN \ | ||||
|         clang-format-13=1:13.0.1-11+b2 \ | ||||
|         clang-tidy-14=1:14.0.6-12 \ | ||||
|         patch=2.7.6-7 \ | ||||
|         software-properties-common=0.99.30-4 \ | ||||
|         nano=7.2-1 \ | ||||
|         software-properties-common=0.99.30-4.1~deb12u1 \ | ||||
|         nano=7.2-1+deb12u1 \ | ||||
|         build-essential=12.9 \ | ||||
|         python3-dev=3.11.2-1+b1 \ | ||||
|     && rm -rf \ | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
							
								
								
									
										47
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/30-esphome-fork.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										47
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/30-esphome-fork.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| #!/usr/bin/with-contenv bashio | ||||
| # ============================================================================== | ||||
| # This file installs the user ESPHome fork if specified. | ||||
| # The fork must be up to date with the latest ESPHome dev branch | ||||
| # and have no conflicts. | ||||
| # This config option only exists in the ESPHome Dev add-on. | ||||
| # ============================================================================== | ||||
|  | ||||
| declare esphome_fork | ||||
|  | ||||
| if bashio::config.has_value 'esphome_fork'; then | ||||
|   esphome_fork=$(bashio::config 'esphome_fork') | ||||
|   # format: [username][/repository]:ref | ||||
|   if [[ "$esphome_fork" =~ ^(([^/]+)(/([^:]+))?:)?([^:/]+)$ ]]; then | ||||
|     username="${BASH_REMATCH[2]:-esphome}" | ||||
|     repository="${BASH_REMATCH[4]:-esphome}" | ||||
|     ref="${BASH_REMATCH[5]}" | ||||
|   else | ||||
|     bashio::exit.nok "Invalid esphome_fork format: $esphome_fork" | ||||
|   fi | ||||
|   full_url="https://github.com/${username}/${repository}/archive/${ref}.tar.gz" | ||||
|   bashio::log.info "Checking forked ESPHome" | ||||
|   dev_version=$(python3 -c "from esphome.const import __version__; print(__version__)") | ||||
|   bashio::log.info "Downloading ESPHome from fork '${esphome_fork}' (${full_url})..." | ||||
|   curl -L -o /tmp/esphome.tar.gz "${full_url}" -qq || | ||||
|     bashio::exit.nok "Failed downloading ESPHome fork." | ||||
|   bashio::log.info "Installing ESPHome from fork '${esphome_fork}' (${full_url})..." | ||||
|   rm -rf /esphome || bashio::exit.nok "Failed to remove ESPHome." | ||||
|   mkdir /esphome | ||||
|   tar -zxf /tmp/esphome.tar.gz -C /esphome --strip-components=1 || | ||||
|     bashio::exit.nok "Failed installing ESPHome from fork." | ||||
|   pip install -U -e /esphome || bashio::exit.nok "Failed installing ESPHome from fork." | ||||
|   rm -f /tmp/esphome.tar.gz | ||||
|   fork_version=$(python3 -c "from esphome.const import __version__; print(__version__)") | ||||
|  | ||||
|   if [[ "$fork_version" != "$dev_version" ]]; then | ||||
|     bashio::log.error "############################" | ||||
|     bashio::log.error "Uninstalled fork as version does not match" | ||||
|     bashio::log.error "Update (or ask the author to update) the branch" | ||||
|     bashio::log.error "This is important as the dev addon and the dev ESPHome" | ||||
|     bashio::log.error "branch can have changes that are not compatible with old forks" | ||||
|     bashio::log.error "and get reported as bugs which we cannot solve easily." | ||||
|     bashio::log.error "############################" | ||||
|     bashio::exit.nok | ||||
|   fi | ||||
|   bashio::log.info "Installed ESPHome from fork '${esphome_fork}' (${full_url})..." | ||||
| fi | ||||
| @@ -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 | ||||
|  | ||||
| @@ -18,34 +18,35 @@ from esphome.const import ( | ||||
|     CONF_BAUD_RATE, | ||||
|     CONF_BROKER, | ||||
|     CONF_DEASSERT_RTS_DTR, | ||||
|     CONF_DISABLED, | ||||
|     CONF_ESPHOME, | ||||
|     CONF_LOGGER, | ||||
|     CONF_MDNS, | ||||
|     CONF_MQTT, | ||||
|     CONF_NAME, | ||||
|     CONF_OTA, | ||||
|     CONF_MQTT, | ||||
|     CONF_MDNS, | ||||
|     CONF_DISABLED, | ||||
|     CONF_PASSWORD, | ||||
|     CONF_PORT, | ||||
|     CONF_ESPHOME, | ||||
|     CONF_PLATFORM, | ||||
|     CONF_PLATFORMIO_OPTIONS, | ||||
|     CONF_PORT, | ||||
|     CONF_SUBSTITUTIONS, | ||||
|     PLATFORM_BK72XX, | ||||
|     PLATFORM_RTL87XX, | ||||
|     PLATFORM_ESP32, | ||||
|     PLATFORM_ESP8266, | ||||
|     PLATFORM_RP2040, | ||||
|     PLATFORM_RTL87XX, | ||||
|     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__) | ||||
|  | ||||
| @@ -65,7 +66,7 @@ def choose_prompt(options, purpose: str = None): | ||||
|         f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:' | ||||
|     ) | ||||
|     for i, (desc, _) in enumerate(options): | ||||
|         safe_print(f"  [{i+1}] {desc}") | ||||
|         safe_print(f"  [{i + 1}] {desc}") | ||||
|  | ||||
|     while True: | ||||
|         opt = input("(number): ") | ||||
| @@ -115,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: | ||||
| @@ -330,22 +332,27 @@ def upload_program(config, args, host): | ||||
|  | ||||
|         return 1  # Unknown target platform | ||||
|  | ||||
|     if CONF_OTA not in config: | ||||
|     ota_conf = {} | ||||
|     for ota_item in config.get(CONF_OTA, []): | ||||
|         if ota_item[CONF_PLATFORM] == CONF_ESPHOME: | ||||
|             ota_conf = ota_item | ||||
|             break | ||||
|  | ||||
|     if not ota_conf: | ||||
|         raise EsphomeError( | ||||
|             "Cannot upload Over the Air as the config does not include the ota: " | ||||
|             "component" | ||||
|             f"Cannot upload Over the Air as the {CONF_OTA} configuration is not present or does not include {CONF_PLATFORM}: {CONF_ESPHOME}" | ||||
|         ) | ||||
|  | ||||
|     from esphome import espota2 | ||||
|  | ||||
|     ota_conf = config[CONF_OTA] | ||||
|     remote_port = ota_conf[CONF_PORT] | ||||
|     password = ota_conf.get(CONF_PASSWORD, "") | ||||
|  | ||||
|     if ( | ||||
|         not is_ip_address(CORE.address) | ||||
|         not is_ip_address(CORE.address)  # pylint: disable=too-many-boolean-expressions | ||||
|         and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED]) | ||||
|         and CONF_MQTT in config | ||||
|         and (not args.device or args.device in ("MQTT", "OTA")) | ||||
|     ): | ||||
|         from esphome import mqtt | ||||
|  | ||||
| @@ -482,6 +489,15 @@ def command_run(args, config): | ||||
|     if exit_code != 0: | ||||
|         return exit_code | ||||
|     _LOGGER.info("Successfully compiled program.") | ||||
|     if CORE.is_host: | ||||
|         from esphome.platformio_api import get_idedata | ||||
|  | ||||
|         idedata = get_idedata(config) | ||||
|         if idedata is None: | ||||
|             return 1 | ||||
|         program_path = idedata.raw["prog_path"] | ||||
|         return run_external_process(program_path) | ||||
|  | ||||
|     port = choose_upload_log_host( | ||||
|         default=args.device, | ||||
|         check_default=None, | ||||
| @@ -581,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) | ||||
|  | ||||
| @@ -680,7 +697,8 @@ def command_rename(args, config): | ||||
|         os.remove(new_path) | ||||
|         return 1 | ||||
|  | ||||
|     os.remove(CORE.config_path) | ||||
|     if CORE.config_path != new_path: | ||||
|         os.remove(CORE.config_path) | ||||
|  | ||||
|     print(color(Fore.BOLD_GREEN, "SUCCESS")) | ||||
|     print() | ||||
| @@ -713,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" | ||||
| @@ -731,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) | ||||
| @@ -768,7 +797,9 @@ def parse_args(argv): | ||||
|     ) | ||||
|  | ||||
|     parser_upload = subparsers.add_parser( | ||||
|         "upload", help="Validate the configuration and upload the latest binary." | ||||
|         "upload", | ||||
|         help="Validate the configuration and upload the latest binary.", | ||||
|         parents=[mqtt_options], | ||||
|     ) | ||||
|     parser_upload.add_argument( | ||||
|         "configuration", help="Your YAML configuration file(s).", nargs="+" | ||||
| @@ -785,6 +816,7 @@ def parse_args(argv): | ||||
|     parser_logs = subparsers.add_parser( | ||||
|         "logs", | ||||
|         help="Validate the configuration and show all logs.", | ||||
|         aliases=["log"], | ||||
|         parents=[mqtt_options], | ||||
|     ) | ||||
|     parser_logs.add_argument( | ||||
| @@ -929,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) | ||||
|  | ||||
| @@ -1004,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 | ||||
| @@ -18,10 +20,20 @@ from esphome.util import Registry | ||||
|  | ||||
|  | ||||
| def maybe_simple_id(*validators): | ||||
|     """Allow a raw ID to be specified in place of a config block. | ||||
|     If the value that's being validated is a dictionary, it's passed as-is to the specified validators. Otherwise, it's | ||||
|     wrapped in a dict that looks like ``{"id": <value>}``, and that dict is then handed off to the specified validators. | ||||
|     """ | ||||
|     return maybe_conf(CONF_ID, *validators) | ||||
|  | ||||
|  | ||||
| def maybe_conf(conf, *validators): | ||||
|     """Allow a raw value to be specified in place of a config block. | ||||
|     If the value that's being validated is a dictionary, it's passed as-is to the specified validators. Otherwise, it's | ||||
|     wrapped in a dict that looks like ``{<conf>: <value>}``, and that dict is then handed off to the specified | ||||
|     validators. | ||||
|     (This is a general case of ``maybe_simple_id`` that allows the wrapping key to be something other than ``id``.) | ||||
|     """ | ||||
|     validator = cv.All(*validators) | ||||
|  | ||||
|     @schema_extractor("maybe") | ||||
| @@ -63,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) | ||||
| @@ -156,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) | ||||
| @@ -213,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,84 +8,86 @@ | ||||
| # 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, | ||||
|     std_string_ref, | ||||
|     std_vector, | ||||
|     uint8, | ||||
|     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, | ||||
| ) | ||||
|   | ||||
| @@ -4,11 +4,11 @@ from esphome.const import ( | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     ICON_ARROW_EXPAND_VERTICAL, | ||||
|     DEVICE_CLASS_DISTANCE, | ||||
|     UNIT_MILLIMETER, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@TH-Braemer"] | ||||
| DEPENDENCIES = ["uart"] | ||||
| UNIT_MILLIMETERS = "mm" | ||||
|  | ||||
| a02yyuw_ns = cg.esphome_ns.namespace("a02yyuw") | ||||
| A02yyuwComponent = a02yyuw_ns.class_( | ||||
| @@ -17,7 +17,7 @@ A02yyuwComponent = a02yyuw_ns.class_( | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema( | ||||
|     A02yyuwComponent, | ||||
|     unit_of_measurement=UNIT_MILLIMETERS, | ||||
|     unit_of_measurement=UNIT_MILLIMETER, | ||||
|     icon=ICON_ARROW_EXPAND_VERTICAL, | ||||
|     accuracy_decimals=0, | ||||
|     state_class=STATE_CLASS_MEASUREMENT, | ||||
|   | ||||
| @@ -18,11 +18,23 @@ from esphome.components.esp32.const import ( | ||||
|  | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
|  | ||||
| adc_ns = cg.esphome_ns.namespace("adc") | ||||
|  | ||||
|  | ||||
| """ | ||||
| From the below patch versions (and 5.2+) ADC_ATTEN_DB_11 is deprecated and replaced with ADC_ATTEN_DB_12. | ||||
| 4.4.7 | ||||
| 5.0.5 | ||||
| 5.1.3 | ||||
| 5.2+ | ||||
| """ | ||||
|  | ||||
| ATTENUATION_MODES = { | ||||
|     "0db": cg.global_ns.ADC_ATTEN_DB_0, | ||||
|     "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, | ||||
|     "6db": cg.global_ns.ADC_ATTEN_DB_6, | ||||
|     "11db": cg.global_ns.ADC_ATTEN_DB_11, | ||||
|     "11db": adc_ns.ADC_ATTEN_DB_12_COMPAT, | ||||
|     "12db": adc_ns.ADC_ATTEN_DB_12_COMPAT, | ||||
|     "auto": "auto", | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -46,27 +46,27 @@ extern "C" | ||||
|     ADCSensor::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||
| #if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040) | ||||
|   pin_->setup(); | ||||
|   this->pin_->setup(); | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|   if (channel1_ != ADC1_CHANNEL_MAX) { | ||||
|   if (this->channel1_ != ADC1_CHANNEL_MAX) { | ||||
|     adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); | ||||
|     if (!autorange_) { | ||||
|       adc1_config_channel_atten(channel1_, attenuation_); | ||||
|     if (!this->autorange_) { | ||||
|       adc1_config_channel_atten(this->channel1_, this->attenuation_); | ||||
|     } | ||||
|   } else if (channel2_ != ADC2_CHANNEL_MAX) { | ||||
|     if (!autorange_) { | ||||
|       adc2_config_channel_atten(channel2_, attenuation_); | ||||
|   } else if (this->channel2_ != ADC2_CHANNEL_MAX) { | ||||
|     if (!this->autorange_) { | ||||
|       adc2_config_channel_atten(this->channel2_, this->attenuation_); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // load characteristics for each attenuation | ||||
|   for (int32_t i = 0; i <= ADC_ATTEN_DB_11; i++) { | ||||
|     auto adc_unit = channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2; | ||||
|   for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) { | ||||
|     auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2; | ||||
|     auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, | ||||
|                                               1100,  // default vref | ||||
|                                               &cal_characteristics_[i]); | ||||
|                                               &this->cal_characteristics_[i]); | ||||
|     switch (cal_value) { | ||||
|       case ESP_ADC_CAL_VAL_EFUSE_VREF: | ||||
|         ESP_LOGV(TAG, "Using eFuse Vref for calibration"); | ||||
| @@ -99,27 +99,27 @@ void ADCSensor::dump_config() { | ||||
| #ifdef USE_ADC_SENSOR_VCC | ||||
|   ESP_LOGCONFIG(TAG, "  Pin: VCC"); | ||||
| #else | ||||
|   LOG_PIN("  Pin: ", pin_); | ||||
|   LOG_PIN("  Pin: ", this->pin_); | ||||
| #endif | ||||
| #endif  // USE_ESP8266 || USE_LIBRETINY | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|   LOG_PIN("  Pin: ", pin_); | ||||
|   if (autorange_) { | ||||
|     ESP_LOGCONFIG(TAG, " Attenuation: auto"); | ||||
|   LOG_PIN("  Pin: ", this->pin_); | ||||
|   if (this->autorange_) { | ||||
|     ESP_LOGCONFIG(TAG, "  Attenuation: auto"); | ||||
|   } else { | ||||
|     switch (this->attenuation_) { | ||||
|       case ADC_ATTEN_DB_0: | ||||
|         ESP_LOGCONFIG(TAG, " Attenuation: 0db"); | ||||
|         ESP_LOGCONFIG(TAG, "  Attenuation: 0db"); | ||||
|         break; | ||||
|       case ADC_ATTEN_DB_2_5: | ||||
|         ESP_LOGCONFIG(TAG, " Attenuation: 2.5db"); | ||||
|         ESP_LOGCONFIG(TAG, "  Attenuation: 2.5db"); | ||||
|         break; | ||||
|       case ADC_ATTEN_DB_6: | ||||
|         ESP_LOGCONFIG(TAG, " Attenuation: 6db"); | ||||
|         ESP_LOGCONFIG(TAG, "  Attenuation: 6db"); | ||||
|         break; | ||||
|       case ADC_ATTEN_DB_11: | ||||
|         ESP_LOGCONFIG(TAG, " Attenuation: 11db"); | ||||
|       case ADC_ATTEN_DB_12_COMPAT: | ||||
|         ESP_LOGCONFIG(TAG, "  Attenuation: 12db"); | ||||
|         break; | ||||
|       default:  // This is to satisfy the unused ADC_ATTEN_MAX | ||||
|         break; | ||||
| @@ -134,11 +134,11 @@ void ADCSensor::dump_config() { | ||||
| #ifdef USE_ADC_SENSOR_VCC | ||||
|     ESP_LOGCONFIG(TAG, "  Pin: VCC"); | ||||
| #else | ||||
|     LOG_PIN("  Pin: ", pin_); | ||||
|     LOG_PIN("  Pin: ", this->pin_); | ||||
| #endif  // USE_ADC_SENSOR_VCC | ||||
|   } | ||||
| #endif  // USE_RP2040 | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| @@ -149,14 +149,24 @@ void ADCSensor::update() { | ||||
|   this->publish_state(value_v); | ||||
| } | ||||
|  | ||||
| void ADCSensor::set_sample_count(uint8_t sample_count) { | ||||
|   if (sample_count != 0) { | ||||
|     this->sample_count_ = sample_count; | ||||
|   } | ||||
| } | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| float ADCSensor::sample() { | ||||
|   uint32_t raw = 0; | ||||
|   for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||
| #ifdef USE_ADC_SENSOR_VCC | ||||
|   int32_t raw = ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance) | ||||
|     raw += ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance) | ||||
| #else | ||||
|   int32_t raw = analogRead(this->pin_->get_pin());  // NOLINT | ||||
|     raw += analogRead(this->pin_->get_pin());  // NOLINT | ||||
| #endif | ||||
|   if (output_raw_) { | ||||
|   } | ||||
|   raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||
|   if (this->output_raw_) { | ||||
|     return raw; | ||||
|   } | ||||
|   return raw / 1024.0f; | ||||
| @@ -165,77 +175,81 @@ float ADCSensor::sample() { | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
| float ADCSensor::sample() { | ||||
|   if (!autorange_) { | ||||
|     int raw = -1; | ||||
|     if (channel1_ != ADC1_CHANNEL_MAX) { | ||||
|       raw = adc1_get_raw(channel1_); | ||||
|     } else if (channel2_ != ADC2_CHANNEL_MAX) { | ||||
|       adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw); | ||||
|   if (!this->autorange_) { | ||||
|     uint32_t sum = 0; | ||||
|     for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||
|       int raw = -1; | ||||
|       if (this->channel1_ != ADC1_CHANNEL_MAX) { | ||||
|         raw = adc1_get_raw(this->channel1_); | ||||
|       } else if (this->channel2_ != ADC2_CHANNEL_MAX) { | ||||
|         adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw); | ||||
|       } | ||||
|       if (raw == -1) { | ||||
|         return NAN; | ||||
|       } | ||||
|       sum += raw; | ||||
|     } | ||||
|  | ||||
|     if (raw == -1) { | ||||
|       return NAN; | ||||
|     sum = (sum + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||
|     if (this->output_raw_) { | ||||
|       return sum; | ||||
|     } | ||||
|     if (output_raw_) { | ||||
|       return raw; | ||||
|     } | ||||
|     uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int32_t) attenuation_]); | ||||
|     uint32_t mv = esp_adc_cal_raw_to_voltage(sum, &this->cal_characteristics_[(int32_t) this->attenuation_]); | ||||
|     return mv / 1000.0f; | ||||
|   } | ||||
|  | ||||
|   int raw11 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX; | ||||
|   int raw12 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX; | ||||
|  | ||||
|   if (channel1_ != ADC1_CHANNEL_MAX) { | ||||
|     adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_11); | ||||
|     raw11 = adc1_get_raw(channel1_); | ||||
|     if (raw11 < ADC_MAX) { | ||||
|       adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_6); | ||||
|       raw6 = adc1_get_raw(channel1_); | ||||
|   if (this->channel1_ != ADC1_CHANNEL_MAX) { | ||||
|     adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_12_COMPAT); | ||||
|     raw12 = adc1_get_raw(this->channel1_); | ||||
|     if (raw12 < ADC_MAX) { | ||||
|       adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_6); | ||||
|       raw6 = adc1_get_raw(this->channel1_); | ||||
|       if (raw6 < ADC_MAX) { | ||||
|         adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_2_5); | ||||
|         raw2 = adc1_get_raw(channel1_); | ||||
|         adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_2_5); | ||||
|         raw2 = adc1_get_raw(this->channel1_); | ||||
|         if (raw2 < ADC_MAX) { | ||||
|           adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_0); | ||||
|           raw0 = adc1_get_raw(channel1_); | ||||
|           adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_0); | ||||
|           raw0 = adc1_get_raw(this->channel1_); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } else if (channel2_ != ADC2_CHANNEL_MAX) { | ||||
|     adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_11); | ||||
|     adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw11); | ||||
|     if (raw11 < ADC_MAX) { | ||||
|       adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_6); | ||||
|       adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6); | ||||
|   } else if (this->channel2_ != ADC2_CHANNEL_MAX) { | ||||
|     adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_12_COMPAT); | ||||
|     adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw12); | ||||
|     if (raw12 < ADC_MAX) { | ||||
|       adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_6); | ||||
|       adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6); | ||||
|       if (raw6 < ADC_MAX) { | ||||
|         adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_2_5); | ||||
|         adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2); | ||||
|         adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_2_5); | ||||
|         adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2); | ||||
|         if (raw2 < ADC_MAX) { | ||||
|           adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_0); | ||||
|           adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0); | ||||
|           adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_0); | ||||
|           adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) { | ||||
|   if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw12 == -1) { | ||||
|     return NAN; | ||||
|   } | ||||
|  | ||||
|   uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_11]); | ||||
|   uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]); | ||||
|   uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]); | ||||
|   uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]); | ||||
|   uint32_t mv12 = esp_adc_cal_raw_to_voltage(raw12, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_12_COMPAT]); | ||||
|   uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]); | ||||
|   uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]); | ||||
|   uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]); | ||||
|  | ||||
|   // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC) | ||||
|   uint32_t c11 = std::min(raw11, ADC_HALF); | ||||
|   uint32_t c12 = std::min(raw12, ADC_HALF); | ||||
|   uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); | ||||
|   uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); | ||||
|   uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF); | ||||
|   // max theoretical csum value is 4096*4 = 16384 | ||||
|   uint32_t csum = c11 + c6 + c2 + c0; | ||||
|   uint32_t csum = c12 + c6 + c2 + c0; | ||||
|  | ||||
|   // each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32 | ||||
|   uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); | ||||
|   uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); | ||||
|   return mv_scaled / (float) (csum * 1000U); | ||||
| } | ||||
| #endif  // USE_ESP32 | ||||
| @@ -246,8 +260,11 @@ float ADCSensor::sample() { | ||||
|     adc_set_temp_sensor_enabled(true); | ||||
|     delay(1); | ||||
|     adc_select_input(4); | ||||
|  | ||||
|     int32_t raw = adc_read(); | ||||
|     uint32_t raw = 0; | ||||
|     for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||
|       raw += adc_read(); | ||||
|     } | ||||
|     raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||
|     adc_set_temp_sensor_enabled(false); | ||||
|     if (this->output_raw_) { | ||||
|       return raw; | ||||
| @@ -268,7 +285,11 @@ float ADCSensor::sample() { | ||||
|     adc_gpio_init(pin); | ||||
|     adc_select_input(pin - 26); | ||||
|  | ||||
|     int32_t raw = adc_read(); | ||||
|     uint32_t raw = 0; | ||||
|     for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||
|       raw += adc_read(); | ||||
|     } | ||||
|     raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||
|  | ||||
| #ifdef CYW43_USES_VSYS_PIN | ||||
|     if (pin == PICO_VSYS_PIN) { | ||||
| @@ -276,7 +297,7 @@ float ADCSensor::sample() { | ||||
|     } | ||||
| #endif  // CYW43_USES_VSYS_PIN | ||||
|  | ||||
|     if (output_raw_) { | ||||
|     if (this->output_raw_) { | ||||
|       return raw; | ||||
|     } | ||||
|     float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0; | ||||
| @@ -287,10 +308,19 @@ float ADCSensor::sample() { | ||||
|  | ||||
| #ifdef USE_LIBRETINY | ||||
| float ADCSensor::sample() { | ||||
|   if (output_raw_) { | ||||
|     return analogRead(this->pin_->get_pin());  // NOLINT | ||||
|   uint32_t raw = 0; | ||||
|   if (this->output_raw_) { | ||||
|     for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||
|       raw += analogRead(this->pin_->get_pin());  // NOLINT | ||||
|     } | ||||
|     raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||
|     return raw; | ||||
|   } | ||||
|   return analogReadVoltage(this->pin_->get_pin()) / 1000.0f;  // NOLINT | ||||
|   for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||
|     raw += analogReadVoltage(this->pin_->get_pin());  // NOLINT | ||||
|   } | ||||
|   raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||
|   return raw / 1000.0f; | ||||
| } | ||||
| #endif  // USE_LIBRETINY | ||||
|  | ||||
|   | ||||
| @@ -1,33 +1,48 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/voltage_sampler/voltage_sampler.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
| #include "driver/adc.h" | ||||
| #include <esp_adc_cal.h> | ||||
| #include "driver/adc.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace adc { | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
| // clang-format off | ||||
| #if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 7)) || \ | ||||
|     (ESP_IDF_VERSION_MAJOR == 5 && \ | ||||
|      ((ESP_IDF_VERSION_MINOR == 0 && ESP_IDF_VERSION_PATCH >= 5) || \ | ||||
|       (ESP_IDF_VERSION_MINOR == 1 && ESP_IDF_VERSION_PATCH >= 3) || \ | ||||
|       (ESP_IDF_VERSION_MINOR >= 2)) \ | ||||
|     ) | ||||
| // clang-format on | ||||
| static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_12; | ||||
| #else | ||||
| static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11; | ||||
| #endif | ||||
| #endif  // USE_ESP32 | ||||
|  | ||||
| class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { | ||||
|  public: | ||||
| #ifdef USE_ESP32 | ||||
|   /// Set the attenuation for this pin. Only available on the ESP32. | ||||
|   void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; } | ||||
|   void set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } | ||||
|   void set_channel1(adc1_channel_t channel) { | ||||
|     channel1_ = channel; | ||||
|     channel2_ = ADC2_CHANNEL_MAX; | ||||
|     this->channel1_ = channel; | ||||
|     this->channel2_ = ADC2_CHANNEL_MAX; | ||||
|   } | ||||
|   void set_channel2(adc2_channel_t channel) { | ||||
|     channel2_ = channel; | ||||
|     channel1_ = ADC1_CHANNEL_MAX; | ||||
|     this->channel2_ = channel; | ||||
|     this->channel1_ = ADC1_CHANNEL_MAX; | ||||
|   } | ||||
|   void set_autorange(bool autorange) { autorange_ = autorange; } | ||||
|   void set_autorange(bool autorange) { this->autorange_ = autorange; } | ||||
| #endif | ||||
|  | ||||
|   /// Update ADC values | ||||
| @@ -38,7 +53,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | ||||
|   /// `HARDWARE_LATE` setup priority | ||||
|   float get_setup_priority() const override; | ||||
|   void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } | ||||
|   void set_output_raw(bool output_raw) { output_raw_ = output_raw; } | ||||
|   void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; } | ||||
|   void set_sample_count(uint8_t sample_count); | ||||
|   float sample() override; | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| @@ -46,12 +62,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_RP2040 | ||||
|   void set_is_temperature() { is_temperature_ = true; } | ||||
|   void set_is_temperature() { this->is_temperature_ = true; } | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   InternalGPIOPin *pin_; | ||||
|   bool output_raw_{false}; | ||||
|   uint8_t sample_count_{1}; | ||||
|  | ||||
| #ifdef USE_RP2040 | ||||
|   bool is_temperature_{false}; | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import logging | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| import esphome.final_validate as fv | ||||
| @@ -19,16 +21,35 @@ from . import ( | ||||
|     ATTENUATION_MODES, | ||||
|     ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, | ||||
|     ESP32_VARIANT_ADC2_PIN_TO_CHANNEL, | ||||
|     adc_ns, | ||||
|     validate_adc_pin, | ||||
| ) | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| AUTO_LOAD = ["voltage_sampler"] | ||||
|  | ||||
| CONF_SAMPLES = "samples" | ||||
|  | ||||
|  | ||||
| _attenuation = cv.enum(ATTENUATION_MODES, lower=True) | ||||
|  | ||||
|  | ||||
| def validate_config(config): | ||||
|     if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": | ||||
|         raise cv.Invalid("Automatic attenuation cannot be used when raw output is set") | ||||
|  | ||||
|     if config.get(CONF_ATTENUATION, None) == "auto" and config.get(CONF_SAMPLES, 1) > 1: | ||||
|         raise cv.Invalid( | ||||
|             "Automatic attenuation cannot be used when multisampling is set" | ||||
|         ) | ||||
|     if config.get(CONF_ATTENUATION) == "11db": | ||||
|         _LOGGER.warning( | ||||
|             "`attenuation: 11db` is deprecated, use `attenuation: 12db` instead" | ||||
|         ) | ||||
|         # Alter value here so `config` command prints the recommended change | ||||
|         config[CONF_ATTENUATION] = _attenuation("12db") | ||||
|  | ||||
|     return config | ||||
|  | ||||
|  | ||||
| @@ -47,7 +68,6 @@ def final_validate_config(config): | ||||
|     return config | ||||
|  | ||||
|  | ||||
| adc_ns = cg.esphome_ns.namespace("adc") | ||||
| ADCSensor = adc_ns.class_( | ||||
|     "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler | ||||
| ) | ||||
| @@ -65,8 +85,9 @@ CONFIG_SCHEMA = cv.All( | ||||
|             cv.Required(CONF_PIN): validate_adc_pin, | ||||
|             cv.Optional(CONF_RAW, default=False): cv.boolean, | ||||
|             cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( | ||||
|                 cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) | ||||
|                 cv.only_on_esp32, _attenuation | ||||
|             ), | ||||
|             cv.Optional(CONF_SAMPLES, default=1): cv.int_range(min=1, max=255), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")), | ||||
| @@ -90,6 +111,7 @@ async def to_code(config): | ||||
|         cg.add(var.set_pin(pin)) | ||||
|  | ||||
|     cg.add(var.set_output_raw(config[CONF_RAW])) | ||||
|     cg.add(var.set_sample_count(config[CONF_SAMPLES])) | ||||
|  | ||||
|     if attenuation := config.get(CONF_ATTENUATION): | ||||
|         if attenuation == "auto": | ||||
|   | ||||
| @@ -11,6 +11,8 @@ | ||||
| #include "ade7880_registers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #include <cinttypes> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ade7880 { | ||||
|  | ||||
| @@ -156,7 +158,7 @@ void ADE7880::update() { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   ESP_LOGD(TAG, "update took %u ms", millis() - start); | ||||
|   ESP_LOGD(TAG, "update took %" PRIu32 " ms", millis() - start); | ||||
| } | ||||
|  | ||||
| void ADE7880::dump_config() { | ||||
| @@ -176,9 +178,9 @@ void ADE7880::dump_config() { | ||||
|     LOG_SENSOR("    ", "Forward Active Energy", this->channel_a_->forward_active_energy); | ||||
|     LOG_SENSOR("    ", "Reverse Active Energy", this->channel_a_->reverse_active_energy); | ||||
|     ESP_LOGCONFIG(TAG, "    Calibration:"); | ||||
|     ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_a_->current_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Voltage: %d", this->channel_a_->voltage_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Power: %d", this->channel_a_->power_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Current: %" PRId32, this->channel_a_->current_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Voltage: %" PRId32, this->channel_a_->voltage_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Power: %" PRId32, this->channel_a_->power_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Phase Angle: %u", this->channel_a_->phase_angle_calibration); | ||||
|   } | ||||
|  | ||||
| @@ -192,9 +194,9 @@ void ADE7880::dump_config() { | ||||
|     LOG_SENSOR("    ", "Forward Active Energy", this->channel_b_->forward_active_energy); | ||||
|     LOG_SENSOR("    ", "Reverse Active Energy", this->channel_b_->reverse_active_energy); | ||||
|     ESP_LOGCONFIG(TAG, "    Calibration:"); | ||||
|     ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_b_->current_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Voltage: %d", this->channel_b_->voltage_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Power: %d", this->channel_b_->power_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Current: %" PRId32, this->channel_b_->current_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Voltage: %" PRId32, this->channel_b_->voltage_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Power: %" PRId32, this->channel_b_->power_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Phase Angle: %u", this->channel_b_->phase_angle_calibration); | ||||
|   } | ||||
|  | ||||
| @@ -208,9 +210,9 @@ void ADE7880::dump_config() { | ||||
|     LOG_SENSOR("    ", "Forward Active Energy", this->channel_c_->forward_active_energy); | ||||
|     LOG_SENSOR("    ", "Reverse Active Energy", this->channel_c_->reverse_active_energy); | ||||
|     ESP_LOGCONFIG(TAG, "    Calibration:"); | ||||
|     ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_c_->current_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Voltage: %d", this->channel_c_->voltage_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Power: %d", this->channel_c_->power_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Current: %" PRId32, this->channel_c_->current_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Voltage: %" PRId32, this->channel_c_->voltage_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Power: %" PRId32, this->channel_c_->power_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Phase Angle: %u", this->channel_c_->phase_angle_calibration); | ||||
|   } | ||||
|  | ||||
| @@ -218,7 +220,7 @@ void ADE7880::dump_config() { | ||||
|     ESP_LOGCONFIG(TAG, "  Neutral:"); | ||||
|     LOG_SENSOR("    ", "Current", this->channel_n_->current); | ||||
|     ESP_LOGCONFIG(TAG, "    Calibration:"); | ||||
|     ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_n_->current_gain_calibration); | ||||
|     ESP_LOGCONFIG(TAG, "     Current: %" PRId32, this->channel_n_->current_gain_calibration); | ||||
|   } | ||||
|  | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   | ||||
| @@ -19,6 +19,7 @@ from esphome.const import ( | ||||
|     CONF_RESET_PIN, | ||||
|     CONF_REVERSE_ACTIVE_ENERGY, | ||||
|     CONF_VOLTAGE, | ||||
|     CONF_VOLTAGE_GAIN, | ||||
|     DEVICE_CLASS_APPARENT_POWER, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
| @@ -47,7 +48,6 @@ CONF_CURRENT_GAIN = "current_gain" | ||||
| CONF_IRQ0_PIN = "irq0_pin" | ||||
| CONF_IRQ1_PIN = "irq1_pin" | ||||
| CONF_POWER_GAIN = "power_gain" | ||||
| CONF_VOLTAGE_GAIN = "voltage_gain" | ||||
|  | ||||
| CONF_NEUTRAL = "neutral" | ||||
|  | ||||
|   | ||||
| @@ -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." | ||||
| ) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from esphome.const import ( | ||||
|     CONF_IRQ_PIN, | ||||
|     CONF_VOLTAGE, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_VOLTAGE_GAIN, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_APPARENT_POWER, | ||||
|     DEVICE_CLASS_POWER, | ||||
| @@ -36,7 +37,6 @@ CONF_POWER_FACTOR_B = "power_factor_b" | ||||
| CONF_VOLTAGE_PGA_GAIN = "voltage_pga_gain" | ||||
| CONF_CURRENT_PGA_GAIN_A = "current_pga_gain_a" | ||||
| CONF_CURRENT_PGA_GAIN_B = "current_pga_gain_b" | ||||
| CONF_VOLTAGE_GAIN = "voltage_gain" | ||||
| CONF_CURRENT_GAIN_A = "current_gain_a" | ||||
| CONF_CURRENT_GAIN_B = "current_gain_b" | ||||
| CONF_ACTIVE_POWER_GAIN_A = "active_power_gain_a" | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| #include "ade7953_base.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #include <cinttypes> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ade7953_base { | ||||
|  | ||||
| @@ -105,7 +107,7 @@ void ADE7953::update() { | ||||
|     this->last_update_ = now; | ||||
|     // prevent DIV/0 | ||||
|     pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000; | ||||
|     ESP_LOGVV(TAG, "ADE7953::update() diff=%d pf=%f", diff, pf); | ||||
|     ESP_LOGVV(TAG, "ADE7953::update() diff=%" PRIu32 " pf=%f", diff, pf); | ||||
|   } | ||||
|  | ||||
|   // Apparent power | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -4,13 +4,14 @@ from esphome.components import i2c | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| DEPENDENCIES = ["i2c"] | ||||
| AUTO_LOAD = ["sensor", "voltage_sampler"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| ads1115_ns = cg.esphome_ns.namespace("ads1115") | ||||
| ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice) | ||||
|  | ||||
| CONF_CONTINUOUS_MODE = "continuous_mode" | ||||
| CONF_ADS1115_ID = "ads1115_id" | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #include "ads1115.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ads1115 { | ||||
| @@ -75,25 +75,19 @@ void ADS1115Component::dump_config() { | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, "Communication with ADS1115 failed!"); | ||||
|   } | ||||
|  | ||||
|   for (auto *sensor : this->sensors_) { | ||||
|     LOG_SENSOR("  ", "Sensor", sensor); | ||||
|     ESP_LOGCONFIG(TAG, "    Multiplexer: %u", sensor->get_multiplexer()); | ||||
|     ESP_LOGCONFIG(TAG, "    Gain: %u", sensor->get_gain()); | ||||
|     ESP_LOGCONFIG(TAG, "    Resolution: %u", sensor->get_resolution()); | ||||
|   } | ||||
| } | ||||
| float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { | ||||
| float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain, | ||||
|                                             ADS1115Resolution resolution) { | ||||
|   uint16_t config = this->prev_config_; | ||||
|   // Multiplexer | ||||
|   //        0bxBBBxxxxxxxxxxxx | ||||
|   config &= 0b1000111111111111; | ||||
|   config |= (sensor->get_multiplexer() & 0b111) << 12; | ||||
|   config |= (multiplexer & 0b111) << 12; | ||||
|  | ||||
|   // Gain | ||||
|   //        0bxxxxBBBxxxxxxxxx | ||||
|   config &= 0b1111000111111111; | ||||
|   config |= (sensor->get_gain() & 0b111) << 9; | ||||
|   config |= (gain & 0b111) << 9; | ||||
|  | ||||
|   if (!this->continuous_mode_) { | ||||
|     // Start conversion | ||||
| @@ -132,7 +126,7 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { | ||||
|     return NAN; | ||||
|   } | ||||
|  | ||||
|   if (sensor->get_resolution() == ADS1015_12_BITS) { | ||||
|   if (resolution == ADS1015_12_BITS) { | ||||
|     bool negative = (raw_conversion >> 15) == 1; | ||||
|  | ||||
|     // shift raw_conversion as it's only 12-bits, left justified | ||||
| @@ -151,8 +145,8 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { | ||||
|   auto signed_conversion = static_cast<int16_t>(raw_conversion); | ||||
|  | ||||
|   float millivolts; | ||||
|   float divider = (sensor->get_resolution() == ADS1115_16_BITS) ? 32768.0f : 2048.0f; | ||||
|   switch (sensor->get_gain()) { | ||||
|   float divider = (resolution == ADS1115_16_BITS) ? 32768.0f : 2048.0f; | ||||
|   switch (gain) { | ||||
|     case ADS1115_GAIN_6P144: | ||||
|       millivolts = (signed_conversion * 6144) / divider; | ||||
|       break; | ||||
| @@ -179,14 +173,5 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { | ||||
|   return millivolts / 1e3f; | ||||
| } | ||||
|  | ||||
| float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); } | ||||
| void ADS1115Sensor::update() { | ||||
|   float v = this->parent_->request_measurement(this); | ||||
|   if (!std::isnan(v)) { | ||||
|     ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v); | ||||
|     this->publish_state(v); | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace ads1115 | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/components/voltage_sampler/voltage_sampler.h" | ||||
| #include "esphome/core/component.h" | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| @@ -35,12 +33,8 @@ enum ADS1115Resolution { | ||||
|   ADS1015_12_BITS = 12, | ||||
| }; | ||||
|  | ||||
| class ADS1115Sensor; | ||||
|  | ||||
| class ADS1115Component : public Component, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void register_sensor(ADS1115Sensor *obj) { this->sensors_.push_back(obj); } | ||||
|   /// Set up the internal sensor array. | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   /// HARDWARE_LATE setup priority | ||||
| @@ -48,33 +42,12 @@ class ADS1115Component : public Component, public i2c::I2CDevice { | ||||
|   void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; } | ||||
|  | ||||
|   /// Helper method to request a measurement from a sensor. | ||||
|   float request_measurement(ADS1115Sensor *sensor); | ||||
|   float request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution); | ||||
|  | ||||
|  protected: | ||||
|   std::vector<ADS1115Sensor *> sensors_; | ||||
|   uint16_t prev_config_{0}; | ||||
|   bool continuous_mode_; | ||||
| }; | ||||
|  | ||||
| /// Internal holder class that is in instance of Sensor so that the hub can create individual sensors. | ||||
| class ADS1115Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { | ||||
|  public: | ||||
|   ADS1115Sensor(ADS1115Component *parent) : parent_(parent) {} | ||||
|   void update() override; | ||||
|   void set_multiplexer(ADS1115Multiplexer multiplexer) { multiplexer_ = multiplexer; } | ||||
|   void set_gain(ADS1115Gain gain) { gain_ = gain; } | ||||
|   void set_resolution(ADS1115Resolution resolution) { resolution_ = resolution; } | ||||
|   float sample() override; | ||||
|   uint8_t get_multiplexer() const { return multiplexer_; } | ||||
|   uint8_t get_gain() const { return gain_; } | ||||
|   uint8_t get_resolution() const { return resolution_; } | ||||
|  | ||||
|  protected: | ||||
|   ADS1115Component *parent_; | ||||
|   ADS1115Multiplexer multiplexer_; | ||||
|   ADS1115Gain gain_; | ||||
|   ADS1115Resolution resolution_; | ||||
| }; | ||||
|  | ||||
| }  // namespace ads1115 | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -10,8 +10,9 @@ from esphome.const import ( | ||||
|     UNIT_VOLT, | ||||
|     CONF_ID, | ||||
| ) | ||||
| from . import ads1115_ns, ADS1115Component | ||||
| from .. import ads1115_ns, ADS1115Component, CONF_ADS1115_ID | ||||
| 
 | ||||
| AUTO_LOAD = ["voltage_sampler"] | ||||
| DEPENDENCIES = ["ads1115"] | ||||
| 
 | ||||
| ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer") | ||||
| @@ -43,20 +44,10 @@ RESOLUTION = { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def validate_gain(value): | ||||
|     if isinstance(value, float): | ||||
|         value = f"{value:0.03f}" | ||||
|     elif not isinstance(value, str): | ||||
|         raise cv.Invalid(f'invalid gain "{value}"') | ||||
| 
 | ||||
|     return cv.enum(GAIN)(value) | ||||
| 
 | ||||
| 
 | ||||
| ADS1115Sensor = ads1115_ns.class_( | ||||
|     "ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler | ||||
| ) | ||||
| 
 | ||||
| CONF_ADS1115_ID = "ads1115_id" | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema( | ||||
|         ADS1115Sensor, | ||||
| @@ -69,7 +60,7 @@ CONFIG_SCHEMA = ( | ||||
|         { | ||||
|             cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component), | ||||
|             cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"), | ||||
|             cv.Required(CONF_GAIN): validate_gain, | ||||
|             cv.Required(CONF_GAIN): cv.enum(GAIN, string=True), | ||||
|             cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum( | ||||
|                 RESOLUTION, upper=True, space="_" | ||||
|             ), | ||||
| @@ -80,13 +71,11 @@ CONFIG_SCHEMA = ( | ||||
| 
 | ||||
| 
 | ||||
| async def to_code(config): | ||||
|     paren = await cg.get_variable(config[CONF_ADS1115_ID]) | ||||
|     var = cg.new_Pvariable(config[CONF_ID], paren) | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await sensor.register_sensor(var, config) | ||||
|     await cg.register_component(var, config) | ||||
|     await cg.register_parented(var, config[CONF_ADS1115_ID]) | ||||
| 
 | ||||
|     cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER])) | ||||
|     cg.add(var.set_gain(config[CONF_GAIN])) | ||||
|     cg.add(var.set_resolution(config[CONF_RESOLUTION])) | ||||
| 
 | ||||
|     cg.add(paren.register_sensor(var)) | ||||
							
								
								
									
										30
									
								
								esphome/components/ads1115/sensor/ads1115_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/ads1115/sensor/ads1115_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| #include "ads1115_sensor.h" | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ads1115 { | ||||
|  | ||||
| static const char *const TAG = "ads1115.sensor"; | ||||
|  | ||||
| float ADS1115Sensor::sample() { | ||||
|   return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->resolution_); | ||||
| } | ||||
|  | ||||
| void ADS1115Sensor::update() { | ||||
|   float v = this->sample(); | ||||
|   if (!std::isnan(v)) { | ||||
|     ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v); | ||||
|     this->publish_state(v); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ADS1115Sensor::dump_config() { | ||||
|   LOG_SENSOR("  ", "ADS1115 Sensor", this); | ||||
|   ESP_LOGCONFIG(TAG, "    Multiplexer: %u", this->multiplexer_); | ||||
|   ESP_LOGCONFIG(TAG, "    Gain: %u", this->gain_); | ||||
|   ESP_LOGCONFIG(TAG, "    Resolution: %u", this->resolution_); | ||||
| } | ||||
|  | ||||
| }  // namespace ads1115 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										35
									
								
								esphome/components/ads1115/sensor/ads1115_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/ads1115/sensor/ads1115_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/voltage_sampler/voltage_sampler.h" | ||||
|  | ||||
| #include "../ads1115.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ads1115 { | ||||
|  | ||||
| /// Internal holder class that is in instance of Sensor so that the hub can create individual sensors. | ||||
| class ADS1115Sensor : public sensor::Sensor, | ||||
|                       public PollingComponent, | ||||
|                       public voltage_sampler::VoltageSampler, | ||||
|                       public Parented<ADS1115Component> { | ||||
|  public: | ||||
|   void update() override; | ||||
|   void set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; } | ||||
|   void set_gain(ADS1115Gain gain) { this->gain_ = gain; } | ||||
|   void set_resolution(ADS1115Resolution resolution) { this->resolution_ = resolution; } | ||||
|   float sample() override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   ADS1115Multiplexer multiplexer_; | ||||
|   ADS1115Gain gain_; | ||||
|   ADS1115Resolution resolution_; | ||||
| }; | ||||
|  | ||||
| }  // namespace ads1115 | ||||
| }  // namespace esphome | ||||
| @@ -1,5 +1,7 @@ | ||||
| #include "ags10.h" | ||||
|  | ||||
| #include <cinttypes> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ags10 { | ||||
| static const char *const TAG = "ags10"; | ||||
| @@ -35,7 +37,7 @@ void AGS10Component::setup() { | ||||
|  | ||||
|   auto resistance = this->read_resistance_(); | ||||
|   if (resistance) { | ||||
|     ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08X", *resistance); | ||||
|     ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08" PRIX32, *resistance); | ||||
|     if (this->resistance_ != nullptr) { | ||||
|       this->resistance_->publish_state(*resistance); | ||||
|     } | ||||
|   | ||||
| @@ -93,8 +93,9 @@ void AHT10Component::restart_read_() { | ||||
|  | ||||
| void AHT10Component::read_data_() { | ||||
|   uint8_t data[6]; | ||||
|   if (this->read_count_ > 1) | ||||
|   if (this->read_count_ > 1) { | ||||
|     ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_)); | ||||
|   } | ||||
|   if (this->read(data, 6) != i2c::ERROR_OK) { | ||||
|     this->status_set_warning("AHT10 read failed, retrying soon"); | ||||
|     this->restart_read_(); | ||||
| @@ -119,8 +120,9 @@ void AHT10Component::read_data_() { | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|   if (this->read_count_ > 1) | ||||
|   if (this->read_count_ > 1) { | ||||
|     ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_)); | ||||
|   } | ||||
|   uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; | ||||
|   uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; | ||||
|  | ||||
|   | ||||
							
								
								
									
										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,14 +1,17 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| 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, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.cpp_helpers import setup_entity | ||||
|  | ||||
| CODEOWNERS = ["@grahambrown11", "@hwstar"] | ||||
| @@ -75,65 +78,72 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_( | ||||
|     "AlarmControlPanelCondition", automation.Condition | ||||
| ) | ||||
|  | ||||
| ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_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( | ||||
| @@ -185,6 +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 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): | ||||
|   | ||||
| @@ -97,9 +97,11 @@ void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) { | ||||
| void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { | ||||
|   switch (event) { | ||||
|     case ESP_GATTC_OPEN_EVT: { | ||||
|       this->response_offset_ = 0; | ||||
|       this->response_length_ = 0; | ||||
|       ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str()); | ||||
|       if (param->open.status == ESP_GATT_OK) { | ||||
|         this->response_offset_ = 0; | ||||
|         this->response_length_ = 0; | ||||
|         ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str()); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_CONNECT_EVT: { | ||||
|   | ||||
| @@ -26,7 +26,9 @@ void Am43::setup() { | ||||
| void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { | ||||
|   switch (event) { | ||||
|     case ESP_GATTC_OPEN_EVT: { | ||||
|       this->logged_in_ = false; | ||||
|       if (param->open.status == ESP_GATT_OK) { | ||||
|         this->logged_in_ = false; | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_DISCONNECT_EVT: { | ||||
|   | ||||
| @@ -1,18 +1,27 @@ | ||||
| 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 | ||||
| from esphome.components.image import ( | ||||
|     CONF_USE_TRANSPARENCY, | ||||
|     LOCAL_SCHEMA, | ||||
|     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_SOURCE, | ||||
|     CONF_TYPE, | ||||
|     CONF_URL, | ||||
| ) | ||||
| from esphome.core import CORE, HexInt | ||||
|  | ||||
| @@ -43,6 +52,40 @@ SetFrameAction = animation_ns.class_( | ||||
|     "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_) | ||||
| ) | ||||
|  | ||||
| TYPED_FILE_SCHEMA = cv.typed_schema( | ||||
|     { | ||||
|         SOURCE_LOCAL: LOCAL_SCHEMA, | ||||
|         SOURCE_WEB: WEB_SCHEMA, | ||||
|     }, | ||||
|     key=CONF_SOURCE, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def _file_schema(value): | ||||
|     if isinstance(value, str): | ||||
|         return validate_file_shorthand(value) | ||||
|     return TYPED_FILE_SCHEMA(value) | ||||
|  | ||||
|  | ||||
| FILE_SCHEMA = cv.Schema(_file_schema) | ||||
|  | ||||
|  | ||||
| def validate_file_shorthand(value): | ||||
|     value = cv.string_strict(value) | ||||
|     if value.startswith("http://") or value.startswith("https://"): | ||||
|         return FILE_SCHEMA( | ||||
|             { | ||||
|                 CONF_SOURCE: SOURCE_WEB, | ||||
|                 CONF_URL: value, | ||||
|             } | ||||
|         ) | ||||
|     return FILE_SCHEMA( | ||||
|         { | ||||
|             CONF_SOURCE: SOURCE_LOCAL, | ||||
|             CONF_PATH: value, | ||||
|         } | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def validate_cross_dependencies(config): | ||||
|     """ | ||||
| @@ -67,7 +110,7 @@ ANIMATION_SCHEMA = cv.Schema( | ||||
|     cv.All( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.declare_id(Animation_), | ||||
|             cv.Required(CONF_FILE): cv.file_, | ||||
|             cv.Required(CONF_FILE): FILE_SCHEMA, | ||||
|             cv.Optional(CONF_RESIZE): cv.dimensions, | ||||
|             cv.Optional(CONF_TYPE, default="BINARY"): cv.enum( | ||||
|                 espImage.IMAGE_TYPE, upper=True | ||||
| @@ -124,7 +167,14 @@ async def animation_action_to_code(config, action_id, template_arg, args): | ||||
| async def to_code(config): | ||||
|     from PIL import Image | ||||
|  | ||||
|     path = CORE.relative_config_path(config[CONF_FILE]) | ||||
|     conf_file = config[CONF_FILE] | ||||
|     if conf_file[CONF_SOURCE] == SOURCE_LOCAL: | ||||
|         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: | ||||
| @@ -136,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] | ||||
|  | ||||
| @@ -157,7 +206,7 @@ async def to_code(config): | ||||
|             pixels = list(frame.getdata()) | ||||
|             if len(pixels) != height * width: | ||||
|                 raise core.EsphomeError( | ||||
|                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" | ||||
|                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})" | ||||
|                 ) | ||||
|             for pix, a in pixels: | ||||
|                 if transparent: | ||||
| @@ -180,7 +229,7 @@ async def to_code(config): | ||||
|             pixels = list(frame.getdata()) | ||||
|             if len(pixels) != height * width: | ||||
|                 raise core.EsphomeError( | ||||
|                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" | ||||
|                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})" | ||||
|                 ) | ||||
|             for pix in pixels: | ||||
|                 data[pos] = pix[0] | ||||
| @@ -203,7 +252,7 @@ async def to_code(config): | ||||
|             pixels = list(frame.getdata()) | ||||
|             if len(pixels) != height * width: | ||||
|                 raise core.EsphomeError( | ||||
|                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" | ||||
|                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})" | ||||
|                 ) | ||||
|             for r, g, b, a in pixels: | ||||
|                 if transparent: | ||||
| @@ -222,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) | ||||
| @@ -232,24 +282,20 @@ async def to_code(config): | ||||
|             pixels = list(frame.getdata()) | ||||
|             if len(pixels) != height * width: | ||||
|                 raise core.EsphomeError( | ||||
|                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" | ||||
|                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})" | ||||
|                 ) | ||||
|             for r, g, b, a in pixels: | ||||
|                 R = r >> 3 | ||||
|                 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 | ||||
| @@ -259,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) | ||||
|   | ||||
| @@ -43,8 +43,12 @@ service APIConnection { | ||||
|   rpc select_command (SelectCommandRequest) returns (void) {} | ||||
|   rpc button_command (ButtonCommandRequest) returns (void) {} | ||||
|   rpc lock_command (LockCommandRequest) returns (void) {} | ||||
|   rpc valve_command (ValveCommandRequest) returns (void) {} | ||||
|   rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} | ||||
|   rpc date_command (DateCommandRequest) returns (void) {} | ||||
|   rpc time_command (TimeCommandRequest) returns (void) {} | ||||
|   rpc datetime_command (DateTimeCommandRequest) returns (void) {} | ||||
|   rpc update_command (UpdateCommandRequest) returns (void) {} | ||||
|  | ||||
|   rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} | ||||
|   rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} | ||||
| @@ -58,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) {} | ||||
| } | ||||
| @@ -682,6 +688,7 @@ message SubscribeHomeAssistantStateResponse { | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   string entity_id = 1; | ||||
|   string attribute = 2; | ||||
|   bool once = 3; | ||||
| } | ||||
|  | ||||
| message HomeAssistantStateResponse { | ||||
| @@ -1102,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; | ||||
| @@ -1117,6 +1137,8 @@ message ListEntitiesMediaPlayerResponse { | ||||
|   EntityCategory entity_category = 7; | ||||
|  | ||||
|   bool supports_pause = 8; | ||||
|  | ||||
|   repeated MediaPlayerSupportedFormat supported_formats = 9; | ||||
| } | ||||
| message MediaPlayerStateResponse { | ||||
|   option (id) = 64; | ||||
| @@ -1144,6 +1166,9 @@ message MediaPlayerCommandRequest { | ||||
|  | ||||
|   bool has_media_url = 6; | ||||
|   string media_url = 7; | ||||
|  | ||||
|   bool has_announcement = 8; | ||||
|   bool announcement = 9; | ||||
| } | ||||
|  | ||||
| // ==================== BLUETOOTH ==================== | ||||
| @@ -1511,6 +1536,72 @@ message VoiceAssistantAudio { | ||||
|   bool end = 2; | ||||
| } | ||||
|  | ||||
| enum VoiceAssistantTimerEvent { | ||||
|   VOICE_ASSISTANT_TIMER_STARTED = 0; | ||||
|   VOICE_ASSISTANT_TIMER_UPDATED = 1; | ||||
|   VOICE_ASSISTANT_TIMER_CANCELLED = 2; | ||||
|   VOICE_ASSISTANT_TIMER_FINISHED = 3; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantTimerEventResponse { | ||||
|   option (id) = 115; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   VoiceAssistantTimerEvent event_type = 1; | ||||
|   string timer_id = 2; | ||||
|   string name = 3; | ||||
|   uint32 total_seconds = 4; | ||||
|   uint32 seconds_left = 5; | ||||
|   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 { | ||||
| @@ -1658,3 +1749,205 @@ message DateCommandRequest { | ||||
|   uint32 month = 3; | ||||
|   uint32 day = 4; | ||||
| } | ||||
|  | ||||
| // ==================== DATETIME TIME ==================== | ||||
| message ListEntitiesTimeResponse { | ||||
|   option (id) = 103; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_DATETIME_TIME"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
| } | ||||
| message TimeStateResponse { | ||||
|   option (id) = 104; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_DATETIME_TIME"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   // If the time does not have a valid state yet. | ||||
|   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller | ||||
|   bool missing_state = 2; | ||||
|   uint32 hour = 3; | ||||
|   uint32 minute = 4; | ||||
|   uint32 second = 5; | ||||
| } | ||||
| message TimeCommandRequest { | ||||
|   option (id) = 105; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_DATETIME_TIME"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   uint32 hour = 2; | ||||
|   uint32 minute = 3; | ||||
|   uint32 second = 4; | ||||
| } | ||||
|  | ||||
| // ==================== EVENT ==================== | ||||
| message ListEntitiesEventResponse { | ||||
|   option (id) = 107; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_EVENT"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   string device_class = 8; | ||||
|  | ||||
|   repeated string event_types = 9; | ||||
| } | ||||
| message EventResponse { | ||||
|   option (id) = 108; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_EVENT"; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   string event_type = 2; | ||||
| } | ||||
|  | ||||
| // ==================== VALVE ==================== | ||||
| message ListEntitiesValveResponse { | ||||
|   option (id) = 109; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_VALVE"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   string device_class = 8; | ||||
|  | ||||
|   bool assumed_state = 9; | ||||
|   bool supports_position = 10; | ||||
|   bool supports_stop = 11; | ||||
| } | ||||
|  | ||||
| enum ValveOperation { | ||||
|   VALVE_OPERATION_IDLE = 0; | ||||
|   VALVE_OPERATION_IS_OPENING = 1; | ||||
|   VALVE_OPERATION_IS_CLOSING = 2; | ||||
| } | ||||
| message ValveStateResponse { | ||||
|   option (id) = 110; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_VALVE"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   float position = 2; | ||||
|   ValveOperation current_operation = 3; | ||||
| } | ||||
|  | ||||
| message ValveCommandRequest { | ||||
|   option (id) = 111; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VALVE"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool has_position = 2; | ||||
|   float position = 3; | ||||
|   bool stop = 4; | ||||
| } | ||||
|  | ||||
| // ==================== DATETIME DATETIME ==================== | ||||
| message ListEntitiesDateTimeResponse { | ||||
|   option (id) = 112; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_DATETIME_DATETIME"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
| } | ||||
| message DateTimeStateResponse { | ||||
|   option (id) = 113; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_DATETIME_DATETIME"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   // If the datetime does not have a valid state yet. | ||||
|   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller | ||||
|   bool missing_state = 2; | ||||
|   fixed32 epoch_seconds = 3; | ||||
| } | ||||
| message DateTimeCommandRequest { | ||||
|   option (id) = 114; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_DATETIME_DATETIME"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   fixed32 epoch_seconds = 2; | ||||
| } | ||||
|  | ||||
| // ==================== UPDATE ==================== | ||||
| message ListEntitiesUpdateResponse { | ||||
|   option (id) = 116; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_UPDATE"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   string device_class = 8; | ||||
| } | ||||
| message UpdateStateResponse { | ||||
|   option (id) = 117; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_UPDATE"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool missing_state = 2; | ||||
|   bool in_progress = 3; | ||||
|   bool has_progress = 4; | ||||
|   float progress = 5; | ||||
|   string current_version = 6; | ||||
|   string latest_version = 7; | ||||
|   string title = 8; | ||||
|   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; | ||||
|   option (ifdef) = "USE_UPDATE"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   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_++; | ||||
|       } | ||||
| @@ -735,6 +737,81 @@ void APIConnection::date_command(const DateCommandRequest &msg) { | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_TIME | ||||
| bool APIConnection::send_time_state(datetime::TimeEntity *time) { | ||||
|   if (!this->state_subscription_) | ||||
|     return false; | ||||
|  | ||||
|   TimeStateResponse resp{}; | ||||
|   resp.key = time->get_object_id_hash(); | ||||
|   resp.missing_state = !time->has_state(); | ||||
|   resp.hour = time->hour; | ||||
|   resp.minute = time->minute; | ||||
|   resp.second = time->second; | ||||
|   return this->send_time_state_response(resp); | ||||
| } | ||||
| bool APIConnection::send_time_info(datetime::TimeEntity *time) { | ||||
|   ListEntitiesTimeResponse msg; | ||||
|   msg.key = time->get_object_id_hash(); | ||||
|   msg.object_id = time->get_object_id(); | ||||
|   if (time->has_own_name()) | ||||
|     msg.name = time->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("time", time); | ||||
|   msg.icon = time->get_icon(); | ||||
|   msg.disabled_by_default = time->is_disabled_by_default(); | ||||
|   msg.entity_category = static_cast<enums::EntityCategory>(time->get_entity_category()); | ||||
|  | ||||
|   return this->send_list_entities_time_response(msg); | ||||
| } | ||||
| void APIConnection::time_command(const TimeCommandRequest &msg) { | ||||
|   datetime::TimeEntity *time = App.get_time_by_key(msg.key); | ||||
|   if (time == nullptr) | ||||
|     return; | ||||
|  | ||||
|   auto call = time->make_call(); | ||||
|   call.set_time(msg.hour, msg.minute, msg.second); | ||||
|   call.perform(); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { | ||||
|   if (!this->state_subscription_) | ||||
|     return false; | ||||
|  | ||||
|   DateTimeStateResponse resp{}; | ||||
|   resp.key = datetime->get_object_id_hash(); | ||||
|   resp.missing_state = !datetime->has_state(); | ||||
|   if (datetime->has_state()) { | ||||
|     ESPTime state = datetime->state_as_esptime(); | ||||
|     resp.epoch_seconds = state.timestamp; | ||||
|   } | ||||
|   return this->send_date_time_state_response(resp); | ||||
| } | ||||
| bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { | ||||
|   ListEntitiesDateTimeResponse msg; | ||||
|   msg.key = datetime->get_object_id_hash(); | ||||
|   msg.object_id = datetime->get_object_id(); | ||||
|   if (datetime->has_own_name()) | ||||
|     msg.name = datetime->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("datetime", datetime); | ||||
|   msg.icon = datetime->get_icon(); | ||||
|   msg.disabled_by_default = datetime->is_disabled_by_default(); | ||||
|   msg.entity_category = static_cast<enums::EntityCategory>(datetime->get_entity_category()); | ||||
|  | ||||
|   return this->send_list_entities_date_time_response(msg); | ||||
| } | ||||
| void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { | ||||
|   datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key); | ||||
|   if (datetime == nullptr) | ||||
|     return; | ||||
|  | ||||
|   auto call = datetime->make_call(); | ||||
|   call.set_datetime(msg.epoch_seconds); | ||||
|   call.perform(); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TEXT | ||||
| bool APIConnection::send_text_state(text::Text *text, std::string state) { | ||||
|   if (!this->state_subscription_) | ||||
| @@ -878,6 +955,48 @@ void APIConnection::lock_command(const LockCommandRequest &msg) { | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_VALVE | ||||
| bool APIConnection::send_valve_state(valve::Valve *valve) { | ||||
|   if (!this->state_subscription_) | ||||
|     return false; | ||||
|  | ||||
|   ValveStateResponse resp{}; | ||||
|   resp.key = valve->get_object_id_hash(); | ||||
|   resp.position = valve->position; | ||||
|   resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation); | ||||
|   return this->send_valve_state_response(resp); | ||||
| } | ||||
| bool APIConnection::send_valve_info(valve::Valve *valve) { | ||||
|   auto traits = valve->get_traits(); | ||||
|   ListEntitiesValveResponse msg; | ||||
|   msg.key = valve->get_object_id_hash(); | ||||
|   msg.object_id = valve->get_object_id(); | ||||
|   if (valve->has_own_name()) | ||||
|     msg.name = valve->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("valve", valve); | ||||
|   msg.icon = valve->get_icon(); | ||||
|   msg.disabled_by_default = valve->is_disabled_by_default(); | ||||
|   msg.entity_category = static_cast<enums::EntityCategory>(valve->get_entity_category()); | ||||
|   msg.device_class = valve->get_device_class(); | ||||
|   msg.assumed_state = traits.get_is_assumed_state(); | ||||
|   msg.supports_position = traits.get_supports_position(); | ||||
|   msg.supports_stop = traits.get_supports_stop(); | ||||
|   return this->send_list_entities_valve_response(msg); | ||||
| } | ||||
| void APIConnection::valve_command(const ValveCommandRequest &msg) { | ||||
|   valve::Valve *valve = App.get_valve_by_key(msg.key); | ||||
|   if (valve == nullptr) | ||||
|     return; | ||||
|  | ||||
|   auto call = valve->make_call(); | ||||
|   if (msg.has_position) | ||||
|     call.set_position(msg.position); | ||||
|   if (msg.stop) | ||||
|     call.set_command_stop(); | ||||
|   call.perform(); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { | ||||
|   if (!this->state_subscription_) | ||||
| @@ -885,7 +1004,11 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla | ||||
|  | ||||
|   MediaPlayerStateResponse resp{}; | ||||
|   resp.key = media_player->get_object_id_hash(); | ||||
|   resp.state = static_cast<enums::MediaPlayerState>(media_player->state); | ||||
|  | ||||
|   media_player::MediaPlayerState report_state = media_player->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING | ||||
|                                                     ? media_player::MEDIA_PLAYER_STATE_PLAYING | ||||
|                                                     : media_player->state; | ||||
|   resp.state = static_cast<enums::MediaPlayerState>(report_state); | ||||
|   resp.volume = media_player->volume; | ||||
|   resp.muted = media_player->is_muted(); | ||||
|   return this->send_media_player_state_response(resp); | ||||
| @@ -904,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) { | ||||
| @@ -921,6 +1054,9 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | ||||
|   if (msg.has_media_url) { | ||||
|     call.set_media_url(msg.media_url); | ||||
|   } | ||||
|   if (msg.has_announcement) { | ||||
|     call.set_announcement(msg.announcement); | ||||
|   } | ||||
|   call.perform(); | ||||
| } | ||||
| #endif | ||||
| @@ -1069,6 +1205,61 @@ void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) { | ||||
|     voice_assistant::global_voice_assistant->on_audio(msg); | ||||
|   } | ||||
| }; | ||||
| void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &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_timer_event(msg); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| 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 | ||||
|  | ||||
| @@ -1130,6 +1321,88 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_EVENT | ||||
| bool APIConnection::send_event(event::Event *event, std::string event_type) { | ||||
|   EventResponse resp{}; | ||||
|   resp.key = event->get_object_id_hash(); | ||||
|   resp.event_type = std::move(event_type); | ||||
|   return this->send_event_response(resp); | ||||
| } | ||||
| bool APIConnection::send_event_info(event::Event *event) { | ||||
|   ListEntitiesEventResponse msg; | ||||
|   msg.key = event->get_object_id_hash(); | ||||
|   msg.object_id = event->get_object_id(); | ||||
|   if (event->has_own_name()) | ||||
|     msg.name = event->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("event", event); | ||||
|   msg.icon = event->get_icon(); | ||||
|   msg.disabled_by_default = event->is_disabled_by_default(); | ||||
|   msg.entity_category = static_cast<enums::EntityCategory>(event->get_entity_category()); | ||||
|   msg.device_class = event->get_device_class(); | ||||
|   for (const auto &event_type : event->get_event_types()) | ||||
|     msg.event_types.push_back(event_type); | ||||
|   return this->send_list_entities_event_response(msg); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_UPDATE | ||||
| bool APIConnection::send_update_state(update::UpdateEntity *update) { | ||||
|   if (!this->state_subscription_) | ||||
|     return false; | ||||
|  | ||||
|   UpdateStateResponse resp{}; | ||||
|   resp.key = update->get_object_id_hash(); | ||||
|   resp.missing_state = !update->has_state(); | ||||
|   if (update->has_state()) { | ||||
|     resp.in_progress = update->state == update::UpdateState::UPDATE_STATE_INSTALLING; | ||||
|     if (update->update_info.has_progress) { | ||||
|       resp.has_progress = true; | ||||
|       resp.progress = update->update_info.progress; | ||||
|     } | ||||
|     resp.current_version = update->update_info.current_version; | ||||
|     resp.latest_version = update->update_info.latest_version; | ||||
|     resp.title = update->update_info.title; | ||||
|     resp.release_summary = update->update_info.summary; | ||||
|     resp.release_url = update->update_info.release_url; | ||||
|   } | ||||
|  | ||||
|   return this->send_update_state_response(resp); | ||||
| } | ||||
| bool APIConnection::send_update_info(update::UpdateEntity *update) { | ||||
|   ListEntitiesUpdateResponse msg; | ||||
|   msg.key = update->get_object_id_hash(); | ||||
|   msg.object_id = update->get_object_id(); | ||||
|   if (update->has_own_name()) | ||||
|     msg.name = update->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("update", update); | ||||
|   msg.icon = update->get_icon(); | ||||
|   msg.disabled_by_default = update->is_disabled_by_default(); | ||||
|   msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category()); | ||||
|   msg.device_class = update->get_device_class(); | ||||
|   return this->send_list_entities_update_response(msg); | ||||
| } | ||||
| void APIConnection::update_command(const UpdateCommandRequest &msg) { | ||||
|   update::UpdateEntity *update = App.get_update_by_key(msg.key); | ||||
|   if (update == nullptr) | ||||
|     return; | ||||
|  | ||||
|   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 | ||||
|  | ||||
| bool APIConnection::send_log_message(int level, const char *tag, const char *line) { | ||||
|   if (this->log_subscription_ < level) | ||||
|     return false; | ||||
| @@ -1296,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> | ||||
|  | ||||
| @@ -77,6 +78,16 @@ class APIConnection : public APIServerConnection { | ||||
|   bool send_date_info(datetime::DateEntity *date); | ||||
|   void date_command(const DateCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool send_time_state(datetime::TimeEntity *time); | ||||
|   bool send_time_info(datetime::TimeEntity *time); | ||||
|   void time_command(const TimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool send_datetime_state(datetime::DateTimeEntity *datetime); | ||||
|   bool send_datetime_info(datetime::DateTimeEntity *datetime); | ||||
|   void datetime_command(const DateTimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool send_text_state(text::Text *text, std::string state); | ||||
|   bool send_text_info(text::Text *text); | ||||
| @@ -96,6 +107,11 @@ class APIConnection : public APIServerConnection { | ||||
|   bool send_lock_info(lock::Lock *a_lock); | ||||
|   void lock_command(const LockCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool send_valve_state(valve::Valve *valve); | ||||
|   bool send_valve_info(valve::Valve *valve); | ||||
|   void valve_command(const ValveCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   bool send_media_player_state(media_player::MediaPlayer *media_player); | ||||
|   bool send_media_player_info(media_player::MediaPlayer *media_player); | ||||
| @@ -135,6 +151,11 @@ class APIConnection : public APIServerConnection { | ||||
|   void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; | ||||
|   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 | ||||
| @@ -143,6 +164,17 @@ class APIConnection : public APIServerConnection { | ||||
|   void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_EVENT | ||||
|   bool send_event(event::Event *event, std::string event_type); | ||||
|   bool send_event_info(event::Event *event); | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_UPDATE | ||||
|   bool send_update_state(update::UpdateEntity *update); | ||||
|   bool send_update_info(update::UpdateEntity *update); | ||||
|   void update_command(const UpdateCommandRequest &msg) override; | ||||
| #endif | ||||
|  | ||||
|   void on_disconnect_response(const DisconnectResponse &value) override; | ||||
|   void on_ping_response(const PingResponse &value) override { | ||||
|     // we initiated ping | ||||
| @@ -237,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 | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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, | ||||
| @@ -191,6 +195,12 @@ enum VoiceAssistantEvent : uint32_t { | ||||
|   VOICE_ASSISTANT_TTS_STREAM_START = 98, | ||||
|   VOICE_ASSISTANT_TTS_STREAM_END = 99, | ||||
| }; | ||||
| enum VoiceAssistantTimerEvent : uint32_t { | ||||
|   VOICE_ASSISTANT_TIMER_STARTED = 0, | ||||
|   VOICE_ASSISTANT_TIMER_UPDATED = 1, | ||||
|   VOICE_ASSISTANT_TIMER_CANCELLED = 2, | ||||
|   VOICE_ASSISTANT_TIMER_FINISHED = 3, | ||||
| }; | ||||
| enum AlarmControlPanelState : uint32_t { | ||||
|   ALARM_STATE_DISARMED = 0, | ||||
|   ALARM_STATE_ARMED_HOME = 1, | ||||
| @@ -216,6 +226,16 @@ enum TextMode : uint32_t { | ||||
|   TEXT_MODE_TEXT = 0, | ||||
|   TEXT_MODE_PASSWORD = 1, | ||||
| }; | ||||
| enum ValveOperation : uint32_t { | ||||
|   VALVE_OPERATION_IDLE = 0, | ||||
|   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 | ||||
|  | ||||
| @@ -820,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; | ||||
| @@ -827,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: | ||||
| @@ -1249,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{}; | ||||
| @@ -1259,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; | ||||
| @@ -1293,6 +1332,8 @@ class MediaPlayerCommandRequest : public ProtoMessage { | ||||
|   float volume{0.0f}; | ||||
|   bool has_media_url{false}; | ||||
|   std::string media_url{}; | ||||
|   bool has_announcement{false}; | ||||
|   bool announcement{false}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -1768,6 +1809,93 @@ class VoiceAssistantAudio : public ProtoMessage { | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class VoiceAssistantTimerEventResponse : public ProtoMessage { | ||||
|  public: | ||||
|   enums::VoiceAssistantTimerEvent event_type{}; | ||||
|   std::string timer_id{}; | ||||
|   std::string name{}; | ||||
|   uint32_t total_seconds{0}; | ||||
|   uint32_t seconds_left{0}; | ||||
|   bool is_active{false}; | ||||
|   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 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{}; | ||||
| @@ -1919,6 +2047,242 @@ class DateCommandRequest : public ProtoMessage { | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class ListEntitiesTimeResponse : public ProtoMessage { | ||||
|  public: | ||||
|   std::string object_id{}; | ||||
|   uint32_t key{0}; | ||||
|   std::string name{}; | ||||
|   std::string unique_id{}; | ||||
|   std::string icon{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class TimeStateResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   bool missing_state{false}; | ||||
|   uint32_t hour{0}; | ||||
|   uint32_t minute{0}; | ||||
|   uint32_t second{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class TimeCommandRequest : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   uint32_t hour{0}; | ||||
|   uint32_t minute{0}; | ||||
|   uint32_t second{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class ListEntitiesEventResponse : public ProtoMessage { | ||||
|  public: | ||||
|   std::string object_id{}; | ||||
|   uint32_t key{0}; | ||||
|   std::string name{}; | ||||
|   std::string unique_id{}; | ||||
|   std::string icon{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   std::string device_class{}; | ||||
|   std::vector<std::string> event_types{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class EventResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   std::string event_type{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
| }; | ||||
| class ListEntitiesValveResponse : public ProtoMessage { | ||||
|  public: | ||||
|   std::string object_id{}; | ||||
|   uint32_t key{0}; | ||||
|   std::string name{}; | ||||
|   std::string unique_id{}; | ||||
|   std::string icon{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   std::string device_class{}; | ||||
|   bool assumed_state{false}; | ||||
|   bool supports_position{false}; | ||||
|   bool supports_stop{false}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class ValveStateResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   float position{0.0f}; | ||||
|   enums::ValveOperation current_operation{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class ValveCommandRequest : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   bool has_position{false}; | ||||
|   float position{0.0f}; | ||||
|   bool stop{false}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class ListEntitiesDateTimeResponse : public ProtoMessage { | ||||
|  public: | ||||
|   std::string object_id{}; | ||||
|   uint32_t key{0}; | ||||
|   std::string name{}; | ||||
|   std::string unique_id{}; | ||||
|   std::string icon{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class DateTimeStateResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   bool missing_state{false}; | ||||
|   uint32_t epoch_seconds{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class DateTimeCommandRequest : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   uint32_t epoch_seconds{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
| }; | ||||
| class ListEntitiesUpdateResponse : public ProtoMessage { | ||||
|  public: | ||||
|   std::string object_id{}; | ||||
|   uint32_t key{0}; | ||||
|   std::string name{}; | ||||
|   std::string unique_id{}; | ||||
|   std::string icon{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   std::string device_class{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class UpdateStateResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   bool missing_state{false}; | ||||
|   bool in_progress{false}; | ||||
|   bool has_progress{false}; | ||||
|   float progress{0.0f}; | ||||
|   std::string current_version{}; | ||||
|   std::string latest_version{}; | ||||
|   std::string title{}; | ||||
|   std::string release_summary{}; | ||||
|   std::string release_url{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class UpdateCommandRequest : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t key{0}; | ||||
|   enums::UpdateCommand command{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -484,6 +484,31 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud | ||||
|   return this->send_message_<VoiceAssistantAudio>(msg, 106); | ||||
| } | ||||
| #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) { | ||||
| @@ -539,6 +564,94 @@ bool APIServerConnectionBase::send_date_state_response(const DateStateResponse & | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
| bool APIServerConnectionBase::send_list_entities_time_response(const ListEntitiesTimeResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_list_entities_time_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<ListEntitiesTimeResponse>(msg, 103); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
| bool APIServerConnectionBase::send_time_state_response(const TimeStateResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_time_state_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<TimeStateResponse>(msg, 104); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
| bool APIServerConnectionBase::send_list_entities_event_response(const ListEntitiesEventResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_list_entities_event_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<ListEntitiesEventResponse>(msg, 107); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
| bool APIServerConnectionBase::send_event_response(const EventResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_event_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<EventResponse>(msg, 108); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| bool APIServerConnectionBase::send_list_entities_valve_response(const ListEntitiesValveResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_list_entities_valve_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<ListEntitiesValveResponse>(msg, 109); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| bool APIServerConnectionBase::send_valve_state_response(const ValveStateResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_valve_state_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<ValveStateResponse>(msg, 110); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool APIServerConnectionBase::send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_list_entities_date_time_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<ListEntitiesDateTimeResponse>(msg, 112); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool APIServerConnectionBase::send_date_time_state_response(const DateTimeStateResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_date_time_state_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<DateTimeStateResponse>(msg, 113); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| bool APIServerConnectionBase::send_list_entities_update_response(const ListEntitiesUpdateResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_list_entities_update_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<ListEntitiesUpdateResponse>(msg, 116); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| bool APIServerConnectionBase::send_update_state_response(const UpdateStateResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_update_state_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<UpdateStateResponse>(msg, 117); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| #endif | ||||
| bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||
|   switch (msg_type) { | ||||
|     case 1: { | ||||
| @@ -979,6 +1092,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_date_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 105: { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|       TimeCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_time_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_time_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
| @@ -990,6 +1114,83 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_audio(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 111: { | ||||
| #ifdef USE_VALVE | ||||
|       ValveCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_valve_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 114: { | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|       DateTimeCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_date_time_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 115: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       VoiceAssistantTimerEventResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_timer_event_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 118: { | ||||
| #ifdef USE_UPDATE | ||||
|       UpdateCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       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; | ||||
|     } | ||||
| @@ -1253,6 +1454,19 @@ void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) | ||||
|   this->lock_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->valve_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
| @@ -1279,6 +1493,45 @@ void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) | ||||
|   this->date_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
| void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->time_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->datetime_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->update_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( | ||||
|     const SubscribeBluetoothLEAdvertisementsRequest &msg) { | ||||
| @@ -1428,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()) { | ||||
|   | ||||
| @@ -244,6 +244,24 @@ class APIServerConnectionBase : public ProtoService { | ||||
|   bool send_voice_assistant_audio(const VoiceAssistantAudio &msg); | ||||
|   virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){}; | ||||
| #endif | ||||
| #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 | ||||
| @@ -270,6 +288,48 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   virtual void on_date_command_request(const DateCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool send_list_entities_time_response(const ListEntitiesTimeResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool send_time_state_response(const TimeStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   virtual void on_time_command_request(const TimeCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   bool send_list_entities_event_response(const ListEntitiesEventResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   bool send_event_response(const EventResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool send_list_entities_valve_response(const ListEntitiesValveResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool send_valve_state_response(const ValveStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   virtual void on_valve_command_request(const ValveCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool send_date_time_state_response(const DateTimeStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   virtual void on_date_time_command_request(const DateTimeCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   bool send_list_entities_update_response(const ListEntitiesUpdateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   bool send_update_state_response(const UpdateStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   virtual void on_update_command_request(const UpdateCommandRequest &value){}; | ||||
| #endif | ||||
|  protected: | ||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
| @@ -322,12 +382,24 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_LOCK | ||||
|   virtual void lock_command(const LockCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   virtual void valve_command(const ValveCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   virtual void date_command(const DateCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   virtual void time_command(const TimeCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   virtual void datetime_command(const DateTimeCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   virtual void update_command(const UpdateCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; | ||||
| #endif | ||||
| @@ -362,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 | ||||
| @@ -411,12 +490,24 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_LOCK | ||||
|   void on_lock_command_request(const LockCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   void on_valve_command_request(const ValveCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   void on_date_command_request(const DateCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   void on_time_command_request(const TimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   void on_date_time_command_request(const DateTimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   void on_update_command_request(const UpdateCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; | ||||
| #endif | ||||
| @@ -451,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" | ||||
| @@ -264,6 +265,24 @@ void APIServer::on_date_update(datetime::DateEntity *obj) { | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_TIME | ||||
| void APIServer::on_time_update(datetime::TimeEntity *obj) { | ||||
|   if (obj->is_internal()) | ||||
|     return; | ||||
|   for (auto &c : this->clients_) | ||||
|     c->send_time_state(obj); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| void APIServer::on_datetime_update(datetime::DateTimeEntity *obj) { | ||||
|   if (obj->is_internal()) | ||||
|     return; | ||||
|   for (auto &c : this->clients_) | ||||
|     c->send_datetime_state(obj); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TEXT | ||||
| void APIServer::on_text_update(text::Text *obj, const std::string &state) { | ||||
|   if (obj->is_internal()) | ||||
| @@ -291,6 +310,15 @@ void APIServer::on_lock_update(lock::Lock *obj) { | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_VALVE | ||||
| void APIServer::on_valve_update(valve::Valve *obj) { | ||||
|   if (obj->is_internal()) | ||||
|     return; | ||||
|   for (auto &c : this->clients_) | ||||
|     c->send_valve_state(obj); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| void APIServer::on_media_player_update(media_player::MediaPlayer *obj) { | ||||
|   if (obj->is_internal()) | ||||
| @@ -300,6 +328,20 @@ void APIServer::on_media_player_update(media_player::MediaPlayer *obj) { | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_EVENT | ||||
| void APIServer::on_event(event::Event *obj, const std::string &event_type) { | ||||
|   for (auto &c : this->clients_) | ||||
|     c->send_event(obj, event_type); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_UPDATE | ||||
| void APIServer::on_update(update::UpdateEntity *obj) { | ||||
|   for (auto &c : this->clients_) | ||||
|     c->send_update_state(obj); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } | ||||
| void APIServer::set_port(uint16_t port) { this->port_ = port; } | ||||
| APIServer *global_api_server = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
| @@ -318,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_; | ||||
| } | ||||
| @@ -352,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" | ||||
| @@ -69,6 +70,12 @@ class APIServer : public Component, public Controller { | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   void on_date_update(datetime::DateEntity *obj) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   void on_time_update(datetime::TimeEntity *obj) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   void on_datetime_update(datetime::DateTimeEntity *obj) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   void on_text_update(text::Text *obj, const std::string &state) override; | ||||
| #endif | ||||
| @@ -78,6 +85,9 @@ class APIServer : public Component, public Controller { | ||||
| #ifdef USE_LOCK | ||||
|   void on_lock_update(lock::Lock *obj) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   void on_valve_update(valve::Valve *obj) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   void on_media_player_update(media_player::MediaPlayer *obj) override; | ||||
| #endif | ||||
| @@ -90,6 +100,12 @@ class APIServer : public Component, public Controller { | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override; | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   void on_event(event::Event *obj, const std::string &event_type) override; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   void on_update(update::UpdateEntity *obj) override; | ||||
| #endif | ||||
|  | ||||
|   bool is_connected() const; | ||||
|  | ||||
| @@ -97,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_; } | ||||
|  | ||||
| @@ -135,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 { | ||||
|  | ||||
| @@ -105,7 +105,7 @@ class CustomAPIDevice { | ||||
|   /** Subscribe to the state (or attribute state) of an entity from Home Assistant. | ||||
|    * | ||||
|    * Usage: | ||||
|    *å | ||||
|    * | ||||
|    * ```cpp | ||||
|    * void setup() override { | ||||
|    *   subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast"); | ||||
| @@ -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" | ||||
| @@ -38,6 +39,9 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) | ||||
| #ifdef USE_LOCK | ||||
| bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); } | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| bool ListEntitiesIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_info(valve); } | ||||
| #endif | ||||
|  | ||||
| bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } | ||||
| ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} | ||||
| @@ -64,6 +68,16 @@ bool ListEntitiesIterator::on_number(number::Number *number) { return this->clie | ||||
| bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_TIME | ||||
| bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { | ||||
|   return this->client_->send_datetime_info(datetime); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TEXT | ||||
| bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); } | ||||
| #endif | ||||
| @@ -82,6 +96,13 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont | ||||
|   return this->client_->send_alarm_control_panel_info(a_alarm_control_panel); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
| bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); } | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); } | ||||
| #endif | ||||
|  | ||||
| }  // 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 { | ||||
|  | ||||
| @@ -49,6 +49,12 @@ class ListEntitiesIterator : public ComponentIterator { | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   bool on_date(datetime::DateEntity *date) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool on_time(datetime::TimeEntity *time) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool on_datetime(datetime::DateTimeEntity *datetime) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool on_text(text::Text *text) override; | ||||
| #endif | ||||
| @@ -58,11 +64,20 @@ class ListEntitiesIterator : public ComponentIterator { | ||||
| #ifdef USE_LOCK | ||||
|   bool on_lock(lock::Lock *a_lock) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool on_valve(valve::Valve *valve) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   bool on_media_player(media_player::MediaPlayer *media_player) override; | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   bool on_event(event::Event *event) override; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   bool on_update(update::UpdateEntity *update) override; | ||||
| #endif | ||||
|   bool on_end() override; | ||||
|  | ||||
| @@ -72,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" | ||||
|  | ||||
| @@ -45,6 +46,14 @@ bool InitialStateIterator::on_number(number::Number *number) { | ||||
| #ifdef USE_DATETIME_DATE | ||||
| bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
| bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool InitialStateIterator::on_datetime(datetime::DateTimeEntity *datetime) { | ||||
|   return this->client_->send_datetime_state(datetime); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
| bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); } | ||||
| #endif | ||||
| @@ -56,6 +65,9 @@ bool InitialStateIterator::on_select(select::Select *select) { | ||||
| #ifdef USE_LOCK | ||||
| bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| bool InitialStateIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_state(valve); } | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_player) { | ||||
|   return this->client_->send_media_player_state(media_player); | ||||
| @@ -66,7 +78,11 @@ bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont | ||||
|   return this->client_->send_alarm_control_panel_state(a_alarm_control_panel); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); } | ||||
| #endif | ||||
| InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} | ||||
|  | ||||
| }  // 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 { | ||||
|  | ||||
| @@ -46,6 +46,12 @@ class InitialStateIterator : public ComponentIterator { | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   bool on_date(datetime::DateEntity *date) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool on_time(datetime::TimeEntity *time) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool on_datetime(datetime::DateTimeEntity *datetime) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool on_text(text::Text *text) override; | ||||
| #endif | ||||
| @@ -55,11 +61,20 @@ class InitialStateIterator : public ComponentIterator { | ||||
| #ifdef USE_LOCK | ||||
|   bool on_lock(lock::Lock *a_lock) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool on_valve(valve::Valve *valve) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   bool on_media_player(media_player::MediaPlayer *media_player) override; | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   bool on_event(event::Event *event) override { return true; }; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   bool on_update(update::UpdateEntity *update) override; | ||||
| #endif | ||||
|  protected: | ||||
|   APIConnection *client_; | ||||
| @@ -67,3 +82,4 @@ class InitialStateIterator : public ComponentIterator { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -54,7 +54,6 @@ FAST_FILTER = { | ||||
|     "LSB10": 7, | ||||
| } | ||||
|  | ||||
| CONF_ANGLE = "angle" | ||||
| CONF_RAW_ANGLE = "raw_angle" | ||||
| CONF_RAW_POSITION = "raw_position" | ||||
| CONF_WATCHDOG = "watchdog" | ||||
|   | ||||
| @@ -11,6 +11,7 @@ from esphome.const import ( | ||||
|     CONF_MAGNITUDE, | ||||
|     CONF_STATUS, | ||||
|     CONF_POSITION, | ||||
|     CONF_ANGLE, | ||||
| ) | ||||
| from .. import as5600_ns, AS5600Component | ||||
|  | ||||
| @@ -19,7 +20,6 @@ DEPENDENCIES = ["as5600"] | ||||
|  | ||||
| AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingComponent) | ||||
|  | ||||
| CONF_ANGLE = "angle" | ||||
| CONF_RAW_ANGLE = "raw_angle" | ||||
| CONF_RAW_POSITION = "raw_position" | ||||
| CONF_WATCHDOG = "watchdog" | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -22,7 +22,6 @@ CONF_AT581X_ID = "at581x_id" | ||||
|  | ||||
|  | ||||
| CONF_SENSING_DISTANCE = "sensing_distance" | ||||
| CONF_SENSITIVITY = "sensitivity" | ||||
| CONF_POWERON_SELFCHECK_TIME = "poweron_selfcheck_time" | ||||
| CONF_PROTECT_TIME = "protect_time" | ||||
| CONF_TRIGGER_BASE = "trigger_base" | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user