mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			181 Commits
		
	
	
		
			2021.11.0b
			...
			2021.12.0b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8aaf9fd83f | ||
| 
						 | 
					08057720b8 | ||
| 
						 | 
					bfaa648837 | ||
| 
						 | 
					d504daef91 | ||
| 
						 | 
					b8d3ef2f49 | ||
| 
						 | 
					3bf6320030 | ||
| 
						 | 
					708b928c73 | ||
| 
						 | 
					649366ff44 | ||
| 
						 | 
					e5c9e87fad | ||
| 
						 | 
					f3d9d707b6 | ||
| 
						 | 
					090e10730c | ||
| 
						 | 
					fbc84861c7 | ||
| 
						 | 
					e763469af8 | ||
| 
						 | 
					3c0c514e44 | ||
| 
						 | 
					ed5e2dd332 | ||
| 
						 | 
					09b7c6f550 | ||
| 
						 | 
					df315a1f51 | ||
| 
						 | 
					7ee4bb621c | ||
| 
						 | 
					24874f4c3c | ||
| 
						 | 
					c128880033 | ||
| 
						 | 
					a66e94a0b0 | ||
| 
						 | 
					56870ed4a8 | ||
| 
						 | 
					3ac720df47 | ||
| 
						 | 
					1bc757ad06 | ||
| 
						 | 
					f72abc6f3d | ||
| 
						 | 
					5ac88de985 | ||
| 
						 | 
					0826b367d6 | ||
| 
						 | 
					329bf861d6 | ||
| 
						 | 
					9dcd3d18a0 | ||
| 
						 | 
					db66cd88b6 | ||
| 
						 | 
					86c205fe43 | ||
| 
						 | 
					c6414138c7 | ||
| 
						 | 
					36b355eb82 | ||
| 
						 | 
					9ca4e8f32a | ||
| 
						 | 
					1b88b7a166 | ||
| 
						 | 
					caf352ff06 | ||
| 
						 | 
					54106179a1 | ||
| 
						 | 
					607601b3a4 | ||
| 
						 | 
					f58828cb82 | ||
| 
						 | 
					11330af05f | ||
| 
						 | 
					fbe1bca1b9 | ||
| 
						 | 
					24a5325db3 | ||
| 
						 | 
					1ec3140759 | ||
| 
						 | 
					ca8db7696e | ||
| 
						 | 
					c9190574a9 | ||
| 
						 | 
					bfeb0b3639 | ||
| 
						 | 
					cbc1334b8d | ||
| 
						 | 
					08cbb97ec9 | ||
| 
						 | 
					5719cc1a24 | ||
| 
						 | 
					d9513e5ff2 | ||
| 
						 | 
					b5a0e8b2c0 | ||
| 
						 | 
					b32b918936 | ||
| 
						 | 
					0f47ffd908 | ||
| 
						 | 
					cd018ad3a5 | ||
| 
						 | 
					24dfecb6f0 | ||
| 
						 | 
					ab027a6ae2 | ||
| 
						 | 
					556d071e7f | ||
| 
						 | 
					939fb313df | ||
| 
						 | 
					b5639a6472 | ||
| 
						 | 
					f50e40e0b8 | ||
| 
						 | 
					6f07421911 | ||
| 
						 | 
					adf48246a9 | ||
| 
						 | 
					7be9291b13 | ||
| 
						 | 
					ea9e75039b | ||
| 
						 | 
					a5fb036011 | ||
| 
						 | 
					e55506f9db | ||
| 
						 | 
					50ec1d0445 | ||
| 
						 | 
					3d5e1d8d91 | ||
| 
						 | 
					db2128a344 | ||
| 
						 | 
					cae283dc86 | ||
| 
						 | 
					7afcb0fb04 | ||
| 
						 | 
					10f830c3ef | ||
| 
						 | 
					7a5c3aa7ed | ||
| 
						 | 
					2b50406856 | ||
| 
						 | 
					10a2a7e0fc | ||
| 
						 | 
					7a564b222d | ||
| 
						 | 
					21db43db06 | ||
| 
						 | 
					5009b3029f | ||
| 
						 | 
					57a029189c | ||
| 
						 | 
					671d68bc2c | ||
| 
						 | 
					5946c37925 | ||
| 
						 | 
					17a37b1de9 | ||
| 
						 | 
					e7827a6997 | ||
| 
						 | 
					2347e043a9 | ||
| 
						 | 
					00965fe19e | ||
| 
						 | 
					9681dfb458 | ||
| 
						 | 
					5e631bc6ba | ||
| 
						 | 
					b5f660398c | ||
| 
						 | 
					d50bdf619f | ||
| 
						 | 
					4e448b21ff | ||
| 
						 | 
					2a78c2970d | ||
| 
						 | 
					0cb715bb76 | ||
| 
						 | 
					7d03823afd | ||
| 
						 | 
					8e1c9f5042 | ||
| 
						 | 
					980b7cda8f | ||
| 
						 | 
					3a72dd5cb6 | ||
| 
						 | 
					3178243811 | ||
| 
						 | 
					d30e2f2a4f | ||
| 
						 | 
					3637be251e | ||
| 
						 | 
					2aea27d272 | ||
| 
						 | 
					ceb9b1d1ff | ||
| 
						 | 
					ccfa1e23f0 | ||
| 
						 | 
					290da8df2d | ||
| 
						 | 
					4b1d73791d | ||
| 
						 | 
					7e8012c1a0 | ||
| 
						 | 
					15cd602e8b | ||
| 
						 | 
					598f5b241f | ||
| 
						 | 
					335e69e6cd | ||
| 
						 | 
					05fe5db030 | ||
| 
						 | 
					710096b1c6 | ||
| 
						 | 
					07b882c801 | ||
| 
						 | 
					3e5331a263 | ||
| 
						 | 
					897277992b | ||
| 
						 | 
					1424091ee5 | ||
| 
						 | 
					61ec16cdfc | ||
| 
						 | 
					e5cb5756aa | ||
| 
						 | 
					9e1c3e8f01 | ||
| 
						 | 
					448e1690aa | ||
| 
						 | 
					8267f01ccd | ||
| 
						 | 
					6f9439e1bc | ||
| 
						 | 
					06994c0dfc | ||
| 
						 | 
					dee5d639e2 | ||
| 
						 | 
					df6730be55 | ||
| 
						 | 
					6226dae05c | ||
| 
						 | 
					9c6a475a6e | ||
| 
						 | 
					8294d10d5b | ||
| 
						 | 
					67558bec47 | ||
| 
						 | 
					6c1ef398bb | ||
| 
						 | 
					84873d4074 | ||
| 
						 | 
					58a0b28a39 | ||
| 
						 | 
					0469e19f54 | ||
| 
						 | 
					dbcfa7b599 | ||
| 
						 | 
					b37d3a66cc | ||
| 
						 | 
					7e495a5e27 | ||
| 
						 | 
					c41547fd4a | ||
| 
						 | 
					0d47d41c85 | ||
| 
						 | 
					df68403b6d | ||
| 
						 | 
					57bdc2b885 | ||
| 
						 | 
					f565ff5def | ||
| 
						 | 
					8ece639987 | ||
| 
						 | 
					b35f509784 | ||
| 
						 | 
					41a3a17456 | ||
| 
						 | 
					cbbafbcca2 | ||
| 
						 | 
					c75566b374 | ||
| 
						 | 
					f1954df573 | ||
| 
						 | 
					7279f1fcc1 | ||
| 
						 | 
					d7432f7c10 | ||
| 
						 | 
					b0a0a153f3 | ||
| 
						 | 
					9e4fa5dcf1 | ||
| 
						 | 
					024632dbd0 | ||
| 
						 | 
					0a545a28b9 | ||
| 
						 | 
					0f2df59998 | ||
| 
						 | 
					0809673ba9 | ||
| 
						 | 
					b386284180 | ||
| 
						 | 
					515519bc87 | ||
| 
						 | 
					5404163be0 | ||
| 
						 | 
					29a7d32f77 | ||
| 
						 | 
					687a7e9b2f | ||
| 
						 | 
					09e8782318 | ||
| 
						 | 
					0b193eee43 | ||
| 
						 | 
					7333123ba4 | ||
| 
						 | 
					d99c5ed890 | ||
| 
						 | 
					04740fbcbb | ||
| 
						 | 
					6a7440f7d3 | ||
| 
						 | 
					14299bb2cc | ||
| 
						 | 
					66cebfc992 | ||
| 
						 | 
					108b8e6705 | ||
| 
						 | 
					4eaa6afa4d | ||
| 
						 | 
					f643a46bbf | ||
| 
						 | 
					aae63a7ff3 | ||
| 
						 | 
					582567696e | ||
| 
						 | 
					2e0c89409d | ||
| 
						 | 
					7bb7456a8b | ||
| 
						 | 
					0372e12b81 | ||
| 
						 | 
					a6873c1520 | ||
| 
						 | 
					f11220da3a | ||
| 
						 | 
					bb9793d5b7 | ||
| 
						 | 
					e99af991ec | ||
| 
						 | 
					abf3708cc2 | ||
| 
						 | 
					4395d6156d | ||
| 
						 | 
					04ba53c870 | 
							
								
								
									
										15
									
								
								.clang-tidy
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								.clang-tidy
									
									
									
									
									
								
							@@ -5,11 +5,8 @@ Checks: >-
 | 
			
		||||
  -altera-*,
 | 
			
		||||
  -android-*,
 | 
			
		||||
  -boost-*,
 | 
			
		||||
  -bugprone-branch-clone,
 | 
			
		||||
  -bugprone-easily-swappable-parameters,
 | 
			
		||||
  -bugprone-narrowing-conversions,
 | 
			
		||||
  -bugprone-signed-char-misuse,
 | 
			
		||||
  -bugprone-too-small-loop-variable,
 | 
			
		||||
  -cert-dcl50-cpp,
 | 
			
		||||
  -cert-err58-cpp,
 | 
			
		||||
  -cert-oop57-cpp,
 | 
			
		||||
@@ -19,12 +16,10 @@ Checks: >-
 | 
			
		||||
  -clang-diagnostic-delete-abstract-non-virtual-dtor,
 | 
			
		||||
  -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
 | 
			
		||||
  -clang-diagnostic-shadow-field,
 | 
			
		||||
  -clang-diagnostic-sign-compare,
 | 
			
		||||
  -clang-diagnostic-unused-variable,
 | 
			
		||||
  -clang-diagnostic-unused-const-variable,
 | 
			
		||||
  -clang-diagnostic-unused-parameter,
 | 
			
		||||
  -concurrency-*,
 | 
			
		||||
  -cppcoreguidelines-avoid-c-arrays,
 | 
			
		||||
  -cppcoreguidelines-avoid-goto,
 | 
			
		||||
  -cppcoreguidelines-avoid-magic-numbers,
 | 
			
		||||
  -cppcoreguidelines-init-variables,
 | 
			
		||||
  -cppcoreguidelines-macro-usage,
 | 
			
		||||
@@ -41,7 +36,6 @@ Checks: >-
 | 
			
		||||
  -cppcoreguidelines-pro-type-union-access,
 | 
			
		||||
  -cppcoreguidelines-pro-type-vararg,
 | 
			
		||||
  -cppcoreguidelines-special-member-functions,
 | 
			
		||||
  -fuchsia-default-arguments,
 | 
			
		||||
  -fuchsia-multiple-inheritance,
 | 
			
		||||
  -fuchsia-overloaded-operator,
 | 
			
		||||
  -fuchsia-statically-constructed-objects,
 | 
			
		||||
@@ -51,6 +45,7 @@ Checks: >-
 | 
			
		||||
  -google-explicit-constructor,
 | 
			
		||||
  -google-readability-braces-around-statements,
 | 
			
		||||
  -google-readability-casting,
 | 
			
		||||
  -google-readability-namespace-comments,
 | 
			
		||||
  -google-readability-todo,
 | 
			
		||||
  -google-runtime-references,
 | 
			
		||||
  -hicpp-*,
 | 
			
		||||
@@ -97,9 +92,11 @@ CheckOptions:
 | 
			
		||||
    value:           '1'
 | 
			
		||||
  - key:             google-readability-function-size.StatementThreshold
 | 
			
		||||
    value:           '800'
 | 
			
		||||
  - key:             google-readability-namespace-comments.ShortNamespaceLines
 | 
			
		||||
  - key:             google-runtime-int.TypeSuffix
 | 
			
		||||
    value:           '_t'
 | 
			
		||||
  - key:             llvm-namespace-comment.ShortNamespaceLines
 | 
			
		||||
    value:           '10'
 | 
			
		||||
  - key:             google-readability-namespace-comments.SpacesBeforeComments
 | 
			
		||||
  - key:             llvm-namespace-comment.SpacesBeforeComments
 | 
			
		||||
    value:           '2'
 | 
			
		||||
  - key:             modernize-loop-convert.MaxCopySize
 | 
			
		||||
    value:           '16'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,3 @@
 | 
			
		||||
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
 | 
			
		||||
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
 | 
			
		||||
name: CI
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
@@ -11,6 +9,10 @@ on:
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
 | 
			
		||||
  cancel-in-progress: true
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  ci:
 | 
			
		||||
    name: ${{ matrix.name }}
 | 
			
		||||
@@ -34,7 +36,7 @@ jobs:
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test3.yaml
 | 
			
		||||
            name: Test tests/test3.yaml
 | 
			
		||||
            pio_cache_key: test1
 | 
			
		||||
            pio_cache_key: test3
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test4.yaml
 | 
			
		||||
            name: Test tests/test4.yaml
 | 
			
		||||
@@ -80,18 +82,23 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
      - name: Cache virtualenv
 | 
			
		||||
        uses: actions/cache@v2
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
 | 
			
		||||
          path: .venv
 | 
			
		||||
          key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            pip-${{ steps.python.outputs.python-version }}-
 | 
			
		||||
            venv-${{ steps.python.outputs.python-version }}-
 | 
			
		||||
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
      - name: Set up virtualenv
 | 
			
		||||
        run: |
 | 
			
		||||
          pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
			
		||||
          pip3 install -e .
 | 
			
		||||
          python -m venv .venv
 | 
			
		||||
          source .venv/bin/activate
 | 
			
		||||
          pip install -U pip
 | 
			
		||||
          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
			
		||||
          pip install -e .
 | 
			
		||||
          echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
 | 
			
		||||
          echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
      # Use per check platformio cache because checks use different parts
 | 
			
		||||
      - name: Cache platformio
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
								
							@@ -4,7 +4,7 @@
 | 
			
		||||
            "owner": "ci-custom",
 | 
			
		||||
            "pattern": [
 | 
			
		||||
                {
 | 
			
		||||
                    "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
 | 
			
		||||
                    "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$",
 | 
			
		||||
                    "file": 1,
 | 
			
		||||
                    "line": 2,
 | 
			
		||||
                    "column": 3,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							@@ -5,7 +5,7 @@
 | 
			
		||||
      "severity": "error",
 | 
			
		||||
      "pattern": [
 | 
			
		||||
        {
 | 
			
		||||
          "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
 | 
			
		||||
          "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
 | 
			
		||||
          "file": 1,
 | 
			
		||||
          "line": 2,
 | 
			
		||||
          "column": 3,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,22 @@
 | 
			
		||||
{
 | 
			
		||||
  "problemMatcher": [
 | 
			
		||||
    {
 | 
			
		||||
      "owner": "black",
 | 
			
		||||
      "severity": "error",
 | 
			
		||||
      "pattern": [
 | 
			
		||||
        {
 | 
			
		||||
          "regexp": "^(.*): (Please format this file with the black formatter)",
 | 
			
		||||
          "file": 1,
 | 
			
		||||
          "message": 2
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "owner": "flake8",
 | 
			
		||||
      "severity": "error",
 | 
			
		||||
      "pattern": [
 | 
			
		||||
          {
 | 
			
		||||
          "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
 | 
			
		||||
          "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
 | 
			
		||||
          "file": 1,
 | 
			
		||||
          "line": 2,
 | 
			
		||||
          "message": 3
 | 
			
		||||
@@ -17,7 +28,7 @@
 | 
			
		||||
      "severity": "error",
 | 
			
		||||
      "pattern": [
 | 
			
		||||
        {
 | 
			
		||||
          "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
 | 
			
		||||
          "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
 | 
			
		||||
          "file": 1,
 | 
			
		||||
          "line": 2,
 | 
			
		||||
          "message": 3
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@ esphome/components/bang_bang/* @OttoWinter
 | 
			
		||||
esphome/components/binary_sensor/* @esphome/core
 | 
			
		||||
esphome/components/ble_client/* @buxtronix
 | 
			
		||||
esphome/components/bme680_bsec/* @trvrnrth
 | 
			
		||||
esphome/components/button/* @esphome/core
 | 
			
		||||
esphome/components/canbus/* @danielschramm @mvturnho
 | 
			
		||||
esphome/components/cap1188/* @MrEditor97
 | 
			
		||||
esphome/components/captive_portal/* @OttoWinter
 | 
			
		||||
@@ -72,7 +73,6 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
			
		||||
esphome/components/homeassistant/* @OttoWinter
 | 
			
		||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
			
		||||
esphome/components/i2c/* @esphome/core
 | 
			
		||||
esphome/components/improv/* @jesserockz
 | 
			
		||||
esphome/components/improv_serial/* @esphome/core
 | 
			
		||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
 | 
			
		||||
esphome/components/inkplate6/* @jesserockz
 | 
			
		||||
@@ -181,6 +181,7 @@ esphome/components/tuya/binary_sensor/* @jesserockz
 | 
			
		||||
esphome/components/tuya/climate/* @jesserockz
 | 
			
		||||
esphome/components/tuya/sensor/* @jesserockz
 | 
			
		||||
esphome/components/tuya/switch/* @jesserockz
 | 
			
		||||
esphome/components/tuya/text_sensor/* @dentra
 | 
			
		||||
esphome/components/uart/* @esphome/core
 | 
			
		||||
esphome/components/ultrasonic/* @OttoWinter
 | 
			
		||||
esphome/components/version/* @esphome/core
 | 
			
		||||
 
 | 
			
		||||
@@ -147,9 +147,9 @@ RUN \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
        /var/lib/apt/lists/*
 | 
			
		||||
 | 
			
		||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
 | 
			
		||||
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
 | 
			
		||||
RUN \
 | 
			
		||||
    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
 | 
			
		||||
    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini
 | 
			
		||||
 | 
			
		||||
VOLUME ["/esphome"]
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_PORT,
 | 
			
		||||
    CONF_ESPHOME,
 | 
			
		||||
    CONF_PLATFORMIO_OPTIONS,
 | 
			
		||||
    SECRETS_FILES,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, EsphomeError, coroutine
 | 
			
		||||
from esphome.helpers import indent
 | 
			
		||||
@@ -200,8 +201,7 @@ def upload_using_esptool(config, port):
 | 
			
		||||
        firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
 | 
			
		||||
        flash_images = [
 | 
			
		||||
            platformio_api.FlashImage(
 | 
			
		||||
                path=idedata.firmware_bin_path,
 | 
			
		||||
                offset=firmware_offset,
 | 
			
		||||
                path=idedata.firmware_bin_path, offset=firmware_offset
 | 
			
		||||
            ),
 | 
			
		||||
            *idedata.extra_flash_images,
 | 
			
		||||
        ]
 | 
			
		||||
@@ -607,10 +607,7 @@ def parse_args(argv):
 | 
			
		||||
        "wizard",
 | 
			
		||||
        help="A helpful setup wizard that will guide you through setting up ESPHome.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_wizard.add_argument(
 | 
			
		||||
        "configuration",
 | 
			
		||||
        help="Your YAML configuration file.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
 | 
			
		||||
 | 
			
		||||
    parser_fingerprint = subparsers.add_parser(
 | 
			
		||||
        "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
 | 
			
		||||
@@ -632,8 +629,7 @@ def parse_args(argv):
 | 
			
		||||
        "dashboard", help="Create a simple web server for a dashboard."
 | 
			
		||||
    )
 | 
			
		||||
    parser_dashboard.add_argument(
 | 
			
		||||
        "configuration",
 | 
			
		||||
        help="Your YAML configuration file directory.",
 | 
			
		||||
        "configuration", help="Your YAML configuration file directory."
 | 
			
		||||
    )
 | 
			
		||||
    parser_dashboard.add_argument(
 | 
			
		||||
        "--port",
 | 
			
		||||
@@ -641,6 +637,12 @@ def parse_args(argv):
 | 
			
		||||
        type=int,
 | 
			
		||||
        default=6052,
 | 
			
		||||
    )
 | 
			
		||||
    parser_dashboard.add_argument(
 | 
			
		||||
        "--address",
 | 
			
		||||
        help="The address to bind to.",
 | 
			
		||||
        type=str,
 | 
			
		||||
        default="0.0.0.0",
 | 
			
		||||
    )
 | 
			
		||||
    parser_dashboard.add_argument(
 | 
			
		||||
        "--username",
 | 
			
		||||
        help="The optional username to require for authentication.",
 | 
			
		||||
@@ -789,6 +791,10 @@ def run_esphome(argv):
 | 
			
		||||
            return 1
 | 
			
		||||
 | 
			
		||||
    for conf_path in args.configuration:
 | 
			
		||||
        if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
 | 
			
		||||
            _LOGGER.warning("Skipping secrets file %s", conf_path)
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        CORE.config_path = conf_path
 | 
			
		||||
        CORE.dashboard = args.dashboard
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ void AdalightLightEffect::stop() {
 | 
			
		||||
  AddressableLightEffect::stop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int AdalightLightEffect::get_frame_size_(int led_count) const {
 | 
			
		||||
unsigned int AdalightLightEffect::get_frame_size_(int led_count) const {
 | 
			
		||||
  // 3 bytes: Ada
 | 
			
		||||
  // 2 bytes: LED count
 | 
			
		||||
  // 1 byte: checksum
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
 | 
			
		||||
    CONSUMED,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  int get_frame_size_(int led_count) const;
 | 
			
		||||
  unsigned int get_frame_size_(int led_count) const;
 | 
			
		||||
  void reset_frame_(light::AddressableLight &it);
 | 
			
		||||
  void blank_all_leds_(light::AddressableLight &it);
 | 
			
		||||
  Frame parse_frame_(light::AddressableLight &it);
 | 
			
		||||
 
 | 
			
		||||
@@ -91,7 +91,7 @@ void ADCSensor::dump_config() {
 | 
			
		||||
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
void ADCSensor::update() {
 | 
			
		||||
  float value_v = this->sample();
 | 
			
		||||
  ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
 | 
			
		||||
  ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
 | 
			
		||||
  this->publish_state(value_v);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -73,13 +73,6 @@ void AHT10Component::update() {
 | 
			
		||||
  bool success = false;
 | 
			
		||||
  for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
 | 
			
		||||
    ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
 | 
			
		||||
    delayMicroseconds(4);
 | 
			
		||||
 | 
			
		||||
    uint8_t reg = 0;
 | 
			
		||||
    if (this->write(®, 1) != i2c::ERROR_OK) {
 | 
			
		||||
      ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    delay(delay_ms);
 | 
			
		||||
    if (this->read(data, 6) != i2c::ERROR_OK) {
 | 
			
		||||
      ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
 | 
			
		||||
@@ -117,12 +110,12 @@ void AHT10Component::update() {
 | 
			
		||||
  uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
 | 
			
		||||
  uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
 | 
			
		||||
 | 
			
		||||
  float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0;
 | 
			
		||||
  float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
 | 
			
		||||
  float humidity;
 | 
			
		||||
  if (raw_humidity == 0) {  // unrealistic value
 | 
			
		||||
    humidity = NAN;
 | 
			
		||||
  } else {
 | 
			
		||||
    humidity = (float) raw_humidity * 100.0 / 1048576.0;
 | 
			
		||||
    humidity = (float) raw_humidity * 100.0f / 1048576.0f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->temperature_sensor_ != nullptr) {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,9 +38,9 @@ void AM2320Component::update() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0;
 | 
			
		||||
  float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f;
 | 
			
		||||
  temperature = (data[4] & 0x80) ? -temperature : temperature;
 | 
			
		||||
  float humidity = ((data[2] << 8) + data[3]) / 10.0;
 | 
			
		||||
  float humidity = ((data[2] << 8) + data[3]) / 10.0f;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity);
 | 
			
		||||
  if (this->temperature_sensor_ != nullptr)
 | 
			
		||||
 
 | 
			
		||||
@@ -44,8 +44,9 @@ async def to_code(config):
 | 
			
		||||
    width, height = image.size
 | 
			
		||||
    frames = image.n_frames
 | 
			
		||||
    if CONF_RESIZE in config:
 | 
			
		||||
        image.thumbnail(config[CONF_RESIZE])
 | 
			
		||||
        width, height = image.size
 | 
			
		||||
        new_width_max, new_height_max = config[CONF_RESIZE]
 | 
			
		||||
        ratio = min(new_width_max / width, new_height_max / height)
 | 
			
		||||
        width, height = int(width * ratio), int(height * ratio)
 | 
			
		||||
    else:
 | 
			
		||||
        if width > 500 or height > 500:
 | 
			
		||||
            _LOGGER.warning(
 | 
			
		||||
@@ -59,7 +60,13 @@ async def to_code(config):
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("L", dither=Image.NONE)
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                frame = frame.resize([width, height])
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
            if len(pixels) != height * width:
 | 
			
		||||
                raise core.EsphomeError(
 | 
			
		||||
                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
			
		||||
                )
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix
 | 
			
		||||
                pos += 1
 | 
			
		||||
@@ -69,8 +76,16 @@ async def to_code(config):
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                image.thumbnail(config[CONF_RESIZE])
 | 
			
		||||
            frame = image.convert("RGB")
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                frame = frame.resize([width, height])
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
            if len(pixels) != height * width:
 | 
			
		||||
                raise core.EsphomeError(
 | 
			
		||||
                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
			
		||||
                )
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix[0]
 | 
			
		||||
                pos += 1
 | 
			
		||||
@@ -85,6 +100,8 @@ async def to_code(config):
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("1", dither=Image.NONE)
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                frame = frame.resize([width, height])
 | 
			
		||||
            for y in range(height):
 | 
			
		||||
                for x in range(width):
 | 
			
		||||
                    if frame.getpixel((x, y)):
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
 | 
			
		||||
  climate::ClimateTraits traits() override {
 | 
			
		||||
    auto traits = climate::ClimateTraits();
 | 
			
		||||
    traits.set_supports_current_temperature(true);
 | 
			
		||||
    traits.set_supports_heat_mode(true);
 | 
			
		||||
    traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
 | 
			
		||||
    traits.set_visual_min_temperature(25.0);
 | 
			
		||||
    traits.set_visual_max_temperature(100.0);
 | 
			
		||||
    traits.set_visual_temperature_step(0.1);
 | 
			
		||||
 
 | 
			
		||||
@@ -73,51 +73,46 @@ AnovaPacket *AnovaCodec::get_stop_request() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
 | 
			
		||||
  memset(this->buf_, 0, 32);
 | 
			
		||||
  strncpy(this->buf_, (char *) data, length);
 | 
			
		||||
  char buf[32];
 | 
			
		||||
  memset(buf, 0, sizeof(buf));
 | 
			
		||||
  strncpy(buf, (char *) data, std::min<uint16_t>(length, sizeof(buf) - 1));
 | 
			
		||||
  this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
 | 
			
		||||
  switch (this->current_query_) {
 | 
			
		||||
    case READ_DEVICE_STATUS: {
 | 
			
		||||
      if (!strncmp(this->buf_, "stopped", 7)) {
 | 
			
		||||
      if (!strncmp(buf, "stopped", 7)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = false;
 | 
			
		||||
      }
 | 
			
		||||
      if (!strncmp(this->buf_, "running", 7)) {
 | 
			
		||||
      if (!strncmp(buf, "running", 7)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case START: {
 | 
			
		||||
      if (!strncmp(this->buf_, "start", 5)) {
 | 
			
		||||
      if (!strncmp(buf, "start", 5)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case STOP: {
 | 
			
		||||
      if (!strncmp(this->buf_, "stop", 4)) {
 | 
			
		||||
      if (!strncmp(buf, "stop", 4)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = false;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case READ_TARGET_TEMPERATURE: {
 | 
			
		||||
      this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
 | 
			
		||||
      if (this->fahrenheit_)
 | 
			
		||||
        this->target_temp_ = ftoc(this->target_temp_);
 | 
			
		||||
      this->has_target_temp_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case READ_TARGET_TEMPERATURE:
 | 
			
		||||
    case SET_TARGET_TEMPERATURE: {
 | 
			
		||||
      this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
 | 
			
		||||
      this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
 | 
			
		||||
      if (this->fahrenheit_)
 | 
			
		||||
        this->target_temp_ = ftoc(this->target_temp_);
 | 
			
		||||
      this->has_target_temp_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case READ_CURRENT_TEMPERATURE: {
 | 
			
		||||
      this->current_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
 | 
			
		||||
      this->current_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
 | 
			
		||||
      if (this->fahrenheit_)
 | 
			
		||||
        this->current_temp_ = ftoc(this->current_temp_);
 | 
			
		||||
      this->has_current_temp_ = true;
 | 
			
		||||
@@ -125,8 +120,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
 | 
			
		||||
    }
 | 
			
		||||
    case SET_UNIT:
 | 
			
		||||
    case READ_UNIT: {
 | 
			
		||||
      this->unit_ = this->buf_[0];
 | 
			
		||||
      this->fahrenheit_ = this->buf_[0] == 'f';
 | 
			
		||||
      this->unit_ = buf[0];
 | 
			
		||||
      this->fahrenheit_ = buf[0] == 'f';
 | 
			
		||||
      this->has_unit_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,6 @@ class AnovaCodec {
 | 
			
		||||
  bool has_current_temp_;
 | 
			
		||||
  bool has_unit_;
 | 
			
		||||
  bool has_running_;
 | 
			
		||||
  char buf_[32];
 | 
			
		||||
  bool fahrenheit_;
 | 
			
		||||
 | 
			
		||||
  CurrentQuery current_query_;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ service APIConnection {
 | 
			
		||||
  rpc climate_command (ClimateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc number_command (NumberCommandRequest) returns (void) {}
 | 
			
		||||
  rpc select_command (SelectCommandRequest) returns (void) {}
 | 
			
		||||
  rpc button_command (ButtonCommandRequest) returns (void) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -868,6 +869,11 @@ message ClimateCommandRequest {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== NUMBER ====================
 | 
			
		||||
enum NumberMode {
 | 
			
		||||
  NUMBER_MODE_AUTO = 0;
 | 
			
		||||
  NUMBER_MODE_BOX = 1;
 | 
			
		||||
  NUMBER_MODE_SLIDER = 2;
 | 
			
		||||
}
 | 
			
		||||
message ListEntitiesNumberResponse {
 | 
			
		||||
  option (id) = 49;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
@@ -884,6 +890,8 @@ message ListEntitiesNumberResponse {
 | 
			
		||||
  float step = 8;
 | 
			
		||||
  bool disabled_by_default = 9;
 | 
			
		||||
  EntityCategory entity_category = 10;
 | 
			
		||||
  string unit_of_measurement = 11;
 | 
			
		||||
  NumberMode mode = 12;
 | 
			
		||||
}
 | 
			
		||||
message NumberStateResponse {
 | 
			
		||||
  option (id) = 50;
 | 
			
		||||
@@ -944,3 +952,28 @@ message SelectCommandRequest {
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  string state = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== BUTTON ====================
 | 
			
		||||
message ListEntitiesButtonResponse {
 | 
			
		||||
  option (id) = 61;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BUTTON";
 | 
			
		||||
 | 
			
		||||
  string object_id = 1;
 | 
			
		||||
  fixed32 key = 2;
 | 
			
		||||
  string name = 3;
 | 
			
		||||
  string unique_id = 4;
 | 
			
		||||
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  bool disabled_by_default = 6;
 | 
			
		||||
  EntityCategory entity_category = 7;
 | 
			
		||||
  string device_class = 8;
 | 
			
		||||
}
 | 
			
		||||
message ButtonCommandRequest {
 | 
			
		||||
  option (id) = 62;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BUTTON";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -132,7 +132,7 @@ void APIConnection::loop() {
 | 
			
		||||
 | 
			
		||||
  if (state_subs_at_ != -1) {
 | 
			
		||||
    const auto &subs = this->parent_->get_state_subs();
 | 
			
		||||
    if (state_subs_at_ >= subs.size()) {
 | 
			
		||||
    if (state_subs_at_ >= (int) subs.size()) {
 | 
			
		||||
      state_subs_at_ = -1;
 | 
			
		||||
    } else {
 | 
			
		||||
      auto &it = subs[state_subs_at_];
 | 
			
		||||
@@ -619,6 +619,8 @@ bool APIConnection::send_number_info(number::Number *number) {
 | 
			
		||||
  msg.icon = number->get_icon();
 | 
			
		||||
  msg.disabled_by_default = number->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
 | 
			
		||||
  msg.unit_of_measurement = number->traits.get_unit_of_measurement();
 | 
			
		||||
  msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
 | 
			
		||||
 | 
			
		||||
  msg.min_value = number->traits.get_min_value();
 | 
			
		||||
  msg.max_value = number->traits.get_max_value();
 | 
			
		||||
@@ -674,6 +676,28 @@ void APIConnection::select_command(const SelectCommandRequest &msg) {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
bool APIConnection::send_button_info(button::Button *button) {
 | 
			
		||||
  ListEntitiesButtonResponse msg;
 | 
			
		||||
  msg.key = button->get_object_id_hash();
 | 
			
		||||
  msg.object_id = button->get_object_id();
 | 
			
		||||
  msg.name = button->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("button", button);
 | 
			
		||||
  msg.icon = button->get_icon();
 | 
			
		||||
  msg.disabled_by_default = button->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category());
 | 
			
		||||
  msg.device_class = button->get_device_class();
 | 
			
		||||
  return this->send_list_entities_button_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::button_command(const ButtonCommandRequest &msg) {
 | 
			
		||||
  button::Button *button = App.get_button_by_key(msg.key);
 | 
			
		||||
  if (button == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  button->press();
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
 | 
			
		||||
  if (!this->state_subscription_)
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  bool send_select_state(select::Select *select, std::string state);
 | 
			
		||||
  bool send_select_info(select::Select *select);
 | 
			
		||||
  void select_command(const SelectCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  bool send_button_info(button::Button *button);
 | 
			
		||||
  void button_command(const ButtonCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
  bool send_log_message(int level, const char *tag, const char *line);
 | 
			
		||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
			
		||||
 
 | 
			
		||||
@@ -174,9 +174,6 @@ APIError APINoiseFrameHelper::loop() {
 | 
			
		||||
 * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
 | 
			
		||||
 */
 | 
			
		||||
APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
			
		||||
  int err;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  if (frame == nullptr) {
 | 
			
		||||
    HELPER_LOG("Bad argument for try_read_frame_");
 | 
			
		||||
    return APIError::BAD_ARG;
 | 
			
		||||
@@ -200,7 +197,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
			
		||||
      return APIError::CONNECTION_CLOSED;
 | 
			
		||||
    }
 | 
			
		||||
    rx_header_buf_len_ += received;
 | 
			
		||||
    if (received != to_read) {
 | 
			
		||||
    if ((size_t) received != to_read) {
 | 
			
		||||
      // not a full read
 | 
			
		||||
      return APIError::WOULD_BLOCK;
 | 
			
		||||
    }
 | 
			
		||||
@@ -247,7 +244,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
			
		||||
      return APIError::CONNECTION_CLOSED;
 | 
			
		||||
    }
 | 
			
		||||
    rx_buf_len_ += received;
 | 
			
		||||
    if (received != to_read) {
 | 
			
		||||
    if ((size_t) received != to_read) {
 | 
			
		||||
      // not all read
 | 
			
		||||
      return APIError::WOULD_BLOCK;
 | 
			
		||||
    }
 | 
			
		||||
@@ -544,7 +541,6 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
 | 
			
		||||
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
  if (iovcnt == 0)
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  int err;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  size_t total_write_len = 0;
 | 
			
		||||
@@ -584,7 +580,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
    state_ = State::FAILED;
 | 
			
		||||
    HELPER_LOG("Socket write failed with errno %d", errno);
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;
 | 
			
		||||
  } else if (sent != total_write_len) {
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
@@ -778,9 +774,6 @@ APIError APIPlaintextFrameHelper::loop() {
 | 
			
		||||
 * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
 | 
			
		||||
 */
 | 
			
		||||
APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
			
		||||
  int err;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  if (frame == nullptr) {
 | 
			
		||||
    HELPER_LOG("Bad argument for try_read_frame_");
 | 
			
		||||
    return APIError::BAD_ARG;
 | 
			
		||||
@@ -854,7 +847,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
			
		||||
      return APIError::CONNECTION_CLOSED;
 | 
			
		||||
    }
 | 
			
		||||
    rx_buf_len_ += received;
 | 
			
		||||
    if (received != to_read) {
 | 
			
		||||
    if ((size_t) received != to_read) {
 | 
			
		||||
      // not all read
 | 
			
		||||
      return APIError::WOULD_BLOCK;
 | 
			
		||||
    }
 | 
			
		||||
@@ -874,7 +867,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
			
		||||
  int err;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  if (state_ != State::DATA) {
 | 
			
		||||
@@ -894,9 +886,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
			
		||||
}
 | 
			
		||||
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
 | 
			
		||||
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
 | 
			
		||||
  int err;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  if (state_ != State::DATA) {
 | 
			
		||||
    return APIError::BAD_STATE;
 | 
			
		||||
  }
 | 
			
		||||
@@ -940,7 +929,6 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
 | 
			
		||||
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
  if (iovcnt == 0)
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  int err;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  size_t total_write_len = 0;
 | 
			
		||||
@@ -980,7 +968,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
 | 
			
		||||
    state_ = State::FAILED;
 | 
			
		||||
    HELPER_LOG("Socket write failed with errno %d", errno);
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;
 | 
			
		||||
  } else if (sent != total_write_len) {
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
 
 | 
			
		||||
@@ -266,6 +266,18 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::NUMBER_MODE_AUTO:
 | 
			
		||||
      return "NUMBER_MODE_AUTO";
 | 
			
		||||
    case enums::NUMBER_MODE_BOX:
 | 
			
		||||
      return "NUMBER_MODE_BOX";
 | 
			
		||||
    case enums::NUMBER_MODE_SLIDER:
 | 
			
		||||
      return "NUMBER_MODE_SLIDER";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -279,7 +291,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
 | 
			
		||||
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void HelloRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("HelloRequest {\n");
 | 
			
		||||
  out.append("  client_info: ");
 | 
			
		||||
  out.append("'").append(this->client_info).append("'");
 | 
			
		||||
@@ -318,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void HelloResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("HelloResponse {\n");
 | 
			
		||||
  out.append("  api_version_major: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->api_version_major);
 | 
			
		||||
@@ -349,7 +361,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value
 | 
			
		||||
void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); }
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ConnectRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ConnectRequest {\n");
 | 
			
		||||
  out.append("  password: ");
 | 
			
		||||
  out.append("'").append(this->password).append("'");
 | 
			
		||||
@@ -370,7 +382,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ConnectResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ConnectResponse {\n");
 | 
			
		||||
  out.append("  invalid_password: ");
 | 
			
		||||
  out.append(YESNO(this->invalid_password));
 | 
			
		||||
@@ -464,7 +476,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("DeviceInfoResponse {\n");
 | 
			
		||||
  out.append("  uses_password: ");
 | 
			
		||||
  out.append(YESNO(this->uses_password));
 | 
			
		||||
@@ -588,7 +600,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesBinarySensorResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -660,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BinarySensorStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BinarySensorStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -754,7 +766,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesCoverResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -844,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void CoverStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("CoverStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -927,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void CoverCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("CoverCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -1043,7 +1055,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesFanResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -1139,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void FanStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("FanStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -1240,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void FanCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("FanCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -1391,7 +1403,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesLightResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesLightResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -1549,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void LightStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("LightStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -1772,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void LightCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("LightCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -1983,7 +1995,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesSensorResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -2072,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SensorStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SensorStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -2152,7 +2164,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesSwitchResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -2215,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SwitchStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SwitchStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -2254,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SwitchCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SwitchCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -2324,7 +2336,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesTextSensorResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -2394,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void TextSensorStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("TextSensorStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -2431,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SubscribeLogsRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SubscribeLogsRequest {\n");
 | 
			
		||||
  out.append("  level: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::LogLevel>(this->level));
 | 
			
		||||
@@ -2474,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SubscribeLogsResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SubscribeLogsResponse {\n");
 | 
			
		||||
  out.append("  level: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::LogLevel>(this->level));
 | 
			
		||||
@@ -2516,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void HomeassistantServiceMap::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("HomeassistantServiceMap {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  out.append("'").append(this->key).append("'");
 | 
			
		||||
@@ -2575,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void HomeassistantServiceResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("HomeassistantServiceResponse {\n");
 | 
			
		||||
  out.append("  service: ");
 | 
			
		||||
  out.append("'").append(this->service).append("'");
 | 
			
		||||
@@ -2631,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SubscribeHomeAssistantStateResponse {\n");
 | 
			
		||||
  out.append("  entity_id: ");
 | 
			
		||||
  out.append("'").append(this->entity_id).append("'");
 | 
			
		||||
@@ -2668,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void HomeAssistantStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("HomeAssistantStateResponse {\n");
 | 
			
		||||
  out.append("  entity_id: ");
 | 
			
		||||
  out.append("'").append(this->entity_id).append("'");
 | 
			
		||||
@@ -2701,7 +2713,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void GetTimeResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("GetTimeResponse {\n");
 | 
			
		||||
  out.append("  epoch_seconds: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->epoch_seconds);
 | 
			
		||||
@@ -2736,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesServicesArgument {\n");
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
@@ -2781,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesServicesResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesServicesResponse {\n");
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
@@ -2875,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ExecuteServiceArgument::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ExecuteServiceArgument {\n");
 | 
			
		||||
  out.append("  bool_: ");
 | 
			
		||||
  out.append(YESNO(this->bool_));
 | 
			
		||||
@@ -2956,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ExecuteServiceRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ExecuteServiceRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -3028,7 +3040,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesCameraResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -3098,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void CameraImageResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("CameraImageResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -3135,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void CameraImageRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("CameraImageRequest {\n");
 | 
			
		||||
  out.append("  single: ");
 | 
			
		||||
  out.append(YESNO(this->single));
 | 
			
		||||
@@ -3281,7 +3293,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesClimateResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -3468,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ClimateStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ClimateStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -3656,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ClimateCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ClimateCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -3758,6 +3770,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 12: {
 | 
			
		||||
      this->mode = value.as_enum<enums::NumberMode>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -3780,6 +3796,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel
 | 
			
		||||
      this->icon = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->unit_of_measurement = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -3817,10 +3837,12 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_float(8, this->step);
 | 
			
		||||
  buffer.encode_bool(9, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
 | 
			
		||||
  buffer.encode_string(11, this->unit_of_measurement);
 | 
			
		||||
  buffer.encode_enum<enums::NumberMode>(12, this->mode);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesNumberResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -3865,6 +3887,14 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  unit_of_measurement: ");
 | 
			
		||||
  out.append("'").append(this->unit_of_measurement).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  mode: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::NumberMode>(this->mode));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -3899,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void NumberStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("NumberStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -3937,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void NumberCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("NumberCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -4015,7 +4045,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesSelectResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
@@ -4091,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SelectStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SelectStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -4134,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SelectCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SelectCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
@@ -4147,6 +4177,127 @@ void SelectCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->object_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->name = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->unique_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->icon = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->device_class = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(1, this->object_id);
 | 
			
		||||
  buffer.encode_fixed32(2, this->key);
 | 
			
		||||
  buffer.encode_string(3, this->name);
 | 
			
		||||
  buffer.encode_string(4, this->unique_id);
 | 
			
		||||
  buffer.encode_string(5, this->icon);
 | 
			
		||||
  buffer.encode_bool(6, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
 | 
			
		||||
  buffer.encode_string(8, this->device_class);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesButtonResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  unique_id: ");
 | 
			
		||||
  out.append("'").append(this->unique_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  device_class: ");
 | 
			
		||||
  out.append("'").append(this->device_class).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ButtonCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  out.append("ButtonCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -123,6 +123,11 @@ enum ClimatePreset : uint32_t {
 | 
			
		||||
  CLIMATE_PRESET_SLEEP = 6,
 | 
			
		||||
  CLIMATE_PRESET_ACTIVITY = 7,
 | 
			
		||||
};
 | 
			
		||||
enum NumberMode : uint32_t {
 | 
			
		||||
  NUMBER_MODE_AUTO = 0,
 | 
			
		||||
  NUMBER_MODE_BOX = 1,
 | 
			
		||||
  NUMBER_MODE_SLIDER = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace enums
 | 
			
		||||
 | 
			
		||||
@@ -957,6 +962,8 @@ class ListEntitiesNumberResponse : public ProtoMessage {
 | 
			
		||||
  float step{0.0f};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  std::string unit_of_measurement{};
 | 
			
		||||
  enums::NumberMode mode{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -1041,6 +1048,37 @@ class SelectCommandRequest : public ProtoMessage {
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
};
 | 
			
		||||
class ListEntitiesButtonResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string object_id{};
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  std::string name{};
 | 
			
		||||
  std::string unique_id{};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  std::string device_class{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class ButtonCommandRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<ListEntitiesButtonResponse>(msg, 61);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
			
		||||
  switch (msg_type) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -513,6 +523,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_select_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 62: {
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
      ButtonCommandRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_button_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest &
 | 
			
		||||
  this->select_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->button_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  virtual void on_select_command_request(const SelectCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  virtual void on_button_command_request(const ButtonCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
			
		||||
@@ -171,6 +177,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  virtual void select_command(const SelectCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  virtual void button_command(const ButtonCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  void on_hello_request(const HelloRequest &msg) override;
 | 
			
		||||
@@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  void on_select_command_request(const SelectCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  void on_button_command_request(const ButtonCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); }
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
 | 
			
		||||
  return this->client_->send_text_sensor_info(text_sensor);
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator {
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  bool on_switch(switch_::Switch *a_switch) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  bool on_button(button::Button *button) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator {
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  bool on_switch(switch_::Switch *a_switch) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  bool on_button(button::Button *button) override { return true; };
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -116,6 +116,21 @@ void ComponentIterator::advance() {
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
    case IteratorState::BUTTON:
 | 
			
		||||
      if (this->at_ >= App.get_buttons().size()) {
 | 
			
		||||
        advance_platform = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        auto *button = App.get_buttons()[this->at_];
 | 
			
		||||
        if (button->is_internal()) {
 | 
			
		||||
          success = true;
 | 
			
		||||
          break;
 | 
			
		||||
        } else {
 | 
			
		||||
          success = this->on_button(button);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
    case IteratorState::TEXT_SENSOR:
 | 
			
		||||
      if (this->at_ >= App.get_text_sensors().size()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,9 @@ class ComponentIterator {
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  virtual bool on_switch(switch_::Switch *a_switch) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  virtual bool on_button(button::Button *button) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -78,6 +81,9 @@ class ComponentIterator {
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
    SWITCH,
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
    BUTTON,
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
    TEXT_SENSOR,
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_SMOKE,
 | 
			
		||||
    DEVICE_CLASS_SOUND,
 | 
			
		||||
    DEVICE_CLASS_TAMPER,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
    DEVICE_CLASS_VIBRATION,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
)
 | 
			
		||||
@@ -82,6 +83,7 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_SMOKE,
 | 
			
		||||
    DEVICE_CLASS_SOUND,
 | 
			
		||||
    DEVICE_CLASS_TAMPER,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
    DEVICE_CLASS_VIBRATION,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -388,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
 | 
			
		||||
  return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
 | 
			
		||||
  auto client = this->service->client;
 | 
			
		||||
  auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
 | 
			
		||||
                                         ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ class BLECharacteristic {
 | 
			
		||||
  void parse_descriptors();
 | 
			
		||||
  BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLEDescriptor *get_descriptor(uint16_t uuid);
 | 
			
		||||
 | 
			
		||||
  void write_value(uint8_t *new_val, int16_t new_val_size);
 | 
			
		||||
  BLEService *service;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										67
									
								
								esphome/components/ble_client/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								esphome/components/ble_client/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import output, ble_client, esp32_ble_tracker
 | 
			
		||||
from esphome.const import CONF_ID, CONF_SERVICE_UUID
 | 
			
		||||
from .. import ble_client_ns
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["ble_client"]
 | 
			
		||||
 | 
			
		||||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
 | 
			
		||||
 | 
			
		||||
BLEBinaryOutput = ble_client_ns.class_(
 | 
			
		||||
    "BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    output.BINARY_OUTPUT_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
 | 
			
		||||
            cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
        cg.add(var.set_service_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_char_uuid16(
 | 
			
		||||
                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
 | 
			
		||||
        esp32_ble_tracker.bt_uuid32_format
 | 
			
		||||
    ):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_char_uuid32(
 | 
			
		||||
                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
 | 
			
		||||
        esp32_ble_tracker.bt_uuid128_format
 | 
			
		||||
    ):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(
 | 
			
		||||
            config[CONF_CHARACTERISTIC_UUID]
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_char_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    yield output.register_output(var, config)
 | 
			
		||||
    yield ble_client.register_ble_node(var, config)
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
							
								
								
									
										71
									
								
								esphome/components/ble_client/output/ble_binary_output.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								esphome/components/ble_client/output/ble_binary_output.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
#include "ble_binary_output.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ble_binary_output";
 | 
			
		||||
 | 
			
		||||
void BLEBinaryOutput::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "BLE Binary Output:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MAC address        : %s", this->parent_->address_str().c_str());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Service UUID       : %s", this->service_uuid_.to_string().c_str());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
 | 
			
		||||
  LOG_BINARY_OUTPUT(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                          esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT:
 | 
			
		||||
      this->client_state_ = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT:
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
 | 
			
		||||
      this->client_state_ = espbt::ClientState::IDLE;
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_GATTC_WRITE_CHAR_EVT: {
 | 
			
		||||
      if (param->write.status == 0) {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
 | 
			
		||||
      if (chr == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (param->write.handle == chr->handle) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEBinaryOutput::write_state(bool state) {
 | 
			
		||||
  if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%s] Not connected to BLE client.  State update can not be written.",
 | 
			
		||||
             this->char_uuid_.to_string().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
 | 
			
		||||
  if (chr == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%s] Characteristic not found.  State update can not be written.",
 | 
			
		||||
             this->char_uuid_.to_string().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t state_as_uint = (uint8_t) state;
 | 
			
		||||
  ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
 | 
			
		||||
  chr->write_value(&state_as_uint, sizeof(state_as_uint));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										39
									
								
								esphome/components/ble_client/output/ble_binary_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/ble_client/output/ble_binary_output.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/components/output/binary_output.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void loop() override {}
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
  void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
 | 
			
		||||
  void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
 | 
			
		||||
  void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
 | 
			
		||||
  void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
 | 
			
		||||
  void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
 | 
			
		||||
  void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void write_state(bool state) override;
 | 
			
		||||
  espbt::ESPBTUUID service_uuid_;
 | 
			
		||||
  espbt::ESPBTUUID char_uuid_;
 | 
			
		||||
  espbt::ClientState client_state_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -67,7 +67,7 @@ async def to_code(config):
 | 
			
		||||
            var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
        cg.add(var.set_service_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
@@ -87,7 +87,9 @@ async def to_code(config):
 | 
			
		||||
    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
 | 
			
		||||
        esp32_ble_tracker.bt_uuid128_format
 | 
			
		||||
    ):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(
 | 
			
		||||
            config[CONF_CHARACTERISTIC_UUID]
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_char_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    if CONF_DESCRIPTOR_UUID in config:
 | 
			
		||||
@@ -108,7 +110,9 @@ async def to_code(config):
 | 
			
		||||
        elif len(config[CONF_DESCRIPTOR_UUID]) == len(
 | 
			
		||||
            esp32_ble_tracker.bt_uuid128_format
 | 
			
		||||
        ):
 | 
			
		||||
            uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
 | 
			
		||||
            uuid128 = esp32_ble_tracker.as_reversed_hex_array(
 | 
			
		||||
                config[CONF_DESCRIPTOR_UUID]
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(var.set_descr_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2;
 | 
			
		||||
static const uint8_t BME280_REGISTER_STATUS = 0xF3;
 | 
			
		||||
static const uint8_t BME280_REGISTER_CONTROL = 0xF4;
 | 
			
		||||
static const uint8_t BME280_REGISTER_CONFIG = 0xF5;
 | 
			
		||||
static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7;
 | 
			
		||||
static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7;
 | 
			
		||||
static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA;
 | 
			
		||||
static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD;
 | 
			
		||||
@@ -178,21 +179,27 @@ void BME280Component::update() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float meas_time = 1.5;
 | 
			
		||||
  float meas_time = 1.5f;
 | 
			
		||||
  meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
 | 
			
		||||
  meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f;
 | 
			
		||||
  meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;
 | 
			
		||||
 | 
			
		||||
  this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
 | 
			
		||||
    uint8_t data[8];
 | 
			
		||||
    if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
 | 
			
		||||
      ESP_LOGW(TAG, "Error reading registers.");
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    int32_t t_fine = 0;
 | 
			
		||||
    float temperature = this->read_temperature_(&t_fine);
 | 
			
		||||
    float temperature = this->read_temperature_(data, &t_fine);
 | 
			
		||||
    if (std::isnan(temperature)) {
 | 
			
		||||
      ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    float pressure = this->read_pressure_(t_fine);
 | 
			
		||||
    float humidity = this->read_humidity_(t_fine);
 | 
			
		||||
    float pressure = this->read_pressure_(data, t_fine);
 | 
			
		||||
    float humidity = this->read_humidity_(data, t_fine);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
 | 
			
		||||
    if (this->temperature_sensor_ != nullptr)
 | 
			
		||||
@@ -204,11 +211,8 @@ void BME280Component::update() {
 | 
			
		||||
    this->status_clear_warning();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
float BME280Component::read_temperature_(int32_t *t_fine) {
 | 
			
		||||
  uint8_t data[3];
 | 
			
		||||
  if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3))
 | 
			
		||||
    return NAN;
 | 
			
		||||
  int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
 | 
			
		||||
float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
 | 
			
		||||
  int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
 | 
			
		||||
  adc >>= 4;
 | 
			
		||||
  if (adc == 0x80000)
 | 
			
		||||
    // temperature was disabled
 | 
			
		||||
@@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) {
 | 
			
		||||
  return temperature / 100.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float BME280Component::read_pressure_(int32_t t_fine) {
 | 
			
		||||
  uint8_t data[3];
 | 
			
		||||
  if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3))
 | 
			
		||||
    return NAN;
 | 
			
		||||
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
 | 
			
		||||
  int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
 | 
			
		||||
  adc >>= 4;
 | 
			
		||||
  if (adc == 0x80000)
 | 
			
		||||
@@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) {
 | 
			
		||||
  return (p / 256.0f) / 100.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float BME280Component::read_humidity_(int32_t t_fine) {
 | 
			
		||||
  uint16_t raw_adc;
 | 
			
		||||
  if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000)
 | 
			
		||||
float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
 | 
			
		||||
  uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
 | 
			
		||||
  if (raw_adc == 0x8000)
 | 
			
		||||
    return NAN;
 | 
			
		||||
 | 
			
		||||
  int32_t adc = raw_adc;
 | 
			
		||||
 
 | 
			
		||||
@@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /// Read the temperature value and store the calculated ambient temperature in t_fine.
 | 
			
		||||
  float read_temperature_(int32_t *t_fine);
 | 
			
		||||
  float read_temperature_(const uint8_t *data, int32_t *t_fine);
 | 
			
		||||
  /// Read the pressure value in hPa using the provided t_fine value.
 | 
			
		||||
  float read_pressure_(int32_t t_fine);
 | 
			
		||||
  float read_pressure_(const uint8_t *data, int32_t t_fine);
 | 
			
		||||
  /// Read the humidity value in % using the provided t_fine value.
 | 
			
		||||
  float read_humidity_(int32_t t_fine);
 | 
			
		||||
  float read_humidity_(const uint8_t *data, int32_t t_fine);
 | 
			
		||||
  uint8_t read_u8_(uint8_t a_register);
 | 
			
		||||
  uint16_t read_u16_le_(uint8_t a_register);
 | 
			
		||||
  int16_t read_s16_le_(uint8_t a_register);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										127
									
								
								esphome/components/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								esphome/components/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.components import mqtt
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_ON_PRESS,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    DEVICE_CLASS_RESTART,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_RESTART,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
button_ns = cg.esphome_ns.namespace("button")
 | 
			
		||||
Button = button_ns.class_("Button", cg.EntityBase)
 | 
			
		||||
ButtonPtr = Button.operator("ptr")
 | 
			
		||||
 | 
			
		||||
PressAction = button_ns.class_("PressAction", automation.Action)
 | 
			
		||||
 | 
			
		||||
ButtonPressTrigger = button_ns.class_(
 | 
			
		||||
    "ButtonPressTrigger", automation.Trigger.template()
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
 | 
			
		||||
        cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
 | 
			
		||||
        cv.Optional(CONF_ON_PRESS): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def button_schema(
 | 
			
		||||
    icon: str = _UNDEF,
 | 
			
		||||
    entity_category: str = _UNDEF,
 | 
			
		||||
    device_class: str = _UNDEF,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = BUTTON_SCHEMA
 | 
			
		||||
    if icon is not _UNDEF:
 | 
			
		||||
        schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
 | 
			
		||||
    if entity_category is not _UNDEF:
 | 
			
		||||
        schema = schema.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_ENTITY_CATEGORY, default=entity_category
 | 
			
		||||
                ): cv.entity_category
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    if device_class is not _UNDEF:
 | 
			
		||||
        schema = schema.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_DEVICE_CLASS, default=device_class
 | 
			
		||||
                ): validate_device_class
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    return schema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_button_core_(var, config):
 | 
			
		||||
    await setup_entity(var, config)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_PRESS, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
    if CONF_DEVICE_CLASS in config:
 | 
			
		||||
        cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
 | 
			
		||||
 | 
			
		||||
    if CONF_MQTT_ID in config:
 | 
			
		||||
        mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
 | 
			
		||||
        await mqtt.register_mqtt_component(mqtt_, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_button(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
        var = cg.Pvariable(config[CONF_ID], var)
 | 
			
		||||
    cg.add(cg.App.register_button(var))
 | 
			
		||||
    await setup_button_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_button(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await register_button(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BUTTON_PRESS_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Button),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA)
 | 
			
		||||
async def button_press_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(100.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_global(button_ns.using)
 | 
			
		||||
    cg.add_define("USE_BUTTON")
 | 
			
		||||
							
								
								
									
										28
									
								
								esphome/components/button/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/button/automation.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/button/button.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace button {
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class PressAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit PressAction(Button *button) : button_(button) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->button_->press(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Button *button_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ButtonPressTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  ButtonPressTrigger(Button *button) {
 | 
			
		||||
    button->add_on_press_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace button
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										28
									
								
								esphome/components/button/button.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/button/button.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
#include "button.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace button {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "button";
 | 
			
		||||
 | 
			
		||||
Button::Button(const std::string &name) : EntityBase(name) {}
 | 
			
		||||
Button::Button() : Button("") {}
 | 
			
		||||
 | 
			
		||||
void Button::press() {
 | 
			
		||||
  ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
 | 
			
		||||
  this->press_action();
 | 
			
		||||
  this->press_callback_.call();
 | 
			
		||||
}
 | 
			
		||||
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
 | 
			
		||||
uint32_t Button::hash_base() { return 1495763804UL; }
 | 
			
		||||
 | 
			
		||||
void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
 | 
			
		||||
std::string Button::get_device_class() {
 | 
			
		||||
  if (this->device_class_.has_value())
 | 
			
		||||
    return *this->device_class_;
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace button
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										57
									
								
								esphome/components/button/button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/button/button.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/entity_base.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace button {
 | 
			
		||||
 | 
			
		||||
#define LOG_BUTTON(prefix, type, obj) \
 | 
			
		||||
  if ((obj) != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
 | 
			
		||||
    if (!(obj)->get_icon().empty()) { \
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
 | 
			
		||||
    } \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
/** Base class for all buttons.
 | 
			
		||||
 *
 | 
			
		||||
 * A button is just a momentary switch that does not have a state, only a trigger.
 | 
			
		||||
 */
 | 
			
		||||
class Button : public EntityBase {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit Button();
 | 
			
		||||
  explicit Button(const std::string &name);
 | 
			
		||||
 | 
			
		||||
  /** Press this button. This is called by the front-end.
 | 
			
		||||
   *
 | 
			
		||||
   * For implementing buttons, please override press_action.
 | 
			
		||||
   */
 | 
			
		||||
  void press();
 | 
			
		||||
 | 
			
		||||
  /** Set callback for state changes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param callback The void() callback.
 | 
			
		||||
   */
 | 
			
		||||
  void add_on_press_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  /// Set the Home Assistant device class (see button::device_class).
 | 
			
		||||
  void set_device_class(const std::string &device_class);
 | 
			
		||||
 | 
			
		||||
  /// Get the device class for this button.
 | 
			
		||||
  std::string get_device_class();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /** You should implement this virtual method if you want to create your own button.
 | 
			
		||||
   */
 | 
			
		||||
  virtual void press_action(){};
 | 
			
		||||
 | 
			
		||||
  uint32_t hash_base() override;
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void()> press_callback_{};
 | 
			
		||||
  optional<std::string> device_class_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace button
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -85,14 +85,7 @@ void CaptivePortal::start() {
 | 
			
		||||
  this->dns_server_->start(53, "*", (uint32_t) ip);
 | 
			
		||||
 | 
			
		||||
  this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
 | 
			
		||||
    bool not_found = false;
 | 
			
		||||
    if (!this->active_) {
 | 
			
		||||
      not_found = true;
 | 
			
		||||
    } else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
 | 
			
		||||
      not_found = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (not_found) {
 | 
			
		||||
    if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
 | 
			
		||||
      req->send(404, "text/html", "File not found");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_MODE_COMMAND_TOPIC,
 | 
			
		||||
    CONF_MODE_STATE_TOPIC,
 | 
			
		||||
    CONF_ON_STATE,
 | 
			
		||||
    CONF_PRESET,
 | 
			
		||||
    CONF_SWING_MODE,
 | 
			
		||||
    CONF_SWING_MODE_COMMAND_TOPIC,
 | 
			
		||||
@@ -34,6 +35,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
 | 
			
		||||
    CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
 | 
			
		||||
    CONF_TEMPERATURE_STEP,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_VISUAL,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
)
 | 
			
		||||
@@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
 | 
			
		||||
 | 
			
		||||
# Actions
 | 
			
		||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
 | 
			
		||||
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
 | 
			
		||||
 | 
			
		||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
 | 
			
		||||
    {
 | 
			
		||||
@@ -161,6 +164,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -205,7 +213,7 @@ async def setup_climate_core_(var, config):
 | 
			
		||||
        if CONF_MODE_COMMAND_TOPIC in config:
 | 
			
		||||
            cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
 | 
			
		||||
        if CONF_MODE_STATE_TOPIC in config:
 | 
			
		||||
            cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC]))
 | 
			
		||||
            cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
 | 
			
		||||
 | 
			
		||||
        if CONF_SWING_MODE_COMMAND_TOPIC in config:
 | 
			
		||||
            cg.add(
 | 
			
		||||
@@ -256,6 +264,10 @@ async def setup_climate_core_(var, config):
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_STATE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_climate(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
 
 | 
			
		||||
@@ -42,5 +42,12 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
 | 
			
		||||
  Climate *climate_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class StateTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  StateTrigger(Climate *climate) {
 | 
			
		||||
    climate->add_on_state_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace climate
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -102,8 +102,6 @@ void CS5460AComponent::hw_init_() {
 | 
			
		||||
 | 
			
		||||
/* Doesn't reset the register values etc., just restarts the "computation cycle" */
 | 
			
		||||
void CS5460AComponent::restart_() {
 | 
			
		||||
  int cnt;
 | 
			
		||||
 | 
			
		||||
  this->enable();
 | 
			
		||||
  /* Stop running conversion, wake up if needed */
 | 
			
		||||
  this->write_byte(CMD_POWER_UP);
 | 
			
		||||
 
 | 
			
		||||
@@ -90,6 +90,7 @@ void CSE7766Component::parse_data_() {
 | 
			
		||||
  uint32_t power_cycle = this->get_24_bit_uint_(17);
 | 
			
		||||
 | 
			
		||||
  uint8_t adj = this->raw_data_[20];
 | 
			
		||||
  uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
 | 
			
		||||
 | 
			
		||||
  bool power_ok = true;
 | 
			
		||||
  bool voltage_ok = true;
 | 
			
		||||
@@ -127,6 +128,18 @@ void CSE7766Component::parse_data_() {
 | 
			
		||||
    power = power_calib / float(power_cycle);
 | 
			
		||||
    this->power_acc_ += power;
 | 
			
		||||
    this->power_counts_ += 1;
 | 
			
		||||
 | 
			
		||||
    uint32_t difference;
 | 
			
		||||
    if (this->cf_pulses_last_ == 0)
 | 
			
		||||
      this->cf_pulses_last_ = cf_pulses;
 | 
			
		||||
 | 
			
		||||
    if (cf_pulses < this->cf_pulses_last_) {
 | 
			
		||||
      difference = cf_pulses + (0x10000 - this->cf_pulses_last_);
 | 
			
		||||
    } else {
 | 
			
		||||
      difference = cf_pulses - this->cf_pulses_last_;
 | 
			
		||||
    }
 | 
			
		||||
    this->cf_pulses_last_ = cf_pulses;
 | 
			
		||||
    this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) {
 | 
			
		||||
@@ -136,9 +149,9 @@ void CSE7766Component::parse_data_() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void CSE7766Component::update() {
 | 
			
		||||
  float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0;
 | 
			
		||||
  float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0;
 | 
			
		||||
  float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0;
 | 
			
		||||
  float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f;
 | 
			
		||||
  float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f;
 | 
			
		||||
  float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_,
 | 
			
		||||
           this->power_acc_);
 | 
			
		||||
@@ -152,6 +165,8 @@ void CSE7766Component::update() {
 | 
			
		||||
    this->current_sensor_->publish_state(current);
 | 
			
		||||
  if (this->power_sensor_ != nullptr)
 | 
			
		||||
    this->power_sensor_->publish_state(power);
 | 
			
		||||
  if (this->energy_sensor_ != nullptr)
 | 
			
		||||
    this->energy_sensor_->publish_state(this->energy_total_);
 | 
			
		||||
 | 
			
		||||
  this->voltage_acc_ = 0.0f;
 | 
			
		||||
  this->current_acc_ = 0.0f;
 | 
			
		||||
@@ -172,6 +187,7 @@ void CSE7766Component::dump_config() {
 | 
			
		||||
  LOG_SENSOR("  ", "Voltage", this->voltage_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Current", this->current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Power", this->power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Energy", this->energy_sensor_);
 | 
			
		||||
  this->check_uart_settings(4800);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
  void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
 | 
			
		||||
  void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
 | 
			
		||||
  void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
 | 
			
		||||
  void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
 | 
			
		||||
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
@@ -29,9 +30,12 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *current_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *power_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *energy_sensor_{nullptr};
 | 
			
		||||
  float voltage_acc_{0.0f};
 | 
			
		||||
  float current_acc_{0.0f};
 | 
			
		||||
  float power_acc_{0.0f};
 | 
			
		||||
  float energy_total_{0.0f};
 | 
			
		||||
  uint32_t cf_pulses_last_{0};
 | 
			
		||||
  uint32_t voltage_counts_{0};
 | 
			
		||||
  uint32_t current_counts_{0};
 | 
			
		||||
  uint32_t power_counts_{0};
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,20 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, uart
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_ENERGY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_POWER,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_ENERGY,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_TOTAL_INCREASING,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
    UNIT_WATT_HOURS,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["uart"]
 | 
			
		||||
@@ -44,6 +48,12 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ENERGY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_WATT_HOURS,
 | 
			
		||||
                accuracy_decimals=3,
 | 
			
		||||
                device_class=DEVICE_CLASS_ENERGY,
 | 
			
		||||
                state_class=STATE_CLASS_TOTAL_INCREASING,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
@@ -71,3 +81,7 @@ async def to_code(config):
 | 
			
		||||
        conf = config[CONF_POWER]
 | 
			
		||||
        sens = await sensor.new_sensor(conf)
 | 
			
		||||
        cg.add(var.set_power_sensor(sens))
 | 
			
		||||
    if CONF_ENERGY in config:
 | 
			
		||||
        conf = config[CONF_ENERGY]
 | 
			
		||||
        sens = await sensor.new_sensor(conf)
 | 
			
		||||
        cg.add(var.set_energy_sensor(sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -231,7 +231,7 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
      // frame header
 | 
			
		||||
      if (byte != 0x27)
 | 
			
		||||
        return false;
 | 
			
		||||
    } else if (pos == 3) {
 | 
			
		||||
    } else if (pos == 3) {  // NOLINT(bugprone-branch-clone)
 | 
			
		||||
      // frame header
 | 
			
		||||
      if (byte != 0x00)
 | 
			
		||||
        return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -29,12 +29,11 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
WIFI_MESSAGE = """
 | 
			
		||||
WIFI_CONFIG = """
 | 
			
		||||
 | 
			
		||||
# Do not forget to add your own wifi configuration before installing this configuration
 | 
			
		||||
# wifi:
 | 
			
		||||
#   ssid: !secret wifi_ssid
 | 
			
		||||
#   password: !secret wifi_password
 | 
			
		||||
wifi:
 | 
			
		||||
  ssid: !secret wifi_ssid
 | 
			
		||||
  password: !secret wifi_password
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -55,6 +54,6 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
 | 
			
		||||
        "esphome": {"name_add_mac_suffix": False},
 | 
			
		||||
    }
 | 
			
		||||
    p.write_text(
 | 
			
		||||
        dump(config) + WIFI_MESSAGE,
 | 
			
		||||
        dump(config) + WIFI_CONFIG,
 | 
			
		||||
        encoding="utf8",
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,23 @@
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/version.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#include <esp_heap_caps.h>
 | 
			
		||||
#include <esp_system.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#if ESP_IDF_VERSION_MAJOR >= 4
 | 
			
		||||
#include <esp32/rom/rtc.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <rom/rtc.h>
 | 
			
		||||
#include <esp_idf_version.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#include <Esp.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#include <esp_heap_caps.h>
 | 
			
		||||
#include <esp_system.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace debug {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -77,8 +77,8 @@ void DeepSleepComponent::begin_sleep(bool manual) {
 | 
			
		||||
  if (this->sleep_duration_.has_value())
 | 
			
		||||
    esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
 | 
			
		||||
  if (this->wakeup_pin_ != nullptr) {
 | 
			
		||||
    bool level = this->wakeup_pin_->is_inverted();
 | 
			
		||||
    if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) {
 | 
			
		||||
    bool level = !this->wakeup_pin_->is_inverted();
 | 
			
		||||
    if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
 | 
			
		||||
      level = !level;
 | 
			
		||||
    }
 | 
			
		||||
    esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
 | 
			
		||||
 
 | 
			
		||||
@@ -496,7 +496,7 @@ bool Animation::get_pixel(int x, int y) const {
 | 
			
		||||
    return false;
 | 
			
		||||
  const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
 | 
			
		||||
  const uint32_t frame_index = this->height_ * width_8 * this->current_frame_;
 | 
			
		||||
  if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
 | 
			
		||||
  if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
 | 
			
		||||
    return false;
 | 
			
		||||
  const uint32_t pos = x + y * width_8 + frame_index;
 | 
			
		||||
  return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
			
		||||
@@ -505,7 +505,7 @@ Color Animation::get_color_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
 | 
			
		||||
  if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
 | 
			
		||||
  if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
 | 
			
		||||
  const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
 | 
			
		||||
@@ -517,7 +517,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
 | 
			
		||||
  if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
 | 
			
		||||
  if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_ + frame_index);
 | 
			
		||||
  const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ class ColorUtil {
 | 
			
		||||
                       ? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1))
 | 
			
		||||
                       : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1));
 | 
			
		||||
 | 
			
		||||
    third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & 0xFF), ((1 << third_bits) - 1))
 | 
			
		||||
    third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & ((1 << third_bits) - 1)), ((1 << third_bits) - 1))
 | 
			
		||||
                                     : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1));
 | 
			
		||||
 | 
			
		||||
    Color color_return;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import uart
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_UART_ID,
 | 
			
		||||
    CONF_RECEIVE_TIMEOUT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@glmnet", "@zuidwijk"]
 | 
			
		||||
@@ -11,10 +13,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"]
 | 
			
		||||
DEPENDENCIES = ["uart"]
 | 
			
		||||
AUTO_LOAD = ["sensor", "text_sensor"]
 | 
			
		||||
 | 
			
		||||
CONF_DSMR_ID = "dsmr_id"
 | 
			
		||||
CONF_DECRYPTION_KEY = "decryption_key"
 | 
			
		||||
CONF_CRC_CHECK = "crc_check"
 | 
			
		||||
CONF_DECRYPTION_KEY = "decryption_key"
 | 
			
		||||
CONF_DSMR_ID = "dsmr_id"
 | 
			
		||||
CONF_GAS_MBUS_ID = "gas_mbus_id"
 | 
			
		||||
CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
 | 
			
		||||
CONF_REQUEST_INTERVAL = "request_interval"
 | 
			
		||||
CONF_REQUEST_PIN = "request_pin"
 | 
			
		||||
 | 
			
		||||
# Hack to prevent compile error due to ambiguity with lib namespace
 | 
			
		||||
dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
 | 
			
		||||
@@ -46,6 +51,14 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
 | 
			
		||||
            cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
 | 
			
		||||
            cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
 | 
			
		||||
            cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_REQUEST_INTERVAL, default="0ms"
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_RECEIVE_TIMEOUT, default="200ms"
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(uart.UART_DEVICE_SCHEMA),
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
@@ -55,10 +68,17 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    uart_component = await cg.get_variable(config[CONF_UART_ID])
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK])
 | 
			
		||||
    cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH]))
 | 
			
		||||
    if CONF_DECRYPTION_KEY in config:
 | 
			
		||||
        cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_REQUEST_PIN in config:
 | 
			
		||||
        request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
 | 
			
		||||
        cg.add(var.set_request_pin(request_pin))
 | 
			
		||||
    cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds))
 | 
			
		||||
    cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds))
 | 
			
		||||
 | 
			
		||||
    cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
 | 
			
		||||
 | 
			
		||||
    # DSMR Parser
 | 
			
		||||
 
 | 
			
		||||
@@ -12,171 +12,275 @@ namespace dsmr {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "dsmr";
 | 
			
		||||
 | 
			
		||||
void Dsmr::loop() {
 | 
			
		||||
  if (this->decryption_key_.empty())
 | 
			
		||||
    this->receive_telegram_();
 | 
			
		||||
  else
 | 
			
		||||
    this->receive_encrypted_();
 | 
			
		||||
void Dsmr::setup() {
 | 
			
		||||
  this->telegram_ = new char[this->max_telegram_len_];  // NOLINT
 | 
			
		||||
  if (this->request_pin_ != nullptr) {
 | 
			
		||||
    this->request_pin_->setup();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Dsmr::available_within_timeout_() {
 | 
			
		||||
  uint8_t tries = READ_TIMEOUT_MS / 5;
 | 
			
		||||
  while (tries--) {
 | 
			
		||||
    delay(5);
 | 
			
		||||
    if (available()) {
 | 
			
		||||
      return true;
 | 
			
		||||
void Dsmr::loop() {
 | 
			
		||||
  if (this->ready_to_request_data_()) {
 | 
			
		||||
    if (this->decryption_key_.empty()) {
 | 
			
		||||
      this->receive_telegram_();
 | 
			
		||||
    } else {
 | 
			
		||||
      this->receive_encrypted_telegram_();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Dsmr::ready_to_request_data_() {
 | 
			
		||||
  // When using a request pin, then wait for the next request interval.
 | 
			
		||||
  if (this->request_pin_ != nullptr) {
 | 
			
		||||
    if (!this->requesting_data_ && this->request_interval_reached_()) {
 | 
			
		||||
      this->start_requesting_data_();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // Otherwise, sink serial data until next request interval.
 | 
			
		||||
  else {
 | 
			
		||||
    if (this->request_interval_reached_()) {
 | 
			
		||||
      this->start_requesting_data_();
 | 
			
		||||
    }
 | 
			
		||||
    if (!this->requesting_data_) {
 | 
			
		||||
      while (this->available()) {
 | 
			
		||||
        this->read();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return this->requesting_data_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Dsmr::request_interval_reached_() {
 | 
			
		||||
  if (this->last_request_time_ == 0) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  return millis() - this->last_request_time_ > this->request_interval_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Dsmr::receive_timeout_reached_() { return millis() - this->last_read_time_ > this->receive_timeout_; }
 | 
			
		||||
 | 
			
		||||
bool Dsmr::available_within_timeout_() {
 | 
			
		||||
  // Data are available for reading on the UART bus?
 | 
			
		||||
  // Then we can start reading right away.
 | 
			
		||||
  if (this->available()) {
 | 
			
		||||
    this->last_read_time_ = millis();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  // When we're not in the process of reading a telegram, then there is
 | 
			
		||||
  // no need to actively wait for new data to come in.
 | 
			
		||||
  if (!header_found_) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  // A telegram is being read. The smart meter might not deliver a telegram
 | 
			
		||||
  // in one go, but instead send it in chunks with small pauses in between.
 | 
			
		||||
  // When the UART RX buffer cannot hold a full telegram, then make sure
 | 
			
		||||
  // that the UART read buffer does not overflow while other components
 | 
			
		||||
  // perform their work in their loop. Do this by not returning control to
 | 
			
		||||
  // the main loop, until the read timeout is reached.
 | 
			
		||||
  if (this->parent_->get_rx_buffer_size() < this->max_telegram_len_) {
 | 
			
		||||
    while (!this->receive_timeout_reached_()) {
 | 
			
		||||
      delay(5);
 | 
			
		||||
      if (this->available()) {
 | 
			
		||||
        this->last_read_time_ = millis();
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // No new data has come in during the read timeout? Then stop reading the
 | 
			
		||||
  // telegram and start waiting for the next one to arrive.
 | 
			
		||||
  if (this->receive_timeout_reached_()) {
 | 
			
		||||
    ESP_LOGW(TAG, "Timeout while reading data for telegram");
 | 
			
		||||
    this->reset_telegram_();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Dsmr::receive_telegram_() {
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (!available()) {
 | 
			
		||||
      if (!header_found_ || !available_within_timeout_()) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
void Dsmr::start_requesting_data_() {
 | 
			
		||||
  if (!this->requesting_data_) {
 | 
			
		||||
    if (this->request_pin_ != nullptr) {
 | 
			
		||||
      ESP_LOGV(TAG, "Start requesting data from P1 port");
 | 
			
		||||
      this->request_pin_->digital_write(true);
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGV(TAG, "Start reading data from P1 port");
 | 
			
		||||
    }
 | 
			
		||||
    this->requesting_data_ = true;
 | 
			
		||||
    this->last_request_time_ = millis();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    const char c = read();
 | 
			
		||||
void Dsmr::stop_requesting_data_() {
 | 
			
		||||
  if (this->requesting_data_) {
 | 
			
		||||
    if (this->request_pin_ != nullptr) {
 | 
			
		||||
      ESP_LOGV(TAG, "Stop requesting data from P1 port");
 | 
			
		||||
      this->request_pin_->digital_write(false);
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGV(TAG, "Stop reading data from P1 port");
 | 
			
		||||
    }
 | 
			
		||||
    while (this->available()) {
 | 
			
		||||
      this->read();
 | 
			
		||||
    }
 | 
			
		||||
    this->requesting_data_ = false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Dsmr::reset_telegram_() {
 | 
			
		||||
  this->header_found_ = false;
 | 
			
		||||
  this->footer_found_ = false;
 | 
			
		||||
  this->bytes_read_ = 0;
 | 
			
		||||
  this->crypt_bytes_read_ = 0;
 | 
			
		||||
  this->crypt_telegram_len_ = 0;
 | 
			
		||||
  this->last_read_time_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Dsmr::receive_telegram_() {
 | 
			
		||||
  while (this->available_within_timeout_()) {
 | 
			
		||||
    const char c = this->read();
 | 
			
		||||
 | 
			
		||||
    // Find a new telegram header, i.e. forward slash.
 | 
			
		||||
    if (c == '/') {
 | 
			
		||||
      ESP_LOGV(TAG, "Header of telegram found");
 | 
			
		||||
      header_found_ = true;
 | 
			
		||||
      footer_found_ = false;
 | 
			
		||||
      telegram_len_ = 0;
 | 
			
		||||
      this->reset_telegram_();
 | 
			
		||||
      this->header_found_ = true;
 | 
			
		||||
    }
 | 
			
		||||
    if (!header_found_)
 | 
			
		||||
    if (!this->header_found_)
 | 
			
		||||
      continue;
 | 
			
		||||
 | 
			
		||||
    // Check for buffer overflow.
 | 
			
		||||
    if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
 | 
			
		||||
      header_found_ = false;
 | 
			
		||||
      footer_found_ = false;
 | 
			
		||||
      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
 | 
			
		||||
    if (this->bytes_read_ >= this->max_telegram_len_) {
 | 
			
		||||
      this->reset_telegram_();
 | 
			
		||||
      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Some v2.2 or v3 meters will send a new value which starts with '('
 | 
			
		||||
    // in a new line while the value belongs to the previous ObisId. For
 | 
			
		||||
    // proper parsing remove these new line characters
 | 
			
		||||
    while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r'))
 | 
			
		||||
      telegram_len_--;
 | 
			
		||||
    // in a new line, while the value belongs to the previous ObisId. For
 | 
			
		||||
    // proper parsing, remove these new line characters.
 | 
			
		||||
    if (c == '(') {
 | 
			
		||||
      while (true) {
 | 
			
		||||
        auto previous_char = this->telegram_[this->bytes_read_ - 1];
 | 
			
		||||
        if (previous_char == '\n' || previous_char == '\r') {
 | 
			
		||||
          this->bytes_read_--;
 | 
			
		||||
        } else {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Store the byte in the buffer.
 | 
			
		||||
    telegram_[telegram_len_] = c;
 | 
			
		||||
    telegram_len_++;
 | 
			
		||||
    this->telegram_[this->bytes_read_] = c;
 | 
			
		||||
    this->bytes_read_++;
 | 
			
		||||
 | 
			
		||||
    // Check for a footer, i.e. exlamation mark, followed by a hex checksum.
 | 
			
		||||
    if (c == '!') {
 | 
			
		||||
      ESP_LOGV(TAG, "Footer of telegram found");
 | 
			
		||||
      footer_found_ = true;
 | 
			
		||||
      this->footer_found_ = true;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    // Check for the end of the hex checksum, i.e. a newline.
 | 
			
		||||
    if (footer_found_ && c == '\n') {
 | 
			
		||||
    if (this->footer_found_ && c == '\n') {
 | 
			
		||||
      // Parse the telegram and publish sensor values.
 | 
			
		||||
      parse_telegram();
 | 
			
		||||
 | 
			
		||||
      header_found_ = false;
 | 
			
		||||
      this->parse_telegram();
 | 
			
		||||
      this->reset_telegram_();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Dsmr::receive_encrypted_() {
 | 
			
		||||
  // Encrypted buffer
 | 
			
		||||
  uint8_t buffer[MAX_TELEGRAM_LENGTH];
 | 
			
		||||
  size_t buffer_length = 0;
 | 
			
		||||
  size_t packet_size = 0;
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (!available()) {
 | 
			
		||||
      if (!header_found_) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (!available_within_timeout_()) {
 | 
			
		||||
        ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const char c = read();
 | 
			
		||||
void Dsmr::receive_encrypted_telegram_() {
 | 
			
		||||
  while (this->available_within_timeout_()) {
 | 
			
		||||
    const char c = this->read();
 | 
			
		||||
 | 
			
		||||
    // Find a new telegram start byte.
 | 
			
		||||
    if (!header_found_) {
 | 
			
		||||
    if (!this->header_found_) {
 | 
			
		||||
      if ((uint8_t) c != 0xDB) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
 | 
			
		||||
      header_found_ = true;
 | 
			
		||||
      this->reset_telegram_();
 | 
			
		||||
      this->header_found_ = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check for buffer overflow.
 | 
			
		||||
    if (buffer_length >= MAX_TELEGRAM_LENGTH) {
 | 
			
		||||
      header_found_ = false;
 | 
			
		||||
      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
 | 
			
		||||
    if (this->crypt_bytes_read_ >= this->max_telegram_len_) {
 | 
			
		||||
      this->reset_telegram_();
 | 
			
		||||
      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buffer[buffer_length++] = c;
 | 
			
		||||
    // Store the byte in the buffer.
 | 
			
		||||
    this->crypt_telegram_[this->crypt_bytes_read_] = c;
 | 
			
		||||
    this->crypt_bytes_read_++;
 | 
			
		||||
 | 
			
		||||
    if (packet_size == 0 && buffer_length > 20) {
 | 
			
		||||
    // Read the length of the incoming encrypted telegram.
 | 
			
		||||
    if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) {
 | 
			
		||||
      // Complete header + data bytes
 | 
			
		||||
      packet_size = 13 + (buffer[11] << 8 | buffer[12]);
 | 
			
		||||
      ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
 | 
			
		||||
      this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]);
 | 
			
		||||
      ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_);
 | 
			
		||||
    }
 | 
			
		||||
    if (buffer_length == packet_size && packet_size > 0) {
 | 
			
		||||
      ESP_LOGV(TAG, "End of encrypted telegram found");
 | 
			
		||||
      GCM<AES128> *gcmaes128{new GCM<AES128>()};
 | 
			
		||||
      gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
 | 
			
		||||
      // the iv is 8 bytes of the system title + 4 bytes frame counter
 | 
			
		||||
      // system title is at byte 2 and frame counter at byte 15
 | 
			
		||||
      for (int i = 10; i < 14; i++)
 | 
			
		||||
        buffer[i] = buffer[i + 4];
 | 
			
		||||
      constexpr uint16_t iv_size{12};
 | 
			
		||||
      gcmaes128->setIV(&buffer[2], iv_size);
 | 
			
		||||
      gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
 | 
			
		||||
                         // the ciphertext start at byte 18
 | 
			
		||||
                         &buffer[18],
 | 
			
		||||
                         // cipher size
 | 
			
		||||
                         buffer_length - 17);
 | 
			
		||||
      delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
 | 
			
		||||
      telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
 | 
			
		||||
      ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_);
 | 
			
		||||
      ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
 | 
			
		||||
 | 
			
		||||
      parse_telegram();
 | 
			
		||||
 | 
			
		||||
      header_found_ = false;
 | 
			
		||||
      telegram_len_ = 0;
 | 
			
		||||
      return;
 | 
			
		||||
    // Check for the end of the encrypted telegram.
 | 
			
		||||
    if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGV(TAG, "End of encrypted telegram found");
 | 
			
		||||
 | 
			
		||||
    // Decrypt the encrypted telegram.
 | 
			
		||||
    GCM<AES128> *gcmaes128{new GCM<AES128>()};
 | 
			
		||||
    gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
 | 
			
		||||
    // the iv is 8 bytes of the system title + 4 bytes frame counter
 | 
			
		||||
    // system title is at byte 2 and frame counter at byte 15
 | 
			
		||||
    for (int i = 10; i < 14; i++)
 | 
			
		||||
      this->crypt_telegram_[i] = this->crypt_telegram_[i + 4];
 | 
			
		||||
    constexpr uint16_t iv_size{12};
 | 
			
		||||
    gcmaes128->setIV(&this->crypt_telegram_[2], iv_size);
 | 
			
		||||
    gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
 | 
			
		||||
                       // the ciphertext start at byte 18
 | 
			
		||||
                       &this->crypt_telegram_[18],
 | 
			
		||||
                       // cipher size
 | 
			
		||||
                       this->crypt_bytes_read_ - 17);
 | 
			
		||||
    delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
 | 
			
		||||
    this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_);
 | 
			
		||||
    ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_);
 | 
			
		||||
    ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
 | 
			
		||||
 | 
			
		||||
    // Parse the decrypted telegram and publish sensor values.
 | 
			
		||||
    this->parse_telegram();
 | 
			
		||||
    this->reset_telegram_();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Dsmr::parse_telegram() {
 | 
			
		||||
  MyData data;
 | 
			
		||||
  ESP_LOGV(TAG, "Trying to parse telegram");
 | 
			
		||||
  this->stop_requesting_data_();
 | 
			
		||||
  ::dsmr::ParseResult<void> res =
 | 
			
		||||
      ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
 | 
			
		||||
      ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false,
 | 
			
		||||
                              this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values.
 | 
			
		||||
  if (res.err) {
 | 
			
		||||
    // Parsing error, show it
 | 
			
		||||
    auto err_str = res.fullError(telegram_, telegram_ + telegram_len_);
 | 
			
		||||
    auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_);
 | 
			
		||||
    ESP_LOGE(TAG, "%s", err_str.c_str());
 | 
			
		||||
    return false;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->status_clear_warning();
 | 
			
		||||
    publish_sensors(data);
 | 
			
		||||
    this->publish_sensors(data);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Dsmr::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "DSMR:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Max telegram length: %d", this->max_telegram_len_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Receive timeout: %.1fs", this->receive_timeout_ / 1e3f);
 | 
			
		||||
  if (this->request_pin_ != nullptr) {
 | 
			
		||||
    LOG_PIN("  Request Pin: ", this->request_pin_);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->request_interval_ > 0) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Request Interval: %.1fs", this->request_interval_ / 1e3f);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#define DSMR_LOG_SENSOR(s) LOG_SENSOR("  ", #s, this->s_##s##_);
 | 
			
		||||
  DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
 | 
			
		||||
@@ -189,6 +293,10 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
 | 
			
		||||
  if (decryption_key.length() == 0) {
 | 
			
		||||
    ESP_LOGI(TAG, "Disabling decryption");
 | 
			
		||||
    this->decryption_key_.clear();
 | 
			
		||||
    if (this->crypt_telegram_ != nullptr) {
 | 
			
		||||
      delete[] this->crypt_telegram_;
 | 
			
		||||
      this->crypt_telegram_ = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -205,7 +313,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
 | 
			
		||||
  char temp[3] = {0};
 | 
			
		||||
  for (int i = 0; i < 16; i++) {
 | 
			
		||||
    strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
 | 
			
		||||
    decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
 | 
			
		||||
    this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->crypt_telegram_ == nullptr) {
 | 
			
		||||
    this->crypt_telegram_ = new uint8_t[this->max_telegram_len_];  // NOLINT
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,9 +16,6 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dsmr {
 | 
			
		||||
 | 
			
		||||
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
 | 
			
		||||
static constexpr uint32_t READ_TIMEOUT_MS = 200;
 | 
			
		||||
 | 
			
		||||
using namespace ::dsmr::fields;
 | 
			
		||||
 | 
			
		||||
// DSMR_**_LIST generated by ESPHome and written in esphome/core/defines
 | 
			
		||||
@@ -52,6 +49,7 @@ class Dsmr : public Component, public uart::UARTDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  bool parse_telegram();
 | 
			
		||||
@@ -71,6 +69,10 @@ class Dsmr : public Component, public uart::UARTDevice {
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  void set_decryption_key(const std::string &decryption_key);
 | 
			
		||||
  void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
 | 
			
		||||
  void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
 | 
			
		||||
  void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
 | 
			
		||||
  void set_receive_timeout(uint32_t timeout) { this->receive_timeout_ = timeout; }
 | 
			
		||||
 | 
			
		||||
// Sensor setters
 | 
			
		||||
#define DSMR_SET_SENSOR(s) \
 | 
			
		||||
@@ -83,7 +85,8 @@ class Dsmr : public Component, public uart::UARTDevice {
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void receive_telegram_();
 | 
			
		||||
  void receive_encrypted_();
 | 
			
		||||
  void receive_encrypted_telegram_();
 | 
			
		||||
  void reset_telegram_();
 | 
			
		||||
 | 
			
		||||
  /// Wait for UART data to become available within the read timeout.
 | 
			
		||||
  ///
 | 
			
		||||
@@ -96,11 +99,26 @@ class Dsmr : public Component, public uart::UARTDevice {
 | 
			
		||||
  /// lost in the process.
 | 
			
		||||
  bool available_within_timeout_();
 | 
			
		||||
 | 
			
		||||
  // Telegram buffer
 | 
			
		||||
  char telegram_[MAX_TELEGRAM_LENGTH];
 | 
			
		||||
  int telegram_len_{0};
 | 
			
		||||
  // Request telegram
 | 
			
		||||
  uint32_t request_interval_;
 | 
			
		||||
  bool request_interval_reached_();
 | 
			
		||||
  GPIOPin *request_pin_{nullptr};
 | 
			
		||||
  uint32_t last_request_time_{0};
 | 
			
		||||
  bool requesting_data_{false};
 | 
			
		||||
  bool ready_to_request_data_();
 | 
			
		||||
  void start_requesting_data_();
 | 
			
		||||
  void stop_requesting_data_();
 | 
			
		||||
 | 
			
		||||
  // Serial parser
 | 
			
		||||
  // Read telegram
 | 
			
		||||
  uint32_t receive_timeout_;
 | 
			
		||||
  bool receive_timeout_reached_();
 | 
			
		||||
  size_t max_telegram_len_;
 | 
			
		||||
  char *telegram_{nullptr};
 | 
			
		||||
  size_t bytes_read_{0};
 | 
			
		||||
  uint8_t *crypt_telegram_{nullptr};
 | 
			
		||||
  size_t crypt_telegram_len_{0};
 | 
			
		||||
  size_t crypt_bytes_read_{0};
 | 
			
		||||
  uint32_t last_read_time_{0};
 | 
			
		||||
  bool header_found_{false};
 | 
			
		||||
  bool footer_found_{false};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -311,9 +311,16 @@ async def to_code(config):
 | 
			
		||||
        )
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True)
 | 
			
		||||
 | 
			
		||||
        # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
 | 
			
		||||
 | 
			
		||||
        # Setup watchdog
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True)
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True)
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False)
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False)
 | 
			
		||||
 | 
			
		||||
        cg.add_platformio_option("board_build.partitions", "partitions.csv")
 | 
			
		||||
 | 
			
		||||
        for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,17 @@
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/task.h>
 | 
			
		||||
#include <esp_idf_version.h>
 | 
			
		||||
#include <esp_task_wdt.h>
 | 
			
		||||
#include <soc/rtc.h>
 | 
			
		||||
 | 
			
		||||
#if ESP_IDF_VERSION_MAJOR >= 4
 | 
			
		||||
#include <hal/cpu_hal.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#include <esp32-hal.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void setup();
 | 
			
		||||
void loop();
 | 
			
		||||
 | 
			
		||||
@@ -29,24 +34,24 @@ void arch_restart() {
 | 
			
		||||
    yield();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR HOT arch_feed_wdt() {
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#if CONFIG_ARDUINO_RUNNING_CORE == 0
 | 
			
		||||
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
 | 
			
		||||
  // ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task.
 | 
			
		||||
  // To cause the Watchdog to be triggered we need to put the current task
 | 
			
		||||
  // to sleep to get the idle task scheduled.
 | 
			
		||||
  delay(1);
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
 | 
			
		||||
  delay(1);
 | 
			
		||||
void arch_init() {
 | 
			
		||||
  // Enable the task watchdog only on the loop task (from which we're currently running)
 | 
			
		||||
#if defined(USE_ESP_IDF)
 | 
			
		||||
  esp_task_wdt_add(nullptr);
 | 
			
		||||
  // Idle task watchdog is disabled on ESP-IDF
 | 
			
		||||
#elif defined(USE_ARDUINO)
 | 
			
		||||
  enableLoopWDT();
 | 
			
		||||
  // Disable idle task watchdog on the core we're using (Arduino pins the process to a core)
 | 
			
		||||
#if CONFIG_ARDUINO_RUNNING_CORE == 0
 | 
			
		||||
  disableCore0WDT();
 | 
			
		||||
#endif
 | 
			
		||||
#if CONFIG_ARDUINO_RUNNING_CORE == 1
 | 
			
		||||
  disableCore1WDT();
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
 | 
			
		||||
 | 
			
		||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
 | 
			
		||||
uint32_t arch_get_cpu_cycle_count() {
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,7 @@ class ESP32Preferences : public ESPPreferences {
 | 
			
		||||
  uint32_t current_offset = 0;
 | 
			
		||||
 | 
			
		||||
  void open() {
 | 
			
		||||
    nvs_flash_init();
 | 
			
		||||
    esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
 | 
			
		||||
    if (err == 0)
 | 
			
		||||
      return;
 | 
			
		||||
 
 | 
			
		||||
@@ -21,12 +21,19 @@ static const char *const TAG = "esp32_camera_web_server";
 | 
			
		||||
#define CONTENT_TYPE "image/jpeg"
 | 
			
		||||
#define CONTENT_LENGTH "Content-Length"
 | 
			
		||||
 | 
			
		||||
static const char *const STREAM_HEADER =
 | 
			
		||||
    "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY
 | 
			
		||||
    "\r\n";
 | 
			
		||||
static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n";
 | 
			
		||||
static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
 | 
			
		||||
static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
 | 
			
		||||
                                         "Access-Control-Allow-Origin: *\r\n"
 | 
			
		||||
                                         "Connection: close\r\n"
 | 
			
		||||
                                         "Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
 | 
			
		||||
                                         "\r\n"
 | 
			
		||||
                                         "--" PART_BOUNDARY "\r\n";
 | 
			
		||||
static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n"
 | 
			
		||||
                                        "\r\n"
 | 
			
		||||
                                        "No frames send.\r\n"
 | 
			
		||||
                                        "--" PART_BOUNDARY "\r\n";
 | 
			
		||||
static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
 | 
			
		||||
static const char *const STREAM_BOUNDARY = "\r\n"
 | 
			
		||||
                                           "--" PART_BOUNDARY "\r\n";
 | 
			
		||||
 | 
			
		||||
CameraWebServer::CameraWebServer() {}
 | 
			
		||||
 | 
			
		||||
@@ -45,6 +52,7 @@ void CameraWebServer::setup() {
 | 
			
		||||
  config.ctrl_port = this->port_;
 | 
			
		||||
  config.max_open_sockets = 1;
 | 
			
		||||
  config.backlog_conn = 2;
 | 
			
		||||
  config.lru_purge_enable = true;
 | 
			
		||||
 | 
			
		||||
  if (httpd_start(&this->httpd_, &config) != ESP_OK) {
 | 
			
		||||
    mark_failed();
 | 
			
		||||
@@ -172,9 +180,6 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
 | 
			
		||||
      ESP_LOGW(TAG, "STREAM: failed to acquire frame");
 | 
			
		||||
      res = ESP_FAIL;
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
 | 
			
		||||
      res = httpd_send_all(req, part_buf, hlen);
 | 
			
		||||
@@ -182,6 +187,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      frames++;
 | 
			
		||||
      int64_t frame_time = millis() - last_frame;
 | 
			
		||||
@@ -193,7 +201,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!frames) {
 | 
			
		||||
    res = httpd_send_all(req, STREAM_500, strlen(STREAM_500));
 | 
			
		||||
    res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"]
 | 
			
		||||
AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
 | 
			
		||||
DEPENDENCIES = ["wifi", "esp32"]
 | 
			
		||||
@@ -56,6 +56,7 @@ async def to_code(config):
 | 
			
		||||
    cg.add(ble_server.register_service_component(var))
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_IMPROV")
 | 
			
		||||
    cg.add_library("esphome/Improv", "1.0.0")
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
 | 
			
		||||
    cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_server/ble_server.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_server/ble_characteristic.h"
 | 
			
		||||
#include "esphome/components/improv/improv.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_server/ble_server.h"
 | 
			
		||||
#include "esphome/components/output/binary_output.h"
 | 
			
		||||
#include "esphome/components/wifi/wifi_component.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
@@ -12,6 +11,8 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <improv.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_improv {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
 | 
			
		||||
 | 
			
		||||
  if (this->iir_filter_enabled_()) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    IIR Filter: %ums", this->iir_filter_);
 | 
			
		||||
    touch_pad_filter_start(this->iir_filter_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  IIR Filter DISABLED");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = {
 | 
			
		||||
    "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0},
 | 
			
		||||
    "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FLASH_SIZE_1_MB = 2 ** 20
 | 
			
		||||
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
 | 
			
		||||
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
 | 
			
		||||
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB
 | 
			
		||||
FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB
 | 
			
		||||
 | 
			
		||||
ESP8266_FLASH_SIZES = {
 | 
			
		||||
    "d1": FLASH_SIZE_4_MB,
 | 
			
		||||
    "d1_mini": FLASH_SIZE_4_MB,
 | 
			
		||||
    "d1_mini_lite": FLASH_SIZE_1_MB,
 | 
			
		||||
    "d1_mini_pro": FLASH_SIZE_16_MB,
 | 
			
		||||
    "esp01": FLASH_SIZE_512_KB,
 | 
			
		||||
    "esp01_1m": FLASH_SIZE_1_MB,
 | 
			
		||||
    "esp07": FLASH_SIZE_4_MB,
 | 
			
		||||
    "esp12e": FLASH_SIZE_4_MB,
 | 
			
		||||
    "esp210": FLASH_SIZE_4_MB,
 | 
			
		||||
    "esp8285": FLASH_SIZE_1_MB,
 | 
			
		||||
    "esp_wroom_02": FLASH_SIZE_2_MB,
 | 
			
		||||
    "espduino": FLASH_SIZE_4_MB,
 | 
			
		||||
    "espectro": FLASH_SIZE_4_MB,
 | 
			
		||||
    "espino": FLASH_SIZE_4_MB,
 | 
			
		||||
    "espinotee": FLASH_SIZE_4_MB,
 | 
			
		||||
    "espmxdevkit": FLASH_SIZE_1_MB,
 | 
			
		||||
    "espresso_lite_v1": FLASH_SIZE_4_MB,
 | 
			
		||||
    "espresso_lite_v2": FLASH_SIZE_4_MB,
 | 
			
		||||
    "gen4iod": FLASH_SIZE_512_KB,
 | 
			
		||||
    "heltec_wifi_kit_8": FLASH_SIZE_4_MB,
 | 
			
		||||
    "huzzah": FLASH_SIZE_4_MB,
 | 
			
		||||
    "inventone": FLASH_SIZE_4_MB,
 | 
			
		||||
    "modwifi": FLASH_SIZE_2_MB,
 | 
			
		||||
    "nodemcu": FLASH_SIZE_4_MB,
 | 
			
		||||
    "nodemcuv2": FLASH_SIZE_4_MB,
 | 
			
		||||
    "oak": FLASH_SIZE_4_MB,
 | 
			
		||||
    "phoenix_v1": FLASH_SIZE_4_MB,
 | 
			
		||||
    "phoenix_v2": FLASH_SIZE_4_MB,
 | 
			
		||||
    "sonoff_basic": FLASH_SIZE_1_MB,
 | 
			
		||||
    "sonoff_s20": FLASH_SIZE_1_MB,
 | 
			
		||||
    "sonoff_sv": FLASH_SIZE_1_MB,
 | 
			
		||||
    "sonoff_th": FLASH_SIZE_1_MB,
 | 
			
		||||
    "sparkfunBlynk": FLASH_SIZE_4_MB,
 | 
			
		||||
    "thing": FLASH_SIZE_512_KB,
 | 
			
		||||
    "thingdev": FLASH_SIZE_512_KB,
 | 
			
		||||
    "wifi_slot": FLASH_SIZE_1_MB,
 | 
			
		||||
    "wifiduino": FLASH_SIZE_4_MB,
 | 
			
		||||
    "wifinfo": FLASH_SIZE_1_MB,
 | 
			
		||||
    "wio_link": FLASH_SIZE_4_MB,
 | 
			
		||||
    "wio_node": FLASH_SIZE_4_MB,
 | 
			
		||||
    "xinabox_cw01": FLASH_SIZE_4_MB,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ESP8266_LD_SCRIPTS = {
 | 
			
		||||
    FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"),
 | 
			
		||||
    FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"),
 | 
			
		||||
    FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"),
 | 
			
		||||
    FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"),
 | 
			
		||||
    FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"),
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ void arch_restart() {
 | 
			
		||||
    yield();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void arch_init() {}
 | 
			
		||||
void IRAM_ATTR HOT arch_feed_wdt() {
 | 
			
		||||
  ESP.wdtFeed();  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
}
 | 
			
		||||
@@ -27,7 +28,7 @@ void IRAM_ATTR HOT arch_feed_wdt() {
 | 
			
		||||
uint8_t progmem_read_byte(const uint8_t *addr) {
 | 
			
		||||
  return pgm_read_byte(addr);  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
uint32_t arch_get_cpu_cycle_count() {
 | 
			
		||||
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() {
 | 
			
		||||
  return ESP.getCycleCount();  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
}
 | 
			
		||||
uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ namespace esp8266 {
 | 
			
		||||
static const char *const TAG = "esp8266";
 | 
			
		||||
 | 
			
		||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {  // NOLINT(bugprone-branch-clone)
 | 
			
		||||
    return INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    return OUTPUT;
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
 | 
			
		||||
 | 
			
		||||
extern "C" uint32_t _SPIFFS_end;  // NOLINT
 | 
			
		||||
 | 
			
		||||
static const uint32_t get_esp8266_flash_sector() {
 | 
			
		||||
static uint32_t get_esp8266_flash_sector() {
 | 
			
		||||
  union {
 | 
			
		||||
    uint32_t *ptr;
 | 
			
		||||
    uint32_t uint;
 | 
			
		||||
@@ -63,7 +63,7 @@ static const uint32_t get_esp8266_flash_sector() {
 | 
			
		||||
  data.ptr = &_SPIFFS_end;
 | 
			
		||||
  return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
 | 
			
		||||
}
 | 
			
		||||
static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
 | 
			
		||||
static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
 | 
			
		||||
 | 
			
		||||
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
 | 
			
		||||
  uint32_t crc = type;
 | 
			
		||||
 
 | 
			
		||||
@@ -184,7 +184,9 @@ void EthernetComponent::start_connect_() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_ETH);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "DHCPC stop error");
 | 
			
		||||
  if (err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) {
 | 
			
		||||
    ESPHL_ERROR_CHECK(err, "DHCPC stop error");
 | 
			
		||||
  }
 | 
			
		||||
  err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_ETH, &info);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,8 @@ from esphome.const import (
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_EXTERNAL_COMPONENTS,
 | 
			
		||||
    CONF_PATH,
 | 
			
		||||
    CONF_USERNAME,
 | 
			
		||||
    CONF_PASSWORD,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome import git, loader
 | 
			
		||||
@@ -27,6 +29,8 @@ TYPE_LOCAL = "local"
 | 
			
		||||
GIT_SCHEMA = {
 | 
			
		||||
    cv.Required(CONF_URL): cv.url,
 | 
			
		||||
    cv.Optional(CONF_REF): cv.git_ref,
 | 
			
		||||
    cv.Optional(CONF_USERNAME): cv.string,
 | 
			
		||||
    cv.Optional(CONF_PASSWORD): cv.string,
 | 
			
		||||
}
 | 
			
		||||
LOCAL_SCHEMA = {
 | 
			
		||||
    cv.Required(CONF_PATH): cv.directory,
 | 
			
		||||
@@ -99,6 +103,8 @@ def _process_git_config(config: dict, refresh) -> str:
 | 
			
		||||
        ref=config.get(CONF_REF),
 | 
			
		||||
        refresh=refresh,
 | 
			
		||||
        domain=DOMAIN,
 | 
			
		||||
        username=config.get(CONF_USERNAME),
 | 
			
		||||
        password=config.get(CONF_PASSWORD),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if (repo_dir / "esphome" / "components").is_dir():
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ void EZOSensor::update() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::loop() {
 | 
			
		||||
  uint8_t buf[20];
 | 
			
		||||
  uint8_t buf[21];
 | 
			
		||||
  if (!(this->state_ & EZO_STATE_WAIT)) {
 | 
			
		||||
    if (this->state_ & EZO_STATE_SEND_TEMP) {
 | 
			
		||||
      int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
 | 
			
		||||
@@ -74,7 +74,12 @@ void EZOSensor::loop() {
 | 
			
		||||
  if (buf[0] != 1)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 1).value_or(0);
 | 
			
		||||
  // some sensors return multiple comma-separated values, terminate string after first one
 | 
			
		||||
  for (size_t i = 1; i < sizeof(buf) - 1; i++)
 | 
			
		||||
    if (buf[i] == ',')
 | 
			
		||||
      buf[i] = '\0';
 | 
			
		||||
 | 
			
		||||
  float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 2).value_or(0);
 | 
			
		||||
  this->publish_state(val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -86,7 +86,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
 | 
			
		||||
    // Look back in trace data to best-fit into local range
 | 
			
		||||
    float mx = NAN;
 | 
			
		||||
    float mn = NAN;
 | 
			
		||||
    for (int16_t i = 0; i < this->width_; i++) {
 | 
			
		||||
    for (uint32_t i = 0; i < this->width_; i++) {
 | 
			
		||||
      for (auto *trace : traces_) {
 | 
			
		||||
        float v = trace->get_tracedata()->get_value(i);
 | 
			
		||||
        if (!std::isnan(v)) {
 | 
			
		||||
@@ -132,7 +132,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
 | 
			
		||||
  if (!std::isnan(this->gridspacing_y_)) {
 | 
			
		||||
    for (int y = yn; y <= ym; y++) {
 | 
			
		||||
      int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn)));
 | 
			
		||||
      for (int x = 0; x < this->width_; x += 2) {
 | 
			
		||||
      for (uint32_t x = 0; x < this->width_; x += 2) {
 | 
			
		||||
        buff->draw_pixel_at(x_offset + x, y_offset + py, color);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -147,7 +147,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
 | 
			
		||||
      ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines");
 | 
			
		||||
    }
 | 
			
		||||
    for (int i = 0; i <= n; i++) {
 | 
			
		||||
      for (int y = 0; y < this->height_; y += 2) {
 | 
			
		||||
      for (uint32_t y = 0; y < this->height_; y += 2) {
 | 
			
		||||
        buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -158,14 +158,14 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
 | 
			
		||||
  for (auto *trace : traces_) {
 | 
			
		||||
    Color c = trace->get_line_color();
 | 
			
		||||
    uint16_t thick = trace->get_line_thickness();
 | 
			
		||||
    for (int16_t i = 0; i < this->width_; i++) {
 | 
			
		||||
    for (uint32_t i = 0; i < this->width_; i++) {
 | 
			
		||||
      float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange;
 | 
			
		||||
      if (!std::isnan(v) && (thick > 0)) {
 | 
			
		||||
        int16_t x = this->width_ - 1 - i;
 | 
			
		||||
        uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick;
 | 
			
		||||
        if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
 | 
			
		||||
          int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2;
 | 
			
		||||
          for (int16_t t = 0; t < thick; t++) {
 | 
			
		||||
          for (uint16_t t = 0; t < thick; t++) {
 | 
			
		||||
            buff->draw_pixel_at(x_offset + x, y_offset + y + t, c);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
@@ -179,8 +179,8 @@ void GraphLegend::init(Graph *g) {
 | 
			
		||||
  parent_ = g;
 | 
			
		||||
 | 
			
		||||
  // Determine maximum expected text and value width / height
 | 
			
		||||
  int txtw = 0, txtos = 0, txtbl = 0, txth = 0;
 | 
			
		||||
  int valw = 0, valos = 0, valbl = 0, valh = 0;
 | 
			
		||||
  int txtw = 0, txth = 0;
 | 
			
		||||
  int valw = 0, valh = 0;
 | 
			
		||||
  int lt = 0;
 | 
			
		||||
  for (auto *trace : g->traces_) {
 | 
			
		||||
    std::string txtstr = trace->get_name();
 | 
			
		||||
@@ -320,7 +320,7 @@ void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_
 | 
			
		||||
 | 
			
		||||
    if (legend_->lines_) {
 | 
			
		||||
      uint16_t thick = trace->get_line_thickness();
 | 
			
		||||
      for (int16_t i = 0; i < legend_->x0_ * 4 / 3; i++) {
 | 
			
		||||
      for (int i = 0; i < legend_->x0_ * 4 / 3; i++) {
 | 
			
		||||
        uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick;
 | 
			
		||||
        if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
 | 
			
		||||
          buff->vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick,
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@ PROTOCOLS = {
 | 
			
		||||
    "gree": Protocol.PROTOCOL_GREE,
 | 
			
		||||
    "greeya": Protocol.PROTOCOL_GREEYAA,
 | 
			
		||||
    "greeyan": Protocol.PROTOCOL_GREEYAN,
 | 
			
		||||
    "greeyac": Protocol.PROTOCOL_GREEYAC,
 | 
			
		||||
    "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
 | 
			
		||||
    "hitachi": Protocol.PROTOCOL_HITACHI,
 | 
			
		||||
    "hyundai": Protocol.PROTOCOL_HYUNDAI,
 | 
			
		||||
@@ -111,4 +112,6 @@ def to_code(config):
 | 
			
		||||
    cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
 | 
			
		||||
    cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
 | 
			
		||||
 | 
			
		||||
    cg.add_library("tonia/HeatpumpIR", "1.0.15")
 | 
			
		||||
    # PIO isn't updating releases, so referencing the release tag directly. See:
 | 
			
		||||
    # https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
 | 
			
		||||
    cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18")
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
 | 
			
		||||
    {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT
 | 
			
		||||
    {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }},                        // NOLINT
 | 
			
		||||
    {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
@@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() {
 | 
			
		||||
  }
 | 
			
		||||
  this->heatpump_ir_ = protocol_constructor->second();
 | 
			
		||||
  climate_ir::ClimateIR::setup();
 | 
			
		||||
  if (this->sensor_) {
 | 
			
		||||
    this->sensor_->add_on_state_callback([this](float state) {
 | 
			
		||||
      this->current_temperature = state;
 | 
			
		||||
 | 
			
		||||
      IRSenderESPHome esp_sender(this->transmitter_);
 | 
			
		||||
      this->heatpump_ir_->send(esp_sender, uint8_t(lround(this->current_temperature + 0.5)));
 | 
			
		||||
 | 
			
		||||
      // current temperature changed, publish state
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
    });
 | 
			
		||||
    this->current_temperature = this->sensor_->state;
 | 
			
		||||
  } else
 | 
			
		||||
    this->current_temperature = NAN;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HeatpumpIRClimate::transmit_state() {
 | 
			
		||||
@@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() {
 | 
			
		||||
 | 
			
		||||
  temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_);
 | 
			
		||||
 | 
			
		||||
  IRSenderESPHome esp_sender(0, this->transmitter_);
 | 
			
		||||
 | 
			
		||||
  IRSenderESPHome esp_sender(this->transmitter_);
 | 
			
		||||
  heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd,
 | 
			
		||||
                     swing_h_cmd);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ enum Protocol {
 | 
			
		||||
  PROTOCOL_GREE,
 | 
			
		||||
  PROTOCOL_GREEYAA,
 | 
			
		||||
  PROTOCOL_GREEYAN,
 | 
			
		||||
  PROTOCOL_GREEYAC,
 | 
			
		||||
  PROTOCOL_HISENSE_AUD,
 | 
			
		||||
  PROTOCOL_HITACHI,
 | 
			
		||||
  PROTOCOL_HYUNDAI,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,8 @@ namespace heatpumpir {
 | 
			
		||||
 | 
			
		||||
class IRSenderESPHome : public IRSender {
 | 
			
		||||
 public:
 | 
			
		||||
  IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter)
 | 
			
		||||
      : IRSender(pin), transmit_(transmitter->transmit()){};
 | 
			
		||||
  IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter)
 | 
			
		||||
      : IRSender(0), transmit_(transmitter->transmit()){};
 | 
			
		||||
  void setFrequency(int frequency) override;  // NOLINT(readability-identifier-naming)
 | 
			
		||||
  void space(int space_length) override;
 | 
			
		||||
  void mark(int mark_length) override;
 | 
			
		||||
 
 | 
			
		||||
@@ -299,9 +299,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) {
 | 
			
		||||
      GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE);
 | 
			
		||||
  ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh);
 | 
			
		||||
 | 
			
		||||
  if ((swing_modeh & 0x7) == 0x0) {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
 | 
			
		||||
  } else if ((swing_modeh & 0x3) == 0x3) {
 | 
			
		||||
  if ((swing_modeh & 0x3) == 0x3) {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_OFF;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
 | 
			
		||||
 
 | 
			
		||||
@@ -300,9 +300,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) {
 | 
			
		||||
                                               HITACHI_AC424_SWINGH_SIZE);
 | 
			
		||||
  ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh);
 | 
			
		||||
 | 
			
		||||
  if ((swing_modeh & 0x7) == 0x0) {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
 | 
			
		||||
  } else if ((swing_modeh & 0x3) == 0x3) {
 | 
			
		||||
  if ((swing_modeh & 0x3) == 0x3) {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_OFF;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -13,5 +12,3 @@ class AbstractAQICalculator {
 | 
			
		||||
 | 
			
		||||
}  // namespace hm3301
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "abstract_aqi_calculator.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -33,7 +31,7 @@ class AQICalculator : public AbstractAQICalculator {
 | 
			
		||||
    int conc_lo = array[grid_index][0];
 | 
			
		||||
    int conc_hi = array[grid_index][1];
 | 
			
		||||
 | 
			
		||||
    return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
 | 
			
		||||
    return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
 | 
			
		||||
@@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator {
 | 
			
		||||
 | 
			
		||||
}  // namespace hm3301
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "caqi_calculator.h"
 | 
			
		||||
#include "aqi_calculator.h"
 | 
			
		||||
 | 
			
		||||
@@ -29,5 +27,3 @@ class AQICalculatorFactory {
 | 
			
		||||
 | 
			
		||||
}  // namespace hm3301
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "abstract_aqi_calculator.h"
 | 
			
		||||
 | 
			
		||||
@@ -37,9 +35,7 @@ class CAQICalculator : public AbstractAQICalculator {
 | 
			
		||||
    int conc_lo = array[grid_index][0];
 | 
			
		||||
    int conc_hi = array[grid_index][1];
 | 
			
		||||
 | 
			
		||||
    int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
 | 
			
		||||
 | 
			
		||||
    return aqi;
 | 
			
		||||
    return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
 | 
			
		||||
@@ -54,5 +50,3 @@ class CAQICalculator : public AbstractAQICalculator {
 | 
			
		||||
 | 
			
		||||
}  // namespace hm3301
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "hm3301.h"
 | 
			
		||||
 | 
			
		||||
@@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7;
 | 
			
		||||
 | 
			
		||||
void HM3301Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up HM3301...");
 | 
			
		||||
  hm3301_ = make_unique<HM330X>();
 | 
			
		||||
  error_code_ = hm3301_->init();
 | 
			
		||||
  if (error_code_ != NO_ERROR) {
 | 
			
		||||
  if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) {
 | 
			
		||||
    error_code_ = ERROR_COMM;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@@ -38,7 +35,7 @@ void HM3301Component::dump_config() {
 | 
			
		||||
float HM3301Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
void HM3301Component::update() {
 | 
			
		||||
  if (!this->read_sensor_value_(data_buffer_)) {
 | 
			
		||||
  if (this->read(data_buffer_, 29) != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "Read result failed");
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -87,8 +84,6 @@ void HM3301Component::update() {
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); }
 | 
			
		||||
 | 
			
		||||
bool HM3301Component::validate_checksum_(const uint8_t *data) {
 | 
			
		||||
  uint8_t sum = 0;
 | 
			
		||||
  for (int i = 0; i < 28; i++) {
 | 
			
		||||
@@ -104,5 +99,3 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) {
 | 
			
		||||
 | 
			
		||||
}  // namespace hm3301
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,15 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "aqi_calculator_factory.h"
 | 
			
		||||
 | 
			
		||||
#include <Seeed_HM330X.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hm3301 {
 | 
			
		||||
 | 
			
		||||
static const uint8_t SELECT_COMM_CMD = 0X88;
 | 
			
		||||
 | 
			
		||||
class HM3301Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  HM3301Component() = default;
 | 
			
		||||
@@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::unique_ptr<HM330X> hm3301_;
 | 
			
		||||
 | 
			
		||||
  HM330XErrorCode error_code_{NO_ERROR};
 | 
			
		||||
  enum {
 | 
			
		||||
    NO_ERROR = 0,
 | 
			
		||||
    ERROR_PARAM = -1,
 | 
			
		||||
    ERROR_COMM = -2,
 | 
			
		||||
    ERROR_OTHERS = -128,
 | 
			
		||||
  } error_code_{NO_ERROR};
 | 
			
		||||
 | 
			
		||||
  uint8_t data_buffer_[30];
 | 
			
		||||
 | 
			
		||||
@@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  AQICalculatorType aqi_calc_type_;
 | 
			
		||||
  AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory();
 | 
			
		||||
 | 
			
		||||
  bool read_sensor_value_(uint8_t *);
 | 
			
		||||
  bool validate_checksum_(const uint8_t *);
 | 
			
		||||
  uint16_t get_sensor_value_(const uint8_t *, uint8_t);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace hm3301
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x40)),
 | 
			
		||||
    _validate,
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -109,6 +108,3 @@ async def to_code(config):
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_AQI])
 | 
			
		||||
        cg.add(var.set_aqi_sensor(sens))
 | 
			
		||||
        cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE]))
 | 
			
		||||
 | 
			
		||||
    # https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301
 | 
			
		||||
    cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3")
 | 
			
		||||
 
 | 
			
		||||
@@ -114,8 +114,8 @@ CONFIG_SCHEMA = (
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def auto_data_rate(config):
 | 
			
		||||
    interval_sec = config[CONF_UPDATE_INTERVAL].seconds
 | 
			
		||||
    interval_hz = 1.0 / interval_sec
 | 
			
		||||
    interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds
 | 
			
		||||
    interval_hz = 1000.0 / interval_msec
 | 
			
		||||
    for datarate in sorted(HMC5883LDatarates.keys()):
 | 
			
		||||
        if float(datarate) >= interval_hz:
 | 
			
		||||
            return HMC5883LDatarates[datarate]
 | 
			
		||||
 
 | 
			
		||||
@@ -86,8 +86,8 @@ void ILI9341Display::update() {
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::display_() {
 | 
			
		||||
  // we will only update the changed window to the display
 | 
			
		||||
  int w = this->x_high_ - this->x_low_ + 1;
 | 
			
		||||
  int h = this->y_high_ - this->y_low_ + 1;
 | 
			
		||||
  uint16_t w = this->x_high_ - this->x_low_ + 1;
 | 
			
		||||
  uint16_t h = this->y_high_ - this->y_low_ + 1;
 | 
			
		||||
 | 
			
		||||
  set_addr_window_(this->x_low_, this->y_low_, w, h);
 | 
			
		||||
  this->start_data_();
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
@@ -1,93 +0,0 @@
 | 
			
		||||
#include "improv.h"
 | 
			
		||||
 | 
			
		||||
namespace improv {
 | 
			
		||||
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data) {
 | 
			
		||||
  return parse_improv_data(data.data(), data.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
 | 
			
		||||
  ImprovCommand improv_command;
 | 
			
		||||
  Command command = (Command) data[0];
 | 
			
		||||
  uint8_t data_length = data[1];
 | 
			
		||||
 | 
			
		||||
  if (data_length != length - 3) {
 | 
			
		||||
    improv_command.command = UNKNOWN;
 | 
			
		||||
    return improv_command;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t checksum = data[length - 1];
 | 
			
		||||
 | 
			
		||||
  uint32_t calculated_checksum = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < length - 1; i++) {
 | 
			
		||||
    calculated_checksum += data[i];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ((uint8_t) calculated_checksum != checksum) {
 | 
			
		||||
    improv_command.command = BAD_CHECKSUM;
 | 
			
		||||
    return improv_command;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (command == WIFI_SETTINGS) {
 | 
			
		||||
    uint8_t ssid_length = data[2];
 | 
			
		||||
    uint8_t ssid_start = 3;
 | 
			
		||||
    size_t ssid_end = ssid_start + ssid_length;
 | 
			
		||||
 | 
			
		||||
    uint8_t pass_length = data[ssid_end];
 | 
			
		||||
    size_t pass_start = ssid_end + 1;
 | 
			
		||||
    size_t pass_end = pass_start + pass_length;
 | 
			
		||||
 | 
			
		||||
    std::string ssid(data + ssid_start, data + ssid_end);
 | 
			
		||||
    std::string password(data + pass_start, data + pass_end);
 | 
			
		||||
    return {.command = command, .ssid = ssid, .password = password};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  improv_command.command = command;
 | 
			
		||||
  return improv_command;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum) {
 | 
			
		||||
  std::vector<uint8_t> out;
 | 
			
		||||
  uint32_t length = 0;
 | 
			
		||||
  out.push_back(command);
 | 
			
		||||
  for (auto str : datum) {
 | 
			
		||||
    uint8_t len = str.length();
 | 
			
		||||
    length += len;
 | 
			
		||||
    out.push_back(len);
 | 
			
		||||
    out.insert(out.end(), str.begin(), str.end());
 | 
			
		||||
  }
 | 
			
		||||
  out.insert(out.begin() + 1, length);
 | 
			
		||||
 | 
			
		||||
  uint32_t calculated_checksum = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t byte : out) {
 | 
			
		||||
    calculated_checksum += byte;
 | 
			
		||||
  }
 | 
			
		||||
  out.push_back(calculated_checksum);
 | 
			
		||||
  return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum) {
 | 
			
		||||
  std::vector<uint8_t> out;
 | 
			
		||||
  uint32_t length = 0;
 | 
			
		||||
  out.push_back(command);
 | 
			
		||||
  for (auto str : datum) {
 | 
			
		||||
    uint8_t len = str.length();
 | 
			
		||||
    length += len;
 | 
			
		||||
    out.push_back(len);
 | 
			
		||||
    out.insert(out.end(), str.begin(), str.end());
 | 
			
		||||
  }
 | 
			
		||||
  out.insert(out.begin() + 1, length);
 | 
			
		||||
 | 
			
		||||
  uint32_t calculated_checksum = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t byte : out) {
 | 
			
		||||
    calculated_checksum += byte;
 | 
			
		||||
  }
 | 
			
		||||
  out.push_back(calculated_checksum);
 | 
			
		||||
  return out;
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
}  // namespace improv
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO
 | 
			
		||||
#include "WString.h"
 | 
			
		||||
#endif  // ARDUINO
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace improv {
 | 
			
		||||
 | 
			
		||||
static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000";
 | 
			
		||||
static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001";
 | 
			
		||||
static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002";
 | 
			
		||||
static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003";
 | 
			
		||||
static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004";
 | 
			
		||||
static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005";
 | 
			
		||||
 | 
			
		||||
enum Error : uint8_t {
 | 
			
		||||
  ERROR_NONE = 0x00,
 | 
			
		||||
  ERROR_INVALID_RPC = 0x01,
 | 
			
		||||
  ERROR_UNKNOWN_RPC = 0x02,
 | 
			
		||||
  ERROR_UNABLE_TO_CONNECT = 0x03,
 | 
			
		||||
  ERROR_NOT_AUTHORIZED = 0x04,
 | 
			
		||||
  ERROR_UNKNOWN = 0xFF,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum State : uint8_t {
 | 
			
		||||
  STATE_STOPPED = 0x00,
 | 
			
		||||
  STATE_AWAITING_AUTHORIZATION = 0x01,
 | 
			
		||||
  STATE_AUTHORIZED = 0x02,
 | 
			
		||||
  STATE_PROVISIONING = 0x03,
 | 
			
		||||
  STATE_PROVISIONED = 0x04,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum Command : uint8_t {
 | 
			
		||||
  UNKNOWN = 0x00,
 | 
			
		||||
  WIFI_SETTINGS = 0x01,
 | 
			
		||||
  IDENTIFY = 0x02,
 | 
			
		||||
  GET_CURRENT_STATE = 0x02,
 | 
			
		||||
  GET_DEVICE_INFO = 0x03,
 | 
			
		||||
  BAD_CHECKSUM = 0xFF,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const uint8_t CAPABILITY_IDENTIFY = 0x01;
 | 
			
		||||
 | 
			
		||||
struct ImprovCommand {
 | 
			
		||||
  Command command;
 | 
			
		||||
  std::string ssid;
 | 
			
		||||
  std::string password;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data);
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum);
 | 
			
		||||
#ifdef ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum);
 | 
			
		||||
#endif  // ARDUINO
 | 
			
		||||
 | 
			
		||||
}  // namespace improv
 | 
			
		||||
@@ -5,7 +5,6 @@ import esphome.final_validate as fv
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
DEPENDENCIES = ["logger", "wifi"]
 | 
			
		||||
AUTO_LOAD = ["improv"]
 | 
			
		||||
 | 
			
		||||
improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
 | 
			
		||||
 | 
			
		||||
@@ -31,3 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    cg.add_library("esphome/Improv", "1.0.0")
 | 
			
		||||
 
 | 
			
		||||
@@ -98,13 +98,13 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
 | 
			
		||||
  std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
 | 
			
		||||
  urls.push_back(webserver_url);
 | 
			
		||||
#endif
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(command, urls);
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
 | 
			
		||||
  std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos);
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
 | 
			
		||||
  return data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -140,22 +140,33 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
 | 
			
		||||
  if (at < 8 + data_len)
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  if (at == 8 + data_len) {
 | 
			
		||||
  if (at == 8 + data_len)
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  if (at == 8 + data_len + 1) {
 | 
			
		||||
    uint8_t checksum = 0x00;
 | 
			
		||||
    for (size_t i = 0; i < at; i++)
 | 
			
		||||
      checksum += raw[i];
 | 
			
		||||
 | 
			
		||||
    if (checksum != byte) {
 | 
			
		||||
      ESP_LOGW(TAG, "Error decoding Improv payload");
 | 
			
		||||
      this->set_error_(improv::ERROR_INVALID_RPC);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (type == TYPE_RPC) {
 | 
			
		||||
      this->set_error_(improv::ERROR_NONE);
 | 
			
		||||
      auto command = improv::parse_improv_data(&raw[9], data_len);
 | 
			
		||||
      auto command = improv::parse_improv_data(&raw[9], data_len, false);
 | 
			
		||||
      return this->parse_improv_payload_(command);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
 | 
			
		||||
  // If we got here then the command coming is is improv, but not an RPC command
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
 | 
			
		||||
  switch (command.command) {
 | 
			
		||||
    case improv::BAD_CHECKSUM:
 | 
			
		||||
      ESP_LOGW(TAG, "Error decoding Improv payload");
 | 
			
		||||
      this->set_error_(improv::ERROR_INVALID_RPC);
 | 
			
		||||
      return false;
 | 
			
		||||
    case improv::WIFI_SETTINGS: {
 | 
			
		||||
      wifi::WiFiAP sta{};
 | 
			
		||||
      sta.set_ssid(command.ssid);
 | 
			
		||||
@@ -232,6 +243,12 @@ void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
 | 
			
		||||
  data[7] = TYPE_RPC_RESPONSE;
 | 
			
		||||
  data[8] = response.size();
 | 
			
		||||
  data.insert(data.end(), response.begin(), response.end());
 | 
			
		||||
 | 
			
		||||
  uint8_t checksum = 0x00;
 | 
			
		||||
  for (uint8_t d : data)
 | 
			
		||||
    checksum += d;
 | 
			
		||||
  data.push_back(checksum);
 | 
			
		||||
 | 
			
		||||
  this->write_data_(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/improv/improv.h"
 | 
			
		||||
#include "esphome/components/wifi/wifi_component.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include <improv.h>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#include <HardwareSerial.h>
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ void GPIOLCDDisplay::setup() {
 | 
			
		||||
  this->enable_pin_->setup();  // OUTPUT
 | 
			
		||||
  this->enable_pin_->digital_write(false);
 | 
			
		||||
 | 
			
		||||
  for (uint8_t i = 0; i < (this->is_four_bit_mode() ? 4 : 8); i++) {
 | 
			
		||||
  for (uint8_t i = 0; i < (uint8_t)(this->is_four_bit_mode() ? 4u : 8u); i++) {
 | 
			
		||||
    this->data_pins_[i]->setup();  // OUTPUT
 | 
			
		||||
    this->data_pins_[i]->digital_write(false);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -15,17 +15,37 @@ namespace ledc {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ledc.output";
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1;
 | 
			
		||||
#if SOC_LEDC_SUPPORT_HS_MODE
 | 
			
		||||
// Only ESP32 has LEDC_HIGH_SPEED_MODE
 | 
			
		||||
inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
 | 
			
		||||
#else
 | 
			
		||||
// S2, C3, S3 only support LEDC_LOW_SPEED_MODE
 | 
			
		||||
// See
 | 
			
		||||
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
 | 
			
		||||
inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
 | 
			
		||||
#endif
 | 
			
		||||
#else
 | 
			
		||||
static const int MAX_RES_BITS = 20;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); }
 | 
			
		||||
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) {
 | 
			
		||||
  const float max_div_num = ((1 << 20) - 1) / 256.0f;
 | 
			
		||||
 | 
			
		||||
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
 | 
			
		||||
  const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f);
 | 
			
		||||
  return 80e6f / (max_div_num * float(1 << bit_depth));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
 | 
			
		||||
  for (int i = 20; i >= 1; i--) {
 | 
			
		||||
    const float min_frequency = ledc_min_frequency_for_bit_depth(i);
 | 
			
		||||
  ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
 | 
			
		||||
  for (int i = MAX_RES_BITS; i >= 1; i--) {
 | 
			
		||||
    const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
 | 
			
		||||
    const float max_frequency = ledc_max_frequency_for_bit_depth(i);
 | 
			
		||||
    if (min_frequency <= frequency && frequency <= max_frequency)
 | 
			
		||||
    if (min_frequency <= frequency && frequency <= max_frequency) {
 | 
			
		||||
      ESP_LOGD(TAG, "Resolution calculated as %d", i);
 | 
			
		||||
      return i;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return {};
 | 
			
		||||
}
 | 
			
		||||
@@ -48,7 +68,7 @@ void LEDCOutput::write_state(float state) {
 | 
			
		||||
  ledcWrite(this->channel_, duty);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
 | 
			
		||||
  auto speed_mode = get_speed_mode(channel_);
 | 
			
		||||
  auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
 | 
			
		||||
  ledc_set_duty(speed_mode, chan_num, duty);
 | 
			
		||||
  ledc_update_duty(speed_mode, chan_num);
 | 
			
		||||
@@ -63,11 +83,15 @@ void LEDCOutput::setup() {
 | 
			
		||||
  ledcAttachPin(this->pin_->get_pin(), this->channel_);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
 | 
			
		||||
  auto speed_mode = get_speed_mode(channel_);
 | 
			
		||||
  auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
 | 
			
		||||
  auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
 | 
			
		||||
 | 
			
		||||
  bit_depth_ = *ledc_bit_depth_for_frequency(frequency_);
 | 
			
		||||
  if (bit_depth_ < 1) {
 | 
			
		||||
    ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_);
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ledc_timer_config_t timer_conf{};
 | 
			
		||||
  timer_conf.speed_mode = speed_mode;
 | 
			
		||||
@@ -114,7 +138,7 @@ void LEDCOutput::update_frequency(float frequency) {
 | 
			
		||||
    ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
 | 
			
		||||
  auto speed_mode = get_speed_mode(channel_);
 | 
			
		||||
  auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
 | 
			
		||||
 | 
			
		||||
  ledc_timer_config_t timer_conf{};
 | 
			
		||||
 
 | 
			
		||||
@@ -167,7 +167,7 @@ class AddressableScanEffect : public AddressableLightEffect {
 | 
			
		||||
    this->last_move_ = now;
 | 
			
		||||
 | 
			
		||||
    it.all() = Color::BLACK;
 | 
			
		||||
    for (auto i = 0; i < this->scan_width_; i++) {
 | 
			
		||||
    for (uint32_t i = 0; i < this->scan_width_; i++) {
 | 
			
		||||
      it[this->at_led_ + i] = current_color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -178,7 +178,7 @@ class AddressableScanEffect : public AddressableLightEffect {
 | 
			
		||||
  uint32_t move_interval_{};
 | 
			
		||||
  uint32_t scan_width_{1};
 | 
			
		||||
  uint32_t last_move_{0};
 | 
			
		||||
  int at_led_{0};
 | 
			
		||||
  uint32_t at_led_{0};
 | 
			
		||||
  bool direction_{true};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,7 @@ void LightCall::perform() {
 | 
			
		||||
    // EFFECT
 | 
			
		||||
    auto effect = this->effect_;
 | 
			
		||||
    const char *effect_s;
 | 
			
		||||
    if (effect == 0)
 | 
			
		||||
    if (effect == 0u)
 | 
			
		||||
      effect_s = "None";
 | 
			
		||||
    else
 | 
			
		||||
      effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,7 @@ void LTR390Component::read_mode_(int mode_index) {
 | 
			
		||||
 | 
			
		||||
    // If there are more modes to read then begin the next
 | 
			
		||||
    // otherwise stop
 | 
			
		||||
    if (mode_index + 1 < this->mode_funcs_.size()) {
 | 
			
		||||
    if (mode_index + 1 < (int) this->mode_funcs_.size()) {
 | 
			
		||||
      this->read_mode_(mode_index + 1);
 | 
			
		||||
    } else {
 | 
			
		||||
      this->reading_ = false;
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user