mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			204 Commits
		
	
	
		
			2024.11.0b
			...
			2024.12.2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4b51ba3fa4 | ||
| 
						 | 
					499953e3f4 | ||
| 
						 | 
					69f1a81e1d | ||
| 
						 | 
					37fcccbb1c | ||
| 
						 | 
					fe0700166a | ||
| 
						 | 
					d28cf011d1 | ||
| 
						 | 
					434879ea04 | ||
| 
						 | 
					0f0b829bc6 | ||
| 
						 | 
					d330e73c1e | ||
| 
						 | 
					561d92d402 | ||
| 
						 | 
					1a69236473 | ||
| 
						 | 
					c86ea99145 | ||
| 
						 | 
					7661609049 | ||
| 
						 | 
					c38826824f | ||
| 
						 | 
					e890486043 | ||
| 
						 | 
					6dcbd1a8ae | ||
| 
						 | 
					63b0930ae8 | ||
| 
						 | 
					5382bd2a97 | ||
| 
						 | 
					de1fbd390b | ||
| 
						 | 
					af23357dca | ||
| 
						 | 
					0fbe6c0d8b | ||
| 
						 | 
					4e1ff31342 | ||
| 
						 | 
					1656ced351 | ||
| 
						 | 
					5dcaf1241f | ||
| 
						 | 
					7aa54b6879 | ||
| 
						 | 
					444e162c92 | ||
| 
						 | 
					bb27eaaf1e | ||
| 
						 | 
					517f659da8 | ||
| 
						 | 
					5a92e24662 | ||
| 
						 | 
					437b236a4d | ||
| 
						 | 
					14eac3dbce | ||
| 
						 | 
					132a096ae7 | ||
| 
						 | 
					440080a753 | ||
| 
						 | 
					f15e3cfb9b | ||
| 
						 | 
					9d000e9abf | ||
| 
						 | 
					97fd7493b5 | ||
| 
						 | 
					4c87658503 | ||
| 
						 | 
					c80e035bd5 | ||
| 
						 | 
					c8ec0bb7ea | ||
| 
						 | 
					86ae1c5931 | ||
| 
						 | 
					d0958f7cf2 | ||
| 
						 | 
					982ce1db72 | ||
| 
						 | 
					f042c6e643 | ||
| 
						 | 
					5fcd26bfe9 | ||
| 
						 | 
					5717d557f5 | ||
| 
						 | 
					3bac45e737 | ||
| 
						 | 
					e623989878 | ||
| 
						 | 
					39cbc6b183 | ||
| 
						 | 
					749a5e3348 | ||
| 
						 | 
					b0e3ac01e8 | ||
| 
						 | 
					58123845ff | ||
| 
						 | 
					bfd75d736c | ||
| 
						 | 
					4e3195b474 | ||
| 
						 | 
					d3a71a1d45 | ||
| 
						 | 
					555bdac604 | ||
| 
						 | 
					acc8d24a32 | ||
| 
						 | 
					f3cc1e541a | ||
| 
						 | 
					ece72c6b18 | ||
| 
						 | 
					4e839d42d0 | ||
| 
						 | 
					d429aa8bb8 | ||
| 
						 | 
					472402745d | ||
| 
						 | 
					016fac2496 | ||
| 
						 | 
					79478cdb8a | ||
| 
						 | 
					dbed74b50d | ||
| 
						 | 
					d00ec7e544 | ||
| 
						 | 
					a37ff2dbd9 | ||
| 
						 | 
					00ddb0a427 | ||
| 
						 | 
					c95887a14a | ||
| 
						 | 
					dc5942a59b | ||
| 
						 | 
					584dbf2668 | ||
| 
						 | 
					9c8976be13 | ||
| 
						 | 
					e08a9cc3a3 | ||
| 
						 | 
					b79a3d6727 | ||
| 
						 | 
					fb96e3588d | ||
| 
						 | 
					edd847ea40 | ||
| 
						 | 
					83d6834e27 | ||
| 
						 | 
					8f69d07061 | ||
| 
						 | 
					30477c764d | ||
| 
						 | 
					217a80a178 | ||
| 
						 | 
					5486b40aab | ||
| 
						 | 
					beb8ab50e2 | ||
| 
						 | 
					7cdf5b55ef | ||
| 
						 | 
					c9b0490305 | ||
| 
						 | 
					d305870284 | ||
| 
						 | 
					ff5004d7db | ||
| 
						 | 
					7aa3a1a1cc | ||
| 
						 | 
					e124151e5c | ||
| 
						 | 
					e229ed0da3 | ||
| 
						 | 
					12cdeca48a | ||
| 
						 | 
					a825ef59d4 | ||
| 
						 | 
					65a5216d17 | ||
| 
						 | 
					567256bd62 | ||
| 
						 | 
					4da57c35d0 | ||
| 
						 | 
					f2e8e655ba | ||
| 
						 | 
					8439232b11 | ||
| 
						 | 
					e6c730ab10 | ||
| 
						 | 
					e49df765d2 | ||
| 
						 | 
					e6da55b925 | ||
| 
						 | 
					c894645747 | ||
| 
						 | 
					2539cba610 | ||
| 
						 | 
					5ddbe5cdba | ||
| 
						 | 
					4c7552eca4 | ||
| 
						 | 
					72bf0086e4 | ||
| 
						 | 
					1b91e0027b | ||
| 
						 | 
					e9851e7eb2 | ||
| 
						 | 
					80fedbc1a5 | ||
| 
						 | 
					a4a71797d9 | ||
| 
						 | 
					4a97064b2c | ||
| 
						 | 
					a3ef2ed7fd | ||
| 
						 | 
					3a8b41daa3 | ||
| 
						 | 
					921be1a17c | ||
| 
						 | 
					e3d673d16c | ||
| 
						 | 
					39f3f795e2 | ||
| 
						 | 
					53691d28a8 | ||
| 
						 | 
					3730b0310b | ||
| 
						 | 
					2b9013699d | ||
| 
						 | 
					be78827274 | ||
| 
						 | 
					cd1ee96606 | ||
| 
						 | 
					2fa8d907b3 | ||
| 
						 | 
					4c383906c4 | ||
| 
						 | 
					bdc6302ea1 | ||
| 
						 | 
					31c13e4c16 | ||
| 
						 | 
					6b59f55a50 | ||
| 
						 | 
					e6bd2238ce | ||
| 
						 | 
					2d4688a206 | ||
| 
						 | 
					536bcab5de | ||
| 
						 | 
					1c2d2bce5a | ||
| 
						 | 
					2eac8b6c46 | ||
| 
						 | 
					6e50e2aa65 | ||
| 
						 | 
					841d278224 | ||
| 
						 | 
					11076e4614 | ||
| 
						 | 
					72df3d1606 | ||
| 
						 | 
					ae6736311a | ||
| 
						 | 
					c0dcecc465 | ||
| 
						 | 
					d9d368d38e | ||
| 
						 | 
					a70cee1dc1 | ||
| 
						 | 
					f4766ab74f | ||
| 
						 | 
					4fbf41472a | ||
| 
						 | 
					6ee02c47c2 | ||
| 
						 | 
					140d77061b | ||
| 
						 | 
					d6f4f05090 | ||
| 
						 | 
					bdb91112ea | ||
| 
						 | 
					b027b6a711 | ||
| 
						 | 
					89ecfc2004 | ||
| 
						 | 
					cf835d1580 | ||
| 
						 | 
					17a09cd221 | ||
| 
						 | 
					1bd2d41ffd | ||
| 
						 | 
					aa6cea6f7e | ||
| 
						 | 
					ebf895990b | ||
| 
						 | 
					46a435f5f2 | ||
| 
						 | 
					6c548a1596 | ||
| 
						 | 
					7f75f2135d | ||
| 
						 | 
					c49f7293fe | ||
| 
						 | 
					71496574e9 | ||
| 
						 | 
					b95b4a0694 | ||
| 
						 | 
					59653ec785 | ||
| 
						 | 
					e02f3cdac7 | ||
| 
						 | 
					d4d630823c | ||
| 
						 | 
					9fc1377b44 | ||
| 
						 | 
					e3e3d92347 | ||
| 
						 | 
					13077095c2 | ||
| 
						 | 
					4001d82ca2 | ||
| 
						 | 
					4936ca1700 | ||
| 
						 | 
					2ecd5cff07 | ||
| 
						 | 
					dea297c8d7 | ||
| 
						 | 
					ef7c5c6055 | ||
| 
						 | 
					ee3cfb2b76 | ||
| 
						 | 
					2cc2a2153b | ||
| 
						 | 
					e51f3d9498 | ||
| 
						 | 
					1c1f3f7c55 | ||
| 
						 | 
					ea424b0699 | ||
| 
						 | 
					489d0d20d2 | ||
| 
						 | 
					f04e3de7b8 | ||
| 
						 | 
					a0693060e4 | ||
| 
						 | 
					888b237964 | ||
| 
						 | 
					122ff731ef | ||
| 
						 | 
					3232866dc3 | ||
| 
						 | 
					ccf2854b61 | ||
| 
						 | 
					03ae6b2c1b | ||
| 
						 | 
					6bcbbcce02 | ||
| 
						 | 
					fbb9967117 | ||
| 
						 | 
					6d4f787f67 | ||
| 
						 | 
					5e27a8df1f | ||
| 
						 | 
					846b091aac | ||
| 
						 | 
					372d68a177 | ||
| 
						 | 
					4fc19902ab | ||
| 
						 | 
					9a7d5dcad8 | ||
| 
						 | 
					ef78c404dd | ||
| 
						 | 
					c857f98557 | ||
| 
						 | 
					cf63d627fe | ||
| 
						 | 
					49e9c43339 | ||
| 
						 | 
					f1dc9537ff | ||
| 
						 | 
					6e41c22e9d | ||
| 
						 | 
					e81191ebd2 | ||
| 
						 | 
					b29c119408 | ||
| 
						 | 
					e819185de1 | ||
| 
						 | 
					0b51ec2c88 | ||
| 
						 | 
					5e62c489b0 | ||
| 
						 | 
					d015088855 | ||
| 
						 | 
					39c889e662 | ||
| 
						 | 
					c7c8711c9c | ||
| 
						 | 
					0a92405f2d | ||
| 
						 | 
					a2cab960a9 | ||
| 
						 | 
					1f7f03f563 | 
							
								
								
									
										36
									
								
								.clang-tidy
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								.clang-tidy
									
									
									
									
									
								
							@@ -7,28 +7,39 @@ Checks: >-
 | 
			
		||||
  -boost-*,
 | 
			
		||||
  -bugprone-easily-swappable-parameters,
 | 
			
		||||
  -bugprone-implicit-widening-of-multiplication-result,
 | 
			
		||||
  -bugprone-multi-level-implicit-pointer-conversion,
 | 
			
		||||
  -bugprone-narrowing-conversions,
 | 
			
		||||
  -bugprone-signed-char-misuse,
 | 
			
		||||
  -bugprone-switch-missing-default-case,
 | 
			
		||||
  -cert-dcl50-cpp,
 | 
			
		||||
  -cert-err33-c,
 | 
			
		||||
  -cert-err58-cpp,
 | 
			
		||||
  -cert-oop57-cpp,
 | 
			
		||||
  -cert-str34-c,
 | 
			
		||||
  -clang-analyzer-optin.core.EnumCastOutOfRange,
 | 
			
		||||
  -clang-analyzer-optin.cplusplus.UninitializedObject,
 | 
			
		||||
  -clang-analyzer-osx.*,
 | 
			
		||||
  -clang-diagnostic-delete-abstract-non-virtual-dtor,
 | 
			
		||||
  -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
 | 
			
		||||
  -clang-diagnostic-deprecated-declarations,
 | 
			
		||||
  -clang-diagnostic-ignored-optimization-argument,
 | 
			
		||||
  -clang-diagnostic-missing-field-initializers,
 | 
			
		||||
  -clang-diagnostic-shadow-field,
 | 
			
		||||
  -clang-diagnostic-unused-const-variable,
 | 
			
		||||
  -clang-diagnostic-unused-parameter,
 | 
			
		||||
  -clang-diagnostic-vla-cxx-extension,
 | 
			
		||||
  -concurrency-*,
 | 
			
		||||
  -cppcoreguidelines-avoid-c-arrays,
 | 
			
		||||
  -cppcoreguidelines-avoid-const-or-ref-data-members,
 | 
			
		||||
  -cppcoreguidelines-avoid-do-while,
 | 
			
		||||
  -cppcoreguidelines-avoid-magic-numbers,
 | 
			
		||||
  -cppcoreguidelines-init-variables,
 | 
			
		||||
  -cppcoreguidelines-macro-to-enum,
 | 
			
		||||
  -cppcoreguidelines-macro-usage,
 | 
			
		||||
  -cppcoreguidelines-missing-std-forward,
 | 
			
		||||
  -cppcoreguidelines-narrowing-conversions,
 | 
			
		||||
  -cppcoreguidelines-non-private-member-variables-in-classes,
 | 
			
		||||
  -cppcoreguidelines-owning-memory,
 | 
			
		||||
  -cppcoreguidelines-prefer-member-initializer,
 | 
			
		||||
  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
 | 
			
		||||
  -cppcoreguidelines-pro-bounds-constant-array-index,
 | 
			
		||||
@@ -40,7 +51,9 @@ Checks: >-
 | 
			
		||||
  -cppcoreguidelines-pro-type-static-cast-downcast,
 | 
			
		||||
  -cppcoreguidelines-pro-type-union-access,
 | 
			
		||||
  -cppcoreguidelines-pro-type-vararg,
 | 
			
		||||
  -cppcoreguidelines-rvalue-reference-param-not-moved,
 | 
			
		||||
  -cppcoreguidelines-special-member-functions,
 | 
			
		||||
  -cppcoreguidelines-use-default-member-init,
 | 
			
		||||
  -cppcoreguidelines-virtual-class-destructor,
 | 
			
		||||
  -fuchsia-multiple-inheritance,
 | 
			
		||||
  -fuchsia-overloaded-operator,
 | 
			
		||||
@@ -60,20 +73,32 @@ Checks: >-
 | 
			
		||||
  -llvm-include-order,
 | 
			
		||||
  -llvm-qualified-auto,
 | 
			
		||||
  -llvmlibc-*,
 | 
			
		||||
  -misc-non-private-member-variables-in-classes,
 | 
			
		||||
  -misc-const-correctness,
 | 
			
		||||
  -misc-include-cleaner,
 | 
			
		||||
  -misc-no-recursion,
 | 
			
		||||
  -misc-non-private-member-variables-in-classes,
 | 
			
		||||
  -misc-unused-parameters,
 | 
			
		||||
  -modernize-avoid-c-arrays,
 | 
			
		||||
  -misc-use-anonymous-namespace,
 | 
			
		||||
  -modernize-avoid-bind,
 | 
			
		||||
  -modernize-avoid-c-arrays,
 | 
			
		||||
  -modernize-concat-nested-namespaces,
 | 
			
		||||
  -modernize-macro-to-enum,
 | 
			
		||||
  -modernize-return-braced-init-list,
 | 
			
		||||
  -modernize-type-traits,
 | 
			
		||||
  -modernize-use-auto,
 | 
			
		||||
  -modernize-use-constraints,
 | 
			
		||||
  -modernize-use-default-member-init,
 | 
			
		||||
  -modernize-use-equals-default,
 | 
			
		||||
  -modernize-use-trailing-return-type,
 | 
			
		||||
  -modernize-use-nodiscard,
 | 
			
		||||
  -modernize-use-nullptr,
 | 
			
		||||
  -modernize-use-nodiscard,
 | 
			
		||||
  -modernize-use-nullptr,
 | 
			
		||||
  -modernize-use-trailing-return-type,
 | 
			
		||||
  -mpi-*,
 | 
			
		||||
  -objc-*,
 | 
			
		||||
  -performance-enum-size,
 | 
			
		||||
  -readability-avoid-nested-conditional-operator,
 | 
			
		||||
  -readability-container-contains,
 | 
			
		||||
  -readability-container-data-pointer,
 | 
			
		||||
  -readability-convert-member-functions-to-static,
 | 
			
		||||
  -readability-else-after-return,
 | 
			
		||||
@@ -82,11 +107,14 @@ Checks: >-
 | 
			
		||||
  -readability-isolate-declaration,
 | 
			
		||||
  -readability-magic-numbers,
 | 
			
		||||
  -readability-make-member-function-const,
 | 
			
		||||
  -readability-named-parameter,
 | 
			
		||||
  -readability-redundant-casting,
 | 
			
		||||
  -readability-redundant-inline-specifier,
 | 
			
		||||
  -readability-redundant-member-init,
 | 
			
		||||
  -readability-redundant-string-init,
 | 
			
		||||
  -readability-uppercase-literal-suffix,
 | 
			
		||||
  -readability-use-anyofallof,
 | 
			
		||||
WarningsAsErrors: '*'
 | 
			
		||||
AnalyzeTemporaryDtors: false
 | 
			
		||||
FormatStyle:     google
 | 
			
		||||
CheckOptions:
 | 
			
		||||
  - key:             google-readability-function-size.StatementThreshold
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							@@ -46,7 +46,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to ghcr by digest
 | 
			
		||||
      id: build-ghcr
 | 
			
		||||
      uses: docker/build-push-action@v6.9.0
 | 
			
		||||
      uses: docker/build-push-action@v6.10.0
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_BUILD_SUMMARY: false
 | 
			
		||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
			
		||||
@@ -72,7 +72,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to dockerhub by digest
 | 
			
		||||
      id: build-dockerhub
 | 
			
		||||
      uses: docker/build-push-action@v6.9.0
 | 
			
		||||
      uses: docker/build-push-action@v6.10.0
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_BUILD_SUMMARY: false
 | 
			
		||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							@@ -22,7 +22,7 @@ runs:
 | 
			
		||||
        python-version: ${{ inputs.python-version }}
 | 
			
		||||
    - name: Restore Python virtual environment
 | 
			
		||||
      id: cache-venv
 | 
			
		||||
      uses: actions/cache/restore@v4.1.2
 | 
			
		||||
      uses: actions/cache/restore@v4.2.0
 | 
			
		||||
      with:
 | 
			
		||||
        path: venv
 | 
			
		||||
        # yamllint disable-line rule:line-length
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -30,7 +30,7 @@ concurrency:
 | 
			
		||||
jobs:
 | 
			
		||||
  common:
 | 
			
		||||
    name: Create common environment
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    outputs:
 | 
			
		||||
      cache-key: ${{ steps.cache-key.outputs.key }}
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -46,7 +46,7 @@ jobs:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
      - name: Restore Python virtual environment
 | 
			
		||||
        id: cache-venv
 | 
			
		||||
        uses: actions/cache@v4.1.2
 | 
			
		||||
        uses: actions/cache@v4.2.0
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
@@ -62,7 +62,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  black:
 | 
			
		||||
    name: Check black
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -83,7 +83,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  flake8:
 | 
			
		||||
    name: Check flake8
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -104,7 +104,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  pylint:
 | 
			
		||||
    name: Check pylint
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -125,7 +125,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  pyupgrade:
 | 
			
		||||
    name: Check pyupgrade
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -146,7 +146,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  ci-custom:
 | 
			
		||||
    name: Run script/ci-custom
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -219,13 +219,13 @@ jobs:
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          pytest -vv --cov-report=xml --tb=native tests
 | 
			
		||||
      - name: Upload coverage to Codecov
 | 
			
		||||
        uses: codecov/codecov-action@v4
 | 
			
		||||
        uses: codecov/codecov-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          token: ${{ secrets.CODECOV_TOKEN }}
 | 
			
		||||
 | 
			
		||||
  clang-format:
 | 
			
		||||
    name: Check clang-format
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -251,7 +251,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  clang-tidy:
 | 
			
		||||
    name: ${{ matrix.name }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - black
 | 
			
		||||
@@ -302,23 +302,18 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Cache platformio
 | 
			
		||||
        if: github.ref == 'refs/heads/dev'
 | 
			
		||||
        uses: actions/cache@v4.1.2
 | 
			
		||||
        uses: actions/cache@v4.2.0
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: platformio-${{ matrix.pio_cache_key }}
 | 
			
		||||
 | 
			
		||||
      - name: Cache platformio
 | 
			
		||||
        if: github.ref != 'refs/heads/dev'
 | 
			
		||||
        uses: actions/cache/restore@v4.1.2
 | 
			
		||||
        uses: actions/cache/restore@v4.2.0
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: platformio-${{ matrix.pio_cache_key }}
 | 
			
		||||
 | 
			
		||||
      - name: Install clang-tidy
 | 
			
		||||
        run: |
 | 
			
		||||
          sudo apt-get update
 | 
			
		||||
          sudo apt-get install clang-tidy-14
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
@@ -345,7 +340,7 @@ jobs:
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
  list-components:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    if: github.event_name == 'pull_request'
 | 
			
		||||
@@ -387,7 +382,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  test-build-components:
 | 
			
		||||
    name: Component test ${{ matrix.file }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - list-components
 | 
			
		||||
@@ -421,7 +416,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  test-build-components-splitter:
 | 
			
		||||
    name: Split components for testing into 20 groups maximum
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - list-components
 | 
			
		||||
@@ -439,7 +434,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  test-build-components-split:
 | 
			
		||||
    name: Test split components
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - list-components
 | 
			
		||||
@@ -483,7 +478,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  ci-status:
 | 
			
		||||
    name: CI Status
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - black
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -65,7 +65,7 @@ jobs:
 | 
			
		||||
          pip3 install build
 | 
			
		||||
          python3 -m build
 | 
			
		||||
      - name: Publish
 | 
			
		||||
        uses: pypa/gh-action-pypi-publish@v1.12.2
 | 
			
		||||
        uses: pypa/gh-action-pypi-publish@v1.12.3
 | 
			
		||||
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build ESPHome ${{ matrix.platform }}
 | 
			
		||||
 
 | 
			
		||||
@@ -179,6 +179,7 @@ esphome/components/haier/text_sensor/* @paveldn
 | 
			
		||||
esphome/components/havells_solar/* @sourabhjaiswal
 | 
			
		||||
esphome/components/hbridge/fan/* @WeekendWarrior
 | 
			
		||||
esphome/components/hbridge/light/* @DotNetDann
 | 
			
		||||
esphome/components/hbridge/switch/* @dwmw2
 | 
			
		||||
esphome/components/he60r/* @clydebarrow
 | 
			
		||||
esphome/components/heatpumpir/* @rob-deutsch
 | 
			
		||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
			
		||||
@@ -354,6 +355,8 @@ esphome/components/sdl/* @clydebarrow
 | 
			
		||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
			
		||||
esphome/components/sdp3x/* @Azimath
 | 
			
		||||
esphome/components/seeed_mr24hpc1/* @limengdu
 | 
			
		||||
esphome/components/seeed_mr60bha2/* @limengdu
 | 
			
		||||
esphome/components/seeed_mr60fda2/* @limengdu
 | 
			
		||||
esphome/components/selec_meter/* @sourabhjaiswal
 | 
			
		||||
esphome/components/select/* @esphome/core
 | 
			
		||||
esphome/components/sen0321/* @notjj
 | 
			
		||||
@@ -408,6 +411,7 @@ esphome/components/substitutions/* @esphome/core
 | 
			
		||||
esphome/components/sun/* @OttoWinter
 | 
			
		||||
esphome/components/sun_gtil2/* @Mat931
 | 
			
		||||
esphome/components/switch/* @esphome/core
 | 
			
		||||
esphome/components/switch/binary_sensor/* @ssieb
 | 
			
		||||
esphome/components/t6615/* @tylermenezes
 | 
			
		||||
esphome/components/tc74/* @sethgirvan
 | 
			
		||||
esphome/components/tca9548a/* @andreashergert1984
 | 
			
		||||
 
 | 
			
		||||
@@ -99,15 +99,17 @@ BUILD_DEPS="
 | 
			
		||||
    libfreetype-dev=2.12.1+dfsg-5+deb12u3
 | 
			
		||||
    libssl-dev=3.0.15-1~deb12u1
 | 
			
		||||
    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
 | 
			
		||||
"
 | 
			
		||||
LIB_DEPS="
 | 
			
		||||
    libtiff6=4.5.0-6+deb12u1
 | 
			
		||||
    libopenjp2-7=2.5.0-2
 | 
			
		||||
"
 | 
			
		||||
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ] || [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]
 | 
			
		||||
then
 | 
			
		||||
    apt-get update
 | 
			
		||||
    apt-get install -y --no-install-recommends $BUILD_DEPS
 | 
			
		||||
    apt-get install -y --no-install-recommends $BUILD_DEPS $LIB_DEPS
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo
 | 
			
		||||
@@ -161,6 +163,18 @@ ENTRYPOINT ["/entrypoint.sh"]
 | 
			
		||||
CMD ["dashboard", "/config"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ARG BUILD_VERSION=dev
 | 
			
		||||
 | 
			
		||||
# Labels
 | 
			
		||||
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}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ======================= hassio-type image =======================
 | 
			
		||||
@@ -192,7 +206,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
 | 
			
		||||
# Labels
 | 
			
		||||
LABEL \
 | 
			
		||||
    io.hass.name="ESPHome" \
 | 
			
		||||
    io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
 | 
			
		||||
    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.type="addon" \
 | 
			
		||||
    io.hass.version="${BUILD_VERSION}"
 | 
			
		||||
    # io.hass.arch is inherited from addon-debian-base
 | 
			
		||||
@@ -207,17 +221,22 @@ ENV \
 | 
			
		||||
  PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
 | 
			
		||||
 | 
			
		||||
RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
    curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
 | 
			
		||||
    && echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \
 | 
			
		||||
    && 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 \
 | 
			
		||||
    && if [ "$TARGETARCH$TARGETVARIANT" != "armv7" ]; then \
 | 
			
		||||
    # move this up after armv7 is retired
 | 
			
		||||
    apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \
 | 
			
		||||
    fi; \
 | 
			
		||||
    rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
        /var/lib/apt/lists/*
 | 
			
		||||
 
 | 
			
		||||
@@ -363,7 +363,7 @@ def upload_program(config, args, host):
 | 
			
		||||
 | 
			
		||||
    from esphome import espota2
 | 
			
		||||
 | 
			
		||||
    remote_port = ota_conf[CONF_PORT]
 | 
			
		||||
    remote_port = int(ota_conf[CONF_PORT])
 | 
			
		||||
    password = ota_conf.get(CONF_PASSWORD, "")
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,12 @@
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include <esp_adc_cal.h>
 | 
			
		||||
#include "driver/adc.h"
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc {
 | 
			
		||||
@@ -43,7 +42,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
    this->channel1_ = ADC1_CHANNEL_MAX;
 | 
			
		||||
  }
 | 
			
		||||
  void set_autorange(bool autorange) { this->autorange_ = autorange; }
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
  /// Update ADC values
 | 
			
		||||
  void update() override;
 | 
			
		||||
@@ -59,11 +58,11 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
  std::string unique_id() override;
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  void set_is_temperature() { this->is_temperature_ = true; }
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
@@ -72,7 +71,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  bool is_temperature_{false};
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
			
		||||
@@ -83,8 +82,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
  esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
 | 
			
		||||
#else
 | 
			
		||||
  esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // ESP_IDF_VERSION_MAJOR
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace adc
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								esphome/components/adc/adc_sensor_common.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/adc/adc_sensor_common.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
#include "adc_sensor.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "adc.common";
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
}  // namespace adc
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,30 +1,13 @@
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "adc_sensor.h"
 | 
			
		||||
#include "esphome/core/helpers.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 adc {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "adc";
 | 
			
		||||
static const char *const TAG = "adc.esp32";
 | 
			
		||||
 | 
			
		||||
// 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);
 | 
			
		||||
 | 
			
		||||
#ifndef SOC_ADC_RTC_MAX_BITWIDTH
 | 
			
		||||
@@ -32,24 +15,15 @@ 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;
 | 
			
		||||
#else
 | 
			
		||||
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP32_VARIANT_ESP32S2
 | 
			
		||||
#endif  // SOC_ADC_RTC_MAX_BITWIDTH
 | 
			
		||||
 | 
			
		||||
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;  // 2048 (12 bit) or 4096 (13 bit)
 | 
			
		||||
#endif
 | 
			
		||||
static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;
 | 
			
		||||
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
extern "C"
 | 
			
		||||
#endif
 | 
			
		||||
    void
 | 
			
		||||
    ADCSensor::setup() {
 | 
			
		||||
void ADCSensor::setup() {
 | 
			
		||||
  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) {
 | 
			
		||||
    adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
 | 
			
		||||
    if (!this->autorange_) {
 | 
			
		||||
@@ -61,7 +35,6 @@ extern "C"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // load characteristics for each attenuation
 | 
			
		||||
  for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) {
 | 
			
		||||
    auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
 | 
			
		||||
    auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
 | 
			
		||||
@@ -79,31 +52,10 @@ extern "C"
 | 
			
		||||
        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() {
 | 
			
		||||
  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_);
 | 
			
		||||
  if (this->autorange_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Attenuation: auto");
 | 
			
		||||
@@ -125,55 +77,10 @@ void ADCSensor::dump_config() {
 | 
			
		||||
        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_);
 | 
			
		||||
  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() {
 | 
			
		||||
  if (!this->autorange_) {
 | 
			
		||||
    uint32_t sum = 0;
 | 
			
		||||
@@ -240,93 +147,17 @@ 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 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 c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
 | 
			
		||||
  uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
 | 
			
		||||
  uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF);
 | 
			
		||||
  // max theoretical csum value is 4096*4 = 16384
 | 
			
		||||
  uint32_t csum = 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);
 | 
			
		||||
  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 esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
							
								
								
									
										58
									
								
								esphome/components/adc/adc_sensor_esp8266.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								esphome/components/adc/adc_sensor_esp8266.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
#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_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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  // USE_ADC_SENSOR_VCC
 | 
			
		||||
  }
 | 
			
		||||
  raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero)
 | 
			
		||||
  if (this->output_raw_) {
 | 
			
		||||
    return raw;
 | 
			
		||||
  }
 | 
			
		||||
  return raw / 1024.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 | 
			
		||||
 | 
			
		||||
}  // namespace adc
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
							
								
								
									
										48
									
								
								esphome/components/adc/adc_sensor_libretiny.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								esphome/components/adc/adc_sensor_libretiny.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
#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_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace adc
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_LIBRETINY
 | 
			
		||||
							
								
								
									
										93
									
								
								esphome/components/adc/adc_sensor_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								esphome/components/adc/adc_sensor_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
#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_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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.0f : 1.0f;
 | 
			
		||||
  return raw * 3.3f / 4096.0f * coeff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace adc
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
@@ -72,10 +72,9 @@ void AlarmControlPanelCall::validate_() {
 | 
			
		||||
      this->state_.reset();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (state == ACP_STATE_DISARMED &&
 | 
			
		||||
        !(this->parent_->is_state_armed(this->parent_->get_state()) ||
 | 
			
		||||
          this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING ||
 | 
			
		||||
          this->parent_->get_state() == ACP_STATE_TRIGGERED)) {
 | 
			
		||||
    if (state == ACP_STATE_DISARMED && !this->parent_->is_state_armed(this->parent_->get_state()) &&
 | 
			
		||||
        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");
 | 
			
		||||
      this->state_.reset();
 | 
			
		||||
      return;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import logging
 | 
			
		||||
 | 
			
		||||
from esphome import automation, core
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import font
 | 
			
		||||
import esphome.components.image as espImage
 | 
			
		||||
from esphome.components.image import (
 | 
			
		||||
    CONF_USE_TRANSPARENCY,
 | 
			
		||||
@@ -131,7 +130,7 @@ ANIMATION_SCHEMA = cv.Schema(
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = ANIMATION_SCHEMA
 | 
			
		||||
 | 
			
		||||
NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -122,7 +122,8 @@ void APDS9306::update() {
 | 
			
		||||
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 | 
			
		||||
  if (!(status &= 0b00001000)) {  // No new data
 | 
			
		||||
  status &= 0b00001000;
 | 
			
		||||
  if (!status) {  // No new data
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace audio {
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
 | 
			
		||||
  void publish_initial_state(bool state);
 | 
			
		||||
 | 
			
		||||
  /// The current reported state of the binary sensor.
 | 
			
		||||
  bool state;
 | 
			
		||||
  bool state{false};
 | 
			
		||||
 | 
			
		||||
  void add_filter(Filter *filter);
 | 
			
		||||
  void add_filters(const std::vector<Filter *> &filters);
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ from esphome.components.libretiny.const import (
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
 | 
			
		||||
from .boards import BK72XX_BOARDS, BK72XX_BOARD_PINS
 | 
			
		||||
from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@kuba2k2"]
 | 
			
		||||
AUTO_LOAD = ["libretiny"]
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ bool CSE7766Component::check_byte_() {
 | 
			
		||||
  uint8_t index = this->raw_data_index_;
 | 
			
		||||
  uint8_t byte = this->raw_data_[index];
 | 
			
		||||
  if (index == 0) {
 | 
			
		||||
    return !((byte != 0x55) && ((byte & 0xF0) != 0xF0) && (byte != 0xAA));
 | 
			
		||||
    return (byte == 0x55) || ((byte & 0xF0) == 0xF0) || (byte == 0xAA);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (index == 1) {
 | 
			
		||||
 
 | 
			
		||||
@@ -60,9 +60,7 @@ ESPTime DateTimeEntity::state_as_esptime() const {
 | 
			
		||||
  obj.hour = this->hour_;
 | 
			
		||||
  obj.minute = this->minute_;
 | 
			
		||||
  obj.second = this->second_;
 | 
			
		||||
  obj.day_of_week = 1;  // Required to be valid for recalc_timestamp_local but not used.
 | 
			
		||||
  obj.day_of_year = 1;  // Required to be valid for recalc_timestamp_local but not used.
 | 
			
		||||
  obj.recalc_timestamp_local(false);
 | 
			
		||||
  obj.recalc_timestamp_local();
 | 
			
		||||
  return obj;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -52,11 +52,11 @@ void DeepSleepComponent::dump_config_platform_() {
 | 
			
		||||
 | 
			
		||||
bool DeepSleepComponent::prepare_to_sleep_() {
 | 
			
		||||
  if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr &&
 | 
			
		||||
      !this->sleep_duration_.has_value() && this->wakeup_pin_->digital_read()) {
 | 
			
		||||
      this->wakeup_pin_->digital_read()) {
 | 
			
		||||
    // Defer deep sleep until inactive
 | 
			
		||||
    if (!this->next_enter_deep_sleep_) {
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      ESP_LOGW(TAG, "Waiting for pin_ to switch state to enter deep sleep...");
 | 
			
		||||
      ESP_LOGW(TAG, "Waiting wakeup pin state change to enter deep sleep...");
 | 
			
		||||
    }
 | 
			
		||||
    this->next_enter_deep_sleep_ = true;
 | 
			
		||||
    return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -135,7 +135,8 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
 | 
			
		||||
 | 
			
		||||
      // Wait for falling edge
 | 
			
		||||
      while (this->pin_->digital_read()) {
 | 
			
		||||
        if ((end_time = micros()) - start_time > 90) {
 | 
			
		||||
        end_time = micros();
 | 
			
		||||
        if (end_time - start_time > 90) {
 | 
			
		||||
          if (i < 0) {
 | 
			
		||||
            error_code = 3;
 | 
			
		||||
          } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#include "display.h"
 | 
			
		||||
#include "display_color_utils.h"
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include "display_color_utils.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
@@ -662,20 +662,24 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
 | 
			
		||||
  if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
 | 
			
		||||
    this->trigger(from, to);
 | 
			
		||||
}
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
 | 
			
		||||
                       ESPTime time) {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  size_t ret = time.strftime(buffer, sizeof(buffer), format);
 | 
			
		||||
  if (ret > 0)
 | 
			
		||||
    this->print(x, y, font, color, align, buffer);
 | 
			
		||||
    this->print(x, y, font, color, align, buffer, background);
 | 
			
		||||
}
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, color, COLOR_OFF, align, format, time);
 | 
			
		||||
}
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
 | 
			
		||||
  this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
 | 
			
		||||
}
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, COLOR_ON, align, format, time);
 | 
			
		||||
  this->strftime(x, y, font, COLOR_ON, COLOR_OFF, align, format, time);
 | 
			
		||||
}
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
 | 
			
		||||
  this->strftime(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Display::start_clipping(Rect rect) {
 | 
			
		||||
 
 | 
			
		||||
@@ -437,6 +437,20 @@ class Display : public PollingComponent {
 | 
			
		||||
   */
 | 
			
		||||
  void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param background The background color to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param format The format to use.
 | 
			
		||||
   * @param ... The arguments to use for the text formatting.
 | 
			
		||||
   */
 | 
			
		||||
  void strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
 | 
			
		||||
                ESPTime time) __attribute__((format(strftime, 8, 0)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
 
 | 
			
		||||
@@ -68,8 +68,6 @@ IsActiveCondition = display_menu_base_ns.class_(
 | 
			
		||||
    "IsActiveCondition", automation.Condition
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
MenuItemType = display_menu_base_ns.enum("MenuItemType")
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_TYPES = {
 | 
			
		||||
 
 | 
			
		||||
@@ -280,7 +280,7 @@ bool DisplayMenuComponent::cursor_down_() {
 | 
			
		||||
bool DisplayMenuComponent::enter_menu_() {
 | 
			
		||||
  this->displayed_item_->on_leave();
 | 
			
		||||
  this->displayed_item_ = static_cast<MenuItemMenu *>(this->get_selected_item_());
 | 
			
		||||
  this->selection_stack_.push_front({this->top_index_, this->cursor_index_});
 | 
			
		||||
  this->selection_stack_.emplace_front(this->top_index_, this->cursor_index_);
 | 
			
		||||
  this->cursor_index_ = this->top_index_ = 0;
 | 
			
		||||
  this->displayed_item_->on_enter();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -296,7 +296,7 @@ void Dsmr::dump_config() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Dsmr::set_decryption_key(const std::string &decryption_key) {
 | 
			
		||||
  if (decryption_key.length() == 0) {
 | 
			
		||||
  if (decryption_key.empty()) {
 | 
			
		||||
    ESP_LOGI(TAG, "Disabling decryption");
 | 
			
		||||
    this->decryption_key_.clear();
 | 
			
		||||
    if (this->crypt_telegram_ != nullptr) {
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,8 @@ _LOGGER = logging.getLogger(__name__)
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
AUTO_LOAD = ["preferences"]
 | 
			
		||||
 | 
			
		||||
CONF_RELEASE = "release"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_core_data(config):
 | 
			
		||||
    CORE.data[KEY_ESP32] = {}
 | 
			
		||||
@@ -216,11 +218,17 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
 | 
			
		||||
    return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _format_framework_espidf_version(ver: cv.Version) -> str:
 | 
			
		||||
def _format_framework_espidf_version(
 | 
			
		||||
    ver: cv.Version, release: str, for_platformio: bool
 | 
			
		||||
) -> str:
 | 
			
		||||
    # format the given arduino (https://github.com/espressif/esp-idf/releases) version to
 | 
			
		||||
    # a PIO platformio/framework-espidf value
 | 
			
		||||
    # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
 | 
			
		||||
    return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
 | 
			
		||||
    if for_platformio:
 | 
			
		||||
        return f"platformio/framework-espidf@~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
 | 
			
		||||
    if release:
 | 
			
		||||
        return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip"
 | 
			
		||||
    return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NOTE: Keep this in mind when updating the recommended version:
 | 
			
		||||
@@ -241,11 +249,33 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
 | 
			
		||||
# The default/recommended esp-idf framework version
 | 
			
		||||
#  - https://github.com/espressif/esp-idf/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
 | 
			
		||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8)
 | 
			
		||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5)
 | 
			
		||||
# The platformio/espressif32 version to use for esp-idf frameworks
 | 
			
		||||
#  - https://github.com/platformio/platform-espressif32/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
 | 
			
		||||
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 4, 0)
 | 
			
		||||
ESP_IDF_PLATFORM_VERSION = cv.Version(51, 3, 7)
 | 
			
		||||
 | 
			
		||||
# List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions
 | 
			
		||||
SUPPORTED_PLATFORMIO_ESP_IDF_5X = [
 | 
			
		||||
    cv.Version(5, 3, 1),
 | 
			
		||||
    cv.Version(5, 3, 0),
 | 
			
		||||
    cv.Version(5, 2, 2),
 | 
			
		||||
    cv.Version(5, 2, 1),
 | 
			
		||||
    cv.Version(5, 1, 2),
 | 
			
		||||
    cv.Version(5, 1, 1),
 | 
			
		||||
    cv.Version(5, 1, 0),
 | 
			
		||||
    cv.Version(5, 0, 2),
 | 
			
		||||
    cv.Version(5, 0, 1),
 | 
			
		||||
    cv.Version(5, 0, 0),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# pioarduino versions that don't require a release number
 | 
			
		||||
# List based on https://github.com/pioarduino/esp-idf/releases
 | 
			
		||||
SUPPORTED_PIOARDUINO_ESP_IDF_5X = [
 | 
			
		||||
    cv.Version(5, 3, 1),
 | 
			
		||||
    cv.Version(5, 3, 0),
 | 
			
		||||
    cv.Version(5, 1, 5),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _arduino_check_versions(value):
 | 
			
		||||
@@ -286,8 +316,8 @@ def _arduino_check_versions(value):
 | 
			
		||||
def _esp_idf_check_versions(value):
 | 
			
		||||
    value = value.copy()
 | 
			
		||||
    lookups = {
 | 
			
		||||
        "dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"),
 | 
			
		||||
        "latest": (cv.Version(5, 1, 2), None),
 | 
			
		||||
        "dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"),
 | 
			
		||||
        "latest": (cv.Version(5, 1, 5), None),
 | 
			
		||||
        "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -305,13 +335,51 @@ def _esp_idf_check_versions(value):
 | 
			
		||||
    if version < cv.Version(4, 0, 0):
 | 
			
		||||
        raise cv.Invalid("Only ESP-IDF 4.0+ is supported.")
 | 
			
		||||
 | 
			
		||||
    value[CONF_VERSION] = str(version)
 | 
			
		||||
    value[CONF_SOURCE] = source or _format_framework_espidf_version(version)
 | 
			
		||||
    # flag this for later *before* we set value[CONF_PLATFORM_VERSION] below
 | 
			
		||||
    has_platform_ver = CONF_PLATFORM_VERSION in value
 | 
			
		||||
 | 
			
		||||
    value[CONF_PLATFORM_VERSION] = value.get(
 | 
			
		||||
        CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION))
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        (is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION]))
 | 
			
		||||
        and version.major >= 5
 | 
			
		||||
        and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X
 | 
			
		||||
    ):
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"ESP-IDF {str(version)} not supported by platformio/espressif32"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        version.major < 5
 | 
			
		||||
        or (
 | 
			
		||||
            version in SUPPORTED_PLATFORMIO_ESP_IDF_5X
 | 
			
		||||
            and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
 | 
			
		||||
        )
 | 
			
		||||
    ) and not has_platform_ver:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        not is_platformio
 | 
			
		||||
        and CONF_RELEASE not in value
 | 
			
		||||
        and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
 | 
			
		||||
    ):
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"ESP-IDF {value[CONF_VERSION]} is not available with pioarduino; you may need to specify '{CONF_RELEASE}'"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    value[CONF_VERSION] = str(version)
 | 
			
		||||
    value[CONF_SOURCE] = source or _format_framework_espidf_version(
 | 
			
		||||
        version, value.get(CONF_RELEASE, None), is_platformio
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if value[CONF_SOURCE].startswith("http"):
 | 
			
		||||
        # prefix is necessary or platformio will complain with a cryptic error
 | 
			
		||||
        value[CONF_SOURCE] = f"framework-espidf@{value[CONF_SOURCE]}"
 | 
			
		||||
 | 
			
		||||
    if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "The selected ESP-IDF framework version is not the recommended one. "
 | 
			
		||||
@@ -323,6 +391,12 @@ def _esp_idf_check_versions(value):
 | 
			
		||||
 | 
			
		||||
def _parse_platform_version(value):
 | 
			
		||||
    try:
 | 
			
		||||
        ver = cv.Version.parse(cv.version_number(value))
 | 
			
		||||
        if ver.major >= 50:  # a pioarduino version
 | 
			
		||||
            if "-" in value:
 | 
			
		||||
                # maybe a release candidate?...definitely not our default, just use it as-is...
 | 
			
		||||
                return f"https://github.com/pioarduino/platform-espressif32.git#{value}"
 | 
			
		||||
            return f"https://github.com/pioarduino/platform-espressif32.git#{ver.major}.{ver.minor:02d}.{ver.patch:02d}"
 | 
			
		||||
        # if platform version is a valid version constraint, prefix the default package
 | 
			
		||||
        cv.platformio_version_constraint(value)
 | 
			
		||||
        return f"platformio/espressif32@{value}"
 | 
			
		||||
@@ -330,6 +404,14 @@ def _parse_platform_version(value):
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _platform_is_platformio(value):
 | 
			
		||||
    try:
 | 
			
		||||
        ver = cv.Version.parse(cv.version_number(value))
 | 
			
		||||
        return ver.major < 50
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        return "platformio" in value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _detect_variant(value):
 | 
			
		||||
    board = value[CONF_BOARD]
 | 
			
		||||
    if board in BOARDS:
 | 
			
		||||
@@ -355,24 +437,20 @@ def _detect_variant(value):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def final_validate(config):
 | 
			
		||||
    if CONF_PLATFORMIO_OPTIONS not in fv.full_config.get()[CONF_ESPHOME]:
 | 
			
		||||
    if not (
 | 
			
		||||
        pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS)
 | 
			
		||||
    ):
 | 
			
		||||
        # Not specified or empty
 | 
			
		||||
        return config
 | 
			
		||||
 | 
			
		||||
    pio_flash_size_key = "board_upload.flash_size"
 | 
			
		||||
    pio_partitions_key = "board_build.partitions"
 | 
			
		||||
    if (
 | 
			
		||||
        CONF_PARTITIONS in config
 | 
			
		||||
        and pio_partitions_key
 | 
			
		||||
        in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
 | 
			
		||||
    ):
 | 
			
		||||
    if CONF_PARTITIONS in config and pio_partitions_key in pio_options:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        pio_flash_size_key
 | 
			
		||||
        in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
 | 
			
		||||
    ):
 | 
			
		||||
    if pio_flash_size_key in pio_options:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
 | 
			
		||||
        )
 | 
			
		||||
@@ -412,6 +490,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_RELEASE): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_SOURCE): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
 | 
			
		||||
            cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
 | 
			
		||||
@@ -515,15 +594,17 @@ async def to_code(config):
 | 
			
		||||
        cg.add_build_flag("-DUSE_ESP_IDF")
 | 
			
		||||
        cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
 | 
			
		||||
        cg.add_build_flag("-Wno-nonnull-compare")
 | 
			
		||||
        cg.add_platformio_option(
 | 
			
		||||
            "platform_packages",
 | 
			
		||||
            [f"platformio/framework-espidf@{conf[CONF_SOURCE]}"],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
 | 
			
		||||
 | 
			
		||||
        # platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
 | 
			
		||||
        # This is espressif's own published version which is more up to date.
 | 
			
		||||
        cg.add_platformio_option(
 | 
			
		||||
            "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
 | 
			
		||||
        )
 | 
			
		||||
        add_idf_sdkconfig_option(
 | 
			
		||||
            f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
 | 
			
		||||
        )
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
 | 
			
		||||
        add_idf_sdkconfig_option(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,12 @@
 | 
			
		||||
from .const import VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3
 | 
			
		||||
from .const import (
 | 
			
		||||
    VARIANT_ESP32,
 | 
			
		||||
    VARIANT_ESP32C2,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32C6,
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
    VARIANT_ESP32S2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ESP32_BASE_PINS = {
 | 
			
		||||
    "TX": 1,
 | 
			
		||||
@@ -1344,6 +1352,26 @@ done | sort
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
BOARDS = {
 | 
			
		||||
    "4d_systems_esp32s3_gen4_r8n16": {
 | 
			
		||||
        "name": "4D Systems GEN4-ESP32 16MB (ESP32S3-R8N16)",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_camera_esp32s3": {
 | 
			
		||||
        "name": "Adafruit pyCamera S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32c6": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-C6",
 | 
			
		||||
        "variant": VARIANT_ESP32C6,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32s2": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32s2_reversetft": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S2 Reverse TFT",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32s2_tft": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S2 TFT",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
@@ -1356,6 +1384,10 @@ BOARDS = {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S3 No PSRAM",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32s3_reversetft": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S3 Reverse TFT",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32s3_tft": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S3 TFT",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
@@ -1376,10 +1408,18 @@ BOARDS = {
 | 
			
		||||
        "name": "Adafruit MagTag 2.9",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_matrixportal_esp32s3": {
 | 
			
		||||
        "name": "Adafruit MatrixPortal ESP32-S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_metro_esp32s2": {
 | 
			
		||||
        "name": "Adafruit Metro ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_metro_esp32s3": {
 | 
			
		||||
        "name": "Adafruit Metro ESP32-S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qtpy_esp32c3": {
 | 
			
		||||
        "name": "Adafruit QT Py ESP32-C3",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
@@ -1392,10 +1432,18 @@ BOARDS = {
 | 
			
		||||
        "name": "Adafruit QT Py ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qtpy_esp32s3_n4r2": {
 | 
			
		||||
        "name": "Adafruit QT Py ESP32-S3 (4M Flash 2M PSRAM)",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qtpy_esp32s3_nopsram": {
 | 
			
		||||
        "name": "Adafruit QT Py ESP32-S3 No PSRAM",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qualia_s3_rgb666": {
 | 
			
		||||
        "name": "Adafruit Qualia ESP32-S3 RGB666",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "airm2m_core_esp32c3": {
 | 
			
		||||
        "name": "AirM2M CORE ESP32C3",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
@@ -1404,14 +1452,30 @@ BOARDS = {
 | 
			
		||||
        "name": "ALKS ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "arduino_nano_esp32": {
 | 
			
		||||
        "name": "Arduino Nano ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "atd147_s3": {
 | 
			
		||||
        "name": "ArtronShop ATD1.47-S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "atmegazero_esp32s2": {
 | 
			
		||||
        "name": "EspinalLab ATMegaZero ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "aventen_s3_sync": {
 | 
			
		||||
        "name": "Aventen S3 Sync",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "az-delivery-devkit-v4": {
 | 
			
		||||
        "name": "AZ-Delivery ESP-32 Dev Kit C V4",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "bee_data_logger": {
 | 
			
		||||
        "name": "Smart Bee Data Logger",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "bee_motion_mini": {
 | 
			
		||||
        "name": "Smart Bee Motion Mini",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
@@ -1436,14 +1500,6 @@ BOARDS = {
 | 
			
		||||
        "name": "BPI-Leaf-S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "briki_abc_esp32": {
 | 
			
		||||
        "name": "Briki ABC (MBC-WB) - ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "briki_mbc-wb_esp32": {
 | 
			
		||||
        "name": "Briki MBC-WB - ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "cnrs_aw2eth": {
 | 
			
		||||
        "name": "CNRS AW2ETH",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1496,18 +1552,38 @@ BOARDS = {
 | 
			
		||||
        "name": "DFRobot Beetle ESP32-C3",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "dfrobot_firebeetle2_esp32e": {
 | 
			
		||||
        "name": "DFRobot Firebeetle 2 ESP32-E",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "dfrobot_firebeetle2_esp32s3": {
 | 
			
		||||
        "name": "DFRobot Firebeetle 2 ESP32-S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "dfrobot_romeo_esp32s3": {
 | 
			
		||||
        "name": "DFRobot Romeo ESP32-S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "dpu_esp32": {
 | 
			
		||||
        "name": "TAMC DPU ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "edgebox-esp-100": {
 | 
			
		||||
        "name": "Seeed Studio Edgebox-ESP-100",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp320": {
 | 
			
		||||
        "name": "Electronic SweetPeas ESP320",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-c2-devkitm-1": {
 | 
			
		||||
        "name": "Espressif ESP32-C2-DevKitM-1",
 | 
			
		||||
        "variant": VARIANT_ESP32C2,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-c3-devkitc-02": {
 | 
			
		||||
        "name": "Espressif ESP32-C3-DevKitC-02",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-c3-devkitm-1": {
 | 
			
		||||
        "name": "Espressif ESP32-C3-DevKitM-1",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
@@ -1516,6 +1592,14 @@ BOARDS = {
 | 
			
		||||
        "name": "Ai-Thinker ESP-C3-M1-I-Kit",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-c6-devkitc-1": {
 | 
			
		||||
        "name": "Espressif ESP32-C6-DevKitC-1",
 | 
			
		||||
        "variant": VARIANT_ESP32C6,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-c6-devkitm-1": {
 | 
			
		||||
        "name": "Espressif ESP32-C6-DevKitM-1",
 | 
			
		||||
        "variant": VARIANT_ESP32C6,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32cam": {
 | 
			
		||||
        "name": "AI Thinker ESP32-CAM",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1544,6 +1628,14 @@ BOARDS = {
 | 
			
		||||
        "name": "OLIMEX ESP32-GATEWAY",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-h2-devkitm-1": {
 | 
			
		||||
        "name": "Espressif ESP32-H2-DevKit",
 | 
			
		||||
        "variant": VARIANT_ESP32H2,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-pico-devkitm-2": {
 | 
			
		||||
        "name": "Espressif ESP32-PICO-DevKitM-2",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-poe-iso": {
 | 
			
		||||
        "name": "OLIMEX ESP32-PoE-ISO",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1580,10 +1672,22 @@ BOARDS = {
 | 
			
		||||
        "name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-s3-korvo-2": {
 | 
			
		||||
        "name": "Espressif ESP32-S3-Korvo-2",
 | 
			
		||||
    "esp32-s3-devkitm-1": {
 | 
			
		||||
        "name": "Espressif ESP32-S3-DevKitM-1",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32s3_powerfeather": {
 | 
			
		||||
        "name": "ESP32-S3 PowerFeather",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32s3usbotg": {
 | 
			
		||||
        "name": "Espressif ESP32-S3-USB-OTG",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-solo1": {
 | 
			
		||||
        "name": "Espressif Generic ESP32-solo1 4M Flash",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32thing": {
 | 
			
		||||
        "name": "SparkFun ESP32 Thing",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1652,9 +1756,9 @@ BOARDS = {
 | 
			
		||||
        "name": "Heltec WiFi Kit 32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wifi_kit_32_v2": {
 | 
			
		||||
        "name": "Heltec WiFi Kit 32 (V2)",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    "heltec_wifi_kit_32_V3": {
 | 
			
		||||
        "name": "Heltec WiFi Kit 32 (V3)",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wifi_lora_32": {
 | 
			
		||||
        "name": "Heltec WiFi LoRa 32",
 | 
			
		||||
@@ -1664,6 +1768,10 @@ BOARDS = {
 | 
			
		||||
        "name": "Heltec WiFi LoRa 32 (V2)",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wifi_lora_32_V3": {
 | 
			
		||||
        "name": "Heltec WiFi LoRa 32 (V3)",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wireless_stick_lite": {
 | 
			
		||||
        "name": "Heltec Wireless Stick Lite",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1708,6 +1816,14 @@ BOARDS = {
 | 
			
		||||
        "name": "oddWires IoT-Bus Proteus",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ioxesp32": {
 | 
			
		||||
        "name": "ArtronShop IOXESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ioxesp32ps": {
 | 
			
		||||
        "name": "ArtronShop IOXESP32PS",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "kb32-ft": {
 | 
			
		||||
        "name": "MakerAsia KB32-FT",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1720,10 +1836,26 @@ BOARDS = {
 | 
			
		||||
        "name": "Labplus mPython",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lilka_v2": {
 | 
			
		||||
        "name": "Lilka v2",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "lilygo-t-display": {
 | 
			
		||||
        "name": "LilyGo T-Display",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lilygo-t-display-s3": {
 | 
			
		||||
        "name": "LilyGo T-Display-S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "lionbit": {
 | 
			
		||||
        "name": "Lion:Bit Dev Board",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lionbits3": {
 | 
			
		||||
        "name": "Lion:Bit S3 STEM Dev Board",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin32_lite": {
 | 
			
		||||
        "name": "WEMOS LOLIN32 Lite",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1752,10 +1884,18 @@ BOARDS = {
 | 
			
		||||
        "name": "WEMOS LOLIN S2 PICO",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_s3_mini": {
 | 
			
		||||
        "name": "WEMOS LOLIN S3 Mini",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_s3": {
 | 
			
		||||
        "name": "WEMOS LOLIN S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_s3_pro": {
 | 
			
		||||
        "name": "WEMOS LOLIN S3 PRO",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "lopy4": {
 | 
			
		||||
        "name": "Pycom LoPy4",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1768,10 +1908,18 @@ BOARDS = {
 | 
			
		||||
        "name": "M5Stack-ATOM",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-atoms3": {
 | 
			
		||||
        "name": "M5Stack AtomS3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-core2": {
 | 
			
		||||
        "name": "M5Stack Core2",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-core-esp32-16M": {
 | 
			
		||||
        "name": "M5Stack Core ESP32 16M",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-core-esp32": {
 | 
			
		||||
        "name": "M5Stack Core ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1780,6 +1928,10 @@ BOARDS = {
 | 
			
		||||
        "name": "M5Stack-Core Ink",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-cores3": {
 | 
			
		||||
        "name": "M5Stack CoreS3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-fire": {
 | 
			
		||||
        "name": "M5Stack FIRE",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1788,6 +1940,14 @@ BOARDS = {
 | 
			
		||||
        "name": "M5Stack GREY ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack_paper": {
 | 
			
		||||
        "name": "M5Stack Paper",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-stamps3": {
 | 
			
		||||
        "name": "M5Stack StampS3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-station": {
 | 
			
		||||
        "name": "M5Stack Station",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1796,6 +1956,10 @@ BOARDS = {
 | 
			
		||||
        "name": "M5Stack Timer CAM",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stamp-pico": {
 | 
			
		||||
        "name": "M5Stamp-Pico",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stick-c": {
 | 
			
		||||
        "name": "M5Stick-C",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1832,10 +1996,26 @@ BOARDS = {
 | 
			
		||||
        "name": "Deparment of Alchemy MiniMain ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "motorgo_mini_1": {
 | 
			
		||||
        "name": "MotorGo Mini 1 (ESP32-S3)",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "namino_arancio": {
 | 
			
		||||
        "name": "Namino Arancio",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "namino_rosso": {
 | 
			
		||||
        "name": "Namino Rosso",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "nano32": {
 | 
			
		||||
        "name": "MakerAsia Nano32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "nebulas3": {
 | 
			
		||||
        "name": "Kinetic Dynamics Nebula S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "nina_w10": {
 | 
			
		||||
        "name": "u-blox NINA-W10 series",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1896,10 +2076,22 @@ BOARDS = {
 | 
			
		||||
        "name": "Munich Labs RedPill ESP32-S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "roboheart_hercules": {
 | 
			
		||||
        "name": "RoboHeart Hercules",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "seeed_xiao_esp32c3": {
 | 
			
		||||
        "name": "Seeed Studio XIAO ESP32C3",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "seeed_xiao_esp32s3": {
 | 
			
		||||
        "name": "Seeed Studio XIAO ESP32S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "sensebox_mcu_esp32s2": {
 | 
			
		||||
        "name": "senseBox MCU-S2 ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "sensesiot_weizen": {
 | 
			
		||||
        "name": "LOGISENSES Senses Weizen",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -1912,6 +2104,10 @@ BOARDS = {
 | 
			
		||||
        "name": "S.ODI Ultra v1",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "sparkfun_esp32c6_thing_plus": {
 | 
			
		||||
        "name": "Sparkfun ESP32-C6 Thing Plus",
 | 
			
		||||
        "variant": VARIANT_ESP32C6,
 | 
			
		||||
    },
 | 
			
		||||
    "sparkfun_esp32_iot_redboard": {
 | 
			
		||||
        "name": "SparkFun ESP32 IoT RedBoard",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
@@ -2004,6 +2200,10 @@ BOARDS = {
 | 
			
		||||
        "name": "Unexpected Maker FeatherS3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "um_nanos3": {
 | 
			
		||||
        "name": "Unexpected Maker NanoS3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "um_pros3": {
 | 
			
		||||
        "name": "Unexpected Maker PROS3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
@@ -2040,6 +2240,14 @@ BOARDS = {
 | 
			
		||||
        "name": "uPesy ESP32 Wrover DevKit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "valtrack_v4_mfw_esp32_c3": {
 | 
			
		||||
        "name": "Valetron Systems VALTRACK-V4MVF",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "valtrack_v4_vts_esp32_c3": {
 | 
			
		||||
        "name": "Valetron Systems VALTRACK-V4VTS",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "vintlabs-devkit-v1": {
 | 
			
		||||
        "name": "VintLabs ESP32 Devkit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,10 @@ from esphome import automation
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
 | 
			
		||||
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz", "@Rapsssito"]
 | 
			
		||||
@@ -50,6 +52,7 @@ TX_POWER_LEVELS = {
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ESP32BLE),
 | 
			
		||||
        cv.Optional(CONF_NAME): cv.All(cv.string, cv.Length(max=20)),
 | 
			
		||||
        cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
 | 
			
		||||
            IO_CAPABILITY, lower=True
 | 
			
		||||
        ),
 | 
			
		||||
@@ -67,7 +70,22 @@ def validate_variant(_):
 | 
			
		||||
        raise cv.Invalid(f"{variant} does not support Bluetooth")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = validate_variant
 | 
			
		||||
def final_validation(config):
 | 
			
		||||
    validate_variant(config)
 | 
			
		||||
    if (name := config.get(CONF_NAME)) is not None:
 | 
			
		||||
        full_config = fv.full_config.get()
 | 
			
		||||
        max_length = 20
 | 
			
		||||
        if full_config[CONF_ESPHOME][CONF_NAME_ADD_MAC_SUFFIX]:
 | 
			
		||||
            max_length -= 7  # "-AABBCC" is appended when add mac suffix option is used
 | 
			
		||||
        if len(name) > max_length:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"Name '{name}' is too long, maximum length is {max_length} characters"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = final_validation
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
@@ -75,6 +93,8 @@ async def to_code(config):
 | 
			
		||||
    cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT]))
 | 
			
		||||
    cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
 | 
			
		||||
    cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
 | 
			
		||||
    if (name := config.get(CONF_NAME)) is not None:
 | 
			
		||||
        cg.add(var.set_name(name))
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,9 @@ namespace esp32_ble {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32_ble";
 | 
			
		||||
 | 
			
		||||
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR(  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
    RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
 | 
			
		||||
 | 
			
		||||
void ESP32BLE::setup() {
 | 
			
		||||
  global_ble = this;
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up BLE...");
 | 
			
		||||
@@ -188,12 +191,20 @@ bool ESP32BLE::ble_setup_() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string name = App.get_name();
 | 
			
		||||
  if (name.length() > 20) {
 | 
			
		||||
  std::string name;
 | 
			
		||||
  if (this->name_.has_value()) {
 | 
			
		||||
    name = this->name_.value();
 | 
			
		||||
    if (App.is_name_add_mac_suffix_enabled()) {
 | 
			
		||||
      name.erase(name.begin() + 13, name.end() - 7);  // Remove characters between 13 and the mac address
 | 
			
		||||
    } else {
 | 
			
		||||
      name = name.substr(0, 20);
 | 
			
		||||
      name += "-" + get_mac_address().substr(6);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    name = App.get_name();
 | 
			
		||||
    if (name.length() > 20) {
 | 
			
		||||
      if (App.is_name_add_mac_suffix_enabled()) {
 | 
			
		||||
        name.erase(name.begin() + 13, name.end() - 7);  // Remove characters between 13 and the mac address
 | 
			
		||||
      } else {
 | 
			
		||||
        name = name.substr(0, 20);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -314,7 +325,8 @@ void ESP32BLE::loop() {
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    delete ble_event;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
    ble_event->~BLEEvent();
 | 
			
		||||
    EVENT_ALLOCATOR.deallocate(ble_event, 1);
 | 
			
		||||
    ble_event = this->ble_events_.pop();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->advertising_ != nullptr) {
 | 
			
		||||
@@ -323,9 +335,14 @@ void ESP32BLE::loop() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
			
		||||
  BLEEvent *new_event = new BLEEvent(event, param);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
 | 
			
		||||
  if (new_event == nullptr) {
 | 
			
		||||
    // Memory too fragmented to allocate new event. Can only drop it until memory comes back
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  new (new_event) BLEEvent(event, param);
 | 
			
		||||
  global_ble->ble_events_.push(new_event);
 | 
			
		||||
}  // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
 | 
			
		||||
}  // NOLINT(clang-analyzer-unix.Malloc)
 | 
			
		||||
 | 
			
		||||
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
			
		||||
  ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event);
 | 
			
		||||
@@ -336,9 +353,14 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap
 | 
			
		||||
 | 
			
		||||
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
 | 
			
		||||
                                   esp_ble_gatts_cb_param_t *param) {
 | 
			
		||||
  BLEEvent *new_event = new BLEEvent(event, gatts_if, param);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
 | 
			
		||||
  if (new_event == nullptr) {
 | 
			
		||||
    // Memory too fragmented to allocate new event. Can only drop it until memory comes back
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  new (new_event) BLEEvent(event, gatts_if, param);
 | 
			
		||||
  global_ble->ble_events_.push(new_event);
 | 
			
		||||
}  // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
 | 
			
		||||
}  // NOLINT(clang-analyzer-unix.Malloc)
 | 
			
		||||
 | 
			
		||||
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
 | 
			
		||||
                                         esp_ble_gatts_cb_param_t *param) {
 | 
			
		||||
@@ -350,9 +372,14 @@ void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if
 | 
			
		||||
 | 
			
		||||
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                   esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  BLEEvent *new_event = new BLEEvent(event, gattc_if, param);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
 | 
			
		||||
  if (new_event == nullptr) {
 | 
			
		||||
    // Memory too fragmented to allocate new event. Can only drop it until memory comes back
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  new (new_event) BLEEvent(event, gattc_if, param);
 | 
			
		||||
  global_ble->ble_events_.push(new_event);
 | 
			
		||||
}  // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
 | 
			
		||||
}  // NOLINT(clang-analyzer-unix.Malloc)
 | 
			
		||||
 | 
			
		||||
void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                         esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
 
 | 
			
		||||
@@ -90,6 +90,7 @@ class ESP32BLE : public Component {
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void set_name(const std::string &name) { this->name_ = name; }
 | 
			
		||||
 | 
			
		||||
  void advertising_start();
 | 
			
		||||
  void advertising_set_service_data(const std::vector<uint8_t> &data);
 | 
			
		||||
@@ -131,6 +132,7 @@ class ESP32BLE : public Component {
 | 
			
		||||
  esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
 | 
			
		||||
  uint32_t advertising_cycle_time_;
 | 
			
		||||
  bool enable_on_boot_;
 | 
			
		||||
  optional<std::string> name_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
 | 
			
		||||
ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) {
 | 
			
		||||
  ESPBTUUID ret;
 | 
			
		||||
  ret.uuid_.len = ESP_UUID_LEN_128;
 | 
			
		||||
  for (int i = 0; i < ESP_UUID_LEN_128; i++)
 | 
			
		||||
  for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++)
 | 
			
		||||
    ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i];
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
@@ -43,30 +43,30 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
 | 
			
		||||
  if (data.length() == 4) {
 | 
			
		||||
    ret.uuid_.len = ESP_UUID_LEN_16;
 | 
			
		||||
    ret.uuid_.uuid.uuid16 = 0;
 | 
			
		||||
    for (int i = 0; i < data.length();) {
 | 
			
		||||
    for (uint i = 0; i < data.length(); i += 2) {
 | 
			
		||||
      uint8_t msb = data.c_str()[i];
 | 
			
		||||
      uint8_t lsb = data.c_str()[i + 1];
 | 
			
		||||
      uint8_t lsb_shift = i <= 2 ? (2 - i) * 4 : 0;
 | 
			
		||||
 | 
			
		||||
      if (msb > '9')
 | 
			
		||||
        msb -= 7;
 | 
			
		||||
      if (lsb > '9')
 | 
			
		||||
        lsb -= 7;
 | 
			
		||||
      ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4;
 | 
			
		||||
      i += 2;
 | 
			
		||||
      ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift;
 | 
			
		||||
    }
 | 
			
		||||
  } else if (data.length() == 8) {
 | 
			
		||||
    ret.uuid_.len = ESP_UUID_LEN_32;
 | 
			
		||||
    ret.uuid_.uuid.uuid32 = 0;
 | 
			
		||||
    for (int i = 0; i < data.length();) {
 | 
			
		||||
    for (uint i = 0; i < data.length(); i += 2) {
 | 
			
		||||
      uint8_t msb = data.c_str()[i];
 | 
			
		||||
      uint8_t lsb = data.c_str()[i + 1];
 | 
			
		||||
      uint8_t lsb_shift = i <= 6 ? (6 - i) * 4 : 0;
 | 
			
		||||
 | 
			
		||||
      if (msb > '9')
 | 
			
		||||
        msb -= 7;
 | 
			
		||||
      if (lsb > '9')
 | 
			
		||||
        lsb -= 7;
 | 
			
		||||
      ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4;
 | 
			
		||||
      i += 2;
 | 
			
		||||
      ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift;
 | 
			
		||||
    }
 | 
			
		||||
  } else if (data.length() == 16) {  // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
 | 
			
		||||
                                     // investigated (lack of time)
 | 
			
		||||
@@ -77,7 +77,7 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
 | 
			
		||||
    // UUID format.
 | 
			
		||||
    ret.uuid_.len = ESP_UUID_LEN_128;
 | 
			
		||||
    int n = 0;
 | 
			
		||||
    for (int i = 0; i < data.length();) {
 | 
			
		||||
    for (uint i = 0; i < data.length(); i += 2) {
 | 
			
		||||
      if (data.c_str()[i] == '-')
 | 
			
		||||
        i++;
 | 
			
		||||
      uint8_t msb = data.c_str()[i];
 | 
			
		||||
@@ -88,7 +88,6 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
 | 
			
		||||
      if (lsb > '9')
 | 
			
		||||
        lsb -= 7;
 | 
			
		||||
      ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F);
 | 
			
		||||
      i += 2;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str());
 | 
			
		||||
@@ -155,7 +154,7 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case ESP_UUID_LEN_128:
 | 
			
		||||
        for (int i = 0; i < ESP_UUID_LEN_128; i++) {
 | 
			
		||||
        for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++) {
 | 
			
		||||
          if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) {
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
 
 | 
			
		||||
@@ -432,7 +432,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
 | 
			
		||||
  ESP_LOGVV(TAG, "Parse Result:");
 | 
			
		||||
  const char *address_type = "";
 | 
			
		||||
  const char *address_type;
 | 
			
		||||
  switch (this->address_type_) {
 | 
			
		||||
    case BLE_ADDR_TYPE_PUBLIC:
 | 
			
		||||
      address_type = "PUBLIC";
 | 
			
		||||
@@ -446,6 +446,9 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
 | 
			
		||||
    case BLE_ADDR_TYPE_RPA_RANDOM:
 | 
			
		||||
      address_type = "RPA_RANDOM";
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      address_type = "UNKNOWN";
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "  Address: %02X:%02X:%02X:%02X:%02X:%02X (%s)", this->address_[0], this->address_[1],
 | 
			
		||||
            this->address_[2], this->address_[3], this->address_[4], this->address_[5], address_type);
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/preferences.h"
 | 
			
		||||
 | 
			
		||||
struct httpd_req;
 | 
			
		||||
struct httpd_req;  // NOLINT(readability-identifier-naming)
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_camera_web_server {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import esp32_rmt, light
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CHIPSET,
 | 
			
		||||
    CONF_IS_RGBW,
 | 
			
		||||
@@ -103,7 +103,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                default="0 us",
 | 
			
		||||
            ): cv.positive_time_period_nanoseconds,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,13 @@
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BOARD,
 | 
			
		||||
    CONF_BOARD_FLASH_MODE,
 | 
			
		||||
    CONF_FRAMEWORK,
 | 
			
		||||
    CONF_PLATFORM_VERSION,
 | 
			
		||||
    CONF_SOURCE,
 | 
			
		||||
    CONF_VERSION,
 | 
			
		||||
    KEY_CORE,
 | 
			
		||||
@@ -12,27 +15,22 @@ from esphome.const import (
 | 
			
		||||
    KEY_TARGET_FRAMEWORK,
 | 
			
		||||
    KEY_TARGET_PLATFORM,
 | 
			
		||||
    PLATFORM_ESP8266,
 | 
			
		||||
    CONF_PLATFORM_VERSION,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.helpers import copy_file_if_changed
 | 
			
		||||
 | 
			
		||||
from .boards import BOARDS, ESP8266_LD_SCRIPTS
 | 
			
		||||
from .const import (
 | 
			
		||||
    CONF_RESTORE_FROM_FLASH,
 | 
			
		||||
    CONF_EARLY_PIN_INIT,
 | 
			
		||||
    CONF_RESTORE_FROM_FLASH,
 | 
			
		||||
    KEY_BOARD,
 | 
			
		||||
    KEY_ESP8266,
 | 
			
		||||
    KEY_FLASH_SIZE,
 | 
			
		||||
    KEY_PIN_INITIAL_STATES,
 | 
			
		||||
    esp8266_ns,
 | 
			
		||||
)
 | 
			
		||||
from .boards import BOARDS, ESP8266_LD_SCRIPTS
 | 
			
		||||
 | 
			
		||||
from .gpio import PinInitialState, add_pin_initial_states_array
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
AUTO_LOAD = ["preferences"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
 | 
			
		||||
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
 | 
			
		||||
from esphome.config_helpers import merge_config
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ESPHOME,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
@@ -18,6 +17,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_VERSION,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@@ -124,7 +124,6 @@ FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate
 | 
			
		||||
@coroutine_with_priority(52.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await ota_to_code(var, config)
 | 
			
		||||
    cg.add(var.set_port(config[CONF_PORT]))
 | 
			
		||||
    if CONF_PASSWORD in config:
 | 
			
		||||
        cg.add(var.set_auth_password(config[CONF_PASSWORD]))
 | 
			
		||||
@@ -132,3 +131,4 @@ async def to_code(config):
 | 
			
		||||
    cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
 | 
			
		||||
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await ota_to_code(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -111,11 +111,11 @@ void EZOSensor::loop() {
 | 
			
		||||
  if (buf[0] == 1) {
 | 
			
		||||
    std::string payload = reinterpret_cast<char *>(&buf[1]);
 | 
			
		||||
    if (!payload.empty()) {
 | 
			
		||||
      auto start_location = payload.find(',');
 | 
			
		||||
      switch (to_run->command_type) {
 | 
			
		||||
        case EzoCommandType::EZO_READ: {
 | 
			
		||||
          // some sensors return multiple comma-separated values, terminate string after first one
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
          if (start_location != std::string::npos) {
 | 
			
		||||
            payload.erase(start_location);
 | 
			
		||||
          }
 | 
			
		||||
          auto val = parse_number<float>(payload);
 | 
			
		||||
@@ -126,49 +126,37 @@ void EZOSensor::loop() {
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_LED: {
 | 
			
		||||
        case EzoCommandType::EZO_LED:
 | 
			
		||||
          this->led_callback_.call(payload.back() == '1');
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_DEVICE_INFORMATION: {
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
        case EzoCommandType::EZO_DEVICE_INFORMATION:
 | 
			
		||||
          if (start_location != std::string::npos) {
 | 
			
		||||
            this->device_infomation_callback_.call(payload.substr(start_location + 1));
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_SLOPE: {
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
        case EzoCommandType::EZO_SLOPE:
 | 
			
		||||
          if (start_location != std::string::npos) {
 | 
			
		||||
            this->slope_callback_.call(payload.substr(start_location + 1));
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_CALIBRATION: {
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
        case EzoCommandType::EZO_CALIBRATION:
 | 
			
		||||
          if (start_location != std::string::npos) {
 | 
			
		||||
            this->calibration_callback_.call(payload.substr(start_location + 1));
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_T: {
 | 
			
		||||
          int start_location = 0;
 | 
			
		||||
          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
			
		||||
        case EzoCommandType::EZO_T:
 | 
			
		||||
          if (start_location != std::string::npos) {
 | 
			
		||||
            this->t_callback_.call(payload.substr(start_location + 1));
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case EzoCommandType::EZO_CUSTOM: {
 | 
			
		||||
        case EzoCommandType::EZO_CUSTOM:
 | 
			
		||||
          this->custom_callback_.call(payload);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        default: {
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->commands_.pop_front();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -178,7 +166,7 @@ void EZOSensor::add_command_(const std::string &command, EzoCommandType command_
 | 
			
		||||
  ezo_command->command_type = command_type;
 | 
			
		||||
  ezo_command->delay_ms = delay_ms;
 | 
			
		||||
  this->commands_.push_back(std::move(ezo_command));
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
 | 
			
		||||
  std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
from collections.abc import Iterable
 | 
			
		||||
import functools
 | 
			
		||||
import hashlib
 | 
			
		||||
import logging
 | 
			
		||||
@@ -8,7 +7,6 @@ import re
 | 
			
		||||
 | 
			
		||||
import freetype
 | 
			
		||||
import glyphsets
 | 
			
		||||
from packaging import version
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
from esphome import core, external_files
 | 
			
		||||
@@ -53,8 +51,11 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs"
 | 
			
		||||
# Cache loaded freetype fonts
 | 
			
		||||
class FontCache(dict):
 | 
			
		||||
    def __missing__(self, key):
 | 
			
		||||
        res = self[key] = freetype.Face(key)
 | 
			
		||||
        return res
 | 
			
		||||
        try:
 | 
			
		||||
            res = self[key] = freetype.Face(key)
 | 
			
		||||
            return res
 | 
			
		||||
        except freetype.FT_Exception as e:
 | 
			
		||||
            raise cv.Invalid(f"Could not load Font file {key}: {e}") from e
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FONT_CACHE = FontCache()
 | 
			
		||||
@@ -88,7 +89,7 @@ def flatten(lists) -> list:
 | 
			
		||||
    return list(chain.from_iterable(lists))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_missing_glyphs(file, codepoints: Iterable, warning: bool = False):
 | 
			
		||||
def check_missing_glyphs(file, codepoints, warning: bool = False):
 | 
			
		||||
    """
 | 
			
		||||
    Check that the given font file actually contains the requested glyphs
 | 
			
		||||
    :param file: A Truetype font file
 | 
			
		||||
@@ -177,24 +178,6 @@ def validate_glyphs(config):
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_pillow_installed(value):
 | 
			
		||||
    try:
 | 
			
		||||
        import PIL
 | 
			
		||||
    except ImportError as err:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Please install the pillow python package to use this feature. "
 | 
			
		||||
            '(pip install "pillow==10.4.0")'
 | 
			
		||||
        ) from err
 | 
			
		||||
 | 
			
		||||
    if version.parse(PIL.__version__) != version.parse("10.4.0"):
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Please update your pillow installation to 10.4.0. "
 | 
			
		||||
            '(pip install "pillow==10.4.0")'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FONT_EXTENSIONS = (".ttf", ".woff", ".otf")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -393,7 +376,9 @@ def font_file_schema(value):
 | 
			
		||||
# Default if no glyphs or glyphsets are provided
 | 
			
		||||
DEFAULT_GLYPHSET = "GF_Latin_Kernel"
 | 
			
		||||
# default for bitmap fonts
 | 
			
		||||
DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz<C2><B0>'
 | 
			
		||||
DEFAULT_GLYPHS = (
 | 
			
		||||
    ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_RAW_GLYPH_ID = "raw_glyph_id"
 | 
			
		||||
 | 
			
		||||
@@ -421,7 +406,7 @@ FONT_SCHEMA = cv.Schema(
 | 
			
		||||
    },
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, validate_glyphs)
 | 
			
		||||
CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_glyphs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# PIL doesn't provide a consistent interface for both TrueType and bitmap
 | 
			
		||||
 
 | 
			
		||||
@@ -133,9 +133,11 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
 | 
			
		||||
    auto diff_r = (float) color.r - (float) background.r;
 | 
			
		||||
    auto diff_g = (float) color.g - (float) background.g;
 | 
			
		||||
    auto diff_b = (float) color.b - (float) background.b;
 | 
			
		||||
    auto diff_w = (float) color.w - (float) background.w;
 | 
			
		||||
    auto b_r = (float) background.r;
 | 
			
		||||
    auto b_g = (float) background.g;
 | 
			
		||||
    auto b_b = (float) background.g;
 | 
			
		||||
    auto b_b = (float) background.b;
 | 
			
		||||
    auto b_w = (float) background.w;
 | 
			
		||||
    for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) {
 | 
			
		||||
      for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
 | 
			
		||||
        uint8_t pixel = 0;
 | 
			
		||||
@@ -153,8 +155,8 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
 | 
			
		||||
          display->draw_pixel_at(glyph_x, glyph_y, color);
 | 
			
		||||
        } else if (pixel != 0) {
 | 
			
		||||
          auto on = (float) pixel / (float) bpp_max;
 | 
			
		||||
          auto blended =
 | 
			
		||||
              Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), (uint8_t) (diff_b * on + b_b));
 | 
			
		||||
          auto blended = Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g),
 | 
			
		||||
                               (uint8_t) (diff_b * on + b_b), (uint8_t) (diff_w * on + b_w));
 | 
			
		||||
          display->draw_pixel_at(glyph_x, glyph_y, blended);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,8 @@ CODEOWNERS = ["@MrMDavidson"]
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["display_menu_base"]
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,9 @@ void HonClimate::set_beeper_state(bool state) {
 | 
			
		||||
  if (state != this->settings_.beeper_state) {
 | 
			
		||||
    this->settings_.beeper_state = state;
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
    this->beeper_switch_->publish_state(state);
 | 
			
		||||
    if (this->beeper_switch_ != nullptr) {
 | 
			
		||||
      this->beeper_switch_->publish_state(state);
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
    this->hon_rtc_.save(&this->settings_);
 | 
			
		||||
  }
 | 
			
		||||
@@ -45,10 +47,17 @@ bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state;
 | 
			
		||||
 | 
			
		||||
void HonClimate::set_quiet_mode_state(bool state) {
 | 
			
		||||
  if (state != this->get_quiet_mode_state()) {
 | 
			
		||||
    this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
 | 
			
		||||
    if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
 | 
			
		||||
      this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
 | 
			
		||||
      this->force_send_control_ = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->quiet_mode_state_ = state ? SwitchState::ON : SwitchState::OFF;
 | 
			
		||||
    }
 | 
			
		||||
    this->settings_.quiet_mode_state = state;
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
    this->quiet_mode_switch_->publish_state(state);
 | 
			
		||||
    if (this->quiet_mode_switch_ != nullptr) {
 | 
			
		||||
      this->quiet_mode_switch_->publish_state(state);
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
    this->hon_rtc_.save(&this->settings_);
 | 
			
		||||
  }
 | 
			
		||||
@@ -509,7 +518,7 @@ void HonClimate::initialization() {
 | 
			
		||||
  }
 | 
			
		||||
  this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
 | 
			
		||||
  this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
 | 
			
		||||
  this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
 | 
			
		||||
  this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::ON : SwitchState::OFF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
haier_protocol::HaierMessage HonClimate::get_control_message() {
 | 
			
		||||
@@ -932,7 +941,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
 | 
			
		||||
      if (this->mode == CLIMATE_MODE_OFF) {
 | 
			
		||||
        // AC just turned on from remote need to turn off display
 | 
			
		||||
        this->force_send_control_ = true;
 | 
			
		||||
      } else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
 | 
			
		||||
      } else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
 | 
			
		||||
        this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -1004,6 +1013,11 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
 | 
			
		||||
      if (new_quiet_mode != this->get_quiet_mode_state()) {
 | 
			
		||||
        this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
 | 
			
		||||
        this->settings_.quiet_mode_state = new_quiet_mode;
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
        if (this->quiet_mode_switch_ != nullptr) {
 | 
			
		||||
          this->quiet_mode_switch_->publish_state(new_quiet_mode);
 | 
			
		||||
        }
 | 
			
		||||
#endif  // USE_SWITCH
 | 
			
		||||
        this->hon_rtc_.save(&this->settings_);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -1069,19 +1083,17 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
  climate_control = this->current_hvac_settings_;
 | 
			
		||||
  // Beeper command
 | 
			
		||||
  {
 | 
			
		||||
    this->control_messages_queue_.push(
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS,
 | 
			
		||||
                                     this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2));
 | 
			
		||||
    this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                              (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS,
 | 
			
		||||
                                          this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2);
 | 
			
		||||
  }
 | 
			
		||||
  // Health mode
 | 
			
		||||
  {
 | 
			
		||||
    this->control_messages_queue_.push(
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::HEALTH_MODE,
 | 
			
		||||
                                     this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2));
 | 
			
		||||
    this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                              (uint8_t) hon_protocol::DataParameters::HEALTH_MODE,
 | 
			
		||||
                                          this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2);
 | 
			
		||||
    this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
 | 
			
		||||
  }
 | 
			
		||||
  // Climate mode
 | 
			
		||||
@@ -1099,51 +1111,46 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
      case CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
        new_power = true;
 | 
			
		||||
        buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO;
 | 
			
		||||
        this->control_messages_queue_.push(
 | 
			
		||||
            haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                         (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                             (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                         buffer, 2));
 | 
			
		||||
        this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                              (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                                  (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                              buffer, 2);
 | 
			
		||||
        fan_mode_buf[1] = this->other_modes_fan_speed_;
 | 
			
		||||
        break;
 | 
			
		||||
      case CLIMATE_MODE_HEAT:
 | 
			
		||||
        new_power = true;
 | 
			
		||||
        buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT;
 | 
			
		||||
        this->control_messages_queue_.push(
 | 
			
		||||
            haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                         (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                             (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                         buffer, 2));
 | 
			
		||||
        this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                              (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                                  (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                              buffer, 2);
 | 
			
		||||
        fan_mode_buf[1] = this->other_modes_fan_speed_;
 | 
			
		||||
        break;
 | 
			
		||||
      case CLIMATE_MODE_DRY:
 | 
			
		||||
        new_power = true;
 | 
			
		||||
        buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY;
 | 
			
		||||
        this->control_messages_queue_.push(
 | 
			
		||||
            haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                         (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                             (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                         buffer, 2));
 | 
			
		||||
        this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                              (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                                  (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                              buffer, 2);
 | 
			
		||||
        fan_mode_buf[1] = this->other_modes_fan_speed_;
 | 
			
		||||
        break;
 | 
			
		||||
      case CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
        new_power = true;
 | 
			
		||||
        buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN;
 | 
			
		||||
        this->control_messages_queue_.push(
 | 
			
		||||
            haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                         (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                             (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                         buffer, 2));
 | 
			
		||||
        this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                              (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                                  (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                              buffer, 2);
 | 
			
		||||
        fan_mode_buf[1] = this->other_modes_fan_speed_;  // Auto doesn't work in fan only mode
 | 
			
		||||
        break;
 | 
			
		||||
      case CLIMATE_MODE_COOL:
 | 
			
		||||
        new_power = true;
 | 
			
		||||
        buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL;
 | 
			
		||||
        this->control_messages_queue_.push(
 | 
			
		||||
            haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                         (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                             (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                         buffer, 2));
 | 
			
		||||
        this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                              (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                                  (uint8_t) hon_protocol::DataParameters::AC_MODE,
 | 
			
		||||
                                              buffer, 2);
 | 
			
		||||
        fan_mode_buf[1] = this->other_modes_fan_speed_;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
@@ -1153,11 +1160,10 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
  }
 | 
			
		||||
  // Climate power
 | 
			
		||||
  {
 | 
			
		||||
    this->control_messages_queue_.push(
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::AC_POWER,
 | 
			
		||||
                                     new_power ? ONE_BUF : ZERO_BUF, 2));
 | 
			
		||||
    this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                              (uint8_t) hon_protocol::DataParameters::AC_POWER,
 | 
			
		||||
                                          new_power ? ONE_BUF : ZERO_BUF, 2);
 | 
			
		||||
  }
 | 
			
		||||
  // CLimate preset
 | 
			
		||||
  {
 | 
			
		||||
@@ -1199,36 +1205,32 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
    }
 | 
			
		||||
    auto presets = this->traits_.get_supported_presets();
 | 
			
		||||
    if (quiet_mode_buf[1] != 0xFF) {
 | 
			
		||||
      this->control_messages_queue_.push(
 | 
			
		||||
          haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                       (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                           (uint8_t) hon_protocol::DataParameters::QUIET_MODE,
 | 
			
		||||
                                       quiet_mode_buf, 2));
 | 
			
		||||
      this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                            (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                                (uint8_t) hon_protocol::DataParameters::QUIET_MODE,
 | 
			
		||||
                                            quiet_mode_buf, 2);
 | 
			
		||||
    }
 | 
			
		||||
    if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) {
 | 
			
		||||
      this->control_messages_queue_.push(
 | 
			
		||||
          haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                       (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                           (uint8_t) hon_protocol::DataParameters::FAST_MODE,
 | 
			
		||||
                                       fast_mode_buf, 2));
 | 
			
		||||
      this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                            (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                                (uint8_t) hon_protocol::DataParameters::FAST_MODE,
 | 
			
		||||
                                            fast_mode_buf, 2);
 | 
			
		||||
    }
 | 
			
		||||
    if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) {
 | 
			
		||||
      this->control_messages_queue_.push(
 | 
			
		||||
          haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                       (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                           (uint8_t) hon_protocol::DataParameters::TEN_DEGREE,
 | 
			
		||||
                                       away_mode_buf, 2));
 | 
			
		||||
      this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                            (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                                (uint8_t) hon_protocol::DataParameters::TEN_DEGREE,
 | 
			
		||||
                                            away_mode_buf, 2);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // Target temperature
 | 
			
		||||
  if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
 | 
			
		||||
    uint8_t buffer[2] = {0x00, 0x00};
 | 
			
		||||
    buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
 | 
			
		||||
    this->control_messages_queue_.push(
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::SET_POINT,
 | 
			
		||||
                                     buffer, 2));
 | 
			
		||||
    this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                              (uint8_t) hon_protocol::DataParameters::SET_POINT,
 | 
			
		||||
                                          buffer, 2);
 | 
			
		||||
  }
 | 
			
		||||
  // Vertical swing mode
 | 
			
		||||
  if (climate_control.swing_mode.has_value()) {
 | 
			
		||||
@@ -1248,16 +1250,14 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
      case CLIMATE_SWING_BOTH:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    this->control_messages_queue_.push(
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE,
 | 
			
		||||
                                     horizontal_swing_buf, 2));
 | 
			
		||||
    this->control_messages_queue_.push(
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE,
 | 
			
		||||
                                     vertical_swing_buf, 2));
 | 
			
		||||
    this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                              (uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE,
 | 
			
		||||
                                          horizontal_swing_buf, 2);
 | 
			
		||||
    this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                              (uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE,
 | 
			
		||||
                                          vertical_swing_buf, 2);
 | 
			
		||||
  }
 | 
			
		||||
  // Fan mode
 | 
			
		||||
  if (climate_control.fan_mode.has_value()) {
 | 
			
		||||
@@ -1280,11 +1280,10 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    if (fan_mode_buf[1] != 0xFF) {
 | 
			
		||||
      this->control_messages_queue_.push(
 | 
			
		||||
          haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                       (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                           (uint8_t) hon_protocol::DataParameters::FAN_MODE,
 | 
			
		||||
                                       fan_mode_buf, 2));
 | 
			
		||||
      this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                            (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                                (uint8_t) hon_protocol::DataParameters::FAN_MODE,
 | 
			
		||||
                                            fan_mode_buf, 2);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								esphome/components/hbridge/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/hbridge/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import switch
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_OPTIMISTIC, CONF_PULSE_LENGTH, CONF_WAIT_TIME
 | 
			
		||||
 | 
			
		||||
from .. import hbridge_ns
 | 
			
		||||
 | 
			
		||||
HBridgeSwitch = hbridge_ns.class_("HBridgeSwitch", switch.Switch, cg.Component)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@dwmw2"]
 | 
			
		||||
 | 
			
		||||
CONF_OFF_PIN = "off_pin"
 | 
			
		||||
CONF_ON_PIN = "on_pin"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    switch.switch_schema(HBridgeSwitch)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ON_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Required(CONF_OFF_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_PULSE_LENGTH, default="100ms"
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_WAIT_TIME): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await switch.new_switch(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    on_pin = await cg.gpio_pin_expression(config[CONF_ON_PIN])
 | 
			
		||||
    cg.add(var.set_on_pin(on_pin))
 | 
			
		||||
    off_pin = await cg.gpio_pin_expression(config[CONF_OFF_PIN])
 | 
			
		||||
    cg.add(var.set_off_pin(off_pin))
 | 
			
		||||
    cg.add(var.set_pulse_length(config[CONF_PULSE_LENGTH]))
 | 
			
		||||
    cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
 | 
			
		||||
    if wait_time := config.get(CONF_WAIT_TIME):
 | 
			
		||||
        cg.add(var.set_wait_time(wait_time))
 | 
			
		||||
							
								
								
									
										95
									
								
								esphome/components/hbridge/switch/hbridge_switch.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								esphome/components/hbridge/switch/hbridge_switch.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
#include "hbridge_switch.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hbridge {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "switch.hbridge";
 | 
			
		||||
 | 
			
		||||
float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
void HBridgeSwitch::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str());
 | 
			
		||||
 | 
			
		||||
  optional<bool> initial_state = this->get_initial_state_with_restore_mode().value_or(false);
 | 
			
		||||
 | 
			
		||||
  // Like GPIOSwitch does, set the pin state both before and after pin setup()
 | 
			
		||||
  this->on_pin_->digital_write(false);
 | 
			
		||||
  this->on_pin_->setup();
 | 
			
		||||
  this->on_pin_->digital_write(false);
 | 
			
		||||
 | 
			
		||||
  this->off_pin_->digital_write(false);
 | 
			
		||||
  this->off_pin_->setup();
 | 
			
		||||
  this->off_pin_->digital_write(false);
 | 
			
		||||
 | 
			
		||||
  if (initial_state.has_value())
 | 
			
		||||
    this->write_state(initial_state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HBridgeSwitch::dump_config() {
 | 
			
		||||
  LOG_SWITCH("", "H-Bridge Switch", this);
 | 
			
		||||
  LOG_PIN("  On Pin: ", this->on_pin_);
 | 
			
		||||
  LOG_PIN("  Off Pin: ", this->off_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Pulse length: %" PRId32 " ms", this->pulse_length_);
 | 
			
		||||
  if (this->wait_time_)
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Wait time %" PRId32 " ms", this->wait_time_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HBridgeSwitch::write_state(bool state) {
 | 
			
		||||
  this->desired_state_ = state;
 | 
			
		||||
  if (!this->timer_running_)
 | 
			
		||||
    this->timer_fn_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HBridgeSwitch::timer_fn_() {
 | 
			
		||||
  uint32_t next_timeout = 0;
 | 
			
		||||
 | 
			
		||||
  while ((uint8_t) this->desired_state_ != this->relay_state_) {
 | 
			
		||||
    switch (this->relay_state_) {
 | 
			
		||||
      case RELAY_STATE_ON:
 | 
			
		||||
      case RELAY_STATE_OFF:
 | 
			
		||||
      case RELAY_STATE_UNKNOWN:
 | 
			
		||||
        if (this->desired_state_) {
 | 
			
		||||
          this->on_pin_->digital_write(true);
 | 
			
		||||
          this->relay_state_ = RELAY_STATE_SWITCHING_ON;
 | 
			
		||||
        } else {
 | 
			
		||||
          this->off_pin_->digital_write(true);
 | 
			
		||||
          this->relay_state_ = RELAY_STATE_SWITCHING_OFF;
 | 
			
		||||
        }
 | 
			
		||||
        next_timeout = this->pulse_length_;
 | 
			
		||||
        if (!this->optimistic_)
 | 
			
		||||
          this->publish_state(this->desired_state_);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case RELAY_STATE_SWITCHING_ON:
 | 
			
		||||
        this->on_pin_->digital_write(false);
 | 
			
		||||
        this->relay_state_ = RELAY_STATE_ON;
 | 
			
		||||
        if (this->optimistic_)
 | 
			
		||||
          this->publish_state(true);
 | 
			
		||||
        next_timeout = this->wait_time_;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case RELAY_STATE_SWITCHING_OFF:
 | 
			
		||||
        this->off_pin_->digital_write(false);
 | 
			
		||||
        this->relay_state_ = RELAY_STATE_OFF;
 | 
			
		||||
        if (this->optimistic_)
 | 
			
		||||
          this->publish_state(false);
 | 
			
		||||
        next_timeout = this->wait_time_;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (next_timeout) {
 | 
			
		||||
      this->timer_running_ = true;
 | 
			
		||||
      this->set_timeout(next_timeout, [this]() { this->timer_fn_(); });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // In the case where ON/OFF state has been reached but we need to
 | 
			
		||||
    // immediately change back again to reach desired_state_, we loop.
 | 
			
		||||
  }
 | 
			
		||||
  this->timer_running_ = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace hbridge
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										50
									
								
								esphome/components/hbridge/switch/hbridge_switch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								esphome/components/hbridge/switch/hbridge_switch.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/switch/switch.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hbridge {
 | 
			
		||||
 | 
			
		||||
enum RelayState : uint8_t {
 | 
			
		||||
  RELAY_STATE_OFF = 0,
 | 
			
		||||
  RELAY_STATE_ON = 1,
 | 
			
		||||
  RELAY_STATE_SWITCHING_ON = 2,
 | 
			
		||||
  RELAY_STATE_SWITCHING_OFF = 3,
 | 
			
		||||
  RELAY_STATE_UNKNOWN = 4,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HBridgeSwitch : public switch_::Switch, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_on_pin(GPIOPin *pin) { this->on_pin_ = pin; }
 | 
			
		||||
  void set_off_pin(GPIOPin *pin) { this->off_pin_ = pin; }
 | 
			
		||||
  void set_pulse_length(uint32_t pulse_length) { this->pulse_length_ = pulse_length; }
 | 
			
		||||
  void set_wait_time(uint32_t wait_time) { this->wait_time_ = wait_time; }
 | 
			
		||||
  void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
 | 
			
		||||
 | 
			
		||||
  // ========== INTERNAL METHODS ==========
 | 
			
		||||
  // (In most use cases you won't need these)
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void write_state(bool state) override;
 | 
			
		||||
  void timer_fn_();
 | 
			
		||||
 | 
			
		||||
  bool timer_running_{false};
 | 
			
		||||
  bool desired_state_{false};
 | 
			
		||||
  RelayState relay_state_{RELAY_STATE_UNKNOWN};
 | 
			
		||||
  GPIOPin *on_pin_{nullptr};
 | 
			
		||||
  GPIOPin *off_pin_{nullptr};
 | 
			
		||||
  uint32_t pulse_length_{0};
 | 
			
		||||
  uint32_t wait_time_{0};
 | 
			
		||||
  bool optimistic_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace hbridge
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -133,8 +133,10 @@ bool HitachiClimate::get_swing_v_() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_swing_h_(uint8_t position) {
 | 
			
		||||
  if (position > HITACHI_AC344_SWINGH_LEFT_MAX)
 | 
			
		||||
    return set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
 | 
			
		||||
  if (position > HITACHI_AC344_SWINGH_LEFT_MAX) {
 | 
			
		||||
    set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  set_bits(&remote_state_[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE, position);
 | 
			
		||||
  set_button_(HITACHI_AC344_BUTTON_SWINGH);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -133,8 +133,10 @@ bool HitachiClimate::get_swing_v_() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_swing_h_(uint8_t position) {
 | 
			
		||||
  if (position > HITACHI_AC424_SWINGH_LEFT_MAX)
 | 
			
		||||
    return set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE);
 | 
			
		||||
  if (position > HITACHI_AC424_SWINGH_LEFT_MAX) {
 | 
			
		||||
    set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  set_bits(&remote_state_[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, HITACHI_AC424_SWINGH_SIZE, position);
 | 
			
		||||
  set_button_(HITACHI_AC424_BUTTON_SWINGH);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ void HomeassistantNumber::min_retrieved_(const std::string &min) {
 | 
			
		||||
  auto min_value = parse_number<float>(min);
 | 
			
		||||
  if (!min_value.has_value()) {
 | 
			
		||||
    ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_.c_str(), min.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str());
 | 
			
		||||
  this->traits.set_min_value(min_value.value());
 | 
			
		||||
@@ -36,6 +37,7 @@ void HomeassistantNumber::max_retrieved_(const std::string &max) {
 | 
			
		||||
  auto max_value = parse_number<float>(max);
 | 
			
		||||
  if (!max_value.has_value()) {
 | 
			
		||||
    ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_.c_str(), max.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str());
 | 
			
		||||
  this->traits.set_max_value(max_value.value());
 | 
			
		||||
@@ -45,6 +47,7 @@ void HomeassistantNumber::step_retrieved_(const std::string &step) {
 | 
			
		||||
  auto step_value = parse_number<float>(step);
 | 
			
		||||
  if (!step_value.has_value()) {
 | 
			
		||||
    ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_.c_str(), step.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str());
 | 
			
		||||
  this->traits.set_step(step_value.value());
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ static const char *const TAG = "honeywellabp2";
 | 
			
		||||
void HONEYWELLABP2Sensor::read_sensor_data() {
 | 
			
		||||
  if (this->read(raw_data_, 7) != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with ABP2 failed!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->status_set_warning("couldn't read sensor data");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  float press_counts = encode_uint24(raw_data_[1], raw_data_[2], raw_data_[3]);  // calculate digital pressure counts
 | 
			
		||||
@@ -25,12 +25,13 @@ void HONEYWELLABP2Sensor::read_sensor_data() {
 | 
			
		||||
                          (this->max_pressure_ - this->min_pressure_)) +
 | 
			
		||||
                         this->min_pressure_;
 | 
			
		||||
  this->last_temperature_ = (temp_counts * 200 / 16777215) - 50;
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HONEYWELLABP2Sensor::start_measurement() {
 | 
			
		||||
  if (this->write(i2c_cmd_, 3) != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with ABP2 failed!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->status_set_warning("couldn't start measurement");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->measurement_running_ = true;
 | 
			
		||||
@@ -39,7 +40,7 @@ void HONEYWELLABP2Sensor::start_measurement() {
 | 
			
		||||
bool HONEYWELLABP2Sensor::is_measurement_ready() {
 | 
			
		||||
  if (this->read(raw_data_, 1) != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with ABP2 failed!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->status_set_warning("couldn't check measurement");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if ((raw_data_[0] & (0x1 << STATUS_BIT_BUSY)) > 0) {
 | 
			
		||||
@@ -52,7 +53,7 @@ bool HONEYWELLABP2Sensor::is_measurement_ready() {
 | 
			
		||||
void HONEYWELLABP2Sensor::measurement_timeout() {
 | 
			
		||||
  ESP_LOGE(TAG, "Timeout!");
 | 
			
		||||
  this->measurement_running_ = false;
 | 
			
		||||
  this->mark_failed();
 | 
			
		||||
  this->status_set_warning("measurement timed out");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float HONEYWELLABP2Sensor::get_pressure() { return this->last_pressure_; }
 | 
			
		||||
@@ -79,7 +80,7 @@ void HONEYWELLABP2Sensor::update() {
 | 
			
		||||
  ESP_LOGV(TAG, "Update Honeywell ABP2 Sensor");
 | 
			
		||||
 | 
			
		||||
  this->start_measurement();
 | 
			
		||||
  this->set_timeout("meas_timeout", 50, [this] { this->measurement_timeout(); });
 | 
			
		||||
  this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout(); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HONEYWELLABP2Sensor::dump_config() {
 | 
			
		||||
 
 | 
			
		||||
@@ -189,7 +189,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
 | 
			
		||||
    if (container == nullptr) {
 | 
			
		||||
      for (auto *trigger : this->error_triggers_)
 | 
			
		||||
        trigger->trigger(x...);
 | 
			
		||||
        trigger->trigger();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@ bool HX711Sensor::read_sensor_(uint32_t *result) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Cycle clock pin for gain setting
 | 
			
		||||
    for (uint8_t i = 0; i < this->gain_; i++) {
 | 
			
		||||
    for (uint8_t i = 0; i < static_cast<uint8_t>(this->gain_); i++) {
 | 
			
		||||
      this->sck_pin_->digital_write(true);
 | 
			
		||||
      delayMicroseconds(1);
 | 
			
		||||
      this->sck_pin_->digital_write(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hx711 {
 | 
			
		||||
 | 
			
		||||
enum HX711Gain {
 | 
			
		||||
enum HX711Gain : uint8_t {
 | 
			
		||||
  HX711_GAIN_128 = 1,
 | 
			
		||||
  HX711_GAIN_32 = 2,
 | 
			
		||||
  HX711_GAIN_64 = 3,
 | 
			
		||||
 
 | 
			
		||||
@@ -17,14 +17,14 @@ void IDFI2CBus::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up I2C bus...");
 | 
			
		||||
  static i2c_port_t next_port = I2C_NUM_0;
 | 
			
		||||
  port_ = next_port;
 | 
			
		||||
#if I2C_NUM_MAX > 1
 | 
			
		||||
#if SOC_I2C_NUM > 1
 | 
			
		||||
  next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX;
 | 
			
		||||
#else
 | 
			
		||||
  next_port = I2C_NUM_MAX;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (port_ == I2C_NUM_MAX) {
 | 
			
		||||
    ESP_LOGE(TAG, "Too many I2C buses configured");
 | 
			
		||||
    ESP_LOGE(TAG, "Too many I2C buses configured. Max %u supported.", SOC_I2C_NUM);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t {
 | 
			
		||||
  STATE_RUNNING = (1 << 11),
 | 
			
		||||
  STATE_STOPPING = (1 << 12),
 | 
			
		||||
  STATE_STOPPED = (1 << 13),
 | 
			
		||||
  ERR_INVALID_FORMAT = (1 << 14),
 | 
			
		||||
  ERR_TASK_FAILED_TO_START = (1 << 15),
 | 
			
		||||
  ERR_ESP_INVALID_STATE = (1 << 16),
 | 
			
		||||
  ERR_TASK_FAILED_TO_START = (1 << 14),
 | 
			
		||||
  ERR_ESP_INVALID_STATE = (1 << 15),
 | 
			
		||||
  ERR_ESP_NOT_SUPPORTED = (1 << 16),
 | 
			
		||||
  ERR_ESP_INVALID_ARG = (1 << 17),
 | 
			
		||||
  ERR_ESP_INVALID_SIZE = (1 << 18),
 | 
			
		||||
  ERR_ESP_NO_MEM = (1 << 19),
 | 
			
		||||
  ERR_ESP_FAIL = (1 << 20),
 | 
			
		||||
  ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | ERR_ESP_NO_MEM | ERR_ESP_FAIL,
 | 
			
		||||
  ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_NOT_SUPPORTED | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE |
 | 
			
		||||
                     ERR_ESP_NO_MEM | ERR_ESP_FAIL,
 | 
			
		||||
  ALL_BITS = 0x00FFFFFF,  // All valid FreeRTOS event group bits
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -55,6 +56,8 @@ static esp_err_t err_bit_to_esp_err(uint32_t bit) {
 | 
			
		||||
      return ESP_ERR_INVALID_SIZE;
 | 
			
		||||
    case SpeakerEventGroupBits::ERR_ESP_NO_MEM:
 | 
			
		||||
      return ESP_ERR_NO_MEM;
 | 
			
		||||
    case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED:
 | 
			
		||||
      return ESP_ERR_NOT_SUPPORTED;
 | 
			
		||||
    default:
 | 
			
		||||
      return ESP_FAIL;
 | 
			
		||||
  }
 | 
			
		||||
@@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() {
 | 
			
		||||
    xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (event_group_bits & SpeakerEventGroupBits::ERR_INVALID_FORMAT) {
 | 
			
		||||
  if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) {
 | 
			
		||||
    uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS;
 | 
			
		||||
    ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) {
 | 
			
		||||
    this->status_set_error("Failed to adjust I2S bus to match the incoming audio");
 | 
			
		||||
    ESP_LOGE(TAG,
 | 
			
		||||
             "Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8,
 | 
			
		||||
             this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels,
 | 
			
		||||
             this->audio_stream_info_.bits_per_sample);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) {
 | 
			
		||||
    uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS;
 | 
			
		||||
    ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioSpeaker::set_volume(float volume) {
 | 
			
		||||
@@ -236,13 +239,15 @@ void I2SAudioSpeaker::speaker_task(void *params) {
 | 
			
		||||
  xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING);
 | 
			
		||||
 | 
			
		||||
  audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_;
 | 
			
		||||
  const ssize_t bytes_per_sample = audio_stream_info.get_bytes_per_sample();
 | 
			
		||||
  const uint8_t number_of_channels = audio_stream_info.channels;
 | 
			
		||||
 | 
			
		||||
  const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * this_speaker->sample_rate_ / 1000 *
 | 
			
		||||
                                  bytes_per_sample * number_of_channels;
 | 
			
		||||
  const uint32_t bytes_per_ms =
 | 
			
		||||
      audio_stream_info.channels * audio_stream_info.get_bytes_per_sample() * audio_stream_info.sample_rate / 1000;
 | 
			
		||||
 | 
			
		||||
  const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms;
 | 
			
		||||
 | 
			
		||||
  // Ensure ring buffer is at least as large as the total size of the DMA buffers
 | 
			
		||||
  const size_t ring_buffer_size =
 | 
			
		||||
      this_speaker->buffer_duration_ms_ * this_speaker->sample_rate_ / 1000 * bytes_per_sample * number_of_channels;
 | 
			
		||||
      std::max((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms);
 | 
			
		||||
 | 
			
		||||
  if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) {
 | 
			
		||||
    // Failed to allocate buffers
 | 
			
		||||
@@ -250,14 +255,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
 | 
			
		||||
    this_speaker->delete_task_(dma_buffers_size);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) {
 | 
			
		||||
    // Failed to start I2S driver
 | 
			
		||||
    this_speaker->delete_task_(dma_buffers_size);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!this_speaker->send_esp_err_to_event_group_(this_speaker->reconfigure_i2s_stream_info_(audio_stream_info))) {
 | 
			
		||||
    // Successfully set the I2S stream info, ready to write audio data to the I2S port
 | 
			
		||||
 | 
			
		||||
  if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) {
 | 
			
		||||
    xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
 | 
			
		||||
 | 
			
		||||
    bool stop_gracefully = false;
 | 
			
		||||
@@ -275,6 +273,12 @@ void I2SAudioSpeaker::speaker_task(void *params) {
 | 
			
		||||
        stop_gracefully = true;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this_speaker->audio_stream_info_ != audio_stream_info) {
 | 
			
		||||
        // Audio stream info has changed, stop the speaker task so it will restart with the proper settings.
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      i2s_event_t i2s_event;
 | 
			
		||||
      while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) {
 | 
			
		||||
        if (i2s_event.type == I2S_EVENT_TX_Q_OVF) {
 | 
			
		||||
@@ -316,17 +320,14 @@ void I2SAudioSpeaker::speaker_task(void *params) {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // Couldn't configure the I2S port to be compatible with the incoming audio
 | 
			
		||||
    xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_INVALID_FORMAT);
 | 
			
		||||
 | 
			
		||||
    xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
 | 
			
		||||
 | 
			
		||||
    i2s_driver_uninstall(this_speaker->parent_->get_port());
 | 
			
		||||
 | 
			
		||||
    this_speaker->parent_->unlock();
 | 
			
		||||
  }
 | 
			
		||||
  i2s_zero_dma_buffer(this_speaker->parent_->get_port());
 | 
			
		||||
 | 
			
		||||
  xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
 | 
			
		||||
 | 
			
		||||
  i2s_driver_uninstall(this_speaker->parent_->get_port());
 | 
			
		||||
 | 
			
		||||
  this_speaker->parent_->unlock();
 | 
			
		||||
  this_speaker->delete_task_(dma_buffers_size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -382,6 +383,9 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) {
 | 
			
		||||
    case ESP_ERR_NO_MEM:
 | 
			
		||||
      xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
 | 
			
		||||
      return true;
 | 
			
		||||
    case ESP_ERR_NOT_SUPPORTED:
 | 
			
		||||
      xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED);
 | 
			
		||||
      return true;
 | 
			
		||||
    default:
 | 
			
		||||
      xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL);
 | 
			
		||||
      return true;
 | 
			
		||||
@@ -411,18 +415,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin
 | 
			
		||||
  return ESP_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t I2SAudioSpeaker::start_i2s_driver_() {
 | 
			
		||||
esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info) {
 | 
			
		||||
  if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.sample_rate)) {  // NOLINT
 | 
			
		||||
    //  Can't reconfigure I2S bus, so the sample rate must match the configured value
 | 
			
		||||
    return ESP_ERR_NOT_SUPPORTED;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) {
 | 
			
		||||
    // Currently can't handle the case when the incoming audio has more bits per sample than the configured value
 | 
			
		||||
    return ESP_ERR_NOT_SUPPORTED;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!this->parent_->try_lock()) {
 | 
			
		||||
    return ESP_ERR_INVALID_STATE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  i2s_channel_fmt_t channel = this->channel_;
 | 
			
		||||
 | 
			
		||||
  if (audio_stream_info.channels == 1) {
 | 
			
		||||
    if (this->channel_ == I2S_CHANNEL_FMT_ONLY_LEFT) {
 | 
			
		||||
      channel = I2S_CHANNEL_FMT_ONLY_LEFT;
 | 
			
		||||
    } else {
 | 
			
		||||
      channel = I2S_CHANNEL_FMT_ONLY_RIGHT;
 | 
			
		||||
    }
 | 
			
		||||
  } else if (audio_stream_info.channels == 2) {
 | 
			
		||||
    channel = I2S_CHANNEL_FMT_RIGHT_LEFT;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000;
 | 
			
		||||
 | 
			
		||||
  i2s_driver_config_t config = {
 | 
			
		||||
    .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX),
 | 
			
		||||
    .sample_rate = this->sample_rate_,
 | 
			
		||||
    .sample_rate = audio_stream_info.sample_rate,
 | 
			
		||||
    .bits_per_sample = this->bits_per_sample_,
 | 
			
		||||
    .channel_format = this->channel_,
 | 
			
		||||
    .channel_format = channel,
 | 
			
		||||
    .communication_format = this->i2s_comm_fmt_,
 | 
			
		||||
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
 | 
			
		||||
    .dma_buf_count = DMA_BUFFERS_COUNT,
 | 
			
		||||
@@ -477,30 +503,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() {
 | 
			
		||||
  return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t I2SAudioSpeaker::reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info) {
 | 
			
		||||
  if (this->i2s_mode_ & I2S_MODE_MASTER) {
 | 
			
		||||
    // ESP controls for the the I2S bus, so adjust the sample rate and bits per sample to match the incoming audio
 | 
			
		||||
    this->sample_rate_ = audio_stream_info.sample_rate;
 | 
			
		||||
    this->bits_per_sample_ = (i2s_bits_per_sample_t) audio_stream_info.bits_per_sample;
 | 
			
		||||
  } else if (this->sample_rate_ != audio_stream_info.sample_rate) {
 | 
			
		||||
    // Can't reconfigure I2S bus, so the sample rate must match the configured value
 | 
			
		||||
    return ESP_ERR_INVALID_ARG;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) {
 | 
			
		||||
    // Currently can't handle the case when the incoming audio has more bits per sample than the configured value
 | 
			
		||||
    return ESP_ERR_INVALID_ARG;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (audio_stream_info.channels == 1) {
 | 
			
		||||
    return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_MONO);
 | 
			
		||||
  } else if (audio_stream_info.channels == 2) {
 | 
			
		||||
    return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_STEREO);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return ESP_ERR_INVALID_ARG;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioSpeaker::delete_task_(size_t buffer_size) {
 | 
			
		||||
  this->audio_ring_buffer_.reset();  // Releases onwership of the shared_ptr
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -91,24 +91,15 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
 | 
			
		||||
  esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size);
 | 
			
		||||
 | 
			
		||||
  /// @brief Starts the ESP32 I2S driver.
 | 
			
		||||
  /// Attempts to lock the I2S port, starts the I2S driver, and sets the data out pin. If it fails, it will unlock
 | 
			
		||||
  /// the I2S port and uninstall the driver, if necessary.
 | 
			
		||||
  /// @return ESP_ERR_INVALID_STATE if the I2S port is already locked.
 | 
			
		||||
  ///         ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error.
 | 
			
		||||
  /// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out
 | 
			
		||||
  /// pin. If it fails, it will unlock the I2S port and uninstall the driver, if necessary.
 | 
			
		||||
  /// @param audio_stream_info Stream information for the I2S driver.
 | 
			
		||||
  /// @return ESP_ERR_NOT_ALLOWED if the I2S port can't play the incoming audio stream.
 | 
			
		||||
  ///         ESP_ERR_INVALID_STATE if the I2S port is already locked.
 | 
			
		||||
  ///         ESP_ERR_INVALID_ARG if nstalling the driver or setting the data outpin fails due to a parameter error.
 | 
			
		||||
  ///         ESP_ERR_NO_MEM if the driver fails to install due to a memory allocation error.
 | 
			
		||||
  ///         ESP_FAIL if setting the data out pin fails due to an IO error
 | 
			
		||||
  ///         ESP_OK if successful
 | 
			
		||||
  esp_err_t start_i2s_driver_();
 | 
			
		||||
 | 
			
		||||
  /// @brief Adjusts the I2S driver configuration to match the incoming audio stream.
 | 
			
		||||
  /// Modifies I2S driver's sample rate, bits per sample, and number of channel settings. If the I2S is in secondary
 | 
			
		||||
  /// mode, it only modifies the number of channels.
 | 
			
		||||
  /// @param audio_stream_info  Describes the incoming audio stream
 | 
			
		||||
  /// @return ESP_ERR_INVALID_ARG if there is a parameter error, if there is more than 2 channels in the stream, or if
 | 
			
		||||
  ///           the audio settings are incompatible with the configuration.
 | 
			
		||||
  ///         ESP_ERR_NO_MEM if the driver fails to reconfigure due to a memory allocation error.
 | 
			
		||||
  ///         ESP_OK if successful.
 | 
			
		||||
  esp_err_t reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info);
 | 
			
		||||
  ///         ESP_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful
 | 
			
		||||
  esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info);
 | 
			
		||||
 | 
			
		||||
  /// @brief Deletes the speaker's task.
 | 
			
		||||
  /// Deallocates the data_buffer_ and audio_ring_buffer_, if necessary, and deletes the task. Should only be called by
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
from esphome import core, pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import display, font, spi
 | 
			
		||||
from esphome.components import display, spi
 | 
			
		||||
from esphome.components.display import validate_rotation
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
@@ -147,7 +147,6 @@ def _validate(config):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    font.validate_pillow_installed,
 | 
			
		||||
    display.FULL_DISPLAY_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ILI9XXXDisplay),
 | 
			
		||||
 
 | 
			
		||||
@@ -313,8 +313,9 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
 | 
			
		||||
  // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not
 | 
			
		||||
  // configured the renderer well.
 | 
			
		||||
  if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian) {
 | 
			
		||||
    return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
 | 
			
		||||
                                            x_pad);
 | 
			
		||||
    display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
 | 
			
		||||
                                     x_pad);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
 | 
			
		||||
  // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ import puremagic
 | 
			
		||||
 | 
			
		||||
from esphome import core, external_files
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import font
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DITHER,
 | 
			
		||||
@@ -233,7 +232,7 @@ IMAGE_SCHEMA = cv.Schema(
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = IMAGE_SCHEMA
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_svg_image(file: bytes, resize: tuple[int, int]):
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ void Logger::write_header_(int level, const char *tag, int line) {
 | 
			
		||||
  if (current_task == main_task_) {
 | 
			
		||||
    this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line);
 | 
			
		||||
  } else {
 | 
			
		||||
    const char *thread_name = "";
 | 
			
		||||
    const char *thread_name = "";  // NOLINT(clang-analyzer-deadcode.DeadStores)
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
    thread_name = pcTaskGetName(current_task);
 | 
			
		||||
#elif defined(USE_LIBRETINY)
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ bool operator==(const GainTimePair &lhs, const GainTimePair &rhs) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs) {
 | 
			
		||||
  return !(lhs.gain == rhs.gain && lhs.time == rhs.time);
 | 
			
		||||
  return lhs.gain != rhs.gain || lhs.time != rhs.time;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
 | 
			
		||||
 
 | 
			
		||||
@@ -322,8 +322,8 @@ async def to_code(configs):
 | 
			
		||||
            await encoders_to_code(lv_component, config, default_group)
 | 
			
		||||
            await keypads_to_code(lv_component, config, default_group)
 | 
			
		||||
            await theme_to_code(config)
 | 
			
		||||
            await styles_to_code(config)
 | 
			
		||||
            await gradients_to_code(config)
 | 
			
		||||
            await styles_to_code(config)
 | 
			
		||||
            await set_obj_properties(lv_scr_act, config)
 | 
			
		||||
            await add_widgets(lv_scr_act, config)
 | 
			
		||||
            await add_pages(lv_component, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ import logging
 | 
			
		||||
 | 
			
		||||
from esphome import codegen as cg, config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ITEMS
 | 
			
		||||
from esphome.core import Lambda
 | 
			
		||||
from esphome.core import ID, Lambda
 | 
			
		||||
from esphome.cpp_generator import LambdaExpression, MockObj
 | 
			
		||||
from esphome.cpp_types import uint32
 | 
			
		||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
 | 
			
		||||
@@ -38,7 +38,7 @@ def literal(arg):
 | 
			
		||||
def call_lambda(lamb: LambdaExpression):
 | 
			
		||||
    expr = lamb.content.strip()
 | 
			
		||||
    if expr.startswith("return") and expr.endswith(";"):
 | 
			
		||||
        return expr[7:][:-1]
 | 
			
		||||
        return expr[6:][:-1].strip()
 | 
			
		||||
    return f"{lamb}()"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -72,6 +72,12 @@ class LValidator:
 | 
			
		||||
            )
 | 
			
		||||
        if self.retmapper is not None:
 | 
			
		||||
            return self.retmapper(value)
 | 
			
		||||
        if isinstance(value, ID):
 | 
			
		||||
            return await cg.get_variable(value)
 | 
			
		||||
        if isinstance(value, list):
 | 
			
		||||
            value = [
 | 
			
		||||
                await cg.get_variable(x) if isinstance(x, ID) else x for x in value
 | 
			
		||||
            ]
 | 
			
		||||
        return cg.safe_exp(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -162,6 +168,7 @@ LV_EVENT_MAP = {
 | 
			
		||||
    "READY": "READY",
 | 
			
		||||
    "CANCEL": "CANCEL",
 | 
			
		||||
    "ALL_EVENTS": "ALL",
 | 
			
		||||
    "CHANGE": "VALUE_CHANGED",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
from typing import Union
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import image
 | 
			
		||||
from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw
 | 
			
		||||
from esphome.components.font import Font
 | 
			
		||||
from esphome.components.image import Image_
 | 
			
		||||
@@ -30,8 +31,8 @@ from .defines import (
 | 
			
		||||
    call_lambda,
 | 
			
		||||
    literal,
 | 
			
		||||
)
 | 
			
		||||
from .helpers import esphome_fonts_used, lv_fonts_used, requires_component
 | 
			
		||||
from .types import lv_font_t, lv_gradient_t, lv_img_t
 | 
			
		||||
from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component
 | 
			
		||||
from .types import lv_font_t, lv_gradient_t
 | 
			
		||||
 | 
			
		||||
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
 | 
			
		||||
 | 
			
		||||
@@ -326,13 +327,18 @@ def image_validator(value):
 | 
			
		||||
    value = requires_component("image")(value)
 | 
			
		||||
    value = cv.use_id(Image_)(value)
 | 
			
		||||
    lv_images_used.add(value)
 | 
			
		||||
    add_lv_use("img", "label")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lv_image = LValidator(
 | 
			
		||||
    image_validator,
 | 
			
		||||
    lv_img_t,
 | 
			
		||||
    retmapper=lambda x: MockObj(x, "->").get_lv_img_dsc(),
 | 
			
		||||
    image.Image_.operator("ptr"),
 | 
			
		||||
    requires="image",
 | 
			
		||||
)
 | 
			
		||||
lv_image_list = LValidator(
 | 
			
		||||
    cv.ensure_list(image_validator),
 | 
			
		||||
    cg.std_vector.template(image.Image_.operator("ptr")),
 | 
			
		||||
    requires="image",
 | 
			
		||||
)
 | 
			
		||||
lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal)
 | 
			
		||||
 
 | 
			
		||||
@@ -279,7 +279,7 @@ std::string LvSelectable::get_selected_text() {
 | 
			
		||||
static std::string join_string(std::vector<std::string> options) {
 | 
			
		||||
  return std::accumulate(
 | 
			
		||||
      options.begin(), options.end(), std::string(),
 | 
			
		||||
      [](const std::string &a, const std::string &b) -> std::string { return a + (a.length() > 0 ? "\n" : "") + b; });
 | 
			
		||||
      [](const std::string &a, const std::string &b) -> std::string { return a + (!a.empty() ? "\n" : "") + b; });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LvSelectable::set_selected_text(const std::string &text, lv_anim_enable_t anim) {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,26 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT
 | 
			
		||||
inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) {
 | 
			
		||||
  lv_img_set_src(obj, image->get_lv_img_dsc());
 | 
			
		||||
}
 | 
			
		||||
inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) {
 | 
			
		||||
  lv_disp_set_bg_image(disp, image->get_lv_img_dsc());
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_LVGL_IMAGE
 | 
			
		||||
#ifdef USE_LVGL_ANIMIMG
 | 
			
		||||
inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) {
 | 
			
		||||
  auto *dsc = static_cast<std::vector<lv_img_dsc_t *> *>(lv_obj_get_user_data(img));
 | 
			
		||||
  if (dsc == nullptr) {
 | 
			
		||||
    // object will be lazily allocated but never freed.
 | 
			
		||||
    dsc = new std::vector<lv_img_dsc_t *>(images.size());  // NOLINT
 | 
			
		||||
    lv_obj_set_user_data(img, dsc);
 | 
			
		||||
  }
 | 
			
		||||
  dsc->clear();
 | 
			
		||||
  for (auto &image : images) {
 | 
			
		||||
    dsc->push_back(image->get_lv_img_dsc());
 | 
			
		||||
  }
 | 
			
		||||
  lv_animimg_set_src(img, (const void **) dsc->data(), dsc->size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif  // USE_LVGL_ANIMIMG
 | 
			
		||||
 | 
			
		||||
// Parent class for things that wrap an LVGL object
 | 
			
		||||
class LvCompound {
 | 
			
		||||
 
 | 
			
		||||
@@ -341,6 +341,7 @@ FLEX_OBJ_SCHEMA = {
 | 
			
		||||
    cv.Optional(df.CONF_FLEX_GROW): cv.int_,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DISP_BG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(df.CONF_DISP_BG_IMAGE): lv_image,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,18 @@
 | 
			
		||||
from esphome import automation
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_DURATION, CONF_ID
 | 
			
		||||
 | 
			
		||||
from ..automation import action_to_code
 | 
			
		||||
from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC
 | 
			
		||||
from ..helpers import lvgl_components_required
 | 
			
		||||
from ..lv_validation import lv_image, lv_milliseconds
 | 
			
		||||
from ..lv_validation import lv_image_list, lv_milliseconds
 | 
			
		||||
from ..lvcode import lv
 | 
			
		||||
from ..types import LvType, ObjUpdateAction, void_ptr
 | 
			
		||||
from ..types import LvType, ObjUpdateAction
 | 
			
		||||
from . import Widget, WidgetType, get_widgets
 | 
			
		||||
from .img import CONF_IMAGE
 | 
			
		||||
from .label import CONF_LABEL
 | 
			
		||||
 | 
			
		||||
CONF_ANIMIMG = "animimg"
 | 
			
		||||
CONF_SRC_LIST_ID = "src_list_id"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def lv_repeat_count(value):
 | 
			
		||||
@@ -32,14 +30,14 @@ ANIMIMG_BASE_SCHEMA = cv.Schema(
 | 
			
		||||
ANIMIMG_SCHEMA = ANIMIMG_BASE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_DURATION): lv_milliseconds,
 | 
			
		||||
        cv.Required(CONF_SRC): cv.ensure_list(lv_image),
 | 
			
		||||
        cv.GenerateID(CONF_SRC_LIST_ID): cv.declare_id(void_ptr),
 | 
			
		||||
        cv.Required(CONF_SRC): lv_image_list,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ANIMIMG_MODIFY_SCHEMA = ANIMIMG_BASE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_DURATION): lv_milliseconds,
 | 
			
		||||
        cv.Optional(CONF_SRC): lv_image_list,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -59,17 +57,14 @@ class AnimimgType(WidgetType):
 | 
			
		||||
    async def to_code(self, w: Widget, config):
 | 
			
		||||
        lvgl_components_required.add(CONF_IMAGE)
 | 
			
		||||
        lvgl_components_required.add(CONF_ANIMIMG)
 | 
			
		||||
        if CONF_SRC in config:
 | 
			
		||||
            srcs = [
 | 
			
		||||
                await lv_image.process(await cg.get_variable(x))
 | 
			
		||||
                for x in config[CONF_SRC]
 | 
			
		||||
            ]
 | 
			
		||||
            src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs)
 | 
			
		||||
            count = len(config[CONF_SRC])
 | 
			
		||||
            lv.animimg_set_src(w.obj, src_id, count)
 | 
			
		||||
        lv.animimg_set_repeat_count(w.obj, config[CONF_REPEAT_COUNT])
 | 
			
		||||
        lv.animimg_set_duration(w.obj, config[CONF_DURATION])
 | 
			
		||||
        if config.get(CONF_AUTO_START):
 | 
			
		||||
        if srcs := config.get(CONF_SRC):
 | 
			
		||||
            srcs = await lv_image_list.process(srcs)
 | 
			
		||||
            lv.animimg_set_src(w.obj, srcs)
 | 
			
		||||
        if repeat_count := config.get(CONF_REPEAT_COUNT):
 | 
			
		||||
            lv.animimg_set_repeat_count(w.obj, repeat_count)
 | 
			
		||||
        if duration := config.get(CONF_DURATION):
 | 
			
		||||
            lv.animimg_set_duration(w.obj, duration)
 | 
			
		||||
        if config[CONF_AUTO_START]:
 | 
			
		||||
            lv.animimg_start(w.obj)
 | 
			
		||||
 | 
			
		||||
    def get_uses(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ANGLE, CONF_MODE
 | 
			
		||||
 | 
			
		||||
@@ -65,7 +64,6 @@ class ImgType(WidgetType):
 | 
			
		||||
 | 
			
		||||
    async def to_code(self, w: Widget, config):
 | 
			
		||||
        if src := config.get(CONF_SRC):
 | 
			
		||||
            src = await cg.get_variable(src)
 | 
			
		||||
            lv.img_set_src(w.obj, await lv_image.process(src))
 | 
			
		||||
        if (cf_angle := config.get(CONF_ANGLE)) is not None:
 | 
			
		||||
            pivot_x = config[CONF_PIVOT_X]
 | 
			
		||||
@@ -81,7 +79,7 @@ class ImgType(WidgetType):
 | 
			
		||||
        if CONF_ANTIALIAS in config:
 | 
			
		||||
            lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS])
 | 
			
		||||
        if mode := config.get(CONF_MODE):
 | 
			
		||||
            lv.img_set_mode(w.obj, mode)
 | 
			
		||||
            await w.set_property("size_mode", mode)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
img_spec = ImgType()
 | 
			
		||||
 
 | 
			
		||||
@@ -35,11 +35,20 @@ LINE_SCHEMA = {
 | 
			
		||||
    cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LINE_MODIFY_SCHEMA = {
 | 
			
		||||
    cv.Optional(CONF_POINTS): cv_point_list,
 | 
			
		||||
    cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LineType(WidgetType):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__(
 | 
			
		||||
            CONF_LINE, LvType("lv_line_t"), (CONF_MAIN,), LINE_SCHEMA, modify_schema={}
 | 
			
		||||
            CONF_LINE,
 | 
			
		||||
            LvType("lv_line_t"),
 | 
			
		||||
            (CONF_MAIN,),
 | 
			
		||||
            LINE_SCHEMA,
 | 
			
		||||
            modify_schema=LINE_MODIFY_SCHEMA,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    async def to_code(self, w: Widget, config):
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ from ..lvcode import (
 | 
			
		||||
)
 | 
			
		||||
from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema
 | 
			
		||||
from ..types import LV_EVENT, char_ptr, lv_obj_t
 | 
			
		||||
from . import Widget, set_obj_properties
 | 
			
		||||
from . import Widget, add_widgets, set_obj_properties
 | 
			
		||||
from .button import button_spec
 | 
			
		||||
from .buttonmatrix import (
 | 
			
		||||
    BUTTONMATRIX_BUTTON_SCHEMA,
 | 
			
		||||
@@ -119,6 +119,7 @@ async def msgbox_to_code(top_layer, conf):
 | 
			
		||||
        button_style = {CONF_ITEMS: button_style}
 | 
			
		||||
        await set_obj_properties(buttonmatrix_widget, button_style)
 | 
			
		||||
    await set_obj_properties(msgbox_widget, conf)
 | 
			
		||||
    await add_widgets(msgbox_widget, conf)
 | 
			
		||||
    async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action:
 | 
			
		||||
        outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN")
 | 
			
		||||
    if close_button:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
from esphome import automation, pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import key_provider
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@ssieb"]
 | 
			
		||||
 | 
			
		||||
@@ -14,6 +14,9 @@ matrix_keypad_ns = cg.esphome_ns.namespace("matrix_keypad")
 | 
			
		||||
MatrixKeypad = matrix_keypad_ns.class_(
 | 
			
		||||
    "MatrixKeypad", key_provider.KeyProvider, cg.Component
 | 
			
		||||
)
 | 
			
		||||
MatrixKeyTrigger = matrix_keypad_ns.class_(
 | 
			
		||||
    "MatrixKeyTrigger", automation.Trigger.template(cg.uint8)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_KEYPAD_ID = "keypad_id"
 | 
			
		||||
CONF_ROWS = "rows"
 | 
			
		||||
@@ -47,6 +50,11 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_DEBOUNCE_TIME, default=1): cv.int_range(min=1, max=100),
 | 
			
		||||
            cv.Optional(CONF_HAS_DIODES): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_HAS_PULLDOWNS): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_ON_KEY): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MatrixKeyTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    check_keys,
 | 
			
		||||
@@ -73,3 +81,7 @@ async def to_code(config):
 | 
			
		||||
        cg.add(var.set_has_diodes(config[CONF_HAS_DIODES]))
 | 
			
		||||
    if CONF_HAS_PULLDOWNS in config:
 | 
			
		||||
        cg.add(var.set_has_pulldowns(config[CONF_HAS_PULLDOWNS]))
 | 
			
		||||
    for conf in config.get(CONF_ON_KEY, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
 | 
			
		||||
        cg.add(var.register_key_trigger(trigger))
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.uint8, "x")], conf)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace matrix_keypad {
 | 
			
		||||
 | 
			
		||||
class MatrixKeypadBinarySensor : public MatrixKeypadListener, public binary_sensor::BinarySensor {
 | 
			
		||||
class MatrixKeypadBinarySensor : public MatrixKeypadListener, public binary_sensor::BinarySensorInitiallyOff {
 | 
			
		||||
 public:
 | 
			
		||||
  MatrixKeypadBinarySensor(uint8_t key) : has_key_(true), key_(key){};
 | 
			
		||||
  MatrixKeypadBinarySensor(const char *key) : has_key_(true), key_((uint8_t) key[0]){};
 | 
			
		||||
 
 | 
			
		||||
@@ -86,6 +86,8 @@ void MatrixKeypad::loop() {
 | 
			
		||||
  if (!this->keys_.empty()) {
 | 
			
		||||
    uint8_t keycode = this->keys_[key];
 | 
			
		||||
    ESP_LOGD(TAG, "key '%c' pressed", keycode);
 | 
			
		||||
    for (auto &trigger : this->key_triggers_)
 | 
			
		||||
      trigger->trigger(keycode);
 | 
			
		||||
    for (auto &listener : this->listeners_)
 | 
			
		||||
      listener->key_pressed(keycode);
 | 
			
		||||
    this->send_key_(keycode);
 | 
			
		||||
@@ -107,5 +109,7 @@ void MatrixKeypad::dump_config() {
 | 
			
		||||
 | 
			
		||||
void MatrixKeypad::register_listener(MatrixKeypadListener *listener) { this->listeners_.push_back(listener); }
 | 
			
		||||
 | 
			
		||||
void MatrixKeypad::register_key_trigger(MatrixKeyTrigger *trig) { this->key_triggers_.push_back(trig); }
 | 
			
		||||
 | 
			
		||||
}  // namespace matrix_keypad
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/key_provider/key_provider.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
@@ -18,6 +19,8 @@ class MatrixKeypadListener {
 | 
			
		||||
  virtual void key_released(uint8_t key){};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MatrixKeyTrigger : public Trigger<uint8_t> {};
 | 
			
		||||
 | 
			
		||||
class MatrixKeypad : public key_provider::KeyProvider, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
@@ -31,6 +34,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component {
 | 
			
		||||
  void set_has_pulldowns(int has_pulldowns) { has_pulldowns_ = has_pulldowns; };
 | 
			
		||||
 | 
			
		||||
  void register_listener(MatrixKeypadListener *listener);
 | 
			
		||||
  void register_key_trigger(MatrixKeyTrigger *trig);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<GPIOPin *> rows_;
 | 
			
		||||
@@ -42,6 +46,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component {
 | 
			
		||||
  int pressed_key_ = -1;
 | 
			
		||||
 | 
			
		||||
  std::vector<MatrixKeypadListener *> listeners_{};
 | 
			
		||||
  std::vector<MatrixKeyTrigger *> key_triggers_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace matrix_keypad
 | 
			
		||||
 
 | 
			
		||||
@@ -106,7 +106,8 @@ void MAX31865Sensor::read_data_() {
 | 
			
		||||
 | 
			
		||||
  // Check faults
 | 
			
		||||
  const uint8_t faults = this->read_register_(FAULT_STATUS_REG);
 | 
			
		||||
  if ((has_fault_ = faults & 0b00111100)) {
 | 
			
		||||
  has_fault_ = faults & 0b00111100;
 | 
			
		||||
  if (has_fault_) {
 | 
			
		||||
    if (faults & (1 << 2)) {
 | 
			
		||||
      ESP_LOGE(TAG, "Overvoltage/undervoltage fault");
 | 
			
		||||
    }
 | 
			
		||||
@@ -125,7 +126,8 @@ void MAX31865Sensor::read_data_() {
 | 
			
		||||
  } else {
 | 
			
		||||
    this->status_clear_error();
 | 
			
		||||
  }
 | 
			
		||||
  if ((has_warn_ = faults & 0b11000000)) {
 | 
			
		||||
  has_warn_ = faults & 0b11000000;
 | 
			
		||||
  if (has_warn_) {
 | 
			
		||||
    if (faults & (1 << 6)) {
 | 
			
		||||
      ESP_LOGW(TAG, "RTD Low Threshold");
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -38,8 +38,9 @@ void Modbus::loop() {
 | 
			
		||||
 | 
			
		||||
    // stop blocking new send commands after sent_wait_time_ ms after response received
 | 
			
		||||
    if (now - this->last_send_ > send_wait_time_) {
 | 
			
		||||
      if (waiting_for_response > 0)
 | 
			
		||||
      if (waiting_for_response > 0) {
 | 
			
		||||
        ESP_LOGV(TAG, "Stop waiting for response from %d", waiting_for_response);
 | 
			
		||||
      }
 | 
			
		||||
      waiting_for_response = 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -163,7 +163,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_OFFLINE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger),
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOfflineTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -152,11 +152,11 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
 | 
			
		||||
  auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
 | 
			
		||||
    return (r.start_address == start_address && r.register_type == register_type);
 | 
			
		||||
  });
 | 
			
		||||
  auto reg_it = std::find_if(
 | 
			
		||||
      std::begin(this->register_ranges_), std::end(this->register_ranges_),
 | 
			
		||||
      [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); });
 | 
			
		||||
 | 
			
		||||
  if (reg_it == register_ranges_.end()) {
 | 
			
		||||
  if (reg_it == this->register_ranges_.end()) {
 | 
			
		||||
    ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
 | 
			
		||||
  } else {
 | 
			
		||||
    return reg_it->sensors;
 | 
			
		||||
@@ -240,18 +240,18 @@ void ModbusController::update() {
 | 
			
		||||
 | 
			
		||||
// walk through the sensors and determine the register ranges to read
 | 
			
		||||
size_t ModbusController::create_register_ranges_() {
 | 
			
		||||
  register_ranges_.clear();
 | 
			
		||||
  if (this->parent_->role == modbus::ModbusRole::CLIENT && sensorset_.empty()) {
 | 
			
		||||
  this->register_ranges_.clear();
 | 
			
		||||
  if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) {
 | 
			
		||||
    ESP_LOGW(TAG, "No sensors registered");
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // iterator is sorted see SensorItemsComparator for details
 | 
			
		||||
  auto ix = sensorset_.begin();
 | 
			
		||||
  auto ix = this->sensorset_.begin();
 | 
			
		||||
  RegisterRange r = {};
 | 
			
		||||
  uint8_t buffer_offset = 0;
 | 
			
		||||
  SensorItem *prev = nullptr;
 | 
			
		||||
  while (ix != sensorset_.end()) {
 | 
			
		||||
  while (ix != this->sensorset_.end()) {
 | 
			
		||||
    SensorItem *curr = *ix;
 | 
			
		||||
 | 
			
		||||
    ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count,
 | 
			
		||||
@@ -278,12 +278,12 @@ size_t ModbusController::create_register_ranges_() {
 | 
			
		||||
          // this register can re-use the data from the previous register
 | 
			
		||||
 | 
			
		||||
          // remove this sensore because start_address is changed (sort-order)
 | 
			
		||||
          ix = sensorset_.erase(ix);
 | 
			
		||||
          ix = this->sensorset_.erase(ix);
 | 
			
		||||
 | 
			
		||||
          curr->start_address = r.start_address;
 | 
			
		||||
          curr->offset += prev->offset;
 | 
			
		||||
 | 
			
		||||
          sensorset_.insert(curr);
 | 
			
		||||
          this->sensorset_.insert(curr);
 | 
			
		||||
          // move iterator backwards because it will be incremented later
 | 
			
		||||
          ix--;
 | 
			
		||||
 | 
			
		||||
@@ -293,14 +293,14 @@ size_t ModbusController::create_register_ranges_() {
 | 
			
		||||
          // this register can extend the current range
 | 
			
		||||
 | 
			
		||||
          // remove this sensore because start_address is changed (sort-order)
 | 
			
		||||
          ix = sensorset_.erase(ix);
 | 
			
		||||
          ix = this->sensorset_.erase(ix);
 | 
			
		||||
 | 
			
		||||
          curr->start_address = r.start_address;
 | 
			
		||||
          curr->offset += buffer_offset;
 | 
			
		||||
          buffer_offset += curr->get_register_size();
 | 
			
		||||
          r.register_count += curr->register_count;
 | 
			
		||||
 | 
			
		||||
          sensorset_.insert(curr);
 | 
			
		||||
          this->sensorset_.insert(curr);
 | 
			
		||||
          // move iterator backwards because it will be incremented later
 | 
			
		||||
          ix--;
 | 
			
		||||
 | 
			
		||||
@@ -327,7 +327,7 @@ size_t ModbusController::create_register_ranges_() {
 | 
			
		||||
      ix++;
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
 | 
			
		||||
      register_ranges_.push_back(r);
 | 
			
		||||
      this->register_ranges_.push_back(r);
 | 
			
		||||
      r = {};
 | 
			
		||||
      buffer_offset = 0;
 | 
			
		||||
      // do not increment the iterator here because the current sensor has to be re-evaluated
 | 
			
		||||
@@ -339,10 +339,10 @@ size_t ModbusController::create_register_ranges_() {
 | 
			
		||||
  if (r.register_count > 0) {
 | 
			
		||||
    // Add the last range
 | 
			
		||||
    ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
 | 
			
		||||
    register_ranges_.push_back(r);
 | 
			
		||||
    this->register_ranges_.push_back(r);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return register_ranges_.size();
 | 
			
		||||
  return this->register_ranges_.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ModbusController::dump_config() {
 | 
			
		||||
@@ -352,18 +352,18 @@ void ModbusController::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Offline Skip Updates: %d", this->offline_skip_updates_);
 | 
			
		||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "sensormap");
 | 
			
		||||
  for (auto &it : sensorset_) {
 | 
			
		||||
  for (auto &it : this->sensorset_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d",
 | 
			
		||||
                  static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
 | 
			
		||||
                  it->get_register_size());
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ranges");
 | 
			
		||||
  for (auto &it : register_ranges_) {
 | 
			
		||||
  for (auto &it : this->register_ranges_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
 | 
			
		||||
                  it.start_address, it.register_count, it.skip_updates);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "server registers");
 | 
			
		||||
  for (auto &r : server_registers_) {
 | 
			
		||||
  for (auto &r : this->server_registers_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Address=0x%02X value_type=%zu register_count=%u", r->address,
 | 
			
		||||
                  static_cast<uint8_t>(r->value_type), r->register_count);
 | 
			
		||||
  }
 | 
			
		||||
@@ -372,15 +372,15 @@ void ModbusController::dump_config() {
 | 
			
		||||
 | 
			
		||||
void ModbusController::loop() {
 | 
			
		||||
  // Incoming data to process?
 | 
			
		||||
  if (!incoming_queue_.empty()) {
 | 
			
		||||
    auto &message = incoming_queue_.front();
 | 
			
		||||
  if (!this->incoming_queue_.empty()) {
 | 
			
		||||
    auto &message = this->incoming_queue_.front();
 | 
			
		||||
    if (message != nullptr)
 | 
			
		||||
      process_modbus_data_(message.get());
 | 
			
		||||
    incoming_queue_.pop();
 | 
			
		||||
      this->process_modbus_data_(message.get());
 | 
			
		||||
    this->incoming_queue_.pop();
 | 
			
		||||
 | 
			
		||||
  } else {
 | 
			
		||||
    // all messages processed send pending commands
 | 
			
		||||
    send_next_command_();
 | 
			
		||||
    this->send_next_command_();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -391,7 +391,7 @@ void ModbusController::on_write_register_response(ModbusRegisterType register_ty
 | 
			
		||||
 | 
			
		||||
void ModbusController::dump_sensors_() {
 | 
			
		||||
  ESP_LOGV(TAG, "sensors");
 | 
			
		||||
  for (auto &it : sensorset_) {
 | 
			
		||||
  for (auto &it : this->sensorset_) {
 | 
			
		||||
    ESP_LOGV(TAG, "  Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count,
 | 
			
		||||
             it->get_register_size(), it->offset);
 | 
			
		||||
  }
 | 
			
		||||
@@ -622,51 +622,87 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens
 | 
			
		||||
                          uint32_t bitmask) {
 | 
			
		||||
  int64_t value = 0;  // int64_t because it can hold signed and unsigned 32 bits
 | 
			
		||||
 | 
			
		||||
  size_t size = data.size() - offset;
 | 
			
		||||
  bool error = false;
 | 
			
		||||
  switch (sensor_value_type) {
 | 
			
		||||
    case SensorValueType::U_WORD:
 | 
			
		||||
      value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask);  // default is 0xFFFF ;
 | 
			
		||||
      if (size >= 2) {
 | 
			
		||||
        value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask);  // default is 0xFFFF ;
 | 
			
		||||
      } else {
 | 
			
		||||
        error = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::U_DWORD:
 | 
			
		||||
    case SensorValueType::FP32:
 | 
			
		||||
      value = get_data<uint32_t>(data, offset);
 | 
			
		||||
      value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
 | 
			
		||||
      if (size >= 4) {
 | 
			
		||||
        value = get_data<uint32_t>(data, offset);
 | 
			
		||||
        value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
 | 
			
		||||
      } else {
 | 
			
		||||
        error = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::U_DWORD_R:
 | 
			
		||||
    case SensorValueType::FP32_R:
 | 
			
		||||
      value = get_data<uint32_t>(data, offset);
 | 
			
		||||
      value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
 | 
			
		||||
      value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
 | 
			
		||||
      if (size >= 4) {
 | 
			
		||||
        value = get_data<uint32_t>(data, offset);
 | 
			
		||||
        value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
 | 
			
		||||
        value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
 | 
			
		||||
      } else {
 | 
			
		||||
        error = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::S_WORD:
 | 
			
		||||
      value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
 | 
			
		||||
                                         bitmask);  // default is 0xFFFF ;
 | 
			
		||||
      if (size >= 2) {
 | 
			
		||||
        value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
 | 
			
		||||
                                           bitmask);  // default is 0xFFFF ;
 | 
			
		||||
      } else {
 | 
			
		||||
        error = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::S_DWORD:
 | 
			
		||||
      value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
 | 
			
		||||
      if (size >= 4) {
 | 
			
		||||
        value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
 | 
			
		||||
      } else {
 | 
			
		||||
        error = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::S_DWORD_R: {
 | 
			
		||||
      value = get_data<uint32_t>(data, offset);
 | 
			
		||||
      // Currently the high word is at the low position
 | 
			
		||||
      // the sign bit is therefore at low before the switch
 | 
			
		||||
      uint32_t sign_bit = (value & 0x8000) << 16;
 | 
			
		||||
      value = mask_and_shift_by_rightbit(
 | 
			
		||||
          static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
 | 
			
		||||
      if (size >= 4) {
 | 
			
		||||
        value = get_data<uint32_t>(data, offset);
 | 
			
		||||
        // Currently the high word is at the low position
 | 
			
		||||
        // the sign bit is therefore at low before the switch
 | 
			
		||||
        uint32_t sign_bit = (value & 0x8000) << 16;
 | 
			
		||||
        value = mask_and_shift_by_rightbit(
 | 
			
		||||
            static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
 | 
			
		||||
      } else {
 | 
			
		||||
        error = true;
 | 
			
		||||
      }
 | 
			
		||||
    } break;
 | 
			
		||||
    case SensorValueType::U_QWORD:
 | 
			
		||||
    case SensorValueType::S_QWORD:
 | 
			
		||||
      // Ignore bitmask for QWORD
 | 
			
		||||
      value = get_data<uint64_t>(data, offset);
 | 
			
		||||
      if (size >= 8) {
 | 
			
		||||
        value = get_data<uint64_t>(data, offset);
 | 
			
		||||
      } else {
 | 
			
		||||
        error = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::U_QWORD_R:
 | 
			
		||||
    case SensorValueType::S_QWORD_R: {
 | 
			
		||||
      // Ignore bitmask for QWORD
 | 
			
		||||
      uint64_t tmp = get_data<uint64_t>(data, offset);
 | 
			
		||||
      value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
 | 
			
		||||
      if (size >= 8) {
 | 
			
		||||
        uint64_t tmp = get_data<uint64_t>(data, offset);
 | 
			
		||||
        value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
 | 
			
		||||
      } else {
 | 
			
		||||
        error = true;
 | 
			
		||||
      }
 | 
			
		||||
    } break;
 | 
			
		||||
    case SensorValueType::RAW:
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  if (error)
 | 
			
		||||
    ESP_LOGE(TAG, "not enough data for value");
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -240,14 +240,14 @@ class SensorItem {
 | 
			
		||||
  }
 | 
			
		||||
  // Override register size for modbus devices not using 1 register for one dword
 | 
			
		||||
  void set_register_size(uint8_t register_size) { response_bytes = register_size; }
 | 
			
		||||
  ModbusRegisterType register_type;
 | 
			
		||||
  SensorValueType sensor_value_type;
 | 
			
		||||
  uint16_t start_address;
 | 
			
		||||
  uint32_t bitmask;
 | 
			
		||||
  uint8_t offset;
 | 
			
		||||
  uint8_t register_count;
 | 
			
		||||
  ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
 | 
			
		||||
  SensorValueType sensor_value_type{SensorValueType::RAW};
 | 
			
		||||
  uint16_t start_address{0};
 | 
			
		||||
  uint32_t bitmask{0};
 | 
			
		||||
  uint8_t offset{0};
 | 
			
		||||
  uint8_t register_count{0};
 | 
			
		||||
  uint8_t response_bytes{0};
 | 
			
		||||
  uint16_t skip_updates;
 | 
			
		||||
  uint16_t skip_updates{0};
 | 
			
		||||
  std::vector<uint8_t> custom_data{};
 | 
			
		||||
  bool force_new_range{false};
 | 
			
		||||
};
 | 
			
		||||
@@ -261,9 +261,9 @@ class ServerRegister {
 | 
			
		||||
    this->register_count = register_count;
 | 
			
		||||
    this->read_lambda = std::move(read_lambda);
 | 
			
		||||
  }
 | 
			
		||||
  uint16_t address;
 | 
			
		||||
  SensorValueType value_type;
 | 
			
		||||
  uint8_t register_count;
 | 
			
		||||
  uint16_t address{0};
 | 
			
		||||
  SensorValueType value_type{SensorValueType::RAW};
 | 
			
		||||
  uint8_t register_count{0};
 | 
			
		||||
  std::function<float()> read_lambda;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -312,11 +312,11 @@ struct RegisterRange {
 | 
			
		||||
class ModbusCommandItem {
 | 
			
		||||
 public:
 | 
			
		||||
  static const size_t MAX_PAYLOAD_BYTES = 240;
 | 
			
		||||
  ModbusController *modbusdevice;
 | 
			
		||||
  uint16_t register_address;
 | 
			
		||||
  uint16_t register_count;
 | 
			
		||||
  ModbusFunctionCode function_code;
 | 
			
		||||
  ModbusRegisterType register_type;
 | 
			
		||||
  ModbusController *modbusdevice{nullptr};
 | 
			
		||||
  uint16_t register_address{0};
 | 
			
		||||
  uint16_t register_count{0};
 | 
			
		||||
  ModbusFunctionCode function_code{ModbusFunctionCode::CUSTOM};
 | 
			
		||||
  ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
 | 
			
		||||
  std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
 | 
			
		||||
      on_data_func;
 | 
			
		||||
  std::vector<uint8_t> payload = {};
 | 
			
		||||
@@ -493,23 +493,23 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
 | 
			
		||||
  /// Collection of all sensors for this component
 | 
			
		||||
  SensorSet sensorset_;
 | 
			
		||||
  /// Collection of all server registers for this component
 | 
			
		||||
  std::vector<ServerRegister *> server_registers_;
 | 
			
		||||
  std::vector<ServerRegister *> server_registers_{};
 | 
			
		||||
  /// Continuous range of modbus registers
 | 
			
		||||
  std::vector<RegisterRange> register_ranges_;
 | 
			
		||||
  std::vector<RegisterRange> register_ranges_{};
 | 
			
		||||
  /// Hold the pending requests to be sent
 | 
			
		||||
  std::list<std::unique_ptr<ModbusCommandItem>> command_queue_;
 | 
			
		||||
  /// modbus response data waiting to get processed
 | 
			
		||||
  std::queue<std::unique_ptr<ModbusCommandItem>> incoming_queue_;
 | 
			
		||||
  /// if duplicate commands can be sent
 | 
			
		||||
  bool allow_duplicate_commands_;
 | 
			
		||||
  bool allow_duplicate_commands_{false};
 | 
			
		||||
  /// when was the last send operation
 | 
			
		||||
  uint32_t last_command_timestamp_;
 | 
			
		||||
  uint32_t last_command_timestamp_{0};
 | 
			
		||||
  /// min time in ms between sending modbus commands
 | 
			
		||||
  uint16_t command_throttle_;
 | 
			
		||||
  uint16_t command_throttle_{0};
 | 
			
		||||
  /// if module didn't respond the last command
 | 
			
		||||
  bool module_offline_;
 | 
			
		||||
  bool module_offline_{false};
 | 
			
		||||
  /// how many updates to skip if module is offline
 | 
			
		||||
  uint16_t offline_skip_updates_;
 | 
			
		||||
  uint16_t offline_skip_updates_{0};
 | 
			
		||||
  /// How many times we will retry a command if we get no response
 | 
			
		||||
  uint8_t max_cmd_retries_{4};
 | 
			
		||||
  /// Command sent callback
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ namespace modbus_controller {
 | 
			
		||||
static const char *const TAG = "modbus.number";
 | 
			
		||||
 | 
			
		||||
void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) {
 | 
			
		||||
  float result = payload_to_float(data, *this) / multiply_by_;
 | 
			
		||||
  float result = payload_to_float(data, *this) / this->multiply_by_;
 | 
			
		||||
 | 
			
		||||
  // Is there a lambda registered
 | 
			
		||||
  // call it with the pre converted value and the raw data array
 | 
			
		||||
@@ -43,7 +43,7 @@ void ModbusNumber::control(float value) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    write_value = multiply_by_ * write_value;
 | 
			
		||||
    write_value = this->multiply_by_ * write_value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!data.empty()) {
 | 
			
		||||
@@ -63,21 +63,21 @@ void ModbusNumber::control(float value) {
 | 
			
		||||
    // Create and send the write command
 | 
			
		||||
    if (this->register_count == 1 && !this->use_write_multiple_) {
 | 
			
		||||
      // since offset is in bytes and a register is 16 bits we get the start by adding offset/2
 | 
			
		||||
      write_cmd =
 | 
			
		||||
          ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]);
 | 
			
		||||
      write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2,
 | 
			
		||||
                                                                 data[0]);
 | 
			
		||||
    } else {
 | 
			
		||||
      write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2,
 | 
			
		||||
                                                                   this->register_count, data);
 | 
			
		||||
      write_cmd = ModbusCommandItem::create_write_multiple_command(
 | 
			
		||||
          this->parent_, this->start_address + this->offset / 2, this->register_count, data);
 | 
			
		||||
    }
 | 
			
		||||
    // publish new value
 | 
			
		||||
    write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
 | 
			
		||||
                                                      const std::vector<uint8_t> &data) {
 | 
			
		||||
      // gets called when the write command is ack'd from the device
 | 
			
		||||
      parent_->on_write_register_response(write_cmd.register_type, start_address, data);
 | 
			
		||||
      this->parent_->on_write_register_response(write_cmd.register_type, start_address, data);
 | 
			
		||||
      this->publish_state(value);
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  parent_->queue_command(write_cmd);
 | 
			
		||||
  this->parent_->queue_command(write_cmd);
 | 
			
		||||
  this->publish_state(value);
 | 
			
		||||
}
 | 
			
		||||
void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); }
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
 | 
			
		||||
  void parse_and_publish(const std::vector<uint8_t> &data) override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
			
		||||
  void set_parent(ModbusController *parent) { this->parent_ = parent; }
 | 
			
		||||
  void set_write_multiply(float factor) { multiply_by_ = factor; }
 | 
			
		||||
  void set_write_multiply(float factor) { this->multiply_by_ = factor; }
 | 
			
		||||
 | 
			
		||||
  using transform_func_t = std::function<optional<float>(ModbusNumber *, float, const std::vector<uint8_t> &)>;
 | 
			
		||||
  using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>;
 | 
			
		||||
@@ -39,9 +39,9 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void control(float value) override;
 | 
			
		||||
  optional<transform_func_t> transform_func_;
 | 
			
		||||
  optional<write_transform_func_t> write_transform_func_;
 | 
			
		||||
  ModbusController *parent_;
 | 
			
		||||
  optional<transform_func_t> transform_func_{nullopt};
 | 
			
		||||
  optional<write_transform_func_t> write_transform_func_{nullopt};
 | 
			
		||||
  ModbusController *parent_{nullptr};
 | 
			
		||||
  float multiply_by_{1.0};
 | 
			
		||||
  bool use_write_multiple_{false};
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ void ModbusFloatOutput::write_state(float value) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    value = multiply_by_ * value;
 | 
			
		||||
    value = this->multiply_by_ * value;
 | 
			
		||||
  }
 | 
			
		||||
  // lambda didn't set payload
 | 
			
		||||
  if (data.empty()) {
 | 
			
		||||
@@ -40,12 +40,13 @@ void ModbusFloatOutput::write_state(float value) {
 | 
			
		||||
  // Create and send the write command
 | 
			
		||||
  ModbusCommandItem write_cmd;
 | 
			
		||||
  if (this->register_count == 1 && !this->use_write_multiple_) {
 | 
			
		||||
    write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]);
 | 
			
		||||
    write_cmd =
 | 
			
		||||
        ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset, data[0]);
 | 
			
		||||
  } else {
 | 
			
		||||
    write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset,
 | 
			
		||||
    write_cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset,
 | 
			
		||||
                                                                 this->register_count, data);
 | 
			
		||||
  }
 | 
			
		||||
  parent_->queue_command(write_cmd);
 | 
			
		||||
  this->parent_->queue_command(write_cmd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ModbusFloatOutput::dump_config() {
 | 
			
		||||
@@ -90,9 +91,9 @@ void ModbusBinaryOutput::write_state(bool state) {
 | 
			
		||||
    // offset for coil and discrete inputs is the coil/register number not bytes
 | 
			
		||||
    if (this->use_write_multiple_) {
 | 
			
		||||
      std::vector<bool> states{state};
 | 
			
		||||
      cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
 | 
			
		||||
      cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states);
 | 
			
		||||
    } else {
 | 
			
		||||
      cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
 | 
			
		||||
      cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->parent_->queue_command(cmd);
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  void set_parent(ModbusController *parent) { this->parent_ = parent; }
 | 
			
		||||
  void set_write_multiply(float factor) { multiply_by_ = factor; }
 | 
			
		||||
  void set_write_multiply(float factor) { this->multiply_by_ = factor; }
 | 
			
		||||
  // Do nothing
 | 
			
		||||
  void parse_and_publish(const std::vector<uint8_t> &data) override{};
 | 
			
		||||
 | 
			
		||||
@@ -37,9 +37,9 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
 | 
			
		||||
  void write_state(float value) override;
 | 
			
		||||
  optional<write_transform_func_t> write_transform_func_{nullopt};
 | 
			
		||||
 | 
			
		||||
  ModbusController *parent_;
 | 
			
		||||
  ModbusController *parent_{nullptr};
 | 
			
		||||
  float multiply_by_{1.0};
 | 
			
		||||
  bool use_write_multiple_;
 | 
			
		||||
  bool use_write_multiple_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem {
 | 
			
		||||
@@ -68,8 +68,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public
 | 
			
		||||
  void write_state(bool state) override;
 | 
			
		||||
  optional<write_transform_func_t> write_transform_func_{nullopt};
 | 
			
		||||
 | 
			
		||||
  ModbusController *parent_;
 | 
			
		||||
  bool use_write_multiple_;
 | 
			
		||||
  ModbusController *parent_{nullptr};
 | 
			
		||||
  bool use_write_multiple_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace modbus_controller
 | 
			
		||||
 
 | 
			
		||||
@@ -74,12 +74,13 @@ void ModbusSelect::control(const std::string &value) {
 | 
			
		||||
  const uint16_t write_address = this->start_address + this->offset / 2;
 | 
			
		||||
  ModbusCommandItem write_cmd;
 | 
			
		||||
  if ((this->register_count == 1) && (!this->use_write_multiple_)) {
 | 
			
		||||
    write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]);
 | 
			
		||||
    write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, write_address, data[0]);
 | 
			
		||||
  } else {
 | 
			
		||||
    write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data);
 | 
			
		||||
    write_cmd =
 | 
			
		||||
        ModbusCommandItem::create_write_multiple_command(this->parent_, write_address, this->register_count, data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  parent_->queue_command(write_cmd);
 | 
			
		||||
  this->parent_->queue_command(write_cmd);
 | 
			
		||||
 | 
			
		||||
  if (this->optimistic_)
 | 
			
		||||
    this->publish_state(value);
 | 
			
		||||
 
 | 
			
		||||
@@ -42,12 +42,12 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
 | 
			
		||||
  void control(const std::string &value) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<int64_t> mapping_;
 | 
			
		||||
  ModbusController *parent_;
 | 
			
		||||
  std::vector<int64_t> mapping_{};
 | 
			
		||||
  ModbusController *parent_{nullptr};
 | 
			
		||||
  bool use_write_multiple_{false};
 | 
			
		||||
  bool optimistic_{false};
 | 
			
		||||
  optional<transform_func_t> transform_func_;
 | 
			
		||||
  optional<write_transform_func_t> write_transform_func_;
 | 
			
		||||
  optional<transform_func_t> transform_func_{nullopt};
 | 
			
		||||
  optional<write_transform_func_t> write_transform_func_{nullopt};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace modbus_controller
 | 
			
		||||
 
 | 
			
		||||
@@ -80,24 +80,24 @@ void ModbusSwitch::write_state(bool state) {
 | 
			
		||||
      // offset for coil and discrete inputs is the coil/register number not bytes
 | 
			
		||||
      if (this->use_write_multiple_) {
 | 
			
		||||
        std::vector<bool> states{state};
 | 
			
		||||
        cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
 | 
			
		||||
        cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states);
 | 
			
		||||
      } else {
 | 
			
		||||
        cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
 | 
			
		||||
        cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // since offset is in bytes and a register is 16 bits we get the start by adding offset/2
 | 
			
		||||
      if (this->use_write_multiple_) {
 | 
			
		||||
        std::vector<uint16_t> bool_states(1, state ? (0xFFFF & this->bitmask) : 0);
 | 
			
		||||
        cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1,
 | 
			
		||||
        cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset / 2, 1,
 | 
			
		||||
                                                               bool_states);
 | 
			
		||||
      } else {
 | 
			
		||||
        cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2,
 | 
			
		||||
        cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2,
 | 
			
		||||
                                                             state ? 0xFFFF & this->bitmask : 0u);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->parent_->queue_command(cmd);
 | 
			
		||||
  publish_state(state);
 | 
			
		||||
  this->publish_state(state);
 | 
			
		||||
}
 | 
			
		||||
// ModbusSwitch end
 | 
			
		||||
}  // namespace modbus_controller
 | 
			
		||||
 
 | 
			
		||||
@@ -40,8 +40,8 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
 | 
			
		||||
  void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  ModbusController *parent_;
 | 
			
		||||
  bool use_write_multiple_;
 | 
			
		||||
  ModbusController *parent_{nullptr};
 | 
			
		||||
  bool use_write_multiple_{false};
 | 
			
		||||
  optional<transform_func_t> publish_transform_func_{nullopt};
 | 
			
		||||
  optional<write_transform_func_t> write_transform_func_{nullopt};
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_USE_ABBREVIATIONS,
 | 
			
		||||
    CONF_USERNAME,
 | 
			
		||||
    CONF_WILL_MESSAGE,
 | 
			
		||||
    CONF_PUBLISH_NAN_AS_NONE,
 | 
			
		||||
    PLATFORM_BK72XX,
 | 
			
		||||
    PLATFORM_ESP32,
 | 
			
		||||
    PLATFORM_ESP8266,
 | 
			
		||||
@@ -296,6 +297,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                    cv.Optional(CONF_QOS, default=0): cv.mqtt_qos,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    validate_config,
 | 
			
		||||
@@ -449,6 +451,8 @@ async def to_code(config):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -80,8 +80,7 @@ const EntityBase *MQTTAlarmControlPanelComponent::get_entity() const { return th
 | 
			
		||||
 | 
			
		||||
bool MQTTAlarmControlPanelComponent::send_initial_state() { return this->publish_state(); }
 | 
			
		||||
bool MQTTAlarmControlPanelComponent::publish_state() {
 | 
			
		||||
  bool success = true;
 | 
			
		||||
  const char *state_s = "";
 | 
			
		||||
  const char *state_s;
 | 
			
		||||
  switch (this->alarm_control_panel_->get_state()) {
 | 
			
		||||
    case ACP_STATE_DISARMED:
 | 
			
		||||
      state_s = "disarmed";
 | 
			
		||||
@@ -116,9 +115,7 @@ bool MQTTAlarmControlPanelComponent::publish_state() {
 | 
			
		||||
    default:
 | 
			
		||||
      state_s = "unknown";
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->publish(this->get_state_topic_(), state_s))
 | 
			
		||||
    success = false;
 | 
			
		||||
  return success;
 | 
			
		||||
  return this->publish(this->get_state_topic_(), state_s);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace mqtt
 | 
			
		||||
 
 | 
			
		||||
@@ -151,11 +151,11 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) {
 | 
			
		||||
      break;
 | 
			
		||||
    case MQTT_EVENT_DATA: {
 | 
			
		||||
      static std::string topic;
 | 
			
		||||
      if (event.topic.length() > 0) {
 | 
			
		||||
      if (!event.topic.empty()) {
 | 
			
		||||
        topic = event.topic;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str());
 | 
			
		||||
      this->on_message_.call(event.topic.length() > 0 ? topic.c_str() : nullptr, event.data.data(), event.data.size(),
 | 
			
		||||
      this->on_message_.call(!event.topic.empty() ? topic.c_str() : nullptr, event.data.data(), event.data.size(),
 | 
			
		||||
                             event.current_data_offset, event.total_data_len);
 | 
			
		||||
    } break;
 | 
			
		||||
    case MQTT_EVENT_ERROR:
 | 
			
		||||
@@ -184,7 +184,7 @@ void MQTTBackendESP32::mqtt_event_handler(void *handler_args, esp_event_base_t b
 | 
			
		||||
  // queue event to decouple processing
 | 
			
		||||
  if (instance) {
 | 
			
		||||
    auto event = *static_cast<esp_mqtt_event_t *>(event_data);
 | 
			
		||||
    instance->mqtt_events_.push(Event(event));
 | 
			
		||||
    instance->mqtt_events_.emplace(event);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -608,6 +608,10 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this
 | 
			
		||||
const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; }
 | 
			
		||||
void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; }
 | 
			
		||||
const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; }
 | 
			
		||||
void MQTTClientComponent::set_publish_nan_as_none(bool publish_nan_as_none) {
 | 
			
		||||
  this->publish_nan_as_none_ = publish_nan_as_none;
 | 
			
		||||
}
 | 
			
		||||
bool MQTTClientComponent::is_publish_nan_as_none() const { return this->publish_nan_as_none_; }
 | 
			
		||||
void MQTTClientComponent::disable_birth_message() {
 | 
			
		||||
  this->birth_message_.topic = "";
 | 
			
		||||
  this->recalculate_availability_();
 | 
			
		||||
 
 | 
			
		||||
@@ -263,6 +263,10 @@ class MQTTClientComponent : public Component {
 | 
			
		||||
  void set_on_connect(mqtt_on_connect_callback_t &&callback);
 | 
			
		||||
  void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback);
 | 
			
		||||
 | 
			
		||||
  // Publish None state instead of NaN for Home Assistant
 | 
			
		||||
  void set_publish_nan_as_none(bool publish_nan_as_none);
 | 
			
		||||
  bool is_publish_nan_as_none() const;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void send_device_info_();
 | 
			
		||||
 | 
			
		||||
@@ -328,6 +332,8 @@ class MQTTClientComponent : public Component {
 | 
			
		||||
  uint32_t connect_begin_;
 | 
			
		||||
  uint32_t last_connected_{0};
 | 
			
		||||
  optional<MQTTClientDisconnectReason> disconnect_reason_{};
 | 
			
		||||
 | 
			
		||||
  bool publish_nan_as_none_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern MQTTClientComponent *global_mqtt_client;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 
 | 
			
		||||
@@ -257,7 +257,7 @@ const EntityBase *MQTTClimateComponent::get_entity() const { return this->device
 | 
			
		||||
bool MQTTClimateComponent::publish_state_() {
 | 
			
		||||
  auto traits = this->device_->get_traits();
 | 
			
		||||
  // mode
 | 
			
		||||
  const char *mode_s = "";
 | 
			
		||||
  const char *mode_s;
 | 
			
		||||
  switch (this->device_->mode) {
 | 
			
		||||
    case CLIMATE_MODE_OFF:
 | 
			
		||||
      mode_s = "off";
 | 
			
		||||
@@ -280,6 +280,8 @@ bool MQTTClimateComponent::publish_state_() {
 | 
			
		||||
    case CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
      mode_s = "heat_cool";
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      mode_s = "unknown";
 | 
			
		||||
  }
 | 
			
		||||
  bool success = true;
 | 
			
		||||
  if (!this->publish(this->get_mode_state_topic(), mode_s))
 | 
			
		||||
@@ -343,6 +345,8 @@ bool MQTTClimateComponent::publish_state_() {
 | 
			
		||||
        case CLIMATE_PRESET_ACTIVITY:
 | 
			
		||||
          payload = "activity";
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          payload = "unknown";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (this->device_->custom_preset.has_value())
 | 
			
		||||
@@ -352,7 +356,7 @@ bool MQTTClimateComponent::publish_state_() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (traits.get_supports_action()) {
 | 
			
		||||
    const char *payload = "unknown";
 | 
			
		||||
    const char *payload;
 | 
			
		||||
    switch (this->device_->action) {
 | 
			
		||||
      case CLIMATE_ACTION_OFF:
 | 
			
		||||
        payload = "off";
 | 
			
		||||
@@ -372,6 +376,8 @@ bool MQTTClimateComponent::publish_state_() {
 | 
			
		||||
      case CLIMATE_ACTION_FAN:
 | 
			
		||||
        payload = "fan";
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        payload = "unknown";
 | 
			
		||||
    }
 | 
			
		||||
    if (!this->publish(this->get_action_state_topic(), payload))
 | 
			
		||||
      success = false;
 | 
			
		||||
@@ -411,6 +417,8 @@ bool MQTTClimateComponent::publish_state_() {
 | 
			
		||||
        case CLIMATE_FAN_QUIET:
 | 
			
		||||
          payload = "quiet";
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          payload = "unknown";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (this->device_->custom_fan_mode.has_value())
 | 
			
		||||
@@ -420,7 +428,7 @@ bool MQTTClimateComponent::publish_state_() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (traits.get_supports_swing_modes()) {
 | 
			
		||||
    const char *payload = "";
 | 
			
		||||
    const char *payload;
 | 
			
		||||
    switch (this->device_->swing_mode) {
 | 
			
		||||
      case CLIMATE_SWING_OFF:
 | 
			
		||||
        payload = "off";
 | 
			
		||||
@@ -434,6 +442,8 @@ bool MQTTClimateComponent::publish_state_() {
 | 
			
		||||
      case CLIMATE_SWING_HORIZONTAL:
 | 
			
		||||
        payload = "horizontal";
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        payload = "unknown";
 | 
			
		||||
    }
 | 
			
		||||
    if (!this->publish(this->get_swing_mode_state_topic(), payload))
 | 
			
		||||
      success = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,8 @@ bool MQTTSensorComponent::send_initial_state() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool MQTTSensorComponent::publish_state(float value) {
 | 
			
		||||
  if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value))
 | 
			
		||||
    return this->publish(this->get_state_topic_(), "None");
 | 
			
		||||
  int8_t accuracy = this->sensor_->get_accuracy_decimals();
 | 
			
		||||
  return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -116,7 +116,7 @@ struct IPAddress {
 | 
			
		||||
  operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  bool is_set() { return !ip_addr_isany(&ip_addr_); }
 | 
			
		||||
  bool is_set() { return !ip_addr_isany(&ip_addr_); }  // NOLINT(readability-simplify-boolean-expr)
 | 
			
		||||
  bool is_ip4() { return IP_IS_V4(&ip_addr_); }
 | 
			
		||||
  bool is_ip6() { return IP_IS_V6(&ip_addr_); }
 | 
			
		||||
  std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,3 +6,5 @@ Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
 | 
			
		||||
nextion_ref = Nextion.operator("ref")
 | 
			
		||||
 | 
			
		||||
CONF_NEXTION_ID = "nextion_id"
 | 
			
		||||
CONF_PUBLISH_STATE = "publish_state"
 | 
			
		||||
CONF_SEND_TO_NEXTION = "send_to_nextion"
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,13 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
 | 
			
		||||
class BufferOverflowTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit BufferOverflowTrigger(Nextion *nextion) {
 | 
			
		||||
    nextion->add_buffer_overflow_event_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SetupTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit SetupTrigger(Nextion *nextion) {
 | 
			
		||||
@@ -42,5 +49,74 @@ class TouchTrigger : public Trigger<uint8_t, uint8_t, bool> {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class NextionPublishFloatAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit NextionPublishFloatAction(NextionComponent *component) : component_(component) {}
 | 
			
		||||
 | 
			
		||||
  TEMPLATABLE_VALUE(float, state)
 | 
			
		||||
  TEMPLATABLE_VALUE(bool, publish_state)
 | 
			
		||||
  TEMPLATABLE_VALUE(bool, send_to_nextion)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...),
 | 
			
		||||
                                this->send_to_nextion_.value(x...));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_state(std::function<void(Ts..., float)> state) { this->state_ = state; }
 | 
			
		||||
  void set_publish_state(std::function<void(Ts..., bool)> publish_state) { this->publish_state_ = publish_state; }
 | 
			
		||||
  void set_send_to_nextion(std::function<void(Ts..., bool)> send_to_nextion) {
 | 
			
		||||
    this->send_to_nextion_ = send_to_nextion;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  NextionComponent *component_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class NextionPublishTextAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit NextionPublishTextAction(NextionComponent *component) : component_(component) {}
 | 
			
		||||
 | 
			
		||||
  TEMPLATABLE_VALUE(const char *, state)
 | 
			
		||||
  TEMPLATABLE_VALUE(bool, publish_state)
 | 
			
		||||
  TEMPLATABLE_VALUE(bool, send_to_nextion)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...),
 | 
			
		||||
                                this->send_to_nextion_.value(x...));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_state(std::function<void(Ts..., const char *)> state) { this->state_ = state; }
 | 
			
		||||
  void set_publish_state(std::function<void(Ts..., bool)> publish_state) { this->publish_state_ = publish_state; }
 | 
			
		||||
  void set_send_to_nextion(std::function<void(Ts..., bool)> send_to_nextion) {
 | 
			
		||||
    this->send_to_nextion_ = send_to_nextion;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  NextionComponent *component_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class NextionPublishBoolAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit NextionPublishBoolAction(NextionComponent *component) : component_(component) {}
 | 
			
		||||
 | 
			
		||||
  TEMPLATABLE_VALUE(bool, state)
 | 
			
		||||
  TEMPLATABLE_VALUE(bool, publish_state)
 | 
			
		||||
  TEMPLATABLE_VALUE(bool, send_to_nextion)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...),
 | 
			
		||||
                                this->send_to_nextion_.value(x...));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_state(std::function<void(Ts..., bool)> state) { this->state_ = state; }
 | 
			
		||||
  void set_publish_state(std::function<void(Ts..., bool)> publish_state) { this->publish_state_ = publish_state; }
 | 
			
		||||
  void set_send_to_nextion(std::function<void(Ts..., bool)> send_to_nextion) {
 | 
			
		||||
    this->send_to_nextion_ = send_to_nextion;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  NextionComponent *component_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user