mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			43 Commits
		
	
	
		
			2025.5.0
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e8f3eefd49 | ||
| 
						 | 
					679f8140c6 | ||
| 
						 | 
					5f1a08c922 | ||
| 
						 | 
					9567b45b30 | ||
| 
						 | 
					6d8afce3cd | ||
| 
						 | 
					e0485894ca | ||
| 
						 | 
					d5c008c027 | ||
| 
						 | 
					f72151ac9b | ||
| 
						 | 
					aa72db9db6 | ||
| 
						 | 
					3773aa6f41 | ||
| 
						 | 
					abb812b9e1 | ||
| 
						 | 
					85a3789fb9 | ||
| 
						 | 
					b6f69cef56 | ||
| 
						 | 
					219d704ce4 | ||
| 
						 | 
					14bc9b64bd | ||
| 
						 | 
					dd7f56e502 | ||
| 
						 | 
					c018ea0848 | ||
| 
						 | 
					317ee53188 | ||
| 
						 | 
					6e09f4b4d7 | ||
| 
						 | 
					709a92649c | ||
| 
						 | 
					78a8271797 | ||
| 
						 | 
					970680b1b2 | ||
| 
						 | 
					f500bd5e6f | ||
| 
						 | 
					e2bb81e233 | ||
| 
						 | 
					26a1d14ee0 | ||
| 
						 | 
					97f07f8d13 | ||
| 
						 | 
					ff9bffc363 | ||
| 
						 | 
					89b3af8be4 | ||
| 
						 | 
					c9b2e54c1a | ||
| 
						 | 
					6dd92053b5 | ||
| 
						 | 
					33346c0b6a | ||
| 
						 | 
					161fbecfe1 | ||
| 
						 | 
					fce2eafda0 | ||
| 
						 | 
					c19f0cf6bc | ||
| 
						 | 
					b05e7bfe0a | ||
| 
						 | 
					3e58ee2130 | ||
| 
						 | 
					bab9c7c70e | ||
| 
						 | 
					0b60a1d9eb | ||
| 
						 | 
					f7455ad76a | ||
| 
						 | 
					3190e86ba8 | ||
| 
						 | 
					a34569d314 | ||
| 
						 | 
					6c1c200cf9 | ||
| 
						 | 
					3635179564 | 
							
								
								
									
										36
									
								
								.clang-tidy
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								.clang-tidy
									
									
									
									
									
								
							@@ -7,39 +7,28 @@ Checks: >-
 | 
				
			|||||||
  -boost-*,
 | 
					  -boost-*,
 | 
				
			||||||
  -bugprone-easily-swappable-parameters,
 | 
					  -bugprone-easily-swappable-parameters,
 | 
				
			||||||
  -bugprone-implicit-widening-of-multiplication-result,
 | 
					  -bugprone-implicit-widening-of-multiplication-result,
 | 
				
			||||||
  -bugprone-multi-level-implicit-pointer-conversion,
 | 
					 | 
				
			||||||
  -bugprone-narrowing-conversions,
 | 
					  -bugprone-narrowing-conversions,
 | 
				
			||||||
  -bugprone-signed-char-misuse,
 | 
					  -bugprone-signed-char-misuse,
 | 
				
			||||||
  -bugprone-switch-missing-default-case,
 | 
					 | 
				
			||||||
  -cert-dcl50-cpp,
 | 
					  -cert-dcl50-cpp,
 | 
				
			||||||
  -cert-err33-c,
 | 
					  -cert-err33-c,
 | 
				
			||||||
  -cert-err58-cpp,
 | 
					  -cert-err58-cpp,
 | 
				
			||||||
  -cert-oop57-cpp,
 | 
					  -cert-oop57-cpp,
 | 
				
			||||||
  -cert-str34-c,
 | 
					  -cert-str34-c,
 | 
				
			||||||
  -clang-analyzer-optin.core.EnumCastOutOfRange,
 | 
					 | 
				
			||||||
  -clang-analyzer-optin.cplusplus.UninitializedObject,
 | 
					  -clang-analyzer-optin.cplusplus.UninitializedObject,
 | 
				
			||||||
  -clang-analyzer-osx.*,
 | 
					  -clang-analyzer-osx.*,
 | 
				
			||||||
  -clang-diagnostic-delete-abstract-non-virtual-dtor,
 | 
					  -clang-diagnostic-delete-abstract-non-virtual-dtor,
 | 
				
			||||||
  -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
 | 
					  -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
 | 
				
			||||||
  -clang-diagnostic-deprecated-declarations,
 | 
					 | 
				
			||||||
  -clang-diagnostic-ignored-optimization-argument,
 | 
					  -clang-diagnostic-ignored-optimization-argument,
 | 
				
			||||||
  -clang-diagnostic-missing-field-initializers,
 | 
					 | 
				
			||||||
  -clang-diagnostic-shadow-field,
 | 
					  -clang-diagnostic-shadow-field,
 | 
				
			||||||
  -clang-diagnostic-unused-const-variable,
 | 
					  -clang-diagnostic-unused-const-variable,
 | 
				
			||||||
  -clang-diagnostic-unused-parameter,
 | 
					  -clang-diagnostic-unused-parameter,
 | 
				
			||||||
  -clang-diagnostic-vla-cxx-extension,
 | 
					 | 
				
			||||||
  -concurrency-*,
 | 
					  -concurrency-*,
 | 
				
			||||||
  -cppcoreguidelines-avoid-c-arrays,
 | 
					  -cppcoreguidelines-avoid-c-arrays,
 | 
				
			||||||
  -cppcoreguidelines-avoid-const-or-ref-data-members,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-avoid-do-while,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-avoid-magic-numbers,
 | 
					  -cppcoreguidelines-avoid-magic-numbers,
 | 
				
			||||||
  -cppcoreguidelines-init-variables,
 | 
					  -cppcoreguidelines-init-variables,
 | 
				
			||||||
  -cppcoreguidelines-macro-to-enum,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-macro-usage,
 | 
					  -cppcoreguidelines-macro-usage,
 | 
				
			||||||
  -cppcoreguidelines-missing-std-forward,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-narrowing-conversions,
 | 
					  -cppcoreguidelines-narrowing-conversions,
 | 
				
			||||||
  -cppcoreguidelines-non-private-member-variables-in-classes,
 | 
					  -cppcoreguidelines-non-private-member-variables-in-classes,
 | 
				
			||||||
  -cppcoreguidelines-owning-memory,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-prefer-member-initializer,
 | 
					  -cppcoreguidelines-prefer-member-initializer,
 | 
				
			||||||
  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
 | 
					  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
 | 
				
			||||||
  -cppcoreguidelines-pro-bounds-constant-array-index,
 | 
					  -cppcoreguidelines-pro-bounds-constant-array-index,
 | 
				
			||||||
@@ -51,9 +40,7 @@ Checks: >-
 | 
				
			|||||||
  -cppcoreguidelines-pro-type-static-cast-downcast,
 | 
					  -cppcoreguidelines-pro-type-static-cast-downcast,
 | 
				
			||||||
  -cppcoreguidelines-pro-type-union-access,
 | 
					  -cppcoreguidelines-pro-type-union-access,
 | 
				
			||||||
  -cppcoreguidelines-pro-type-vararg,
 | 
					  -cppcoreguidelines-pro-type-vararg,
 | 
				
			||||||
  -cppcoreguidelines-rvalue-reference-param-not-moved,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-special-member-functions,
 | 
					  -cppcoreguidelines-special-member-functions,
 | 
				
			||||||
  -cppcoreguidelines-use-default-member-init,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-virtual-class-destructor,
 | 
					  -cppcoreguidelines-virtual-class-destructor,
 | 
				
			||||||
  -fuchsia-multiple-inheritance,
 | 
					  -fuchsia-multiple-inheritance,
 | 
				
			||||||
  -fuchsia-overloaded-operator,
 | 
					  -fuchsia-overloaded-operator,
 | 
				
			||||||
@@ -73,32 +60,20 @@ Checks: >-
 | 
				
			|||||||
  -llvm-include-order,
 | 
					  -llvm-include-order,
 | 
				
			||||||
  -llvm-qualified-auto,
 | 
					  -llvm-qualified-auto,
 | 
				
			||||||
  -llvmlibc-*,
 | 
					  -llvmlibc-*,
 | 
				
			||||||
  -misc-const-correctness,
 | 
					 | 
				
			||||||
  -misc-include-cleaner,
 | 
					 | 
				
			||||||
  -misc-no-recursion,
 | 
					 | 
				
			||||||
  -misc-non-private-member-variables-in-classes,
 | 
					  -misc-non-private-member-variables-in-classes,
 | 
				
			||||||
 | 
					  -misc-no-recursion,
 | 
				
			||||||
  -misc-unused-parameters,
 | 
					  -misc-unused-parameters,
 | 
				
			||||||
  -misc-use-anonymous-namespace,
 | 
					 | 
				
			||||||
  -modernize-avoid-bind,
 | 
					 | 
				
			||||||
  -modernize-avoid-c-arrays,
 | 
					  -modernize-avoid-c-arrays,
 | 
				
			||||||
 | 
					  -modernize-avoid-bind,
 | 
				
			||||||
  -modernize-concat-nested-namespaces,
 | 
					  -modernize-concat-nested-namespaces,
 | 
				
			||||||
  -modernize-macro-to-enum,
 | 
					 | 
				
			||||||
  -modernize-return-braced-init-list,
 | 
					  -modernize-return-braced-init-list,
 | 
				
			||||||
  -modernize-type-traits,
 | 
					 | 
				
			||||||
  -modernize-use-auto,
 | 
					  -modernize-use-auto,
 | 
				
			||||||
  -modernize-use-constraints,
 | 
					 | 
				
			||||||
  -modernize-use-default-member-init,
 | 
					  -modernize-use-default-member-init,
 | 
				
			||||||
  -modernize-use-equals-default,
 | 
					  -modernize-use-equals-default,
 | 
				
			||||||
  -modernize-use-nodiscard,
 | 
					 | 
				
			||||||
  -modernize-use-nullptr,
 | 
					 | 
				
			||||||
  -modernize-use-nodiscard,
 | 
					 | 
				
			||||||
  -modernize-use-nullptr,
 | 
					 | 
				
			||||||
  -modernize-use-trailing-return-type,
 | 
					  -modernize-use-trailing-return-type,
 | 
				
			||||||
 | 
					  -modernize-use-nodiscard,
 | 
				
			||||||
  -mpi-*,
 | 
					  -mpi-*,
 | 
				
			||||||
  -objc-*,
 | 
					  -objc-*,
 | 
				
			||||||
  -performance-enum-size,
 | 
					 | 
				
			||||||
  -readability-avoid-nested-conditional-operator,
 | 
					 | 
				
			||||||
  -readability-container-contains,
 | 
					 | 
				
			||||||
  -readability-container-data-pointer,
 | 
					  -readability-container-data-pointer,
 | 
				
			||||||
  -readability-convert-member-functions-to-static,
 | 
					  -readability-convert-member-functions-to-static,
 | 
				
			||||||
  -readability-else-after-return,
 | 
					  -readability-else-after-return,
 | 
				
			||||||
@@ -107,14 +82,11 @@ Checks: >-
 | 
				
			|||||||
  -readability-isolate-declaration,
 | 
					  -readability-isolate-declaration,
 | 
				
			||||||
  -readability-magic-numbers,
 | 
					  -readability-magic-numbers,
 | 
				
			||||||
  -readability-make-member-function-const,
 | 
					  -readability-make-member-function-const,
 | 
				
			||||||
  -readability-named-parameter,
 | 
					 | 
				
			||||||
  -readability-redundant-casting,
 | 
					 | 
				
			||||||
  -readability-redundant-inline-specifier,
 | 
					 | 
				
			||||||
  -readability-redundant-member-init,
 | 
					 | 
				
			||||||
  -readability-redundant-string-init,
 | 
					  -readability-redundant-string-init,
 | 
				
			||||||
  -readability-uppercase-literal-suffix,
 | 
					  -readability-uppercase-literal-suffix,
 | 
				
			||||||
  -readability-use-anyofallof,
 | 
					  -readability-use-anyofallof,
 | 
				
			||||||
WarningsAsErrors: '*'
 | 
					WarningsAsErrors: '*'
 | 
				
			||||||
 | 
					AnalyzeTemporaryDtors: false
 | 
				
			||||||
FormatStyle:     google
 | 
					FormatStyle:     google
 | 
				
			||||||
CheckOptions:
 | 
					CheckOptions:
 | 
				
			||||||
  - key:             google-readability-function-size.StatementThreshold
 | 
					  - key:             google-readability-function-size.StatementThreshold
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@
 | 
				
			|||||||
        "ms-python.python",
 | 
					        "ms-python.python",
 | 
				
			||||||
        "ms-python.pylint",
 | 
					        "ms-python.pylint",
 | 
				
			||||||
        "ms-python.flake8",
 | 
					        "ms-python.flake8",
 | 
				
			||||||
        "charliermarsh.ruff",
 | 
					        "ms-python.black-formatter",
 | 
				
			||||||
        "visualstudioexptteam.vscodeintellicode",
 | 
					        "visualstudioexptteam.vscodeintellicode",
 | 
				
			||||||
        // yaml
 | 
					        // yaml
 | 
				
			||||||
        "redhat.vscode-yaml",
 | 
					        "redhat.vscode-yaml",
 | 
				
			||||||
@@ -49,11 +49,14 @@
 | 
				
			|||||||
        "flake8.args": [
 | 
					        "flake8.args": [
 | 
				
			||||||
          "--config=${workspaceFolder}/.flake8"
 | 
					          "--config=${workspaceFolder}/.flake8"
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "ruff.configuration": "${workspaceFolder}/pyproject.toml",
 | 
					        "black-formatter.args": [
 | 
				
			||||||
 | 
					          "--config",
 | 
				
			||||||
 | 
					          "${workspaceFolder}/pyproject.toml"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
        "[python]": {
 | 
					        "[python]": {
 | 
				
			||||||
          // VS will say "Value is not accepted" before building the devcontainer, but the warning
 | 
					          // VS will say "Value is not accepted" before building the devcontainer, but the warning
 | 
				
			||||||
          // should go away after build is completed.
 | 
					          // should go away after build is completed.
 | 
				
			||||||
          "editor.defaultFormatter": "charliermarsh.ruff"
 | 
					          "editor.defaultFormatter": "ms-python.black-formatter"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "editor.formatOnPaste": false,
 | 
					        "editor.formatOnPaste": false,
 | 
				
			||||||
        "editor.formatOnSave": true,
 | 
					        "editor.formatOnSave": true,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -75,9 +75,6 @@ target/
 | 
				
			|||||||
# pyenv
 | 
					# pyenv
 | 
				
			||||||
.python-version
 | 
					.python-version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# asdf
 | 
					 | 
				
			||||||
.tool-versions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# celery beat schedule file
 | 
					# celery beat schedule file
 | 
				
			||||||
celerybeat-schedule
 | 
					celerybeat-schedule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -114,5 +111,4 @@ config/
 | 
				
			|||||||
examples/
 | 
					examples/
 | 
				
			||||||
Dockerfile
 | 
					Dockerfile
 | 
				
			||||||
.git/
 | 
					.git/
 | 
				
			||||||
tests/
 | 
					tests/build/
 | 
				
			||||||
.*
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					# These are supported funding model platforms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					custom: https://www.nabucasa.com
 | 
				
			||||||
							
								
								
									
										15
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -7,16 +7,11 @@
 | 
				
			|||||||
- [ ] Bugfix (non-breaking change which fixes an issue)
 | 
					- [ ] Bugfix (non-breaking change which fixes an issue)
 | 
				
			||||||
- [ ] New feature (non-breaking change which adds functionality)
 | 
					- [ ] New feature (non-breaking change which adds functionality)
 | 
				
			||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
 | 
					- [ ] 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
 | 
					- [ ] Other
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Related issue or feature (if applicable):**
 | 
					**Related issue or feature (if applicable):** fixes <link to issue>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- 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>
 | 
				
			||||||
 | 
					 | 
				
			||||||
**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
 | 
					## Test Environment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,6 +23,12 @@
 | 
				
			|||||||
- [ ] RTL87xx
 | 
					- [ ] RTL87xx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Example entry for `config.yaml`:
 | 
					## 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
 | 
					```yaml
 | 
				
			||||||
# Example config.yaml
 | 
					# Example config.yaml
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,15 @@
 | 
				
			|||||||
name: Build Image
 | 
					name: Build Image
 | 
				
			||||||
inputs:
 | 
					inputs:
 | 
				
			||||||
 | 
					  platform:
 | 
				
			||||||
 | 
					    description: "Platform to build for"
 | 
				
			||||||
 | 
					    required: true
 | 
				
			||||||
 | 
					    example: "linux/amd64"
 | 
				
			||||||
  target:
 | 
					  target:
 | 
				
			||||||
    description: "Target to build"
 | 
					    description: "Target to build"
 | 
				
			||||||
    required: true
 | 
					    required: true
 | 
				
			||||||
    example: "docker"
 | 
					    example: "docker"
 | 
				
			||||||
  build_type:
 | 
					  baseimg:
 | 
				
			||||||
    description: "Build type"
 | 
					    description: "Base image type"
 | 
				
			||||||
    required: true
 | 
					    required: true
 | 
				
			||||||
    example: "docker"
 | 
					    example: "docker"
 | 
				
			||||||
  suffix:
 | 
					  suffix:
 | 
				
			||||||
@@ -15,11 +19,6 @@ inputs:
 | 
				
			|||||||
    description: "Version to build"
 | 
					    description: "Version to build"
 | 
				
			||||||
    required: true
 | 
					    required: true
 | 
				
			||||||
    example: "2023.12.0"
 | 
					    example: "2023.12.0"
 | 
				
			||||||
  base_os:
 | 
					 | 
				
			||||||
    description: "Base OS to use"
 | 
					 | 
				
			||||||
    required: false
 | 
					 | 
				
			||||||
    default: "debian"
 | 
					 | 
				
			||||||
    example: "debian"
 | 
					 | 
				
			||||||
runs:
 | 
					runs:
 | 
				
			||||||
  using: "composite"
 | 
					  using: "composite"
 | 
				
			||||||
  steps:
 | 
					  steps:
 | 
				
			||||||
@@ -47,52 +46,52 @@ runs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    - name: Build and push to ghcr by digest
 | 
					    - name: Build and push to ghcr by digest
 | 
				
			||||||
      id: build-ghcr
 | 
					      id: build-ghcr
 | 
				
			||||||
      uses: docker/build-push-action@v6.17.0
 | 
					      uses: docker/build-push-action@v6.7.0
 | 
				
			||||||
      env:
 | 
					      env:
 | 
				
			||||||
        DOCKER_BUILD_SUMMARY: false
 | 
					        DOCKER_BUILD_SUMMARY: false
 | 
				
			||||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
					        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        context: .
 | 
					        context: .
 | 
				
			||||||
        file: ./docker/Dockerfile
 | 
					        file: ./docker/Dockerfile
 | 
				
			||||||
 | 
					        platforms: ${{ inputs.platform }}
 | 
				
			||||||
        target: ${{ inputs.target }}
 | 
					        target: ${{ inputs.target }}
 | 
				
			||||||
        cache-from: type=gha
 | 
					        cache-from: type=gha
 | 
				
			||||||
        cache-to: ${{ steps.cache-to.outputs.value }}
 | 
					        cache-to: ${{ steps.cache-to.outputs.value }}
 | 
				
			||||||
        build-args: |
 | 
					        build-args: |
 | 
				
			||||||
          BUILD_TYPE=${{ inputs.build_type }}
 | 
					          BASEIMGTYPE=${{ inputs.baseimg }}
 | 
				
			||||||
          BUILD_VERSION=${{ inputs.version }}
 | 
					          BUILD_VERSION=${{ inputs.version }}
 | 
				
			||||||
          BUILD_OS=${{ inputs.base_os }}
 | 
					 | 
				
			||||||
        outputs: |
 | 
					        outputs: |
 | 
				
			||||||
          type=image,name=ghcr.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
 | 
					          type=image,name=ghcr.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Export ghcr digests
 | 
					    - name: Export ghcr digests
 | 
				
			||||||
      shell: bash
 | 
					      shell: bash
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        mkdir -p /tmp/digests/${{ inputs.build_type }}/ghcr
 | 
					        mkdir -p /tmp/digests/${{ inputs.target }}/ghcr
 | 
				
			||||||
        digest="${{ steps.build-ghcr.outputs.digest }}"
 | 
					        digest="${{ steps.build-ghcr.outputs.digest }}"
 | 
				
			||||||
        touch "/tmp/digests/${{ inputs.build_type }}/ghcr/${digest#sha256:}"
 | 
					        touch "/tmp/digests/${{ inputs.target }}/ghcr/${digest#sha256:}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Build and push to dockerhub by digest
 | 
					    - name: Build and push to dockerhub by digest
 | 
				
			||||||
      id: build-dockerhub
 | 
					      id: build-dockerhub
 | 
				
			||||||
      uses: docker/build-push-action@v6.17.0
 | 
					      uses: docker/build-push-action@v6.7.0
 | 
				
			||||||
      env:
 | 
					      env:
 | 
				
			||||||
        DOCKER_BUILD_SUMMARY: false
 | 
					        DOCKER_BUILD_SUMMARY: false
 | 
				
			||||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
					        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        context: .
 | 
					        context: .
 | 
				
			||||||
        file: ./docker/Dockerfile
 | 
					        file: ./docker/Dockerfile
 | 
				
			||||||
 | 
					        platforms: ${{ inputs.platform }}
 | 
				
			||||||
        target: ${{ inputs.target }}
 | 
					        target: ${{ inputs.target }}
 | 
				
			||||||
        cache-from: type=gha
 | 
					        cache-from: type=gha
 | 
				
			||||||
        cache-to: ${{ steps.cache-to.outputs.value }}
 | 
					        cache-to: ${{ steps.cache-to.outputs.value }}
 | 
				
			||||||
        build-args: |
 | 
					        build-args: |
 | 
				
			||||||
          BUILD_TYPE=${{ inputs.build_type }}
 | 
					          BASEIMGTYPE=${{ inputs.baseimg }}
 | 
				
			||||||
          BUILD_VERSION=${{ inputs.version }}
 | 
					          BUILD_VERSION=${{ inputs.version }}
 | 
				
			||||||
          BUILD_OS=${{ inputs.base_os }}
 | 
					 | 
				
			||||||
        outputs: |
 | 
					        outputs: |
 | 
				
			||||||
          type=image,name=docker.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
 | 
					          type=image,name=docker.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Export dockerhub digests
 | 
					    - name: Export dockerhub digests
 | 
				
			||||||
      shell: bash
 | 
					      shell: bash
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        mkdir -p /tmp/digests/${{ inputs.build_type }}/dockerhub
 | 
					        mkdir -p /tmp/digests/${{ inputs.target }}/dockerhub
 | 
				
			||||||
        digest="${{ steps.build-dockerhub.outputs.digest }}"
 | 
					        digest="${{ steps.build-dockerhub.outputs.digest }}"
 | 
				
			||||||
        touch "/tmp/digests/${{ inputs.build_type }}/dockerhub/${digest#sha256:}"
 | 
					        touch "/tmp/digests/${{ inputs.target }}/dockerhub/${digest#sha256:}"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							@@ -17,12 +17,12 @@ runs:
 | 
				
			|||||||
  steps:
 | 
					  steps:
 | 
				
			||||||
    - name: Set up Python ${{ inputs.python-version }}
 | 
					    - name: Set up Python ${{ inputs.python-version }}
 | 
				
			||||||
      id: python
 | 
					      id: python
 | 
				
			||||||
      uses: actions/setup-python@v5.6.0
 | 
					      uses: actions/setup-python@v5.2.0
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        python-version: ${{ inputs.python-version }}
 | 
					        python-version: ${{ inputs.python-version }}
 | 
				
			||||||
    - name: Restore Python virtual environment
 | 
					    - name: Restore Python virtual environment
 | 
				
			||||||
      id: cache-venv
 | 
					      id: cache-venv
 | 
				
			||||||
      uses: actions/cache/restore@v4.2.3
 | 
					      uses: actions/cache/restore@v4.0.2
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        path: venv
 | 
					        path: venv
 | 
				
			||||||
        # yamllint disable-line rule:line-length
 | 
					        # yamllint disable-line rule:line-length
 | 
				
			||||||
@@ -34,7 +34,7 @@ runs:
 | 
				
			|||||||
        python -m venv venv
 | 
					        python -m venv venv
 | 
				
			||||||
        source venv/bin/activate
 | 
					        source venv/bin/activate
 | 
				
			||||||
        python --version
 | 
					        python --version
 | 
				
			||||||
        pip install -r requirements.txt -r requirements_test.txt
 | 
					        pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
				
			||||||
        pip install -e .
 | 
					        pip install -e .
 | 
				
			||||||
    - name: Create Python virtual environment
 | 
					    - name: Create Python virtual environment
 | 
				
			||||||
      if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
 | 
					      if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
 | 
				
			||||||
@@ -43,5 +43,5 @@ runs:
 | 
				
			|||||||
        python -m venv venv
 | 
					        python -m venv venv
 | 
				
			||||||
        ./venv/Scripts/activate
 | 
					        ./venv/Scripts/activate
 | 
				
			||||||
        python --version
 | 
					        python --version
 | 
				
			||||||
        pip install -r requirements.txt -r requirements_test.txt
 | 
					        pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
				
			||||||
        pip install -e .
 | 
					        pip install -e .
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							@@ -17,6 +17,7 @@ updates:
 | 
				
			|||||||
      docker-actions:
 | 
					      docker-actions:
 | 
				
			||||||
        applies-to: version-updates
 | 
					        applies-to: version-updates
 | 
				
			||||||
        patterns:
 | 
					        patterns:
 | 
				
			||||||
 | 
					          - "docker/setup-qemu-action"
 | 
				
			||||||
          - "docker/login-action"
 | 
					          - "docker/login-action"
 | 
				
			||||||
          - "docker/setup-buildx-action"
 | 
					          - "docker/setup-buildx-action"
 | 
				
			||||||
  - package-ecosystem: github-actions
 | 
					  - package-ecosystem: github-actions
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							@@ -23,7 +23,7 @@ jobs:
 | 
				
			|||||||
      - name: Checkout
 | 
					      - name: Checkout
 | 
				
			||||||
        uses: actions/checkout@v4.1.7
 | 
					        uses: actions/checkout@v4.1.7
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@v5.2.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.11"
 | 
					          python-version: "3.11"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -57,17 +57,6 @@ jobs:
 | 
				
			|||||||
              event: 'REQUEST_CHANGES',
 | 
					              event: 'REQUEST_CHANGES',
 | 
				
			||||||
              body: 'You have altered the generated proto files but they do not match what is expected.\nPlease run "script/api_protobuf/api_protobuf.py" and commit the changes.'
 | 
					              body: 'You have altered the generated proto files but they do not match what is expected.\nPlease run "script/api_protobuf/api_protobuf.py" and commit the changes.'
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
      - if: failure()
 | 
					 | 
				
			||||||
        name: Show changes
 | 
					 | 
				
			||||||
        run: git diff
 | 
					 | 
				
			||||||
      - if: failure()
 | 
					 | 
				
			||||||
        name: Archive artifacts
 | 
					 | 
				
			||||||
        uses: actions/upload-artifact@v4.6.2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          name: generated-proto-files
 | 
					 | 
				
			||||||
          path: |
 | 
					 | 
				
			||||||
            esphome/components/api/api_pb2.*
 | 
					 | 
				
			||||||
            esphome/components/api/api_pb2_service.*
 | 
					 | 
				
			||||||
      - if: success()
 | 
					      - if: success()
 | 
				
			||||||
        name: Dismiss review
 | 
					        name: Dismiss review
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@v7.0.1
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -33,23 +33,22 @@ concurrency:
 | 
				
			|||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  check-docker:
 | 
					  check-docker:
 | 
				
			||||||
    name: Build docker containers
 | 
					    name: Build docker containers
 | 
				
			||||||
    runs-on: ${{ matrix.os }}
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        os: ["ubuntu-24.04", "ubuntu-24.04-arm"]
 | 
					        arch: [amd64, armv7, aarch64]
 | 
				
			||||||
        build_type:
 | 
					        build_type: ["ha-addon", "docker", "lint"]
 | 
				
			||||||
          - "ha-addon"
 | 
					 | 
				
			||||||
          - "docker"
 | 
					 | 
				
			||||||
          # - "lint"
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v4.1.7
 | 
					      - uses: actions/checkout@v4.1.7
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@v5.2.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.9"
 | 
					          python-version: "3.9"
 | 
				
			||||||
      - name: Set up Docker Buildx
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
        uses: docker/setup-buildx-action@v3.10.0
 | 
					        uses: docker/setup-buildx-action@v3.6.1
 | 
				
			||||||
 | 
					      - name: Set up QEMU
 | 
				
			||||||
 | 
					        uses: docker/setup-qemu-action@v3.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set TAG
 | 
					      - name: Set TAG
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
@@ -59,6 +58,6 @@ jobs:
 | 
				
			|||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          docker/build.py \
 | 
					          docker/build.py \
 | 
				
			||||||
            --tag "${TAG}" \
 | 
					            --tag "${TAG}" \
 | 
				
			||||||
            --arch "${{ matrix.os == 'ubuntu-24.04-arm' && 'aarch64' || 'amd64' }}" \
 | 
					            --arch "${{ matrix.arch }}" \
 | 
				
			||||||
            --build-type "${{ matrix.build_type }}" \
 | 
					            --build-type "${{ matrix.build_type }}" \
 | 
				
			||||||
            build
 | 
					            build
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										74
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										74
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -13,7 +13,6 @@ on:
 | 
				
			|||||||
      - ".github/workflows/ci.yml"
 | 
					      - ".github/workflows/ci.yml"
 | 
				
			||||||
      - "!.yamllint"
 | 
					      - "!.yamllint"
 | 
				
			||||||
      - "!.github/dependabot.yml"
 | 
					      - "!.github/dependabot.yml"
 | 
				
			||||||
      - "!docker/**"
 | 
					 | 
				
			||||||
  merge_group:
 | 
					  merge_group:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
permissions:
 | 
					permissions:
 | 
				
			||||||
@@ -31,7 +30,7 @@ concurrency:
 | 
				
			|||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  common:
 | 
					  common:
 | 
				
			||||||
    name: Create common environment
 | 
					    name: Create common environment
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    outputs:
 | 
					    outputs:
 | 
				
			||||||
      cache-key: ${{ steps.cache-key.outputs.key }}
 | 
					      cache-key: ${{ steps.cache-key.outputs.key }}
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
@@ -39,15 +38,15 @@ jobs:
 | 
				
			|||||||
        uses: actions/checkout@v4.1.7
 | 
					        uses: actions/checkout@v4.1.7
 | 
				
			||||||
      - name: Generate cache-key
 | 
					      - name: Generate cache-key
 | 
				
			||||||
        id: cache-key
 | 
					        id: cache-key
 | 
				
			||||||
        run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
 | 
					        run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
 | 
				
			||||||
      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
 | 
					      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
        id: python
 | 
					        id: python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@v5.2.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
      - name: Restore Python virtual environment
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
        id: cache-venv
 | 
					        id: cache-venv
 | 
				
			||||||
        uses: actions/cache@v4.2.3
 | 
					        uses: actions/cache@v4.0.2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: venv
 | 
					          path: venv
 | 
				
			||||||
          # yamllint disable-line rule:line-length
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
@@ -58,12 +57,12 @@ jobs:
 | 
				
			|||||||
          python -m venv venv
 | 
					          python -m venv venv
 | 
				
			||||||
          . venv/bin/activate
 | 
					          . venv/bin/activate
 | 
				
			||||||
          python --version
 | 
					          python --version
 | 
				
			||||||
          pip install -r requirements.txt -r requirements_test.txt
 | 
					          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
				
			||||||
          pip install -e .
 | 
					          pip install -e .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ruff:
 | 
					  black:
 | 
				
			||||||
    name: Check ruff
 | 
					    name: Check black
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
@@ -74,17 +73,17 @@ jobs:
 | 
				
			|||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
					          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
				
			||||||
      - name: Run Ruff
 | 
					      - name: Run black
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          . venv/bin/activate
 | 
					          . venv/bin/activate
 | 
				
			||||||
          ruff format esphome tests
 | 
					          black --verbose esphome tests
 | 
				
			||||||
      - name: Suggested changes
 | 
					      - name: Suggested changes
 | 
				
			||||||
        run: script/ci-suggest-changes
 | 
					        run: script/ci-suggest-changes
 | 
				
			||||||
        if: always()
 | 
					        if: always()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  flake8:
 | 
					  flake8:
 | 
				
			||||||
    name: Check flake8
 | 
					    name: Check flake8
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
@@ -105,7 +104,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  pylint:
 | 
					  pylint:
 | 
				
			||||||
    name: Check pylint
 | 
					    name: Check pylint
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
@@ -126,7 +125,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  pyupgrade:
 | 
					  pyupgrade:
 | 
				
			||||||
    name: Check pyupgrade
 | 
					    name: Check pyupgrade
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
@@ -147,7 +146,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  ci-custom:
 | 
					  ci-custom:
 | 
				
			||||||
    name: Run script/ci-custom
 | 
					    name: Run script/ci-custom
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
@@ -165,7 +164,6 @@ jobs:
 | 
				
			|||||||
          . venv/bin/activate
 | 
					          . venv/bin/activate
 | 
				
			||||||
          script/ci-custom.py
 | 
					          script/ci-custom.py
 | 
				
			||||||
          script/build_codeowners.py --check
 | 
					          script/build_codeowners.py --check
 | 
				
			||||||
          script/build_language_schema.py --check
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pytest:
 | 
					  pytest:
 | 
				
			||||||
    name: Run pytest
 | 
					    name: Run pytest
 | 
				
			||||||
@@ -221,13 +219,13 @@ jobs:
 | 
				
			|||||||
          . venv/bin/activate
 | 
					          . venv/bin/activate
 | 
				
			||||||
          pytest -vv --cov-report=xml --tb=native tests
 | 
					          pytest -vv --cov-report=xml --tb=native tests
 | 
				
			||||||
      - name: Upload coverage to Codecov
 | 
					      - name: Upload coverage to Codecov
 | 
				
			||||||
        uses: codecov/codecov-action@v5.4.2
 | 
					        uses: codecov/codecov-action@v4
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          token: ${{ secrets.CODECOV_TOKEN }}
 | 
					          token: ${{ secrets.CODECOV_TOKEN }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clang-format:
 | 
					  clang-format:
 | 
				
			||||||
    name: Check clang-format
 | 
					    name: Check clang-format
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
@@ -253,10 +251,10 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  clang-tidy:
 | 
					  clang-tidy:
 | 
				
			||||||
    name: ${{ matrix.name }}
 | 
					    name: ${{ matrix.name }}
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
      - ruff
 | 
					      - black
 | 
				
			||||||
      - ci-custom
 | 
					      - ci-custom
 | 
				
			||||||
      - clang-format
 | 
					      - clang-format
 | 
				
			||||||
      - flake8
 | 
					      - flake8
 | 
				
			||||||
@@ -292,11 +290,6 @@ jobs:
 | 
				
			|||||||
            name: Run script/clang-tidy for ESP32 IDF
 | 
					            name: Run script/clang-tidy for ESP32 IDF
 | 
				
			||||||
            options: --environment esp32-idf-tidy --grep USE_ESP_IDF
 | 
					            options: --environment esp32-idf-tidy --grep USE_ESP_IDF
 | 
				
			||||||
            pio_cache_key: tidyesp32-idf
 | 
					            pio_cache_key: tidyesp32-idf
 | 
				
			||||||
          - id: clang-tidy
 | 
					 | 
				
			||||||
            name: Run script/clang-tidy for ZEPHYR
 | 
					 | 
				
			||||||
            options: --environment nrf52-tidy --grep USE_ZEPHYR
 | 
					 | 
				
			||||||
            pio_cache_key: tidy-zephyr
 | 
					 | 
				
			||||||
            ignore_errors: true
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
@@ -309,18 +302,21 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      - name: Cache platformio
 | 
					      - name: Cache platformio
 | 
				
			||||||
        if: github.ref == 'refs/heads/dev'
 | 
					        if: github.ref == 'refs/heads/dev'
 | 
				
			||||||
        uses: actions/cache@v4.2.3
 | 
					        uses: actions/cache@v4.0.2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: ~/.platformio
 | 
					          path: ~/.platformio
 | 
				
			||||||
          key: platformio-${{ matrix.pio_cache_key }}
 | 
					          key: platformio-${{ matrix.pio_cache_key }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Cache platformio
 | 
					      - name: Cache platformio
 | 
				
			||||||
        if: github.ref != 'refs/heads/dev'
 | 
					        if: github.ref != 'refs/heads/dev'
 | 
				
			||||||
        uses: actions/cache/restore@v4.2.3
 | 
					        uses: actions/cache/restore@v4.0.2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: ~/.platformio
 | 
					          path: ~/.platformio
 | 
				
			||||||
          key: platformio-${{ matrix.pio_cache_key }}
 | 
					          key: platformio-${{ matrix.pio_cache_key }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Install clang-tidy
 | 
				
			||||||
 | 
					        run: sudo apt-get install clang-tidy-14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Register problem matchers
 | 
					      - name: Register problem matchers
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
					          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
				
			||||||
@@ -336,18 +332,18 @@ jobs:
 | 
				
			|||||||
      - name: Run clang-tidy
 | 
					      - name: Run clang-tidy
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          . venv/bin/activate
 | 
					          . venv/bin/activate
 | 
				
			||||||
          script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
 | 
					          script/clang-tidy --all-headers --fix ${{ matrix.options }}
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
					          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
				
			||||||
          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
					          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Suggested changes
 | 
					      - name: Suggested changes
 | 
				
			||||||
        run: script/ci-suggest-changes ${{ matrix.ignore_errors && '|| true' || '' }}
 | 
					        run: script/ci-suggest-changes
 | 
				
			||||||
        # yamllint disable-line rule:line-length
 | 
					        # yamllint disable-line rule:line-length
 | 
				
			||||||
        if: always()
 | 
					        if: always()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  list-components:
 | 
					  list-components:
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
    if: github.event_name == 'pull_request'
 | 
					    if: github.event_name == 'pull_request'
 | 
				
			||||||
@@ -389,7 +385,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  test-build-components:
 | 
					  test-build-components:
 | 
				
			||||||
    name: Component test ${{ matrix.file }}
 | 
					    name: Component test ${{ matrix.file }}
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
      - list-components
 | 
					      - list-components
 | 
				
			||||||
@@ -401,9 +397,7 @@ jobs:
 | 
				
			|||||||
        file: ${{ fromJson(needs.list-components.outputs.components) }}
 | 
					        file: ${{ fromJson(needs.list-components.outputs.components) }}
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Install dependencies
 | 
				
			||||||
        run: |
 | 
					        run: sudo apt-get install libsdl2-dev
 | 
				
			||||||
          sudo apt-get update
 | 
					 | 
				
			||||||
          sudo apt-get install libsdl2-dev
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v4.1.7
 | 
					        uses: actions/checkout@v4.1.7
 | 
				
			||||||
@@ -423,7 +417,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  test-build-components-splitter:
 | 
					  test-build-components-splitter:
 | 
				
			||||||
    name: Split components for testing into 20 groups maximum
 | 
					    name: Split components for testing into 20 groups maximum
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
      - list-components
 | 
					      - list-components
 | 
				
			||||||
@@ -441,7 +435,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  test-build-components-split:
 | 
					  test-build-components-split:
 | 
				
			||||||
    name: Test split components
 | 
					    name: Test split components
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
      - list-components
 | 
					      - list-components
 | 
				
			||||||
@@ -457,9 +451,7 @@ jobs:
 | 
				
			|||||||
        run: echo ${{ matrix.components }}
 | 
					        run: echo ${{ matrix.components }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Install dependencies
 | 
				
			||||||
        run: |
 | 
					        run: sudo apt-get install libsdl2-dev
 | 
				
			||||||
          sudo apt-get update
 | 
					 | 
				
			||||||
          sudo apt-get install libsdl2-dev
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v4.1.7
 | 
					        uses: actions/checkout@v4.1.7
 | 
				
			||||||
@@ -485,10 +477,10 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  ci-status:
 | 
					  ci-status:
 | 
				
			||||||
    name: CI Status
 | 
					    name: CI Status
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
      - ruff
 | 
					      - black
 | 
				
			||||||
      - ci-custom
 | 
					      - ci-custom
 | 
				
			||||||
      - clang-format
 | 
					      - clang-format
 | 
				
			||||||
      - flake8
 | 
					      - flake8
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										91
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										91
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,91 +0,0 @@
 | 
				
			|||||||
# 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}}"
 | 
					 | 
				
			||||||
							
								
								
									
										4
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "problemMatcher": [
 | 
					  "problemMatcher": [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "owner": "ruff",
 | 
					      "owner": "black",
 | 
				
			||||||
      "severity": "error",
 | 
					      "severity": "error",
 | 
				
			||||||
      "pattern": [
 | 
					      "pattern": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "regexp": "^(.*): (Please format this file with the ruff formatter)",
 | 
					          "regexp": "^(.*): (Please format this file with the black formatter)",
 | 
				
			||||||
          "file": 1,
 | 
					          "file": 1,
 | 
				
			||||||
          "message": 2
 | 
					          "message": 2
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										123
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										123
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,6 @@ jobs:
 | 
				
			|||||||
    outputs:
 | 
					    outputs:
 | 
				
			||||||
      tag: ${{ steps.tag.outputs.tag }}
 | 
					      tag: ${{ steps.tag.outputs.tag }}
 | 
				
			||||||
      branch_build: ${{ steps.tag.outputs.branch_build }}
 | 
					      branch_build: ${{ steps.tag.outputs.branch_build }}
 | 
				
			||||||
      deploy_env: ${{ steps.tag.outputs.deploy_env }}
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v4.1.7
 | 
					      - uses: actions/checkout@v4.1.7
 | 
				
			||||||
      - name: Get tag
 | 
					      - name: Get tag
 | 
				
			||||||
@@ -28,11 +27,6 @@ jobs:
 | 
				
			|||||||
          if [[ "${{ github.event_name }}" = "release" ]]; then
 | 
					          if [[ "${{ github.event_name }}" = "release" ]]; then
 | 
				
			||||||
            TAG="${{ github.event.release.tag_name}}"
 | 
					            TAG="${{ github.event.release.tag_name}}"
 | 
				
			||||||
            BRANCH_BUILD="false"
 | 
					            BRANCH_BUILD="false"
 | 
				
			||||||
            if [[ "${{ github.event.release.prerelease }}" = "true" ]]; then
 | 
					 | 
				
			||||||
              ENVIRONMENT="beta"
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
              ENVIRONMENT="production"
 | 
					 | 
				
			||||||
            fi
 | 
					 | 
				
			||||||
          else
 | 
					          else
 | 
				
			||||||
            TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
 | 
					            TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
 | 
				
			||||||
            today="$(date --utc '+%Y%m%d')"
 | 
					            today="$(date --utc '+%Y%m%d')"
 | 
				
			||||||
@@ -41,15 +35,12 @@ jobs:
 | 
				
			|||||||
            if [[ "$BRANCH" != "dev" ]]; then
 | 
					            if [[ "$BRANCH" != "dev" ]]; then
 | 
				
			||||||
              TAG="${TAG}-${BRANCH}"
 | 
					              TAG="${TAG}-${BRANCH}"
 | 
				
			||||||
              BRANCH_BUILD="true"
 | 
					              BRANCH_BUILD="true"
 | 
				
			||||||
              ENVIRONMENT=""
 | 
					 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
              BRANCH_BUILD="false"
 | 
					              BRANCH_BUILD="false"
 | 
				
			||||||
              ENVIRONMENT="dev"
 | 
					 | 
				
			||||||
            fi
 | 
					            fi
 | 
				
			||||||
          fi
 | 
					          fi
 | 
				
			||||||
          echo "tag=${TAG}" >> $GITHUB_OUTPUT
 | 
					          echo "tag=${TAG}" >> $GITHUB_OUTPUT
 | 
				
			||||||
          echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT
 | 
					          echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT
 | 
				
			||||||
          echo "deploy_env=${ENVIRONMENT}" >> $GITHUB_OUTPUT
 | 
					 | 
				
			||||||
        # yamllint enable rule:line-length
 | 
					        # yamllint enable rule:line-length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  deploy-pypi:
 | 
					  deploy-pypi:
 | 
				
			||||||
@@ -62,52 +53,55 @@ jobs:
 | 
				
			|||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v4.1.7
 | 
					      - uses: actions/checkout@v4.1.7
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@v5.2.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.x"
 | 
					          python-version: "3.x"
 | 
				
			||||||
 | 
					      - name: Set up python environment
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          ESPHOME_NO_VENV: 1
 | 
				
			||||||
 | 
					        run: script/setup
 | 
				
			||||||
      - name: Build
 | 
					      - name: Build
 | 
				
			||||||
        run: |-
 | 
					        run: |-
 | 
				
			||||||
          pip3 install build
 | 
					          pip3 install build
 | 
				
			||||||
          python3 -m build
 | 
					          python3 -m build
 | 
				
			||||||
      - name: Publish
 | 
					      - name: Publish
 | 
				
			||||||
        uses: pypa/gh-action-pypi-publish@v1.12.4
 | 
					        uses: pypa/gh-action-pypi-publish@v1.10.1
 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          skip-existing: true
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  deploy-docker:
 | 
					  deploy-docker:
 | 
				
			||||||
    name: Build ESPHome ${{ matrix.platform.arch }}
 | 
					    name: Build ESPHome ${{ matrix.platform }}
 | 
				
			||||||
    if: github.repository == 'esphome/esphome'
 | 
					    if: github.repository == 'esphome/esphome'
 | 
				
			||||||
    permissions:
 | 
					    permissions:
 | 
				
			||||||
      contents: read
 | 
					      contents: read
 | 
				
			||||||
      packages: write
 | 
					      packages: write
 | 
				
			||||||
    runs-on: ${{ matrix.platform.os }}
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs: [init]
 | 
					    needs: [init]
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        platform:
 | 
					        platform:
 | 
				
			||||||
          - arch: amd64
 | 
					          - linux/amd64
 | 
				
			||||||
            os: "ubuntu-24.04"
 | 
					          - linux/arm/v7
 | 
				
			||||||
          - arch: arm64
 | 
					          - linux/arm64
 | 
				
			||||||
            os: "ubuntu-24.04-arm"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v4.1.7
 | 
					      - uses: actions/checkout@v4.1.7
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@v5.2.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.9"
 | 
					          python-version: "3.9"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set up Docker Buildx
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
        uses: docker/setup-buildx-action@v3.10.0
 | 
					        uses: docker/setup-buildx-action@v3.6.1
 | 
				
			||||||
 | 
					      - name: Set up QEMU
 | 
				
			||||||
 | 
					        if: matrix.platform != 'linux/amd64'
 | 
				
			||||||
 | 
					        uses: docker/setup-qemu-action@v3.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Log in to docker hub
 | 
					      - name: Log in to docker hub
 | 
				
			||||||
        uses: docker/login-action@v3.4.0
 | 
					        uses: docker/login-action@v3.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          username: ${{ secrets.DOCKER_USER }}
 | 
					          username: ${{ secrets.DOCKER_USER }}
 | 
				
			||||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
					          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
				
			||||||
      - name: Log in to the GitHub container registry
 | 
					      - name: Log in to the GitHub container registry
 | 
				
			||||||
        uses: docker/login-action@v3.4.0
 | 
					        uses: docker/login-action@v3.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          registry: ghcr.io
 | 
					          registry: ghcr.io
 | 
				
			||||||
          username: ${{ github.actor }}
 | 
					          username: ${{ github.actor }}
 | 
				
			||||||
@@ -116,36 +110,45 @@ jobs:
 | 
				
			|||||||
      - name: Build docker
 | 
					      - name: Build docker
 | 
				
			||||||
        uses: ./.github/actions/build-image
 | 
					        uses: ./.github/actions/build-image
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          target: final
 | 
					          platform: ${{ matrix.platform }}
 | 
				
			||||||
          build_type: docker
 | 
					          target: docker
 | 
				
			||||||
 | 
					          baseimg: docker
 | 
				
			||||||
          suffix: ""
 | 
					          suffix: ""
 | 
				
			||||||
          version: ${{ needs.init.outputs.tag }}
 | 
					          version: ${{ needs.init.outputs.tag }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build ha-addon
 | 
					      - name: Build ha-addon
 | 
				
			||||||
        uses: ./.github/actions/build-image
 | 
					        uses: ./.github/actions/build-image
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          target: final
 | 
					          platform: ${{ matrix.platform }}
 | 
				
			||||||
          build_type: ha-addon
 | 
					          target: hassio
 | 
				
			||||||
 | 
					          baseimg: hassio
 | 
				
			||||||
          suffix: "hassio"
 | 
					          suffix: "hassio"
 | 
				
			||||||
          version: ${{ needs.init.outputs.tag }}
 | 
					          version: ${{ needs.init.outputs.tag }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      # - name: Build lint
 | 
					      - name: Build lint
 | 
				
			||||||
      #   uses: ./.github/actions/build-image
 | 
					        uses: ./.github/actions/build-image
 | 
				
			||||||
      #   with:
 | 
					        with:
 | 
				
			||||||
      #     target: lint
 | 
					          platform: ${{ matrix.platform }}
 | 
				
			||||||
      #     build_type: lint
 | 
					          target: lint
 | 
				
			||||||
      #     suffix: lint
 | 
					          baseimg: docker
 | 
				
			||||||
      #     version: ${{ needs.init.outputs.tag }}
 | 
					          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
 | 
					      - name: Upload digests
 | 
				
			||||||
        uses: actions/upload-artifact@v4.6.2
 | 
					        uses: actions/upload-artifact@v4.4.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          name: digests-${{ matrix.platform.arch }}
 | 
					          name: digests-${{ steps.sanitize.outputs.name }}
 | 
				
			||||||
          path: /tmp/digests
 | 
					          path: /tmp/digests
 | 
				
			||||||
          retention-days: 1
 | 
					          retention-days: 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  deploy-manifest:
 | 
					  deploy-manifest:
 | 
				
			||||||
    name: Publish ESPHome ${{ matrix.image.build_type }} to ${{ matrix.registry }}
 | 
					    name: Publish ESPHome ${{ matrix.image.title }} to ${{ matrix.registry }}
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - init
 | 
					      - init
 | 
				
			||||||
@@ -158,12 +161,15 @@ jobs:
 | 
				
			|||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        image:
 | 
					        image:
 | 
				
			||||||
          - build_type: "docker"
 | 
					          - title: "ha-addon"
 | 
				
			||||||
            suffix: ""
 | 
					            target: "hassio"
 | 
				
			||||||
          - build_type: "ha-addon"
 | 
					 | 
				
			||||||
            suffix: "hassio"
 | 
					            suffix: "hassio"
 | 
				
			||||||
            # - build_type: "lint"
 | 
					          - title: "docker"
 | 
				
			||||||
            #   suffix: "lint"
 | 
					            target: "docker"
 | 
				
			||||||
 | 
					            suffix: ""
 | 
				
			||||||
 | 
					          - title: "lint"
 | 
				
			||||||
 | 
					            target: "lint"
 | 
				
			||||||
 | 
					            suffix: "lint"
 | 
				
			||||||
        registry:
 | 
					        registry:
 | 
				
			||||||
          - ghcr
 | 
					          - ghcr
 | 
				
			||||||
          - dockerhub
 | 
					          - dockerhub
 | 
				
			||||||
@@ -171,24 +177,24 @@ jobs:
 | 
				
			|||||||
      - uses: actions/checkout@v4.1.7
 | 
					      - uses: actions/checkout@v4.1.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Download digests
 | 
					      - name: Download digests
 | 
				
			||||||
        uses: actions/download-artifact@v4.3.0
 | 
					        uses: actions/download-artifact@v4.1.8
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          pattern: digests-*
 | 
					          pattern: digests-*
 | 
				
			||||||
          path: /tmp/digests
 | 
					          path: /tmp/digests
 | 
				
			||||||
          merge-multiple: true
 | 
					          merge-multiple: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set up Docker Buildx
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
        uses: docker/setup-buildx-action@v3.10.0
 | 
					        uses: docker/setup-buildx-action@v3.6.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Log in to docker hub
 | 
					      - name: Log in to docker hub
 | 
				
			||||||
        if: matrix.registry == 'dockerhub'
 | 
					        if: matrix.registry == 'dockerhub'
 | 
				
			||||||
        uses: docker/login-action@v3.4.0
 | 
					        uses: docker/login-action@v3.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          username: ${{ secrets.DOCKER_USER }}
 | 
					          username: ${{ secrets.DOCKER_USER }}
 | 
				
			||||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
					          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
				
			||||||
      - name: Log in to the GitHub container registry
 | 
					      - name: Log in to the GitHub container registry
 | 
				
			||||||
        if: matrix.registry == 'ghcr'
 | 
					        if: matrix.registry == 'ghcr'
 | 
				
			||||||
        uses: docker/login-action@v3.4.0
 | 
					        uses: docker/login-action@v3.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          registry: ghcr.io
 | 
					          registry: ghcr.io
 | 
				
			||||||
          username: ${{ github.actor }}
 | 
					          username: ${{ github.actor }}
 | 
				
			||||||
@@ -207,7 +213,7 @@ jobs:
 | 
				
			|||||||
          done
 | 
					          done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Create manifest list and push
 | 
					      - name: Create manifest list and push
 | 
				
			||||||
        working-directory: /tmp/digests/${{ matrix.image.build_type }}/${{ matrix.registry }}
 | 
					        working-directory: /tmp/digests/${{ matrix.image.target }}/${{ matrix.registry }}
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \
 | 
					          docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \
 | 
				
			||||||
            $(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *)
 | 
					            $(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *)
 | 
				
			||||||
@@ -238,24 +244,3 @@ jobs:
 | 
				
			|||||||
                content: description
 | 
					                content: description
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					 | 
				
			||||||
  deploy-esphome-schema:
 | 
					 | 
				
			||||||
    if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    needs: [init]
 | 
					 | 
				
			||||||
    environment: ${{ needs.init.outputs.deploy_env }}
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - name: Trigger Workflow
 | 
					 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
 | 
					 | 
				
			||||||
          script: |
 | 
					 | 
				
			||||||
            github.rest.actions.createWorkflowDispatch({
 | 
					 | 
				
			||||||
              owner: "esphome",
 | 
					 | 
				
			||||||
              repo: "esphome-schema",
 | 
					 | 
				
			||||||
              workflow_id: "generate-schemas.yml",
 | 
					 | 
				
			||||||
              ref: "main",
 | 
					 | 
				
			||||||
              inputs: {
 | 
					 | 
				
			||||||
                version: "${{ needs.init.outputs.tag }}",
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -17,7 +17,7 @@ jobs:
 | 
				
			|||||||
  stale:
 | 
					  stale:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/stale@v9.1.0
 | 
					      - uses: actions/stale@v9.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          days-before-pr-stale: 90
 | 
					          days-before-pr-stale: 90
 | 
				
			||||||
          days-before-pr-close: 7
 | 
					          days-before-pr-close: 7
 | 
				
			||||||
@@ -37,7 +37,7 @@ jobs:
 | 
				
			|||||||
  close-issues:
 | 
					  close-issues:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/stale@v9.1.0
 | 
					      - uses: actions/stale@v9.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          days-before-pr-stale: -1
 | 
					          days-before-pr-stale: -1
 | 
				
			||||||
          days-before-pr-close: -1
 | 
					          days-before-pr-close: -1
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -22,7 +22,7 @@ jobs:
 | 
				
			|||||||
          path: lib/home-assistant
 | 
					          path: lib/home-assistant
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Setup Python
 | 
					      - name: Setup Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@v5.2.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: 3.12
 | 
					          python-version: 3.12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,11 +36,11 @@ jobs:
 | 
				
			|||||||
          python ./script/sync-device_class.py
 | 
					          python ./script/sync-device_class.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Commit changes
 | 
					      - name: Commit changes
 | 
				
			||||||
        uses: peter-evans/create-pull-request@v7.0.8
 | 
					        uses: peter-evans/create-pull-request@v7.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
					          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
				
			||||||
          committer: esphomebot <esphome@openhomefoundation.org>
 | 
					          committer: esphomebot <esphome@nabucasa.com>
 | 
				
			||||||
          author: esphomebot <esphome@openhomefoundation.org>
 | 
					          author: esphomebot <esphome@nabucasa.com>
 | 
				
			||||||
          branch: sync/device-classes
 | 
					          branch: sync/device-classes
 | 
				
			||||||
          delete-branch: true
 | 
					          delete-branch: true
 | 
				
			||||||
          title: "Synchronise Device Classes from Home Assistant"
 | 
					          title: "Synchronise Device Classes from Home Assistant"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -75,9 +75,6 @@ cov.xml
 | 
				
			|||||||
# pyenv
 | 
					# pyenv
 | 
				
			||||||
.python-version
 | 
					.python-version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# asdf
 | 
					 | 
				
			||||||
.tool-versions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Environments
 | 
					# Environments
 | 
				
			||||||
.env
 | 
					.env
 | 
				
			||||||
.venv
 | 
					.venv
 | 
				
			||||||
@@ -143,4 +140,3 @@ sdkconfig.*
 | 
				
			|||||||
/components
 | 
					/components
 | 
				
			||||||
/managed_components
 | 
					/managed_components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
api-docs/
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,19 +4,27 @@
 | 
				
			|||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
					  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
				
			||||||
    # Ruff version.
 | 
					    # Ruff version.
 | 
				
			||||||
    rev: v0.11.9
 | 
					    rev: v0.5.4
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      # Run the linter.
 | 
					      # Run the linter.
 | 
				
			||||||
      - id: ruff
 | 
					      - id: ruff
 | 
				
			||||||
        args: [--fix]
 | 
					        args: [--fix]
 | 
				
			||||||
      # Run the formatter.
 | 
					      # Run the formatter.
 | 
				
			||||||
      - id: ruff-format
 | 
					      - id: ruff-format
 | 
				
			||||||
 | 
					  - repo: https://github.com/psf/black-pre-commit-mirror
 | 
				
			||||||
 | 
					    rev: 24.4.2
 | 
				
			||||||
 | 
					    hooks:
 | 
				
			||||||
 | 
					      - id: black
 | 
				
			||||||
 | 
					        args:
 | 
				
			||||||
 | 
					          - --safe
 | 
				
			||||||
 | 
					          - --quiet
 | 
				
			||||||
 | 
					        files: ^((esphome|script|tests)/.+)?[^/]+\.py$
 | 
				
			||||||
  - repo: https://github.com/PyCQA/flake8
 | 
					  - repo: https://github.com/PyCQA/flake8
 | 
				
			||||||
    rev: 7.2.0
 | 
					    rev: 6.1.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: flake8
 | 
					      - id: flake8
 | 
				
			||||||
        additional_dependencies:
 | 
					        additional_dependencies:
 | 
				
			||||||
          - flake8-docstrings==1.7.0
 | 
					          - flake8-docstrings==1.5.0
 | 
				
			||||||
          - pydocstyle==5.1.1
 | 
					          - pydocstyle==5.1.1
 | 
				
			||||||
        files: ^(esphome|tests)/.+\.py$
 | 
					        files: ^(esphome|tests)/.+\.py$
 | 
				
			||||||
  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
					  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
				
			||||||
@@ -33,7 +41,7 @@ repos:
 | 
				
			|||||||
      - id: pyupgrade
 | 
					      - id: pyupgrade
 | 
				
			||||||
        args: [--py39-plus]
 | 
					        args: [--py39-plus]
 | 
				
			||||||
  - repo: https://github.com/adrienverge/yamllint.git
 | 
					  - repo: https://github.com/adrienverge/yamllint.git
 | 
				
			||||||
    rev: v1.37.1
 | 
					    rev: v1.35.1
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: yamllint
 | 
					      - id: yamllint
 | 
				
			||||||
  - repo: https://github.com/pre-commit/mirrors-clang-format
 | 
					  - repo: https://github.com/pre-commit/mirrors-clang-format
 | 
				
			||||||
@@ -45,6 +53,6 @@ repos:
 | 
				
			|||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: pylint
 | 
					      - id: pylint
 | 
				
			||||||
        name: pylint
 | 
					        name: pylint
 | 
				
			||||||
        entry: python3 script/run-in-env.py pylint
 | 
					        entry: script/run-in-env.sh pylint
 | 
				
			||||||
        language: system
 | 
					        language: script
 | 
				
			||||||
        types: [python]
 | 
					        types: [python]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										63
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -24,7 +24,6 @@ esphome/components/ade7953_i2c/* @angelnu
 | 
				
			|||||||
esphome/components/ade7953_spi/* @angelnu
 | 
					esphome/components/ade7953_spi/* @angelnu
 | 
				
			||||||
esphome/components/ads1118/* @solomondg1
 | 
					esphome/components/ads1118/* @solomondg1
 | 
				
			||||||
esphome/components/ags10/* @mak-42
 | 
					esphome/components/ags10/* @mak-42
 | 
				
			||||||
esphome/components/aic3204/* @kbx81
 | 
					 | 
				
			||||||
esphome/components/airthings_ble/* @jeromelaban
 | 
					esphome/components/airthings_ble/* @jeromelaban
 | 
				
			||||||
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
 | 
					esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
 | 
				
			||||||
esphome/components/airthings_wave_mini/* @ncareau
 | 
					esphome/components/airthings_wave_mini/* @ncareau
 | 
				
			||||||
@@ -48,10 +47,6 @@ esphome/components/at581x/* @X-Ryl669
 | 
				
			|||||||
esphome/components/atc_mithermometer/* @ahpohl
 | 
					esphome/components/atc_mithermometer/* @ahpohl
 | 
				
			||||||
esphome/components/atm90e26/* @danieltwagner
 | 
					esphome/components/atm90e26/* @danieltwagner
 | 
				
			||||||
esphome/components/atm90e32/* @circuitsetup @descipher
 | 
					esphome/components/atm90e32/* @circuitsetup @descipher
 | 
				
			||||||
esphome/components/audio/* @kahrendt
 | 
					 | 
				
			||||||
esphome/components/audio_adc/* @kbx81
 | 
					 | 
				
			||||||
esphome/components/audio_dac/* @kbx81
 | 
					 | 
				
			||||||
esphome/components/axs15231/* @clydebarrow
 | 
					 | 
				
			||||||
esphome/components/b_parasite/* @rbaron
 | 
					esphome/components/b_parasite/* @rbaron
 | 
				
			||||||
esphome/components/ballu/* @bazuchan
 | 
					esphome/components/ballu/* @bazuchan
 | 
				
			||||||
esphome/components/bang_bang/* @OttoWinter
 | 
					esphome/components/bang_bang/* @OttoWinter
 | 
				
			||||||
@@ -86,19 +81,16 @@ esphome/components/bmp581/* @kahrendt
 | 
				
			|||||||
esphome/components/bp1658cj/* @Cossid
 | 
					esphome/components/bp1658cj/* @Cossid
 | 
				
			||||||
esphome/components/bp5758d/* @Cossid
 | 
					esphome/components/bp5758d/* @Cossid
 | 
				
			||||||
esphome/components/button/* @esphome/core
 | 
					esphome/components/button/* @esphome/core
 | 
				
			||||||
esphome/components/bytebuffer/* @clydebarrow
 | 
					 | 
				
			||||||
esphome/components/canbus/* @danielschramm @mvturnho
 | 
					esphome/components/canbus/* @danielschramm @mvturnho
 | 
				
			||||||
esphome/components/cap1188/* @mreditor97
 | 
					esphome/components/cap1188/* @mreditor97
 | 
				
			||||||
esphome/components/captive_portal/* @OttoWinter
 | 
					esphome/components/captive_portal/* @OttoWinter
 | 
				
			||||||
esphome/components/ccs811/* @habbie
 | 
					esphome/components/ccs811/* @habbie
 | 
				
			||||||
esphome/components/cd74hc4067/* @asoehlke
 | 
					esphome/components/cd74hc4067/* @asoehlke
 | 
				
			||||||
esphome/components/ch422g/* @clydebarrow @jesterret
 | 
					esphome/components/ch422g/* @jesterret
 | 
				
			||||||
esphome/components/chsc6x/* @kkosik20
 | 
					 | 
				
			||||||
esphome/components/climate/* @esphome/core
 | 
					esphome/components/climate/* @esphome/core
 | 
				
			||||||
esphome/components/climate_ir/* @glmnet
 | 
					esphome/components/climate_ir/* @glmnet
 | 
				
			||||||
esphome/components/color_temperature/* @jesserockz
 | 
					esphome/components/color_temperature/* @jesserockz
 | 
				
			||||||
esphome/components/combination/* @Cat-Ion @kahrendt
 | 
					esphome/components/combination/* @Cat-Ion @kahrendt
 | 
				
			||||||
esphome/components/const/* @esphome/core
 | 
					 | 
				
			||||||
esphome/components/coolix/* @glmnet
 | 
					esphome/components/coolix/* @glmnet
 | 
				
			||||||
esphome/components/copy/* @OttoWinter
 | 
					esphome/components/copy/* @OttoWinter
 | 
				
			||||||
esphome/components/cover/* @esphome/core
 | 
					esphome/components/cover/* @esphome/core
 | 
				
			||||||
@@ -134,10 +126,6 @@ esphome/components/ens160_base/* @latonita @vincentscode
 | 
				
			|||||||
esphome/components/ens160_i2c/* @latonita
 | 
					esphome/components/ens160_i2c/* @latonita
 | 
				
			||||||
esphome/components/ens160_spi/* @latonita
 | 
					esphome/components/ens160_spi/* @latonita
 | 
				
			||||||
esphome/components/ens210/* @itn3rd77
 | 
					esphome/components/ens210/* @itn3rd77
 | 
				
			||||||
esphome/components/es7210/* @kahrendt
 | 
					 | 
				
			||||||
esphome/components/es7243e/* @kbx81
 | 
					 | 
				
			||||||
esphome/components/es8156/* @kbx81
 | 
					 | 
				
			||||||
esphome/components/es8311/* @kahrendt @kroimon
 | 
					 | 
				
			||||||
esphome/components/esp32/* @esphome/core
 | 
					esphome/components/esp32/* @esphome/core
 | 
				
			||||||
esphome/components/esp32_ble/* @Rapsssito @jesserockz
 | 
					esphome/components/esp32_ble/* @Rapsssito @jesserockz
 | 
				
			||||||
esphome/components/esp32_ble_client/* @jesserockz
 | 
					esphome/components/esp32_ble_client/* @jesserockz
 | 
				
			||||||
@@ -148,9 +136,9 @@ esphome/components/esp32_improv/* @jesserockz
 | 
				
			|||||||
esphome/components/esp32_rmt/* @jesserockz
 | 
					esphome/components/esp32_rmt/* @jesserockz
 | 
				
			||||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
 | 
					esphome/components/esp32_rmt_led_strip/* @jesserockz
 | 
				
			||||||
esphome/components/esp8266/* @esphome/core
 | 
					esphome/components/esp8266/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/esp_adf/* @jesserockz
 | 
				
			||||||
esphome/components/ethernet_info/* @gtjadsonsantos
 | 
					esphome/components/ethernet_info/* @gtjadsonsantos
 | 
				
			||||||
esphome/components/event/* @nohat
 | 
					esphome/components/event/* @nohat
 | 
				
			||||||
esphome/components/event_emitter/* @Rapsssito
 | 
					 | 
				
			||||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
					esphome/components/exposure_notifications/* @OttoWinter
 | 
				
			||||||
esphome/components/ezo/* @ssieb
 | 
					esphome/components/ezo/* @ssieb
 | 
				
			||||||
esphome/components/ezo_pmp/* @carlos-sarmiento
 | 
					esphome/components/ezo_pmp/* @carlos-sarmiento
 | 
				
			||||||
@@ -165,15 +153,13 @@ esphome/components/ft63x6/* @gpambrozio
 | 
				
			|||||||
esphome/components/gcja5/* @gcormier
 | 
					esphome/components/gcja5/* @gcormier
 | 
				
			||||||
esphome/components/gdk101/* @Szewcson
 | 
					esphome/components/gdk101/* @Szewcson
 | 
				
			||||||
esphome/components/globals/* @esphome/core
 | 
					esphome/components/globals/* @esphome/core
 | 
				
			||||||
esphome/components/gp2y1010au0f/* @zry98
 | 
					 | 
				
			||||||
esphome/components/gp8403/* @jesserockz
 | 
					esphome/components/gp8403/* @jesserockz
 | 
				
			||||||
esphome/components/gpio/* @esphome/core
 | 
					esphome/components/gpio/* @esphome/core
 | 
				
			||||||
esphome/components/gpio/one_wire/* @ssieb
 | 
					esphome/components/gpio/one_wire/* @ssieb
 | 
				
			||||||
esphome/components/gps/* @coogle @ximex
 | 
					esphome/components/gps/* @coogle
 | 
				
			||||||
esphome/components/graph/* @synco
 | 
					esphome/components/graph/* @synco
 | 
				
			||||||
esphome/components/graphical_display_menu/* @MrMDavidson
 | 
					esphome/components/graphical_display_menu/* @MrMDavidson
 | 
				
			||||||
esphome/components/gree/* @orestismers
 | 
					esphome/components/gree/* @orestismers
 | 
				
			||||||
esphome/components/grove_gas_mc_v2/* @YorkshireIoT
 | 
					 | 
				
			||||||
esphome/components/grove_tb6612fng/* @max246
 | 
					esphome/components/grove_tb6612fng/* @max246
 | 
				
			||||||
esphome/components/growatt_solar/* @leeuwte
 | 
					esphome/components/growatt_solar/* @leeuwte
 | 
				
			||||||
esphome/components/gt911/* @clydebarrow @jesserockz
 | 
					esphome/components/gt911/* @clydebarrow @jesserockz
 | 
				
			||||||
@@ -181,12 +167,10 @@ esphome/components/haier/* @paveldn
 | 
				
			|||||||
esphome/components/haier/binary_sensor/* @paveldn
 | 
					esphome/components/haier/binary_sensor/* @paveldn
 | 
				
			||||||
esphome/components/haier/button/* @paveldn
 | 
					esphome/components/haier/button/* @paveldn
 | 
				
			||||||
esphome/components/haier/sensor/* @paveldn
 | 
					esphome/components/haier/sensor/* @paveldn
 | 
				
			||||||
esphome/components/haier/switch/* @paveldn
 | 
					 | 
				
			||||||
esphome/components/haier/text_sensor/* @paveldn
 | 
					esphome/components/haier/text_sensor/* @paveldn
 | 
				
			||||||
esphome/components/havells_solar/* @sourabhjaiswal
 | 
					esphome/components/havells_solar/* @sourabhjaiswal
 | 
				
			||||||
esphome/components/hbridge/fan/* @WeekendWarrior
 | 
					esphome/components/hbridge/fan/* @WeekendWarrior
 | 
				
			||||||
esphome/components/hbridge/light/* @DotNetDann
 | 
					esphome/components/hbridge/light/* @DotNetDann
 | 
				
			||||||
esphome/components/hbridge/switch/* @dwmw2
 | 
					 | 
				
			||||||
esphome/components/he60r/* @clydebarrow
 | 
					esphome/components/he60r/* @clydebarrow
 | 
				
			||||||
esphome/components/heatpumpir/* @rob-deutsch
 | 
					esphome/components/heatpumpir/* @rob-deutsch
 | 
				
			||||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
					esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
				
			||||||
@@ -208,11 +192,10 @@ esphome/components/htu31d/* @betterengineering
 | 
				
			|||||||
esphome/components/hydreon_rgxx/* @functionpointer
 | 
					esphome/components/hydreon_rgxx/* @functionpointer
 | 
				
			||||||
esphome/components/hyt271/* @Philippe12
 | 
					esphome/components/hyt271/* @Philippe12
 | 
				
			||||||
esphome/components/i2c/* @esphome/core
 | 
					esphome/components/i2c/* @esphome/core
 | 
				
			||||||
esphome/components/i2c_device/* @gabest11
 | 
					 | 
				
			||||||
esphome/components/i2s_audio/* @jesserockz
 | 
					esphome/components/i2s_audio/* @jesserockz
 | 
				
			||||||
esphome/components/i2s_audio/media_player/* @jesserockz
 | 
					esphome/components/i2s_audio/media_player/* @jesserockz
 | 
				
			||||||
esphome/components/i2s_audio/microphone/* @jesserockz
 | 
					esphome/components/i2s_audio/microphone/* @jesserockz
 | 
				
			||||||
esphome/components/i2s_audio/speaker/* @jesserockz @kahrendt
 | 
					esphome/components/i2s_audio/speaker/* @jesserockz
 | 
				
			||||||
esphome/components/iaqcore/* @yozik04
 | 
					esphome/components/iaqcore/* @yozik04
 | 
				
			||||||
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
 | 
					esphome/components/ili9xxx/* @clydebarrow @nielsnl68
 | 
				
			||||||
esphome/components/improv_base/* @esphome/core
 | 
					esphome/components/improv_base/* @esphome/core
 | 
				
			||||||
@@ -236,7 +219,6 @@ esphome/components/kuntze/* @ssieb
 | 
				
			|||||||
esphome/components/lcd_menu/* @numo68
 | 
					esphome/components/lcd_menu/* @numo68
 | 
				
			||||||
esphome/components/ld2410/* @regevbr @sebcaps
 | 
					esphome/components/ld2410/* @regevbr @sebcaps
 | 
				
			||||||
esphome/components/ld2420/* @descipher
 | 
					esphome/components/ld2420/* @descipher
 | 
				
			||||||
esphome/components/ld2450/* @hareeshmu
 | 
					 | 
				
			||||||
esphome/components/ledc/* @OttoWinter
 | 
					esphome/components/ledc/* @OttoWinter
 | 
				
			||||||
esphome/components/libretiny/* @kuba2k2
 | 
					esphome/components/libretiny/* @kuba2k2
 | 
				
			||||||
esphome/components/libretiny_pwm/* @kuba2k2
 | 
					esphome/components/libretiny_pwm/* @kuba2k2
 | 
				
			||||||
@@ -245,15 +227,12 @@ esphome/components/lightwaverf/* @max246
 | 
				
			|||||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
					esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
				
			||||||
esphome/components/lock/* @esphome/core
 | 
					esphome/components/lock/* @esphome/core
 | 
				
			||||||
esphome/components/logger/* @esphome/core
 | 
					esphome/components/logger/* @esphome/core
 | 
				
			||||||
esphome/components/logger/select/* @clydebarrow
 | 
					 | 
				
			||||||
esphome/components/ltr390/* @latonita @sjtrny
 | 
					esphome/components/ltr390/* @latonita @sjtrny
 | 
				
			||||||
esphome/components/ltr501/* @latonita
 | 
					esphome/components/ltr501/* @latonita
 | 
				
			||||||
esphome/components/ltr_als_ps/* @latonita
 | 
					esphome/components/ltr_als_ps/* @latonita
 | 
				
			||||||
esphome/components/lvgl/* @clydebarrow
 | 
					esphome/components/lvgl/* @clydebarrow
 | 
				
			||||||
esphome/components/m5stack_8angle/* @rnauber
 | 
					esphome/components/m5stack_8angle/* @rnauber
 | 
				
			||||||
esphome/components/mapping/* @clydebarrow
 | 
					 | 
				
			||||||
esphome/components/matrix_keypad/* @ssieb
 | 
					esphome/components/matrix_keypad/* @ssieb
 | 
				
			||||||
esphome/components/max17043/* @blacknell
 | 
					 | 
				
			||||||
esphome/components/max31865/* @DAVe3283
 | 
					esphome/components/max31865/* @DAVe3283
 | 
				
			||||||
esphome/components/max44009/* @berfenger
 | 
					esphome/components/max44009/* @berfenger
 | 
				
			||||||
esphome/components/max6956/* @looping40
 | 
					esphome/components/max6956/* @looping40
 | 
				
			||||||
@@ -268,7 +247,6 @@ esphome/components/mcp23x17_base/* @jesserockz
 | 
				
			|||||||
esphome/components/mcp23xxx_base/* @jesserockz
 | 
					esphome/components/mcp23xxx_base/* @jesserockz
 | 
				
			||||||
esphome/components/mcp2515/* @danielschramm @mvturnho
 | 
					esphome/components/mcp2515/* @danielschramm @mvturnho
 | 
				
			||||||
esphome/components/mcp3204/* @rsumner
 | 
					esphome/components/mcp3204/* @rsumner
 | 
				
			||||||
esphome/components/mcp4461/* @p1ngb4ck
 | 
					 | 
				
			||||||
esphome/components/mcp4728/* @berfenger
 | 
					esphome/components/mcp4728/* @berfenger
 | 
				
			||||||
esphome/components/mcp47a1/* @jesserockz
 | 
					esphome/components/mcp47a1/* @jesserockz
 | 
				
			||||||
esphome/components/mcp9600/* @mreditor97
 | 
					esphome/components/mcp9600/* @mreditor97
 | 
				
			||||||
@@ -278,13 +256,11 @@ esphome/components/mdns/* @esphome/core
 | 
				
			|||||||
esphome/components/media_player/* @jesserockz
 | 
					esphome/components/media_player/* @jesserockz
 | 
				
			||||||
esphome/components/micro_wake_word/* @jesserockz @kahrendt
 | 
					esphome/components/micro_wake_word/* @jesserockz @kahrendt
 | 
				
			||||||
esphome/components/micronova/* @jorre05
 | 
					esphome/components/micronova/* @jorre05
 | 
				
			||||||
esphome/components/microphone/* @jesserockz @kahrendt
 | 
					esphome/components/microphone/* @jesserockz
 | 
				
			||||||
esphome/components/mics_4514/* @jesserockz
 | 
					esphome/components/mics_4514/* @jesserockz
 | 
				
			||||||
esphome/components/midea/* @dudanov
 | 
					esphome/components/midea/* @dudanov
 | 
				
			||||||
esphome/components/midea_ir/* @dudanov
 | 
					esphome/components/midea_ir/* @dudanov
 | 
				
			||||||
esphome/components/mipi_spi/* @clydebarrow
 | 
					 | 
				
			||||||
esphome/components/mitsubishi/* @RubyBailey
 | 
					esphome/components/mitsubishi/* @RubyBailey
 | 
				
			||||||
esphome/components/mixer/speaker/* @kahrendt
 | 
					 | 
				
			||||||
esphome/components/mlx90393/* @functionpointer
 | 
					esphome/components/mlx90393/* @functionpointer
 | 
				
			||||||
esphome/components/mlx90614/* @jesserockz
 | 
					esphome/components/mlx90614/* @jesserockz
 | 
				
			||||||
esphome/components/mmc5603/* @benhoff
 | 
					esphome/components/mmc5603/* @benhoff
 | 
				
			||||||
@@ -303,8 +279,6 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt
 | 
				
			|||||||
esphome/components/mpl3115a2/* @kbickar
 | 
					esphome/components/mpl3115a2/* @kbickar
 | 
				
			||||||
esphome/components/mpu6886/* @fabaff
 | 
					esphome/components/mpu6886/* @fabaff
 | 
				
			||||||
esphome/components/ms8607/* @e28eta
 | 
					esphome/components/ms8607/* @e28eta
 | 
				
			||||||
esphome/components/msa3xx/* @latonita
 | 
					 | 
				
			||||||
esphome/components/nau7802/* @cujomalainey
 | 
					 | 
				
			||||||
esphome/components/network/* @esphome/core
 | 
					esphome/components/network/* @esphome/core
 | 
				
			||||||
esphome/components/nextion/* @edwardtfn @senexcrenshaw
 | 
					esphome/components/nextion/* @edwardtfn @senexcrenshaw
 | 
				
			||||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
 | 
					esphome/components/nextion/binary_sensor/* @senexcrenshaw
 | 
				
			||||||
@@ -313,14 +287,11 @@ esphome/components/nextion/switch/* @senexcrenshaw
 | 
				
			|||||||
esphome/components/nextion/text_sensor/* @senexcrenshaw
 | 
					esphome/components/nextion/text_sensor/* @senexcrenshaw
 | 
				
			||||||
esphome/components/nfc/* @jesserockz @kbx81
 | 
					esphome/components/nfc/* @jesserockz @kbx81
 | 
				
			||||||
esphome/components/noblex/* @AGalfra
 | 
					esphome/components/noblex/* @AGalfra
 | 
				
			||||||
esphome/components/npi19/* @bakerkj
 | 
					 | 
				
			||||||
esphome/components/number/* @esphome/core
 | 
					esphome/components/number/* @esphome/core
 | 
				
			||||||
esphome/components/one_wire/* @ssieb
 | 
					esphome/components/one_wire/* @ssieb
 | 
				
			||||||
esphome/components/online_image/* @clydebarrow @guillempages
 | 
					esphome/components/online_image/* @guillempages
 | 
				
			||||||
esphome/components/opentherm/* @olegtarasov
 | 
					 | 
				
			||||||
esphome/components/ota/* @esphome/core
 | 
					esphome/components/ota/* @esphome/core
 | 
				
			||||||
esphome/components/output/* @esphome/core
 | 
					esphome/components/output/* @esphome/core
 | 
				
			||||||
esphome/components/packet_transport/* @clydebarrow
 | 
					 | 
				
			||||||
esphome/components/pca6416a/* @Mat931
 | 
					esphome/components/pca6416a/* @Mat931
 | 
				
			||||||
esphome/components/pca9554/* @clydebarrow @hwstar
 | 
					esphome/components/pca9554/* @clydebarrow @hwstar
 | 
				
			||||||
esphome/components/pcf85063/* @brogon
 | 
					esphome/components/pcf85063/* @brogon
 | 
				
			||||||
@@ -328,9 +299,7 @@ esphome/components/pcf8563/* @KoenBreeman
 | 
				
			|||||||
esphome/components/pid/* @OttoWinter
 | 
					esphome/components/pid/* @OttoWinter
 | 
				
			||||||
esphome/components/pipsolar/* @andreashergert1984
 | 
					esphome/components/pipsolar/* @andreashergert1984
 | 
				
			||||||
esphome/components/pm1006/* @habbie
 | 
					esphome/components/pm1006/* @habbie
 | 
				
			||||||
esphome/components/pm2005/* @andrewjswan
 | 
					 | 
				
			||||||
esphome/components/pmsa003i/* @sjtrny
 | 
					esphome/components/pmsa003i/* @sjtrny
 | 
				
			||||||
esphome/components/pmsx003/* @ximex
 | 
					 | 
				
			||||||
esphome/components/pmwcs3/* @SeByDocKy
 | 
					esphome/components/pmwcs3/* @SeByDocKy
 | 
				
			||||||
esphome/components/pn532/* @OttoWinter @jesserockz
 | 
					esphome/components/pn532/* @OttoWinter @jesserockz
 | 
				
			||||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
 | 
					esphome/components/pn532_i2c/* @OttoWinter @jesserockz
 | 
				
			||||||
@@ -348,14 +317,14 @@ esphome/components/pvvx_mithermometer/* @pasiz
 | 
				
			|||||||
esphome/components/pylontech/* @functionpointer
 | 
					esphome/components/pylontech/* @functionpointer
 | 
				
			||||||
esphome/components/qmp6988/* @andrewpc
 | 
					esphome/components/qmp6988/* @andrewpc
 | 
				
			||||||
esphome/components/qr_code/* @wjtje
 | 
					esphome/components/qr_code/* @wjtje
 | 
				
			||||||
esphome/components/qspi_dbi/* @clydebarrow
 | 
					esphome/components/qspi_amoled/* @clydebarrow
 | 
				
			||||||
esphome/components/qwiic_pir/* @kahrendt
 | 
					esphome/components/qwiic_pir/* @kahrendt
 | 
				
			||||||
esphome/components/radon_eye_ble/* @jeffeb3
 | 
					esphome/components/radon_eye_ble/* @jeffeb3
 | 
				
			||||||
esphome/components/radon_eye_rd200/* @jeffeb3
 | 
					esphome/components/radon_eye_rd200/* @jeffeb3
 | 
				
			||||||
esphome/components/rc522/* @glmnet
 | 
					esphome/components/rc522/* @glmnet
 | 
				
			||||||
esphome/components/rc522_i2c/* @glmnet
 | 
					esphome/components/rc522_i2c/* @glmnet
 | 
				
			||||||
esphome/components/rc522_spi/* @glmnet
 | 
					esphome/components/rc522_spi/* @glmnet
 | 
				
			||||||
esphome/components/resampler/speaker/* @kahrendt
 | 
					esphome/components/resistance_sampler/* @jesserockz
 | 
				
			||||||
esphome/components/restart/* @esphome/core
 | 
					esphome/components/restart/* @esphome/core
 | 
				
			||||||
esphome/components/rf_bridge/* @jesserockz
 | 
					esphome/components/rf_bridge/* @jesserockz
 | 
				
			||||||
esphome/components/rgbct/* @jesserockz
 | 
					esphome/components/rgbct/* @jesserockz
 | 
				
			||||||
@@ -368,12 +337,10 @@ esphome/components/rtttl/* @glmnet
 | 
				
			|||||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
 | 
					esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
 | 
				
			||||||
esphome/components/scd4x/* @martgras @sjtrny
 | 
					esphome/components/scd4x/* @martgras @sjtrny
 | 
				
			||||||
esphome/components/script/* @esphome/core
 | 
					esphome/components/script/* @esphome/core
 | 
				
			||||||
esphome/components/sdl/* @bdm310 @clydebarrow
 | 
					esphome/components/sdl/* @clydebarrow
 | 
				
			||||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
					esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
				
			||||||
esphome/components/sdp3x/* @Azimath
 | 
					esphome/components/sdp3x/* @Azimath
 | 
				
			||||||
esphome/components/seeed_mr24hpc1/* @limengdu
 | 
					esphome/components/seeed_mr24hpc1/* @limengdu
 | 
				
			||||||
esphome/components/seeed_mr60bha2/* @limengdu
 | 
					 | 
				
			||||||
esphome/components/seeed_mr60fda2/* @limengdu
 | 
					 | 
				
			||||||
esphome/components/selec_meter/* @sourabhjaiswal
 | 
					esphome/components/selec_meter/* @sourabhjaiswal
 | 
				
			||||||
esphome/components/select/* @esphome/core
 | 
					esphome/components/select/* @esphome/core
 | 
				
			||||||
esphome/components/sen0321/* @notjj
 | 
					esphome/components/sen0321/* @notjj
 | 
				
			||||||
@@ -399,9 +366,7 @@ esphome/components/smt100/* @piechade
 | 
				
			|||||||
esphome/components/sn74hc165/* @jesserockz
 | 
					esphome/components/sn74hc165/* @jesserockz
 | 
				
			||||||
esphome/components/socket/* @esphome/core
 | 
					esphome/components/socket/* @esphome/core
 | 
				
			||||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
					esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
				
			||||||
esphome/components/sound_level/* @kahrendt
 | 
					esphome/components/speaker/* @jesserockz
 | 
				
			||||||
esphome/components/speaker/* @jesserockz @kahrendt
 | 
					 | 
				
			||||||
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
 | 
					 | 
				
			||||||
esphome/components/spi/* @clydebarrow @esphome/core
 | 
					esphome/components/spi/* @clydebarrow @esphome/core
 | 
				
			||||||
esphome/components/spi_device/* @clydebarrow
 | 
					esphome/components/spi_device/* @clydebarrow
 | 
				
			||||||
esphome/components/spi_led_strip/* @clydebarrow
 | 
					esphome/components/spi_led_strip/* @clydebarrow
 | 
				
			||||||
@@ -430,16 +395,11 @@ esphome/components/substitutions/* @esphome/core
 | 
				
			|||||||
esphome/components/sun/* @OttoWinter
 | 
					esphome/components/sun/* @OttoWinter
 | 
				
			||||||
esphome/components/sun_gtil2/* @Mat931
 | 
					esphome/components/sun_gtil2/* @Mat931
 | 
				
			||||||
esphome/components/switch/* @esphome/core
 | 
					esphome/components/switch/* @esphome/core
 | 
				
			||||||
esphome/components/switch/binary_sensor/* @ssieb
 | 
					 | 
				
			||||||
esphome/components/syslog/* @clydebarrow
 | 
					 | 
				
			||||||
esphome/components/t6615/* @tylermenezes
 | 
					esphome/components/t6615/* @tylermenezes
 | 
				
			||||||
esphome/components/tc74/* @sethgirvan
 | 
					 | 
				
			||||||
esphome/components/tca9548a/* @andreashergert1984
 | 
					esphome/components/tca9548a/* @andreashergert1984
 | 
				
			||||||
esphome/components/tca9555/* @mobrembski
 | 
					 | 
				
			||||||
esphome/components/tcl112/* @glmnet
 | 
					esphome/components/tcl112/* @glmnet
 | 
				
			||||||
esphome/components/tee501/* @Stock-M
 | 
					esphome/components/tee501/* @Stock-M
 | 
				
			||||||
esphome/components/teleinfo/* @0hax
 | 
					esphome/components/teleinfo/* @0hax
 | 
				
			||||||
esphome/components/tem3200/* @bakerkj
 | 
					 | 
				
			||||||
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
 | 
					esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
 | 
				
			||||||
esphome/components/template/datetime/* @rfdarter
 | 
					esphome/components/template/datetime/* @rfdarter
 | 
				
			||||||
esphome/components/template/event/* @nohat
 | 
					esphome/components/template/event/* @nohat
 | 
				
			||||||
@@ -457,7 +417,6 @@ esphome/components/tmp102/* @timsavage
 | 
				
			|||||||
esphome/components/tmp1075/* @sybrenstuvel
 | 
					esphome/components/tmp1075/* @sybrenstuvel
 | 
				
			||||||
esphome/components/tmp117/* @Azimath
 | 
					esphome/components/tmp117/* @Azimath
 | 
				
			||||||
esphome/components/tof10120/* @wstrzalka
 | 
					esphome/components/tof10120/* @wstrzalka
 | 
				
			||||||
esphome/components/tormatic/* @ti-mo
 | 
					 | 
				
			||||||
esphome/components/toshiba/* @kbx81
 | 
					esphome/components/toshiba/* @kbx81
 | 
				
			||||||
esphome/components/touchscreen/* @jesserockz @nielsnl68
 | 
					esphome/components/touchscreen/* @jesserockz @nielsnl68
 | 
				
			||||||
esphome/components/tsl2591/* @wjcarpenter
 | 
					esphome/components/tsl2591/* @wjcarpenter
 | 
				
			||||||
@@ -471,7 +430,6 @@ esphome/components/tuya/switch/* @jesserockz
 | 
				
			|||||||
esphome/components/tuya/text_sensor/* @dentra
 | 
					esphome/components/tuya/text_sensor/* @dentra
 | 
				
			||||||
esphome/components/uart/* @esphome/core
 | 
					esphome/components/uart/* @esphome/core
 | 
				
			||||||
esphome/components/uart/button/* @ssieb
 | 
					esphome/components/uart/button/* @ssieb
 | 
				
			||||||
esphome/components/uart/packet_transport/* @clydebarrow
 | 
					 | 
				
			||||||
esphome/components/udp/* @clydebarrow
 | 
					esphome/components/udp/* @clydebarrow
 | 
				
			||||||
esphome/components/ufire_ec/* @pvizeli
 | 
					esphome/components/ufire_ec/* @pvizeli
 | 
				
			||||||
esphome/components/ufire_ise/* @pvizeli
 | 
					esphome/components/ufire_ise/* @pvizeli
 | 
				
			||||||
@@ -515,6 +473,5 @@ esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
				
			|||||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
 | 
					esphome/components/xiaomi_rtcgq02lm/* @jesserockz
 | 
				
			||||||
esphome/components/xl9535/* @mreditor97
 | 
					esphome/components/xl9535/* @mreditor97
 | 
				
			||||||
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
 | 
					esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
 | 
				
			||||||
esphome/components/xxtea/* @clydebarrow
 | 
					 | 
				
			||||||
esphome/components/zhlt01/* @cfeenstra1024
 | 
					esphome/components/zhlt01/* @cfeenstra1024
 | 
				
			||||||
esphome/components/zio_ultrasonic/* @kahrendt
 | 
					esphome/components/zio_ultrasonic/* @kahrendt
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Enforcement
 | 
					## Enforcement
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at esphome@openhomefoundation.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
 | 
					Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at esphome@nabucasa.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
 | 
					Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,12 @@
 | 
				
			|||||||
# Contributing to ESPHome [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
 | 
					# Contributing to ESPHome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
We welcome contributions to the ESPHome suite of code and documentation!
 | 
					For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Please read our [contributing guide](https://esphome.io/guides/contributing.html) if you wish to contribute to the
 | 
					Things to note when contributing:
 | 
				
			||||||
project and be sure to join us on [Discord](https://discord.gg/KhAMKrd).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
**See also:**
 | 
					 - Please test your changes :)
 | 
				
			||||||
 | 
					 - If a new feature is added or an existing user-facing feature is changed, you should also
 | 
				
			||||||
[Documentation](https://esphome.io) -- [Issues](https://github.com/esphome/issues/issues) -- [Feature requests](https://github.com/esphome/feature-requests/issues)
 | 
					   update the [docs](https://github.com/esphome/esphome-docs). See [contributing to esphome-docs](https://esphome.io/guides/contributing.html#contributing-to-esphomedocs)
 | 
				
			||||||
 | 
					   for more information.
 | 
				
			||||||
---
 | 
					 - Please also update the tests in the `tests/` folder. You can do so by just adding a line in one of the YAML files
 | 
				
			||||||
 | 
					   which checks if your new feature compiles correctly.
 | 
				
			||||||
[](https://www.openhomefoundation.org/)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							@@ -1,16 +1,9 @@
 | 
				
			|||||||
# ESPHome [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
 | 
					# ESPHome [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<a href="https://esphome.io/">
 | 
					[](https://esphome.io/)
 | 
				
			||||||
  <picture>
 | 
					 | 
				
			||||||
    <source media="(prefers-color-scheme: dark)" srcset="https://esphome.io/_static/logo-text-on-dark.svg", alt="ESPHome Logo">
 | 
					 | 
				
			||||||
    <img src="https://esphome.io/_static/logo-text-on-light.svg" alt="ESPHome Logo">
 | 
					 | 
				
			||||||
  </picture>
 | 
					 | 
				
			||||||
</a>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					**Documentation:** https://esphome.io/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Documentation](https://esphome.io) -- [Issues](https://github.com/esphome/issues/issues) -- [Feature requests](https://github.com/esphome/feature-requests/issues)
 | 
					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/)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,56 +1,139 @@
 | 
				
			|||||||
ARG BUILD_VERSION=dev
 | 
					# Build these with the build.py script
 | 
				
			||||||
ARG BUILD_OS=alpine
 | 
					# Example:
 | 
				
			||||||
ARG BUILD_BASE_VERSION=2025.04.0
 | 
					#   python3 docker/build.py --tag dev --arch amd64 --build-type docker build
 | 
				
			||||||
ARG BUILD_TYPE=docker
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-${BUILD_BASE_VERSION} AS base-source-docker
 | 
					# One of "docker", "hassio"
 | 
				
			||||||
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-ha-addon-${BUILD_BASE_VERSION} AS base-source-ha-addon
 | 
					ARG BASEIMGTYPE=docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARG BUILD_TYPE
 | 
					 | 
				
			||||||
FROM base-source-${BUILD_TYPE} AS base
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN git config --system --add safe.directory "*"
 | 
					# https://github.com/hassio-addons/addon-debian-base/releases
 | 
				
			||||||
 | 
					FROM ghcr.io/hassio-addons/debian-base:7.2.0 AS base-hassio
 | 
				
			||||||
 | 
					# https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm
 | 
				
			||||||
 | 
					FROM debian:12.2-slim AS base-docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
 | 
					FROM base-${BASEIMGTYPE} AS base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN pip install --no-cache-dir -U pip uv==0.6.14
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY requirements.txt /
 | 
					ARG TARGETARCH
 | 
				
			||||||
 | 
					ARG TARGETVARIANT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Note that --break-system-packages is used below because
 | 
				
			||||||
 | 
					# https://peps.python.org/pep-0668/ added a safety check that prevents
 | 
				
			||||||
 | 
					# installing packages with the same name as a system package. This is
 | 
				
			||||||
 | 
					# not a problem for us because we are not concerned about overwriting
 | 
				
			||||||
 | 
					# system packages because we are running in an isolated container.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN \
 | 
					RUN \
 | 
				
			||||||
    uv pip install --no-cache-dir \
 | 
					    apt-get update \
 | 
				
			||||||
    -r /requirements.txt
 | 
					    # Use pinned versions so that we get updates with build caching
 | 
				
			||||||
 | 
					    && apt-get install -y --no-install-recommends \
 | 
				
			||||||
 | 
					        python3-pip=23.0.1+dfsg-1 \
 | 
				
			||||||
 | 
					        python3-setuptools=66.1.1-1 \
 | 
				
			||||||
 | 
					        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+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 \
 | 
				
			||||||
 | 
					    && ( \
 | 
				
			||||||
 | 
					        ( \
 | 
				
			||||||
 | 
					            [ "$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/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV \
 | 
				
			||||||
 | 
					  # Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
 | 
				
			||||||
 | 
					  LANG=C.UTF-8 LC_ALL=C.UTF-8 \
 | 
				
			||||||
 | 
					  # Store globally installed pio libs in /piolibs
 | 
				
			||||||
 | 
					  PLATFORMIO_GLOBALLIB_DIR=/piolibs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Support legacy binaries on Debian multiarch system. There is no "correct" way
 | 
				
			||||||
 | 
					# to do this, other than using properly built toolchains...
 | 
				
			||||||
 | 
					# See: https://unix.stackexchange.com/questions/553743/correct-way-to-add-lib-ld-linux-so-3-in-debian
 | 
				
			||||||
 | 
					RUN \
 | 
				
			||||||
 | 
					    if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
 | 
				
			||||||
 | 
					        ln -s /lib/arm-linux-gnueabihf/ld-linux-armhf.so.3 /lib/ld-linux.so.3; \
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN \
 | 
					RUN \
 | 
				
			||||||
    platformio settings set enable_telemetry No \
 | 
					    # Ubuntu python3-pip is missing wheel
 | 
				
			||||||
 | 
					    if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
 | 
				
			||||||
 | 
					        export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
 | 
				
			||||||
 | 
					    fi; \
 | 
				
			||||||
 | 
					    pip3 install \
 | 
				
			||||||
 | 
					    --break-system-packages --no-cache-dir \
 | 
				
			||||||
 | 
					    # Keep platformio version in sync with requirements.txt
 | 
				
			||||||
 | 
					    platformio==6.1.15 \
 | 
				
			||||||
 | 
					    # Change some platformio settings
 | 
				
			||||||
 | 
					    && platformio settings set enable_telemetry No \
 | 
				
			||||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
					    && platformio settings set check_platformio_interval 1000000 \
 | 
				
			||||||
    && mkdir -p /piolibs
 | 
					    && mkdir -p /piolibs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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 /
 | 
				
			||||||
 | 
					RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
 | 
				
			||||||
 | 
					        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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY script/platformio_install_deps.py platformio.ini /
 | 
					COPY script/platformio_install_deps.py platformio.ini /
 | 
				
			||||||
RUN /platformio_install_deps.py /platformio.ini --libraries
 | 
					RUN /platformio_install_deps.py /platformio.ini --libraries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARG BUILD_VERSION
 | 
					# Avoid unsafe git error when container user and file config volume permissions don't match
 | 
				
			||||||
 | 
					RUN git config --system --add safe.directory '*'
 | 
				
			||||||
LABEL \
 | 
					 | 
				
			||||||
    org.opencontainers.image.authors="The ESPHome Authors" \
 | 
					 | 
				
			||||||
    org.opencontainers.image.title="ESPHome" \
 | 
					 | 
				
			||||||
    org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
 | 
					 | 
				
			||||||
    org.opencontainers.image.url="https://esphome.io/" \
 | 
					 | 
				
			||||||
    org.opencontainers.image.documentation="https://esphome.io/" \
 | 
					 | 
				
			||||||
    org.opencontainers.image.source="https://github.com/esphome/esphome" \
 | 
					 | 
				
			||||||
    org.opencontainers.image.licenses="ESPHome" \
 | 
					 | 
				
			||||||
    org.opencontainers.image.version=${BUILD_VERSION}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ======================= docker-type image =======================
 | 
					# ======================= docker-type image =======================
 | 
				
			||||||
FROM base AS base-docker
 | 
					FROM base AS docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copy esphome and install
 | 
				
			||||||
 | 
					COPY . /esphome
 | 
				
			||||||
 | 
					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 -e /esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Settings for dashboard
 | 
				
			||||||
 | 
					ENV USERNAME="" PASSWORD=""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Expose the dashboard to Docker
 | 
					# Expose the dashboard to Docker
 | 
				
			||||||
EXPOSE 6052
 | 
					EXPOSE 6052
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Run healthcheck (heartbeat)
 | 
					# Run healthcheck (heartbeat)
 | 
				
			||||||
HEALTHCHECK --interval=30s --timeout=30s \
 | 
					HEALTHCHECK --interval=30s --timeout=30s \
 | 
				
			||||||
    CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
 | 
					  CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY docker/docker_entrypoint.sh /entrypoint.sh
 | 
					COPY docker/docker_entrypoint.sh /entrypoint.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,23 +147,73 @@ ENTRYPOINT ["/entrypoint.sh"]
 | 
				
			|||||||
CMD ["dashboard", "/config"]
 | 
					CMD ["dashboard", "/config"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ======================= ha-addon-type image =======================
 | 
					
 | 
				
			||||||
FROM base AS base-ha-addon
 | 
					
 | 
				
			||||||
 | 
					# ======================= hassio-type image =======================
 | 
				
			||||||
 | 
					FROM base AS hassio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN \
 | 
				
			||||||
 | 
					    apt-get update \
 | 
				
			||||||
 | 
					    # Use pinned versions so that we get updates with build caching
 | 
				
			||||||
 | 
					    && apt-get install -y --no-install-recommends \
 | 
				
			||||||
 | 
					        nginx-light=1.22.1-9 \
 | 
				
			||||||
 | 
					    && rm -rf \
 | 
				
			||||||
 | 
					        /tmp/* \
 | 
				
			||||||
 | 
					        /var/{cache,log}/* \
 | 
				
			||||||
 | 
					        /var/lib/apt/lists/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ARG BUILD_VERSION=dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy root filesystem
 | 
					# Copy root filesystem
 | 
				
			||||||
COPY docker/ha-addon-rootfs/ /
 | 
					COPY docker/ha-addon-rootfs/ /
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARG BUILD_VERSION
 | 
					# Copy esphome and install
 | 
				
			||||||
 | 
					COPY . /esphome
 | 
				
			||||||
 | 
					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 -e /esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Labels
 | 
				
			||||||
LABEL \
 | 
					LABEL \
 | 
				
			||||||
    io.hass.name="ESPHome" \
 | 
					    io.hass.name="ESPHome" \
 | 
				
			||||||
    io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
 | 
					    io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
 | 
				
			||||||
    io.hass.type="addon" \
 | 
					    io.hass.type="addon" \
 | 
				
			||||||
    io.hass.version="${BUILD_VERSION}"
 | 
					    io.hass.version="${BUILD_VERSION}"
 | 
				
			||||||
    # io.hass.arch is inherited from addon-debian-base
 | 
					    # io.hass.arch is inherited from addon-debian-base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARG BUILD_TYPE
 | 
					 | 
				
			||||||
FROM base-${BUILD_TYPE} AS final
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy esphome and install
 | 
					
 | 
				
			||||||
COPY . /esphome
 | 
					
 | 
				
			||||||
RUN uv pip install --no-cache-dir -e /esphome
 | 
					# ======================= lint-type image =======================
 | 
				
			||||||
 | 
					FROM base AS lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV \
 | 
				
			||||||
 | 
					  PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN \
 | 
				
			||||||
 | 
					    apt-get update \
 | 
				
			||||||
 | 
					    # Use pinned versions so that we get updates with build caching
 | 
				
			||||||
 | 
					    && apt-get install -y --no-install-recommends \
 | 
				
			||||||
 | 
					        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.1~deb12u1 \
 | 
				
			||||||
 | 
					        nano=7.2-1+deb12u1 \
 | 
				
			||||||
 | 
					        build-essential=12.9 \
 | 
				
			||||||
 | 
					        python3-dev=3.11.2-1+b1 \
 | 
				
			||||||
 | 
					    && rm -rf \
 | 
				
			||||||
 | 
					        /tmp/* \
 | 
				
			||||||
 | 
					        /var/{cache,log}/* \
 | 
				
			||||||
 | 
					        /var/lib/apt/lists/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY requirements_test.txt /
 | 
				
			||||||
 | 
					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 -r /requirements_test.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VOLUME ["/esphome"]
 | 
				
			||||||
 | 
					WORKDIR /esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,22 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
import argparse
 | 
					 | 
				
			||||||
from dataclasses import dataclass
 | 
					from dataclasses import dataclass
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import shlex
 | 
					 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					import argparse
 | 
				
			||||||
 | 
					from platform import machine
 | 
				
			||||||
 | 
					import shlex
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CHANNEL_DEV = "dev"
 | 
					CHANNEL_DEV = "dev"
 | 
				
			||||||
CHANNEL_BETA = "beta"
 | 
					CHANNEL_BETA = "beta"
 | 
				
			||||||
CHANNEL_RELEASE = "release"
 | 
					CHANNEL_RELEASE = "release"
 | 
				
			||||||
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
 | 
					CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARCH_AMD64 = "amd64"
 | 
					ARCH_AMD64 = "amd64"
 | 
				
			||||||
 | 
					ARCH_ARMV7 = "armv7"
 | 
				
			||||||
ARCH_AARCH64 = "aarch64"
 | 
					ARCH_AARCH64 = "aarch64"
 | 
				
			||||||
ARCHS = [ARCH_AMD64, ARCH_AARCH64]
 | 
					ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TYPE_DOCKER = "docker"
 | 
					TYPE_DOCKER = "docker"
 | 
				
			||||||
TYPE_HA_ADDON = "ha-addon"
 | 
					TYPE_HA_ADDON = "ha-addon"
 | 
				
			||||||
@@ -54,7 +57,7 @@ manifest_parser = subparsers.add_parser(
 | 
				
			|||||||
class DockerParams:
 | 
					class DockerParams:
 | 
				
			||||||
    build_to: str
 | 
					    build_to: str
 | 
				
			||||||
    manifest_to: str
 | 
					    manifest_to: str
 | 
				
			||||||
    build_type: str
 | 
					    baseimgtype: str
 | 
				
			||||||
    platform: str
 | 
					    platform: str
 | 
				
			||||||
    target: str
 | 
					    target: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -66,19 +69,25 @@ class DockerParams:
 | 
				
			|||||||
            TYPE_LINT: "esphome/esphome-lint",
 | 
					            TYPE_LINT: "esphome/esphome-lint",
 | 
				
			||||||
        }[build_type]
 | 
					        }[build_type]
 | 
				
			||||||
        build_to = f"{prefix}-{arch}"
 | 
					        build_to = f"{prefix}-{arch}"
 | 
				
			||||||
 | 
					        baseimgtype = {
 | 
				
			||||||
 | 
					            TYPE_DOCKER: "docker",
 | 
				
			||||||
 | 
					            TYPE_HA_ADDON: "hassio",
 | 
				
			||||||
 | 
					            TYPE_LINT: "docker",
 | 
				
			||||||
 | 
					        }[build_type]
 | 
				
			||||||
        platform = {
 | 
					        platform = {
 | 
				
			||||||
            ARCH_AMD64: "linux/amd64",
 | 
					            ARCH_AMD64: "linux/amd64",
 | 
				
			||||||
 | 
					            ARCH_ARMV7: "linux/arm/v7",
 | 
				
			||||||
            ARCH_AARCH64: "linux/arm64",
 | 
					            ARCH_AARCH64: "linux/arm64",
 | 
				
			||||||
        }[arch]
 | 
					        }[arch]
 | 
				
			||||||
        target = {
 | 
					        target = {
 | 
				
			||||||
            TYPE_DOCKER: "final",
 | 
					            TYPE_DOCKER: "docker",
 | 
				
			||||||
            TYPE_HA_ADDON: "final",
 | 
					            TYPE_HA_ADDON: "hassio",
 | 
				
			||||||
            TYPE_LINT: "lint",
 | 
					            TYPE_LINT: "lint",
 | 
				
			||||||
        }[build_type]
 | 
					        }[build_type]
 | 
				
			||||||
        return cls(
 | 
					        return cls(
 | 
				
			||||||
            build_to=build_to,
 | 
					            build_to=build_to,
 | 
				
			||||||
            manifest_to=prefix,
 | 
					            manifest_to=prefix,
 | 
				
			||||||
            build_type=build_type,
 | 
					            baseimgtype=baseimgtype,
 | 
				
			||||||
            platform=platform,
 | 
					            platform=platform,
 | 
				
			||||||
            target=target,
 | 
					            target=target,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@@ -140,7 +149,7 @@ def main():
 | 
				
			|||||||
            "buildx",
 | 
					            "buildx",
 | 
				
			||||||
            "build",
 | 
					            "build",
 | 
				
			||||||
            "--build-arg",
 | 
					            "--build-arg",
 | 
				
			||||||
            f"BUILD_TYPE={params.build_type}",
 | 
					            f"BASEIMGTYPE={params.baseimgtype}",
 | 
				
			||||||
            "--build-arg",
 | 
					            "--build-arg",
 | 
				
			||||||
            f"BUILD_VERSION={args.tag}",
 | 
					            f"BUILD_VERSION={args.tag}",
 | 
				
			||||||
            "--cache-from",
 | 
					            "--cache-from",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
import argparse
 | 
					 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					import argparse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CHANNEL_DEV = "dev"
 | 
					CHANNEL_DEV = "dev"
 | 
				
			||||||
CHANNEL_BETA = "beta"
 | 
					CHANNEL_BETA = "beta"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,10 @@ if bashio::config.true 'streamer_mode'; then
 | 
				
			|||||||
    export ESPHOME_STREAMER_MODE=true
 | 
					    export ESPHOME_STREAMER_MODE=true
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if bashio::config.true 'status_use_ping'; then
 | 
				
			||||||
 | 
					    export ESPHOME_DASHBOARD_USE_PING=true
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if bashio::config.has_value 'relative_url'; then
 | 
					if bashio::config.has_value 'relative_url'; then
 | 
				
			||||||
    export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
 | 
					    export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@
 | 
				
			|||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
import functools
 | 
					import functools
 | 
				
			||||||
import importlib
 | 
					 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
@@ -21,8 +20,6 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_DEASSERT_RTS_DTR,
 | 
					    CONF_DEASSERT_RTS_DTR,
 | 
				
			||||||
    CONF_DISABLED,
 | 
					    CONF_DISABLED,
 | 
				
			||||||
    CONF_ESPHOME,
 | 
					    CONF_ESPHOME,
 | 
				
			||||||
    CONF_LEVEL,
 | 
					 | 
				
			||||||
    CONF_LOG_TOPIC,
 | 
					 | 
				
			||||||
    CONF_LOGGER,
 | 
					    CONF_LOGGER,
 | 
				
			||||||
    CONF_MDNS,
 | 
					    CONF_MDNS,
 | 
				
			||||||
    CONF_MQTT,
 | 
					    CONF_MQTT,
 | 
				
			||||||
@@ -33,7 +30,6 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_PLATFORMIO_OPTIONS,
 | 
					    CONF_PLATFORMIO_OPTIONS,
 | 
				
			||||||
    CONF_PORT,
 | 
					    CONF_PORT,
 | 
				
			||||||
    CONF_SUBSTITUTIONS,
 | 
					    CONF_SUBSTITUTIONS,
 | 
				
			||||||
    CONF_TOPIC,
 | 
					 | 
				
			||||||
    PLATFORM_BK72XX,
 | 
					    PLATFORM_BK72XX,
 | 
				
			||||||
    PLATFORM_ESP32,
 | 
					    PLATFORM_ESP32,
 | 
				
			||||||
    PLATFORM_ESP8266,
 | 
					    PLATFORM_ESP8266,
 | 
				
			||||||
@@ -42,8 +38,8 @@ from esphome.const import (
 | 
				
			|||||||
    SECRETS_FILES,
 | 
					    SECRETS_FILES,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, EsphomeError, coroutine
 | 
					from esphome.core import CORE, EsphomeError, coroutine
 | 
				
			||||||
from esphome.helpers import get_bool_env, indent, is_ip_address
 | 
					from esphome.helpers import indent, is_ip_address, get_bool_env
 | 
				
			||||||
from esphome.log import AnsiFore, color, setup_log
 | 
					from esphome.log import Fore, color, setup_log
 | 
				
			||||||
from esphome.util import (
 | 
					from esphome.util import (
 | 
				
			||||||
    get_serial_ports,
 | 
					    get_serial_ports,
 | 
				
			||||||
    list_yaml_files,
 | 
					    list_yaml_files,
 | 
				
			||||||
@@ -67,7 +63,7 @@ def choose_prompt(options, purpose: str = None):
 | 
				
			|||||||
        return options[0][1]
 | 
					        return options[0][1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    safe_print(
 | 
					    safe_print(
 | 
				
			||||||
        f"Found multiple options{f' for {purpose}' if purpose else ''}, please choose one:"
 | 
					        f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:'
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    for i, (desc, _) in enumerate(options):
 | 
					    for i, (desc, _) in enumerate(options):
 | 
				
			||||||
        safe_print(f"  [{i + 1}] {desc}")
 | 
					        safe_print(f"  [{i + 1}] {desc}")
 | 
				
			||||||
@@ -83,7 +79,7 @@ def choose_prompt(options, purpose: str = None):
 | 
				
			|||||||
                raise ValueError
 | 
					                raise ValueError
 | 
				
			||||||
            break
 | 
					            break
 | 
				
			||||||
        except ValueError:
 | 
					        except ValueError:
 | 
				
			||||||
            safe_print(color(AnsiFore.RED, f"Invalid option: '{opt}'"))
 | 
					            safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
 | 
				
			||||||
    return options[opt - 1][1]
 | 
					    return options[opt - 1][1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -99,12 +95,8 @@ def choose_upload_log_host(
 | 
				
			|||||||
        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
					        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
				
			||||||
        if default == "OTA":
 | 
					        if default == "OTA":
 | 
				
			||||||
            return CORE.address
 | 
					            return CORE.address
 | 
				
			||||||
    if (
 | 
					    if show_mqtt and CONF_MQTT in CORE.config:
 | 
				
			||||||
        show_mqtt
 | 
					        options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
 | 
				
			||||||
        and (mqtt_config := CORE.config.get(CONF_MQTT))
 | 
					 | 
				
			||||||
        and mqtt_logging_enabled(mqtt_config)
 | 
					 | 
				
			||||||
    ):
 | 
					 | 
				
			||||||
        options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
 | 
					 | 
				
			||||||
        if default == "OTA":
 | 
					        if default == "OTA":
 | 
				
			||||||
            return "MQTT"
 | 
					            return "MQTT"
 | 
				
			||||||
    if default is not None:
 | 
					    if default is not None:
 | 
				
			||||||
@@ -114,17 +106,6 @@ def choose_upload_log_host(
 | 
				
			|||||||
    return choose_prompt(options, purpose=purpose)
 | 
					    return choose_prompt(options, purpose=purpose)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def mqtt_logging_enabled(mqtt_config):
 | 
					 | 
				
			||||||
    log_topic = mqtt_config[CONF_LOG_TOPIC]
 | 
					 | 
				
			||||||
    if log_topic is None:
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
    if CONF_TOPIC not in log_topic:
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
    if log_topic.get(CONF_LEVEL, None) == "NONE":
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
    return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_port_type(port):
 | 
					def get_port_type(port):
 | 
				
			||||||
    if port.startswith("/") or port.startswith("COM"):
 | 
					    if port.startswith("/") or port.startswith("COM"):
 | 
				
			||||||
        return "SERIAL"
 | 
					        return "SERIAL"
 | 
				
			||||||
@@ -133,7 +114,7 @@ def get_port_type(port):
 | 
				
			|||||||
    return "NETWORK"
 | 
					    return "NETWORK"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run_miniterm(config, port, args):
 | 
					def run_miniterm(config, port):
 | 
				
			||||||
    import serial
 | 
					    import serial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from esphome import platformio_api
 | 
					    from esphome import platformio_api
 | 
				
			||||||
@@ -154,7 +135,7 @@ def run_miniterm(config, port, args):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    # We can't set to False by default since it leads to toggling and hence
 | 
					    # We can't set to False by default since it leads to toggling and hence
 | 
				
			||||||
    # ESP32 resets on some platforms.
 | 
					    # ESP32 resets on some platforms.
 | 
				
			||||||
    if config["logger"][CONF_DEASSERT_RTS_DTR] or args.reset:
 | 
					    if config["logger"][CONF_DEASSERT_RTS_DTR]:
 | 
				
			||||||
        ser.dtr = False
 | 
					        ser.dtr = False
 | 
				
			||||||
        ser.rts = False
 | 
					        ser.rts = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -244,11 +225,11 @@ def compile_program(args, config):
 | 
				
			|||||||
    return 0 if idedata is not None else 1
 | 
					    return 0 if idedata is not None else 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def upload_using_esptool(config, port, file, speed):
 | 
					def upload_using_esptool(config, port, file):
 | 
				
			||||||
    from esphome import platformio_api
 | 
					    from esphome import platformio_api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    first_baudrate = speed or config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
					    first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
				
			||||||
        "upload_speed", os.getenv("ESPHOME_UPLOAD_SPEED", "460800")
 | 
					        "upload_speed", 460800
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if file is not None:
 | 
					    if file is not None:
 | 
				
			||||||
@@ -337,18 +318,11 @@ def check_permissions(port):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def upload_program(config, args, host):
 | 
					def upload_program(config, args, host):
 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        module = importlib.import_module("esphome.components." + CORE.target_platform)
 | 
					 | 
				
			||||||
        if getattr(module, "upload_program")(config, args, host):
 | 
					 | 
				
			||||||
            return 0
 | 
					 | 
				
			||||||
    except AttributeError:
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if get_port_type(host) == "SERIAL":
 | 
					    if get_port_type(host) == "SERIAL":
 | 
				
			||||||
        check_permissions(host)
 | 
					        check_permissions(host)
 | 
				
			||||||
        if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
 | 
					        if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
 | 
				
			||||||
            file = getattr(args, "file", None)
 | 
					            file = getattr(args, "file", None)
 | 
				
			||||||
            return upload_using_esptool(config, host, file, args.upload_speed)
 | 
					            return upload_using_esptool(config, host, file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if CORE.target_platform in (PLATFORM_RP2040):
 | 
					        if CORE.target_platform in (PLATFORM_RP2040):
 | 
				
			||||||
            return upload_using_platformio(config, args.device)
 | 
					            return upload_using_platformio(config, args.device)
 | 
				
			||||||
@@ -371,16 +345,14 @@ def upload_program(config, args, host):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    from esphome import espota2
 | 
					    from esphome import espota2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    remote_port = int(ota_conf[CONF_PORT])
 | 
					    remote_port = ota_conf[CONF_PORT]
 | 
				
			||||||
    password = ota_conf.get(CONF_PASSWORD, "")
 | 
					    password = ota_conf.get(CONF_PASSWORD, "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
        CONF_MQTT in config  # pylint: disable=too-many-boolean-expressions
 | 
					        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"))
 | 
					        and (not args.device or args.device in ("MQTT", "OTA"))
 | 
				
			||||||
        and (
 | 
					 | 
				
			||||||
            ((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
 | 
					 | 
				
			||||||
            or get_port_type(host) == "MQTT"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        from esphome import mqtt
 | 
					        from esphome import mqtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -399,14 +371,14 @@ def show_logs(config, args, port):
 | 
				
			|||||||
        raise EsphomeError("Logger is not configured!")
 | 
					        raise EsphomeError("Logger is not configured!")
 | 
				
			||||||
    if get_port_type(port) == "SERIAL":
 | 
					    if get_port_type(port) == "SERIAL":
 | 
				
			||||||
        check_permissions(port)
 | 
					        check_permissions(port)
 | 
				
			||||||
        return run_miniterm(config, port, args)
 | 
					        return run_miniterm(config, port)
 | 
				
			||||||
    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
					    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
				
			||||||
        if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
 | 
					        if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
 | 
				
			||||||
            from esphome import mqtt
 | 
					            from esphome import mqtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            port = mqtt.get_esphome_device_ip(
 | 
					            port = mqtt.get_esphome_device_ip(
 | 
				
			||||||
                config, args.username, args.password, args.client_id
 | 
					                config, args.username, args.password, args.client_id
 | 
				
			||||||
            )[0]
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        from esphome.components.api.client import run_logs
 | 
					        from esphome.components.api.client import run_logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -596,30 +568,30 @@ def command_update_all(args):
 | 
				
			|||||||
        click.echo(f"{half_line}{middle_text}{half_line}")
 | 
					        click.echo(f"{half_line}{middle_text}{half_line}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for f in files:
 | 
					    for f in files:
 | 
				
			||||||
        print(f"Updating {color(AnsiFore.CYAN, f)}")
 | 
					        print(f"Updating {color(Fore.CYAN, f)}")
 | 
				
			||||||
        print("-" * twidth)
 | 
					        print("-" * twidth)
 | 
				
			||||||
        print()
 | 
					        print()
 | 
				
			||||||
        rc = run_external_process(
 | 
					        rc = run_external_process(
 | 
				
			||||||
            "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
 | 
					            "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        if rc == 0:
 | 
					        if rc == 0:
 | 
				
			||||||
            print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
 | 
					            print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
 | 
				
			||||||
            success[f] = True
 | 
					            success[f] = True
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
 | 
					            print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
 | 
				
			||||||
            success[f] = False
 | 
					            success[f] = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        print()
 | 
					        print()
 | 
				
			||||||
        print()
 | 
					        print()
 | 
				
			||||||
        print()
 | 
					        print()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]")
 | 
					    print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
 | 
				
			||||||
    failed = 0
 | 
					    failed = 0
 | 
				
			||||||
    for f in files:
 | 
					    for f in files:
 | 
				
			||||||
        if success[f]:
 | 
					        if success[f]:
 | 
				
			||||||
            print(f"  - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
 | 
					            print(f"  - {f}: {color(Fore.GREEN, 'SUCCESS')}")
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            print(f"  - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
 | 
					            print(f"  - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
 | 
				
			||||||
            failed += 1
 | 
					            failed += 1
 | 
				
			||||||
    return failed
 | 
					    return failed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -645,7 +617,7 @@ def command_rename(args, config):
 | 
				
			|||||||
        if c not in ALLOWED_NAME_CHARS:
 | 
					        if c not in ALLOWED_NAME_CHARS:
 | 
				
			||||||
            print(
 | 
					            print(
 | 
				
			||||||
                color(
 | 
					                color(
 | 
				
			||||||
                    AnsiFore.BOLD_RED,
 | 
					                    Fore.BOLD_RED,
 | 
				
			||||||
                    f"'{c}' is an invalid character for names. Valid characters are: "
 | 
					                    f"'{c}' is an invalid character for names. Valid characters are: "
 | 
				
			||||||
                    f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
 | 
					                    f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
@@ -658,9 +630,7 @@ def command_rename(args, config):
 | 
				
			|||||||
    yaml = yaml_util.load_yaml(CORE.config_path)
 | 
					    yaml = yaml_util.load_yaml(CORE.config_path)
 | 
				
			||||||
    if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
 | 
					    if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
 | 
				
			||||||
        print(
 | 
					        print(
 | 
				
			||||||
            color(
 | 
					            color(Fore.BOLD_RED, "Complex YAML files cannot be automatically renamed.")
 | 
				
			||||||
                AnsiFore.BOLD_RED, "Complex YAML files cannot be automatically renamed."
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return 1
 | 
					        return 1
 | 
				
			||||||
    old_name = yaml[CONF_ESPHOME][CONF_NAME]
 | 
					    old_name = yaml[CONF_ESPHOME][CONF_NAME]
 | 
				
			||||||
@@ -683,7 +653,7 @@ def command_rename(args, config):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
            > 1
 | 
					            > 1
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            print(color(AnsiFore.BOLD_RED, "Too many matches in YAML to safely rename"))
 | 
					            print(color(Fore.BOLD_RED, "Too many matches in YAML to safely rename"))
 | 
				
			||||||
            return 1
 | 
					            return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        new_raw = re.sub(
 | 
					        new_raw = re.sub(
 | 
				
			||||||
@@ -695,7 +665,7 @@ def command_rename(args, config):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
 | 
					    new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
 | 
				
			||||||
    print(
 | 
					    print(
 | 
				
			||||||
        f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
 | 
					        f"Updating {color(Fore.CYAN, CORE.config_path)} to {color(Fore.CYAN, new_path)}"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    print()
 | 
					    print()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -704,7 +674,7 @@ def command_rename(args, config):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    rc = run_external_process("esphome", "config", new_path)
 | 
					    rc = run_external_process("esphome", "config", new_path)
 | 
				
			||||||
    if rc != 0:
 | 
					    if rc != 0:
 | 
				
			||||||
        print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
 | 
					        print(color(Fore.BOLD_RED, "Rename failed. Reverting changes."))
 | 
				
			||||||
        os.remove(new_path)
 | 
					        os.remove(new_path)
 | 
				
			||||||
        return 1
 | 
					        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -730,7 +700,7 @@ def command_rename(args, config):
 | 
				
			|||||||
    if CORE.config_path != new_path:
 | 
					    if CORE.config_path != new_path:
 | 
				
			||||||
        os.remove(CORE.config_path)
 | 
					        os.remove(CORE.config_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
 | 
					    print(color(Fore.BOLD_GREEN, "SUCCESS"))
 | 
				
			||||||
    print()
 | 
					    print()
 | 
				
			||||||
    return 0
 | 
					    return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -770,14 +740,6 @@ def parse_args(argv):
 | 
				
			|||||||
    options_parser.add_argument(
 | 
					    options_parser.add_argument(
 | 
				
			||||||
        "-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
 | 
					        "-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    options_parser.add_argument(
 | 
					 | 
				
			||||||
        "-l",
 | 
					 | 
				
			||||||
        "--log-level",
 | 
					 | 
				
			||||||
        help="Set the log level.",
 | 
					 | 
				
			||||||
        default=os.getenv("ESPHOME_LOG_LEVEL", "INFO"),
 | 
					 | 
				
			||||||
        action="store",
 | 
					 | 
				
			||||||
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    options_parser.add_argument(
 | 
					    options_parser.add_argument(
 | 
				
			||||||
        "--dashboard", help=argparse.SUPPRESS, action="store_true"
 | 
					        "--dashboard", help=argparse.SUPPRESS, action="store_true"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@@ -846,10 +808,6 @@ def parse_args(argv):
 | 
				
			|||||||
        "--device",
 | 
					        "--device",
 | 
				
			||||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
					        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser_upload.add_argument(
 | 
					 | 
				
			||||||
        "--upload_speed",
 | 
					 | 
				
			||||||
        help="Override the default or configured upload speed.",
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    parser_upload.add_argument(
 | 
					    parser_upload.add_argument(
 | 
				
			||||||
        "--file",
 | 
					        "--file",
 | 
				
			||||||
        help="Manually specify the binary file to upload.",
 | 
					        help="Manually specify the binary file to upload.",
 | 
				
			||||||
@@ -868,13 +826,6 @@ def parse_args(argv):
 | 
				
			|||||||
        "--device",
 | 
					        "--device",
 | 
				
			||||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
					        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser_logs.add_argument(
 | 
					 | 
				
			||||||
        "--reset",
 | 
					 | 
				
			||||||
        "-r",
 | 
					 | 
				
			||||||
        action="store_true",
 | 
					 | 
				
			||||||
        help="Reset the device before starting serial logs.",
 | 
					 | 
				
			||||||
        default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser_discover = subparsers.add_parser(
 | 
					    parser_discover = subparsers.add_parser(
 | 
				
			||||||
        "discover",
 | 
					        "discover",
 | 
				
			||||||
@@ -897,20 +848,9 @@ def parse_args(argv):
 | 
				
			|||||||
        "--device",
 | 
					        "--device",
 | 
				
			||||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
					        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser_run.add_argument(
 | 
					 | 
				
			||||||
        "--upload_speed",
 | 
					 | 
				
			||||||
        help="Override the default or configured upload speed.",
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    parser_run.add_argument(
 | 
					    parser_run.add_argument(
 | 
				
			||||||
        "--no-logs", help="Disable starting logs.", action="store_true"
 | 
					        "--no-logs", help="Disable starting logs.", action="store_true"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser_run.add_argument(
 | 
					 | 
				
			||||||
        "--reset",
 | 
					 | 
				
			||||||
        "-r",
 | 
					 | 
				
			||||||
        action="store_true",
 | 
					 | 
				
			||||||
        help="Reset the device before starting serial logs.",
 | 
					 | 
				
			||||||
        default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser_clean = subparsers.add_parser(
 | 
					    parser_clean = subparsers.add_parser(
 | 
				
			||||||
        "clean-mqtt",
 | 
					        "clean-mqtt",
 | 
				
			||||||
@@ -1029,16 +969,11 @@ def run_esphome(argv):
 | 
				
			|||||||
    args = parse_args(argv)
 | 
					    args = parse_args(argv)
 | 
				
			||||||
    CORE.dashboard = args.dashboard
 | 
					    CORE.dashboard = args.dashboard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Override log level if verbose is set
 | 
					 | 
				
			||||||
    if args.verbose:
 | 
					 | 
				
			||||||
        args.log_level = "DEBUG"
 | 
					 | 
				
			||||||
    elif args.quiet:
 | 
					 | 
				
			||||||
        args.log_level = "CRITICAL"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setup_log(
 | 
					    setup_log(
 | 
				
			||||||
        log_level=args.log_level,
 | 
					        args.verbose,
 | 
				
			||||||
 | 
					        args.quiet,
 | 
				
			||||||
        # Show timestamp for dashboard access logs
 | 
					        # Show timestamp for dashboard access logs
 | 
				
			||||||
        include_timestamp=args.command == "dashboard",
 | 
					        args.command == "dashboard",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if args.command in PRE_CONFIG_ACTIONS:
 | 
					    if args.command in PRE_CONFIG_ACTIONS:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ALL,
 | 
					 | 
				
			||||||
    CONF_ANY,
 | 
					 | 
				
			||||||
    CONF_AUTOMATION_ID,
 | 
					    CONF_AUTOMATION_ID,
 | 
				
			||||||
    CONF_CONDITION,
 | 
					    CONF_CONDITION,
 | 
				
			||||||
    CONF_COUNT,
 | 
					    CONF_COUNT,
 | 
				
			||||||
@@ -75,13 +73,6 @@ def validate_potentially_and_condition(value):
 | 
				
			|||||||
    return validate_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)
 | 
					DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
 | 
				
			||||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
 | 
					LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
 | 
				
			||||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
 | 
					IfAction = cg.esphome_ns.class_("IfAction", Action)
 | 
				
			||||||
@@ -175,18 +166,6 @@ async def or_condition_to_code(config, condition_id, template_arg, args):
 | 
				
			|||||||
    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
					    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)
 | 
					@register_condition("not", NotCondition, validate_potentially_and_condition)
 | 
				
			||||||
async def not_condition_to_code(config, condition_id, template_arg, args):
 | 
					async def not_condition_to_code(config, condition_id, template_arg, args):
 | 
				
			||||||
    condition = await build_condition(config, template_arg, args)
 | 
					    condition = await build_condition(config, template_arg, args)
 | 
				
			||||||
@@ -244,21 +223,15 @@ async def delay_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
    IfAction,
 | 
					    IfAction,
 | 
				
			||||||
    cv.All(
 | 
					    cv.All(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.Exclusive(
 | 
					            cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
				
			||||||
                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_THEN): validate_action_list,
 | 
				
			||||||
            cv.Optional(CONF_ELSE): 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_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):
 | 
					async def if_action_to_code(config, action_id, 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[CONF_CONDITION], template_arg, args)
 | 
				
			||||||
    conditions = await build_condition(config[cond_conf], template_arg, args)
 | 
					 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
					    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
				
			||||||
    if CONF_THEN in config:
 | 
					    if CONF_THEN in config:
 | 
				
			||||||
        actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
					        actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import sensor, uart
 | 
					from esphome.components import sensor, uart
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    DEVICE_CLASS_DISTANCE,
 | 
					 | 
				
			||||||
    ICON_ARROW_EXPAND_VERTICAL,
 | 
					 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_METER,
 | 
					    UNIT_METER,
 | 
				
			||||||
 | 
					    ICON_ARROW_EXPAND_VERTICAL,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_DISTANCE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@MrSuicideParrot"]
 | 
					CODEOWNERS = ["@MrSuicideParrot"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import sensor, uart
 | 
					from esphome.components import sensor, uart
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    DEVICE_CLASS_DISTANCE,
 | 
					 | 
				
			||||||
    ICON_ARROW_EXPAND_VERTICAL,
 | 
					 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    ICON_ARROW_EXPAND_VERTICAL,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_DISTANCE,
 | 
				
			||||||
    UNIT_MILLIMETER,
 | 
					    UNIT_MILLIMETER,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
from esphome import pins
 | 
					from esphome import pins
 | 
				
			||||||
import esphome.codegen as cg
 | 
					 | 
				
			||||||
from esphome.components import stepper
 | 
					from esphome.components import stepper
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
 | 
					from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a4988_ns = cg.esphome_ns.namespace("a4988")
 | 
					a4988_ns = cg.esphome_ns.namespace("a4988")
 | 
				
			||||||
A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
 | 
					A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,12 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_EQUATION,
 | 
					 | 
				
			||||||
    CONF_HUMIDITY,
 | 
					    CONF_HUMIDITY,
 | 
				
			||||||
    CONF_TEMPERATURE,
 | 
					    CONF_TEMPERATURE,
 | 
				
			||||||
    ICON_WATER,
 | 
					 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    CONF_EQUATION,
 | 
				
			||||||
 | 
					    ICON_WATER,
 | 
				
			||||||
    UNIT_GRAMS_PER_CUBIC_METER,
 | 
					    UNIT_GRAMS_PER_CUBIC_METER,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -114,14 +114,13 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
 | 
				
			|||||||
    // fully off, disable output immediately
 | 
					    // fully off, disable output immediately
 | 
				
			||||||
    this->gate_pin.digital_write(false);
 | 
					    this->gate_pin.digital_write(false);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    auto min_us = this->cycle_time_us * this->min_power / 1000;
 | 
					 | 
				
			||||||
    if (this->method == DIM_METHOD_TRAILING) {
 | 
					    if (this->method == DIM_METHOD_TRAILING) {
 | 
				
			||||||
      this->enable_time_us = 1;  // cannot be 0
 | 
					      this->enable_time_us = 1;  // cannot be 0
 | 
				
			||||||
      // calculate time until disable in µs with integer arithmetic and take into account min_power
 | 
					      this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
 | 
				
			||||||
      this->disable_time_us = std::max((uint32_t) 10, this->value * (this->cycle_time_us - min_us) / 65535 + min_us);
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
 | 
					      // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
 | 
				
			||||||
      // also take into account min_power
 | 
					      // also take into account min_power
 | 
				
			||||||
 | 
					      auto min_us = this->cycle_time_us * this->min_power / 1000;
 | 
				
			||||||
      this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
 | 
					      this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this->method == DIM_METHOD_LEADING_PULSE) {
 | 
					      if (this->method == DIM_METHOD_LEADING_PULSE) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
from esphome import pins
 | 
					 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import output
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_ID, CONF_METHOD, CONF_MIN_POWER
 | 
					from esphome import pins
 | 
				
			||||||
 | 
					from esphome.components import output
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@glmnet"]
 | 
					CODEOWNERS = ["@glmnet"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import uart
 | 
					 | 
				
			||||||
from esphome.components.light.effects import register_addressable_effect
 | 
					 | 
				
			||||||
from esphome.components.light.types import AddressableLightEffect
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import uart
 | 
				
			||||||
 | 
					from esphome.components.light.types import AddressableLightEffect
 | 
				
			||||||
 | 
					from esphome.components.light.effects import register_addressable_effect
 | 
				
			||||||
from esphome.const import CONF_NAME, CONF_UART_ID
 | 
					from esphome.const import CONF_NAME, CONF_UART_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["uart"]
 | 
					DEPENDENCIES = ["uart"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,11 @@
 | 
				
			|||||||
from esphome import pins
 | 
					 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome import pins
 | 
				
			||||||
 | 
					from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from esphome.core import CORE
 | 
				
			||||||
from esphome.components.esp32 import get_esp32_variant
 | 
					from esphome.components.esp32 import get_esp32_variant
 | 
				
			||||||
 | 
					from esphome.const import PLATFORM_ESP8266
 | 
				
			||||||
from esphome.components.esp32.const import (
 | 
					from esphome.components.esp32.const import (
 | 
				
			||||||
    VARIANT_ESP32,
 | 
					    VARIANT_ESP32,
 | 
				
			||||||
    VARIANT_ESP32C2,
 | 
					    VARIANT_ESP32C2,
 | 
				
			||||||
@@ -10,9 +15,6 @@ from esphome.components.esp32.const import (
 | 
				
			|||||||
    VARIANT_ESP32S2,
 | 
					    VARIANT_ESP32S2,
 | 
				
			||||||
    VARIANT_ESP32S3,
 | 
					    VARIANT_ESP32S3,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					 | 
				
			||||||
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
 | 
					 | 
				
			||||||
from esphome.core import CORE
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@esphome/core"]
 | 
					CODEOWNERS = ["@esphome/core"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,21 +38,12 @@ ATTENUATION_MODES = {
 | 
				
			|||||||
    "auto": "auto",
 | 
					    "auto": "auto",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sampling_mode = adc_ns.enum("SamplingMode", is_class=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SAMPLING_MODES = {
 | 
					 | 
				
			||||||
    "avg": sampling_mode.AVG,
 | 
					 | 
				
			||||||
    "min": sampling_mode.MIN,
 | 
					 | 
				
			||||||
    "max": sampling_mode.MAX,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
					adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
				
			||||||
adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
 | 
					adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
				
			||||||
# pin to adc1 channel mapping
 | 
					# pin to adc1 channel mapping
 | 
				
			||||||
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
 | 
					 | 
				
			||||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
					ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32: {
 | 
					    VARIANT_ESP32: {
 | 
				
			||||||
        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
@@ -61,41 +54,6 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
				
			|||||||
        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
					        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
				
			||||||
        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
					        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32C2: {
 | 
					 | 
				
			||||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
					 | 
				
			||||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
					 | 
				
			||||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32C3: {
 | 
					 | 
				
			||||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
					 | 
				
			||||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
					 | 
				
			||||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32C6: {
 | 
					 | 
				
			||||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
					 | 
				
			||||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
					 | 
				
			||||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
					 | 
				
			||||||
        5: adc1_channel_t.ADC1_CHANNEL_5,
 | 
					 | 
				
			||||||
        6: adc1_channel_t.ADC1_CHANNEL_6,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32H2: {
 | 
					 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					 | 
				
			||||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
					 | 
				
			||||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
					 | 
				
			||||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32S2: {
 | 
					    VARIANT_ESP32S2: {
 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
@@ -108,7 +66,6 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
				
			|||||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
					        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
				
			||||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
					        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32S3: {
 | 
					    VARIANT_ESP32S3: {
 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
@@ -121,12 +78,40 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
				
			|||||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
					        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
				
			||||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
					        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3: {
 | 
				
			||||||
 | 
					        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32C2: {
 | 
				
			||||||
 | 
					        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32C6: {
 | 
				
			||||||
 | 
					        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					        5: adc1_channel_t.ADC1_CHANNEL_5,
 | 
				
			||||||
 | 
					        6: adc1_channel_t.ADC1_CHANNEL_6,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32H2: {
 | 
				
			||||||
 | 
					        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# pin to adc2 channel mapping
 | 
					 | 
				
			||||||
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
 | 
					 | 
				
			||||||
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
					ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
 | 
					    # TODO: add other variants
 | 
				
			||||||
    VARIANT_ESP32: {
 | 
					    VARIANT_ESP32: {
 | 
				
			||||||
        4: adc2_channel_t.ADC2_CHANNEL_0,
 | 
					        4: adc2_channel_t.ADC2_CHANNEL_0,
 | 
				
			||||||
        0: adc2_channel_t.ADC2_CHANNEL_1,
 | 
					        0: adc2_channel_t.ADC2_CHANNEL_1,
 | 
				
			||||||
@@ -139,19 +124,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
				
			|||||||
        25: adc2_channel_t.ADC2_CHANNEL_8,
 | 
					        25: adc2_channel_t.ADC2_CHANNEL_8,
 | 
				
			||||||
        26: adc2_channel_t.ADC2_CHANNEL_9,
 | 
					        26: adc2_channel_t.ADC2_CHANNEL_9,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32C2: {
 | 
					 | 
				
			||||||
        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32C3: {
 | 
					 | 
				
			||||||
        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32C6: {},  # no ADC2
 | 
					 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32H2: {},  # no ADC2
 | 
					 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32S2: {
 | 
					    VARIANT_ESP32S2: {
 | 
				
			||||||
        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
					        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
				
			||||||
        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
					        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
				
			||||||
@@ -164,7 +136,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
				
			|||||||
        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
					        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
				
			||||||
        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
					        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
 | 
					 | 
				
			||||||
    VARIANT_ESP32S3: {
 | 
					    VARIANT_ESP32S3: {
 | 
				
			||||||
        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
					        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
				
			||||||
        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
					        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
				
			||||||
@@ -177,6 +148,12 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
				
			|||||||
        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
					        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
				
			||||||
        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
					        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3: {
 | 
				
			||||||
 | 
					        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32C2: {},
 | 
				
			||||||
 | 
					    VARIANT_ESP32C6: {},
 | 
				
			||||||
 | 
					    VARIANT_ESP32H2: {},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,30 @@
 | 
				
			|||||||
#ifdef USE_ESP32
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "adc_sensor.h"
 | 
					#include "adc_sensor.h"
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					#include <Esp.h>
 | 
				
			||||||
 | 
					ADC_MODE(ADC_VCC)
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					#include <Arduino.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					#ifdef CYW43_USES_VSYS_PIN
 | 
				
			||||||
 | 
					#include "pico/cyw43_arch.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#include <hardware/adc.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace adc {
 | 
					namespace adc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "adc.esp32";
 | 
					static const char *const TAG = "adc";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 13-bit for S2, 12-bit for all other ESP32 variants
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
 | 
					static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef SOC_ADC_RTC_MAX_BITWIDTH
 | 
					#ifndef SOC_ADC_RTC_MAX_BITWIDTH
 | 
				
			||||||
@@ -15,15 +32,24 @@ static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_widt
 | 
				
			|||||||
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
 | 
					static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
 | 
					static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
 | 
				
			||||||
#endif  // USE_ESP32_VARIANT_ESP32S2
 | 
					#endif
 | 
				
			||||||
#endif  // SOC_ADC_RTC_MAX_BITWIDTH
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;
 | 
					static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;    // 4095 (12 bit) or 8191 (13 bit)
 | 
				
			||||||
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;
 | 
					static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;  // 2048 (12 bit) or 4096 (13 bit)
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ADCSensor::setup() {
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					extern "C"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    void
 | 
				
			||||||
 | 
					    ADCSensor::setup() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
					  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
				
			||||||
 | 
					#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
 | 
				
			||||||
 | 
					  this->pin_->setup();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
  if (this->channel1_ != ADC1_CHANNEL_MAX) {
 | 
					  if (this->channel1_ != ADC1_CHANNEL_MAX) {
 | 
				
			||||||
    adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
 | 
					    adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
 | 
				
			||||||
    if (!this->autorange_) {
 | 
					    if (!this->autorange_) {
 | 
				
			||||||
@@ -35,6 +61,7 @@ void ADCSensor::setup() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // load characteristics for each attenuation
 | 
				
			||||||
  for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) {
 | 
					  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 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,
 | 
					    auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
 | 
				
			||||||
@@ -52,10 +79,31 @@ void ADCSensor::setup() {
 | 
				
			|||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					  static bool initialized = false;
 | 
				
			||||||
 | 
					  if (!initialized) {
 | 
				
			||||||
 | 
					    adc_init();
 | 
				
			||||||
 | 
					    initialized = true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ADCSensor::dump_config() {
 | 
					void ADCSensor::dump_config() {
 | 
				
			||||||
  LOG_SENSOR("", "ADC Sensor", this);
 | 
					  LOG_SENSOR("", "ADC Sensor", this);
 | 
				
			||||||
 | 
					#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
 | 
				
			||||||
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Pin: VCC");
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  LOG_PIN("  Pin: ", this->pin_);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#endif  // USE_ESP8266 || USE_LIBRETINY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
  LOG_PIN("  Pin: ", this->pin_);
 | 
					  LOG_PIN("  Pin: ", this->pin_);
 | 
				
			||||||
  if (this->autorange_) {
 | 
					  if (this->autorange_) {
 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "  Attenuation: auto");
 | 
					    ESP_LOGCONFIG(TAG, "  Attenuation: auto");
 | 
				
			||||||
@@ -77,15 +125,58 @@ void ADCSensor::dump_config() {
 | 
				
			|||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					  if (this->is_temperature_) {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "  Pin: Temperature");
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "  Pin: VCC");
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					    LOG_PIN("  Pin: ", this->pin_);
 | 
				
			||||||
 | 
					#endif  // USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif  // USE_RP2040
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_);
 | 
					  ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
 | 
					 | 
				
			||||||
  LOG_UPDATE_INTERVAL(this);
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
 | 
				
			||||||
 | 
					void ADCSensor::update() {
 | 
				
			||||||
 | 
					  float value_v = this->sample();
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
 | 
				
			||||||
 | 
					  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
 | 
				
			||||||
 | 
					    raw += ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance)
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					    raw += analogRead(this->pin_->get_pin());  // NOLINT
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero)
 | 
				
			||||||
 | 
					  if (this->output_raw_) {
 | 
				
			||||||
 | 
					    return raw;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return raw / 1024.0f;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
float ADCSensor::sample() {
 | 
					float ADCSensor::sample() {
 | 
				
			||||||
  if (!this->autorange_) {
 | 
					  if (!this->autorange_) {
 | 
				
			||||||
    auto aggr = Aggregator(this->sampling_mode_);
 | 
					    uint32_t sum = 0;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
 | 
					    for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
 | 
				
			||||||
      int raw = -1;
 | 
					      int raw = -1;
 | 
				
			||||||
      if (this->channel1_ != ADC1_CHANNEL_MAX) {
 | 
					      if (this->channel1_ != ADC1_CHANNEL_MAX) {
 | 
				
			||||||
@@ -96,14 +187,13 @@ float ADCSensor::sample() {
 | 
				
			|||||||
      if (raw == -1) {
 | 
					      if (raw == -1) {
 | 
				
			||||||
        return NAN;
 | 
					        return NAN;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      sum += raw;
 | 
				
			||||||
      aggr.add_sample(raw);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    sum = (sum + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero)
 | 
				
			||||||
    if (this->output_raw_) {
 | 
					    if (this->output_raw_) {
 | 
				
			||||||
      return aggr.aggregate();
 | 
					      return sum;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    uint32_t mv =
 | 
					    uint32_t mv = esp_adc_cal_raw_to_voltage(sum, &this->cal_characteristics_[(int32_t) this->attenuation_]);
 | 
				
			||||||
        esp_adc_cal_raw_to_voltage(aggr.aggregate(), &this->cal_characteristics_[(int32_t) this->attenuation_]);
 | 
					 | 
				
			||||||
    return mv / 1000.0f;
 | 
					    return mv / 1000.0f;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -150,17 +240,93 @@ float ADCSensor::sample() {
 | 
				
			|||||||
  uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
 | 
					  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]);
 | 
					  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 c12 = std::min(raw12, ADC_HALF);
 | 
					  uint32_t c12 = std::min(raw12, ADC_HALF);
 | 
				
			||||||
  uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
 | 
					  uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
 | 
				
			||||||
  uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
 | 
					  uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
 | 
				
			||||||
  uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF);
 | 
					  uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF);
 | 
				
			||||||
 | 
					  // max theoretical csum value is 4096*4 = 16384
 | 
				
			||||||
  uint32_t csum = c12 + 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 = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
 | 
					  uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
 | 
				
			||||||
  return mv_scaled / (float) (csum * 1000U);
 | 
					  return mv_scaled / (float) (csum * 1000U);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					float ADCSensor::sample() {
 | 
				
			||||||
 | 
					  if (this->is_temperature_) {
 | 
				
			||||||
 | 
					    adc_set_temp_sensor_enabled(true);
 | 
				
			||||||
 | 
					    delay(1);
 | 
				
			||||||
 | 
					    adc_select_input(4);
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return raw * 3.3f / 4096.0f;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    uint8_t pin = this->pin_->get_pin();
 | 
				
			||||||
 | 
					#ifdef CYW43_USES_VSYS_PIN
 | 
				
			||||||
 | 
					    if (pin == PICO_VSYS_PIN) {
 | 
				
			||||||
 | 
					      // Measuring VSYS on Raspberry Pico W needs to be wrapped with
 | 
				
			||||||
 | 
					      // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
 | 
				
			||||||
 | 
					      // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
 | 
				
			||||||
 | 
					      // VSYS ADC both share GPIO29
 | 
				
			||||||
 | 
					      cyw43_thread_enter();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif  // CYW43_USES_VSYS_PIN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    adc_gpio_init(pin);
 | 
				
			||||||
 | 
					    adc_select_input(pin - 26);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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) {
 | 
				
			||||||
 | 
					      cyw43_thread_exit();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif  // CYW43_USES_VSYS_PIN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this->output_raw_) {
 | 
				
			||||||
 | 
					      return raw;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0;
 | 
				
			||||||
 | 
					    return raw * 3.3f / 4096.0f * coeff;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_LIBRETINY
 | 
				
			||||||
 | 
					float ADCSensor::sample() {
 | 
				
			||||||
 | 
					  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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace adc
 | 
					}  // namespace adc
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_ESP32
 | 
					 | 
				
			||||||
@@ -3,12 +3,13 @@
 | 
				
			|||||||
#include "esphome/components/sensor/sensor.h"
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
 | 
					#include "esphome/components/voltage_sampler/voltage_sampler.h"
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
#include "esphome/core/hal.h"
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
#include <esp_adc_cal.h>
 | 
					#include <esp_adc_cal.h>
 | 
				
			||||||
#include "driver/adc.h"
 | 
					#include "driver/adc.h"
 | 
				
			||||||
#endif  // USE_ESP32
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace adc {
 | 
					namespace adc {
 | 
				
			||||||
@@ -28,21 +29,6 @@ static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11;
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
#endif  // USE_ESP32
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum class SamplingMode : uint8_t { AVG = 0, MIN = 1, MAX = 2 };
 | 
					 | 
				
			||||||
const LogString *sampling_mode_to_str(SamplingMode mode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Aggregator {
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
  void add_sample(uint32_t value);
 | 
					 | 
				
			||||||
  uint32_t aggregate();
 | 
					 | 
				
			||||||
  Aggregator(SamplingMode mode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  SamplingMode mode_{SamplingMode::AVG};
 | 
					 | 
				
			||||||
  uint32_t aggr_{0};
 | 
					 | 
				
			||||||
  uint32_t samples_{0};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
 | 
					class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
#ifdef USE_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
@@ -57,7 +43,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
				
			|||||||
    this->channel1_ = ADC1_CHANNEL_MAX;
 | 
					    this->channel1_ = ADC1_CHANNEL_MAX;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  void set_autorange(bool autorange) { this->autorange_ = autorange; }
 | 
					  void set_autorange(bool autorange) { this->autorange_ = autorange; }
 | 
				
			||||||
#endif  // USE_ESP32
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Update ADC values
 | 
					  /// Update ADC values
 | 
				
			||||||
  void update() override;
 | 
					  void update() override;
 | 
				
			||||||
@@ -69,26 +55,24 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
				
			|||||||
  void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
 | 
					  void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
 | 
				
			||||||
  void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; }
 | 
					  void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; }
 | 
				
			||||||
  void set_sample_count(uint8_t sample_count);
 | 
					  void set_sample_count(uint8_t sample_count);
 | 
				
			||||||
  void set_sampling_mode(SamplingMode sampling_mode);
 | 
					 | 
				
			||||||
  float sample() override;
 | 
					  float sample() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
  std::string unique_id() override;
 | 
					  std::string unique_id() override;
 | 
				
			||||||
#endif  // USE_ESP8266
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_RP2040
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
  void set_is_temperature() { this->is_temperature_ = true; }
 | 
					  void set_is_temperature() { this->is_temperature_ = true; }
 | 
				
			||||||
#endif  // USE_RP2040
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  InternalGPIOPin *pin_;
 | 
					  InternalGPIOPin *pin_;
 | 
				
			||||||
  bool output_raw_{false};
 | 
					  bool output_raw_{false};
 | 
				
			||||||
  uint8_t sample_count_{1};
 | 
					  uint8_t sample_count_{1};
 | 
				
			||||||
  SamplingMode sampling_mode_{SamplingMode::AVG};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_RP2040
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
  bool is_temperature_{false};
 | 
					  bool is_temperature_{false};
 | 
				
			||||||
#endif  // USE_RP2040
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
					  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
				
			||||||
@@ -99,8 +83,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
				
			|||||||
  esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
 | 
					  esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
  esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
 | 
					  esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
 | 
				
			||||||
#endif  // ESP_IDF_VERSION_MAJOR
 | 
					#endif
 | 
				
			||||||
#endif  // USE_ESP32
 | 
					#endif
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace adc
 | 
					}  // namespace adc
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,79 +0,0 @@
 | 
				
			|||||||
#include "adc_sensor.h"
 | 
					 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace esphome {
 | 
					 | 
				
			||||||
namespace adc {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *const TAG = "adc.common";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const LogString *sampling_mode_to_str(SamplingMode mode) {
 | 
					 | 
				
			||||||
  switch (mode) {
 | 
					 | 
				
			||||||
    case SamplingMode::AVG:
 | 
					 | 
				
			||||||
      return LOG_STR("average");
 | 
					 | 
				
			||||||
    case SamplingMode::MIN:
 | 
					 | 
				
			||||||
      return LOG_STR("minimum");
 | 
					 | 
				
			||||||
    case SamplingMode::MAX:
 | 
					 | 
				
			||||||
      return LOG_STR("maximum");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return LOG_STR("unknown");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Aggregator::Aggregator(SamplingMode mode) {
 | 
					 | 
				
			||||||
  this->mode_ = mode;
 | 
					 | 
				
			||||||
  // set to max uint if mode is "min"
 | 
					 | 
				
			||||||
  if (mode == SamplingMode::MIN) {
 | 
					 | 
				
			||||||
    this->aggr_ = UINT32_MAX;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Aggregator::add_sample(uint32_t value) {
 | 
					 | 
				
			||||||
  this->samples_ += 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  switch (this->mode_) {
 | 
					 | 
				
			||||||
    case SamplingMode::AVG:
 | 
					 | 
				
			||||||
      this->aggr_ += value;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    case SamplingMode::MIN:
 | 
					 | 
				
			||||||
      if (value < this->aggr_) {
 | 
					 | 
				
			||||||
        this->aggr_ = value;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    case SamplingMode::MAX:
 | 
					 | 
				
			||||||
      if (value > this->aggr_) {
 | 
					 | 
				
			||||||
        this->aggr_ = value;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
uint32_t Aggregator::aggregate() {
 | 
					 | 
				
			||||||
  if (this->mode_ == SamplingMode::AVG) {
 | 
					 | 
				
			||||||
    if (this->samples_ == 0) {
 | 
					 | 
				
			||||||
      return this->aggr_;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (this->aggr_ + (this->samples_ >> 1)) / this->samples_;  // NOLINT(clang-analyzer-core.DivideZero)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return this->aggr_;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ADCSensor::update() {
 | 
					 | 
				
			||||||
  float value_v = this->sample();
 | 
					 | 
				
			||||||
  ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
 | 
					 | 
				
			||||||
  this->publish_state(value_v);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ADCSensor::set_sample_count(uint8_t sample_count) {
 | 
					 | 
				
			||||||
  if (sample_count != 0) {
 | 
					 | 
				
			||||||
    this->sample_count_ = sample_count;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ADCSensor::set_sampling_mode(SamplingMode sampling_mode) { this->sampling_mode_ = sampling_mode; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}  // namespace adc
 | 
					 | 
				
			||||||
}  // namespace esphome
 | 
					 | 
				
			||||||
@@ -1,62 +0,0 @@
 | 
				
			|||||||
#ifdef USE_ESP8266
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "adc_sensor.h"
 | 
					 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
#include <Esp.h>
 | 
					 | 
				
			||||||
ADC_MODE(ADC_VCC)
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#endif  // USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace esphome {
 | 
					 | 
				
			||||||
namespace adc {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *const TAG = "adc.esp8266";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ADCSensor::setup() {
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
					 | 
				
			||||||
#ifndef USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
  this->pin_->setup();
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ADCSensor::dump_config() {
 | 
					 | 
				
			||||||
  LOG_SENSOR("", "ADC Sensor", this);
 | 
					 | 
				
			||||||
#ifdef USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Pin: VCC");
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
  LOG_PIN("  Pin: ", this->pin_);
 | 
					 | 
				
			||||||
#endif  // USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_);
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
 | 
					 | 
				
			||||||
  LOG_UPDATE_INTERVAL(this);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
float ADCSensor::sample() {
 | 
					 | 
				
			||||||
  auto aggr = Aggregator(this->sampling_mode_);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
 | 
					 | 
				
			||||||
    uint32_t raw = 0;
 | 
					 | 
				
			||||||
#ifdef USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
    raw = ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance)
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
    raw = analogRead(this->pin_->get_pin());  // NOLINT
 | 
					 | 
				
			||||||
#endif  // USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
    aggr.add_sample(raw);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (this->output_raw_) {
 | 
					 | 
				
			||||||
    return aggr.aggregate();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return aggr.aggregate() / 1024.0f;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}  // namespace adc
 | 
					 | 
				
			||||||
}  // namespace esphome
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_ESP8266
 | 
					 | 
				
			||||||
@@ -1,53 +0,0 @@
 | 
				
			|||||||
#ifdef USE_LIBRETINY
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "adc_sensor.h"
 | 
					 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace esphome {
 | 
					 | 
				
			||||||
namespace adc {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *const TAG = "adc.libretiny";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ADCSensor::setup() {
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
					 | 
				
			||||||
#ifndef USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
  this->pin_->setup();
 | 
					 | 
				
			||||||
#endif  // !USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ADCSensor::dump_config() {
 | 
					 | 
				
			||||||
  LOG_SENSOR("", "ADC Sensor", this);
 | 
					 | 
				
			||||||
#ifdef USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Pin: VCC");
 | 
					 | 
				
			||||||
#else   // USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
  LOG_PIN("  Pin: ", this->pin_);
 | 
					 | 
				
			||||||
#endif  // USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_);
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
 | 
					 | 
				
			||||||
  LOG_UPDATE_INTERVAL(this);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
float ADCSensor::sample() {
 | 
					 | 
				
			||||||
  uint32_t raw = 0;
 | 
					 | 
				
			||||||
  auto aggr = Aggregator(this->sampling_mode_);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (this->output_raw_) {
 | 
					 | 
				
			||||||
    for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
 | 
					 | 
				
			||||||
      raw = analogRead(this->pin_->get_pin());  // NOLINT
 | 
					 | 
				
			||||||
      aggr.add_sample(raw);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return aggr.aggregate();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
 | 
					 | 
				
			||||||
    raw = analogReadVoltage(this->pin_->get_pin());  // NOLINT
 | 
					 | 
				
			||||||
    aggr.add_sample(raw);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return aggr.aggregate() / 1000.0f;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}  // namespace adc
 | 
					 | 
				
			||||||
}  // namespace esphome
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_LIBRETINY
 | 
					 | 
				
			||||||
@@ -1,96 +0,0 @@
 | 
				
			|||||||
#ifdef USE_RP2040
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "adc_sensor.h"
 | 
					 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef CYW43_USES_VSYS_PIN
 | 
					 | 
				
			||||||
#include "pico/cyw43_arch.h"
 | 
					 | 
				
			||||||
#endif  // CYW43_USES_VSYS_PIN
 | 
					 | 
				
			||||||
#include <hardware/adc.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace esphome {
 | 
					 | 
				
			||||||
namespace adc {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *const TAG = "adc.rp2040";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ADCSensor::setup() {
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
					 | 
				
			||||||
  static bool initialized = false;
 | 
					 | 
				
			||||||
  if (!initialized) {
 | 
					 | 
				
			||||||
    adc_init();
 | 
					 | 
				
			||||||
    initialized = true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ADCSensor::dump_config() {
 | 
					 | 
				
			||||||
  LOG_SENSOR("", "ADC Sensor", this);
 | 
					 | 
				
			||||||
  if (this->is_temperature_) {
 | 
					 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "  Pin: Temperature");
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
#ifdef USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "  Pin: VCC");
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
    LOG_PIN("  Pin: ", this->pin_);
 | 
					 | 
				
			||||||
#endif  // USE_ADC_SENSOR_VCC
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_);
 | 
					 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
 | 
					 | 
				
			||||||
  LOG_UPDATE_INTERVAL(this);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
float ADCSensor::sample() {
 | 
					 | 
				
			||||||
  uint32_t raw = 0;
 | 
					 | 
				
			||||||
  auto aggr = Aggregator(this->sampling_mode_);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (this->is_temperature_) {
 | 
					 | 
				
			||||||
    adc_set_temp_sensor_enabled(true);
 | 
					 | 
				
			||||||
    delay(1);
 | 
					 | 
				
			||||||
    adc_select_input(4);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
 | 
					 | 
				
			||||||
      raw = adc_read();
 | 
					 | 
				
			||||||
      aggr.add_sample(raw);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    adc_set_temp_sensor_enabled(false);
 | 
					 | 
				
			||||||
    if (this->output_raw_) {
 | 
					 | 
				
			||||||
      return aggr.aggregate();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return aggr.aggregate() * 3.3f / 4096.0f;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  uint8_t pin = this->pin_->get_pin();
 | 
					 | 
				
			||||||
#ifdef CYW43_USES_VSYS_PIN
 | 
					 | 
				
			||||||
  if (pin == PICO_VSYS_PIN) {
 | 
					 | 
				
			||||||
    // Measuring VSYS on Raspberry Pico W needs to be wrapped with
 | 
					 | 
				
			||||||
    // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
 | 
					 | 
				
			||||||
    // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
 | 
					 | 
				
			||||||
    // VSYS ADC both share GPIO29
 | 
					 | 
				
			||||||
    cyw43_thread_enter();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#endif  // CYW43_USES_VSYS_PIN
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  adc_gpio_init(pin);
 | 
					 | 
				
			||||||
  adc_select_input(pin - 26);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
 | 
					 | 
				
			||||||
    raw = adc_read();
 | 
					 | 
				
			||||||
    aggr.add_sample(raw);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef CYW43_USES_VSYS_PIN
 | 
					 | 
				
			||||||
  if (pin == PICO_VSYS_PIN) {
 | 
					 | 
				
			||||||
    cyw43_thread_exit();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#endif  // CYW43_USES_VSYS_PIN
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (this->output_raw_) {
 | 
					 | 
				
			||||||
    return aggr.aggregate();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  float coeff = pin == PICO_VSYS_PIN ? 3.0f : 1.0f;
 | 
					 | 
				
			||||||
  return aggr.aggregate() * 3.3f / 4096.0f * coeff;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}  // namespace adc
 | 
					 | 
				
			||||||
}  // namespace esphome
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_RP2040
 | 
					 | 
				
			||||||
@@ -1,9 +1,11 @@
 | 
				
			|||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					import esphome.final_validate as fv
 | 
				
			||||||
 | 
					from esphome.core import CORE
 | 
				
			||||||
from esphome.components import sensor, voltage_sampler
 | 
					from esphome.components import sensor, voltage_sampler
 | 
				
			||||||
from esphome.components.esp32 import get_esp32_variant
 | 
					from esphome.components.esp32 import get_esp32_variant
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ATTENUATION,
 | 
					    CONF_ATTENUATION,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
@@ -15,14 +17,10 @@ from esphome.const import (
 | 
				
			|||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE
 | 
					 | 
				
			||||||
import esphome.final_validate as fv
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from . import (
 | 
					from . import (
 | 
				
			||||||
    ATTENUATION_MODES,
 | 
					    ATTENUATION_MODES,
 | 
				
			||||||
    ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
 | 
					    ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
 | 
				
			||||||
    ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
 | 
					    ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
 | 
				
			||||||
    SAMPLING_MODES,
 | 
					 | 
				
			||||||
    adc_ns,
 | 
					    adc_ns,
 | 
				
			||||||
    validate_adc_pin,
 | 
					    validate_adc_pin,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -32,11 +30,9 @@ _LOGGER = logging.getLogger(__name__)
 | 
				
			|||||||
AUTO_LOAD = ["voltage_sampler"]
 | 
					AUTO_LOAD = ["voltage_sampler"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_SAMPLES = "samples"
 | 
					CONF_SAMPLES = "samples"
 | 
				
			||||||
CONF_SAMPLING_MODE = "sampling_mode"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_attenuation = cv.enum(ATTENUATION_MODES, lower=True)
 | 
					_attenuation = cv.enum(ATTENUATION_MODES, lower=True)
 | 
				
			||||||
_sampling_mode = cv.enum(SAMPLING_MODES, lower=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_config(config):
 | 
					def validate_config(config):
 | 
				
			||||||
@@ -92,7 +88,6 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
                cv.only_on_esp32, _attenuation
 | 
					                cv.only_on_esp32, _attenuation
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_SAMPLES, default=1): cv.int_range(min=1, max=255),
 | 
					            cv.Optional(CONF_SAMPLES, default=1): cv.int_range(min=1, max=255),
 | 
				
			||||||
            cv.Optional(CONF_SAMPLING_MODE, default="avg"): _sampling_mode,
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .extend(cv.polling_component_schema("60s")),
 | 
					    .extend(cv.polling_component_schema("60s")),
 | 
				
			||||||
@@ -117,7 +112,6 @@ async def to_code(config):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    cg.add(var.set_output_raw(config[CONF_RAW]))
 | 
					    cg.add(var.set_output_raw(config[CONF_RAW]))
 | 
				
			||||||
    cg.add(var.set_sample_count(config[CONF_SAMPLES]))
 | 
					    cg.add(var.set_sample_count(config[CONF_SAMPLES]))
 | 
				
			||||||
    cg.add(var.set_sampling_mode(config[CONF_SAMPLING_MODE]))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if attenuation := config.get(CONF_ATTENUATION):
 | 
					    if attenuation := config.get(CONF_ATTENUATION):
 | 
				
			||||||
        if attenuation == "auto":
 | 
					        if attenuation == "auto":
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import spi
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import spi
 | 
				
			||||||
from esphome.const import CONF_ID
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["spi"]
 | 
					DEPENDENCIES = ["spi"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import sensor, voltage_sampler
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_CHANNEL, CONF_ID
 | 
					from esphome.components import sensor, voltage_sampler
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID, CONF_CHANNEL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .. import ADC128S102, adc128s102_ns
 | 
					from .. import adc128s102_ns, ADC128S102
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AUTO_LOAD = ["voltage_sampler"]
 | 
					AUTO_LOAD = ["voltage_sampler"]
 | 
				
			||||||
DEPENDENCIES = ["adc128s102"]
 | 
					DEPENDENCIES = ["adc128s102"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,15 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import display, light
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import display, light
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ADDRESSABLE_LIGHT_ID,
 | 
					 | 
				
			||||||
    CONF_HEIGHT,
 | 
					 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_LAMBDA,
 | 
					    CONF_LAMBDA,
 | 
				
			||||||
    CONF_PAGES,
 | 
					    CONF_PAGES,
 | 
				
			||||||
    CONF_PIXEL_MAPPER,
 | 
					    CONF_ADDRESSABLE_LIGHT_ID,
 | 
				
			||||||
    CONF_UPDATE_INTERVAL,
 | 
					    CONF_HEIGHT,
 | 
				
			||||||
    CONF_WIDTH,
 | 
					    CONF_WIDTH,
 | 
				
			||||||
 | 
					    CONF_UPDATE_INTERVAL,
 | 
				
			||||||
 | 
					    CONF_PIXEL_MAPPER,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@justfalter"]
 | 
					CODEOWNERS = ["@justfalter"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
from esphome import pins
 | 
					 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import i2c, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, i2c
 | 
				
			||||||
 | 
					from esphome import pins
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ACTIVE_POWER,
 | 
					    CONF_ACTIVE_POWER,
 | 
				
			||||||
    CONF_APPARENT_POWER,
 | 
					    CONF_APPARENT_POWER,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,27 +1,27 @@
 | 
				
			|||||||
from esphome import pins
 | 
					 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor
 | 
				
			||||||
 | 
					from esphome import pins
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_FREQUENCY,
 | 
					 | 
				
			||||||
    CONF_IRQ_PIN,
 | 
					    CONF_IRQ_PIN,
 | 
				
			||||||
    CONF_VOLTAGE,
 | 
					    CONF_VOLTAGE,
 | 
				
			||||||
 | 
					    CONF_FREQUENCY,
 | 
				
			||||||
    CONF_VOLTAGE_GAIN,
 | 
					    CONF_VOLTAGE_GAIN,
 | 
				
			||||||
    DEVICE_CLASS_APPARENT_POWER,
 | 
					 | 
				
			||||||
    DEVICE_CLASS_CURRENT,
 | 
					    DEVICE_CLASS_CURRENT,
 | 
				
			||||||
    DEVICE_CLASS_FREQUENCY,
 | 
					    DEVICE_CLASS_APPARENT_POWER,
 | 
				
			||||||
    DEVICE_CLASS_POWER,
 | 
					    DEVICE_CLASS_POWER,
 | 
				
			||||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
					 | 
				
			||||||
    DEVICE_CLASS_REACTIVE_POWER,
 | 
					    DEVICE_CLASS_REACTIVE_POWER,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_POWER_FACTOR,
 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_FREQUENCY,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_AMPERE,
 | 
					 | 
				
			||||||
    UNIT_HERTZ,
 | 
					 | 
				
			||||||
    UNIT_PERCENT,
 | 
					 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
 | 
					    UNIT_HERTZ,
 | 
				
			||||||
 | 
					    UNIT_AMPERE,
 | 
				
			||||||
    UNIT_VOLT_AMPS,
 | 
					    UNIT_VOLT_AMPS,
 | 
				
			||||||
    UNIT_VOLT_AMPS_REACTIVE,
 | 
					 | 
				
			||||||
    UNIT_WATT,
 | 
					    UNIT_WATT,
 | 
				
			||||||
 | 
					    UNIT_VOLT_AMPS_REACTIVE,
 | 
				
			||||||
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_CURRENT_A = "current_a"
 | 
					CONF_CURRENT_A = "current_a"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,9 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import ade7953_base, i2c
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import i2c, ade7953_base
 | 
				
			||||||
from esphome.const import CONF_ID
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["i2c"]
 | 
					DEPENDENCIES = ["i2c"]
 | 
				
			||||||
AUTO_LOAD = ["ade7953_base"]
 | 
					AUTO_LOAD = ["ade7953_base"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,9 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import ade7953_base, spi
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import spi, ade7953_base
 | 
				
			||||||
from esphome.const import CONF_ID
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["spi"]
 | 
					DEPENDENCIES = ["spi"]
 | 
				
			||||||
AUTO_LOAD = ["ade7953_base"]
 | 
					AUTO_LOAD = ["ade7953_base"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import i2c
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import i2c
 | 
				
			||||||
from esphome.const import CONF_ID
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["i2c"]
 | 
					DEPENDENCIES = ["i2c"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,8 @@ static const char *const TAG = "ads1115";
 | 
				
			|||||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
 | 
					static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
 | 
				
			||||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
 | 
					static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111;  // 3300_SPS for ADS1015
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ADS1115Component::setup() {
 | 
					void ADS1115Component::setup() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
 | 
					  ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
 | 
				
			||||||
  uint16_t value;
 | 
					  uint16_t value;
 | 
				
			||||||
@@ -41,9 +43,9 @@ void ADS1115Component::setup() {
 | 
				
			|||||||
    config |= 0b0000000100000000;
 | 
					    config |= 0b0000000100000000;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Set data rate - 860 samples per second
 | 
					  // Set data rate - 860 samples per second (we're in singleshot mode)
 | 
				
			||||||
  //        0bxxxxxxxx100xxxxx
 | 
					  //        0bxxxxxxxx100xxxxx
 | 
				
			||||||
  config |= ADS1115_860SPS << 5;
 | 
					  config |= ADS1115_DATA_RATE_860_SPS << 5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Set comparator mode - hysteresis
 | 
					  // Set comparator mode - hysteresis
 | 
				
			||||||
  //        0bxxxxxxxxxxx0xxxx
 | 
					  //        0bxxxxxxxxxxx0xxxx
 | 
				
			||||||
@@ -75,7 +77,7 @@ void ADS1115Component::dump_config() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain,
 | 
					float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain,
 | 
				
			||||||
                                            ADS1115Resolution resolution, ADS1115Samplerate samplerate) {
 | 
					                                            ADS1115Resolution resolution) {
 | 
				
			||||||
  uint16_t config = this->prev_config_;
 | 
					  uint16_t config = this->prev_config_;
 | 
				
			||||||
  // Multiplexer
 | 
					  // Multiplexer
 | 
				
			||||||
  //        0bxBBBxxxxxxxxxxxx
 | 
					  //        0bxBBBxxxxxxxxxxxx
 | 
				
			||||||
@@ -87,11 +89,6 @@ float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1
 | 
				
			|||||||
  config &= 0b1111000111111111;
 | 
					  config &= 0b1111000111111111;
 | 
				
			||||||
  config |= (gain & 0b111) << 9;
 | 
					  config |= (gain & 0b111) << 9;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Sample rate
 | 
					 | 
				
			||||||
  //        0bxxxxxxxxBBBxxxxx
 | 
					 | 
				
			||||||
  config &= 0b1111111100011111;
 | 
					 | 
				
			||||||
  config |= (samplerate & 0b111) << 5;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!this->continuous_mode_) {
 | 
					  if (!this->continuous_mode_) {
 | 
				
			||||||
    // Start conversion
 | 
					    // Start conversion
 | 
				
			||||||
    config |= 0b1000000000000000;
 | 
					    config |= 0b1000000000000000;
 | 
				
			||||||
@@ -104,54 +101,8 @@ float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    this->prev_config_ = config;
 | 
					    this->prev_config_ = config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Delay calculated as: ceil((1000/SPS)+.5)
 | 
					    // about 1.2 ms with 860 samples per second
 | 
				
			||||||
    if (resolution == ADS1015_12_BITS) {
 | 
					    delay(2);
 | 
				
			||||||
      switch (samplerate) {
 | 
					 | 
				
			||||||
        case ADS1115_8SPS:
 | 
					 | 
				
			||||||
          delay(9);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_16SPS:
 | 
					 | 
				
			||||||
          delay(5);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_32SPS:
 | 
					 | 
				
			||||||
          delay(3);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_64SPS:
 | 
					 | 
				
			||||||
        case ADS1115_128SPS:
 | 
					 | 
				
			||||||
          delay(2);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        default:
 | 
					 | 
				
			||||||
          delay(1);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      switch (samplerate) {
 | 
					 | 
				
			||||||
        case ADS1115_8SPS:
 | 
					 | 
				
			||||||
          delay(126);  // NOLINT
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_16SPS:
 | 
					 | 
				
			||||||
          delay(63);  // NOLINT
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_32SPS:
 | 
					 | 
				
			||||||
          delay(32);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_64SPS:
 | 
					 | 
				
			||||||
          delay(17);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_128SPS:
 | 
					 | 
				
			||||||
          delay(9);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_250SPS:
 | 
					 | 
				
			||||||
          delay(5);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_475SPS:
 | 
					 | 
				
			||||||
          delay(3);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case ADS1115_860SPS:
 | 
					 | 
				
			||||||
          delay(2);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // in continuous mode, conversion will always be running, rely on the delay
 | 
					    // in continuous mode, conversion will always be running, rely on the delay
 | 
				
			||||||
    // to ensure conversion is taking place with the correct settings
 | 
					    // to ensure conversion is taking place with the correct settings
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,17 +33,6 @@ enum ADS1115Resolution {
 | 
				
			|||||||
  ADS1015_12_BITS = 12,
 | 
					  ADS1015_12_BITS = 12,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum ADS1115Samplerate {
 | 
					 | 
				
			||||||
  ADS1115_8SPS = 0b000,
 | 
					 | 
				
			||||||
  ADS1115_16SPS = 0b001,
 | 
					 | 
				
			||||||
  ADS1115_32SPS = 0b010,
 | 
					 | 
				
			||||||
  ADS1115_64SPS = 0b011,
 | 
					 | 
				
			||||||
  ADS1115_128SPS = 0b100,
 | 
					 | 
				
			||||||
  ADS1115_250SPS = 0b101,
 | 
					 | 
				
			||||||
  ADS1115_475SPS = 0b110,
 | 
					 | 
				
			||||||
  ADS1115_860SPS = 0b111
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ADS1115Component : public Component, public i2c::I2CDevice {
 | 
					class ADS1115Component : public Component, public i2c::I2CDevice {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
@@ -53,8 +42,7 @@ class ADS1115Component : public Component, public i2c::I2CDevice {
 | 
				
			|||||||
  void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
 | 
					  void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Helper method to request a measurement from a sensor.
 | 
					  /// Helper method to request a measurement from a sensor.
 | 
				
			||||||
  float request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution,
 | 
					  float request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution);
 | 
				
			||||||
                            ADS1115Samplerate samplerate);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  uint16_t prev_config_{0};
 | 
					  uint16_t prev_config_{0};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,16 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import sensor, voltage_sampler
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, voltage_sampler
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_GAIN,
 | 
					    CONF_GAIN,
 | 
				
			||||||
    CONF_ID,
 | 
					 | 
				
			||||||
    CONF_MULTIPLEXER,
 | 
					    CONF_MULTIPLEXER,
 | 
				
			||||||
    CONF_RESOLUTION,
 | 
					    CONF_RESOLUTION,
 | 
				
			||||||
    CONF_SAMPLE_RATE,
 | 
					 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from .. import ads1115_ns, ADS1115Component, CONF_ADS1115_ID
 | 
				
			||||||
from .. import CONF_ADS1115_ID, ADS1115Component, ads1115_ns
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
AUTO_LOAD = ["voltage_sampler"]
 | 
					AUTO_LOAD = ["voltage_sampler"]
 | 
				
			||||||
DEPENDENCIES = ["ads1115"]
 | 
					DEPENDENCIES = ["ads1115"]
 | 
				
			||||||
@@ -45,17 +43,6 @@ RESOLUTION = {
 | 
				
			|||||||
    "12_BITS": ADS1115Resolution.ADS1015_12_BITS,
 | 
					    "12_BITS": ADS1115Resolution.ADS1015_12_BITS,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ADS1115Samplerate = ads1115_ns.enum("ADS1115Samplerate")
 | 
					 | 
				
			||||||
SAMPLERATE = {
 | 
					 | 
				
			||||||
    "8": ADS1115Samplerate.ADS1115_8SPS,
 | 
					 | 
				
			||||||
    "16": ADS1115Samplerate.ADS1115_16SPS,
 | 
					 | 
				
			||||||
    "32": ADS1115Samplerate.ADS1115_32SPS,
 | 
					 | 
				
			||||||
    "64": ADS1115Samplerate.ADS1115_64SPS,
 | 
					 | 
				
			||||||
    "128": ADS1115Samplerate.ADS1115_128SPS,
 | 
					 | 
				
			||||||
    "250": ADS1115Samplerate.ADS1115_250SPS,
 | 
					 | 
				
			||||||
    "475": ADS1115Samplerate.ADS1115_475SPS,
 | 
					 | 
				
			||||||
    "860": ADS1115Samplerate.ADS1115_860SPS,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
ADS1115Sensor = ads1115_ns.class_(
 | 
					ADS1115Sensor = ads1115_ns.class_(
 | 
				
			||||||
    "ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
 | 
					    "ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
 | 
				
			||||||
@@ -77,9 +64,6 @@ CONFIG_SCHEMA = (
 | 
				
			|||||||
            cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum(
 | 
					            cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum(
 | 
				
			||||||
                RESOLUTION, upper=True, space="_"
 | 
					                RESOLUTION, upper=True, space="_"
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_SAMPLE_RATE, default="860"): cv.enum(
 | 
					 | 
				
			||||||
                SAMPLERATE, string=True
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .extend(cv.polling_component_schema("60s"))
 | 
					    .extend(cv.polling_component_schema("60s"))
 | 
				
			||||||
@@ -95,4 +79,3 @@ async def to_code(config):
 | 
				
			|||||||
    cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
 | 
					    cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
 | 
				
			||||||
    cg.add(var.set_gain(config[CONF_GAIN]))
 | 
					    cg.add(var.set_gain(config[CONF_GAIN]))
 | 
				
			||||||
    cg.add(var.set_resolution(config[CONF_RESOLUTION]))
 | 
					    cg.add(var.set_resolution(config[CONF_RESOLUTION]))
 | 
				
			||||||
    cg.add(var.set_samplerate(config[CONF_SAMPLE_RATE]))
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ namespace ads1115 {
 | 
				
			|||||||
static const char *const TAG = "ads1115.sensor";
 | 
					static const char *const TAG = "ads1115.sensor";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
float ADS1115Sensor::sample() {
 | 
					float ADS1115Sensor::sample() {
 | 
				
			||||||
  return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->resolution_, this->samplerate_);
 | 
					  return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->resolution_);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ADS1115Sensor::update() {
 | 
					void ADS1115Sensor::update() {
 | 
				
			||||||
@@ -24,7 +24,6 @@ void ADS1115Sensor::dump_config() {
 | 
				
			|||||||
  ESP_LOGCONFIG(TAG, "    Multiplexer: %u", this->multiplexer_);
 | 
					  ESP_LOGCONFIG(TAG, "    Multiplexer: %u", this->multiplexer_);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "    Gain: %u", this->gain_);
 | 
					  ESP_LOGCONFIG(TAG, "    Gain: %u", this->gain_);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "    Resolution: %u", this->resolution_);
 | 
					  ESP_LOGCONFIG(TAG, "    Resolution: %u", this->resolution_);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "    Sample rate: %u", this->samplerate_);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace ads1115
 | 
					}  // namespace ads1115
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,6 @@ class ADS1115Sensor : public sensor::Sensor,
 | 
				
			|||||||
  void set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
 | 
					  void set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
 | 
				
			||||||
  void set_gain(ADS1115Gain gain) { this->gain_ = gain; }
 | 
					  void set_gain(ADS1115Gain gain) { this->gain_ = gain; }
 | 
				
			||||||
  void set_resolution(ADS1115Resolution resolution) { this->resolution_ = resolution; }
 | 
					  void set_resolution(ADS1115Resolution resolution) { this->resolution_ = resolution; }
 | 
				
			||||||
  void set_samplerate(ADS1115Samplerate samplerate) { this->samplerate_ = samplerate; }
 | 
					 | 
				
			||||||
  float sample() override;
 | 
					  float sample() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
@@ -30,7 +29,6 @@ class ADS1115Sensor : public sensor::Sensor,
 | 
				
			|||||||
  ADS1115Multiplexer multiplexer_;
 | 
					  ADS1115Multiplexer multiplexer_;
 | 
				
			||||||
  ADS1115Gain gain_;
 | 
					  ADS1115Gain gain_;
 | 
				
			||||||
  ADS1115Resolution resolution_;
 | 
					  ADS1115Resolution resolution_;
 | 
				
			||||||
  ADS1115Samplerate samplerate_;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace ads1115
 | 
					}  // namespace ads1115
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import spi
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import spi
 | 
				
			||||||
from esphome.const import CONF_ID
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@solomondg1"]
 | 
					CODEOWNERS = ["@solomondg1"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,17 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import sensor, voltage_sampler
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, voltage_sampler
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_GAIN,
 | 
					    CONF_GAIN,
 | 
				
			||||||
    CONF_MULTIPLEXER,
 | 
					    CONF_MULTIPLEXER,
 | 
				
			||||||
    CONF_TYPE,
 | 
					 | 
				
			||||||
    DEVICE_CLASS_TEMPERATURE,
 | 
					 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_CELSIUS,
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
 | 
					    CONF_TYPE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from .. import ads1118_ns, ADS1118, CONF_ADS1118_ID
 | 
				
			||||||
from .. import ADS1118, CONF_ADS1118_ID, ads1118_ns
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
AUTO_LOAD = ["voltage_sampler"]
 | 
					AUTO_LOAD = ["voltage_sampler"]
 | 
				
			||||||
DEPENDENCIES = ["ads1118"]
 | 
					DEPENDENCIES = ["ads1118"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,21 +1,21 @@
 | 
				
			|||||||
from esphome import automation
 | 
					 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import i2c, sensor
 | 
					from esphome import automation
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import i2c, sensor
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ADDRESS,
 | 
					 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_MODE,
 | 
					 | 
				
			||||||
    CONF_TVOC,
 | 
					 | 
				
			||||||
    CONF_VALUE,
 | 
					 | 
				
			||||||
    CONF_VERSION,
 | 
					 | 
				
			||||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
					 | 
				
			||||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
					 | 
				
			||||||
    ICON_RADIATOR,
 | 
					    ICON_RADIATOR,
 | 
				
			||||||
    ICON_RESTART,
 | 
					    ICON_RESTART,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
				
			||||||
 | 
					    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_OHM,
 | 
					    UNIT_OHM,
 | 
				
			||||||
    UNIT_PARTS_PER_BILLION,
 | 
					    UNIT_PARTS_PER_BILLION,
 | 
				
			||||||
 | 
					    CONF_ADDRESS,
 | 
				
			||||||
 | 
					    CONF_TVOC,
 | 
				
			||||||
 | 
					    CONF_VERSION,
 | 
				
			||||||
 | 
					    CONF_MODE,
 | 
				
			||||||
 | 
					    CONF_VALUE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_RESISTANCE = "resistance"
 | 
					CONF_RESISTANCE = "resistance"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,16 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import i2c, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import i2c, sensor
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_HUMIDITY,
 | 
					    CONF_HUMIDITY,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_TEMPERATURE,
 | 
					    CONF_TEMPERATURE,
 | 
				
			||||||
    CONF_VARIANT,
 | 
					 | 
				
			||||||
    DEVICE_CLASS_HUMIDITY,
 | 
					    DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
    DEVICE_CLASS_TEMPERATURE,
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_CELSIUS,
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
    UNIT_PERCENT,
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
 | 
					    CONF_VARIANT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["i2c"]
 | 
					DEPENDENCIES = ["i2c"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,173 +0,0 @@
 | 
				
			|||||||
#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
 | 
					 | 
				
			||||||
@@ -1,88 +0,0 @@
 | 
				
			|||||||
#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
 | 
					 | 
				
			||||||
@@ -1,52 +0,0 @@
 | 
				
			|||||||
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)
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
#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
 | 
					 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import esp32_ble_tracker
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import esp32_ble_tracker
 | 
				
			||||||
from esphome.const import CONF_ID
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
					DEPENDENCIES = ["esp32_ble_tracker"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,18 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import ble_client, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, ble_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_BATTERY_VOLTAGE,
 | 
					    CONF_BATTERY_VOLTAGE,
 | 
				
			||||||
    CONF_HUMIDITY,
 | 
					    CONF_HUMIDITY,
 | 
				
			||||||
    CONF_PRESSURE,
 | 
					    CONF_PRESSURE,
 | 
				
			||||||
    CONF_TEMPERATURE,
 | 
					    CONF_TEMPERATURE,
 | 
				
			||||||
    CONF_TVOC,
 | 
					    CONF_TVOC,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
    DEVICE_CLASS_HUMIDITY,
 | 
					    DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
    DEVICE_CLASS_PRESSURE,
 | 
					    DEVICE_CLASS_PRESSURE,
 | 
				
			||||||
    DEVICE_CLASS_TEMPERATURE,
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
					    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					 | 
				
			||||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
					    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_CELSIUS,
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
@@ -34,7 +35,7 @@ AirthingsWaveBase = airthings_wave_base_ns.class_(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BASE_SCHEMA = (
 | 
					BASE_SCHEMA = (
 | 
				
			||||||
    cv.Schema(
 | 
					    sensor.SENSOR_SCHEMA.extend(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
				
			||||||
                unit_of_measurement=UNIT_PERCENT,
 | 
					                unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,10 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import airthings_wave_base
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_ID
 | 
					from esphome.components import airthings_wave_base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
 | 
					DEPENDENCIES = airthings_wave_base.DEPENDENCIES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,20 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import airthings_wave_base, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, airthings_wave_base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_CO2,
 | 
					    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
				
			||||||
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    ICON_RADIOACTIVE,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_ILLUMINANCE,
 | 
					 | 
				
			||||||
    CONF_RADON,
 | 
					    CONF_RADON,
 | 
				
			||||||
    CONF_RADON_LONG_TERM,
 | 
					    CONF_RADON_LONG_TERM,
 | 
				
			||||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
					    CONF_CO2,
 | 
				
			||||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
					 | 
				
			||||||
    ICON_RADIOACTIVE,
 | 
					 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					 | 
				
			||||||
    UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
					    UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
				
			||||||
    UNIT_LUX,
 | 
					 | 
				
			||||||
    UNIT_PARTS_PER_MILLION,
 | 
					    UNIT_PARTS_PER_MILLION,
 | 
				
			||||||
 | 
					    CONF_ILLUMINANCE,
 | 
				
			||||||
 | 
					    UNIT_LUX,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_ILLUMINANCE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
 | 
					DEPENDENCIES = airthings_wave_base.DEPENDENCIES
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,16 +5,13 @@ from esphome.components import mqtt, web_server
 | 
				
			|||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_CODE,
 | 
					    CONF_CODE,
 | 
				
			||||||
    CONF_ENTITY_CATEGORY,
 | 
					 | 
				
			||||||
    CONF_ICON,
 | 
					 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_MQTT_ID,
 | 
					    CONF_MQTT_ID,
 | 
				
			||||||
    CONF_ON_STATE,
 | 
					    CONF_ON_STATE,
 | 
				
			||||||
    CONF_TRIGGER_ID,
 | 
					    CONF_TRIGGER_ID,
 | 
				
			||||||
    CONF_WEB_SERVER,
 | 
					    CONF_WEB_SERVER_ID,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, coroutine_with_priority
 | 
					from esphome.core import CORE, coroutine_with_priority
 | 
				
			||||||
from esphome.cpp_generator import MockObjClass
 | 
					 | 
				
			||||||
from esphome.cpp_helpers import setup_entity
 | 
					from esphome.cpp_helpers import setup_entity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@grahambrown11", "@hwstar"]
 | 
					CODEOWNERS = ["@grahambrown11", "@hwstar"]
 | 
				
			||||||
@@ -81,11 +78,12 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
 | 
				
			|||||||
    "AlarmControlPanelCondition", automation.Condition
 | 
					    "AlarmControlPanelCondition", automation.Condition
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_ALARM_CONTROL_PANEL_SCHEMA = (
 | 
					ALARM_CONTROL_PANEL_SCHEMA = (
 | 
				
			||||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
					    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
				
			||||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
					    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
				
			||||||
    .extend(
 | 
					    .extend(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(AlarmControlPanel),
 | 
				
			||||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
 | 
					            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
 | 
				
			||||||
                mqtt.MQTTAlarmControlPanelComponent
 | 
					                mqtt.MQTTAlarmControlPanelComponent
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@@ -148,33 +146,6 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
def alarm_control_panel_schema(
 | 
					 | 
				
			||||||
    class_: MockObjClass,
 | 
					 | 
				
			||||||
    *,
 | 
					 | 
				
			||||||
    entity_category: str = cv.UNDEFINED,
 | 
					 | 
				
			||||||
    icon: str = cv.UNDEFINED,
 | 
					 | 
				
			||||||
) -> cv.Schema:
 | 
					 | 
				
			||||||
    schema = {
 | 
					 | 
				
			||||||
        cv.GenerateID(): cv.declare_id(class_),
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for key, default, validator in [
 | 
					 | 
				
			||||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
					 | 
				
			||||||
        (CONF_ICON, icon, cv.icon),
 | 
					 | 
				
			||||||
    ]:
 | 
					 | 
				
			||||||
        if default is not cv.UNDEFINED:
 | 
					 | 
				
			||||||
            schema[cv.Optional(key, default=default)] = validator
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Remove before 2025.11.0
 | 
					 | 
				
			||||||
ALARM_CONTROL_PANEL_SCHEMA = alarm_control_panel_schema(AlarmControlPanel)
 | 
					 | 
				
			||||||
ALARM_CONTROL_PANEL_SCHEMA.add_extra(
 | 
					 | 
				
			||||||
    cv.deprecated_schema_constant("alarm_control_panel")
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
 | 
					ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cv.GenerateID(): cv.use_id(AlarmControlPanel),
 | 
					        cv.GenerateID(): cv.use_id(AlarmControlPanel),
 | 
				
			||||||
@@ -224,8 +195,9 @@ async def setup_alarm_control_panel_core_(var, config):
 | 
				
			|||||||
    for conf in config.get(CONF_ON_READY, []):
 | 
					    for conf in config.get(CONF_ON_READY, []):
 | 
				
			||||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
					        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
				
			||||||
        await automation.build_automation(trigger, [], conf)
 | 
					        await automation.build_automation(trigger, [], conf)
 | 
				
			||||||
    if web_server_config := config.get(CONF_WEB_SERVER):
 | 
					    if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
 | 
				
			||||||
        await web_server.add_entity_config(var, web_server_config)
 | 
					        web_server_ = await cg.get_variable(webserver_id)
 | 
				
			||||||
 | 
					        web_server.add_entity_to_sorting_list(web_server_, var, config)
 | 
				
			||||||
    if mqtt_id := config.get(CONF_MQTT_ID):
 | 
					    if mqtt_id := config.get(CONF_MQTT_ID):
 | 
				
			||||||
        mqtt_ = cg.new_Pvariable(mqtt_id, var)
 | 
					        mqtt_ = cg.new_Pvariable(mqtt_id, var)
 | 
				
			||||||
        await mqtt.register_mqtt_component(mqtt_, config)
 | 
					        await mqtt.register_mqtt_component(mqtt_, config)
 | 
				
			||||||
@@ -238,12 +210,6 @@ async def register_alarm_control_panel(var, config):
 | 
				
			|||||||
    await setup_alarm_control_panel_core_(var, config)
 | 
					    await setup_alarm_control_panel_core_(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def new_alarm_control_panel(config, *args):
 | 
					 | 
				
			||||||
    var = cg.new_Pvariable(config[CONF_ID], *args)
 | 
					 | 
				
			||||||
    await register_alarm_control_panel(var, config)
 | 
					 | 
				
			||||||
    return var
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@automation.register_action(
 | 
					@automation.register_action(
 | 
				
			||||||
    "alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
					    "alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,9 +72,10 @@ void AlarmControlPanelCall::validate_() {
 | 
				
			|||||||
      this->state_.reset();
 | 
					      this->state_.reset();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (state == ACP_STATE_DISARMED && !this->parent_->is_state_armed(this->parent_->get_state()) &&
 | 
					    if (state == ACP_STATE_DISARMED &&
 | 
				
			||||||
        this->parent_->get_state() != ACP_STATE_PENDING && this->parent_->get_state() != ACP_STATE_ARMING &&
 | 
					        !(this->parent_->is_state_armed(this->parent_->get_state()) ||
 | 
				
			||||||
        this->parent_->get_state() != ACP_STATE_TRIGGERED) {
 | 
					          this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING ||
 | 
				
			||||||
 | 
					          this->parent_->get_state() == ACP_STATE_TRIGGERED)) {
 | 
				
			||||||
      ESP_LOGW(TAG, "Cannot disarm when not armed");
 | 
					      ESP_LOGW(TAG, "Cannot disarm when not armed");
 | 
				
			||||||
      this->state_.reset();
 | 
					      this->state_.reset();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,20 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import ble_client, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, ble_client
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_CURRENT,
 | 
					    CONF_CURRENT,
 | 
				
			||||||
    CONF_FLOW,
 | 
					    CONF_FLOW,
 | 
				
			||||||
    CONF_HEAD,
 | 
					    CONF_HEAD,
 | 
				
			||||||
    CONF_ID,
 | 
					 | 
				
			||||||
    CONF_POWER,
 | 
					    CONF_POWER,
 | 
				
			||||||
    CONF_SPEED,
 | 
					    CONF_SPEED,
 | 
				
			||||||
    CONF_VOLTAGE,
 | 
					    CONF_VOLTAGE,
 | 
				
			||||||
    UNIT_AMPERE,
 | 
					    UNIT_AMPERE,
 | 
				
			||||||
    UNIT_CUBIC_METER_PER_HOUR,
 | 
					 | 
				
			||||||
    UNIT_METER,
 | 
					 | 
				
			||||||
    UNIT_REVOLUTIONS_PER_MINUTE,
 | 
					 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
    UNIT_WATT,
 | 
					    UNIT_WATT,
 | 
				
			||||||
 | 
					    UNIT_METER,
 | 
				
			||||||
 | 
					    UNIT_CUBIC_METER_PER_HOUR,
 | 
				
			||||||
 | 
					    UNIT_REVOLUTIONS_PER_MINUTE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
alpha3_ns = cg.esphome_ns.namespace("alpha3")
 | 
					alpha3_ns = cg.esphome_ns.namespace("alpha3")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -128,7 +128,7 @@ void AM2315C::update() {
 | 
				
			|||||||
  data[2] = 0x00;
 | 
					  data[2] = 0x00;
 | 
				
			||||||
  if (this->write(data, 3) != i2c::ERROR_OK) {
 | 
					  if (this->write(data, 3) != i2c::ERROR_OK) {
 | 
				
			||||||
    ESP_LOGE(TAG, "Write failed!");
 | 
					    ESP_LOGE(TAG, "Write failed!");
 | 
				
			||||||
    this->status_set_warning();
 | 
					    this->mark_failed();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -138,12 +138,12 @@ void AM2315C::update() {
 | 
				
			|||||||
    uint8_t status = 0;
 | 
					    uint8_t status = 0;
 | 
				
			||||||
    if (this->read(&status, 1) != i2c::ERROR_OK) {
 | 
					    if (this->read(&status, 1) != i2c::ERROR_OK) {
 | 
				
			||||||
      ESP_LOGE(TAG, "Read failed!");
 | 
					      ESP_LOGE(TAG, "Read failed!");
 | 
				
			||||||
      this->status_set_warning();
 | 
					      this->mark_failed();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if ((status & 0x80) == 0x80) {
 | 
					    if ((status & 0x80) == 0x80) {
 | 
				
			||||||
      ESP_LOGE(TAG, "HW still busy!");
 | 
					      ESP_LOGE(TAG, "HW still busy!");
 | 
				
			||||||
      this->status_set_warning();
 | 
					      this->mark_failed();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -151,7 +151,7 @@ void AM2315C::update() {
 | 
				
			|||||||
    uint8_t data[7];
 | 
					    uint8_t data[7];
 | 
				
			||||||
    if (this->read(data, 7) != i2c::ERROR_OK) {
 | 
					    if (this->read(data, 7) != i2c::ERROR_OK) {
 | 
				
			||||||
      ESP_LOGE(TAG, "Read failed!");
 | 
					      ESP_LOGE(TAG, "Read failed!");
 | 
				
			||||||
      this->status_set_warning();
 | 
					      this->mark_failed();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import i2c, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import i2c, sensor
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_HUMIDITY,
 | 
					    CONF_HUMIDITY,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import i2c, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import i2c, sensor
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_HUMIDITY,
 | 
					    CONF_HUMIDITY,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import ble_client, cover
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_PIN
 | 
					from esphome.components import cover, ble_client
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID, CONF_PIN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@buxtronix"]
 | 
					CODEOWNERS = ["@buxtronix"]
 | 
				
			||||||
DEPENDENCIES = ["ble_client"]
 | 
					DEPENDENCIES = ["ble_client"]
 | 
				
			||||||
@@ -15,9 +15,9 @@ Am43Component = am43_ns.class_(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = (
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
    cover.cover_schema(Am43Component)
 | 
					    cover.COVER_SCHEMA.extend(
 | 
				
			||||||
    .extend(
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(Am43Component),
 | 
				
			||||||
            cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
 | 
					            cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
 | 
				
			||||||
            cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
 | 
					            cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -28,8 +28,9 @@ CONFIG_SCHEMA = (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = await cover.new_cover(config)
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
    cg.add(var.set_pin(config[CONF_PIN]))
 | 
					    cg.add(var.set_pin(config[CONF_PIN]))
 | 
				
			||||||
    cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
 | 
					    cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await cover.register_cover(var, config)
 | 
				
			||||||
    await ble_client.register_ble_node(var, config)
 | 
					    await ble_client.register_ble_node(var, config)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,12 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import ble_client, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, ble_client
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_BATTERY_LEVEL,
 | 
					 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_ILLUMINANCE,
 | 
					    CONF_BATTERY_LEVEL,
 | 
				
			||||||
    DEVICE_CLASS_BATTERY,
 | 
					    DEVICE_CLASS_BATTERY,
 | 
				
			||||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
					    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
				
			||||||
 | 
					    CONF_ILLUMINANCE,
 | 
				
			||||||
    ICON_BRIGHTNESS_5,
 | 
					    ICON_BRIGHTNESS_5,
 | 
				
			||||||
    UNIT_PERCENT,
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,8 +14,7 @@ void AnalogThresholdBinarySensor::setup() {
 | 
				
			|||||||
  if (std::isnan(sensor_value)) {
 | 
					  if (std::isnan(sensor_value)) {
 | 
				
			||||||
    this->publish_initial_state(false);
 | 
					    this->publish_initial_state(false);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    this->publish_initial_state(sensor_value >=
 | 
					    this->publish_initial_state(sensor_value >= (this->lower_threshold_ + this->upper_threshold_) / 2.0f);
 | 
				
			||||||
                                (this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,8 +24,7 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
 | 
				
			|||||||
  this->sensor_->add_on_state_callback([this](float sensor_value) {
 | 
					  this->sensor_->add_on_state_callback([this](float sensor_value) {
 | 
				
			||||||
    // if there is an invalid sensor reading, ignore the change and keep the current state
 | 
					    // if there is an invalid sensor reading, ignore the change and keep the current state
 | 
				
			||||||
    if (!std::isnan(sensor_value)) {
 | 
					    if (!std::isnan(sensor_value)) {
 | 
				
			||||||
      this->publish_state(sensor_value >=
 | 
					      this->publish_state(sensor_value >= (this->state ? this->lower_threshold_ : this->upper_threshold_));
 | 
				
			||||||
                          (this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -34,8 +32,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
 | 
				
			|||||||
void AnalogThresholdBinarySensor::dump_config() {
 | 
					void AnalogThresholdBinarySensor::dump_config() {
 | 
				
			||||||
  LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
 | 
					  LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
 | 
				
			||||||
  LOG_SENSOR("  ", "Sensor", this->sensor_);
 | 
					  LOG_SENSOR("  ", "Sensor", this->sensor_);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Upper threshold: %.11f", this->upper_threshold_.value());
 | 
					  ESP_LOGCONFIG(TAG, "  Upper threshold: %.11f", this->upper_threshold_);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Lower threshold: %.11f", this->lower_threshold_.value());
 | 
					  ESP_LOGCONFIG(TAG, "  Lower threshold: %.11f", this->lower_threshold_);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace analog_threshold
 | 
					}  // namespace analog_threshold
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,13 +15,14 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
 | 
				
			|||||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
					  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void set_sensor(sensor::Sensor *analog_sensor);
 | 
					  void set_sensor(sensor::Sensor *analog_sensor);
 | 
				
			||||||
  template<typename T> void set_upper_threshold(T upper_threshold) { this->upper_threshold_ = upper_threshold; }
 | 
					  void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; }
 | 
				
			||||||
  template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; }
 | 
					  void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  sensor::Sensor *sensor_{nullptr};
 | 
					  sensor::Sensor *sensor_{nullptr};
 | 
				
			||||||
  TemplatableValue<float> upper_threshold_{};
 | 
					
 | 
				
			||||||
  TemplatableValue<float> lower_threshold_{};
 | 
					  float upper_threshold_;
 | 
				
			||||||
 | 
					  float lower_threshold_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace analog_threshold
 | 
					}  // namespace analog_threshold
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,10 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import binary_sensor, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_SENSOR_ID, CONF_THRESHOLD
 | 
					from esphome.components import binary_sensor, sensor
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_SENSOR_ID,
 | 
				
			||||||
 | 
					    CONF_THRESHOLD,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
analog_threshold_ns = cg.esphome_ns.namespace("analog_threshold")
 | 
					analog_threshold_ns = cg.esphome_ns.namespace("analog_threshold")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -18,11 +21,11 @@ CONFIG_SCHEMA = (
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
 | 
					            cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
 | 
				
			||||||
            cv.Required(CONF_THRESHOLD): cv.Any(
 | 
					            cv.Required(CONF_THRESHOLD): cv.Any(
 | 
				
			||||||
                cv.templatable(cv.float_),
 | 
					                cv.float_,
 | 
				
			||||||
                cv.Schema(
 | 
					                cv.Schema(
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        cv.Required(CONF_UPPER): cv.templatable(cv.float_),
 | 
					                        cv.Required(CONF_UPPER): cv.float_,
 | 
				
			||||||
                        cv.Required(CONF_LOWER): cv.templatable(cv.float_),
 | 
					                        cv.Required(CONF_LOWER): cv.float_,
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@@ -39,11 +42,9 @@ async def to_code(config):
 | 
				
			|||||||
    sens = await cg.get_variable(config[CONF_SENSOR_ID])
 | 
					    sens = await cg.get_variable(config[CONF_SENSOR_ID])
 | 
				
			||||||
    cg.add(var.set_sensor(sens))
 | 
					    cg.add(var.set_sensor(sens))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if isinstance(config[CONF_THRESHOLD], dict):
 | 
					    if isinstance(config[CONF_THRESHOLD], float):
 | 
				
			||||||
        lower = await cg.templatable(config[CONF_THRESHOLD][CONF_LOWER], [], float)
 | 
					        cg.add(var.set_upper_threshold(config[CONF_THRESHOLD]))
 | 
				
			||||||
        upper = await cg.templatable(config[CONF_THRESHOLD][CONF_UPPER], [], float)
 | 
					        cg.add(var.set_lower_threshold(config[CONF_THRESHOLD]))
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        lower = await cg.templatable(config[CONF_THRESHOLD], [], float)
 | 
					        cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER]))
 | 
				
			||||||
        upper = lower
 | 
					        cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER]))
 | 
				
			||||||
    cg.add(var.set_upper_threshold(upper))
 | 
					 | 
				
			||||||
    cg.add(var.set_lower_threshold(lower))
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,29 @@
 | 
				
			|||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from esphome import automation
 | 
					from esphome import automation, core
 | 
				
			||||||
import esphome.codegen as cg
 | 
					from esphome.components import font
 | 
				
			||||||
import esphome.components.image as espImage
 | 
					import esphome.components.image as espImage
 | 
				
			||||||
 | 
					from esphome.components.image import (
 | 
				
			||||||
 | 
					    CONF_USE_TRANSPARENCY,
 | 
				
			||||||
 | 
					    LOCAL_SCHEMA,
 | 
				
			||||||
 | 
					    WEB_SCHEMA,
 | 
				
			||||||
 | 
					    SOURCE_WEB,
 | 
				
			||||||
 | 
					    SOURCE_LOCAL,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_ID, CONF_REPEAT
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_FILE,
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_RAW_DATA_ID,
 | 
				
			||||||
 | 
					    CONF_REPEAT,
 | 
				
			||||||
 | 
					    CONF_RESIZE,
 | 
				
			||||||
 | 
					    CONF_TYPE,
 | 
				
			||||||
 | 
					    CONF_SOURCE,
 | 
				
			||||||
 | 
					    CONF_PATH,
 | 
				
			||||||
 | 
					    CONF_URL,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from esphome.core import CORE, HexInt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_LOGGER = logging.getLogger(__name__)
 | 
					_LOGGER = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,7 +31,6 @@ AUTO_LOAD = ["image"]
 | 
				
			|||||||
CODEOWNERS = ["@syndlex"]
 | 
					CODEOWNERS = ["@syndlex"]
 | 
				
			||||||
DEPENDENCIES = ["display"]
 | 
					DEPENDENCIES = ["display"]
 | 
				
			||||||
MULTI_CONF = True
 | 
					MULTI_CONF = True
 | 
				
			||||||
MULTI_CONF_NO_DEFAULT = True
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_LOOP = "loop"
 | 
					CONF_LOOP = "loop"
 | 
				
			||||||
CONF_START_FRAME = "start_frame"
 | 
					CONF_START_FRAME = "start_frame"
 | 
				
			||||||
@@ -34,20 +52,87 @@ SetFrameAction = animation_ns.class_(
 | 
				
			|||||||
    "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
 | 
					    "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = espImage.IMAGE_SCHEMA.extend(
 | 
					TYPED_FILE_SCHEMA = cv.typed_schema(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
					        SOURCE_LOCAL: LOCAL_SCHEMA,
 | 
				
			||||||
        cv.Optional(CONF_LOOP): cv.All(
 | 
					        SOURCE_WEB: WEB_SCHEMA,
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
 | 
					 | 
				
			||||||
                cv.Optional(CONF_END_FRAME): cv.positive_int,
 | 
					 | 
				
			||||||
                cv.Optional(CONF_REPEAT): cv.positive_int,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    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):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Validate fields whose possible values depend on other fields.
 | 
				
			||||||
 | 
					    For example, validate that explicitly transparent image types
 | 
				
			||||||
 | 
					    have "use_transparency" set to True.
 | 
				
			||||||
 | 
					    Also set the default value for those kind of dependent fields.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    image_type = config[CONF_TYPE]
 | 
				
			||||||
 | 
					    is_transparent_type = image_type in ["TRANSPARENT_BINARY", "RGBA"]
 | 
				
			||||||
 | 
					    # If the use_transparency option was not specified, set the default depending on the image type
 | 
				
			||||||
 | 
					    if CONF_USE_TRANSPARENCY not in config:
 | 
				
			||||||
 | 
					        config[CONF_USE_TRANSPARENCY] = is_transparent_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if is_transparent_type and not config[CONF_USE_TRANSPARENCY]:
 | 
				
			||||||
 | 
					        raise cv.Invalid(f"Image type {image_type} must always be transparent.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ANIMATION_SCHEMA = cv.Schema(
 | 
				
			||||||
 | 
					    cv.All(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
				
			||||||
 | 
					            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
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            # Not setting default here on purpose; the default depends on the image type,
 | 
				
			||||||
 | 
					            # and thus will be set in the "validate_cross_dependencies" validator.
 | 
				
			||||||
 | 
					            cv.Optional(CONF_USE_TRANSPARENCY): cv.boolean,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_LOOP): cv.All(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
 | 
				
			||||||
 | 
					                    cv.Optional(CONF_END_FRAME): cv.positive_int,
 | 
				
			||||||
 | 
					                    cv.Optional(CONF_REPEAT): cv.positive_int,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        validate_cross_dependencies,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
 | 
					NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cv.GenerateID(): cv.use_id(Animation_),
 | 
					        cv.GenerateID(): cv.use_id(Animation_),
 | 
				
			||||||
@@ -80,26 +165,179 @@ async def animation_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    (
 | 
					    from PIL import Image
 | 
				
			||||||
        prog_arr,
 | 
					 | 
				
			||||||
        width,
 | 
					 | 
				
			||||||
        height,
 | 
					 | 
				
			||||||
        image_type,
 | 
					 | 
				
			||||||
        trans_value,
 | 
					 | 
				
			||||||
        frame_count,
 | 
					 | 
				
			||||||
    ) = await espImage.write_image(config, all_frames=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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()
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        image = Image.open(path)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        raise core.EsphomeError(f"Could not load image file {path}: {e}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    width, height = image.size
 | 
				
			||||||
 | 
					    frames = image.n_frames
 | 
				
			||||||
 | 
					    if CONF_RESIZE in 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,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    transparent = config[CONF_USE_TRANSPARENCY]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if config[CONF_TYPE] == "GRAYSCALE":
 | 
				
			||||||
 | 
					        data = [0 for _ in range(height * width * frames)]
 | 
				
			||||||
 | 
					        pos = 0
 | 
				
			||||||
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
 | 
					            frame = image.convert("LA", dither=Image.Dither.NONE)
 | 
				
			||||||
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
 | 
					            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})"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            for pix, a in pixels:
 | 
				
			||||||
 | 
					                if transparent:
 | 
				
			||||||
 | 
					                    if pix == 1:
 | 
				
			||||||
 | 
					                        pix = 0
 | 
				
			||||||
 | 
					                    if a < 0x80:
 | 
				
			||||||
 | 
					                        pix = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                data[pos] = pix
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif config[CONF_TYPE] == "RGBA":
 | 
				
			||||||
 | 
					        data = [0 for _ in range(height * width * 4 * frames)]
 | 
				
			||||||
 | 
					        pos = 0
 | 
				
			||||||
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
 | 
					            frame = image.convert("RGBA")
 | 
				
			||||||
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
 | 
					            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})"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            for pix in pixels:
 | 
				
			||||||
 | 
					                data[pos] = pix[0]
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					                data[pos] = pix[1]
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					                data[pos] = pix[2]
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					                data[pos] = pix[3]
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif config[CONF_TYPE] == "RGB24":
 | 
				
			||||||
 | 
					        data = [0 for _ in range(height * width * 3 * frames)]
 | 
				
			||||||
 | 
					        pos = 0
 | 
				
			||||||
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
 | 
					            frame = image.convert("RGBA")
 | 
				
			||||||
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
 | 
					            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})"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            for r, g, b, a in pixels:
 | 
				
			||||||
 | 
					                if transparent:
 | 
				
			||||||
 | 
					                    if r == 0 and g == 0 and b == 1:
 | 
				
			||||||
 | 
					                        b = 0
 | 
				
			||||||
 | 
					                    if a < 0x80:
 | 
				
			||||||
 | 
					                        r = 0
 | 
				
			||||||
 | 
					                        g = 0
 | 
				
			||||||
 | 
					                        b = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                data[pos] = r
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					                data[pos] = g
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					                data[pos] = b
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif config[CONF_TYPE] in ["RGB565", "TRANSPARENT_IMAGE"]:
 | 
				
			||||||
 | 
					        data = [0 for _ in range(height * width * 2 * frames)]
 | 
				
			||||||
 | 
					        pos = 0
 | 
				
			||||||
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
 | 
					            frame = image.convert("RGBA")
 | 
				
			||||||
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
 | 
					            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})"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]:
 | 
				
			||||||
 | 
					        width8 = ((width + 7) // 8) * 8
 | 
				
			||||||
 | 
					        data = [0 for _ in range((height * width8 // 8) * frames)]
 | 
				
			||||||
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
 | 
					            if transparent:
 | 
				
			||||||
 | 
					                alpha = image.split()[-1]
 | 
				
			||||||
 | 
					                has_alpha = alpha.getextrema()[0] < 0xFF
 | 
				
			||||||
 | 
					            frame = image.convert("1", dither=Image.Dither.NONE)
 | 
				
			||||||
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
 | 
					                if transparent:
 | 
				
			||||||
 | 
					                    alpha = alpha.resize([width, height])
 | 
				
			||||||
 | 
					            for x, y in [(i, j) for i in range(width) for j in range(height)]:
 | 
				
			||||||
 | 
					                if transparent and has_alpha:
 | 
				
			||||||
 | 
					                    if not alpha.getpixel((x, y)):
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                elif frame.getpixel((x, y)):
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                pos = x + y * width8 + (height * width8 * frameIndex)
 | 
				
			||||||
 | 
					                data[pos // 8] |= 0x80 >> (pos % 8)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        raise core.EsphomeError(
 | 
				
			||||||
 | 
					            f"Animation f{config[CONF_ID]} has not supported type {config[CONF_TYPE]}."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rhs = [HexInt(x) for x in data]
 | 
				
			||||||
 | 
					    prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
				
			||||||
    var = cg.new_Pvariable(
 | 
					    var = cg.new_Pvariable(
 | 
				
			||||||
        config[CONF_ID],
 | 
					        config[CONF_ID],
 | 
				
			||||||
        prog_arr,
 | 
					        prog_arr,
 | 
				
			||||||
        width,
 | 
					        width,
 | 
				
			||||||
        height,
 | 
					        height,
 | 
				
			||||||
        frame_count,
 | 
					        frames,
 | 
				
			||||||
        image_type,
 | 
					        espImage.IMAGE_TYPE[config[CONF_TYPE]],
 | 
				
			||||||
        trans_value,
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    cg.add(var.set_transparency(transparent))
 | 
				
			||||||
    if loop_config := config.get(CONF_LOOP):
 | 
					    if loop_config := config.get(CONF_LOOP):
 | 
				
			||||||
        start = loop_config[CONF_START_FRAME]
 | 
					        start = loop_config[CONF_START_FRAME]
 | 
				
			||||||
        end = loop_config.get(CONF_END_FRAME, frame_count)
 | 
					        end = loop_config.get(CONF_END_FRAME, frames)
 | 
				
			||||||
        count = loop_config.get(CONF_REPEAT, -1)
 | 
					        count = loop_config.get(CONF_REPEAT, -1)
 | 
				
			||||||
        cg.add(var.set_loop(start, end, count))
 | 
					        cg.add(var.set_loop(start, end, count))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,8 @@ namespace esphome {
 | 
				
			|||||||
namespace animation {
 | 
					namespace animation {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count,
 | 
					Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count,
 | 
				
			||||||
                     image::ImageType type, image::Transparency transparent)
 | 
					                     image::ImageType type)
 | 
				
			||||||
    : Image(data_start, width, height, type, transparent),
 | 
					    : Image(data_start, width, height, type),
 | 
				
			||||||
      animation_data_start_(data_start),
 | 
					      animation_data_start_(data_start),
 | 
				
			||||||
      current_frame_(0),
 | 
					      current_frame_(0),
 | 
				
			||||||
      animation_frame_count_(animation_frame_count),
 | 
					      animation_frame_count_(animation_frame_count),
 | 
				
			||||||
@@ -62,7 +62,7 @@ void Animation::set_frame(int frame) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Animation::update_data_start_() {
 | 
					void Animation::update_data_start_() {
 | 
				
			||||||
  const uint32_t image_size = this->get_width_stride() * this->height_;
 | 
					  const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_;
 | 
				
			||||||
  this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
 | 
					  this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,8 +8,7 @@ namespace animation {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Animation : public image::Image {
 | 
					class Animation : public image::Image {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type,
 | 
					  Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type);
 | 
				
			||||||
            image::Transparency transparent);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint32_t get_animation_frame_count() const;
 | 
					  uint32_t get_animation_frame_count() const;
 | 
				
			||||||
  int get_current_frame() const;
 | 
					  int get_current_frame() const;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import ble_client, climate
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_UNIT_OF_MEASUREMENT
 | 
					from esphome.components import climate, ble_client
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
UNITS = {
 | 
					UNITS = {
 | 
				
			||||||
    "f": "f",
 | 
					    "f": "f",
 | 
				
			||||||
@@ -17,9 +17,9 @@ Anova = anova_ns.class_(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = (
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
    climate.climate_schema(Anova)
 | 
					    climate.CLIMATE_SCHEMA.extend(
 | 
				
			||||||
    .extend(
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(Anova),
 | 
				
			||||||
            cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS),
 | 
					            cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@@ -29,7 +29,8 @@ CONFIG_SCHEMA = (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = await climate.new_climate(config)
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await climate.register_climate(var, config)
 | 
				
			||||||
    await ble_client.register_ble_node(var, config)
 | 
					    await ble_client.register_ble_node(var, config)
 | 
				
			||||||
    cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))
 | 
					    cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -122,8 +122,7 @@ void APDS9306::update() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  this->status_clear_warning();
 | 
					  this->status_clear_warning();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  status &= 0b00001000;
 | 
					  if (!(status &= 0b00001000)) {  // No new data
 | 
				
			||||||
  if (!status) {  // No new data
 | 
					 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,8 @@
 | 
				
			|||||||
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
 | 
					# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import i2c, sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import i2c, sensor
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_GAIN,
 | 
					    CONF_GAIN,
 | 
				
			||||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
					    DEVICE_CLASS_ILLUMINANCE,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import i2c
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import i2c
 | 
				
			||||||
from esphome.const import CONF_ID
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["i2c"]
 | 
					DEPENDENCIES = ["i2c"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import binary_sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import binary_sensor
 | 
				
			||||||
from esphome.const import CONF_DIRECTION, DEVICE_CLASS_MOVING
 | 
					from esphome.const import CONF_DIRECTION, DEVICE_CLASS_MOVING
 | 
				
			||||||
 | 
					 | 
				
			||||||
from . import APDS9960, CONF_APDS9960_ID
 | 
					from . import APDS9960, CONF_APDS9960_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["apds9960"]
 | 
					DEPENDENCIES = ["apds9960"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,12 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import sensor
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_TYPE,
 | 
					    CONF_TYPE,
 | 
				
			||||||
    ICON_LIGHTBULB,
 | 
					 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_PERCENT,
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
 | 
					    ICON_LIGHTBULB,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					 | 
				
			||||||
from . import APDS9960, CONF_APDS9960_ID
 | 
					from . import APDS9960, CONF_APDS9960_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["apds9960"]
 | 
					DEPENDENCIES = ["apds9960"]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,19 +82,6 @@ ACTIONS_SCHEMA = automation.validate_automation(
 | 
				
			|||||||
    ),
 | 
					    ),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENCRYPTION_SCHEMA = cv.Schema(
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        cv.Optional(CONF_KEY): validate_encryption_key,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def _encryption_schema(config):
 | 
					 | 
				
			||||||
    if config is None:
 | 
					 | 
				
			||||||
        config = {}
 | 
					 | 
				
			||||||
    return ENCRYPTION_SCHEMA(config)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CONFIG_SCHEMA = cv.All(
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
    cv.Schema(
 | 
					    cv.Schema(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -108,7 +95,11 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
                CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
 | 
					                CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
 | 
				
			||||||
            ): ACTIONS_SCHEMA,
 | 
					            ): ACTIONS_SCHEMA,
 | 
				
			||||||
            cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
 | 
					            cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
 | 
				
			||||||
            cv.Optional(CONF_ENCRYPTION): _encryption_schema,
 | 
					            cv.Optional(CONF_ENCRYPTION): cv.Schema(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    cv.Required(CONF_KEY): validate_encryption_key,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
 | 
					            cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
 | 
				
			||||||
                single=True
 | 
					                single=True
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@@ -160,17 +151,9 @@ async def to_code(config):
 | 
				
			|||||||
            config[CONF_ON_CLIENT_DISCONNECTED],
 | 
					            config[CONF_ON_CLIENT_DISCONNECTED],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (encryption_config := config.get(CONF_ENCRYPTION, None)) is not None:
 | 
					    if encryption_config := config.get(CONF_ENCRYPTION):
 | 
				
			||||||
        if key := encryption_config.get(CONF_KEY):
 | 
					        decoded = base64.b64decode(encryption_config[CONF_KEY])
 | 
				
			||||||
            decoded = base64.b64decode(key)
 | 
					        cg.add(var.set_noise_psk(list(decoded)))
 | 
				
			||||||
            cg.add(var.set_noise_psk(list(decoded)))
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            # No key provided, but encryption desired
 | 
					 | 
				
			||||||
            # This will allow a plaintext client to provide a noise key,
 | 
					 | 
				
			||||||
            # send it to the device, and then switch to noise.
 | 
					 | 
				
			||||||
            # The key will be saved in flash and used for future connections
 | 
					 | 
				
			||||||
            # and plaintext disabled. Only a factory reset can remove it.
 | 
					 | 
				
			||||||
            cg.add_define("USE_API_PLAINTEXT")
 | 
					 | 
				
			||||||
        cg.add_define("USE_API_NOISE")
 | 
					        cg.add_define("USE_API_NOISE")
 | 
				
			||||||
        cg.add_library("esphome/noise-c", "0.1.6")
 | 
					        cg.add_library("esphome/noise-c", "0.1.6")
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,26 +31,24 @@ service APIConnection {
 | 
				
			|||||||
    option (needs_authentication) = false;
 | 
					    option (needs_authentication) = false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  rpc execute_service (ExecuteServiceRequest) returns (void) {}
 | 
					  rpc execute_service (ExecuteServiceRequest) returns (void) {}
 | 
				
			||||||
  rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  rpc button_command (ButtonCommandRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc camera_image (CameraImageRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc climate_command (ClimateCommandRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc cover_command (CoverCommandRequest) returns (void) {}
 | 
					  rpc cover_command (CoverCommandRequest) returns (void) {}
 | 
				
			||||||
  rpc date_command (DateCommandRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc datetime_command (DateTimeCommandRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc fan_command (FanCommandRequest) returns (void) {}
 | 
					  rpc fan_command (FanCommandRequest) returns (void) {}
 | 
				
			||||||
  rpc light_command (LightCommandRequest) returns (void) {}
 | 
					  rpc light_command (LightCommandRequest) returns (void) {}
 | 
				
			||||||
  rpc lock_command (LockCommandRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc number_command (NumberCommandRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc select_command (SelectCommandRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc siren_command (SirenCommandRequest) returns (void) {}
 | 
					 | 
				
			||||||
  rpc switch_command (SwitchCommandRequest) returns (void) {}
 | 
					  rpc switch_command (SwitchCommandRequest) returns (void) {}
 | 
				
			||||||
 | 
					  rpc camera_image (CameraImageRequest) returns (void) {}
 | 
				
			||||||
 | 
					  rpc climate_command (ClimateCommandRequest) returns (void) {}
 | 
				
			||||||
 | 
					  rpc number_command (NumberCommandRequest) returns (void) {}
 | 
				
			||||||
  rpc text_command (TextCommandRequest) returns (void) {}
 | 
					  rpc text_command (TextCommandRequest) returns (void) {}
 | 
				
			||||||
  rpc time_command (TimeCommandRequest) returns (void) {}
 | 
					  rpc select_command (SelectCommandRequest) returns (void) {}
 | 
				
			||||||
  rpc update_command (UpdateCommandRequest) returns (void) {}
 | 
					  rpc button_command (ButtonCommandRequest) returns (void) {}
 | 
				
			||||||
 | 
					  rpc lock_command (LockCommandRequest) returns (void) {}
 | 
				
			||||||
  rpc valve_command (ValveCommandRequest) 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 subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
				
			||||||
  rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
 | 
					  rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
 | 
				
			||||||
@@ -62,11 +60,8 @@ service APIConnection {
 | 
				
			|||||||
  rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
 | 
					  rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
 | 
				
			||||||
  rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
 | 
					  rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
 | 
				
			||||||
  rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
					  rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
				
			||||||
  rpc bluetooth_scanner_set_mode(BluetoothScannerSetModeRequest) returns (void) {}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) 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) {}
 | 
					  rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -230,12 +225,6 @@ message DeviceInfoResponse {
 | 
				
			|||||||
  uint32 voice_assistant_feature_flags = 17;
 | 
					  uint32 voice_assistant_feature_flags = 17;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  string suggested_area = 16;
 | 
					  string suggested_area = 16;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
 | 
					 | 
				
			||||||
  string bluetooth_mac_address = 18;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Supports receiving and saving api encryption key
 | 
					 | 
				
			||||||
  bool api_encryption_supported = 19;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message ListEntitiesRequest {
 | 
					message ListEntitiesRequest {
 | 
				
			||||||
@@ -656,27 +645,10 @@ message SubscribeLogsResponse {
 | 
				
			|||||||
  option (no_delay) = false;
 | 
					  option (no_delay) = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  LogLevel level = 1;
 | 
					  LogLevel level = 1;
 | 
				
			||||||
  bytes message = 3;
 | 
					  string message = 3;
 | 
				
			||||||
  bool send_failed = 4;
 | 
					  bool send_failed = 4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ==================== NOISE ENCRYPTION ====================
 | 
					 | 
				
			||||||
message NoiseEncryptionSetKeyRequest {
 | 
					 | 
				
			||||||
  option (id) = 124;
 | 
					 | 
				
			||||||
  option (source) = SOURCE_CLIENT;
 | 
					 | 
				
			||||||
  option (ifdef) = "USE_API_NOISE";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bytes key = 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message NoiseEncryptionSetKeyResponse {
 | 
					 | 
				
			||||||
  option (id) = 125;
 | 
					 | 
				
			||||||
  option (source) = SOURCE_SERVER;
 | 
					 | 
				
			||||||
  option (ifdef) = "USE_API_NOISE";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool success = 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ==================== HOMEASSISTANT.SERVICE ====================
 | 
					// ==================== HOMEASSISTANT.SERVICE ====================
 | 
				
			||||||
message SubscribeHomeassistantServicesRequest {
 | 
					message SubscribeHomeassistantServicesRequest {
 | 
				
			||||||
  option (id) = 34;
 | 
					  option (id) = 34;
 | 
				
			||||||
@@ -912,7 +884,6 @@ message ClimateStateResponse {
 | 
				
			|||||||
  float target_temperature = 4;
 | 
					  float target_temperature = 4;
 | 
				
			||||||
  float target_temperature_low = 5;
 | 
					  float target_temperature_low = 5;
 | 
				
			||||||
  float target_temperature_high = 6;
 | 
					  float target_temperature_high = 6;
 | 
				
			||||||
  // For older peers, equal to preset == CLIMATE_PRESET_AWAY
 | 
					 | 
				
			||||||
  bool unused_legacy_away = 7;
 | 
					  bool unused_legacy_away = 7;
 | 
				
			||||||
  ClimateAction action = 8;
 | 
					  ClimateAction action = 8;
 | 
				
			||||||
  ClimateFanMode fan_mode = 9;
 | 
					  ClimateFanMode fan_mode = 9;
 | 
				
			||||||
@@ -938,7 +909,6 @@ message ClimateCommandRequest {
 | 
				
			|||||||
  float target_temperature_low = 7;
 | 
					  float target_temperature_low = 7;
 | 
				
			||||||
  bool has_target_temperature_high = 8;
 | 
					  bool has_target_temperature_high = 8;
 | 
				
			||||||
  float target_temperature_high = 9;
 | 
					  float target_temperature_high = 9;
 | 
				
			||||||
  // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
 | 
					 | 
				
			||||||
  bool unused_has_legacy_away = 10;
 | 
					  bool unused_has_legacy_away = 10;
 | 
				
			||||||
  bool unused_legacy_away = 11;
 | 
					  bool unused_legacy_away = 11;
 | 
				
			||||||
  bool has_fan_mode = 12;
 | 
					  bool has_fan_mode = 12;
 | 
				
			||||||
@@ -1041,49 +1011,6 @@ message SelectCommandRequest {
 | 
				
			|||||||
  string state = 2;
 | 
					  string state = 2;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ==================== SIREN ====================
 | 
					 | 
				
			||||||
message ListEntitiesSirenResponse {
 | 
					 | 
				
			||||||
  option (id) = 55;
 | 
					 | 
				
			||||||
  option (source) = SOURCE_SERVER;
 | 
					 | 
				
			||||||
  option (ifdef) = "USE_SIREN";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  string object_id = 1;
 | 
					 | 
				
			||||||
  fixed32 key = 2;
 | 
					 | 
				
			||||||
  string name = 3;
 | 
					 | 
				
			||||||
  string unique_id = 4;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  string icon = 5;
 | 
					 | 
				
			||||||
  bool disabled_by_default = 6;
 | 
					 | 
				
			||||||
  repeated string tones = 7;
 | 
					 | 
				
			||||||
  bool supports_duration = 8;
 | 
					 | 
				
			||||||
  bool supports_volume = 9;
 | 
					 | 
				
			||||||
  EntityCategory entity_category = 10;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
message SirenStateResponse {
 | 
					 | 
				
			||||||
  option (id) = 56;
 | 
					 | 
				
			||||||
  option (source) = SOURCE_SERVER;
 | 
					 | 
				
			||||||
  option (ifdef) = "USE_SIREN";
 | 
					 | 
				
			||||||
  option (no_delay) = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  fixed32 key = 1;
 | 
					 | 
				
			||||||
  bool state = 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
message SirenCommandRequest {
 | 
					 | 
				
			||||||
  option (id) = 57;
 | 
					 | 
				
			||||||
  option (source) = SOURCE_CLIENT;
 | 
					 | 
				
			||||||
  option (ifdef) = "USE_SIREN";
 | 
					 | 
				
			||||||
  option (no_delay) = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  fixed32 key = 1;
 | 
					 | 
				
			||||||
  bool has_state = 2;
 | 
					 | 
				
			||||||
  bool state = 3;
 | 
					 | 
				
			||||||
  bool has_tone = 4;
 | 
					 | 
				
			||||||
  string tone = 5;
 | 
					 | 
				
			||||||
  bool has_duration = 6;
 | 
					 | 
				
			||||||
  uint32 duration = 7;
 | 
					 | 
				
			||||||
  bool has_volume = 8;
 | 
					 | 
				
			||||||
  float volume = 9;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ==================== LOCK ====================
 | 
					// ==================== LOCK ====================
 | 
				
			||||||
enum LockState {
 | 
					enum LockState {
 | 
				
			||||||
@@ -1191,7 +1118,6 @@ message MediaPlayerSupportedFormat {
 | 
				
			|||||||
  uint32 sample_rate = 2;
 | 
					  uint32 sample_rate = 2;
 | 
				
			||||||
  uint32 num_channels = 3;
 | 
					  uint32 num_channels = 3;
 | 
				
			||||||
  MediaPlayerFormatPurpose purpose = 4;
 | 
					  MediaPlayerFormatPurpose purpose = 4;
 | 
				
			||||||
  uint32 sample_bytes = 5;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
message ListEntitiesMediaPlayerResponse {
 | 
					message ListEntitiesMediaPlayerResponse {
 | 
				
			||||||
  option (id) = 63;
 | 
					  option (id) = 63;
 | 
				
			||||||
@@ -1253,8 +1179,8 @@ message SubscribeBluetoothLEAdvertisementsRequest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
message BluetoothServiceData {
 | 
					message BluetoothServiceData {
 | 
				
			||||||
  string uuid = 1;
 | 
					  string uuid = 1;
 | 
				
			||||||
  repeated uint32 legacy_data = 2 [deprecated = true];  // Removed in api version 1.7
 | 
					  repeated uint32 legacy_data = 2 [deprecated = true];
 | 
				
			||||||
  bytes data = 3;  // Added in api version 1.7
 | 
					  bytes data = 3; // Changed in proto version 1.7
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
message BluetoothLEAdvertisementResponse {
 | 
					message BluetoothLEAdvertisementResponse {
 | 
				
			||||||
  option (id) = 67;
 | 
					  option (id) = 67;
 | 
				
			||||||
@@ -1263,7 +1189,7 @@ message BluetoothLEAdvertisementResponse {
 | 
				
			|||||||
  option (no_delay) = true;
 | 
					  option (no_delay) = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint64 address = 1;
 | 
					  uint64 address = 1;
 | 
				
			||||||
  bytes name = 2;
 | 
					  string name = 2;
 | 
				
			||||||
  sint32 rssi = 3;
 | 
					  sint32 rssi = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  repeated string service_uuids = 4;
 | 
					  repeated string service_uuids = 4;
 | 
				
			||||||
@@ -1452,7 +1378,6 @@ message BluetoothConnectionsFreeResponse {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  uint32 free = 1;
 | 
					  uint32 free = 1;
 | 
				
			||||||
  uint32 limit = 2;
 | 
					  uint32 limit = 2;
 | 
				
			||||||
  repeated uint64 allocated = 3;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message BluetoothGATTErrorResponse {
 | 
					message BluetoothGATTErrorResponse {
 | 
				
			||||||
@@ -1519,38 +1444,7 @@ message BluetoothDeviceClearCacheResponse {
 | 
				
			|||||||
  int32 error = 3;
 | 
					  int32 error = 3;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum BluetoothScannerState {
 | 
					// ==================== PUSH TO TALK ====================
 | 
				
			||||||
  BLUETOOTH_SCANNER_STATE_IDLE = 0;
 | 
					 | 
				
			||||||
  BLUETOOTH_SCANNER_STATE_STARTING = 1;
 | 
					 | 
				
			||||||
  BLUETOOTH_SCANNER_STATE_RUNNING = 2;
 | 
					 | 
				
			||||||
  BLUETOOTH_SCANNER_STATE_FAILED = 3;
 | 
					 | 
				
			||||||
  BLUETOOTH_SCANNER_STATE_STOPPING = 4;
 | 
					 | 
				
			||||||
  BLUETOOTH_SCANNER_STATE_STOPPED = 5;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum BluetoothScannerMode {
 | 
					 | 
				
			||||||
  BLUETOOTH_SCANNER_MODE_PASSIVE = 0;
 | 
					 | 
				
			||||||
  BLUETOOTH_SCANNER_MODE_ACTIVE = 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message BluetoothScannerStateResponse {
 | 
					 | 
				
			||||||
  option(id) = 126;
 | 
					 | 
				
			||||||
  option(source) = SOURCE_SERVER;
 | 
					 | 
				
			||||||
  option(ifdef) = "USE_BLUETOOTH_PROXY";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  BluetoothScannerState state = 1;
 | 
					 | 
				
			||||||
  BluetoothScannerMode mode = 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message BluetoothScannerSetModeRequest {
 | 
					 | 
				
			||||||
  option(id) = 127;
 | 
					 | 
				
			||||||
  option(source) = SOURCE_CLIENT;
 | 
					 | 
				
			||||||
  option(ifdef) = "USE_BLUETOOTH_PROXY";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  BluetoothScannerMode mode = 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ==================== VOICE ASSISTANT ====================
 | 
					 | 
				
			||||||
enum VoiceAssistantSubscribeFlag {
 | 
					enum VoiceAssistantSubscribeFlag {
 | 
				
			||||||
  VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
 | 
					  VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
 | 
				
			||||||
  VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1;
 | 
					  VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1;
 | 
				
			||||||
@@ -1666,8 +1560,6 @@ message VoiceAssistantAnnounceRequest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  string media_id = 1;
 | 
					  string media_id = 1;
 | 
				
			||||||
  string text = 2;
 | 
					  string text = 2;
 | 
				
			||||||
  string preannounce_media_id = 3;
 | 
					 | 
				
			||||||
  bool start_conversation = 4;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message VoiceAssistantAnnounceFinished {
 | 
					message VoiceAssistantAnnounceFinished {
 | 
				
			||||||
@@ -1678,36 +1570,6 @@ message VoiceAssistantAnnounceFinished {
 | 
				
			|||||||
  bool success = 1;
 | 
					  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 ====================
 | 
					// ==================== ALARM CONTROL PANEL ====================
 | 
				
			||||||
enum AlarmControlPanelState {
 | 
					enum AlarmControlPanelState {
 | 
				
			||||||
  ALARM_STATE_DISARMED = 0;
 | 
					  ALARM_STATE_DISARMED = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,64 +1,18 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/defines.h"
 | 
					 | 
				
			||||||
#ifdef USE_API
 | 
					 | 
				
			||||||
#include "api_frame_helper.h"
 | 
					#include "api_frame_helper.h"
 | 
				
			||||||
#include "api_pb2.h"
 | 
					#include "api_pb2.h"
 | 
				
			||||||
#include "api_pb2_service.h"
 | 
					#include "api_pb2_service.h"
 | 
				
			||||||
#include "api_server.h"
 | 
					#include "api_server.h"
 | 
				
			||||||
#include "esphome/core/application.h"
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/core/entity_base.h"
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace api {
 | 
					namespace api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Keepalive timeout in milliseconds
 | 
					 | 
				
			||||||
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using send_message_t = bool (APIConnection::*)(void *);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
  This class holds a pointer to the source component that wants to publish a message, and a pointer to a function that
 | 
					 | 
				
			||||||
  will lazily publish that message.  The two pointers allow dedup in the deferred queue if multiple publishes for the
 | 
					 | 
				
			||||||
  same component are backed up, and take up only 8 bytes of memory.  The entry in the deferred queue (a std::vector) is
 | 
					 | 
				
			||||||
  the DeferredMessage instance itself (not a pointer to one elsewhere in heap) so still only 8 bytes per entry.  Even
 | 
					 | 
				
			||||||
  100 backed up messages (you'd have to have at least 100 sensors publishing because of dedup) would take up only 0.8
 | 
					 | 
				
			||||||
  kB.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
class DeferredMessageQueue {
 | 
					 | 
				
			||||||
  struct DeferredMessage {
 | 
					 | 
				
			||||||
    friend class DeferredMessageQueue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   protected:
 | 
					 | 
				
			||||||
    void *source_;
 | 
					 | 
				
			||||||
    send_message_t send_message_;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   public:
 | 
					 | 
				
			||||||
    DeferredMessage(void *source, send_message_t send_message) : source_(source), send_message_(send_message) {}
 | 
					 | 
				
			||||||
    bool operator==(const DeferredMessage &test) const {
 | 
					 | 
				
			||||||
      return (source_ == test.source_ && send_message_ == test.send_message_);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } __attribute__((packed));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  // vector is used very specifically for its zero memory overhead even though items are popped from the front (memory
 | 
					 | 
				
			||||||
  // footprint is more important than speed here)
 | 
					 | 
				
			||||||
  std::vector<DeferredMessage> deferred_queue_;
 | 
					 | 
				
			||||||
  APIConnection *api_connection_;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // helper for allowing only unique entries in the queue
 | 
					 | 
				
			||||||
  void dmq_push_back_with_dedup_(void *source, send_message_t send_message);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
  DeferredMessageQueue(APIConnection *api_connection) : api_connection_(api_connection) {}
 | 
					 | 
				
			||||||
  void process_queue();
 | 
					 | 
				
			||||||
  void defer(void *source, send_message_t send_message);
 | 
					 | 
				
			||||||
  bool empty() const { return deferred_queue_.empty(); }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class APIConnection : public APIServerConnection {
 | 
					class APIConnection : public APIServerConnection {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
 | 
					  APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
 | 
				
			||||||
@@ -73,216 +27,96 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
#ifdef USE_BINARY_SENSOR
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
  bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
 | 
					  bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
 | 
				
			||||||
  void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
 | 
					  bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_binary_sensor_state_(binary_sensor::BinarySensor *binary_sensor);
 | 
					 | 
				
			||||||
  bool try_send_binary_sensor_state_(binary_sensor::BinarySensor *binary_sensor, bool state);
 | 
					 | 
				
			||||||
  bool try_send_binary_sensor_info_(binary_sensor::BinarySensor *binary_sensor);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_COVER
 | 
					#ifdef USE_COVER
 | 
				
			||||||
  bool send_cover_state(cover::Cover *cover);
 | 
					  bool send_cover_state(cover::Cover *cover);
 | 
				
			||||||
  void send_cover_info(cover::Cover *cover);
 | 
					  bool send_cover_info(cover::Cover *cover);
 | 
				
			||||||
  void cover_command(const CoverCommandRequest &msg) override;
 | 
					  void cover_command(const CoverCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_cover_state_(cover::Cover *cover);
 | 
					 | 
				
			||||||
  bool try_send_cover_info_(cover::Cover *cover);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_FAN
 | 
					#ifdef USE_FAN
 | 
				
			||||||
  bool send_fan_state(fan::Fan *fan);
 | 
					  bool send_fan_state(fan::Fan *fan);
 | 
				
			||||||
  void send_fan_info(fan::Fan *fan);
 | 
					  bool send_fan_info(fan::Fan *fan);
 | 
				
			||||||
  void fan_command(const FanCommandRequest &msg) override;
 | 
					  void fan_command(const FanCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_fan_state_(fan::Fan *fan);
 | 
					 | 
				
			||||||
  bool try_send_fan_info_(fan::Fan *fan);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_LIGHT
 | 
					#ifdef USE_LIGHT
 | 
				
			||||||
  bool send_light_state(light::LightState *light);
 | 
					  bool send_light_state(light::LightState *light);
 | 
				
			||||||
  void send_light_info(light::LightState *light);
 | 
					  bool send_light_info(light::LightState *light);
 | 
				
			||||||
  void light_command(const LightCommandRequest &msg) override;
 | 
					  void light_command(const LightCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_light_state_(light::LightState *light);
 | 
					 | 
				
			||||||
  bool try_send_light_info_(light::LightState *light);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SENSOR
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
  bool send_sensor_state(sensor::Sensor *sensor, float state);
 | 
					  bool send_sensor_state(sensor::Sensor *sensor, float state);
 | 
				
			||||||
  void send_sensor_info(sensor::Sensor *sensor);
 | 
					  bool send_sensor_info(sensor::Sensor *sensor);
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_sensor_state_(sensor::Sensor *sensor);
 | 
					 | 
				
			||||||
  bool try_send_sensor_state_(sensor::Sensor *sensor, float state);
 | 
					 | 
				
			||||||
  bool try_send_sensor_info_(sensor::Sensor *sensor);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SWITCH
 | 
					#ifdef USE_SWITCH
 | 
				
			||||||
  bool send_switch_state(switch_::Switch *a_switch, bool state);
 | 
					  bool send_switch_state(switch_::Switch *a_switch, bool state);
 | 
				
			||||||
  void send_switch_info(switch_::Switch *a_switch);
 | 
					  bool send_switch_info(switch_::Switch *a_switch);
 | 
				
			||||||
  void switch_command(const SwitchCommandRequest &msg) override;
 | 
					  void switch_command(const SwitchCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_switch_state_(switch_::Switch *a_switch);
 | 
					 | 
				
			||||||
  bool try_send_switch_state_(switch_::Switch *a_switch, bool state);
 | 
					 | 
				
			||||||
  bool try_send_switch_info_(switch_::Switch *a_switch);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_TEXT_SENSOR
 | 
					#ifdef USE_TEXT_SENSOR
 | 
				
			||||||
  bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
 | 
					  bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
 | 
				
			||||||
  void send_text_sensor_info(text_sensor::TextSensor *text_sensor);
 | 
					  bool send_text_sensor_info(text_sensor::TextSensor *text_sensor);
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_text_sensor_state_(text_sensor::TextSensor *text_sensor);
 | 
					 | 
				
			||||||
  bool try_send_text_sensor_state_(text_sensor::TextSensor *text_sensor, std::string state);
 | 
					 | 
				
			||||||
  bool try_send_text_sensor_info_(text_sensor::TextSensor *text_sensor);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_ESP32_CAMERA
 | 
					#ifdef USE_ESP32_CAMERA
 | 
				
			||||||
  void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
 | 
					  void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
 | 
				
			||||||
  void send_camera_info(esp32_camera::ESP32Camera *camera);
 | 
					  bool send_camera_info(esp32_camera::ESP32Camera *camera);
 | 
				
			||||||
  void camera_image(const CameraImageRequest &msg) override;
 | 
					  void camera_image(const CameraImageRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_camera_info_(esp32_camera::ESP32Camera *camera);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_CLIMATE
 | 
					#ifdef USE_CLIMATE
 | 
				
			||||||
  bool send_climate_state(climate::Climate *climate);
 | 
					  bool send_climate_state(climate::Climate *climate);
 | 
				
			||||||
  void send_climate_info(climate::Climate *climate);
 | 
					  bool send_climate_info(climate::Climate *climate);
 | 
				
			||||||
  void climate_command(const ClimateCommandRequest &msg) override;
 | 
					  void climate_command(const ClimateCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_climate_state_(climate::Climate *climate);
 | 
					 | 
				
			||||||
  bool try_send_climate_info_(climate::Climate *climate);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_NUMBER
 | 
					#ifdef USE_NUMBER
 | 
				
			||||||
  bool send_number_state(number::Number *number, float state);
 | 
					  bool send_number_state(number::Number *number, float state);
 | 
				
			||||||
  void send_number_info(number::Number *number);
 | 
					  bool send_number_info(number::Number *number);
 | 
				
			||||||
  void number_command(const NumberCommandRequest &msg) override;
 | 
					  void number_command(const NumberCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_number_state_(number::Number *number);
 | 
					 | 
				
			||||||
  bool try_send_number_state_(number::Number *number, float state);
 | 
					 | 
				
			||||||
  bool try_send_number_info_(number::Number *number);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_DATETIME_DATE
 | 
					#ifdef USE_DATETIME_DATE
 | 
				
			||||||
  bool send_date_state(datetime::DateEntity *date);
 | 
					  bool send_date_state(datetime::DateEntity *date);
 | 
				
			||||||
  void send_date_info(datetime::DateEntity *date);
 | 
					  bool send_date_info(datetime::DateEntity *date);
 | 
				
			||||||
  void date_command(const DateCommandRequest &msg) override;
 | 
					  void date_command(const DateCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_date_state_(datetime::DateEntity *date);
 | 
					 | 
				
			||||||
  bool try_send_date_info_(datetime::DateEntity *date);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_DATETIME_TIME
 | 
					#ifdef USE_DATETIME_TIME
 | 
				
			||||||
  bool send_time_state(datetime::TimeEntity *time);
 | 
					  bool send_time_state(datetime::TimeEntity *time);
 | 
				
			||||||
  void send_time_info(datetime::TimeEntity *time);
 | 
					  bool send_time_info(datetime::TimeEntity *time);
 | 
				
			||||||
  void time_command(const TimeCommandRequest &msg) override;
 | 
					  void time_command(const TimeCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_time_state_(datetime::TimeEntity *time);
 | 
					 | 
				
			||||||
  bool try_send_time_info_(datetime::TimeEntity *time);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_DATETIME_DATETIME
 | 
					#ifdef USE_DATETIME_DATETIME
 | 
				
			||||||
  bool send_datetime_state(datetime::DateTimeEntity *datetime);
 | 
					  bool send_datetime_state(datetime::DateTimeEntity *datetime);
 | 
				
			||||||
  void send_datetime_info(datetime::DateTimeEntity *datetime);
 | 
					  bool send_datetime_info(datetime::DateTimeEntity *datetime);
 | 
				
			||||||
  void datetime_command(const DateTimeCommandRequest &msg) override;
 | 
					  void datetime_command(const DateTimeCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_datetime_state_(datetime::DateTimeEntity *datetime);
 | 
					 | 
				
			||||||
  bool try_send_datetime_info_(datetime::DateTimeEntity *datetime);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_TEXT
 | 
					#ifdef USE_TEXT
 | 
				
			||||||
  bool send_text_state(text::Text *text, std::string state);
 | 
					  bool send_text_state(text::Text *text, std::string state);
 | 
				
			||||||
  void send_text_info(text::Text *text);
 | 
					  bool send_text_info(text::Text *text);
 | 
				
			||||||
  void text_command(const TextCommandRequest &msg) override;
 | 
					  void text_command(const TextCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_text_state_(text::Text *text);
 | 
					 | 
				
			||||||
  bool try_send_text_state_(text::Text *text, std::string state);
 | 
					 | 
				
			||||||
  bool try_send_text_info_(text::Text *text);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SELECT
 | 
					#ifdef USE_SELECT
 | 
				
			||||||
  bool send_select_state(select::Select *select, std::string state);
 | 
					  bool send_select_state(select::Select *select, std::string state);
 | 
				
			||||||
  void send_select_info(select::Select *select);
 | 
					  bool send_select_info(select::Select *select);
 | 
				
			||||||
  void select_command(const SelectCommandRequest &msg) override;
 | 
					  void select_command(const SelectCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_select_state_(select::Select *select);
 | 
					 | 
				
			||||||
  bool try_send_select_state_(select::Select *select, std::string state);
 | 
					 | 
				
			||||||
  bool try_send_select_info_(select::Select *select);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BUTTON
 | 
					#ifdef USE_BUTTON
 | 
				
			||||||
  void send_button_info(button::Button *button);
 | 
					  bool send_button_info(button::Button *button);
 | 
				
			||||||
  void button_command(const ButtonCommandRequest &msg) override;
 | 
					  void button_command(const ButtonCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_button_info_(button::Button *button);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_LOCK
 | 
					#ifdef USE_LOCK
 | 
				
			||||||
  bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
 | 
					  bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
 | 
				
			||||||
  void send_lock_info(lock::Lock *a_lock);
 | 
					  bool send_lock_info(lock::Lock *a_lock);
 | 
				
			||||||
  void lock_command(const LockCommandRequest &msg) override;
 | 
					  void lock_command(const LockCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_lock_state_(lock::Lock *a_lock);
 | 
					 | 
				
			||||||
  bool try_send_lock_state_(lock::Lock *a_lock, lock::LockState state);
 | 
					 | 
				
			||||||
  bool try_send_lock_info_(lock::Lock *a_lock);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VALVE
 | 
					#ifdef USE_VALVE
 | 
				
			||||||
  bool send_valve_state(valve::Valve *valve);
 | 
					  bool send_valve_state(valve::Valve *valve);
 | 
				
			||||||
  void send_valve_info(valve::Valve *valve);
 | 
					  bool send_valve_info(valve::Valve *valve);
 | 
				
			||||||
  void valve_command(const ValveCommandRequest &msg) override;
 | 
					  void valve_command(const ValveCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_valve_state_(valve::Valve *valve);
 | 
					 | 
				
			||||||
  bool try_send_valve_info_(valve::Valve *valve);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_MEDIA_PLAYER
 | 
					#ifdef USE_MEDIA_PLAYER
 | 
				
			||||||
  bool send_media_player_state(media_player::MediaPlayer *media_player);
 | 
					  bool send_media_player_state(media_player::MediaPlayer *media_player);
 | 
				
			||||||
  void send_media_player_info(media_player::MediaPlayer *media_player);
 | 
					  bool send_media_player_info(media_player::MediaPlayer *media_player);
 | 
				
			||||||
  void media_player_command(const MediaPlayerCommandRequest &msg) override;
 | 
					  void media_player_command(const MediaPlayerCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_media_player_state_(media_player::MediaPlayer *media_player);
 | 
					 | 
				
			||||||
  bool try_send_media_player_info_(media_player::MediaPlayer *media_player);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  bool try_send_log_message(int level, const char *tag, const char *line);
 | 
					  bool send_log_message(int level, const char *tag, const char *line);
 | 
				
			||||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
					  void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
				
			||||||
    if (!this->service_call_subscription_)
 | 
					    if (!this->service_call_subscription_)
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
@@ -302,7 +136,6 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
 | 
					  void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
 | 
				
			||||||
  BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
 | 
					  BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
 | 
				
			||||||
      const SubscribeBluetoothConnectionsFreeRequest &msg) override;
 | 
					      const SubscribeBluetoothConnectionsFreeRequest &msg) override;
 | 
				
			||||||
  void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
					#ifdef USE_HOMEASSISTANT_TIME
 | 
				
			||||||
@@ -319,45 +152,23 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  void on_voice_assistant_audio(const VoiceAssistantAudio &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_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
 | 
				
			||||||
  void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &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
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
  bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
					  bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
				
			||||||
  void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
					  bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
				
			||||||
  void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
 | 
					  void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_alarm_control_panel_state_(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
					 | 
				
			||||||
  bool try_send_alarm_control_panel_info_(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_EVENT
 | 
					#ifdef USE_EVENT
 | 
				
			||||||
  void send_event(event::Event *event, std::string event_type);
 | 
					  bool send_event(event::Event *event, std::string event_type);
 | 
				
			||||||
  void send_event_info(event::Event *event);
 | 
					  bool send_event_info(event::Event *event);
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_event_(event::Event *event);
 | 
					 | 
				
			||||||
  bool try_send_event_(event::Event *event, std::string event_type);
 | 
					 | 
				
			||||||
  bool try_send_event_info_(event::Event *event);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_UPDATE
 | 
					#ifdef USE_UPDATE
 | 
				
			||||||
  bool send_update_state(update::UpdateEntity *update);
 | 
					  bool send_update_state(update::UpdateEntity *update);
 | 
				
			||||||
  void send_update_info(update::UpdateEntity *update);
 | 
					  bool send_update_info(update::UpdateEntity *update);
 | 
				
			||||||
  void update_command(const UpdateCommandRequest &msg) override;
 | 
					  void update_command(const UpdateCommandRequest &msg) override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  bool try_send_update_state_(update::UpdateEntity *update);
 | 
					 | 
				
			||||||
  bool try_send_update_info_(update::UpdateEntity *update);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void on_disconnect_response(const DisconnectResponse &value) override;
 | 
					  void on_disconnect_response(const DisconnectResponse &value) override;
 | 
				
			||||||
@@ -394,9 +205,6 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
    return {};
 | 
					    return {};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  void execute_service(const ExecuteServiceRequest &msg) override;
 | 
					  void execute_service(const ExecuteServiceRequest &msg) override;
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					 | 
				
			||||||
  NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
 | 
					  bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
 | 
				
			||||||
  bool is_connection_setup() override {
 | 
					  bool is_connection_setup() override {
 | 
				
			||||||
@@ -405,20 +213,11 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  void on_fatal_error() override;
 | 
					  void on_fatal_error() override;
 | 
				
			||||||
  void on_unauthenticated_access() override;
 | 
					  void on_unauthenticated_access() override;
 | 
				
			||||||
  void on_no_setup_connection() override;
 | 
					  void on_no_setup_connection() override;
 | 
				
			||||||
  ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
 | 
					  ProtoWriteBuffer create_buffer() override {
 | 
				
			||||||
    // FIXME: ensure no recursive writes can happen
 | 
					    // FIXME: ensure no recursive writes can happen
 | 
				
			||||||
    this->proto_write_buffer_.clear();
 | 
					    this->proto_write_buffer_.clear();
 | 
				
			||||||
    // Get header padding size - used for both reserve and insert
 | 
					 | 
				
			||||||
    uint8_t header_padding = this->helper_->frame_header_padding();
 | 
					 | 
				
			||||||
    // Reserve space for header padding + message + footer
 | 
					 | 
				
			||||||
    // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
 | 
					 | 
				
			||||||
    // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
 | 
					 | 
				
			||||||
    this->proto_write_buffer_.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
 | 
					 | 
				
			||||||
    // Insert header padding bytes so message encoding starts at the correct position
 | 
					 | 
				
			||||||
    this->proto_write_buffer_.insert(this->proto_write_buffer_.begin(), header_padding, 0);
 | 
					 | 
				
			||||||
    return {&this->proto_write_buffer_};
 | 
					    return {&this->proto_write_buffer_};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  bool try_to_clear_buffer(bool log_out_of_space);
 | 
					 | 
				
			||||||
  bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
 | 
					  bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::string get_client_combined_info() const { return this->client_combined_info_; }
 | 
					  std::string get_client_combined_info() const { return this->client_combined_info_; }
 | 
				
			||||||
@@ -426,99 +225,6 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  friend APIServer;
 | 
					  friend APIServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Generic send entity state method to reduce code duplication.
 | 
					 | 
				
			||||||
   * Only attempts to build and send the message if the transmit buffer is available.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * This is the base version for entities that use their current state.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * @param entity The entity to send state for
 | 
					 | 
				
			||||||
   * @param try_send_func The function that tries to send the state
 | 
					 | 
				
			||||||
   * @return True on success or message deferred, false if subscription check failed
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  bool send_state_(esphome::EntityBase *entity, send_message_t try_send_func) {
 | 
					 | 
				
			||||||
    if (!this->state_subscription_)
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    if (this->try_to_clear_buffer(true) && (this->*try_send_func)(entity)) {
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this->deferred_message_queue_.defer(entity, try_send_func);
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Send entity state method that handles explicit state values.
 | 
					 | 
				
			||||||
   * Only attempts to build and send the message if the transmit buffer is available.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * This method accepts a state parameter to be used instead of the entity's current state.
 | 
					 | 
				
			||||||
   * It attempts to send the state with the provided value first, and if that fails due to buffer constraints,
 | 
					 | 
				
			||||||
   * it defers the entity for later processing using the entity-only function.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * @tparam EntityT The entity type
 | 
					 | 
				
			||||||
   * @tparam StateT Type of the state parameter
 | 
					 | 
				
			||||||
   * @tparam Args Additional argument types (if any)
 | 
					 | 
				
			||||||
   * @param entity The entity to send state for
 | 
					 | 
				
			||||||
   * @param try_send_entity_func The function that tries to send the state with entity pointer only
 | 
					 | 
				
			||||||
   * @param try_send_state_func The function that tries to send the state with entity and state parameters
 | 
					 | 
				
			||||||
   * @param state The state value to send
 | 
					 | 
				
			||||||
   * @param args Additional arguments to pass to the try_send_state_func
 | 
					 | 
				
			||||||
   * @return True on success or message deferred, false if subscription check failed
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  template<typename EntityT, typename StateT, typename... Args>
 | 
					 | 
				
			||||||
  bool send_state_with_value_(EntityT *entity, bool (APIConnection::*try_send_entity_func)(EntityT *),
 | 
					 | 
				
			||||||
                              bool (APIConnection::*try_send_state_func)(EntityT *, StateT, Args...), StateT state,
 | 
					 | 
				
			||||||
                              Args... args) {
 | 
					 | 
				
			||||||
    if (!this->state_subscription_)
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    if (this->try_to_clear_buffer(true) && (this->*try_send_state_func)(entity, state, args...)) {
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this->deferred_message_queue_.defer(entity, reinterpret_cast<send_message_t>(try_send_entity_func));
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Generic send entity info method to reduce code duplication.
 | 
					 | 
				
			||||||
   * Only attempts to build and send the message if the transmit buffer is available.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * @param entity The entity to send info for
 | 
					 | 
				
			||||||
   * @param try_send_func The function that tries to send the info
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  void send_info_(esphome::EntityBase *entity, send_message_t try_send_func) {
 | 
					 | 
				
			||||||
    if (this->try_to_clear_buffer(true) && (this->*try_send_func)(entity)) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this->deferred_message_queue_.defer(entity, try_send_func);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Generic function for generating entity info response messages.
 | 
					 | 
				
			||||||
   * This is used to reduce duplication in the try_send_*_info functions.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * @param entity The entity to generate info for
 | 
					 | 
				
			||||||
   * @param response The response object
 | 
					 | 
				
			||||||
   * @param send_response_func Function pointer to send the response
 | 
					 | 
				
			||||||
   * @return True if the message was sent successfully
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  template<typename ResponseT>
 | 
					 | 
				
			||||||
  bool try_send_entity_info_(esphome::EntityBase *entity, ResponseT &response,
 | 
					 | 
				
			||||||
                             bool (APIServerConnectionBase::*send_response_func)(const ResponseT &)) {
 | 
					 | 
				
			||||||
    // Set common fields that are shared by all entity types
 | 
					 | 
				
			||||||
    response.key = entity->get_object_id_hash();
 | 
					 | 
				
			||||||
    response.object_id = entity->get_object_id();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (entity->has_own_name())
 | 
					 | 
				
			||||||
      response.name = entity->get_name();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Set common EntityBase properties
 | 
					 | 
				
			||||||
    response.icon = entity->get_icon();
 | 
					 | 
				
			||||||
    response.disabled_by_default = entity->is_disabled_by_default();
 | 
					 | 
				
			||||||
    response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Send the response using the provided send method
 | 
					 | 
				
			||||||
    return (this->*send_response_func)(response);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool send_(const void *buf, size_t len, bool force);
 | 
					  bool send_(const void *buf, size_t len, bool force);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  enum class ConnectionState {
 | 
					  enum class ConnectionState {
 | 
				
			||||||
@@ -552,7 +258,6 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  bool service_call_subscription_{false};
 | 
					  bool service_call_subscription_{false};
 | 
				
			||||||
  bool next_close_ = false;
 | 
					  bool next_close_ = false;
 | 
				
			||||||
  APIServer *parent_;
 | 
					  APIServer *parent_;
 | 
				
			||||||
  DeferredMessageQueue deferred_message_queue_;
 | 
					 | 
				
			||||||
  InitialStateIterator initial_state_iterator_;
 | 
					  InitialStateIterator initial_state_iterator_;
 | 
				
			||||||
  ListEntitiesIterator list_entities_iterator_;
 | 
					  ListEntitiesIterator list_entities_iterator_;
 | 
				
			||||||
  int state_subs_at_ = -1;
 | 
					  int state_subs_at_ = -1;
 | 
				
			||||||
@@ -560,4 +265,3 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,10 @@
 | 
				
			|||||||
#include "api_frame_helper.h"
 | 
					#include "api_frame_helper.h"
 | 
				
			||||||
#ifdef USE_API
 | 
					
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
#include "esphome/core/hal.h"
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
#include "esphome/core/application.h"
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
#include "proto.h"
 | 
					#include "proto.h"
 | 
				
			||||||
#include "api_pb2_size.h"
 | 
					 | 
				
			||||||
#include <cstring>
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
@@ -73,91 +72,6 @@ const char *api_error_to_str(APIError err) {
 | 
				
			|||||||
  return "UNKNOWN";
 | 
					  return "UNKNOWN";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Common implementation for writing raw data to socket
 | 
					 | 
				
			||||||
template<typename StateEnum>
 | 
					 | 
				
			||||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket,
 | 
					 | 
				
			||||||
                                    std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state,
 | 
					 | 
				
			||||||
                                    StateEnum failed_state) {
 | 
					 | 
				
			||||||
  // This method writes data to socket or buffers it
 | 
					 | 
				
			||||||
  // Returns APIError::OK if successful (or would block, but data has been buffered)
 | 
					 | 
				
			||||||
  // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (iovcnt == 0)
 | 
					 | 
				
			||||||
    return APIError::OK;  // Nothing to do, success
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  size_t total_write_len = 0;
 | 
					 | 
				
			||||||
  for (int i = 0; i < iovcnt; i++) {
 | 
					 | 
				
			||||||
#ifdef HELPER_LOG_PACKETS
 | 
					 | 
				
			||||||
    ESP_LOGVV(TAG, "Sending raw: %s",
 | 
					 | 
				
			||||||
              format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
    total_write_len += iov[i].iov_len;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!tx_buf.empty()) {
 | 
					 | 
				
			||||||
    // try to empty tx_buf first
 | 
					 | 
				
			||||||
    while (!tx_buf.empty()) {
 | 
					 | 
				
			||||||
      ssize_t sent = socket->write(tx_buf.data(), tx_buf.size());
 | 
					 | 
				
			||||||
      if (is_would_block(sent)) {
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      } else if (sent == -1) {
 | 
					 | 
				
			||||||
        ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
 | 
					 | 
				
			||||||
        state = failed_state;
 | 
					 | 
				
			||||||
        return APIError::SOCKET_WRITE_FAILED;  // Socket write failed
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      // TODO: inefficient if multiple packets in txbuf
 | 
					 | 
				
			||||||
      // replace with deque of buffers
 | 
					 | 
				
			||||||
      tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!tx_buf.empty()) {
 | 
					 | 
				
			||||||
    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
					 | 
				
			||||||
    // Reserve space upfront to avoid multiple reallocations
 | 
					 | 
				
			||||||
    tx_buf.reserve(tx_buf.size() + total_write_len);
 | 
					 | 
				
			||||||
    for (int i = 0; i < iovcnt; i++) {
 | 
					 | 
				
			||||||
      tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
					 | 
				
			||||||
                    reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return APIError::OK;  // Success, data buffered
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ssize_t sent = socket->writev(iov, iovcnt);
 | 
					 | 
				
			||||||
  if (is_would_block(sent)) {
 | 
					 | 
				
			||||||
    // operation would block, add buffer to tx_buf
 | 
					 | 
				
			||||||
    // Reserve space upfront to avoid multiple reallocations
 | 
					 | 
				
			||||||
    tx_buf.reserve(tx_buf.size() + total_write_len);
 | 
					 | 
				
			||||||
    for (int i = 0; i < iovcnt; i++) {
 | 
					 | 
				
			||||||
      tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
					 | 
				
			||||||
                    reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return APIError::OK;  // Success, data buffered
 | 
					 | 
				
			||||||
  } else if (sent == -1) {
 | 
					 | 
				
			||||||
    // an error occurred
 | 
					 | 
				
			||||||
    ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
 | 
					 | 
				
			||||||
    state = failed_state;
 | 
					 | 
				
			||||||
    return APIError::SOCKET_WRITE_FAILED;  // Socket write failed
 | 
					 | 
				
			||||||
  } else if ((size_t) sent != total_write_len) {
 | 
					 | 
				
			||||||
    // partially sent, add end to tx_buf
 | 
					 | 
				
			||||||
    size_t remaining = total_write_len - sent;
 | 
					 | 
				
			||||||
    // Reserve space upfront to avoid multiple reallocations
 | 
					 | 
				
			||||||
    tx_buf.reserve(tx_buf.size() + remaining);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    size_t to_consume = sent;
 | 
					 | 
				
			||||||
    for (int i = 0; i < iovcnt; i++) {
 | 
					 | 
				
			||||||
      if (to_consume >= iov[i].iov_len) {
 | 
					 | 
				
			||||||
        to_consume -= iov[i].iov_len;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
 | 
					 | 
				
			||||||
                      reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
					 | 
				
			||||||
        to_consume = 0;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return APIError::OK;  // Success, data buffered
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return APIError::OK;  // Success, all data sent
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
 | 
					#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
 | 
				
			||||||
// uncomment to log raw packets
 | 
					// uncomment to log raw packets
 | 
				
			||||||
//#define HELPER_LOG_PACKETS
 | 
					//#define HELPER_LOG_PACKETS
 | 
				
			||||||
@@ -397,10 +311,6 @@ APIError APINoiseFrameHelper::state_action_() {
 | 
				
			|||||||
    const std::string &name = App.get_name();
 | 
					    const std::string &name = App.get_name();
 | 
				
			||||||
    const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
 | 
					    const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
 | 
				
			||||||
    msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
 | 
					    msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
 | 
				
			||||||
    // node mac, terminated by null byte
 | 
					 | 
				
			||||||
    const std::string &mac = get_mac_address();
 | 
					 | 
				
			||||||
    const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str());
 | 
					 | 
				
			||||||
    msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    aerr = write_frame_(msg.data(), msg.size());
 | 
					    aerr = write_frame_(msg.data(), msg.size());
 | 
				
			||||||
    if (aerr != APIError::OK)
 | 
					    if (aerr != APIError::OK)
 | 
				
			||||||
@@ -493,12 +403,9 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
 | 
				
			|||||||
  std::vector<uint8_t> data;
 | 
					  std::vector<uint8_t> data;
 | 
				
			||||||
  data.resize(reason.length() + 1);
 | 
					  data.resize(reason.length() + 1);
 | 
				
			||||||
  data[0] = 0x01;  // failure
 | 
					  data[0] = 0x01;  // failure
 | 
				
			||||||
 | 
					  for (size_t i = 0; i < reason.length(); i++) {
 | 
				
			||||||
  // Copy error message in bulk
 | 
					    data[i + 1] = (uint8_t) reason[i];
 | 
				
			||||||
  if (!reason.empty()) {
 | 
					 | 
				
			||||||
    std::memcpy(data.data() + 1, reason.c_str(), reason.length());
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // temporarily remove failed state
 | 
					  // temporarily remove failed state
 | 
				
			||||||
  auto orig_state = state_;
 | 
					  auto orig_state = state_;
 | 
				
			||||||
  state_ = State::EXPLICIT_REJECT;
 | 
					  state_ = State::EXPLICIT_REJECT;
 | 
				
			||||||
@@ -560,7 +467,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
				
			|||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
 | 
					bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
 | 
				
			||||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
 | 
					APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
 | 
				
			||||||
  int err;
 | 
					  int err;
 | 
				
			||||||
  APIError aerr;
 | 
					  APIError aerr;
 | 
				
			||||||
  aerr = state_action_();
 | 
					  aerr = state_action_();
 | 
				
			||||||
@@ -572,36 +479,31 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
 | 
				
			|||||||
    return APIError::WOULD_BLOCK;
 | 
					    return APIError::WOULD_BLOCK;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
 | 
					 | 
				
			||||||
  // Message data starts after padding
 | 
					 | 
				
			||||||
  size_t payload_len = raw_buffer->size() - frame_header_padding_;
 | 
					 | 
				
			||||||
  size_t padding = 0;
 | 
					  size_t padding = 0;
 | 
				
			||||||
  size_t msg_len = 4 + payload_len + padding;
 | 
					  size_t msg_len = 4 + payload_len + padding;
 | 
				
			||||||
 | 
					  size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
 | 
				
			||||||
 | 
					  auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
 | 
				
			||||||
 | 
					  if (tmpbuf == nullptr) {
 | 
				
			||||||
 | 
					    HELPER_LOG("Could not allocate for writing packet");
 | 
				
			||||||
 | 
					    return APIError::OUT_OF_MEMORY;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // We need to resize to include MAC space, but we already reserved it in create_buffer
 | 
					  tmpbuf[0] = 0x01;  // indicator
 | 
				
			||||||
  raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
 | 
					  // tmpbuf[1], tmpbuf[2] to be set later
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Write the noise header in the padded area
 | 
					 | 
				
			||||||
  // Buffer layout:
 | 
					 | 
				
			||||||
  // [0]    - 0x01 indicator byte
 | 
					 | 
				
			||||||
  // [1-2]  - Size of encrypted payload (filled after encryption)
 | 
					 | 
				
			||||||
  // [3-4]  - Message type (encrypted)
 | 
					 | 
				
			||||||
  // [5-6]  - Payload length (encrypted)
 | 
					 | 
				
			||||||
  // [7...] - Actual payload data (encrypted)
 | 
					 | 
				
			||||||
  uint8_t *buf_start = raw_buffer->data();
 | 
					 | 
				
			||||||
  buf_start[0] = 0x01;  // indicator
 | 
					 | 
				
			||||||
  // buf_start[1], buf_start[2] to be set later after encryption
 | 
					 | 
				
			||||||
  const uint8_t msg_offset = 3;
 | 
					  const uint8_t msg_offset = 3;
 | 
				
			||||||
  buf_start[msg_offset + 0] = (uint8_t) (type >> 8);         // type high byte
 | 
					  const uint8_t payload_offset = msg_offset + 4;
 | 
				
			||||||
  buf_start[msg_offset + 1] = (uint8_t) type;                // type low byte
 | 
					  tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8);  // type
 | 
				
			||||||
  buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8);  // data_len high byte
 | 
					  tmpbuf[msg_offset + 1] = (uint8_t) type;
 | 
				
			||||||
  buf_start[msg_offset + 3] = (uint8_t) payload_len;         // data_len low byte
 | 
					  tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8);  // data_len
 | 
				
			||||||
  // payload data is already in the buffer starting at position 7
 | 
					  tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
 | 
				
			||||||
 | 
					  // copy data
 | 
				
			||||||
 | 
					  std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
 | 
				
			||||||
 | 
					  // fill padding with zeros
 | 
				
			||||||
 | 
					  std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  NoiseBuffer mbuf;
 | 
					  NoiseBuffer mbuf;
 | 
				
			||||||
  noise_buffer_init(mbuf);
 | 
					  noise_buffer_init(mbuf);
 | 
				
			||||||
  // The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
 | 
					  noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
 | 
				
			||||||
  noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
 | 
					 | 
				
			||||||
  err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
 | 
					  err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
 | 
				
			||||||
  if (err != 0) {
 | 
					  if (err != 0) {
 | 
				
			||||||
    state_ = State::FAILED;
 | 
					    state_ = State::FAILED;
 | 
				
			||||||
@@ -610,13 +512,11 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  size_t total_len = 3 + mbuf.size;
 | 
					  size_t total_len = 3 + mbuf.size;
 | 
				
			||||||
  buf_start[1] = (uint8_t) (mbuf.size >> 8);
 | 
					  tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
 | 
				
			||||||
  buf_start[2] = (uint8_t) mbuf.size;
 | 
					  tmpbuf[2] = (uint8_t) mbuf.size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  struct iovec iov;
 | 
					  struct iovec iov;
 | 
				
			||||||
  // Point iov_base to the beginning of the buffer (no unused padding in Noise)
 | 
					  iov.iov_base = &tmpbuf[0];
 | 
				
			||||||
  // We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
 | 
					 | 
				
			||||||
  iov.iov_base = buf_start;
 | 
					 | 
				
			||||||
  iov.iov_len = total_len;
 | 
					  iov.iov_len = total_len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // write raw to not have two packets sent if NAGLE disabled
 | 
					  // write raw to not have two packets sent if NAGLE disabled
 | 
				
			||||||
@@ -642,6 +542,71 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					/** Write the data to the socket, or buffer it a write would block
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param data The data to write
 | 
				
			||||||
 | 
					 * @param len The length of data
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
				
			||||||
 | 
					  if (iovcnt == 0)
 | 
				
			||||||
 | 
					    return APIError::OK;
 | 
				
			||||||
 | 
					  APIError aerr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size_t total_write_len = 0;
 | 
				
			||||||
 | 
					  for (int i = 0; i < iovcnt; i++) {
 | 
				
			||||||
 | 
					#ifdef HELPER_LOG_PACKETS
 | 
				
			||||||
 | 
					    ESP_LOGVV(TAG, "Sending raw: %s",
 | 
				
			||||||
 | 
					              format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    total_write_len += iov[i].iov_len;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!tx_buf_.empty()) {
 | 
				
			||||||
 | 
					    // try to empty tx_buf_ first
 | 
				
			||||||
 | 
					    aerr = try_send_tx_buf_();
 | 
				
			||||||
 | 
					    if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
 | 
				
			||||||
 | 
					      return aerr;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!tx_buf_.empty()) {
 | 
				
			||||||
 | 
					    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
				
			||||||
 | 
					    for (int i = 0; i < iovcnt; i++) {
 | 
				
			||||||
 | 
					      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
				
			||||||
 | 
					                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return APIError::OK;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ssize_t sent = socket_->writev(iov, iovcnt);
 | 
				
			||||||
 | 
					  if (is_would_block(sent)) {
 | 
				
			||||||
 | 
					    // operation would block, add buffer to tx_buf
 | 
				
			||||||
 | 
					    for (int i = 0; i < iovcnt; i++) {
 | 
				
			||||||
 | 
					      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
				
			||||||
 | 
					                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return APIError::OK;
 | 
				
			||||||
 | 
					  } else if (sent == -1) {
 | 
				
			||||||
 | 
					    // an error occurred
 | 
				
			||||||
 | 
					    state_ = State::FAILED;
 | 
				
			||||||
 | 
					    HELPER_LOG("Socket write failed with errno %d", errno);
 | 
				
			||||||
 | 
					    return APIError::SOCKET_WRITE_FAILED;
 | 
				
			||||||
 | 
					  } else if ((size_t) sent != total_write_len) {
 | 
				
			||||||
 | 
					    // partially sent, add end to tx_buf
 | 
				
			||||||
 | 
					    size_t to_consume = sent;
 | 
				
			||||||
 | 
					    for (int i = 0; i < iovcnt; i++) {
 | 
				
			||||||
 | 
					      if (to_consume >= iov[i].iov_len) {
 | 
				
			||||||
 | 
					        to_consume -= iov[i].iov_len;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
 | 
				
			||||||
 | 
					                       reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
				
			||||||
 | 
					        to_consume = 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return APIError::OK;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // fully sent
 | 
				
			||||||
 | 
					  return APIError::OK;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
 | 
					APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
 | 
				
			||||||
  uint8_t header[3];
 | 
					  uint8_t header[3];
 | 
				
			||||||
  header[0] = 0x01;  // indicator
 | 
					  header[0] = 0x01;  // indicator
 | 
				
			||||||
@@ -728,8 +693,6 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
 | 
				
			|||||||
    return APIError::HANDSHAKESTATE_SPLIT_FAILED;
 | 
					    return APIError::HANDSHAKESTATE_SPLIT_FAILED;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  HELPER_LOG("Handshake complete!");
 | 
					  HELPER_LOG("Handshake complete!");
 | 
				
			||||||
  noise_handshakestate_free(handshake_);
 | 
					  noise_handshakestate_free(handshake_);
 | 
				
			||||||
  handshake_ = nullptr;
 | 
					  handshake_ = nullptr;
 | 
				
			||||||
@@ -777,11 +740,6 @@ void noise_rand_bytes(void *output, size_t len) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Explicit template instantiation for Noise
 | 
					 | 
				
			||||||
template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
 | 
					 | 
				
			||||||
    const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
 | 
					 | 
				
			||||||
    APINoiseFrameHelper::State &state, APINoiseFrameHelper::State failed_state);
 | 
					 | 
				
			||||||
#endif  // USE_API_NOISE
 | 
					#endif  // USE_API_NOISE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_API_PLAINTEXT
 | 
					#ifdef USE_API_PLAINTEXT
 | 
				
			||||||
@@ -842,10 +800,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
				
			|||||||
  // read header
 | 
					  // read header
 | 
				
			||||||
  while (!rx_header_parsed_) {
 | 
					  while (!rx_header_parsed_) {
 | 
				
			||||||
    uint8_t data;
 | 
					    uint8_t data;
 | 
				
			||||||
    // Reading one byte at a time is fastest in practice for ESP32 when
 | 
					 | 
				
			||||||
    // there is no data on the wire (which is the common case).
 | 
					 | 
				
			||||||
    // This results in faster failure detection compared to
 | 
					 | 
				
			||||||
    // attempting to read multiple bytes at once.
 | 
					 | 
				
			||||||
    ssize_t received = socket_->read(&data, 1);
 | 
					    ssize_t received = socket_->read(&data, 1);
 | 
				
			||||||
    if (received == -1) {
 | 
					    if (received == -1) {
 | 
				
			||||||
      if (errno == EWOULDBLOCK || errno == EAGAIN) {
 | 
					      if (errno == EWOULDBLOCK || errno == EAGAIN) {
 | 
				
			||||||
@@ -859,60 +813,27 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
				
			|||||||
      HELPER_LOG("Connection closed");
 | 
					      HELPER_LOG("Connection closed");
 | 
				
			||||||
      return APIError::CONNECTION_CLOSED;
 | 
					      return APIError::CONNECTION_CLOSED;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    rx_header_buf_.push_back(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Successfully read a byte
 | 
					    // try parse header
 | 
				
			||||||
 | 
					    if (rx_header_buf_[0] != 0x00) {
 | 
				
			||||||
    // Process byte according to current buffer position
 | 
					 | 
				
			||||||
    if (rx_header_buf_pos_ == 0) {  // Case 1: First byte (indicator byte)
 | 
					 | 
				
			||||||
      if (data != 0x00) {
 | 
					 | 
				
			||||||
        state_ = State::FAILED;
 | 
					 | 
				
			||||||
        HELPER_LOG("Bad indicator byte %u", data);
 | 
					 | 
				
			||||||
        return APIError::BAD_INDICATOR;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      // We don't store the indicator byte, just increment position
 | 
					 | 
				
			||||||
      rx_header_buf_pos_ = 1;  // Set to 1 directly
 | 
					 | 
				
			||||||
      continue;                // Need more bytes before we can parse
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Check buffer overflow before storing
 | 
					 | 
				
			||||||
    if (rx_header_buf_pos_ == 5) {  // Case 2: Buffer would overflow (5 bytes is max allowed)
 | 
					 | 
				
			||||||
      state_ = State::FAILED;
 | 
					      state_ = State::FAILED;
 | 
				
			||||||
      HELPER_LOG("Header buffer overflow");
 | 
					      HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
 | 
				
			||||||
      return APIError::BAD_DATA_PACKET;
 | 
					      return APIError::BAD_INDICATOR;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Store byte in buffer (adjust index to account for skipped indicator byte)
 | 
					    size_t i = 1;
 | 
				
			||||||
    rx_header_buf_[rx_header_buf_pos_ - 1] = data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Increment position after storing
 | 
					 | 
				
			||||||
    rx_header_buf_pos_++;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Case 3: If we only have one varint byte, we need more
 | 
					 | 
				
			||||||
    if (rx_header_buf_pos_ == 2) {  // Have read indicator + 1 byte
 | 
					 | 
				
			||||||
      continue;                     // Need more bytes before we can parse
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // At this point, we have at least 3 bytes total:
 | 
					 | 
				
			||||||
    //   - Validated indicator byte (0x00) but not stored
 | 
					 | 
				
			||||||
    //   - At least 2 bytes in the buffer for the varints
 | 
					 | 
				
			||||||
    // Buffer layout:
 | 
					 | 
				
			||||||
    //   First 1-3 bytes: Message size varint (variable length)
 | 
					 | 
				
			||||||
    //     - 2 bytes would only allow up to 16383, which is less than noise's 65535
 | 
					 | 
				
			||||||
    //     - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
 | 
					 | 
				
			||||||
    //   Remaining 1-2 bytes: Message type varint (variable length)
 | 
					 | 
				
			||||||
    // We now attempt to parse both varints. If either is incomplete,
 | 
					 | 
				
			||||||
    // we'll continue reading more bytes.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    uint32_t consumed = 0;
 | 
					    uint32_t consumed = 0;
 | 
				
			||||||
    auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[0], rx_header_buf_pos_ - 1, &consumed);
 | 
					    auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
 | 
				
			||||||
    if (!msg_size_varint.has_value()) {
 | 
					    if (!msg_size_varint.has_value()) {
 | 
				
			||||||
      // not enough data there yet
 | 
					      // not enough data there yet
 | 
				
			||||||
      continue;
 | 
					      continue;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    i += consumed;
 | 
				
			||||||
    rx_header_parsed_len_ = msg_size_varint->as_uint32();
 | 
					    rx_header_parsed_len_ = msg_size_varint->as_uint32();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed);
 | 
					    auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
 | 
				
			||||||
    if (!msg_type_varint.has_value()) {
 | 
					    if (!msg_type_varint.has_value()) {
 | 
				
			||||||
      // not enough data there yet
 | 
					      // not enough data there yet
 | 
				
			||||||
      continue;
 | 
					      continue;
 | 
				
			||||||
@@ -958,7 +879,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
				
			|||||||
  // consume msg
 | 
					  // consume msg
 | 
				
			||||||
  rx_buf_ = {};
 | 
					  rx_buf_ = {};
 | 
				
			||||||
  rx_buf_len_ = 0;
 | 
					  rx_buf_len_ = 0;
 | 
				
			||||||
  rx_header_buf_pos_ = 0;
 | 
					  rx_header_buf_.clear();
 | 
				
			||||||
  rx_header_parsed_ = false;
 | 
					  rx_header_parsed_ = false;
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -972,28 +893,8 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  ParsedFrame frame;
 | 
					  ParsedFrame frame;
 | 
				
			||||||
  aerr = try_read_frame_(&frame);
 | 
					  aerr = try_read_frame_(&frame);
 | 
				
			||||||
  if (aerr != APIError::OK) {
 | 
					  if (aerr != APIError::OK)
 | 
				
			||||||
    if (aerr == APIError::BAD_INDICATOR) {
 | 
					 | 
				
			||||||
      // Make sure to tell the remote that we don't
 | 
					 | 
				
			||||||
      // understand the indicator byte so it knows
 | 
					 | 
				
			||||||
      // we do not support it.
 | 
					 | 
				
			||||||
      struct iovec iov[1];
 | 
					 | 
				
			||||||
      // The \x00 first byte is the marker for plaintext.
 | 
					 | 
				
			||||||
      //
 | 
					 | 
				
			||||||
      // The remote will know how to handle the indicator byte,
 | 
					 | 
				
			||||||
      // but it likely won't understand the rest of the message.
 | 
					 | 
				
			||||||
      //
 | 
					 | 
				
			||||||
      // We must send at least 3 bytes to be read, so we add
 | 
					 | 
				
			||||||
      // a message after the indicator byte to ensures its long
 | 
					 | 
				
			||||||
      // enough and can aid in debugging.
 | 
					 | 
				
			||||||
      const char msg[] = "\x00"
 | 
					 | 
				
			||||||
                         "Bad indicator byte";
 | 
					 | 
				
			||||||
      iov[0].iov_base = (void *) msg;
 | 
					 | 
				
			||||||
      iov[0].iov_len = 19;
 | 
					 | 
				
			||||||
      write_raw_(iov, 1);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return aerr;
 | 
					    return aerr;
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  buffer->container = std::move(frame.msg);
 | 
					  buffer->container = std::move(frame.msg);
 | 
				
			||||||
  buffer->data_offset = 0;
 | 
					  buffer->data_offset = 0;
 | 
				
			||||||
@@ -1002,66 +903,26 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
				
			|||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
 | 
					bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
 | 
				
			||||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
 | 
					APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
 | 
				
			||||||
  if (state_ != State::DATA) {
 | 
					  if (state_ != State::DATA) {
 | 
				
			||||||
    return APIError::BAD_STATE;
 | 
					    return APIError::BAD_STATE;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
 | 
					  std::vector<uint8_t> header;
 | 
				
			||||||
  // Message data starts after padding (frame_header_padding_ = 6)
 | 
					  header.push_back(0x00);
 | 
				
			||||||
  size_t payload_len = raw_buffer->size() - frame_header_padding_;
 | 
					  ProtoVarInt(payload_len).encode(header);
 | 
				
			||||||
 | 
					  ProtoVarInt(type).encode(header);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Calculate varint sizes for header components
 | 
					  struct iovec iov[2];
 | 
				
			||||||
  size_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
 | 
					  iov[0].iov_base = &header[0];
 | 
				
			||||||
  size_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
 | 
					  iov[0].iov_len = header.size();
 | 
				
			||||||
  size_t total_header_len = 1 + size_varint_len + type_varint_len;
 | 
					  if (payload_len == 0) {
 | 
				
			||||||
 | 
					    return write_raw_(iov, 1);
 | 
				
			||||||
  if (total_header_len > frame_header_padding_) {
 | 
					 | 
				
			||||||
    // Header is too large to fit in the padding
 | 
					 | 
				
			||||||
    return APIError::BAD_ARG;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  iov[1].iov_base = const_cast<uint8_t *>(payload);
 | 
				
			||||||
 | 
					  iov[1].iov_len = payload_len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Calculate where to start writing the header
 | 
					  return write_raw_(iov, 2);
 | 
				
			||||||
  // The header starts at the latest possible position to minimize unused padding
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
 | 
					 | 
				
			||||||
  // [0-2]  - Unused padding
 | 
					 | 
				
			||||||
  // [3]    - 0x00 indicator byte
 | 
					 | 
				
			||||||
  // [4]    - Payload size varint (1 byte, for sizes 0-127)
 | 
					 | 
				
			||||||
  // [5]    - Message type varint (1 byte, for types 0-127)
 | 
					 | 
				
			||||||
  // [6...] - Actual payload data
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
 | 
					 | 
				
			||||||
  // [0-1]  - Unused padding
 | 
					 | 
				
			||||||
  // [2]    - 0x00 indicator byte
 | 
					 | 
				
			||||||
  // [3-4]  - Payload size varint (2 bytes, for sizes 128-16383)
 | 
					 | 
				
			||||||
  // [5]    - Message type varint (1 byte, for types 0-127)
 | 
					 | 
				
			||||||
  // [6...] - Actual payload data
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
 | 
					 | 
				
			||||||
  // [0]    - 0x00 indicator byte
 | 
					 | 
				
			||||||
  // [1-3]  - Payload size varint (3 bytes, for sizes 16384-2097151)
 | 
					 | 
				
			||||||
  // [4-5]  - Message type varint (2 bytes, for types 128-32767)
 | 
					 | 
				
			||||||
  // [6...] - Actual payload data
 | 
					 | 
				
			||||||
  uint8_t *buf_start = raw_buffer->data();
 | 
					 | 
				
			||||||
  size_t header_offset = frame_header_padding_ - total_header_len;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Write the plaintext header
 | 
					 | 
				
			||||||
  buf_start[header_offset] = 0x00;  // indicator
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Encode size varint directly into buffer
 | 
					 | 
				
			||||||
  ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Encode type varint directly into buffer
 | 
					 | 
				
			||||||
  ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  struct iovec iov;
 | 
					 | 
				
			||||||
  // Point iov_base to the beginning of our header (skip unused padding)
 | 
					 | 
				
			||||||
  // This ensures we only send the actual header and payload, not the empty padding bytes
 | 
					 | 
				
			||||||
  iov.iov_base = buf_start + header_offset;
 | 
					 | 
				
			||||||
  iov.iov_len = total_header_len + payload_len;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return write_raw_(&iov, 1);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
 | 
					APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
 | 
				
			||||||
  // try send from tx_buf
 | 
					  // try send from tx_buf
 | 
				
			||||||
@@ -1081,6 +942,71 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					/** Write the data to the socket, or buffer it a write would block
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param data The data to write
 | 
				
			||||||
 | 
					 * @param len The length of data
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
				
			||||||
 | 
					  if (iovcnt == 0)
 | 
				
			||||||
 | 
					    return APIError::OK;
 | 
				
			||||||
 | 
					  APIError aerr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size_t total_write_len = 0;
 | 
				
			||||||
 | 
					  for (int i = 0; i < iovcnt; i++) {
 | 
				
			||||||
 | 
					#ifdef HELPER_LOG_PACKETS
 | 
				
			||||||
 | 
					    ESP_LOGVV(TAG, "Sending raw: %s",
 | 
				
			||||||
 | 
					              format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    total_write_len += iov[i].iov_len;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!tx_buf_.empty()) {
 | 
				
			||||||
 | 
					    // try to empty tx_buf_ first
 | 
				
			||||||
 | 
					    aerr = try_send_tx_buf_();
 | 
				
			||||||
 | 
					    if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
 | 
				
			||||||
 | 
					      return aerr;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!tx_buf_.empty()) {
 | 
				
			||||||
 | 
					    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
				
			||||||
 | 
					    for (int i = 0; i < iovcnt; i++) {
 | 
				
			||||||
 | 
					      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
				
			||||||
 | 
					                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return APIError::OK;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ssize_t sent = socket_->writev(iov, iovcnt);
 | 
				
			||||||
 | 
					  if (is_would_block(sent)) {
 | 
				
			||||||
 | 
					    // operation would block, add buffer to tx_buf
 | 
				
			||||||
 | 
					    for (int i = 0; i < iovcnt; i++) {
 | 
				
			||||||
 | 
					      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
				
			||||||
 | 
					                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return APIError::OK;
 | 
				
			||||||
 | 
					  } else if (sent == -1) {
 | 
				
			||||||
 | 
					    // an error occurred
 | 
				
			||||||
 | 
					    state_ = State::FAILED;
 | 
				
			||||||
 | 
					    HELPER_LOG("Socket write failed with errno %d", errno);
 | 
				
			||||||
 | 
					    return APIError::SOCKET_WRITE_FAILED;
 | 
				
			||||||
 | 
					  } else if ((size_t) sent != total_write_len) {
 | 
				
			||||||
 | 
					    // partially sent, add end to tx_buf
 | 
				
			||||||
 | 
					    size_t to_consume = sent;
 | 
				
			||||||
 | 
					    for (int i = 0; i < iovcnt; i++) {
 | 
				
			||||||
 | 
					      if (to_consume >= iov[i].iov_len) {
 | 
				
			||||||
 | 
					        to_consume -= iov[i].iov_len;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
 | 
				
			||||||
 | 
					                       reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
				
			||||||
 | 
					        to_consume = 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return APIError::OK;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // fully sent
 | 
				
			||||||
 | 
					  return APIError::OK;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
APIError APIPlaintextFrameHelper::close() {
 | 
					APIError APIPlaintextFrameHelper::close() {
 | 
				
			||||||
  state_ = State::CLOSED;
 | 
					  state_ = State::CLOSED;
 | 
				
			||||||
@@ -1098,13 +1024,7 @@ APIError APIPlaintextFrameHelper::shutdown(int how) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Explicit template instantiation for Plaintext
 | 
					 | 
				
			||||||
template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
 | 
					 | 
				
			||||||
    const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
 | 
					 | 
				
			||||||
    APIPlaintextFrameHelper::State &state, APIPlaintextFrameHelper::State failed_state);
 | 
					 | 
				
			||||||
#endif  // USE_API_PLAINTEXT
 | 
					#endif  // USE_API_PLAINTEXT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/defines.h"
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
#ifdef USE_API
 | 
					
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					#ifdef USE_API_NOISE
 | 
				
			||||||
#include "noise/protocol.h"
 | 
					#include "noise/protocol.h"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -16,8 +16,6 @@
 | 
				
			|||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace api {
 | 
					namespace api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProtoWriteBuffer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct ReadPacketBuffer {
 | 
					struct ReadPacketBuffer {
 | 
				
			||||||
  std::vector<uint8_t> container;
 | 
					  std::vector<uint8_t> container;
 | 
				
			||||||
  uint16_t type;
 | 
					  uint16_t type;
 | 
				
			||||||
@@ -67,46 +65,26 @@ class APIFrameHelper {
 | 
				
			|||||||
  virtual APIError loop() = 0;
 | 
					  virtual APIError loop() = 0;
 | 
				
			||||||
  virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
 | 
					  virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
 | 
				
			||||||
  virtual bool can_write_without_blocking() = 0;
 | 
					  virtual bool can_write_without_blocking() = 0;
 | 
				
			||||||
  virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
 | 
					  virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
 | 
				
			||||||
  virtual std::string getpeername() = 0;
 | 
					  virtual std::string getpeername() = 0;
 | 
				
			||||||
  virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
 | 
					  virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
 | 
				
			||||||
  virtual APIError close() = 0;
 | 
					  virtual APIError close() = 0;
 | 
				
			||||||
  virtual APIError shutdown(int how) = 0;
 | 
					  virtual APIError shutdown(int how) = 0;
 | 
				
			||||||
  // Give this helper a name for logging
 | 
					  // Give this helper a name for logging
 | 
				
			||||||
  virtual void set_log_info(std::string info) = 0;
 | 
					  virtual void set_log_info(std::string info) = 0;
 | 
				
			||||||
  // Get the frame header padding required by this protocol
 | 
					 | 
				
			||||||
  virtual uint8_t frame_header_padding() = 0;
 | 
					 | 
				
			||||||
  // Get the frame footer size required by this protocol
 | 
					 | 
				
			||||||
  virtual uint8_t frame_footer_size() = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  // Common implementation for writing raw data to socket
 | 
					 | 
				
			||||||
  template<typename StateEnum>
 | 
					 | 
				
			||||||
  APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
 | 
					 | 
				
			||||||
                      const std::string &info, StateEnum &state, StateEnum failed_state);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  uint8_t frame_header_padding_{0};
 | 
					 | 
				
			||||||
  uint8_t frame_footer_size_{0};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					#ifdef USE_API_NOISE
 | 
				
			||||||
class APINoiseFrameHelper : public APIFrameHelper {
 | 
					class APINoiseFrameHelper : public APIFrameHelper {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
 | 
					  APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
 | 
				
			||||||
      : socket_(std::move(socket)), ctx_(std::move(ctx)) {
 | 
					      : socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {}
 | 
				
			||||||
    // Noise header structure:
 | 
					 | 
				
			||||||
    // Pos 0: indicator (0x01)
 | 
					 | 
				
			||||||
    // Pos 1-2: encrypted payload size (16-bit big-endian)
 | 
					 | 
				
			||||||
    // Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
 | 
					 | 
				
			||||||
    // Pos 7+: actual payload data
 | 
					 | 
				
			||||||
    frame_header_padding_ = 7;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  ~APINoiseFrameHelper() override;
 | 
					  ~APINoiseFrameHelper() override;
 | 
				
			||||||
  APIError init() override;
 | 
					  APIError init() override;
 | 
				
			||||||
  APIError loop() override;
 | 
					  APIError loop() override;
 | 
				
			||||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
					  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
				
			||||||
  bool can_write_without_blocking() override;
 | 
					  bool can_write_without_blocking() override;
 | 
				
			||||||
  APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
 | 
					  APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
 | 
				
			||||||
  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
					  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
				
			||||||
  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
					  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
				
			||||||
    return this->socket_->getpeername(addr, addrlen);
 | 
					    return this->socket_->getpeername(addr, addrlen);
 | 
				
			||||||
@@ -115,10 +93,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
				
			|||||||
  APIError shutdown(int how) override;
 | 
					  APIError shutdown(int how) override;
 | 
				
			||||||
  // Give this helper a name for logging
 | 
					  // Give this helper a name for logging
 | 
				
			||||||
  void set_log_info(std::string info) override { info_ = std::move(info); }
 | 
					  void set_log_info(std::string info) override { info_ = std::move(info); }
 | 
				
			||||||
  // Get the frame header padding required by this protocol
 | 
					 | 
				
			||||||
  uint8_t frame_header_padding() override { return frame_header_padding_; }
 | 
					 | 
				
			||||||
  // Get the frame footer size required by this protocol
 | 
					 | 
				
			||||||
  uint8_t frame_footer_size() override { return frame_footer_size_; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  struct ParsedFrame {
 | 
					  struct ParsedFrame {
 | 
				
			||||||
@@ -129,9 +103,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
				
			|||||||
  APIError try_read_frame_(ParsedFrame *frame);
 | 
					  APIError try_read_frame_(ParsedFrame *frame);
 | 
				
			||||||
  APIError try_send_tx_buf_();
 | 
					  APIError try_send_tx_buf_();
 | 
				
			||||||
  APIError write_frame_(const uint8_t *data, size_t len);
 | 
					  APIError write_frame_(const uint8_t *data, size_t len);
 | 
				
			||||||
  inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
 | 
					  APIError write_raw_(const struct iovec *iov, int iovcnt);
 | 
				
			||||||
    return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  APIError init_handshake_();
 | 
					  APIError init_handshake_();
 | 
				
			||||||
  APIError check_handshake_finished_();
 | 
					  APIError check_handshake_finished_();
 | 
				
			||||||
  void send_explicit_handshake_reject_(const std::string &reason);
 | 
					  void send_explicit_handshake_reject_(const std::string &reason);
 | 
				
			||||||
@@ -139,9 +111,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
				
			|||||||
  std::unique_ptr<socket::Socket> socket_;
 | 
					  std::unique_ptr<socket::Socket> socket_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::string info_;
 | 
					  std::string info_;
 | 
				
			||||||
  // Fixed-size header buffer for noise protocol:
 | 
					 | 
				
			||||||
  // 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
 | 
					 | 
				
			||||||
  // Note: Maximum message size is 65535, with a limit of 128 bytes during handshake phase
 | 
					 | 
				
			||||||
  uint8_t rx_header_buf_[3];
 | 
					  uint8_t rx_header_buf_[3];
 | 
				
			||||||
  size_t rx_header_buf_len_ = 0;
 | 
					  size_t rx_header_buf_len_ = 0;
 | 
				
			||||||
  std::vector<uint8_t> rx_buf_;
 | 
					  std::vector<uint8_t> rx_buf_;
 | 
				
			||||||
@@ -172,20 +141,13 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
				
			|||||||
#ifdef USE_API_PLAINTEXT
 | 
					#ifdef USE_API_PLAINTEXT
 | 
				
			||||||
class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
					class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {
 | 
					  APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
 | 
				
			||||||
    // Plaintext header structure (worst case):
 | 
					 | 
				
			||||||
    // Pos 0: indicator (0x00)
 | 
					 | 
				
			||||||
    // Pos 1-3: payload size varint (up to 3 bytes)
 | 
					 | 
				
			||||||
    // Pos 4-5: message type varint (up to 2 bytes)
 | 
					 | 
				
			||||||
    // Pos 6+: actual payload data
 | 
					 | 
				
			||||||
    frame_header_padding_ = 6;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  ~APIPlaintextFrameHelper() override = default;
 | 
					  ~APIPlaintextFrameHelper() override = default;
 | 
				
			||||||
  APIError init() override;
 | 
					  APIError init() override;
 | 
				
			||||||
  APIError loop() override;
 | 
					  APIError loop() override;
 | 
				
			||||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
					  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
				
			||||||
  bool can_write_without_blocking() override;
 | 
					  bool can_write_without_blocking() override;
 | 
				
			||||||
  APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
 | 
					  APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
 | 
				
			||||||
  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
					  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
				
			||||||
  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
					  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
				
			||||||
    return this->socket_->getpeername(addr, addrlen);
 | 
					    return this->socket_->getpeername(addr, addrlen);
 | 
				
			||||||
@@ -194,10 +156,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
				
			|||||||
  APIError shutdown(int how) override;
 | 
					  APIError shutdown(int how) override;
 | 
				
			||||||
  // Give this helper a name for logging
 | 
					  // Give this helper a name for logging
 | 
				
			||||||
  void set_log_info(std::string info) override { info_ = std::move(info); }
 | 
					  void set_log_info(std::string info) override { info_ = std::move(info); }
 | 
				
			||||||
  // Get the frame header padding required by this protocol
 | 
					 | 
				
			||||||
  uint8_t frame_header_padding() override { return frame_header_padding_; }
 | 
					 | 
				
			||||||
  // Get the frame footer size required by this protocol
 | 
					 | 
				
			||||||
  uint8_t frame_footer_size() override { return frame_footer_size_; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  struct ParsedFrame {
 | 
					  struct ParsedFrame {
 | 
				
			||||||
@@ -206,23 +164,12 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  APIError try_read_frame_(ParsedFrame *frame);
 | 
					  APIError try_read_frame_(ParsedFrame *frame);
 | 
				
			||||||
  APIError try_send_tx_buf_();
 | 
					  APIError try_send_tx_buf_();
 | 
				
			||||||
  inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
 | 
					  APIError write_raw_(const struct iovec *iov, int iovcnt);
 | 
				
			||||||
    return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::unique_ptr<socket::Socket> socket_;
 | 
					  std::unique_ptr<socket::Socket> socket_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::string info_;
 | 
					  std::string info_;
 | 
				
			||||||
  // Fixed-size header buffer for plaintext protocol:
 | 
					  std::vector<uint8_t> rx_header_buf_;
 | 
				
			||||||
  // We only need space for the two varints since we validate the indicator byte separately.
 | 
					 | 
				
			||||||
  // To match noise protocol's maximum message size (65535), we need:
 | 
					 | 
				
			||||||
  // 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // While varints could theoretically be up to 10 bytes each for 64-bit values,
 | 
					 | 
				
			||||||
  // attempting to process messages with headers that large would likely crash the
 | 
					 | 
				
			||||||
  // ESP32 due to memory constraints.
 | 
					 | 
				
			||||||
  uint8_t rx_header_buf_[5];  // 5 bytes for varints (3 for size + 2 for type)
 | 
					 | 
				
			||||||
  uint8_t rx_header_buf_pos_ = 0;
 | 
					 | 
				
			||||||
  bool rx_header_parsed_ = false;
 | 
					  bool rx_header_parsed_ = false;
 | 
				
			||||||
  uint32_t rx_header_parsed_type_ = 0;
 | 
					  uint32_t rx_header_parsed_type_ = 0;
 | 
				
			||||||
  uint32_t rx_header_parsed_len_ = 0;
 | 
					  uint32_t rx_header_parsed_len_ = 0;
 | 
				
			||||||
@@ -243,4 +190,3 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
#include <array>
 | 
					 | 
				
			||||||
#include <cstdint>
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <array>
 | 
				
			||||||
#include "esphome/core/defines.h"
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
@@ -11,20 +11,11 @@ using psk_t = std::array<uint8_t, 32>;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class APINoiseContext {
 | 
					class APINoiseContext {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  void set_psk(psk_t psk) {
 | 
					  void set_psk(psk_t psk) { psk_ = psk; }
 | 
				
			||||||
    this->psk_ = psk;
 | 
					  const psk_t &get_psk() const { return psk_; }
 | 
				
			||||||
    bool has_psk = false;
 | 
					 | 
				
			||||||
    for (auto i : psk) {
 | 
					 | 
				
			||||||
      has_psk |= i;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this->has_psk_ = has_psk;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  const psk_t &get_psk() const { return this->psk_; }
 | 
					 | 
				
			||||||
  bool has_psk() const { return this->has_psk_; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  psk_t psk_{};
 | 
					  psk_t psk_;
 | 
				
			||||||
  bool has_psk_{false};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
#endif  // USE_API_NOISE
 | 
					#endif  // USE_API_NOISE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,5 +1,5 @@
 | 
				
			|||||||
// This file was automatically generated with a tool.
 | 
					// This file was automatically generated with a tool.
 | 
				
			||||||
// See script/api_protobuf/api_protobuf.py
 | 
					// See scripts/api_protobuf/api_protobuf.py
 | 
				
			||||||
#include "api_pb2_service.h"
 | 
					#include "api_pb2_service.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -179,16 +179,6 @@ bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorSt
 | 
				
			|||||||
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
 | 
					bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
 | 
				
			||||||
  return this->send_message_<SubscribeLogsResponse>(msg, 29);
 | 
					  return this->send_message_<SubscribeLogsResponse>(msg, 29);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					 | 
				
			||||||
bool APIServerConnectionBase::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg) {
 | 
					 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					 | 
				
			||||||
  ESP_LOGVV(TAG, "send_noise_encryption_set_key_response: %s", msg.dump().c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
  return this->send_message_<NoiseEncryptionSetKeyResponse>(msg, 125);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
 | 
					bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
  ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
 | 
					  ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
 | 
				
			||||||
@@ -292,24 +282,6 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SELECT
 | 
					#ifdef USE_SELECT
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SIREN
 | 
					 | 
				
			||||||
bool APIServerConnectionBase::send_list_entities_siren_response(const ListEntitiesSirenResponse &msg) {
 | 
					 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					 | 
				
			||||||
  ESP_LOGVV(TAG, "send_list_entities_siren_response: %s", msg.dump().c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
  return this->send_message_<ListEntitiesSirenResponse>(msg, 55);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_SIREN
 | 
					 | 
				
			||||||
bool APIServerConnectionBase::send_siren_state_response(const SirenStateResponse &msg) {
 | 
					 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					 | 
				
			||||||
  ESP_LOGVV(TAG, "send_siren_state_response: %s", msg.dump().c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
  return this->send_message_<SirenStateResponse>(msg, 56);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_SIREN
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_LOCK
 | 
					#ifdef USE_LOCK
 | 
				
			||||||
bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
 | 
					bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
@@ -490,16 +462,6 @@ bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const B
 | 
				
			|||||||
  return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
 | 
					  return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					 | 
				
			||||||
bool APIServerConnectionBase::send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg) {
 | 
					 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					 | 
				
			||||||
  ESP_LOGVV(TAG, "send_bluetooth_scanner_state_response: %s", msg.dump().c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
  return this->send_message_<BluetoothScannerStateResponse>(msg, 126);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
@@ -534,19 +496,6 @@ bool APIServerConnectionBase::send_voice_assistant_announce_finished(const Voice
 | 
				
			|||||||
  return this->send_message_<VoiceAssistantAnnounceFinished>(msg, 120);
 | 
					  return this->send_message_<VoiceAssistantAnnounceFinished>(msg, 120);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#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
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
 | 
					bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
 | 
				
			||||||
    const ListEntitiesAlarmControlPanelResponse &msg) {
 | 
					    const ListEntitiesAlarmControlPanelResponse &msg) {
 | 
				
			||||||
@@ -921,17 +870,6 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
				
			|||||||
      ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
 | 
					      ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
      this->on_select_command_request(msg);
 | 
					      this->on_select_command_request(msg);
 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    case 57: {
 | 
					 | 
				
			||||||
#ifdef USE_SIREN
 | 
					 | 
				
			||||||
      SirenCommandRequest msg;
 | 
					 | 
				
			||||||
      msg.decode(msg_data, msg_size);
 | 
					 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					 | 
				
			||||||
      ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      this->on_siren_command_request(msg);
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -1218,50 +1156,6 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
				
			|||||||
      ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str());
 | 
					      ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str());
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
      this->on_voice_assistant_announce_request(msg);
 | 
					      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;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    case 124: {
 | 
					 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					 | 
				
			||||||
      NoiseEncryptionSetKeyRequest msg;
 | 
					 | 
				
			||||||
      msg.decode(msg_data, msg_size);
 | 
					 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					 | 
				
			||||||
      ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      this->on_noise_encryption_set_key_request(msg);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    case 127: {
 | 
					 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					 | 
				
			||||||
      BluetoothScannerSetModeRequest msg;
 | 
					 | 
				
			||||||
      msg.decode(msg_data, msg_size);
 | 
					 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					 | 
				
			||||||
      ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      this->on_bluetooth_scanner_set_mode_request(msg);
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -1382,61 +1276,6 @@ void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  this->execute_service(msg);
 | 
					  this->execute_service(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					 | 
				
			||||||
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
 | 
					 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!this->is_authenticated()) {
 | 
					 | 
				
			||||||
    this->on_unauthenticated_access();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg);
 | 
					 | 
				
			||||||
  if (!this->send_noise_encryption_set_key_response(ret)) {
 | 
					 | 
				
			||||||
    this->on_fatal_error();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_BUTTON
 | 
					 | 
				
			||||||
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
 | 
					 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!this->is_authenticated()) {
 | 
					 | 
				
			||||||
    this->on_unauthenticated_access();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  this->button_command(msg);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_ESP32_CAMERA
 | 
					 | 
				
			||||||
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
 | 
					 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!this->is_authenticated()) {
 | 
					 | 
				
			||||||
    this->on_unauthenticated_access();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  this->camera_image(msg);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_CLIMATE
 | 
					 | 
				
			||||||
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
 | 
					 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!this->is_authenticated()) {
 | 
					 | 
				
			||||||
    this->on_unauthenticated_access();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  this->climate_command(msg);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_COVER
 | 
					#ifdef USE_COVER
 | 
				
			||||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
 | 
					void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
@@ -1450,32 +1289,6 @@ void APIServerConnection::on_cover_command_request(const CoverCommandRequest &ms
 | 
				
			|||||||
  this->cover_command(msg);
 | 
					  this->cover_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_DATETIME_DATE
 | 
					 | 
				
			||||||
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) {
 | 
					 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!this->is_authenticated()) {
 | 
					 | 
				
			||||||
    this->on_unauthenticated_access();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  this->date_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_FAN
 | 
					#ifdef USE_FAN
 | 
				
			||||||
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
 | 
					void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
@@ -1502,8 +1315,8 @@ void APIServerConnection::on_light_command_request(const LightCommandRequest &ms
 | 
				
			|||||||
  this->light_command(msg);
 | 
					  this->light_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_LOCK
 | 
					#ifdef USE_SWITCH
 | 
				
			||||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
 | 
					void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -1512,11 +1325,11 @@ void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg)
 | 
				
			|||||||
    this->on_unauthenticated_access();
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->lock_command(msg);
 | 
					  this->switch_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_MEDIA_PLAYER
 | 
					#ifdef USE_ESP32_CAMERA
 | 
				
			||||||
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
 | 
					void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -1525,7 +1338,20 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
 | 
				
			|||||||
    this->on_unauthenticated_access();
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->media_player_command(msg);
 | 
					  this->camera_image(msg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_CLIMATE
 | 
				
			||||||
 | 
					void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
 | 
				
			||||||
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!this->is_authenticated()) {
 | 
				
			||||||
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->climate_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_NUMBER
 | 
					#ifdef USE_NUMBER
 | 
				
			||||||
@@ -1541,6 +1367,19 @@ void APIServerConnection::on_number_command_request(const NumberCommandRequest &
 | 
				
			|||||||
  this->number_command(msg);
 | 
					  this->number_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_TEXT
 | 
				
			||||||
 | 
					void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
 | 
				
			||||||
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!this->is_authenticated()) {
 | 
				
			||||||
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->text_command(msg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SELECT
 | 
					#ifdef USE_SELECT
 | 
				
			||||||
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
 | 
					void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
@@ -1554,8 +1393,8 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest &
 | 
				
			|||||||
  this->select_command(msg);
 | 
					  this->select_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SIREN
 | 
					#ifdef USE_BUTTON
 | 
				
			||||||
void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) {
 | 
					void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -1564,11 +1403,11 @@ void APIServerConnection::on_siren_command_request(const SirenCommandRequest &ms
 | 
				
			|||||||
    this->on_unauthenticated_access();
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->siren_command(msg);
 | 
					  this->button_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SWITCH
 | 
					#ifdef USE_LOCK
 | 
				
			||||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
 | 
					void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -1577,11 +1416,11 @@ void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &
 | 
				
			|||||||
    this->on_unauthenticated_access();
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->switch_command(msg);
 | 
					  this->lock_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_TEXT
 | 
					#ifdef USE_VALVE
 | 
				
			||||||
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
 | 
					void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -1590,7 +1429,33 @@ void APIServerConnection::on_text_command_request(const TextCommandRequest &msg)
 | 
				
			|||||||
    this->on_unauthenticated_access();
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->text_command(msg);
 | 
					  this->valve_command(msg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_MEDIA_PLAYER
 | 
				
			||||||
 | 
					void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
 | 
				
			||||||
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!this->is_authenticated()) {
 | 
				
			||||||
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->media_player_command(msg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_DATETIME_DATE
 | 
				
			||||||
 | 
					void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) {
 | 
				
			||||||
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!this->is_authenticated()) {
 | 
				
			||||||
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->date_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_DATETIME_TIME
 | 
					#ifdef USE_DATETIME_TIME
 | 
				
			||||||
@@ -1606,6 +1471,19 @@ void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg)
 | 
				
			|||||||
  this->time_command(msg);
 | 
					  this->time_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#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
 | 
					#ifdef USE_UPDATE
 | 
				
			||||||
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
 | 
					void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
@@ -1619,19 +1497,6 @@ void APIServerConnection::on_update_command_request(const UpdateCommandRequest &
 | 
				
			|||||||
  this->update_command(msg);
 | 
					  this->update_command(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#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_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
					void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
				
			||||||
    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
					    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
				
			||||||
@@ -1768,19 +1633,6 @@ void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
 | 
				
			|||||||
  this->unsubscribe_bluetooth_le_advertisements(msg);
 | 
					  this->unsubscribe_bluetooth_le_advertisements(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					 | 
				
			||||||
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
 | 
					 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					 | 
				
			||||||
    this->on_no_setup_connection();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!this->is_authenticated()) {
 | 
					 | 
				
			||||||
    this->on_unauthenticated_access();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  this->bluetooth_scanner_set_mode(msg);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
 | 
					void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
@@ -1794,35 +1646,6 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
 | 
				
			|||||||
  this->subscribe_voice_assistant(msg);
 | 
					  this->subscribe_voice_assistant(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#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
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
 | 
					void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
 | 
				
			||||||
  if (!this->is_connection_setup()) {
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user