mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 08:31:47 +00:00 
			
		
		
		
	Compare commits
	
		
			265 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b89c04b928 | ||
| 
						 | 
					12090657bb | ||
| 
						 | 
					4e21cf0bdd | ||
| 
						 | 
					ba4ef72d56 | ||
| 
						 | 
					f3e6a4314f | ||
| 
						 | 
					e14ce3d950 | ||
| 
						 | 
					0f1e186189 | ||
| 
						 | 
					96d208e0d8 | ||
| 
						 | 
					38ed38864e | ||
| 
						 | 
					a12ba7bd38 | ||
| 
						 | 
					19fc1417ae | ||
| 
						 | 
					e2fefa51f5 | ||
| 
						 | 
					f668d5617f | ||
| 
						 | 
					47c4ff15d6 | ||
| 
						 | 
					ccf1bdc0b4 | ||
| 
						 | 
					e993fcf80c | ||
| 
						 | 
					1bdc30a09e | ||
| 
						 | 
					f56e89597f | ||
| 
						 | 
					9460fb28c4 | ||
| 
						 | 
					7207b9734f | ||
| 
						 | 
					3be3267d06 | ||
| 
						 | 
					98db604dba | ||
| 
						 | 
					ebf6f8c6de | ||
| 
						 | 
					2ebacad398 | ||
| 
						 | 
					53c59cf675 | ||
| 
						 | 
					9da261cb39 | ||
| 
						 | 
					9fbbcd6d8a | ||
| 
						 | 
					1a86167a47 | ||
| 
						 | 
					614ed7fd0c | ||
| 
						 | 
					4eb69d6af5 | ||
| 
						 | 
					0547f2a931 | ||
| 
						 | 
					b5fbe0b145 | ||
| 
						 | 
					443c3c2a56 | ||
| 
						 | 
					d75daa9644 | ||
| 
						 | 
					7963abb27a | ||
| 
						 | 
					0b9e8fda34 | ||
| 
						 | 
					a3cacc0c8b | ||
| 
						 | 
					5a4840f641 | ||
| 
						 | 
					3d7d689040 | ||
| 
						 | 
					b60c08dd28 | ||
| 
						 | 
					80bc567c31 | ||
| 
						 | 
					888ac2e180 | ||
| 
						 | 
					421ebcc8b2 | ||
| 
						 | 
					b56fa8c50a | ||
| 
						 | 
					42401775e1 | ||
| 
						 | 
					9c9bc58c16 | ||
| 
						 | 
					fbc129cccc | ||
| 
						 | 
					99638190cb | ||
| 
						 | 
					d78e9e6aa8 | ||
| 
						 | 
					878155a03d | ||
| 
						 | 
					9922eb83e2 | ||
| 
						 | 
					79f861f012 | ||
| 
						 | 
					4faa9d109e | ||
| 
						 | 
					28534ecc61 | ||
| 
						 | 
					616e0a21d8 | ||
| 
						 | 
					a546ffd490 | ||
| 
						 | 
					b5d0aede38 | ||
| 
						 | 
					a014d853a4 | ||
| 
						 | 
					c4ddf7697d | ||
| 
						 | 
					a2931b6774 | ||
| 
						 | 
					3ac7bf3761 | ||
| 
						 | 
					922344811f | ||
| 
						 | 
					cb2fcaa9b1 | ||
| 
						 | 
					1f50bd0649 | ||
| 
						 | 
					e4b2de5c68 | ||
| 
						 | 
					f862b479e7 | ||
| 
						 | 
					358c59bd8d | ||
| 
						 | 
					74fe135c9c | ||
| 
						 | 
					8d3896172d | ||
| 
						 | 
					9d9725144d | ||
| 
						 | 
					06f83bf1c0 | ||
| 
						 | 
					c2756d57d8 | ||
| 
						 | 
					56504692af | ||
| 
						 | 
					e542e75b9e | ||
| 
						 | 
					806e43c34c | ||
| 
						 | 
					29e7d00894 | ||
| 
						 | 
					9ee661c1e4 | ||
| 
						 | 
					36c0e2416d | ||
| 
						 | 
					e4ba3ff1db | ||
| 
						 | 
					be69b49880 | ||
| 
						 | 
					cc317d27f5 | ||
| 
						 | 
					c16709ed95 | ||
| 
						 | 
					e13eaf6706 | ||
| 
						 | 
					a1eb3b8475 | ||
| 
						 | 
					d52e425ba2 | ||
| 
						 | 
					dd8dc1ef1d | ||
| 
						 | 
					bc427de16a | ||
| 
						 | 
					db5988bbe1 | ||
| 
						 | 
					a3875af4b4 | ||
| 
						 | 
					d70e7da0ef | ||
| 
						 | 
					d42f35de5d | ||
| 
						 | 
					cd57469e06 | ||
| 
						 | 
					d98d6ff45f | ||
| 
						 | 
					14e38f0469 | ||
| 
						 | 
					f0f6d3f1cd | ||
| 
						 | 
					0b383542da | ||
| 
						 | 
					b2cec10601 | ||
| 
						 | 
					48658d5a55 | ||
| 
						 | 
					5207ca1d52 | ||
| 
						 | 
					7196fb8e82 | ||
| 
						 | 
					48ada2eebb | ||
| 
						 | 
					0de5808ed2 | ||
| 
						 | 
					ebc544e4b4 | ||
| 
						 | 
					a31fb3c987 | ||
| 
						 | 
					dfc7cd7f5d | ||
| 
						 | 
					a8bb2a42a1 | ||
| 
						 | 
					3d4c0e6667 | ||
| 
						 | 
					25fb288016 | ||
| 
						 | 
					1b8b8cdd11 | ||
| 
						 | 
					e6737479f7 | ||
| 
						 | 
					7f75832bf1 | ||
| 
						 | 
					33339e3bd8 | ||
| 
						 | 
					c298c1166f | ||
| 
						 | 
					c3d9eef01f | ||
| 
						 | 
					5ffdc66864 | ||
| 
						 | 
					2f50e18eb5 | ||
| 
						 | 
					9922c1503a | ||
| 
						 | 
					fce99d4b17 | ||
| 
						 | 
					11567085d8 | ||
| 
						 | 
					83f8e84247 | ||
| 
						 | 
					215107e8ea | ||
| 
						 | 
					d3f2b93c42 | ||
| 
						 | 
					11eb5cb0fa | ||
| 
						 | 
					9a7af97b2d | ||
| 
						 | 
					5e11469f50 | ||
| 
						 | 
					0c7a3d1fff | ||
| 
						 | 
					8a705bf4b0 | ||
| 
						 | 
					ee7102fcd1 | ||
| 
						 | 
					a44e38300b | ||
| 
						 | 
					b00e20c29f | ||
| 
						 | 
					6a89180deb | ||
| 
						 | 
					65d2b806cc | ||
| 
						 | 
					c149a3033c | ||
| 
						 | 
					4b7c233f1a | ||
| 
						 | 
					6e8e9c2aa9 | ||
| 
						 | 
					b6f628ee40 | ||
| 
						 | 
					bf79a700b7 | ||
| 
						 | 
					cdeb6e750f | ||
| 
						 | 
					d642aeba0f | ||
| 
						 | 
					6a6aee510d | ||
| 
						 | 
					ea17a92dbc | ||
| 
						 | 
					32a0a60480 | ||
| 
						 | 
					5a56644702 | ||
| 
						 | 
					29113808ee | ||
| 
						 | 
					dd226360bb | ||
| 
						 | 
					1a9aedf152 | ||
| 
						 | 
					d82c6df57e | ||
| 
						 | 
					7c91b4474a | ||
| 
						 | 
					a4f21db272 | ||
| 
						 | 
					58a8e1859e | ||
| 
						 | 
					2bed5b18c1 | ||
| 
						 | 
					fb5eb57345 | ||
| 
						 | 
					6471361715 | ||
| 
						 | 
					01687a9d57 | ||
| 
						 | 
					801fbf44c5 | ||
| 
						 | 
					ba1416cc0e | ||
| 
						 | 
					afc1c83af4 | ||
| 
						 | 
					da056866ff | ||
| 
						 | 
					336c2d34e6 | ||
| 
						 | 
					f3a969d35c | ||
| 
						 | 
					63db42a1d4 | ||
| 
						 | 
					c12dd77c64 | ||
| 
						 | 
					f58ffe41f8 | ||
| 
						 | 
					4f138c600b | ||
| 
						 | 
					445d2e372c | ||
| 
						 | 
					1087cb55b4 | ||
| 
						 | 
					600f4be2c4 | ||
| 
						 | 
					5e6665494d | ||
| 
						 | 
					2ef25f3153 | ||
| 
						 | 
					bc28ea1fde | ||
| 
						 | 
					623e31ddee | ||
| 
						 | 
					ceebe14628 | ||
| 
						 | 
					b29cc58144 | ||
| 
						 | 
					1b328da265 | ||
| 
						 | 
					06ca5354b2 | ||
| 
						 | 
					356efdb92c | ||
| 
						 | 
					bb5ab8b36d | ||
| 
						 | 
					6ecf4ecac6 | ||
| 
						 | 
					05ab49a615 | ||
| 
						 | 
					3773c385c7 | ||
| 
						 | 
					3227ef4bca | ||
| 
						 | 
					5a07e8d32b | ||
| 
						 | 
					29571a1acd | ||
| 
						 | 
					b8538c2c12 | ||
| 
						 | 
					7466773ac8 | ||
| 
						 | 
					b8ca40170e | ||
| 
						 | 
					bd86a0ac3b | ||
| 
						 | 
					df3f13ded8 | ||
| 
						 | 
					a428e2b689 | ||
| 
						 | 
					86407b9f6f | ||
| 
						 | 
					eceb79ceab | ||
| 
						 | 
					43fb68f8a0 | ||
| 
						 | 
					14e7b8a1ef | ||
| 
						 | 
					62459a8ae1 | ||
| 
						 | 
					86c0e6114f | ||
| 
						 | 
					1a9141877d | ||
| 
						 | 
					6ec18fc630 | ||
| 
						 | 
					4d674392e8 | ||
| 
						 | 
					6704b2cedf | ||
| 
						 | 
					fe4fb5f1ac | ||
| 
						 | 
					350d4e5071 | ||
| 
						 | 
					23f47d0ad2 | ||
| 
						 | 
					c037e95861 | ||
| 
						 | 
					2e1b35959f | ||
| 
						 | 
					7f46d9e0f9 | ||
| 
						 | 
					069b5f81a0 | ||
| 
						 | 
					3a36d0b13f | ||
| 
						 | 
					f98d93efa8 | ||
| 
						 | 
					91e037346b | ||
| 
						 | 
					8e1430243e | ||
| 
						 | 
					98b3d294aa | ||
| 
						 | 
					38a01988a5 | ||
| 
						 | 
					d16eff5039 | ||
| 
						 | 
					8fb481751f | ||
| 
						 | 
					ba6f89a757 | ||
| 
						 | 
					48e76e1538 | ||
| 
						 | 
					0e1d018ce3 | ||
| 
						 | 
					50fbbf2d3b | ||
| 
						 | 
					247916fe89 | ||
| 
						 | 
					ed801f7a27 | ||
| 
						 | 
					f68d577986 | ||
| 
						 | 
					5c49730cb9 | ||
| 
						 | 
					04c12823b5 | ||
| 
						 | 
					add40c7652 | ||
| 
						 | 
					f0760e99b7 | ||
| 
						 | 
					72391389a3 | ||
| 
						 | 
					e68beb8a43 | ||
| 
						 | 
					40e2832e67 | ||
| 
						 | 
					18fecf8c09 | ||
| 
						 | 
					414cf1b333 | ||
| 
						 | 
					d10f891f51 | ||
| 
						 | 
					36a1f6cfb1 | ||
| 
						 | 
					12bef16d54 | ||
| 
						 | 
					77db8c8401 | ||
| 
						 | 
					66eecd3675 | ||
| 
						 | 
					c03b1fae68 | ||
| 
						 | 
					37d55b55fc | ||
| 
						 | 
					9aed758d1b | ||
| 
						 | 
					dbe5587806 | ||
| 
						 | 
					30eec5adee | ||
| 
						 | 
					5307dfee21 | ||
| 
						 | 
					8b5b9e508b | ||
| 
						 | 
					a0d04ba091 | ||
| 
						 | 
					c02871fdfe | ||
| 
						 | 
					0d52f555b2 | ||
| 
						 | 
					025cf6320f | ||
| 
						 | 
					5997401e9e | ||
| 
						 | 
					b4068dac56 | ||
| 
						 | 
					458d6e24fc | ||
| 
						 | 
					4f4ca61ada | ||
| 
						 | 
					dfeeccfcca | ||
| 
						 | 
					3a101e8ec5 | ||
| 
						 | 
					7a2d7fdd19 | ||
| 
						 | 
					78f5c417a4 | ||
| 
						 | 
					b8c0f88440 | ||
| 
						 | 
					58eeb6b1b8 | ||
| 
						 | 
					f8acc45be4 | ||
| 
						 | 
					b7ab00b699 | ||
| 
						 | 
					045489e6d7 | ||
| 
						 | 
					b14e774a27 | ||
| 
						 | 
					2a8745d7e0 | ||
| 
						 | 
					499cb615f1 | ||
| 
						 | 
					4d192c7387 | ||
| 
						 | 
					9dd01b30bd | ||
| 
						 | 
					881cd535b9 | 
@@ -4,53 +4,60 @@
 | 
			
		||||
  "postCreateCommand": [
 | 
			
		||||
    "script/devcontainer-post-create"
 | 
			
		||||
  ],
 | 
			
		||||
  "containerEnv": {
 | 
			
		||||
    "DEVCONTAINER": "1"
 | 
			
		||||
  },
 | 
			
		||||
  "runArgs": [
 | 
			
		||||
    "--privileged",
 | 
			
		||||
    "-e",
 | 
			
		||||
    "ESPHOME_DASHBOARD_USE_PING=1"
 | 
			
		||||
  ],
 | 
			
		||||
  "appPort": 6052,
 | 
			
		||||
  "extensions": [
 | 
			
		||||
    // python
 | 
			
		||||
    "ms-python.python",
 | 
			
		||||
    "visualstudioexptteam.vscodeintellicode",
 | 
			
		||||
    // yaml
 | 
			
		||||
    "redhat.vscode-yaml",
 | 
			
		||||
    // cpp
 | 
			
		||||
    "ms-vscode.cpptools",
 | 
			
		||||
    // editorconfig
 | 
			
		||||
    "editorconfig.editorconfig",
 | 
			
		||||
  ],
 | 
			
		||||
  "settings": {
 | 
			
		||||
    "python.languageServer": "Pylance",
 | 
			
		||||
    "python.pythonPath": "/usr/bin/python3",
 | 
			
		||||
    "python.linting.pylintEnabled": true,
 | 
			
		||||
    "python.linting.enabled": true,
 | 
			
		||||
    "python.formatting.provider": "black",
 | 
			
		||||
    "editor.formatOnPaste": false,
 | 
			
		||||
    "editor.formatOnSave": true,
 | 
			
		||||
    "editor.formatOnType": true,
 | 
			
		||||
    "files.trimTrailingWhitespace": true,
 | 
			
		||||
    "terminal.integrated.defaultProfile.linux": "bash",
 | 
			
		||||
    "yaml.customTags": [
 | 
			
		||||
      "!secret scalar",
 | 
			
		||||
      "!lambda scalar",
 | 
			
		||||
      "!include_dir_named scalar",
 | 
			
		||||
      "!include_dir_list scalar",
 | 
			
		||||
      "!include_dir_merge_list scalar",
 | 
			
		||||
      "!include_dir_merge_named scalar"
 | 
			
		||||
    ],
 | 
			
		||||
    "files.exclude": {
 | 
			
		||||
      "**/.git": true,
 | 
			
		||||
      "**/.DS_Store": true,
 | 
			
		||||
      "**/*.pyc": {
 | 
			
		||||
        "when": "$(basename).py"
 | 
			
		||||
      },
 | 
			
		||||
      "**/__pycache__": true
 | 
			
		||||
    },
 | 
			
		||||
    "files.associations": {
 | 
			
		||||
      "**/.vscode/*.json": "jsonc"
 | 
			
		||||
    },
 | 
			
		||||
    "C_Cpp.clang_format_path": "/usr/bin/clang-format-11",
 | 
			
		||||
  "customizations": {
 | 
			
		||||
    "vscode": {
 | 
			
		||||
      "extensions": [
 | 
			
		||||
        // python
 | 
			
		||||
        "ms-python.python",
 | 
			
		||||
        "visualstudioexptteam.vscodeintellicode",
 | 
			
		||||
        // yaml
 | 
			
		||||
        "redhat.vscode-yaml",
 | 
			
		||||
        // cpp
 | 
			
		||||
        "ms-vscode.cpptools",
 | 
			
		||||
        // editorconfig
 | 
			
		||||
        "editorconfig.editorconfig",
 | 
			
		||||
      ],
 | 
			
		||||
      "settings": {
 | 
			
		||||
        "python.languageServer": "Pylance",
 | 
			
		||||
        "python.pythonPath": "/usr/bin/python3",
 | 
			
		||||
        "python.linting.pylintEnabled": true,
 | 
			
		||||
        "python.linting.enabled": true,
 | 
			
		||||
        "python.formatting.provider": "black",
 | 
			
		||||
        "editor.formatOnPaste": false,
 | 
			
		||||
        "editor.formatOnSave": true,
 | 
			
		||||
        "editor.formatOnType": true,
 | 
			
		||||
        "files.trimTrailingWhitespace": true,
 | 
			
		||||
        "terminal.integrated.defaultProfile.linux": "bash",
 | 
			
		||||
        "yaml.customTags": [
 | 
			
		||||
          "!secret scalar",
 | 
			
		||||
          "!lambda scalar",
 | 
			
		||||
          "!include_dir_named scalar",
 | 
			
		||||
          "!include_dir_list scalar",
 | 
			
		||||
          "!include_dir_merge_list scalar",
 | 
			
		||||
          "!include_dir_merge_named scalar"
 | 
			
		||||
        ],
 | 
			
		||||
        "files.exclude": {
 | 
			
		||||
          "**/.git": true,
 | 
			
		||||
          "**/.DS_Store": true,
 | 
			
		||||
          "**/*.pyc": {
 | 
			
		||||
            "when": "$(basename).py"
 | 
			
		||||
          },
 | 
			
		||||
          "**/__pycache__": true
 | 
			
		||||
        },
 | 
			
		||||
        "files.associations": {
 | 
			
		||||
          "**/.vscode/*.json": "jsonc"
 | 
			
		||||
        },
 | 
			
		||||
        "C_Cpp.clang_format_path": "/usr/bin/clang-format-13"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -23,6 +23,11 @@ permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
  packages: read
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  # yamllint disable-line rule:line-length
 | 
			
		||||
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
 | 
			
		||||
  cancel-in-progress: true
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  check-docker:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -7,6 +7,7 @@ on:
 | 
			
		||||
    branches: [dev, beta, release]
 | 
			
		||||
 | 
			
		||||
  pull_request:
 | 
			
		||||
  merge_group:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
@@ -40,6 +41,10 @@ jobs:
 | 
			
		||||
            file: tests/test3.yaml
 | 
			
		||||
            name: Test tests/test3.yaml
 | 
			
		||||
            pio_cache_key: test3
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test3.1.yaml
 | 
			
		||||
            name: Test tests/test3.1.yaml
 | 
			
		||||
            pio_cache_key: test3.1
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test4.yaml
 | 
			
		||||
            name: Test tests/test4.yaml
 | 
			
		||||
@@ -128,7 +133,7 @@ jobs:
 | 
			
		||||
      - name: Install clang tools
 | 
			
		||||
        run: |
 | 
			
		||||
          sudo apt-get install \
 | 
			
		||||
              clang-format-11 \
 | 
			
		||||
              clang-format-13 \
 | 
			
		||||
              clang-tidy-11
 | 
			
		||||
        if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
 | 
			
		||||
 | 
			
		||||
@@ -181,9 +186,22 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Run yamllint
 | 
			
		||||
        if: matrix.id == 'yamllint'
 | 
			
		||||
        uses: frenck/action-yamllint@v1.3.1
 | 
			
		||||
        uses: frenck/action-yamllint@v1.4.0
 | 
			
		||||
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        # yamllint disable-line rule:line-length
 | 
			
		||||
        if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python')
 | 
			
		||||
 | 
			
		||||
  ci-status:
 | 
			
		||||
    name: CI Status
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [ci]
 | 
			
		||||
    if: always()
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Successful deploy
 | 
			
		||||
        if: ${{ !(contains(needs.*.result, 'failure')) }}
 | 
			
		||||
        run: exit 0
 | 
			
		||||
      - name: Failing deploy
 | 
			
		||||
        if: ${{ contains(needs.*.result, 'failure') }}
 | 
			
		||||
        run: exit 1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,7 @@ jobs:
 | 
			
		||||
  stale:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/stale@v7
 | 
			
		||||
      - uses: actions/stale@v8
 | 
			
		||||
        with:
 | 
			
		||||
          days-before-pr-stale: 90
 | 
			
		||||
          days-before-pr-close: 7
 | 
			
		||||
@@ -38,7 +38,7 @@ jobs:
 | 
			
		||||
  close-issues:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/stale@v7
 | 
			
		||||
      - uses: actions/stale@v8
 | 
			
		||||
        with:
 | 
			
		||||
          days-before-pr-stale: -1
 | 
			
		||||
          days-before-pr-close: -1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
---
 | 
			
		||||
name: Synchronise Device Classes from Home Assistant
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '45 6 * * *'
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: write
 | 
			
		||||
  pull-requests: write
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  sync:
 | 
			
		||||
    name: Sync Device Classes
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
 | 
			
		||||
      - name: Checkout Home Assistant
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
        with:
 | 
			
		||||
          repository: home-assistant/core
 | 
			
		||||
          path: lib/home-assistant
 | 
			
		||||
 | 
			
		||||
      - name: Setup Python
 | 
			
		||||
        uses: actions/setup-python@v4
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: 3.11
 | 
			
		||||
 | 
			
		||||
      - name: Install Home Assistant
 | 
			
		||||
        run: |
 | 
			
		||||
          python -m pip install --upgrade pip
 | 
			
		||||
          pip install -e lib/home-assistant
 | 
			
		||||
 | 
			
		||||
      - name: Sync
 | 
			
		||||
        run: |
 | 
			
		||||
          python ./script/sync-device_class.py
 | 
			
		||||
 | 
			
		||||
      - name: Get PR template
 | 
			
		||||
        id: pr-template-body
 | 
			
		||||
        run: |
 | 
			
		||||
          body=$(cat .github/PULL_REQUEST_TEMPLATE.md)
 | 
			
		||||
          delimiter="$(openssl rand -hex 8)"
 | 
			
		||||
          echo "body<<$delimiter" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "$body" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "$delimiter" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - name: Commit changes
 | 
			
		||||
        uses: peter-evans/create-pull-request@v4
 | 
			
		||||
        with:
 | 
			
		||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          committer: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
          author: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
          branch: sync/device-classes/
 | 
			
		||||
          branch-suffix: timestamp
 | 
			
		||||
          delete-branch: true
 | 
			
		||||
          title: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          body: ${{ steps.pr-template-body.outputs.body }}
 | 
			
		||||
@@ -2,8 +2,8 @@
 | 
			
		||||
# See https://pre-commit.com for more information
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/ambv/black
 | 
			
		||||
    rev: 22.12.0
 | 
			
		||||
  - repo: https://github.com/psf/black
 | 
			
		||||
    rev: 23.3.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: black
 | 
			
		||||
        args:
 | 
			
		||||
@@ -27,7 +27,7 @@ repos:
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
  - repo: https://github.com/asottile/pyupgrade
 | 
			
		||||
    rev: v3.3.0
 | 
			
		||||
    rev: v3.3.1
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -2,15 +2,24 @@
 | 
			
		||||
  "version": "2.0.0",
 | 
			
		||||
  "tasks": [
 | 
			
		||||
    {
 | 
			
		||||
      "label": "run",
 | 
			
		||||
      "label": "Run Dashboard",
 | 
			
		||||
      "type": "shell",
 | 
			
		||||
      "command": "python3 -m esphome dashboard config/",
 | 
			
		||||
      "command": "${command:python.interpreterPath}",
 | 
			
		||||
      "args": [
 | 
			
		||||
        "-m",
 | 
			
		||||
        "esphome",
 | 
			
		||||
        "dashboard",
 | 
			
		||||
        "config/"
 | 
			
		||||
      ],
 | 
			
		||||
      "problemMatcher": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "label": "clang-tidy",
 | 
			
		||||
      "type": "shell",
 | 
			
		||||
      "command": "./script/clang-tidy",
 | 
			
		||||
      "command": "${command:python.interpreterPath}",
 | 
			
		||||
      "args": [
 | 
			
		||||
        "./script/clang-tidy"
 | 
			
		||||
      ],
 | 
			
		||||
      "problemMatcher": [
 | 
			
		||||
        {
 | 
			
		||||
          "owner": "clang-tidy",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -11,6 +11,7 @@ esphome/*.py @esphome/core
 | 
			
		||||
esphome/core/* @esphome/core
 | 
			
		||||
 | 
			
		||||
# Integrations
 | 
			
		||||
esphome/components/absolute_humidity/* @DAVe3283
 | 
			
		||||
esphome/components/ac_dimmer/* @glmnet
 | 
			
		||||
esphome/components/adc/* @esphome/core
 | 
			
		||||
esphome/components/adc128s102/* @DeerMaximum
 | 
			
		||||
@@ -24,6 +25,7 @@ esphome/components/analog_threshold/* @ianchi
 | 
			
		||||
esphome/components/animation/* @syndlex
 | 
			
		||||
esphome/components/anova/* @buxtronix
 | 
			
		||||
esphome/components/api/* @OttoWinter
 | 
			
		||||
esphome/components/as7341/* @mrgnr
 | 
			
		||||
esphome/components/async_tcp/* @OttoWinter
 | 
			
		||||
esphome/components/atc_mithermometer/* @ahpohl
 | 
			
		||||
esphome/components/b_parasite/* @rbaron
 | 
			
		||||
@@ -90,11 +92,13 @@ esphome/components/factory_reset/* @anatoly-savchenkov
 | 
			
		||||
esphome/components/fastled_base/* @OttoWinter
 | 
			
		||||
esphome/components/feedback/* @ianchi
 | 
			
		||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
 | 
			
		||||
esphome/components/fs3000/* @kahrendt
 | 
			
		||||
esphome/components/globals/* @esphome/core
 | 
			
		||||
esphome/components/gpio/* @esphome/core
 | 
			
		||||
esphome/components/gps/* @coogle
 | 
			
		||||
esphome/components/graph/* @synco
 | 
			
		||||
esphome/components/growatt_solar/* @leeuwte
 | 
			
		||||
esphome/components/haier/* @Yarikx
 | 
			
		||||
esphome/components/havells_solar/* @sourabhjaiswal
 | 
			
		||||
esphome/components/hbridge/fan/* @WeekendWarrior
 | 
			
		||||
esphome/components/hbridge/light/* @DotNetDann
 | 
			
		||||
@@ -107,17 +111,22 @@ esphome/components/hte501/* @Stock-M
 | 
			
		||||
esphome/components/hydreon_rgxx/* @functionpointer
 | 
			
		||||
esphome/components/i2c/* @esphome/core
 | 
			
		||||
esphome/components/i2s_audio/* @jesserockz
 | 
			
		||||
esphome/components/i2s_audio/media_player/* @jesserockz
 | 
			
		||||
esphome/components/i2s_audio/microphone/* @jesserockz
 | 
			
		||||
esphome/components/ili9xxx/* @nielsnl68
 | 
			
		||||
esphome/components/improv_base/* @esphome/core
 | 
			
		||||
esphome/components/improv_serial/* @esphome/core
 | 
			
		||||
esphome/components/ina260/* @MrEditor97
 | 
			
		||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
 | 
			
		||||
esphome/components/inkplate6/* @jesserockz
 | 
			
		||||
esphome/components/integration/* @OttoWinter
 | 
			
		||||
esphome/components/internal_temperature/* @Mat931
 | 
			
		||||
esphome/components/interval/* @esphome/core
 | 
			
		||||
esphome/components/json/* @OttoWinter
 | 
			
		||||
esphome/components/kalman_combinator/* @Cat-Ion
 | 
			
		||||
esphome/components/key_collector/* @ssieb
 | 
			
		||||
esphome/components/key_provider/* @ssieb
 | 
			
		||||
esphome/components/kuntze/* @ssieb
 | 
			
		||||
esphome/components/lcd_menu/* @numo68
 | 
			
		||||
esphome/components/ld2410/* @sebcaps
 | 
			
		||||
esphome/components/ledc/* @OttoWinter
 | 
			
		||||
@@ -147,11 +156,13 @@ esphome/components/mcp9808/* @k7hpn
 | 
			
		||||
esphome/components/md5/* @esphome/core
 | 
			
		||||
esphome/components/mdns/* @esphome/core
 | 
			
		||||
esphome/components/media_player/* @jesserockz
 | 
			
		||||
esphome/components/microphone/* @jesserockz
 | 
			
		||||
esphome/components/mics_4514/* @jesserockz
 | 
			
		||||
esphome/components/midea/* @dudanov
 | 
			
		||||
esphome/components/midea_ir/* @dudanov
 | 
			
		||||
esphome/components/mitsubishi/* @RubyBailey
 | 
			
		||||
esphome/components/mlx90393/* @functionpointer
 | 
			
		||||
esphome/components/mmc5603/* @benhoff
 | 
			
		||||
esphome/components/modbus_controller/* @martgras
 | 
			
		||||
esphome/components/modbus_controller/binary_sensor/* @martgras
 | 
			
		||||
esphome/components/modbus_controller/number/* @martgras
 | 
			
		||||
@@ -160,8 +171,9 @@ esphome/components/modbus_controller/select/* @martgras @stegm
 | 
			
		||||
esphome/components/modbus_controller/sensor/* @martgras
 | 
			
		||||
esphome/components/modbus_controller/switch/* @martgras
 | 
			
		||||
esphome/components/modbus_controller/text_sensor/* @martgras
 | 
			
		||||
esphome/components/mopeka_ble/* @spbrogan
 | 
			
		||||
esphome/components/mopeka_ble/* @Fabian-Schmidt @spbrogan
 | 
			
		||||
esphome/components/mopeka_pro_check/* @spbrogan
 | 
			
		||||
esphome/components/mopeka_std_check/* @Fabian-Schmidt
 | 
			
		||||
esphome/components/mpl3115a2/* @kbickar
 | 
			
		||||
esphome/components/mpu6886/* @fabaff
 | 
			
		||||
esphome/components/network/* @esphome/core
 | 
			
		||||
@@ -208,6 +220,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
			
		||||
esphome/components/sdp3x/* @Azimath
 | 
			
		||||
esphome/components/selec_meter/* @sourabhjaiswal
 | 
			
		||||
esphome/components/select/* @esphome/core
 | 
			
		||||
esphome/components/sen21231/* @shreyaskarnik
 | 
			
		||||
esphome/components/sen5x/* @martgras
 | 
			
		||||
esphome/components/sensirion_common/* @martgras
 | 
			
		||||
esphome/components/sensor/* @esphome/core
 | 
			
		||||
@@ -277,6 +290,7 @@ esphome/components/ufire_ise/* @pvizeli
 | 
			
		||||
esphome/components/ultrasonic/* @OttoWinter
 | 
			
		||||
esphome/components/vbus/* @ssieb
 | 
			
		||||
esphome/components/version/* @esphome/core
 | 
			
		||||
esphome/components/voice_assistant/* @jesserockz
 | 
			
		||||
esphome/components/wake_on_lan/* @willwill2will54
 | 
			
		||||
esphome/components/web_server_base/* @OttoWinter
 | 
			
		||||
esphome/components/whirlpool/* @glmnet
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
include LICENSE
 | 
			
		||||
include README.md
 | 
			
		||||
include requirements.txt
 | 
			
		||||
include esphome/dashboard/templates/*.html
 | 
			
		||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
 | 
			
		||||
recursive-include esphome *.cpp *.h *.tcc
 | 
			
		||||
recursive-include esphome *.cpp *.h *.tcc *.c
 | 
			
		||||
recursive-include esphome *.py.script
 | 
			
		||||
recursive-include esphome LICENSE.txt
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,9 @@
 | 
			
		||||
ARG BASEIMGTYPE=docker
 | 
			
		||||
 | 
			
		||||
# https://github.com/hassio-addons/addon-debian-base/releases
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base:6.2.0 AS base-hassio
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base:6.2.3 AS base-hassio
 | 
			
		||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
 | 
			
		||||
FROM debian:bullseye-20221024-slim AS base-docker
 | 
			
		||||
FROM debian:bullseye-20230208-slim AS base-docker
 | 
			
		||||
 | 
			
		||||
FROM base-${BASEIMGTYPE} AS base
 | 
			
		||||
 | 
			
		||||
@@ -26,7 +26,7 @@ RUN \
 | 
			
		||||
        python3-cryptography=3.3.2-1 \
 | 
			
		||||
        iputils-ping=3:20210202-1 \
 | 
			
		||||
        git=1:2.30.2-1 \
 | 
			
		||||
        curl=7.74.0-1.3+deb11u5 \
 | 
			
		||||
        curl=7.74.0-1.3+deb11u7 \
 | 
			
		||||
        openssh-client=1:8.4p1-5+deb11u1 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
@@ -51,7 +51,7 @@ RUN \
 | 
			
		||||
    # Ubuntu python3-pip is missing wheel
 | 
			
		||||
    pip3 install --no-cache-dir \
 | 
			
		||||
        wheel==0.37.1 \
 | 
			
		||||
        platformio==6.1.5 \
 | 
			
		||||
        platformio==6.1.6 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
@@ -135,7 +135,7 @@ RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
    # Use pinned versions so that we get updates with build caching
 | 
			
		||||
    && apt-get install -y --no-install-recommends \
 | 
			
		||||
        clang-format-11=1:11.0.1-2 \
 | 
			
		||||
        clang-format-13=1:13.0.1-6~deb11u1 \
 | 
			
		||||
        clang-tidy-11=1:11.0.1-2 \
 | 
			
		||||
        patch=2.7.6-7 \
 | 
			
		||||
        software-properties-common=0.96.20.2-2.1 \
 | 
			
		||||
 
 | 
			
		||||
@@ -152,6 +152,8 @@ def run_miniterm(config, port):
 | 
			
		||||
        _LOGGER.error("Could not connect to serial port %s", port)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wrap_to_code(name, comp):
 | 
			
		||||
    coro = coroutine(comp.to_code)
 | 
			
		||||
 
 | 
			
		||||
@@ -254,7 +254,11 @@ async def repeat_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
 | 
			
		||||
    cg.add(var.set_count(count_template))
 | 
			
		||||
    actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
			
		||||
    actions = await build_action_list(
 | 
			
		||||
        config[CONF_THEN],
 | 
			
		||||
        cg.TemplateArguments(cg.uint32, *template_arg.args),
 | 
			
		||||
        [(cg.uint32, "iteration"), *args],
 | 
			
		||||
    )
 | 
			
		||||
    cg.add(var.add_then(actions))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,7 @@ from esphome.cpp_helpers import (  # noqa
 | 
			
		||||
    build_registry_list,
 | 
			
		||||
    extract_registry_entry_config,
 | 
			
		||||
    register_parented,
 | 
			
		||||
    past_safe_mode,
 | 
			
		||||
)
 | 
			
		||||
from esphome.cpp_types import (  # noqa
 | 
			
		||||
    global_ns,
 | 
			
		||||
@@ -63,6 +64,7 @@ from esphome.cpp_types import (  # noqa
 | 
			
		||||
    uint16,
 | 
			
		||||
    uint32,
 | 
			
		||||
    uint64,
 | 
			
		||||
    int16,
 | 
			
		||||
    int32,
 | 
			
		||||
    int64,
 | 
			
		||||
    size_t,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								esphome/components/absolute_humidity/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/absolute_humidity/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
CODEOWNERS = ["@DAVe3283"]
 | 
			
		||||
							
								
								
									
										182
									
								
								esphome/components/absolute_humidity/absolute_humidity.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								esphome/components/absolute_humidity/absolute_humidity.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "absolute_humidity.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace absolute_humidity {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "absolute_humidity.sensor";
 | 
			
		||||
 | 
			
		||||
void AbsoluteHumidityComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up absolute humidity '%s'...", this->get_name().c_str());
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "  Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
 | 
			
		||||
  this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
 | 
			
		||||
  if (this->temperature_sensor_->has_state()) {
 | 
			
		||||
    this->temperature_callback_(this->temperature_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "  Added callback for relative humidity '%s'", this->humidity_sensor_->get_name().c_str());
 | 
			
		||||
  this->humidity_sensor_->add_on_state_callback([this](float state) { this->humidity_callback_(state); });
 | 
			
		||||
  if (this->humidity_sensor_->has_state()) {
 | 
			
		||||
    this->humidity_callback_(this->humidity_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AbsoluteHumidityComponent::dump_config() {
 | 
			
		||||
  LOG_SENSOR("", "Absolute Humidity", this);
 | 
			
		||||
 | 
			
		||||
  switch (this->equation_) {
 | 
			
		||||
    case BUCK:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Buck");
 | 
			
		||||
      break;
 | 
			
		||||
    case TETENS:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Tetens");
 | 
			
		||||
      break;
 | 
			
		||||
    case WOBUS:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Wobus");
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Sources");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Temperature: '%s'", this->temperature_sensor_->get_name().c_str());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Relative Humidity: '%s'", this->humidity_sensor_->get_name().c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
void AbsoluteHumidityComponent::loop() {
 | 
			
		||||
  if (!this->next_update_) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->next_update_ = false;
 | 
			
		||||
 | 
			
		||||
  // Ensure we have source data
 | 
			
		||||
  const bool no_temperature = std::isnan(this->temperature_);
 | 
			
		||||
  const bool no_humidity = std::isnan(this->humidity_);
 | 
			
		||||
  if (no_temperature || no_humidity) {
 | 
			
		||||
    if (no_temperature) {
 | 
			
		||||
      ESP_LOGW(TAG, "No valid state from temperature sensor!");
 | 
			
		||||
    }
 | 
			
		||||
    if (no_humidity) {
 | 
			
		||||
      ESP_LOGW(TAG, "No valid state from temperature sensor!");
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
 | 
			
		||||
    this->publish_state(NAN);
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Convert to desired units
 | 
			
		||||
  const float temperature_c = this->temperature_;
 | 
			
		||||
  const float temperature_k = temperature_c + 273.15;
 | 
			
		||||
  const float hr = this->humidity_ / 100;
 | 
			
		||||
 | 
			
		||||
  // Calculate saturation vapor pressure
 | 
			
		||||
  float es;
 | 
			
		||||
  switch (this->equation_) {
 | 
			
		||||
    case BUCK:
 | 
			
		||||
      es = es_buck(temperature_c);
 | 
			
		||||
      break;
 | 
			
		||||
    case TETENS:
 | 
			
		||||
      es = es_tetens(temperature_c);
 | 
			
		||||
      break;
 | 
			
		||||
    case WOBUS:
 | 
			
		||||
      es = es_wobus(temperature_c);
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
 | 
			
		||||
      this->publish_state(NAN);
 | 
			
		||||
      this->status_set_error();
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
 | 
			
		||||
 | 
			
		||||
  // Calculate absolute humidity
 | 
			
		||||
  const float absolute_humidity = vapor_density(es, hr, temperature_k);
 | 
			
		||||
 | 
			
		||||
  // Publish absolute humidity
 | 
			
		||||
  ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity);
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
  this->publish_state(absolute_humidity);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Buck equation (https://en.wikipedia.org/wiki/Arden_Buck_equation)
 | 
			
		||||
// More accurate than Tetens in normal meteorologic conditions
 | 
			
		||||
float AbsoluteHumidityComponent::es_buck(float temperature_c) {
 | 
			
		||||
  float a, b, c, d;
 | 
			
		||||
  if (temperature_c >= 0) {
 | 
			
		||||
    a = 0.61121;
 | 
			
		||||
    b = 18.678;
 | 
			
		||||
    c = 234.5;
 | 
			
		||||
    d = 257.14;
 | 
			
		||||
  } else {
 | 
			
		||||
    a = 0.61115;
 | 
			
		||||
    b = 18.678;
 | 
			
		||||
    c = 233.7;
 | 
			
		||||
    d = 279.82;
 | 
			
		||||
  }
 | 
			
		||||
  return a * expf((b - (temperature_c / c)) * (temperature_c / (d + temperature_c)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Tetens equation (https://en.wikipedia.org/wiki/Tetens_equation)
 | 
			
		||||
float AbsoluteHumidityComponent::es_tetens(float temperature_c) {
 | 
			
		||||
  float a, b;
 | 
			
		||||
  if (temperature_c >= 0) {
 | 
			
		||||
    a = 17.27;
 | 
			
		||||
    b = 237.3;
 | 
			
		||||
  } else {
 | 
			
		||||
    a = 21.875;
 | 
			
		||||
    b = 265.5;
 | 
			
		||||
  }
 | 
			
		||||
  return 0.61078 * expf((a * temperature_c) / (temperature_c + b));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Wobus equation
 | 
			
		||||
// https://wahiduddin.net/calc/density_altitude.htm
 | 
			
		||||
// https://wahiduddin.net/calc/density_algorithms.htm
 | 
			
		||||
// Calculate the saturation vapor pressure (kPa)
 | 
			
		||||
float AbsoluteHumidityComponent::es_wobus(float t) {
 | 
			
		||||
  // THIS FUNCTION RETURNS THE SATURATION VAPOR PRESSURE ESW (MILLIBARS)
 | 
			
		||||
  // OVER LIQUID WATER GIVEN THE TEMPERATURE T (CELSIUS). THE POLYNOMIAL
 | 
			
		||||
  // APPROXIMATION BELOW IS DUE TO HERMAN WOBUS, A MATHEMATICIAN WHO
 | 
			
		||||
  // WORKED AT THE NAVY WEATHER RESEARCH FACILITY, NORFOLK, VIRGINIA,
 | 
			
		||||
  // BUT WHO IS NOW RETIRED. THE COEFFICIENTS OF THE POLYNOMIAL WERE
 | 
			
		||||
  // CHOSEN TO FIT THE VALUES IN TABLE 94 ON PP. 351-353 OF THE SMITH-
 | 
			
		||||
  // SONIAN METEOROLOGICAL TABLES BY ROLAND LIST (6TH EDITION). THE
 | 
			
		||||
  // APPROXIMATION IS VALID FOR -50 < T < 100C.
 | 
			
		||||
  //
 | 
			
		||||
  //     Baker, Schlatter  17-MAY-1982     Original version.
 | 
			
		||||
 | 
			
		||||
  const float c0 = +0.99999683e00;
 | 
			
		||||
  const float c1 = -0.90826951e-02;
 | 
			
		||||
  const float c2 = +0.78736169e-04;
 | 
			
		||||
  const float c3 = -0.61117958e-06;
 | 
			
		||||
  const float c4 = +0.43884187e-08;
 | 
			
		||||
  const float c5 = -0.29883885e-10;
 | 
			
		||||
  const float c6 = +0.21874425e-12;
 | 
			
		||||
  const float c7 = -0.17892321e-14;
 | 
			
		||||
  const float c8 = +0.11112018e-16;
 | 
			
		||||
  const float c9 = -0.30994571e-19;
 | 
			
		||||
  const float p = c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))));
 | 
			
		||||
  return 0.61078 / pow(p, 8);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/
 | 
			
		||||
// H/T to https://esphome.io/cookbook/bme280_environment.html
 | 
			
		||||
// H/T to https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
 | 
			
		||||
float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) {
 | 
			
		||||
  // es = saturated vapor pressure (kPa)
 | 
			
		||||
  // hr = relative humidity [0-1]
 | 
			
		||||
  // ta = absolute temperature (K)
 | 
			
		||||
 | 
			
		||||
  const float ea = hr * es * 1000;   // vapor pressure of the air (Pa)
 | 
			
		||||
  const float mw = 18.01528;         // molar mass of water (g⋅mol⁻¹)
 | 
			
		||||
  const float r = 8.31446261815324;  // molar gas constant (J⋅K⁻¹)
 | 
			
		||||
  return (ea * mw) / (r * ta);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace absolute_humidity
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										76
									
								
								esphome/components/absolute_humidity/absolute_humidity.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								esphome/components/absolute_humidity/absolute_humidity.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace absolute_humidity {
 | 
			
		||||
 | 
			
		||||
/// Enum listing all implemented saturation vapor pressure equations.
 | 
			
		||||
enum SaturationVaporPressureEquation {
 | 
			
		||||
  BUCK,
 | 
			
		||||
  TETENS,
 | 
			
		||||
  WOBUS,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// This class implements calculation of absolute humidity from temperature and relative humidity.
 | 
			
		||||
class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  AbsoluteHumidityComponent() = default;
 | 
			
		||||
 | 
			
		||||
  void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
 | 
			
		||||
  void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
 | 
			
		||||
  void set_equation(SaturationVaporPressureEquation equation) { this->equation_ = equation; }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void temperature_callback_(float state) {
 | 
			
		||||
    this->next_update_ = true;
 | 
			
		||||
    this->temperature_ = state;
 | 
			
		||||
  }
 | 
			
		||||
  void humidity_callback_(float state) {
 | 
			
		||||
    this->next_update_ = true;
 | 
			
		||||
    this->humidity_ = state;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Buck equation for saturation vapor pressure in kPa.
 | 
			
		||||
   *
 | 
			
		||||
   * @param temperature_c Air temperature in °C.
 | 
			
		||||
   */
 | 
			
		||||
  static float es_buck(float temperature_c);
 | 
			
		||||
  /** Tetens equation for saturation vapor pressure in kPa.
 | 
			
		||||
   *
 | 
			
		||||
   * @param temperature_c Air temperature in °C.
 | 
			
		||||
   */
 | 
			
		||||
  static float es_tetens(float temperature_c);
 | 
			
		||||
  /** Wobus equation for saturation vapor pressure in kPa.
 | 
			
		||||
   *
 | 
			
		||||
   * @param temperature_c Air temperature in °C.
 | 
			
		||||
   */
 | 
			
		||||
  static float es_wobus(float temperature_c);
 | 
			
		||||
 | 
			
		||||
  /** Calculate vapor density (absolute humidity) in g/m³.
 | 
			
		||||
   *
 | 
			
		||||
   * @param es Saturation vapor pressure in kPa.
 | 
			
		||||
   * @param hr Relative humidity 0 to 1.
 | 
			
		||||
   * @param ta Absolute temperature in K.
 | 
			
		||||
   * @param heater_duration The duration in ms that the heater should turn on for when measuring.
 | 
			
		||||
   */
 | 
			
		||||
  static float vapor_density(float es, float hr, float ta);
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *temperature_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *humidity_sensor_{nullptr};
 | 
			
		||||
 | 
			
		||||
  bool next_update_{false};
 | 
			
		||||
 | 
			
		||||
  float temperature_{NAN};
 | 
			
		||||
  float humidity_{NAN};
 | 
			
		||||
  SaturationVaporPressureEquation equation_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace absolute_humidity
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										56
									
								
								esphome/components/absolute_humidity/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/absolute_humidity/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    CONF_EQUATION,
 | 
			
		||||
    ICON_WATER,
 | 
			
		||||
    UNIT_GRAMS_PER_CUBIC_METER,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
absolute_humidity_ns = cg.esphome_ns.namespace("absolute_humidity")
 | 
			
		||||
AbsoluteHumidityComponent = absolute_humidity_ns.class_(
 | 
			
		||||
    "AbsoluteHumidityComponent", sensor.Sensor, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
SaturationVaporPressureEquation = absolute_humidity_ns.enum(
 | 
			
		||||
    "SaturationVaporPressureEquation"
 | 
			
		||||
)
 | 
			
		||||
EQUATION = {
 | 
			
		||||
    "BUCK": SaturationVaporPressureEquation.BUCK,
 | 
			
		||||
    "TETENS": SaturationVaporPressureEquation.TETENS,
 | 
			
		||||
    "WOBUS": SaturationVaporPressureEquation.WOBUS,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
 | 
			
		||||
        icon=ICON_WATER,
 | 
			
		||||
        accuracy_decimals=2,
 | 
			
		||||
        state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    )
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AbsoluteHumidityComponent),
 | 
			
		||||
            cv.Required(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Required(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Optional(CONF_EQUATION, default="WOBUS"): cv.enum(EQUATION, upper=True),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    temperature_sensor = await cg.get_variable(config[CONF_TEMPERATURE])
 | 
			
		||||
    cg.add(var.set_temperature_sensor(temperature_sensor))
 | 
			
		||||
 | 
			
		||||
    humidity_sensor = await cg.get_variable(config[CONF_HUMIDITY])
 | 
			
		||||
    cg.add(var.set_humidity_sensor(humidity_sensor))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_equation(config[CONF_EQUATION]))
 | 
			
		||||
@@ -16,13 +16,16 @@ ADC128S102Sensor = adc128s102_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
CONF_ADC128S102_ID = "adc128s102_id"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ADC128S102Sensor),
 | 
			
		||||
        cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
 | 
			
		||||
        cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("60s"))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(ADC128S102Sensor)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
 | 
			
		||||
            cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ void Am43Component::control(const CoverCall &call) {
 | 
			
		||||
 | 
			
		||||
    if (this->invert_position_)
 | 
			
		||||
      pos = 1 - pos;
 | 
			
		||||
    auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
 | 
			
		||||
    auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t) (pos * 100));
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
 
 | 
			
		||||
@@ -15,18 +15,24 @@ AnalogThresholdBinarySensor = analog_threshold_ns.class_(
 | 
			
		||||
CONF_UPPER = "upper"
 | 
			
		||||
CONF_LOWER = "lower"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(AnalogThresholdBinarySensor),
 | 
			
		||||
        cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
 | 
			
		||||
        cv.Required(CONF_THRESHOLD): cv.Any(
 | 
			
		||||
            cv.float_,
 | 
			
		||||
            cv.Schema(
 | 
			
		||||
                {cv.Required(CONF_UPPER): cv.float_, cv.Required(CONF_LOWER): cv.float_}
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    binary_sensor.binary_sensor_schema(AnalogThresholdBinarySensor)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Required(CONF_THRESHOLD): cv.Any(
 | 
			
		||||
                cv.float_,
 | 
			
		||||
                cv.Schema(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.Required(CONF_UPPER): cv.float_,
 | 
			
		||||
                        cv.Required(CONF_LOWER): cv.float_,
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -76,8 +76,6 @@ 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])
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,9 @@ service APIConnection {
 | 
			
		||||
  rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
 | 
			
		||||
  rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
 | 
			
		||||
  rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -208,6 +211,8 @@ message DeviceInfoResponse {
 | 
			
		||||
  string manufacturer = 12;
 | 
			
		||||
 | 
			
		||||
  string friendly_name = 13;
 | 
			
		||||
 | 
			
		||||
  uint32 voice_assistant_version = 14;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message ListEntitiesRequest {
 | 
			
		||||
@@ -829,7 +834,7 @@ message ListEntitiesClimateResponse {
 | 
			
		||||
  repeated ClimateMode supported_modes = 7;
 | 
			
		||||
  float visual_min_temperature = 8;
 | 
			
		||||
  float visual_max_temperature = 9;
 | 
			
		||||
  float visual_temperature_step = 10;
 | 
			
		||||
  float visual_target_temperature_step = 10;
 | 
			
		||||
  // for older peer versions - in new system this
 | 
			
		||||
  // is if CLIMATE_PRESET_AWAY exists is supported_presets
 | 
			
		||||
  bool legacy_supports_away = 11;
 | 
			
		||||
@@ -842,6 +847,7 @@ message ListEntitiesClimateResponse {
 | 
			
		||||
  bool disabled_by_default = 18;
 | 
			
		||||
  string icon = 19;
 | 
			
		||||
  EntityCategory entity_category = 20;
 | 
			
		||||
  float visual_current_temperature_step = 21;
 | 
			
		||||
}
 | 
			
		||||
message ClimateStateResponse {
 | 
			
		||||
  option (id) = 47;
 | 
			
		||||
@@ -1124,6 +1130,7 @@ message MediaPlayerCommandRequest {
 | 
			
		||||
message SubscribeBluetoothLEAdvertisementsRequest {
 | 
			
		||||
  option (id) = 66;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothServiceData {
 | 
			
		||||
@@ -1155,6 +1162,7 @@ enum BluetoothDeviceRequestType {
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceRequest {
 | 
			
		||||
@@ -1338,3 +1346,91 @@ message BluetoothGATTNotifyResponse {
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDevicePairingResponse {
 | 
			
		||||
  option (id) = 85;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  bool paired = 2;
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceUnpairingResponse {
 | 
			
		||||
  option (id) = 86;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  bool success = 2;
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message UnsubscribeBluetoothLEAdvertisementsRequest {
 | 
			
		||||
  option (id) = 87;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceClearCacheResponse {
 | 
			
		||||
  option (id) = 88;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  bool success = 2;
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== PUSH TO TALK ====================
 | 
			
		||||
message SubscribeVoiceAssistantRequest {
 | 
			
		||||
  option (id) = 89;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  bool subscribe = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantRequest {
 | 
			
		||||
  option (id) = 90;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  bool start = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantResponse {
 | 
			
		||||
  option (id) = 91;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  uint32 port = 1;
 | 
			
		||||
  bool error = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum VoiceAssistantEvent {
 | 
			
		||||
  VOICE_ASSISTANT_ERROR = 0;
 | 
			
		||||
  VOICE_ASSISTANT_RUN_START = 1;
 | 
			
		||||
  VOICE_ASSISTANT_RUN_END = 2;
 | 
			
		||||
  VOICE_ASSISTANT_STT_START = 3;
 | 
			
		||||
  VOICE_ASSISTANT_STT_END = 4;
 | 
			
		||||
  VOICE_ASSISTANT_INTENT_START = 5;
 | 
			
		||||
  VOICE_ASSISTANT_INTENT_END = 6;
 | 
			
		||||
  VOICE_ASSISTANT_TTS_START = 7;
 | 
			
		||||
  VOICE_ASSISTANT_TTS_END = 8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantEventData {
 | 
			
		||||
  string name = 1;
 | 
			
		||||
  string value = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantEventResponse {
 | 
			
		||||
  option (id) = 92;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  VoiceAssistantEvent event_type = 1;
 | 
			
		||||
  repeated VoiceAssistantEventData data = 2;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#include "api_connection.h"
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/entity_base.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
@@ -15,6 +16,9 @@
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
#include "esphome/components/voice_assistant/voice_assistant.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
@@ -180,7 +184,8 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
 | 
			
		||||
  ListEntitiesBinarySensorResponse msg;
 | 
			
		||||
  msg.object_id = binary_sensor->get_object_id();
 | 
			
		||||
  msg.key = binary_sensor->get_object_id_hash();
 | 
			
		||||
  msg.name = binary_sensor->get_name();
 | 
			
		||||
  if (binary_sensor->has_own_name())
 | 
			
		||||
    msg.name = binary_sensor->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
 | 
			
		||||
  msg.device_class = binary_sensor->get_device_class();
 | 
			
		||||
  msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
 | 
			
		||||
@@ -212,7 +217,8 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
 | 
			
		||||
  ListEntitiesCoverResponse msg;
 | 
			
		||||
  msg.key = cover->get_object_id_hash();
 | 
			
		||||
  msg.object_id = cover->get_object_id();
 | 
			
		||||
  msg.name = cover->get_name();
 | 
			
		||||
  if (cover->has_own_name())
 | 
			
		||||
    msg.name = cover->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("cover", cover);
 | 
			
		||||
  msg.assumed_state = traits.get_is_assumed_state();
 | 
			
		||||
  msg.supports_position = traits.get_supports_position();
 | 
			
		||||
@@ -275,7 +281,8 @@ bool APIConnection::send_fan_info(fan::Fan *fan) {
 | 
			
		||||
  ListEntitiesFanResponse msg;
 | 
			
		||||
  msg.key = fan->get_object_id_hash();
 | 
			
		||||
  msg.object_id = fan->get_object_id();
 | 
			
		||||
  msg.name = fan->get_name();
 | 
			
		||||
  if (fan->has_own_name())
 | 
			
		||||
    msg.name = fan->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("fan", fan);
 | 
			
		||||
  msg.supports_oscillation = traits.supports_oscillation();
 | 
			
		||||
  msg.supports_speed = traits.supports_speed();
 | 
			
		||||
@@ -337,7 +344,8 @@ bool APIConnection::send_light_info(light::LightState *light) {
 | 
			
		||||
  ListEntitiesLightResponse msg;
 | 
			
		||||
  msg.key = light->get_object_id_hash();
 | 
			
		||||
  msg.object_id = light->get_object_id();
 | 
			
		||||
  msg.name = light->get_name();
 | 
			
		||||
  if (light->has_own_name())
 | 
			
		||||
    msg.name = light->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("light", light);
 | 
			
		||||
 | 
			
		||||
  msg.disabled_by_default = light->is_disabled_by_default();
 | 
			
		||||
@@ -418,7 +426,8 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
 | 
			
		||||
  ListEntitiesSensorResponse msg;
 | 
			
		||||
  msg.key = sensor->get_object_id_hash();
 | 
			
		||||
  msg.object_id = sensor->get_object_id();
 | 
			
		||||
  msg.name = sensor->get_name();
 | 
			
		||||
  if (sensor->has_own_name())
 | 
			
		||||
    msg.name = sensor->get_name();
 | 
			
		||||
  msg.unique_id = sensor->unique_id();
 | 
			
		||||
  if (msg.unique_id.empty())
 | 
			
		||||
    msg.unique_id = get_default_unique_id("sensor", sensor);
 | 
			
		||||
@@ -448,7 +457,8 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
 | 
			
		||||
  ListEntitiesSwitchResponse msg;
 | 
			
		||||
  msg.key = a_switch->get_object_id_hash();
 | 
			
		||||
  msg.object_id = a_switch->get_object_id();
 | 
			
		||||
  msg.name = a_switch->get_name();
 | 
			
		||||
  if (a_switch->has_own_name())
 | 
			
		||||
    msg.name = a_switch->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("switch", a_switch);
 | 
			
		||||
  msg.icon = a_switch->get_icon();
 | 
			
		||||
  msg.assumed_state = a_switch->assumed_state();
 | 
			
		||||
@@ -533,7 +543,8 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
 | 
			
		||||
  ListEntitiesClimateResponse msg;
 | 
			
		||||
  msg.key = climate->get_object_id_hash();
 | 
			
		||||
  msg.object_id = climate->get_object_id();
 | 
			
		||||
  msg.name = climate->get_name();
 | 
			
		||||
  if (climate->has_own_name())
 | 
			
		||||
    msg.name = climate->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("climate", climate);
 | 
			
		||||
 | 
			
		||||
  msg.disabled_by_default = climate->is_disabled_by_default();
 | 
			
		||||
@@ -548,7 +559,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
 | 
			
		||||
 | 
			
		||||
  msg.visual_min_temperature = traits.get_visual_min_temperature();
 | 
			
		||||
  msg.visual_max_temperature = traits.get_visual_max_temperature();
 | 
			
		||||
  msg.visual_temperature_step = traits.get_visual_temperature_step();
 | 
			
		||||
  msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
 | 
			
		||||
  msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
 | 
			
		||||
 | 
			
		||||
  msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
 | 
			
		||||
  msg.supports_action = traits.get_supports_action();
 | 
			
		||||
 | 
			
		||||
@@ -609,7 +622,8 @@ bool APIConnection::send_number_info(number::Number *number) {
 | 
			
		||||
  ListEntitiesNumberResponse msg;
 | 
			
		||||
  msg.key = number->get_object_id_hash();
 | 
			
		||||
  msg.object_id = number->get_object_id();
 | 
			
		||||
  msg.name = number->get_name();
 | 
			
		||||
  if (number->has_own_name())
 | 
			
		||||
    msg.name = number->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("number", number);
 | 
			
		||||
  msg.icon = number->get_icon();
 | 
			
		||||
  msg.disabled_by_default = number->is_disabled_by_default();
 | 
			
		||||
@@ -650,7 +664,8 @@ bool APIConnection::send_select_info(select::Select *select) {
 | 
			
		||||
  ListEntitiesSelectResponse msg;
 | 
			
		||||
  msg.key = select->get_object_id_hash();
 | 
			
		||||
  msg.object_id = select->get_object_id();
 | 
			
		||||
  msg.name = select->get_name();
 | 
			
		||||
  if (select->has_own_name())
 | 
			
		||||
    msg.name = select->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("select", select);
 | 
			
		||||
  msg.icon = select->get_icon();
 | 
			
		||||
  msg.disabled_by_default = select->is_disabled_by_default();
 | 
			
		||||
@@ -677,7 +692,8 @@ 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();
 | 
			
		||||
  if (button->has_own_name())
 | 
			
		||||
    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();
 | 
			
		||||
@@ -708,7 +724,8 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) {
 | 
			
		||||
  ListEntitiesLockResponse msg;
 | 
			
		||||
  msg.key = a_lock->get_object_id_hash();
 | 
			
		||||
  msg.object_id = a_lock->get_object_id();
 | 
			
		||||
  msg.name = a_lock->get_name();
 | 
			
		||||
  if (a_lock->has_own_name())
 | 
			
		||||
    msg.name = a_lock->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("lock", a_lock);
 | 
			
		||||
  msg.icon = a_lock->get_icon();
 | 
			
		||||
  msg.assumed_state = a_lock->traits.get_assumed_state();
 | 
			
		||||
@@ -753,7 +770,8 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
 | 
			
		||||
  ListEntitiesMediaPlayerResponse msg;
 | 
			
		||||
  msg.key = media_player->get_object_id_hash();
 | 
			
		||||
  msg.object_id = media_player->get_object_id();
 | 
			
		||||
  msg.name = media_player->get_name();
 | 
			
		||||
  if (media_player->has_own_name())
 | 
			
		||||
    msg.name = media_player->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("media_player", media_player);
 | 
			
		||||
  msg.icon = media_player->get_icon();
 | 
			
		||||
  msg.disabled_by_default = media_player->is_disabled_by_default();
 | 
			
		||||
@@ -797,7 +815,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
 | 
			
		||||
  ListEntitiesCameraResponse msg;
 | 
			
		||||
  msg.key = camera->get_object_id_hash();
 | 
			
		||||
  msg.object_id = camera->get_object_id();
 | 
			
		||||
  msg.name = camera->get_name();
 | 
			
		||||
  if (camera->has_own_name())
 | 
			
		||||
    msg.name = camera->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("camera", camera);
 | 
			
		||||
  msg.disabled_by_default = camera->is_disabled_by_default();
 | 
			
		||||
  msg.icon = camera->get_icon();
 | 
			
		||||
@@ -877,6 +896,30 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
bool APIConnection::request_voice_assistant(bool start) {
 | 
			
		||||
  if (!this->voice_assistant_subscription_)
 | 
			
		||||
    return false;
 | 
			
		||||
  VoiceAssistantRequest msg;
 | 
			
		||||
  msg.start = start;
 | 
			
		||||
  return this->send_voice_assistant_request(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
 | 
			
		||||
  if (voice_assistant::global_voice_assistant != nullptr) {
 | 
			
		||||
    struct sockaddr_storage storage;
 | 
			
		||||
    socklen_t len = sizeof(storage);
 | 
			
		||||
    this->helper_->getpeername((struct sockaddr *) &storage, &len);
 | 
			
		||||
    voice_assistant::global_voice_assistant->start(&storage, msg.port);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
 | 
			
		||||
  if (voice_assistant::global_voice_assistant != nullptr) {
 | 
			
		||||
    voice_assistant::global_voice_assistant->on_event(msg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
 | 
			
		||||
  if (this->log_subscription_ < level)
 | 
			
		||||
    return false;
 | 
			
		||||
@@ -896,7 +939,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
 | 
			
		||||
  this->helper_->set_log_info(client_info_);
 | 
			
		||||
  this->client_api_version_major_ = msg.api_version_major;
 | 
			
		||||
  this->client_api_version_minor_ = msg.api_version_minor;
 | 
			
		||||
  ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(),
 | 
			
		||||
  ESP_LOGV(TAG, "Hello from client: '%s' | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(),
 | 
			
		||||
           this->client_api_version_major_, this->client_api_version_minor_);
 | 
			
		||||
 | 
			
		||||
  HelloResponse resp;
 | 
			
		||||
@@ -951,7 +994,12 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
  resp.webserver_port = USE_WEBSERVER_PORT;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 3 : 1;
 | 
			
		||||
  resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active()
 | 
			
		||||
                                     ? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION
 | 
			
		||||
                                     : bluetooth_proxy::PASSIVE_ONLY_VERSION;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  resp.voice_assistant_version = 1;
 | 
			
		||||
#endif
 | 
			
		||||
  return resp;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
#include "api_server.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
@@ -97,6 +98,12 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
    this->send_homeassistant_service_response(call);
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override {
 | 
			
		||||
    this->bluetooth_le_advertisement_subscription_ = true;
 | 
			
		||||
  }
 | 
			
		||||
  void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override {
 | 
			
		||||
    this->bluetooth_le_advertisement_subscription_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
 | 
			
		||||
 | 
			
		||||
  void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
 | 
			
		||||
@@ -117,6 +124,15 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
 | 
			
		||||
    this->voice_assistant_subscription_ = msg.subscribe;
 | 
			
		||||
  }
 | 
			
		||||
  bool request_voice_assistant(bool start);
 | 
			
		||||
  void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
 | 
			
		||||
  void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void on_disconnect_response(const DisconnectResponse &value) override;
 | 
			
		||||
  void on_ping_response(const PingResponse &value) override {
 | 
			
		||||
    // we initiated ping
 | 
			
		||||
@@ -150,9 +166,7 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
  void execute_service(const ExecuteServiceRequest &msg) override;
 | 
			
		||||
  void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override {
 | 
			
		||||
    this->bluetooth_le_advertisement_subscription_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
 | 
			
		||||
  bool is_connection_setup() override {
 | 
			
		||||
    return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
 | 
			
		||||
@@ -197,7 +211,12 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  uint32_t last_traffic_;
 | 
			
		||||
  bool sent_ping_{false};
 | 
			
		||||
  bool service_call_subscription_{false};
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool bluetooth_le_advertisement_subscription_{false};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  bool voice_assistant_subscription_{false};
 | 
			
		||||
#endif
 | 
			
		||||
  bool next_close_ = false;
 | 
			
		||||
  APIServer *parent_;
 | 
			
		||||
  InitialStateIterator initial_state_iterator_;
 | 
			
		||||
 
 | 
			
		||||
@@ -295,7 +295,7 @@ APIError APINoiseFrameHelper::state_action_() {
 | 
			
		||||
    if (aerr != APIError::OK)
 | 
			
		||||
      return aerr;
 | 
			
		||||
    // ignore contents, may be used in future for flags
 | 
			
		||||
    prologue_.push_back((uint8_t)(frame.msg.size() >> 8));
 | 
			
		||||
    prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
 | 
			
		||||
    prologue_.push_back((uint8_t) frame.msg.size());
 | 
			
		||||
    prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
 | 
			
		||||
 | 
			
		||||
@@ -492,9 +492,9 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
 | 
			
		||||
  // tmpbuf[1], tmpbuf[2] to be set later
 | 
			
		||||
  const uint8_t msg_offset = 3;
 | 
			
		||||
  const uint8_t payload_offset = msg_offset + 4;
 | 
			
		||||
  tmpbuf[msg_offset + 0] = (uint8_t)(type >> 8);  // type
 | 
			
		||||
  tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8);  // type
 | 
			
		||||
  tmpbuf[msg_offset + 1] = (uint8_t) type;
 | 
			
		||||
  tmpbuf[msg_offset + 2] = (uint8_t)(payload_len >> 8);  // data_len
 | 
			
		||||
  tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8);  // data_len
 | 
			
		||||
  tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
 | 
			
		||||
  // copy data
 | 
			
		||||
  std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
 | 
			
		||||
@@ -512,7 +512,7 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t total_len = 3 + mbuf.size;
 | 
			
		||||
  tmpbuf[1] = (uint8_t)(mbuf.size >> 8);
 | 
			
		||||
  tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
 | 
			
		||||
  tmpbuf[2] = (uint8_t) mbuf.size;
 | 
			
		||||
 | 
			
		||||
  struct iovec iov;
 | 
			
		||||
@@ -610,7 +610,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
 | 
			
		||||
  uint8_t header[3];
 | 
			
		||||
  header[0] = 0x01;  // indicator
 | 
			
		||||
  header[1] = (uint8_t)(len >> 8);
 | 
			
		||||
  header[1] = (uint8_t) (len >> 8);
 | 
			
		||||
  header[2] = (uint8_t) len;
 | 
			
		||||
 | 
			
		||||
  struct iovec iov[2];
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,8 @@
 | 
			
		||||
#include "noise/protocol.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/socket/socket.h"
 | 
			
		||||
#include "api_noise_context.h"
 | 
			
		||||
#include "esphome/components/socket/socket.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
@@ -67,6 +67,7 @@ class APIFrameHelper {
 | 
			
		||||
  virtual bool can_write_without_blocking() = 0;
 | 
			
		||||
  virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
 | 
			
		||||
  virtual std::string getpeername() = 0;
 | 
			
		||||
  virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
 | 
			
		||||
  virtual APIError close() = 0;
 | 
			
		||||
  virtual APIError shutdown(int how) = 0;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
@@ -84,7 +85,10 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
			
		||||
  bool can_write_without_blocking() override;
 | 
			
		||||
  APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
 | 
			
		||||
  std::string getpeername() override { return socket_->getpeername(); }
 | 
			
		||||
  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
			
		||||
  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
			
		||||
    return this->socket_->getpeername(addr, addrlen);
 | 
			
		||||
  }
 | 
			
		||||
  APIError close() override;
 | 
			
		||||
  APIError shutdown(int how) override;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
@@ -144,7 +148,10 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
			
		||||
  bool can_write_without_blocking() override;
 | 
			
		||||
  APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
 | 
			
		||||
  std::string getpeername() override { return socket_->getpeername(); }
 | 
			
		||||
  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
			
		||||
  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
			
		||||
    return this->socket_->getpeername(addr, addrlen);
 | 
			
		||||
  }
 | 
			
		||||
  APIError close() override;
 | 
			
		||||
  APIError shutdown(int how) override;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
 
 | 
			
		||||
@@ -400,6 +400,34 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE";
 | 
			
		||||
    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE";
 | 
			
		||||
    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE:
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::VoiceAssistantEvent value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::VOICE_ASSISTANT_ERROR:
 | 
			
		||||
      return "VOICE_ASSISTANT_ERROR";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_RUN_START:
 | 
			
		||||
      return "VOICE_ASSISTANT_RUN_START";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_RUN_END:
 | 
			
		||||
      return "VOICE_ASSISTANT_RUN_END";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_STT_START:
 | 
			
		||||
      return "VOICE_ASSISTANT_STT_START";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_STT_END:
 | 
			
		||||
      return "VOICE_ASSISTANT_STT_END";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_INTENT_START:
 | 
			
		||||
      return "VOICE_ASSISTANT_INTENT_START";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_INTENT_END:
 | 
			
		||||
      return "VOICE_ASSISTANT_INTENT_END";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_TTS_START:
 | 
			
		||||
      return "VOICE_ASSISTANT_TTS_START";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_TTS_END:
 | 
			
		||||
      return "VOICE_ASSISTANT_TTS_END";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
@@ -592,6 +620,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
      this->bluetooth_proxy_version = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 14: {
 | 
			
		||||
      this->voice_assistant_version = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -652,6 +684,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint32(11, this->bluetooth_proxy_version);
 | 
			
		||||
  buffer.encode_string(12, this->manufacturer);
 | 
			
		||||
  buffer.encode_string(13, this->friendly_name);
 | 
			
		||||
  buffer.encode_uint32(14, this->voice_assistant_version);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -710,6 +743,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  friendly_name: ");
 | 
			
		||||
  out.append("'").append(this->friendly_name).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  voice_assistant_version: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->voice_assistant_version);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -3451,7 +3489,11 @@ bool ListEntitiesClimateResponse::decode_32bit(uint32_t field_id, Proto32Bit val
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->visual_temperature_step = value.as_float();
 | 
			
		||||
      this->visual_target_temperature_step = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 21: {
 | 
			
		||||
      this->visual_current_temperature_step = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
@@ -3470,7 +3512,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_float(8, this->visual_min_temperature);
 | 
			
		||||
  buffer.encode_float(9, this->visual_max_temperature);
 | 
			
		||||
  buffer.encode_float(10, this->visual_temperature_step);
 | 
			
		||||
  buffer.encode_float(10, this->visual_target_temperature_step);
 | 
			
		||||
  buffer.encode_bool(11, this->legacy_supports_away);
 | 
			
		||||
  buffer.encode_bool(12, this->supports_action);
 | 
			
		||||
  for (auto &it : this->supported_fan_modes) {
 | 
			
		||||
@@ -3491,6 +3533,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(18, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_string(19, this->icon);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
 | 
			
		||||
  buffer.encode_float(21, this->visual_current_temperature_step);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -3537,8 +3580,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  visual_temperature_step: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->visual_temperature_step);
 | 
			
		||||
  out.append("  visual_target_temperature_step: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->visual_target_temperature_step);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
@@ -3591,6 +3634,11 @@ void ListEntitiesClimateResponse::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("  visual_current_temperature_step: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->visual_current_temperature_step);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -5964,6 +6012,290 @@ void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothDevicePairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->paired = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->error = value.as_int32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_bool(2, this->paired);
 | 
			
		||||
  buffer.encode_int32(3, this->error);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothDevicePairingResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothDevicePairingResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  paired: ");
 | 
			
		||||
  out.append(YESNO(this->paired));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  error: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->error);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothDeviceUnpairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->success = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->error = value.as_int32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_bool(2, this->success);
 | 
			
		||||
  buffer.encode_int32(3, this->error);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothDeviceUnpairingResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  success: ");
 | 
			
		||||
  out.append(YESNO(this->success));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  error: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->error);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
void UnsubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothDeviceClearCacheResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->success = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->error = value.as_int32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_bool(2, this->success);
 | 
			
		||||
  buffer.encode_int32(3, this->error);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothDeviceClearCacheResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  success: ");
 | 
			
		||||
  out.append(YESNO(this->success));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  error: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->error);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->subscribe = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->subscribe); }
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SubscribeVoiceAssistantRequest {\n");
 | 
			
		||||
  out.append("  subscribe: ");
 | 
			
		||||
  out.append(YESNO(this->subscribe));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->start = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); }
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void VoiceAssistantRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("VoiceAssistantRequest {\n");
 | 
			
		||||
  out.append("  start: ");
 | 
			
		||||
  out.append(YESNO(this->start));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->port = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->error = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void VoiceAssistantResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint32(1, this->port);
 | 
			
		||||
  buffer.encode_bool(2, this->error);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void VoiceAssistantResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("VoiceAssistantResponse {\n");
 | 
			
		||||
  out.append("  port: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->port);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  error: ");
 | 
			
		||||
  out.append(YESNO(this->error));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->name = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->value = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void VoiceAssistantEventData::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(1, this->name);
 | 
			
		||||
  buffer.encode_string(2, this->value);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void VoiceAssistantEventData::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("VoiceAssistantEventData {\n");
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  value: ");
 | 
			
		||||
  out.append("'").append(this->value).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool VoiceAssistantEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->event_type = value.as_enum<enums::VoiceAssistantEvent>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool VoiceAssistantEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->data.push_back(value.as_message<VoiceAssistantEventData>());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void VoiceAssistantEventResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_enum<enums::VoiceAssistantEvent>(1, this->event_type);
 | 
			
		||||
  for (auto &it : this->data) {
 | 
			
		||||
    buffer.encode_message<VoiceAssistantEventData>(2, it, true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void VoiceAssistantEventResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("VoiceAssistantEventResponse {\n");
 | 
			
		||||
  out.append("  event_type: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::VoiceAssistantEvent>(this->event_type));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  for (const auto &it : this->data) {
 | 
			
		||||
    out.append("  data: ");
 | 
			
		||||
    it.dump_to(out);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -163,6 +163,18 @@ enum BluetoothDeviceRequestType : uint32_t {
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6,
 | 
			
		||||
};
 | 
			
		||||
enum VoiceAssistantEvent : uint32_t {
 | 
			
		||||
  VOICE_ASSISTANT_ERROR = 0,
 | 
			
		||||
  VOICE_ASSISTANT_RUN_START = 1,
 | 
			
		||||
  VOICE_ASSISTANT_RUN_END = 2,
 | 
			
		||||
  VOICE_ASSISTANT_STT_START = 3,
 | 
			
		||||
  VOICE_ASSISTANT_STT_END = 4,
 | 
			
		||||
  VOICE_ASSISTANT_INTENT_START = 5,
 | 
			
		||||
  VOICE_ASSISTANT_INTENT_END = 6,
 | 
			
		||||
  VOICE_ASSISTANT_TTS_START = 7,
 | 
			
		||||
  VOICE_ASSISTANT_TTS_END = 8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace enums
 | 
			
		||||
@@ -278,6 +290,7 @@ class DeviceInfoResponse : public ProtoMessage {
 | 
			
		||||
  uint32_t bluetooth_proxy_version{0};
 | 
			
		||||
  std::string manufacturer{};
 | 
			
		||||
  std::string friendly_name{};
 | 
			
		||||
  uint32_t voice_assistant_version{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -915,7 +928,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
 | 
			
		||||
  std::vector<enums::ClimateMode> supported_modes{};
 | 
			
		||||
  float visual_min_temperature{0.0f};
 | 
			
		||||
  float visual_max_temperature{0.0f};
 | 
			
		||||
  float visual_temperature_step{0.0f};
 | 
			
		||||
  float visual_target_temperature_step{0.0f};
 | 
			
		||||
  bool legacy_supports_away{false};
 | 
			
		||||
  bool supports_action{false};
 | 
			
		||||
  std::vector<enums::ClimateFanMode> supported_fan_modes{};
 | 
			
		||||
@@ -926,6 +939,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  float visual_current_temperature_step{0.0f};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -1527,6 +1541,113 @@ class BluetoothGATTNotifyResponse : public ProtoMessage {
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothDevicePairingResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  bool paired{false};
 | 
			
		||||
  int32_t error{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothDeviceUnpairingResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  bool success{false};
 | 
			
		||||
  int32_t error{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
};
 | 
			
		||||
class BluetoothDeviceClearCacheResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  bool success{false};
 | 
			
		||||
  int32_t error{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class SubscribeVoiceAssistantRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  bool subscribe{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class VoiceAssistantRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  bool start{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class VoiceAssistantResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t port{0};
 | 
			
		||||
  bool error{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class VoiceAssistantEventData : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string name{};
 | 
			
		||||
  std::string value{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
};
 | 
			
		||||
class VoiceAssistantEventResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  enums::VoiceAssistantEvent event_type{};
 | 
			
		||||
  std::vector<VoiceAssistantEventData> data{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -329,6 +329,8 @@ bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayer
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_le_advertisement_response: %s", msg.dump().c_str());
 | 
			
		||||
@@ -425,6 +427,46 @@ bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const Bluetoot
 | 
			
		||||
  return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_device_pairing_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothDevicePairingResponse>(msg, 85);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_device_unpairing_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothDeviceUnpairingResponse>(msg, 86);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_device_clear_cache_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantRequest &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_voice_assistant_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<VoiceAssistantRequest>(msg, 90);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
			
		||||
  switch (msg_type) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -693,12 +735,14 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 66: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      SubscribeBluetoothLEAdvertisementsRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_subscribe_bluetooth_le_advertisements_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 68: {
 | 
			
		||||
@@ -786,6 +830,50 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_subscribe_bluetooth_connections_free_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 87: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      UnsubscribeBluetoothLEAdvertisementsRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_unsubscribe_bluetooth_le_advertisements_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 89: {
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
      SubscribeVoiceAssistantRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_subscribe_voice_assistant_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 91: {
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
      VoiceAssistantResponse msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_voice_assistant_response(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 92: {
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
      VoiceAssistantEventResponse msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_voice_assistant_event_response(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -1049,6 +1137,7 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
 | 
			
		||||
  this->media_player_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
@@ -1061,6 +1150,7 @@ void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
  }
 | 
			
		||||
  this->subscribe_bluetooth_le_advertisements(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
@@ -1169,6 +1259,33 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
    const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->unsubscribe_bluetooth_le_advertisements(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->subscribe_voice_assistant(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -154,8 +154,10 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_subscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
      const SubscribeBluetoothLEAdvertisementsRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
@@ -209,6 +211,31 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_unsubscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
      const UnsubscribeBluetoothLEAdvertisementsRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  bool send_voice_assistant_request(const VoiceAssistantRequest &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
			
		||||
@@ -261,7 +288,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -286,6 +315,12 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
 | 
			
		||||
      const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  void on_hello_request(const HelloRequest &msg) override;
 | 
			
		||||
@@ -333,7 +368,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -358,6 +395,13 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_unsubscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
      const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ void APIServer::setup() {
 | 
			
		||||
 | 
			
		||||
  struct sockaddr_storage server;
 | 
			
		||||
 | 
			
		||||
  socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_));
 | 
			
		||||
  socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
 | 
			
		||||
  if (sl == 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
@@ -309,6 +309,39 @@ void APIServer::send_bluetooth_device_connection(uint64_t address, bool connecte
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error) {
 | 
			
		||||
  BluetoothDevicePairingResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.paired = paired;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_device_pairing_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error) {
 | 
			
		||||
  BluetoothDeviceUnpairingResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.success = success;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_device_unpairing_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error) {
 | 
			
		||||
  BluetoothDeviceClearCacheResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.success = success;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_device_clear_cache_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
 | 
			
		||||
  BluetoothConnectionsFreeResponse call;
 | 
			
		||||
  call.free = free;
 | 
			
		||||
@@ -394,5 +427,21 @@ void APIServer::on_shutdown() {
 | 
			
		||||
  delay(10);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
bool APIServer::start_voice_assistant() {
 | 
			
		||||
  for (auto &c : this->clients_) {
 | 
			
		||||
    if (c->request_voice_assistant(true))
 | 
			
		||||
      return true;
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
void APIServer::stop_voice_assistant() {
 | 
			
		||||
  for (auto &c : this->clients_) {
 | 
			
		||||
    if (c->request_voice_assistant(false))
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,9 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
 | 
			
		||||
  void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
 | 
			
		||||
  void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
 | 
			
		||||
@@ -92,6 +95,11 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
  void request_time();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  bool start_voice_assistant();
 | 
			
		||||
  void stop_voice_assistant();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  bool is_connected() const;
 | 
			
		||||
 | 
			
		||||
  struct HomeAssistantStateSubscription {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#include "proto.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -13,7 +14,7 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
 | 
			
		||||
    uint32_t consumed;
 | 
			
		||||
    auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
 | 
			
		||||
    if (!res.has_value()) {
 | 
			
		||||
      ESP_LOGV(TAG, "Invalid field start at %u", i);
 | 
			
		||||
      ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -25,12 +26,12 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
 | 
			
		||||
      case 0: {  // VarInt
 | 
			
		||||
        res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
 | 
			
		||||
        if (!res.has_value()) {
 | 
			
		||||
          ESP_LOGV(TAG, "Invalid VarInt at %u", i);
 | 
			
		||||
          ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        if (!this->decode_varint(field_id, *res)) {
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32());
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
 | 
			
		||||
        }
 | 
			
		||||
        i += consumed;
 | 
			
		||||
        break;
 | 
			
		||||
@@ -38,38 +39,38 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
 | 
			
		||||
      case 2: {  // Length-delimited
 | 
			
		||||
        res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
 | 
			
		||||
        if (!res.has_value()) {
 | 
			
		||||
          ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
 | 
			
		||||
          ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        uint32_t field_length = res->as_uint32();
 | 
			
		||||
        i += consumed;
 | 
			
		||||
        if (field_length > length - i) {
 | 
			
		||||
          ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
 | 
			
		||||
          ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
 | 
			
		||||
        }
 | 
			
		||||
        i += field_length;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 5: {  // 32-bit
 | 
			
		||||
        if (length - i < 4) {
 | 
			
		||||
          ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %u", i);
 | 
			
		||||
          ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
 | 
			
		||||
        if (!this->decode_32bit(field_id, Proto32Bit(val))) {
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
 | 
			
		||||
        }
 | 
			
		||||
        i += 4;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default:
 | 
			
		||||
        ESP_LOGV(TAG, "Invalid field type at %u", i);
 | 
			
		||||
        ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
 | 
			
		||||
        error = true;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_CAPACITANCE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["sensor", "binary_sensor"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_AS3935_ID = "as3935_id"
 | 
			
		||||
 
 | 
			
		||||
@@ -26,9 +26,13 @@ void AS3935Component::setup() {
 | 
			
		||||
void AS3935Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "AS3935:");
 | 
			
		||||
  LOG_PIN("  Interrupt Pin: ", this->irq_pin_);
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  LOG_BINARY_SENSOR("  ", "Thunder alert", this->thunder_alert_binary_sensor_);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  LOG_SENSOR("  ", "Distance", this->distance_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Lightning energy", this->energy_sensor_);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
@@ -44,16 +48,22 @@ void AS3935Component::loop() {
 | 
			
		||||
    ESP_LOGI(TAG, "Disturber was detected - try increasing the spike rejection value!");
 | 
			
		||||
  } else if (int_value == LIGHTNING_INT) {
 | 
			
		||||
    ESP_LOGI(TAG, "Lightning has been detected!");
 | 
			
		||||
    if (this->thunder_alert_binary_sensor_ != nullptr)
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
    if (this->thunder_alert_binary_sensor_ != nullptr) {
 | 
			
		||||
      this->thunder_alert_binary_sensor_->publish_state(true);
 | 
			
		||||
      this->set_timeout(10, [this]() { this->thunder_alert_binary_sensor_->publish_state(false); });
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
    uint8_t distance = this->get_distance_to_storm_();
 | 
			
		||||
    if (this->distance_sensor_ != nullptr)
 | 
			
		||||
      this->distance_sensor_->publish_state(distance);
 | 
			
		||||
 | 
			
		||||
    uint32_t energy = this->get_lightning_energy_();
 | 
			
		||||
    if (this->energy_sensor_ != nullptr)
 | 
			
		||||
      this->energy_sensor_->publish_state(energy);
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
  this->thunder_alert_binary_sensor_->publish_state(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AS3935Component::write_indoor(bool indoor) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,14 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as3935 {
 | 
			
		||||
@@ -52,6 +57,15 @@ enum AS3935Values {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AS3935Component : public Component {
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  SUB_SENSOR(distance)
 | 
			
		||||
  SUB_SENSOR(energy)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  SUB_BINARY_SENSOR(thunder_alert)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
@@ -59,11 +73,7 @@ class AS3935Component : public Component {
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void set_irq_pin(GPIOPin *irq_pin) { irq_pin_ = irq_pin; }
 | 
			
		||||
  void set_distance_sensor(sensor::Sensor *distance_sensor) { distance_sensor_ = distance_sensor; }
 | 
			
		||||
  void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
 | 
			
		||||
  void set_thunder_alert_binary_sensor(binary_sensor::BinarySensor *thunder_alert_binary_sensor) {
 | 
			
		||||
    thunder_alert_binary_sensor_ = thunder_alert_binary_sensor;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_indoor(bool indoor) { indoor_ = indoor; }
 | 
			
		||||
  void write_indoor(bool indoor);
 | 
			
		||||
  void set_noise_level(uint8_t noise_level) { noise_level_ = noise_level; }
 | 
			
		||||
@@ -92,9 +102,6 @@ class AS3935Component : public Component {
 | 
			
		||||
 | 
			
		||||
  virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0;
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *distance_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *energy_sensor_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *thunder_alert_binary_sensor_{nullptr};
 | 
			
		||||
  GPIOPin *irq_pin_;
 | 
			
		||||
 | 
			
		||||
  bool indoor_;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/as7341/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/as7341/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										271
									
								
								esphome/components/as7341/as7341.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								esphome/components/as7341/as7341.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,271 @@
 | 
			
		||||
#include "as7341.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as7341 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "as7341";
 | 
			
		||||
 | 
			
		||||
void AS7341Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up AS7341...");
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
 | 
			
		||||
  // Verify device ID
 | 
			
		||||
  uint8_t id;
 | 
			
		||||
  this->read_byte(AS7341_ID, &id);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Read ID: 0x%X", id);
 | 
			
		||||
  if ((id & 0xFC) != (AS7341_CHIP_ID << 2)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Power on (enter IDLE state)
 | 
			
		||||
  if (!this->enable_power(true)) {
 | 
			
		||||
    ESP_LOGE(TAG, "  Power on failed!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set configuration
 | 
			
		||||
  this->write_byte(AS7341_CONFIG, 0x00);
 | 
			
		||||
  this->setup_atime(this->atime_);
 | 
			
		||||
  this->setup_astep(this->astep_);
 | 
			
		||||
  this->setup_gain(this->gain_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AS7341Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "AS7341:");
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with AS7341 failed!");
 | 
			
		||||
  }
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Gain: %u", get_gain());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  ATIME: %u", get_atime());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  ASTEP: %u", get_astep());
 | 
			
		||||
 | 
			
		||||
  LOG_SENSOR("  ", "F1", this->f1_);
 | 
			
		||||
  LOG_SENSOR("  ", "F2", this->f2_);
 | 
			
		||||
  LOG_SENSOR("  ", "F3", this->f3_);
 | 
			
		||||
  LOG_SENSOR("  ", "F4", this->f4_);
 | 
			
		||||
  LOG_SENSOR("  ", "F5", this->f5_);
 | 
			
		||||
  LOG_SENSOR("  ", "F6", this->f6_);
 | 
			
		||||
  LOG_SENSOR("  ", "F7", this->f7_);
 | 
			
		||||
  LOG_SENSOR("  ", "F8", this->f8_);
 | 
			
		||||
  LOG_SENSOR("  ", "Clear", this->clear_);
 | 
			
		||||
  LOG_SENSOR("  ", "NIR", this->nir_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float AS7341Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
void AS7341Component::update() {
 | 
			
		||||
  this->read_channels(this->channel_readings_);
 | 
			
		||||
 | 
			
		||||
  if (this->f1_ != nullptr) {
 | 
			
		||||
    this->f1_->publish_state(this->channel_readings_[0]);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->f2_ != nullptr) {
 | 
			
		||||
    this->f2_->publish_state(this->channel_readings_[1]);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->f3_ != nullptr) {
 | 
			
		||||
    this->f3_->publish_state(this->channel_readings_[2]);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->f4_ != nullptr) {
 | 
			
		||||
    this->f4_->publish_state(this->channel_readings_[3]);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->f5_ != nullptr) {
 | 
			
		||||
    this->f5_->publish_state(this->channel_readings_[6]);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->f6_ != nullptr) {
 | 
			
		||||
    this->f6_->publish_state(this->channel_readings_[7]);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->f7_ != nullptr) {
 | 
			
		||||
    this->f7_->publish_state(this->channel_readings_[8]);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->f8_ != nullptr) {
 | 
			
		||||
    this->f8_->publish_state(this->channel_readings_[9]);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->clear_ != nullptr) {
 | 
			
		||||
    this->clear_->publish_state(this->channel_readings_[10]);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->nir_ != nullptr) {
 | 
			
		||||
    this->nir_->publish_state(this->channel_readings_[11]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AS7341Gain AS7341Component::get_gain() {
 | 
			
		||||
  uint8_t data;
 | 
			
		||||
  this->read_byte(AS7341_CFG1, &data);
 | 
			
		||||
  return (AS7341Gain) data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t AS7341Component::get_atime() {
 | 
			
		||||
  uint8_t data;
 | 
			
		||||
  this->read_byte(AS7341_ATIME, &data);
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t AS7341Component::get_astep() {
 | 
			
		||||
  uint16_t data;
 | 
			
		||||
  this->read_byte_16(AS7341_ASTEP, &data);
 | 
			
		||||
  return this->swap_bytes(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::setup_gain(AS7341Gain gain) { return this->write_byte(AS7341_CFG1, gain); }
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::setup_atime(uint8_t atime) { return this->write_byte(AS7341_ATIME, atime); }
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::setup_astep(uint16_t astep) { return this->write_byte_16(AS7341_ASTEP, swap_bytes(astep)); }
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::read_channels(uint16_t *data) {
 | 
			
		||||
  this->set_smux_low_channels(true);
 | 
			
		||||
  this->enable_spectral_measurement(true);
 | 
			
		||||
  this->wait_for_data();
 | 
			
		||||
  bool low_success = this->read_bytes_16(AS7341_CH0_DATA_L, data, 6);
 | 
			
		||||
 | 
			
		||||
  this->set_smux_low_channels(false);
 | 
			
		||||
  this->enable_spectral_measurement(true);
 | 
			
		||||
  this->wait_for_data();
 | 
			
		||||
  bool high_sucess = this->read_bytes_16(AS7341_CH0_DATA_L, &data[6], 6);
 | 
			
		||||
 | 
			
		||||
  return low_success && high_sucess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AS7341Component::set_smux_low_channels(bool enable) {
 | 
			
		||||
  this->enable_spectral_measurement(false);
 | 
			
		||||
  this->set_smux_command(AS7341_SMUX_CMD_WRITE);
 | 
			
		||||
 | 
			
		||||
  if (enable) {
 | 
			
		||||
    this->configure_smux_low_channels();
 | 
			
		||||
 | 
			
		||||
  } else {
 | 
			
		||||
    this->configure_smux_high_channels();
 | 
			
		||||
  }
 | 
			
		||||
  this->enable_smux();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::set_smux_command(AS7341SmuxCommand command) {
 | 
			
		||||
  uint8_t data = command << 3;  // Write to bits 4:3 of the register
 | 
			
		||||
  return this->write_byte(AS7341_CFG6, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AS7341Component::configure_smux_low_channels() {
 | 
			
		||||
  // SMUX Config for F1,F2,F3,F4,NIR,Clear
 | 
			
		||||
  this->write_byte(0x00, 0x30);  // F3 left set to ADC2
 | 
			
		||||
  this->write_byte(0x01, 0x01);  // F1 left set to ADC0
 | 
			
		||||
  this->write_byte(0x02, 0x00);  // Reserved or disabled
 | 
			
		||||
  this->write_byte(0x03, 0x00);  // F8 left disabled
 | 
			
		||||
  this->write_byte(0x04, 0x00);  // F6 left disabled
 | 
			
		||||
  this->write_byte(0x05, 0x42);  // F4 left connected to ADC3/f2 left connected to ADC1
 | 
			
		||||
  this->write_byte(0x06, 0x00);  // F5 left disbled
 | 
			
		||||
  this->write_byte(0x07, 0x00);  // F7 left disbled
 | 
			
		||||
  this->write_byte(0x08, 0x50);  // CLEAR connected to ADC4
 | 
			
		||||
  this->write_byte(0x09, 0x00);  // F5 right disabled
 | 
			
		||||
  this->write_byte(0x0A, 0x00);  // F7 right disabled
 | 
			
		||||
  this->write_byte(0x0B, 0x00);  // Reserved or disabled
 | 
			
		||||
  this->write_byte(0x0C, 0x20);  // F2 right connected to ADC1
 | 
			
		||||
  this->write_byte(0x0D, 0x04);  // F4 right connected to ADC3
 | 
			
		||||
  this->write_byte(0x0E, 0x00);  // F6/F8 right disabled
 | 
			
		||||
  this->write_byte(0x0F, 0x30);  // F3 right connected to AD2
 | 
			
		||||
  this->write_byte(0x10, 0x01);  // F1 right connected to AD0
 | 
			
		||||
  this->write_byte(0x11, 0x50);  // CLEAR right connected to AD4
 | 
			
		||||
  this->write_byte(0x12, 0x00);  // Reserved or disabled
 | 
			
		||||
  this->write_byte(0x13, 0x06);  // NIR connected to ADC5
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AS7341Component::configure_smux_high_channels() {
 | 
			
		||||
  // SMUX Config for F5,F6,F7,F8,NIR,Clear
 | 
			
		||||
  this->write_byte(0x00, 0x00);  // F3 left disable
 | 
			
		||||
  this->write_byte(0x01, 0x00);  // F1 left disable
 | 
			
		||||
  this->write_byte(0x02, 0x00);  // reserved/disable
 | 
			
		||||
  this->write_byte(0x03, 0x40);  // F8 left connected to ADC3
 | 
			
		||||
  this->write_byte(0x04, 0x02);  // F6 left connected to ADC1
 | 
			
		||||
  this->write_byte(0x05, 0x00);  // F4/ F2 disabled
 | 
			
		||||
  this->write_byte(0x06, 0x10);  // F5 left connected to ADC0
 | 
			
		||||
  this->write_byte(0x07, 0x03);  // F7 left connected to ADC2
 | 
			
		||||
  this->write_byte(0x08, 0x50);  // CLEAR Connected to ADC4
 | 
			
		||||
  this->write_byte(0x09, 0x10);  // F5 right connected to ADC0
 | 
			
		||||
  this->write_byte(0x0A, 0x03);  // F7 right connected to ADC2
 | 
			
		||||
  this->write_byte(0x0B, 0x00);  // Reserved or disabled
 | 
			
		||||
  this->write_byte(0x0C, 0x00);  // F2 right disabled
 | 
			
		||||
  this->write_byte(0x0D, 0x00);  // F4 right disabled
 | 
			
		||||
  this->write_byte(0x0E, 0x24);  // F8 right connected to ADC2/ F6 right connected to ADC1
 | 
			
		||||
  this->write_byte(0x0F, 0x00);  // F3 right disabled
 | 
			
		||||
  this->write_byte(0x10, 0x00);  // F1 right disabled
 | 
			
		||||
  this->write_byte(0x11, 0x50);  // CLEAR right connected to AD4
 | 
			
		||||
  this->write_byte(0x12, 0x00);  // Reserved or disabled
 | 
			
		||||
  this->write_byte(0x13, 0x06);  // NIR connected to ADC5
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::enable_smux() {
 | 
			
		||||
  this->set_register_bit(AS7341_ENABLE, 4);
 | 
			
		||||
 | 
			
		||||
  uint16_t timeout = 1000;
 | 
			
		||||
  for (uint16_t time = 0; time < timeout; time++) {
 | 
			
		||||
    // The SMUXEN bit is cleared once the SMUX operation is finished
 | 
			
		||||
    bool smuxen = this->read_register_bit(AS7341_ENABLE, 4);
 | 
			
		||||
    if (!smuxen) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    delay(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::wait_for_data() {
 | 
			
		||||
  uint16_t timeout = 1000;
 | 
			
		||||
  for (uint16_t time = 0; time < timeout; time++) {
 | 
			
		||||
    if (this->is_data_ready()) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    delay(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::is_data_ready() { return this->read_register_bit(AS7341_STATUS2, 6); }
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::enable_power(bool enable) { return this->write_register_bit(AS7341_ENABLE, enable, 0); }
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::enable_spectral_measurement(bool enable) {
 | 
			
		||||
  return this->write_register_bit(AS7341_ENABLE, enable, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::read_register_bit(uint8_t address, uint8_t bit_position) {
 | 
			
		||||
  uint8_t data;
 | 
			
		||||
  this->read_byte(address, &data);
 | 
			
		||||
  bool bit = (data & (1 << bit_position)) > 0;
 | 
			
		||||
  return bit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::write_register_bit(uint8_t address, bool value, uint8_t bit_position) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    return this->set_register_bit(address, bit_position);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->clear_register_bit(address, bit_position);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::set_register_bit(uint8_t address, uint8_t bit_position) {
 | 
			
		||||
  uint8_t data;
 | 
			
		||||
  this->read_byte(address, &data);
 | 
			
		||||
  data |= (1 << bit_position);
 | 
			
		||||
  return this->write_byte(address, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AS7341Component::clear_register_bit(uint8_t address, uint8_t bit_position) {
 | 
			
		||||
  uint8_t data;
 | 
			
		||||
  this->read_byte(address, &data);
 | 
			
		||||
  data &= ~(1 << bit_position);
 | 
			
		||||
  return this->write_byte(address, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t AS7341Component::swap_bytes(uint16_t data) { return (data >> 8) | (data << 8); }
 | 
			
		||||
 | 
			
		||||
}  // namespace as7341
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										144
									
								
								esphome/components/as7341/as7341.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								esphome/components/as7341/as7341.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as7341 {
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_CHIP_ID = 0X09;
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_CONFIG = 0x70;
 | 
			
		||||
static const uint8_t AS7341_LED = 0x74;
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_ENABLE = 0x80;
 | 
			
		||||
static const uint8_t AS7341_ATIME = 0x81;
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_WTIME = 0x83;
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_AUXID = 0x90;
 | 
			
		||||
static const uint8_t AS7341_REVID = 0x91;
 | 
			
		||||
static const uint8_t AS7341_ID = 0x92;
 | 
			
		||||
static const uint8_t AS7341_STATUS = 0x93;
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_CH0_DATA_L = 0x95;
 | 
			
		||||
static const uint8_t AS7341_CH0_DATA_H = 0x96;
 | 
			
		||||
static const uint8_t AS7341_CH1_DATA_L = 0x97;
 | 
			
		||||
static const uint8_t AS7341_CH1_DATA_H = 0x98;
 | 
			
		||||
static const uint8_t AS7341_CH2_DATA_L = 0x99;
 | 
			
		||||
static const uint8_t AS7341_CH2_DATA_H = 0x9A;
 | 
			
		||||
static const uint8_t AS7341_CH3_DATA_L = 0x9B;
 | 
			
		||||
static const uint8_t AS7341_CH3_DATA_H = 0x9C;
 | 
			
		||||
static const uint8_t AS7341_CH4_DATA_L = 0x9D;
 | 
			
		||||
static const uint8_t AS7341_CH4_DATA_H = 0x9E;
 | 
			
		||||
static const uint8_t AS7341_CH5_DATA_L = 0x9F;
 | 
			
		||||
static const uint8_t AS7341_CH5_DATA_H = 0xA0;
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_STATUS2 = 0xA3;
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_CFG1 = 0xAA;  ///< Controls ADC Gain
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_CFG6 = 0xAF;  // Stores SMUX command
 | 
			
		||||
static const uint8_t AS7341_CFG9 = 0xB2;  // Config for system interrupts (SMUX, Flicker detection)
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_ASTEP = 0xCA;      // LSB
 | 
			
		||||
static const uint8_t AS7341_ASTEP_MSB = 0xCB;  // MSB
 | 
			
		||||
 | 
			
		||||
enum AS7341AdcChannel {
 | 
			
		||||
  AS7341_ADC_CHANNEL_0,
 | 
			
		||||
  AS7341_ADC_CHANNEL_1,
 | 
			
		||||
  AS7341_ADC_CHANNEL_2,
 | 
			
		||||
  AS7341_ADC_CHANNEL_3,
 | 
			
		||||
  AS7341_ADC_CHANNEL_4,
 | 
			
		||||
  AS7341_ADC_CHANNEL_5,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum AS7341SmuxCommand {
 | 
			
		||||
  AS7341_SMUX_CMD_ROM_RESET,  ///< ROM code initialization of SMUX
 | 
			
		||||
  AS7341_SMUX_CMD_READ,       ///< Read SMUX configuration to RAM from SMUX chain
 | 
			
		||||
  AS7341_SMUX_CMD_WRITE,      ///< Write SMUX configuration from RAM to SMUX chain
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum AS7341Gain {
 | 
			
		||||
  AS7341_GAIN_0_5X,
 | 
			
		||||
  AS7341_GAIN_1X,
 | 
			
		||||
  AS7341_GAIN_2X,
 | 
			
		||||
  AS7341_GAIN_4X,
 | 
			
		||||
  AS7341_GAIN_8X,
 | 
			
		||||
  AS7341_GAIN_16X,
 | 
			
		||||
  AS7341_GAIN_32X,
 | 
			
		||||
  AS7341_GAIN_64X,
 | 
			
		||||
  AS7341_GAIN_128X,
 | 
			
		||||
  AS7341_GAIN_256X,
 | 
			
		||||
  AS7341_GAIN_512X,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AS7341Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void set_f1_sensor(sensor::Sensor *f1_sensor) { this->f1_ = f1_sensor; }
 | 
			
		||||
  void set_f2_sensor(sensor::Sensor *f2_sensor) { f2_ = f2_sensor; }
 | 
			
		||||
  void set_f3_sensor(sensor::Sensor *f3_sensor) { f3_ = f3_sensor; }
 | 
			
		||||
  void set_f4_sensor(sensor::Sensor *f4_sensor) { f4_ = f4_sensor; }
 | 
			
		||||
  void set_f5_sensor(sensor::Sensor *f5_sensor) { f5_ = f5_sensor; }
 | 
			
		||||
  void set_f6_sensor(sensor::Sensor *f6_sensor) { f6_ = f6_sensor; }
 | 
			
		||||
  void set_f7_sensor(sensor::Sensor *f7_sensor) { f7_ = f7_sensor; }
 | 
			
		||||
  void set_f8_sensor(sensor::Sensor *f8_sensor) { f8_ = f8_sensor; }
 | 
			
		||||
  void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_ = clear_sensor; }
 | 
			
		||||
  void set_nir_sensor(sensor::Sensor *nir_sensor) { nir_ = nir_sensor; }
 | 
			
		||||
 | 
			
		||||
  void set_gain(AS7341Gain gain) { gain_ = gain; }
 | 
			
		||||
  void set_atime(uint8_t atime) { atime_ = atime; }
 | 
			
		||||
  void set_astep(uint16_t astep) { astep_ = astep; }
 | 
			
		||||
 | 
			
		||||
  AS7341Gain get_gain();
 | 
			
		||||
  uint8_t get_atime();
 | 
			
		||||
  uint16_t get_astep();
 | 
			
		||||
  bool setup_gain(AS7341Gain gain);
 | 
			
		||||
  bool setup_atime(uint8_t atime);
 | 
			
		||||
  bool setup_astep(uint16_t astep);
 | 
			
		||||
 | 
			
		||||
  uint16_t read_channel(AS7341AdcChannel channel);
 | 
			
		||||
  bool read_channels(uint16_t *data);
 | 
			
		||||
  void set_smux_low_channels(bool enable);
 | 
			
		||||
  bool set_smux_command(AS7341SmuxCommand command);
 | 
			
		||||
  void configure_smux_low_channels();
 | 
			
		||||
  void configure_smux_high_channels();
 | 
			
		||||
  bool enable_smux();
 | 
			
		||||
 | 
			
		||||
  bool wait_for_data();
 | 
			
		||||
  bool is_data_ready();
 | 
			
		||||
  bool enable_power(bool enable);
 | 
			
		||||
  bool enable_spectral_measurement(bool enable);
 | 
			
		||||
 | 
			
		||||
  bool read_register_bit(uint8_t address, uint8_t bit_position);
 | 
			
		||||
  bool write_register_bit(uint8_t address, bool value, uint8_t bit_position);
 | 
			
		||||
  bool set_register_bit(uint8_t address, uint8_t bit_position);
 | 
			
		||||
  bool clear_register_bit(uint8_t address, uint8_t bit_position);
 | 
			
		||||
  uint16_t swap_bytes(uint16_t data);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  sensor::Sensor *f1_{nullptr};
 | 
			
		||||
  sensor::Sensor *f2_{nullptr};
 | 
			
		||||
  sensor::Sensor *f3_{nullptr};
 | 
			
		||||
  sensor::Sensor *f4_{nullptr};
 | 
			
		||||
  sensor::Sensor *f5_{nullptr};
 | 
			
		||||
  sensor::Sensor *f6_{nullptr};
 | 
			
		||||
  sensor::Sensor *f7_{nullptr};
 | 
			
		||||
  sensor::Sensor *f8_{nullptr};
 | 
			
		||||
  sensor::Sensor *clear_{nullptr};
 | 
			
		||||
  sensor::Sensor *nir_{nullptr};
 | 
			
		||||
 | 
			
		||||
  uint16_t astep_;
 | 
			
		||||
  AS7341Gain gain_;
 | 
			
		||||
  uint8_t atime_;
 | 
			
		||||
  uint16_t channel_readings_[12];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace as7341
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										112
									
								
								esphome/components/as7341/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								esphome/components/as7341/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_GAIN,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
			
		||||
    ICON_BRIGHTNESS_5,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@mrgnr"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
as7341_ns = cg.esphome_ns.namespace("as7341")
 | 
			
		||||
 | 
			
		||||
AS7341Component = as7341_ns.class_(
 | 
			
		||||
    "AS7341Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_ATIME = "atime"
 | 
			
		||||
CONF_ASTEP = "astep"
 | 
			
		||||
 | 
			
		||||
CONF_F1 = "f1"
 | 
			
		||||
CONF_F2 = "f2"
 | 
			
		||||
CONF_F3 = "f3"
 | 
			
		||||
CONF_F4 = "f4"
 | 
			
		||||
CONF_F5 = "f5"
 | 
			
		||||
CONF_F6 = "f6"
 | 
			
		||||
CONF_F7 = "f7"
 | 
			
		||||
CONF_F8 = "f8"
 | 
			
		||||
CONF_CLEAR = "clear"
 | 
			
		||||
CONF_NIR = "nir"
 | 
			
		||||
 | 
			
		||||
UNIT_COUNTS = "#"
 | 
			
		||||
 | 
			
		||||
AS7341_GAIN = as7341_ns.enum("AS7341Gain")
 | 
			
		||||
GAIN_OPTIONS = {
 | 
			
		||||
    "X0.5": AS7341_GAIN.AS7341_GAIN_0_5X,
 | 
			
		||||
    "X1": AS7341_GAIN.AS7341_GAIN_1X,
 | 
			
		||||
    "X2": AS7341_GAIN.AS7341_GAIN_2X,
 | 
			
		||||
    "X4": AS7341_GAIN.AS7341_GAIN_4X,
 | 
			
		||||
    "X8": AS7341_GAIN.AS7341_GAIN_8X,
 | 
			
		||||
    "X16": AS7341_GAIN.AS7341_GAIN_16X,
 | 
			
		||||
    "X32": AS7341_GAIN.AS7341_GAIN_32X,
 | 
			
		||||
    "X64": AS7341_GAIN.AS7341_GAIN_64X,
 | 
			
		||||
    "X128": AS7341_GAIN.AS7341_GAIN_128X,
 | 
			
		||||
    "X256": AS7341_GAIN.AS7341_GAIN_256X,
 | 
			
		||||
    "X512": AS7341_GAIN.AS7341_GAIN_512X,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SENSOR_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
    unit_of_measurement=UNIT_COUNTS,
 | 
			
		||||
    icon=ICON_BRIGHTNESS_5,
 | 
			
		||||
    accuracy_decimals=0,
 | 
			
		||||
    device_class=DEVICE_CLASS_ILLUMINANCE,
 | 
			
		||||
    state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AS7341Component),
 | 
			
		||||
            cv.Optional(CONF_F1): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_F2): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_F3): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_F4): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_F5): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_F6): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_F7): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_F8): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_CLEAR): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_NIR): SENSOR_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_GAIN, default="X8"): cv.enum(GAIN_OPTIONS),
 | 
			
		||||
            cv.Optional(CONF_ATIME, default=29): cv.int_range(min=0, max=255),
 | 
			
		||||
            cv.Optional(CONF_ASTEP, default=599): cv.int_range(min=0, max=65534),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x39))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
SENSORS = {
 | 
			
		||||
    CONF_F1: "set_f1_sensor",
 | 
			
		||||
    CONF_F2: "set_f2_sensor",
 | 
			
		||||
    CONF_F3: "set_f3_sensor",
 | 
			
		||||
    CONF_F4: "set_f4_sensor",
 | 
			
		||||
    CONF_F5: "set_f5_sensor",
 | 
			
		||||
    CONF_F6: "set_f6_sensor",
 | 
			
		||||
    CONF_F7: "set_f7_sensor",
 | 
			
		||||
    CONF_F8: "set_f8_sensor",
 | 
			
		||||
    CONF_CLEAR: "set_clear_sensor",
 | 
			
		||||
    CONF_NIR: "set_nir_sensor",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_gain(config[CONF_GAIN]))
 | 
			
		||||
    cg.add(var.set_atime(config[CONF_ATIME]))
 | 
			
		||||
    cg.add(var.set_astep(config[CONF_ASTEP]))
 | 
			
		||||
 | 
			
		||||
    for conf_id, set_sensor_func in SENSORS.items():
 | 
			
		||||
        if conf_id in config:
 | 
			
		||||
            sens = await sensor.new_sensor(config[conf_id])
 | 
			
		||||
            cg.add(getattr(var, set_sensor_func)(sens))
 | 
			
		||||
@@ -80,7 +80,7 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
 | 
			
		||||
 | 
			
		||||
    for (config_key, setter) in [
 | 
			
		||||
    for config_key, setter in [
 | 
			
		||||
        (CONF_TEMPERATURE, var.set_temperature),
 | 
			
		||||
        (CONF_HUMIDITY, var.set_humidity),
 | 
			
		||||
        (CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "bedjet_hub.h"
 | 
			
		||||
#include "bedjet_child.h"
 | 
			
		||||
#include "bedjet_const.h"
 | 
			
		||||
@@ -541,3 +543,5 @@ void BedJetHub::register_child(BedJetClient *obj) {
 | 
			
		||||
 | 
			
		||||
}  // namespace bedjet
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
@@ -14,8 +15,6 @@
 | 
			
		||||
#include "esphome/components/time/real_time_clock.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -117,7 +116,7 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void setup() override { this->codec_ = make_unique<BedjetCodec>(); }
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::BLUETOOTH; }
 | 
			
		||||
 | 
			
		||||
  /** @return The BedJet's configured name, or the MAC address if not discovered yet. */
 | 
			
		||||
  std::string get_name() {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,13 +27,13 @@ from esphome.const import (
 | 
			
		||||
    CONF_TIMING,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY_CHARGING,
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
    DEVICE_CLASS_COLD,
 | 
			
		||||
    DEVICE_CLASS_CONNECTIVITY,
 | 
			
		||||
    DEVICE_CLASS_DOOR,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_GARAGE_DOOR,
 | 
			
		||||
    DEVICE_CLASS_GAS,
 | 
			
		||||
    DEVICE_CLASS_HEAT,
 | 
			
		||||
@@ -62,13 +62,13 @@ from esphome.util import Registry
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY_CHARGING,
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
    DEVICE_CLASS_COLD,
 | 
			
		||||
    DEVICE_CLASS_CONNECTIVITY,
 | 
			
		||||
    DEVICE_CLASS_DOOR,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_GARAGE_DOOR,
 | 
			
		||||
    DEVICE_CLASS_GAS,
 | 
			
		||||
    DEVICE_CLASS_HEAT,
 | 
			
		||||
@@ -393,28 +393,21 @@ def binary_sensor_schema(
 | 
			
		||||
    entity_category: str = _UNDEF,
 | 
			
		||||
    device_class: str = _UNDEF,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = BINARY_SENSOR_SCHEMA
 | 
			
		||||
    schema = {}
 | 
			
		||||
 | 
			
		||||
    if class_ is not _UNDEF:
 | 
			
		||||
        schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)})
 | 
			
		||||
    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
 | 
			
		||||
        # Not cv.optional
 | 
			
		||||
        schema[cv.GenerateID()] = cv.declare_id(class_)
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_DEVICE_CLASS, device_class, validate_device_class),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not _UNDEF:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return BINARY_SENSOR_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_binary_sensor_core_(var, config):
 | 
			
		||||
 
 | 
			
		||||
@@ -41,16 +41,13 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
 | 
			
		||||
    this->state_callback_.call(state);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
std::string BinarySensor::device_class() { return ""; }
 | 
			
		||||
 | 
			
		||||
BinarySensor::BinarySensor() : state(false) {}
 | 
			
		||||
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
 | 
			
		||||
std::string BinarySensor::get_device_class() {
 | 
			
		||||
  if (this->device_class_.has_value())
 | 
			
		||||
    return *this->device_class_;
 | 
			
		||||
#pragma GCC diagnostic push
 | 
			
		||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 | 
			
		||||
  return this->device_class();
 | 
			
		||||
#pragma GCC diagnostic pop
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
void BinarySensor::add_filter(Filter *filter) {
 | 
			
		||||
  filter->parent_ = this;
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,15 @@ namespace binary_sensor {
 | 
			
		||||
    } \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#define SUB_BINARY_SENSOR(name) \
 | 
			
		||||
 protected: \
 | 
			
		||||
  binary_sensor::BinarySensor *name##_binary_sensor_{nullptr}; \
 | 
			
		||||
\
 | 
			
		||||
 public: \
 | 
			
		||||
  void set_##name##_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { \
 | 
			
		||||
    this->name##_binary_sensor_ = binary_sensor; \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
/** Base class for all binary_sensor-type classes.
 | 
			
		||||
 *
 | 
			
		||||
 * This class includes a callback that components such as MQTT can subscribe to for state changes.
 | 
			
		||||
@@ -71,14 +80,6 @@ class BinarySensor : public EntityBase {
 | 
			
		||||
 | 
			
		||||
  virtual bool is_status_binary_sensor() const;
 | 
			
		||||
 | 
			
		||||
  // ========== OVERRIDE METHODS ==========
 | 
			
		||||
  // (You'll only need this when creating your own custom binary sensor)
 | 
			
		||||
  /** Override this to set the default device class.
 | 
			
		||||
   *
 | 
			
		||||
   * @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
 | 
			
		||||
   */
 | 
			
		||||
  virtual std::string device_class();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  CallbackManager<void(bool)> state_callback_{};
 | 
			
		||||
  optional<std::string> device_class_{};  ///< Stores the override of the device class
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ void BinarySensorMap::process_group_() {
 | 
			
		||||
    if (bs.binary_sensor->state) {
 | 
			
		||||
      num_active_sensors++;
 | 
			
		||||
      total_current_value += bs.sensor_value;
 | 
			
		||||
      mask |= 1 << i;
 | 
			
		||||
      mask |= 1ULL << i;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // check if the sensor map was touched
 | 
			
		||||
@@ -38,12 +38,11 @@ void BinarySensorMap::process_group_() {
 | 
			
		||||
    // did the bit_mask change or is it a new sensor touch
 | 
			
		||||
    if (this->last_mask_ != mask) {
 | 
			
		||||
      float publish_value = total_current_value / num_active_sensors;
 | 
			
		||||
      ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value);
 | 
			
		||||
      this->publish_state(publish_value);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (this->last_mask_ != 0ULL) {
 | 
			
		||||
    // is this a new sensor release
 | 
			
		||||
    ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
 | 
			
		||||
    ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
 | 
			
		||||
    this->publish_state(NAN);
 | 
			
		||||
  }
 | 
			
		||||
  this->last_mask_ = mask;
 | 
			
		||||
@@ -52,28 +51,22 @@ void BinarySensorMap::process_group_() {
 | 
			
		||||
void BinarySensorMap::process_sum_() {
 | 
			
		||||
  float total_current_value = 0.0;
 | 
			
		||||
  uint64_t mask = 0x00;
 | 
			
		||||
  // check all binary_sensors for its state. when active add its value to total_current_value.
 | 
			
		||||
  // create a bitmask for the binary_sensor status on all channels
 | 
			
		||||
  // - check all binary_sensor states
 | 
			
		||||
  // - if active, add its value to total_current_value
 | 
			
		||||
  // - creates a bitmask for the binary_sensor status on all channels
 | 
			
		||||
  for (size_t i = 0; i < this->channels_.size(); i++) {
 | 
			
		||||
    auto bs = this->channels_[i];
 | 
			
		||||
    if (bs.binary_sensor->state) {
 | 
			
		||||
      total_current_value += bs.sensor_value;
 | 
			
		||||
      mask |= 1 << i;
 | 
			
		||||
      mask |= 1ULL << i;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // check if the sensor map was touched
 | 
			
		||||
  if (mask != 0ULL) {
 | 
			
		||||
    // did the bit_mask change or is it a new sensor touch
 | 
			
		||||
    if (this->last_mask_ != mask) {
 | 
			
		||||
      float publish_value = total_current_value;
 | 
			
		||||
      ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value);
 | 
			
		||||
      this->publish_state(publish_value);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (this->last_mask_ != 0ULL) {
 | 
			
		||||
    // is this a new sensor release
 | 
			
		||||
    ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing 0", this->name_.c_str());
 | 
			
		||||
    this->publish_state(0.0);
 | 
			
		||||
 | 
			
		||||
  // update state only if the binary sensor states have changed or if no state has ever been sent on boot
 | 
			
		||||
  if ((this->last_mask_ != mask) || (!this->has_state())) {
 | 
			
		||||
    this->publish_state(total_current_value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->last_mask_ = mask;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.typed_schema(
 | 
			
		||||
        ).extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Required(CONF_CHANNELS): cv.All(
 | 
			
		||||
                    cv.ensure_list(entry), cv.Length(min=1)
 | 
			
		||||
                    cv.ensure_list(entry), cv.Length(min=1, max=64)
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
@@ -50,7 +50,7 @@ CONFIG_SCHEMA = cv.typed_schema(
 | 
			
		||||
        ).extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Required(CONF_CHANNELS): cv.All(
 | 
			
		||||
                    cv.ensure_list(entry), cv.Length(min=1)
 | 
			
		||||
                    cv.ensure_list(entry), cv.Length(min=1, max=64)
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_TOTAL_INCREASING,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_KILOWATT_HOURS,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
@@ -66,16 +67,19 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                unit_of_measurement=UNIT_KILOWATT_HOURS,
 | 
			
		||||
                accuracy_decimals=3,
 | 
			
		||||
                device_class=DEVICE_CLASS_ENERGY,
 | 
			
		||||
                state_class=STATE_CLASS_TOTAL_INCREASING,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ENERGY_2): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_KILOWATT_HOURS,
 | 
			
		||||
                accuracy_decimals=3,
 | 
			
		||||
                device_class=DEVICE_CLASS_ENERGY,
 | 
			
		||||
                state_class=STATE_CLASS_TOTAL_INCREASING,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ENERGY_TOTAL): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_KILOWATT_HOURS,
 | 
			
		||||
                accuracy_decimals=3,
 | 
			
		||||
                device_class=DEVICE_CLASS_ENERGY,
 | 
			
		||||
                state_class=STATE_CLASS_TOTAL_INCREASING,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "automation.h"
 | 
			
		||||
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
@@ -73,3 +75,5 @@ void BLEWriterClientNode::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,25 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
			
		||||
  BLEClientBase::gap_event_handler(event, param);
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GAP_BLE_AUTH_CMPL_EVT:
 | 
			
		||||
      if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->ble_security.auth_cmpl.success) {
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_pairing(this->address_, true);
 | 
			
		||||
      } else {
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_pairing(this->address_, false,
 | 
			
		||||
                                                              param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
 | 
			
		||||
  if (!this->connected()) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_,
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
 | 
			
		||||
 public:
 | 
			
		||||
  bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  esp_err_t read_characteristic(uint16_t handle);
 | 
			
		||||
  esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
 | 
			
		||||
 
 | 
			
		||||
@@ -257,12 +257,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
			
		||||
        ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str());
 | 
			
		||||
      }
 | 
			
		||||
      if (msg.has_address_type) {
 | 
			
		||||
        connection->remote_bda_[0] = (msg.address >> 40) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[1] = (msg.address >> 32) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[2] = (msg.address >> 24) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[3] = (msg.address >> 16) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[4] = (msg.address >> 8) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[5] = (msg.address >> 0) & 0xFF;
 | 
			
		||||
        uint64_to_bd_addr(msg.address, connection->remote_bda_);
 | 
			
		||||
        connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
 | 
			
		||||
        connection->set_state(espbt::ClientState::DISCOVERED);
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -290,9 +285,34 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: {
 | 
			
		||||
      auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
      if (connection != nullptr) {
 | 
			
		||||
        if (!connection->is_paired()) {
 | 
			
		||||
          auto err = connection->pair();
 | 
			
		||||
          if (err != ESP_OK) {
 | 
			
		||||
            api::global_api_server->send_bluetooth_device_pairing(msg.address, false, err);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          api::global_api_server->send_bluetooth_device_pairing(msg.address, true);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: {
 | 
			
		||||
      esp_bd_addr_t address;
 | 
			
		||||
      uint64_to_bd_addr(msg.address, address);
 | 
			
		||||
      esp_err_t ret = esp_ble_remove_bond_device(address);
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: {
 | 
			
		||||
      esp_bd_addr_t address;
 | 
			
		||||
      uint64_to_bd_addr(msg.address, address);
 | 
			
		||||
      esp_err_t ret = esp_ble_gattc_cache_clean(address);
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_clear_cache(msg.address, ret == ESP_OK, ret);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,15 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
  int get_bluetooth_connections_free();
 | 
			
		||||
  int get_bluetooth_connections_limit() { return this->connections_.size(); }
 | 
			
		||||
 | 
			
		||||
  static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
 | 
			
		||||
    bd_addr[0] = (address >> 40) & 0xff;
 | 
			
		||||
    bd_addr[1] = (address >> 32) & 0xff;
 | 
			
		||||
    bd_addr[2] = (address >> 24) & 0xff;
 | 
			
		||||
    bd_addr[3] = (address >> 16) & 0xff;
 | 
			
		||||
    bd_addr[4] = (address >> 8) & 0xff;
 | 
			
		||||
    bd_addr[5] = (address >> 0) & 0xff;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_active(bool active) { this->active_ = active; }
 | 
			
		||||
  bool has_active() { return this->active_; }
 | 
			
		||||
 | 
			
		||||
@@ -59,6 +68,14 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
 | 
			
		||||
extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
// Version 1: Initial version without active connections
 | 
			
		||||
// Version 2: Support for active connections
 | 
			
		||||
// Version 3: New connection API
 | 
			
		||||
// Version 4: Pairing support
 | 
			
		||||
// Version 5: Cache clear support
 | 
			
		||||
static const uint32_t ACTIVE_CONNECTIONS_VERSION = 5;
 | 
			
		||||
static const uint32_t PASSIVE_ONLY_VERSION = 1;
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#include "bme680.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bme680 {
 | 
			
		||||
@@ -117,18 +117,24 @@ void BME680Component::setup() {
 | 
			
		||||
  this->calibration_.gh2 = cal2[12] << 8 | cal2[13];
 | 
			
		||||
  this->calibration_.gh3 = cal2[15];
 | 
			
		||||
 | 
			
		||||
  if (!this->read_byte(0x02, &this->calibration_.res_heat_range)) {
 | 
			
		||||
  uint8_t temp_var = 0;
 | 
			
		||||
  if (!this->read_byte(0x02, &temp_var)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->read_byte(0x00, &this->calibration_.res_heat_val)) {
 | 
			
		||||
  this->calibration_.res_heat_range = ((temp_var & 0x30) / 16);
 | 
			
		||||
 | 
			
		||||
  if (!this->read_byte(0x00, &temp_var)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->read_byte(0x04, &this->calibration_.range_sw_err)) {
 | 
			
		||||
  this->calibration_.res_heat_val = (int8_t) temp_var;
 | 
			
		||||
 | 
			
		||||
  if (!this->read_byte(0x04, &temp_var)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->calibration_.range_sw_err = ((int8_t) temp_var & (int8_t) 0xf0) / 16;
 | 
			
		||||
 | 
			
		||||
  this->calibration_.ambient_temperature = 25;  // prime ambient temperature
 | 
			
		||||
 | 
			
		||||
@@ -181,7 +187,7 @@ void BME680Component::setup() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  gas0_control &= ~0b00001000;
 | 
			
		||||
  gas0_control |= heat_off ? 0b100 : 0b000;
 | 
			
		||||
  gas0_control |= heat_off << 3;
 | 
			
		||||
  if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -249,12 +255,12 @@ uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) {
 | 
			
		||||
  if (temperature > 400)
 | 
			
		||||
    temperature = 400;
 | 
			
		||||
 | 
			
		||||
  const uint8_t ambient_temperature = this->calibration_.ambient_temperature;
 | 
			
		||||
  const int8_t ambient_temperature = this->calibration_.ambient_temperature;
 | 
			
		||||
  const int8_t gh1 = this->calibration_.gh1;
 | 
			
		||||
  const int16_t gh2 = this->calibration_.gh2;
 | 
			
		||||
  const int8_t gh3 = this->calibration_.gh3;
 | 
			
		||||
  const uint8_t res_heat_range = this->calibration_.res_heat_range;
 | 
			
		||||
  const uint8_t res_heat_val = this->calibration_.res_heat_val;
 | 
			
		||||
  const int8_t res_heat_val = this->calibration_.res_heat_val;
 | 
			
		||||
 | 
			
		||||
  uint8_t heatr_res;
 | 
			
		||||
  int32_t var1;
 | 
			
		||||
@@ -269,8 +275,8 @@ uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) {
 | 
			
		||||
  var3 = var1 + (var2 / 2);
 | 
			
		||||
  var4 = (var3 / (res_heat_range + 4));
 | 
			
		||||
  var5 = (131 * res_heat_val) + 65536;
 | 
			
		||||
  heatr_res_x100 = (int32_t)(((var4 / var5) - 250) * 34);
 | 
			
		||||
  heatr_res = (uint8_t)((heatr_res_x100 + 50) / 100);
 | 
			
		||||
  heatr_res_x100 = (int32_t) (((var4 / var5) - 250) * 34);
 | 
			
		||||
  heatr_res = (uint8_t) ((heatr_res_x100 + 50) / 100);
 | 
			
		||||
 | 
			
		||||
  return heatr_res;
 | 
			
		||||
}
 | 
			
		||||
@@ -293,35 +299,57 @@ uint8_t BME680Component::calc_heater_duration_(uint16_t duration) {
 | 
			
		||||
void BME680Component::read_data_() {
 | 
			
		||||
  uint8_t data[15];
 | 
			
		||||
  if (!this->read_bytes(BME680_REGISTER_FIELD0, data, 15)) {
 | 
			
		||||
    if (this->temperature_sensor_ != nullptr)
 | 
			
		||||
      this->temperature_sensor_->publish_state(NAN);
 | 
			
		||||
    if (this->pressure_sensor_ != nullptr)
 | 
			
		||||
      this->pressure_sensor_->publish_state(NAN);
 | 
			
		||||
    if (this->humidity_sensor_ != nullptr)
 | 
			
		||||
      this->humidity_sensor_->publish_state(NAN);
 | 
			
		||||
    if (this->gas_resistance_sensor_ != nullptr)
 | 
			
		||||
      this->gas_resistance_sensor_->publish_state(NAN);
 | 
			
		||||
    ESP_LOGW(TAG, "Communication with BME680 failed!");
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 | 
			
		||||
  uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4);
 | 
			
		||||
  uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4);
 | 
			
		||||
  uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]);
 | 
			
		||||
  uint16_t raw_gas = (uint16_t(data[13]) << 2) | (uint16_t(14) >> 6);
 | 
			
		||||
  uint16_t raw_gas = (uint16_t) ((uint32_t) data[13] * 4 | (((uint32_t) data[14]) / 64));
 | 
			
		||||
  uint8_t gas_range = data[14] & 0x0F;
 | 
			
		||||
 | 
			
		||||
  float temperature = this->calc_temperature_(raw_temperature);
 | 
			
		||||
  float pressure = this->calc_pressure_(raw_pressure);
 | 
			
		||||
  float humidity = this->calc_humidity_(raw_humidity);
 | 
			
		||||
  float gas_resistance = NAN;
 | 
			
		||||
  if (data[14] & 0x20) {
 | 
			
		||||
    gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range);
 | 
			
		||||
  }
 | 
			
		||||
  float gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range);
 | 
			
		||||
 | 
			
		||||
  bool gas_valid = (data[14] >> 5) & 1;
 | 
			
		||||
  bool heat_stable = (data[14] >> 4) & 1;
 | 
			
		||||
  if (this->heater_temperature_ == 0 || this->heater_duration_ == 0)
 | 
			
		||||
    heat_stable = true;  // Allow reporting gas resistance when heater is disabled
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure,
 | 
			
		||||
           humidity, gas_resistance);
 | 
			
		||||
  if (!gas_valid)
 | 
			
		||||
    ESP_LOGW(TAG, "Gas measurement unsuccessful, reading invalid!");
 | 
			
		||||
  if (!heat_stable)
 | 
			
		||||
    ESP_LOGW(TAG, "Heater unstable, reading invalid! (Normal for a few readings after a power cycle)");
 | 
			
		||||
 | 
			
		||||
  if (this->temperature_sensor_ != nullptr)
 | 
			
		||||
    this->temperature_sensor_->publish_state(temperature);
 | 
			
		||||
  if (this->pressure_sensor_ != nullptr)
 | 
			
		||||
    this->pressure_sensor_->publish_state(pressure);
 | 
			
		||||
  if (this->humidity_sensor_ != nullptr)
 | 
			
		||||
    this->humidity_sensor_->publish_state(humidity);
 | 
			
		||||
  if (this->gas_resistance_sensor_ != nullptr)
 | 
			
		||||
    this->gas_resistance_sensor_->publish_state(gas_resistance);
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
  if (this->gas_resistance_sensor_ != nullptr) {
 | 
			
		||||
    if (gas_valid && heat_stable) {
 | 
			
		||||
      this->gas_resistance_sensor_->publish_state(gas_resistance);
 | 
			
		||||
    } else {
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      this->gas_resistance_sensor_->publish_state(NAN);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float BME680Component::calc_temperature_(uint32_t raw_temperature) {
 | 
			
		||||
@@ -428,20 +456,22 @@ float BME680Component::calc_humidity_(uint16_t raw_humidity) {
 | 
			
		||||
 | 
			
		||||
  return calc_hum;
 | 
			
		||||
}
 | 
			
		||||
uint32_t BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) {
 | 
			
		||||
float BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) {
 | 
			
		||||
  float calc_gas_res;
 | 
			
		||||
  float var1 = 0;
 | 
			
		||||
  float var2 = 0;
 | 
			
		||||
  float var3 = 0;
 | 
			
		||||
  float raw_gas_f = raw_gas;
 | 
			
		||||
  float range_f = 1U << range;
 | 
			
		||||
  const float range_sw_err = this->calibration_.range_sw_err;
 | 
			
		||||
 | 
			
		||||
  var1 = 1340.0f + (5.0f * range_sw_err);
 | 
			
		||||
  var2 = var1 * (1.0f + BME680_GAS_LOOKUP_TABLE_1[range] / 100.0f);
 | 
			
		||||
  var3 = 1.0f + (BME680_GAS_LOOKUP_TABLE_2[range] / 100.0f);
 | 
			
		||||
 | 
			
		||||
  calc_gas_res = 1.0f / (var3 * 0.000000125f * float(1 << range) * (((float(raw_gas) - 512.0f) / var2) + 1.0f));
 | 
			
		||||
  calc_gas_res = 1.0f / (var3 * 0.000000125f * range_f * (((raw_gas_f - 512.0f) / var2) + 1.0f));
 | 
			
		||||
 | 
			
		||||
  return static_cast<uint32_t>(calc_gas_res);
 | 
			
		||||
  return calc_gas_res;
 | 
			
		||||
}
 | 
			
		||||
uint32_t BME680Component::calc_meas_duration_() {
 | 
			
		||||
  uint32_t tph_dur;  // Calculate in us
 | 
			
		||||
 
 | 
			
		||||
@@ -59,11 +59,11 @@ struct BME680CalibrationData {
 | 
			
		||||
  int8_t gh3;
 | 
			
		||||
 | 
			
		||||
  uint8_t res_heat_range;
 | 
			
		||||
  uint8_t res_heat_val;
 | 
			
		||||
  uint8_t range_sw_err;
 | 
			
		||||
  int8_t res_heat_val;
 | 
			
		||||
  int8_t range_sw_err;
 | 
			
		||||
 | 
			
		||||
  float tfine;
 | 
			
		||||
  uint8_t ambient_temperature;
 | 
			
		||||
  int8_t ambient_temperature;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BME680Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
@@ -117,7 +117,7 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  /// Calculate the relative humidity in % using the provided raw ADC value.
 | 
			
		||||
  float calc_humidity_(uint16_t raw_humidity);
 | 
			
		||||
  /// Calculate the gas resistance in Ω using the provided raw ADC value.
 | 
			
		||||
  uint32_t calc_gas_resistance_(uint16_t raw_gas, uint8_t range);
 | 
			
		||||
  float calc_gas_resistance_(uint16_t raw_gas, uint8_t range);
 | 
			
		||||
  /// Calculate how long the sensor will take until we can retrieve data.
 | 
			
		||||
  uint32_t calc_meas_duration_();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.components import i2c, esp32
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@trvrnrth"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
AUTO_LOAD = ["sensor", "text_sensor"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_BME680_BSEC_ID = "bme680_bsec_id"
 | 
			
		||||
CONF_TEMPERATURE_OFFSET = "temperature_offset"
 | 
			
		||||
@@ -31,22 +32,31 @@ BME680BSECComponent = bme680_bsec_ns.class_(
 | 
			
		||||
    "BME680BSECComponent", cg.Component, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BME680BSECComponent),
 | 
			
		||||
        cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
 | 
			
		||||
        cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
 | 
			
		||||
            IAQ_MODE_OPTIONS, upper=True
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
 | 
			
		||||
            SAMPLE_RATE_OPTIONS, upper=True
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_STATE_SAVE_INTERVAL, default="6hours"
 | 
			
		||||
        ): cv.positive_time_period_minutes,
 | 
			
		||||
    },
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BME680BSECComponent),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
 | 
			
		||||
            cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
 | 
			
		||||
                IAQ_MODE_OPTIONS, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
 | 
			
		||||
                SAMPLE_RATE_OPTIONS, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_STATE_SAVE_INTERVAL, default="6hours"
 | 
			
		||||
            ): cv.positive_time_period_minutes,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(i2c.i2c_device_schema(0x76)),
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
).extend(i2c.i2c_device_schema(0x76))
 | 
			
		||||
    cv.Any(
 | 
			
		||||
        cv.only_on_esp8266,
 | 
			
		||||
        cv.All(
 | 
			
		||||
            cv.only_on_esp32,
 | 
			
		||||
            esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32]),
 | 
			
		||||
        ),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
@@ -54,6 +64,7 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_device_id(str(config[CONF_ID])))
 | 
			
		||||
    cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
 | 
			
		||||
    cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE]))
 | 
			
		||||
    cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
 | 
			
		||||
 
 | 
			
		||||
@@ -10,19 +10,24 @@ static const char *const TAG = "bme680_bsec.sensor";
 | 
			
		||||
 | 
			
		||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
 | 
			
		||||
 | 
			
		||||
BME680BSECComponent *BME680BSECComponent::instance;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
std::vector<BME680BSECComponent *>
 | 
			
		||||
    BME680BSECComponent::instances;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
uint8_t BME680BSECComponent::work_buffer_[BSEC_MAX_WORKBUFFER_SIZE] = {0};
 | 
			
		||||
 | 
			
		||||
void BME680BSECComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC...");
 | 
			
		||||
  BME680BSECComponent::instance = this;
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up BME680(%s) via BSEC...", this->device_id_.c_str());
 | 
			
		||||
 | 
			
		||||
  this->bsec_status_ = bsec_init();
 | 
			
		||||
  if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t new_idx = BME680BSECComponent::instances.size();
 | 
			
		||||
  BME680BSECComponent::instances.push_back(this);
 | 
			
		||||
 | 
			
		||||
  this->bme680_.dev_id = this->address_;
 | 
			
		||||
  this->bsec_state_data_valid_ = false;
 | 
			
		||||
 | 
			
		||||
  // Initialize the bme680_ structure (passed-in to the bme680_* functions) and the BME680 device
 | 
			
		||||
  this->bme680_.dev_id =
 | 
			
		||||
      new_idx;  // This is a "Place holder to store the id of the device structure" (see bme680_defs.h).
 | 
			
		||||
                // This will be passed-in as first parameter to the next "read" and "write" function pointers.
 | 
			
		||||
                // We currently use the index of the object in the BME680BSECComponent::instances vector to identify
 | 
			
		||||
                // the different devices in the system.
 | 
			
		||||
  this->bme680_.intf = BME680_I2C_INTF;
 | 
			
		||||
  this->bme680_.read = BME680BSECComponent::read_bytes_wrapper;
 | 
			
		||||
  this->bme680_.write = BME680BSECComponent::write_bytes_wrapper;
 | 
			
		||||
@@ -35,29 +40,30 @@ void BME680BSECComponent::setup() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->sample_rate_ == SAMPLE_RATE_ULP) {
 | 
			
		||||
    const uint8_t bsec_config[] = {
 | 
			
		||||
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
 | 
			
		||||
    };
 | 
			
		||||
    this->set_config_(bsec_config);
 | 
			
		||||
  } else {
 | 
			
		||||
    const uint8_t bsec_config[] = {
 | 
			
		||||
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
 | 
			
		||||
    };
 | 
			
		||||
    this->set_config_(bsec_config);
 | 
			
		||||
  }
 | 
			
		||||
  this->update_subscription_();
 | 
			
		||||
  if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
  // Initialize the BSEC library
 | 
			
		||||
  if (this->reinit_bsec_lib_() != 0) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Load the BSEC library state from storage
 | 
			
		||||
  this->load_state_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BME680BSECComponent::set_config_(const uint8_t *config) {
 | 
			
		||||
  uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
 | 
			
		||||
  this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer));
 | 
			
		||||
void BME680BSECComponent::set_config_() {
 | 
			
		||||
  if (this->sample_rate_ == SAMPLE_RATE_ULP) {
 | 
			
		||||
    const uint8_t config[] = {
 | 
			
		||||
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
 | 
			
		||||
    };
 | 
			
		||||
    this->bsec_status_ =
 | 
			
		||||
        bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
 | 
			
		||||
  } else {
 | 
			
		||||
    const uint8_t config[] = {
 | 
			
		||||
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
 | 
			
		||||
    };
 | 
			
		||||
    this->bsec_status_ =
 | 
			
		||||
        bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float BME680BSECComponent::calc_sensor_sample_rate_(SampleRate sample_rate) {
 | 
			
		||||
@@ -118,10 +124,12 @@ void BME680BSECComponent::update_subscription_() {
 | 
			
		||||
  uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
 | 
			
		||||
  this->bsec_status_ =
 | 
			
		||||
      bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings);
 | 
			
		||||
  ESP_LOGV(TAG, "%s: updating subscription for %d virtual sensors (out=%d sensors)", this->device_id_.c_str(),
 | 
			
		||||
           num_virtual_sensors, num_sensor_settings);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BME680BSECComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "BME680 via BSEC:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "%s via BSEC:", this->device_id_.c_str());
 | 
			
		||||
 | 
			
		||||
  bsec_version_t version;
 | 
			
		||||
  bsec_get_version(&version);
 | 
			
		||||
@@ -185,23 +193,31 @@ void BME680BSECComponent::run_() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Performing sensor run");
 | 
			
		||||
  ESP_LOGV(TAG, "%s: Performing sensor run", this->device_id_.c_str());
 | 
			
		||||
 | 
			
		||||
  bsec_bme_settings_t bme680_settings;
 | 
			
		||||
  this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings);
 | 
			
		||||
  // Restore BSEC library state
 | 
			
		||||
  // The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32.
 | 
			
		||||
  // It can be skipped entirely when there is only one device (since the BSEC library won't be shared)
 | 
			
		||||
  if (BME680BSECComponent::instances.size() > 1) {
 | 
			
		||||
    int res = this->reinit_bsec_lib_();
 | 
			
		||||
    if (res != 0)
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_);
 | 
			
		||||
  if (this->bsec_status_ < BSEC_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->next_call_ns_ = bme680_settings.next_call;
 | 
			
		||||
  this->next_call_ns_ = this->bme680_settings_.next_call;
 | 
			
		||||
 | 
			
		||||
  if (bme680_settings.trigger_measurement) {
 | 
			
		||||
    this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling;
 | 
			
		||||
    this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling;
 | 
			
		||||
    this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling;
 | 
			
		||||
    this->bme680_.gas_sett.run_gas = bme680_settings.run_gas;
 | 
			
		||||
    this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature;
 | 
			
		||||
    this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration;
 | 
			
		||||
  if (this->bme680_settings_.trigger_measurement) {
 | 
			
		||||
    this->bme680_.tph_sett.os_temp = this->bme680_settings_.temperature_oversampling;
 | 
			
		||||
    this->bme680_.tph_sett.os_pres = this->bme680_settings_.pressure_oversampling;
 | 
			
		||||
    this->bme680_.tph_sett.os_hum = this->bme680_settings_.humidity_oversampling;
 | 
			
		||||
    this->bme680_.gas_sett.run_gas = this->bme680_settings_.run_gas;
 | 
			
		||||
    this->bme680_.gas_sett.heatr_temp = this->bme680_settings_.heater_temperature;
 | 
			
		||||
    this->bme680_.gas_sett.heatr_dur = this->bme680_settings_.heating_duration;
 | 
			
		||||
    this->bme680_.power_mode = BME680_FORCED_MODE;
 | 
			
		||||
    uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL;
 | 
			
		||||
    this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_);
 | 
			
		||||
@@ -218,19 +234,26 @@ void BME680BSECComponent::run_() {
 | 
			
		||||
 | 
			
		||||
    uint16_t meas_dur = 0;
 | 
			
		||||
    bme680_get_profile_dur(&meas_dur, &this->bme680_);
 | 
			
		||||
 | 
			
		||||
    // Since we are about to go "out of scope" in the loop, take a snapshot of the state now so we can restore it later
 | 
			
		||||
    // TODO: it would be interesting to see if this is really needed here, or if it's needed only after each
 | 
			
		||||
    // bsec_do_steps() call
 | 
			
		||||
    if (BME680BSECComponent::instances.size() > 1)
 | 
			
		||||
      this->snapshot_state_();
 | 
			
		||||
 | 
			
		||||
    ESP_LOGV(TAG, "Queueing read in %ums", meas_dur);
 | 
			
		||||
    this->set_timeout("read", meas_dur,
 | 
			
		||||
                      [this, curr_time_ns, bme680_settings]() { this->read_(curr_time_ns, bme680_settings); });
 | 
			
		||||
    this->set_timeout("read", meas_dur, [this]() { this->read_(); });
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGV(TAG, "Measurement not required");
 | 
			
		||||
    this->read_(curr_time_ns, bme680_settings);
 | 
			
		||||
    this->read_();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings) {
 | 
			
		||||
  ESP_LOGV(TAG, "Reading data");
 | 
			
		||||
void BME680BSECComponent::read_() {
 | 
			
		||||
  ESP_LOGV(TAG, "%s: Reading data", this->device_id_.c_str());
 | 
			
		||||
  int64_t curr_time_ns = this->get_time_ns_();
 | 
			
		||||
 | 
			
		||||
  if (bme680_settings.trigger_measurement) {
 | 
			
		||||
  if (this->bme680_settings_.trigger_measurement) {
 | 
			
		||||
    while (this->bme680_.power_mode != BME680_SLEEP_MODE) {
 | 
			
		||||
      this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_);
 | 
			
		||||
      if (this->bme680_status_ != BME680_OK) {
 | 
			
		||||
@@ -239,7 +262,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!bme680_settings.process_data) {
 | 
			
		||||
  if (!this->bme680_settings_.process_data) {
 | 
			
		||||
    ESP_LOGV(TAG, "Data processing not required");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@@ -259,35 +282,35 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
 | 
			
		||||
  bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR];  // Temperature, Pressure, Humidity & Gas Resistance
 | 
			
		||||
  uint8_t num_inputs = 0;
 | 
			
		||||
 | 
			
		||||
  if (bme680_settings.process_data & BSEC_PROCESS_TEMPERATURE) {
 | 
			
		||||
  if (this->bme680_settings_.process_data & BSEC_PROCESS_TEMPERATURE) {
 | 
			
		||||
    inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
 | 
			
		||||
    inputs[num_inputs].signal = data.temperature / 100.0f;
 | 
			
		||||
    inputs[num_inputs].time_stamp = trigger_time_ns;
 | 
			
		||||
    inputs[num_inputs].time_stamp = curr_time_ns;
 | 
			
		||||
    num_inputs++;
 | 
			
		||||
 | 
			
		||||
    // Temperature offset from the real temperature due to external heat sources
 | 
			
		||||
    inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
 | 
			
		||||
    inputs[num_inputs].signal = this->temperature_offset_;
 | 
			
		||||
    inputs[num_inputs].time_stamp = trigger_time_ns;
 | 
			
		||||
    inputs[num_inputs].time_stamp = curr_time_ns;
 | 
			
		||||
    num_inputs++;
 | 
			
		||||
  }
 | 
			
		||||
  if (bme680_settings.process_data & BSEC_PROCESS_HUMIDITY) {
 | 
			
		||||
  if (this->bme680_settings_.process_data & BSEC_PROCESS_HUMIDITY) {
 | 
			
		||||
    inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
 | 
			
		||||
    inputs[num_inputs].signal = data.humidity / 1000.0f;
 | 
			
		||||
    inputs[num_inputs].time_stamp = trigger_time_ns;
 | 
			
		||||
    inputs[num_inputs].time_stamp = curr_time_ns;
 | 
			
		||||
    num_inputs++;
 | 
			
		||||
  }
 | 
			
		||||
  if (bme680_settings.process_data & BSEC_PROCESS_PRESSURE) {
 | 
			
		||||
  if (this->bme680_settings_.process_data & BSEC_PROCESS_PRESSURE) {
 | 
			
		||||
    inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
 | 
			
		||||
    inputs[num_inputs].signal = data.pressure;
 | 
			
		||||
    inputs[num_inputs].time_stamp = trigger_time_ns;
 | 
			
		||||
    inputs[num_inputs].time_stamp = curr_time_ns;
 | 
			
		||||
    num_inputs++;
 | 
			
		||||
  }
 | 
			
		||||
  if (bme680_settings.process_data & BSEC_PROCESS_GAS) {
 | 
			
		||||
  if (this->bme680_settings_.process_data & BSEC_PROCESS_GAS) {
 | 
			
		||||
    if (data.status & BME680_GASM_VALID_MSK) {
 | 
			
		||||
      inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
 | 
			
		||||
      inputs[num_inputs].signal = data.gas_resistance;
 | 
			
		||||
      inputs[num_inputs].time_stamp = trigger_time_ns;
 | 
			
		||||
      inputs[num_inputs].time_stamp = curr_time_ns;
 | 
			
		||||
      num_inputs++;
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGD(TAG, "BME680 did not report gas data");
 | 
			
		||||
@@ -298,6 +321,22 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Restore BSEC library state
 | 
			
		||||
  // The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32.
 | 
			
		||||
  // It can be skipped entirely when there is only one device (since the BSEC library won't be shared)
 | 
			
		||||
  if (BME680BSECComponent::instances.size() > 1) {
 | 
			
		||||
    int res = this->reinit_bsec_lib_();
 | 
			
		||||
    if (res != 0)
 | 
			
		||||
      return;
 | 
			
		||||
    // Now that the BSEC library has been re-initialized, bsec_sensor_control *NEEDS* to be called in order to support
 | 
			
		||||
    // multiple devices with a different set of enabled sensors (even if the bme680_settings_ data is not used)
 | 
			
		||||
    this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_);
 | 
			
		||||
    if (this->bsec_status_ < BSEC_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
 | 
			
		||||
  uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
 | 
			
		||||
  this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs);
 | 
			
		||||
@@ -305,6 +344,13 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
 | 
			
		||||
    ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "%s: after bsec_do_steps: num_inputs=%d num_outputs=%d", this->device_id_.c_str(), num_inputs,
 | 
			
		||||
           num_outputs);
 | 
			
		||||
 | 
			
		||||
  // Since we are about to go "out of scope" in the loop, take a snapshot of the state now so we can restore it later
 | 
			
		||||
  if (BME680BSECComponent::instances.size() > 1)
 | 
			
		||||
    this->snapshot_state_();
 | 
			
		||||
 | 
			
		||||
  if (num_outputs < 1) {
 | 
			
		||||
    ESP_LOGD(TAG, "No signal outputs provided by BSEC");
 | 
			
		||||
    return;
 | 
			
		||||
@@ -314,7 +360,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
 | 
			
		||||
  ESP_LOGV(TAG, "Queuing sensor state publish actions");
 | 
			
		||||
  ESP_LOGV(TAG, "%s: Queuing sensor state publish actions", this->device_id_.c_str());
 | 
			
		||||
  for (uint8_t i = 0; i < num_outputs; i++) {
 | 
			
		||||
    float signal = outputs[i].signal;
 | 
			
		||||
    switch (outputs[i].sensor_id) {
 | 
			
		||||
@@ -376,12 +422,20 @@ void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const
 | 
			
		||||
  sensor->publish_state(value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
 | 
			
		||||
  return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1;
 | 
			
		||||
// Communication function - read
 | 
			
		||||
// First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is
 | 
			
		||||
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) {
 | 
			
		||||
  BME680BSECComponent *inst = instances[devid];
 | 
			
		||||
  // Use the I2CDevice::read_bytes method to perform the actual I2C register read
 | 
			
		||||
  return inst->read_bytes(a_register, data, len) ? 0 : -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
 | 
			
		||||
  return BME680BSECComponent::instance->write_bytes(a_register, data, len) ? 0 : -1;
 | 
			
		||||
// Communication function - write
 | 
			
		||||
// First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is
 | 
			
		||||
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) {
 | 
			
		||||
  BME680BSECComponent *inst = instances[devid];
 | 
			
		||||
  // Use the I2CDevice::write_bytes method to perform the actual I2C register write
 | 
			
		||||
  return inst->write_bytes(a_register, data, len) ? 0 : -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BME680BSECComponent::delay_ms(uint32_t period) {
 | 
			
		||||
@@ -389,41 +443,97 @@ void BME680BSECComponent::delay_ms(uint32_t period) {
 | 
			
		||||
  delay(period);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Fetch the BSEC library state and save it in the bsec_state_data_ member (volatile memory)
 | 
			
		||||
// Used to share the library when using more than one sensor
 | 
			
		||||
void BME680BSECComponent::snapshot_state_() {
 | 
			
		||||
  uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
 | 
			
		||||
  this->bsec_status_ = bsec_get_state(0, this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_,
 | 
			
		||||
                                      sizeof(this->work_buffer_), &num_serialized_state);
 | 
			
		||||
  if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "%s: Failed to fetch BSEC library state for snapshot (BSEC Error Code %d)", this->device_id_.c_str(),
 | 
			
		||||
             this->bsec_status_);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bsec_state_data_valid_ = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Restores the BSEC library state from a snapshot in memory
 | 
			
		||||
// Used to share the library when using more than one sensor
 | 
			
		||||
void BME680BSECComponent::restore_state_() {
 | 
			
		||||
  if (!this->bsec_state_data_valid_) {
 | 
			
		||||
    ESP_LOGV(TAG, "%s: BSEC state data NOT valid, aborting restore_state_()", this->device_id_.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->bsec_status_ =
 | 
			
		||||
      bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
 | 
			
		||||
  if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to restore BSEC library state (BSEC Error Code %d)", this->bsec_status_);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int BME680BSECComponent::reinit_bsec_lib_() {
 | 
			
		||||
  this->bsec_status_ = bsec_init();
 | 
			
		||||
  if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->set_config_();
 | 
			
		||||
  if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return -2;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->restore_state_();
 | 
			
		||||
 | 
			
		||||
  this->update_subscription_();
 | 
			
		||||
  if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return -3;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BME680BSECComponent::load_state_() {
 | 
			
		||||
  uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_));
 | 
			
		||||
  uint32_t hash = fnv1_hash("bme680_bsec_state_" + this->device_id_);
 | 
			
		||||
  this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
 | 
			
		||||
 | 
			
		||||
  uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
 | 
			
		||||
  if (this->bsec_state_.load(&state)) {
 | 
			
		||||
    ESP_LOGV(TAG, "Loading state");
 | 
			
		||||
    uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
 | 
			
		||||
    this->bsec_status_ = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
 | 
			
		||||
    if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_);
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGI(TAG, "Loaded state");
 | 
			
		||||
  if (!this->bsec_state_.load(&this->bsec_state_data_)) {
 | 
			
		||||
    // No saved BSEC library state available
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "%s: Loading BSEC library state", this->device_id_.c_str());
 | 
			
		||||
  this->bsec_status_ =
 | 
			
		||||
      bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
 | 
			
		||||
  if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "%s: Failed to load BSEC library state (BSEC Error Code %d)", this->device_id_.c_str(),
 | 
			
		||||
             this->bsec_status_);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // All OK: set the BSEC state data as valid
 | 
			
		||||
  this->bsec_state_data_valid_ = true;
 | 
			
		||||
  ESP_LOGI(TAG, "%s: Loaded BSEC library state", this->device_id_.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BME680BSECComponent::save_state_(uint8_t accuracy) {
 | 
			
		||||
  if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Saving state");
 | 
			
		||||
 | 
			
		||||
  uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
 | 
			
		||||
  uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
 | 
			
		||||
  uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
 | 
			
		||||
 | 
			
		||||
  this->bsec_status_ =
 | 
			
		||||
      bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
 | 
			
		||||
  if (this->bsec_status_ != BSEC_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed fetch state for save (BSEC Error Code %d)", this->bsec_status_);
 | 
			
		||||
    return;
 | 
			
		||||
  if (BME680BSECComponent::instances.size() <= 1) {
 | 
			
		||||
    // When a single device is in use, no snapshot is taken regularly so one is taken now
 | 
			
		||||
    // On multiple devices, a snapshot is taken at every loop, so there is no need to take one here
 | 
			
		||||
    this->snapshot_state_();
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->bsec_state_data_valid_)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (!this->bsec_state_.save(&state)) {
 | 
			
		||||
  ESP_LOGV(TAG, "%s: Saving state", this->device_id_.c_str());
 | 
			
		||||
 | 
			
		||||
  if (!this->bsec_state_.save(&this->bsec_state_data_)) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to save state");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ enum SampleRate {
 | 
			
		||||
 | 
			
		||||
class BME680BSECComponent : public Component, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_device_id(const std::string &devid) { this->device_id_.assign(devid); }
 | 
			
		||||
  void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
 | 
			
		||||
  void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; }
 | 
			
		||||
  void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
 | 
			
		||||
@@ -50,9 +51,9 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
 | 
			
		||||
  void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; }
 | 
			
		||||
  void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
 | 
			
		||||
 | 
			
		||||
  static BME680BSECComponent *instance;
 | 
			
		||||
  static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
 | 
			
		||||
  static int8_t write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
 | 
			
		||||
  static std::vector<BME680BSECComponent *> instances;
 | 
			
		||||
  static int8_t read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len);
 | 
			
		||||
  static int8_t write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len);
 | 
			
		||||
  static void delay_ms(uint32_t period);
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
@@ -61,23 +62,33 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void set_config_(const uint8_t *config);
 | 
			
		||||
  void set_config_();
 | 
			
		||||
  float calc_sensor_sample_rate_(SampleRate sample_rate);
 | 
			
		||||
  void update_subscription_();
 | 
			
		||||
 | 
			
		||||
  void run_();
 | 
			
		||||
  void read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings);
 | 
			
		||||
  void read_();
 | 
			
		||||
  void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
 | 
			
		||||
  int64_t get_time_ns_();
 | 
			
		||||
 | 
			
		||||
  void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
 | 
			
		||||
  void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
 | 
			
		||||
 | 
			
		||||
  void load_state_();
 | 
			
		||||
  void save_state_(uint8_t accuracy);
 | 
			
		||||
  void snapshot_state_();  // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile
 | 
			
		||||
                           // memory)
 | 
			
		||||
  void restore_state_();   // Push the state contained in the bsec_state_data_ member (volatile memory) to the BSEC
 | 
			
		||||
                           // library
 | 
			
		||||
  int reinit_bsec_lib_();  // Prepare the BSEC library to be used again after this object returns active
 | 
			
		||||
                           // (as the library may have been used by other objects)
 | 
			
		||||
  void load_state_();      // Initialize the ESP preferences object; retrieve the BSEC library state from the ESP
 | 
			
		||||
                           // preferences (storage); then save it in the bsec_state_data_ member (volatile memory) and
 | 
			
		||||
                           // push it to the BSEC library
 | 
			
		||||
  void save_state_(
 | 
			
		||||
      uint8_t accuracy);  // Save the bsec_state_data_ member (volatile memory) to the ESP preferences (storage)
 | 
			
		||||
 | 
			
		||||
  void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
 | 
			
		||||
 | 
			
		||||
  static uint8_t work_buffer_[BSEC_MAX_WORKBUFFER_SIZE];
 | 
			
		||||
  struct bme680_dev bme680_;
 | 
			
		||||
  bsec_library_return_t bsec_status_{BSEC_OK};
 | 
			
		||||
  int8_t bme680_status_{BME680_OK};
 | 
			
		||||
@@ -88,10 +99,14 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
 | 
			
		||||
 | 
			
		||||
  std::queue<std::function<void()>> queue_;
 | 
			
		||||
 | 
			
		||||
  bool bsec_state_data_valid_;
 | 
			
		||||
  uint8_t bsec_state_data_[BSEC_MAX_STATE_BLOB_SIZE];  // This is the current snapshot of the BSEC library state
 | 
			
		||||
  ESPPreferenceObject bsec_state_;
 | 
			
		||||
  uint32_t state_save_interval_ms_{21600000};  // 6 hours - 4 times a day
 | 
			
		||||
  uint32_t last_state_save_ms_ = 0;
 | 
			
		||||
  bsec_bme_settings_t bme680_settings_;
 | 
			
		||||
 | 
			
		||||
  std::string device_id_;
 | 
			
		||||
  float temperature_offset_{0};
 | 
			
		||||
  IAQMode iaq_mode_{IAQ_MODE_STATIC};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,16 +11,19 @@ from esphome.const import (
 | 
			
		||||
    CONF_ON_PRESS,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_RESTART,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_RESTART,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
]
 | 
			
		||||
@@ -54,30 +57,23 @@ _UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def button_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    *,
 | 
			
		||||
    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
 | 
			
		||||
    schema = {cv.GenerateID(): cv.declare_id(class_)}
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_DEVICE_CLASS, device_class, validate_device_class),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not _UNDEF:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return BUTTON_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_button_core_(var, config):
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,6 @@ 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();
 | 
			
		||||
 
 | 
			
		||||
@@ -15,15 +15,19 @@ namespace button {
 | 
			
		||||
    } \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#define SUB_BUTTON(name) \
 | 
			
		||||
 protected: \
 | 
			
		||||
  button::Button *name##_button_{nullptr}; \
 | 
			
		||||
\
 | 
			
		||||
 public: \
 | 
			
		||||
  void set_##name##_button(button::Button *button) { this->name##_button_ = button; }
 | 
			
		||||
 | 
			
		||||
/** 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.
 | 
			
		||||
 
 | 
			
		||||
@@ -145,8 +145,8 @@ void CCS811Component::send_env_data_() {
 | 
			
		||||
  // https://github.com/adafruit/Adafruit_CCS811/blob/0990f5c620354d8bc087c4706bec091d8e6e5dfd/Adafruit_CCS811.cpp#L135-L142
 | 
			
		||||
  uint16_t hum_conv = static_cast<uint16_t>(lroundf(humidity * 512.0f + 0.5f));
 | 
			
		||||
  uint16_t temp_conv = static_cast<uint16_t>(lroundf(temperature * 512.0f + 0.5f));
 | 
			
		||||
  this->write_bytes(0x05, {(uint8_t)((hum_conv >> 8) & 0xff), (uint8_t)((hum_conv & 0xff)),
 | 
			
		||||
                           (uint8_t)((temp_conv >> 8) & 0xff), (uint8_t)((temp_conv & 0xff))});
 | 
			
		||||
  this->write_bytes(0x05, {(uint8_t) ((hum_conv >> 8) & 0xff), (uint8_t) ((hum_conv & 0xff)),
 | 
			
		||||
                           (uint8_t) ((temp_conv >> 8) & 0xff), (uint8_t) ((temp_conv & 0xff))});
 | 
			
		||||
}
 | 
			
		||||
void CCS811Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "CCS811");
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_MODE_COMMAND_TOPIC,
 | 
			
		||||
    CONF_MODE_STATE_TOPIC,
 | 
			
		||||
    CONF_ON_CONTROL,
 | 
			
		||||
    CONF_ON_STATE,
 | 
			
		||||
    CONF_PRESET,
 | 
			
		||||
    CONF_PRESET_COMMAND_TOPIC,
 | 
			
		||||
@@ -104,9 +105,40 @@ CLIMATE_SWING_MODES = {
 | 
			
		||||
 | 
			
		||||
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
 | 
			
		||||
 | 
			
		||||
CONF_CURRENT_TEMPERATURE = "current_temperature"
 | 
			
		||||
 | 
			
		||||
visual_temperature = cv.float_with_unit(
 | 
			
		||||
    "visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def single_visual_temperature(value):
 | 
			
		||||
    if isinstance(value, dict):
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
    value = visual_temperature(value)
 | 
			
		||||
    return VISUAL_TEMPERATURE_STEP_SCHEMA(
 | 
			
		||||
        {
 | 
			
		||||
            CONF_TARGET_TEMPERATURE: value,
 | 
			
		||||
            CONF_CURRENT_TEMPERATURE: value,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Actions
 | 
			
		||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
 | 
			
		||||
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
 | 
			
		||||
ControlTrigger = climate_ns.class_("ControlTrigger", automation.Trigger.template())
 | 
			
		||||
 | 
			
		||||
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
 | 
			
		||||
    single_visual_temperature,
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_TARGET_TEMPERATURE): visual_temperature,
 | 
			
		||||
            cv.Required(CONF_CURRENT_TEMPERATURE): visual_temperature,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
 | 
			
		||||
    {
 | 
			
		||||
@@ -116,9 +148,7 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
 | 
			
		||||
                cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
 | 
			
		||||
                cv.Optional(CONF_TEMPERATURE_STEP): cv.float_with_unit(
 | 
			
		||||
                    "visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
 | 
			
		||||
@@ -175,6 +205,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_CONTROL): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
@@ -193,7 +228,12 @@ async def setup_climate_core_(var, config):
 | 
			
		||||
    if CONF_MAX_TEMPERATURE in visual:
 | 
			
		||||
        cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE]))
 | 
			
		||||
    if CONF_TEMPERATURE_STEP in visual:
 | 
			
		||||
        cg.add(var.set_visual_temperature_step_override(visual[CONF_TEMPERATURE_STEP]))
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_visual_temperature_step_override(
 | 
			
		||||
                visual[CONF_TEMPERATURE_STEP][CONF_TARGET_TEMPERATURE],
 | 
			
		||||
                visual[CONF_TEMPERATURE_STEP][CONF_CURRENT_TEMPERATURE],
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if CONF_MQTT_ID in config:
 | 
			
		||||
        mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
 | 
			
		||||
@@ -284,6 +324,10 @@ async def setup_climate_core_(var, config):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_CONTROL, []):
 | 
			
		||||
        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,6 +42,13 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
 | 
			
		||||
  Climate *climate_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ControlTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  ControlTrigger(Climate *climate) {
 | 
			
		||||
    climate->add_on_control_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class StateTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  StateTrigger(Climate *climate) {
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,7 @@ void ClimateCall::perform() {
 | 
			
		||||
  if (this->target_temperature_high_.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Target Temperature High: %.2f", *this->target_temperature_high_);
 | 
			
		||||
  }
 | 
			
		||||
  this->parent_->control_callback_.call();
 | 
			
		||||
  this->parent_->control(*this);
 | 
			
		||||
}
 | 
			
		||||
void ClimateCall::validate_() {
 | 
			
		||||
@@ -317,6 +318,10 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
 | 
			
		||||
  this->state_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Climate::add_on_control_callback(std::function<void()> &&callback) {
 | 
			
		||||
  this->control_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Random 32bit value; If this changes existing restore preferences are invalidated
 | 
			
		||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
 | 
			
		||||
 | 
			
		||||
@@ -430,9 +435,11 @@ ClimateTraits Climate::get_traits() {
 | 
			
		||||
  if (this->visual_max_temperature_override_.has_value()) {
 | 
			
		||||
    traits.set_visual_max_temperature(*this->visual_max_temperature_override_);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->visual_temperature_step_override_.has_value()) {
 | 
			
		||||
    traits.set_visual_temperature_step(*this->visual_temperature_step_override_);
 | 
			
		||||
  if (this->visual_target_temperature_step_override_.has_value()) {
 | 
			
		||||
    traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_);
 | 
			
		||||
    traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return traits;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -442,15 +449,11 @@ void Climate::set_visual_min_temperature_override(float visual_min_temperature_o
 | 
			
		||||
void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) {
 | 
			
		||||
  this->visual_max_temperature_override_ = visual_max_temperature_override;
 | 
			
		||||
}
 | 
			
		||||
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
 | 
			
		||||
  this->visual_temperature_step_override_ = visual_temperature_step_override;
 | 
			
		||||
void Climate::set_visual_temperature_step_override(float target, float current) {
 | 
			
		||||
  this->visual_target_temperature_step_override_ = target;
 | 
			
		||||
  this->visual_current_temperature_step_override_ = current;
 | 
			
		||||
}
 | 
			
		||||
#pragma GCC diagnostic push
 | 
			
		||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 | 
			
		||||
Climate::Climate(const std::string &name) : EntityBase(name) {}
 | 
			
		||||
#pragma GCC diagnostic pop
 | 
			
		||||
 | 
			
		||||
Climate::Climate() : Climate("") {}
 | 
			
		||||
ClimateCall Climate::make_call() { return ClimateCall(this); }
 | 
			
		||||
 | 
			
		||||
ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
 | 
			
		||||
@@ -541,7 +544,9 @@ void Climate::dump_traits_(const char *tag) {
 | 
			
		||||
  ESP_LOGCONFIG(tag, "  [x] Visual settings:");
 | 
			
		||||
  ESP_LOGCONFIG(tag, "      - Min: %.1f", traits.get_visual_min_temperature());
 | 
			
		||||
  ESP_LOGCONFIG(tag, "      - Max: %.1f", traits.get_visual_max_temperature());
 | 
			
		||||
  ESP_LOGCONFIG(tag, "      - Step: %.1f", traits.get_visual_temperature_step());
 | 
			
		||||
  ESP_LOGCONFIG(tag, "      - Step:");
 | 
			
		||||
  ESP_LOGCONFIG(tag, "          Target: %.1f", traits.get_visual_target_temperature_step());
 | 
			
		||||
  ESP_LOGCONFIG(tag, "          Current: %.1f", traits.get_visual_current_temperature_step());
 | 
			
		||||
  if (traits.get_supports_current_temperature()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports current temperature");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -166,11 +166,6 @@ struct ClimateDeviceRestoreState {
 | 
			
		||||
 */
 | 
			
		||||
class Climate : public EntityBase {
 | 
			
		||||
 public:
 | 
			
		||||
  /// Construct a climate device with empty name (will be set later).
 | 
			
		||||
  Climate();
 | 
			
		||||
  /// Construct a climate device with a name.
 | 
			
		||||
  Climate(const std::string &name);
 | 
			
		||||
 | 
			
		||||
  /// The active mode of the climate device.
 | 
			
		||||
  ClimateMode mode{CLIMATE_MODE_OFF};
 | 
			
		||||
  /// The active state of the climate device.
 | 
			
		||||
@@ -219,6 +214,14 @@ class Climate : public EntityBase {
 | 
			
		||||
   */
 | 
			
		||||
  void add_on_state_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add a callback for the climate device configuration; each time the configuration parameters of a climate device
 | 
			
		||||
   * is updated (using perform() of a ClimateCall), this callback will be called, before any on_state callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @param callback The callback to call.
 | 
			
		||||
   */
 | 
			
		||||
  void add_on_control_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  /** Make a climate device control call, this is used to control the climate device, see the ClimateCall description
 | 
			
		||||
   * for more info.
 | 
			
		||||
   * @return A new ClimateCall instance targeting this climate device.
 | 
			
		||||
@@ -241,7 +244,7 @@ class Climate : public EntityBase {
 | 
			
		||||
 | 
			
		||||
  void set_visual_min_temperature_override(float visual_min_temperature_override);
 | 
			
		||||
  void set_visual_max_temperature_override(float visual_max_temperature_override);
 | 
			
		||||
  void set_visual_temperature_step_override(float visual_temperature_step_override);
 | 
			
		||||
  void set_visual_temperature_step_override(float target, float current);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend ClimateCall;
 | 
			
		||||
@@ -285,10 +288,12 @@ class Climate : public EntityBase {
 | 
			
		||||
  void dump_traits_(const char *tag);
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void()> state_callback_{};
 | 
			
		||||
  CallbackManager<void()> control_callback_{};
 | 
			
		||||
  ESPPreferenceObject rtc_;
 | 
			
		||||
  optional<float> visual_min_temperature_override_{};
 | 
			
		||||
  optional<float> visual_max_temperature_override_{};
 | 
			
		||||
  optional<float> visual_temperature_step_override_{};
 | 
			
		||||
  optional<float> visual_target_temperature_step_override_{};
 | 
			
		||||
  optional<float> visual_current_temperature_step_override_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace climate
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,12 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace climate {
 | 
			
		||||
 | 
			
		||||
int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
 | 
			
		||||
  return step_to_accuracy_decimals(this->visual_temperature_step_);
 | 
			
		||||
int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const {
 | 
			
		||||
  return step_to_accuracy_decimals(this->visual_target_temperature_step_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int8_t ClimateTraits::get_current_temperature_accuracy_decimals() const {
 | 
			
		||||
  return step_to_accuracy_decimals(this->visual_current_temperature_step_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace climate
 | 
			
		||||
 
 | 
			
		||||
@@ -147,9 +147,20 @@ class ClimateTraits {
 | 
			
		||||
  void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
 | 
			
		||||
  float get_visual_max_temperature() const { return visual_max_temperature_; }
 | 
			
		||||
  void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
 | 
			
		||||
  float get_visual_temperature_step() const { return visual_temperature_step_; }
 | 
			
		||||
  int8_t get_temperature_accuracy_decimals() const;
 | 
			
		||||
  void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
 | 
			
		||||
  float get_visual_target_temperature_step() const { return visual_target_temperature_step_; }
 | 
			
		||||
  float get_visual_current_temperature_step() const { return visual_current_temperature_step_; }
 | 
			
		||||
  void set_visual_target_temperature_step(float temperature_step) {
 | 
			
		||||
    visual_target_temperature_step_ = temperature_step;
 | 
			
		||||
  }
 | 
			
		||||
  void set_visual_current_temperature_step(float temperature_step) {
 | 
			
		||||
    visual_current_temperature_step_ = temperature_step;
 | 
			
		||||
  }
 | 
			
		||||
  void set_visual_temperature_step(float temperature_step) {
 | 
			
		||||
    visual_target_temperature_step_ = temperature_step;
 | 
			
		||||
    visual_current_temperature_step_ = temperature_step;
 | 
			
		||||
  }
 | 
			
		||||
  int8_t get_target_temperature_accuracy_decimals() const;
 | 
			
		||||
  int8_t get_current_temperature_accuracy_decimals() const;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void set_mode_support_(climate::ClimateMode mode, bool supported) {
 | 
			
		||||
@@ -186,7 +197,8 @@ class ClimateTraits {
 | 
			
		||||
 | 
			
		||||
  float visual_min_temperature_{10};
 | 
			
		||||
  float visual_max_temperature_{30};
 | 
			
		||||
  float visual_temperature_step_{0.1};
 | 
			
		||||
  float visual_target_temperature_step_{0.1};
 | 
			
		||||
  float visual_current_temperature_step_{0.1};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace climate
 | 
			
		||||
 
 | 
			
		||||
@@ -10,23 +10,42 @@ CONF_RED_INT = "red_int"
 | 
			
		||||
CONF_GREEN_INT = "green_int"
 | 
			
		||||
CONF_BLUE_INT = "blue_int"
 | 
			
		||||
CONF_WHITE_INT = "white_int"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.declare_id(ColorStruct),
 | 
			
		||||
        cv.Exclusive(CONF_RED, "red"): cv.percentage,
 | 
			
		||||
        cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t,
 | 
			
		||||
        cv.Exclusive(CONF_GREEN, "green"): cv.percentage,
 | 
			
		||||
        cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t,
 | 
			
		||||
        cv.Exclusive(CONF_BLUE, "blue"): cv.percentage,
 | 
			
		||||
        cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t,
 | 
			
		||||
        cv.Exclusive(CONF_WHITE, "white"): cv.percentage,
 | 
			
		||||
        cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONF_HEX = "hex"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
def hex_color(value):
 | 
			
		||||
    if len(value) != 6:
 | 
			
		||||
        raise cv.Invalid("Color must have six digits")
 | 
			
		||||
    try:
 | 
			
		||||
        return (int(value[0:2], 16), int(value[2:4], 16), int(value[4:6], 16))
 | 
			
		||||
    except ValueError as exc:
 | 
			
		||||
        raise cv.Invalid("Color must be hexadecimal") from exc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Any(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.declare_id(ColorStruct),
 | 
			
		||||
            cv.Exclusive(CONF_RED, "red"): cv.percentage,
 | 
			
		||||
            cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t,
 | 
			
		||||
            cv.Exclusive(CONF_GREEN, "green"): cv.percentage,
 | 
			
		||||
            cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t,
 | 
			
		||||
            cv.Exclusive(CONF_BLUE, "blue"): cv.percentage,
 | 
			
		||||
            cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t,
 | 
			
		||||
            cv.Exclusive(CONF_WHITE, "white"): cv.percentage,
 | 
			
		||||
            cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.declare_id(ColorStruct),
 | 
			
		||||
            cv.Required(CONF_HEX): hex_color,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def from_rgbw(config):
 | 
			
		||||
    r = 0
 | 
			
		||||
    if CONF_RED in config:
 | 
			
		||||
        r = int(config[CONF_RED] * 255)
 | 
			
		||||
@@ -51,6 +70,16 @@ async def to_code(config):
 | 
			
		||||
    elif CONF_WHITE_INT in config:
 | 
			
		||||
        w = config[CONF_WHITE_INT]
 | 
			
		||||
 | 
			
		||||
    return (r, g, b, w)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    if CONF_HEX in config:
 | 
			
		||||
        r, g, b = config[CONF_HEX]
 | 
			
		||||
        w = 0
 | 
			
		||||
    else:
 | 
			
		||||
        r, g, b, w = from_rgbw(config)
 | 
			
		||||
 | 
			
		||||
    cg.new_variable(
 | 
			
		||||
        config[CONF_ID],
 | 
			
		||||
        cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)),
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,9 @@ CopyButton = copy_ns.class_("CopyButton", button.Button, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    button.button_schema()
 | 
			
		||||
    button.button_schema(CopyButton)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(CopyButton),
 | 
			
		||||
            cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -15,12 +15,15 @@ from .. import copy_ns
 | 
			
		||||
CopyNumber = copy_ns.class_("CopyNumber", number.Number, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = number.NUMBER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CopyNumber),
 | 
			
		||||
        cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    number.number_schema(CopyNumber)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,15 @@ from .. import copy_ns
 | 
			
		||||
CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = select.SELECT_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CopySelect),
 | 
			
		||||
        cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    select.select_schema(CopySelect)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,17 @@ from esphome.const import (
 | 
			
		||||
    CONF_STOP,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    DEVICE_CLASS_AWNING,
 | 
			
		||||
    DEVICE_CLASS_BLIND,
 | 
			
		||||
    DEVICE_CLASS_CURTAIN,
 | 
			
		||||
    DEVICE_CLASS_DAMPER,
 | 
			
		||||
    DEVICE_CLASS_DOOR,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_GARAGE,
 | 
			
		||||
    DEVICE_CLASS_GATE,
 | 
			
		||||
    DEVICE_CLASS_SHADE,
 | 
			
		||||
    DEVICE_CLASS_SHUTTER,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
@@ -25,17 +36,17 @@ IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
DEVICE_CLASSES = [
 | 
			
		||||
    "",
 | 
			
		||||
    "awning",
 | 
			
		||||
    "blind",
 | 
			
		||||
    "curtain",
 | 
			
		||||
    "damper",
 | 
			
		||||
    "door",
 | 
			
		||||
    "garage",
 | 
			
		||||
    "gate",
 | 
			
		||||
    "shade",
 | 
			
		||||
    "shutter",
 | 
			
		||||
    "window",
 | 
			
		||||
    DEVICE_CLASS_AWNING,
 | 
			
		||||
    DEVICE_CLASS_BLIND,
 | 
			
		||||
    DEVICE_CLASS_CURTAIN,
 | 
			
		||||
    DEVICE_CLASS_DAMPER,
 | 
			
		||||
    DEVICE_CLASS_DOOR,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_GARAGE,
 | 
			
		||||
    DEVICE_CLASS_GATE,
 | 
			
		||||
    DEVICE_CLASS_SHADE,
 | 
			
		||||
    DEVICE_CLASS_SHUTTER,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
cover_ns = cg.esphome_ns.namespace("cover")
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {}
 | 
			
		||||
Cover::Cover() : position{COVER_OPEN} {}
 | 
			
		||||
 | 
			
		||||
CoverCall::CoverCall(Cover *parent) : parent_(parent) {}
 | 
			
		||||
CoverCall &CoverCall::set_command(const char *command) {
 | 
			
		||||
@@ -204,18 +204,13 @@ optional<CoverRestoreState> Cover::restore_state_() {
 | 
			
		||||
    return {};
 | 
			
		||||
  return recovered;
 | 
			
		||||
}
 | 
			
		||||
Cover::Cover() : Cover("") {}
 | 
			
		||||
std::string Cover::get_device_class() {
 | 
			
		||||
  if (this->device_class_override_.has_value())
 | 
			
		||||
    return *this->device_class_override_;
 | 
			
		||||
#pragma GCC diagnostic push
 | 
			
		||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 | 
			
		||||
  return this->device_class();
 | 
			
		||||
#pragma GCC diagnostic pop
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
 | 
			
		||||
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
 | 
			
		||||
std::string Cover::device_class() { return ""; }
 | 
			
		||||
 | 
			
		||||
CoverCall CoverRestoreState::to_call(Cover *cover) {
 | 
			
		||||
  auto call = cover->make_call();
 | 
			
		||||
 
 | 
			
		||||
@@ -111,7 +111,6 @@ const char *cover_operation_to_str(CoverOperation op);
 | 
			
		||||
class Cover : public EntityBase {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit Cover();
 | 
			
		||||
  explicit Cover(const std::string &name);
 | 
			
		||||
 | 
			
		||||
  /// The current operation of the cover (idle, opening, closing).
 | 
			
		||||
  CoverOperation current_operation{COVER_OPERATION_IDLE};
 | 
			
		||||
@@ -170,12 +169,6 @@ class Cover : public EntityBase {
 | 
			
		||||
 | 
			
		||||
  virtual void control(const CoverCall &call) = 0;
 | 
			
		||||
 | 
			
		||||
  /** Override this to set the default device class.
 | 
			
		||||
   *
 | 
			
		||||
   * @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
 | 
			
		||||
   */
 | 
			
		||||
  virtual std::string device_class();
 | 
			
		||||
 | 
			
		||||
  optional<CoverRestoreState> restore_state_();
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void()> state_callback_{};
 | 
			
		||||
 
 | 
			
		||||
@@ -305,7 +305,7 @@ bool CS5460AComponent::check_status_() {
 | 
			
		||||
      voltage_sensor_->publish_state(raw_voltage * voltage_multiplier_);
 | 
			
		||||
 | 
			
		||||
    if (power_sensor_ != nullptr && raw_energy != prev_raw_energy_) {
 | 
			
		||||
      int32_t raw = (int32_t)(raw_energy << 8) >> 8; /* Sign-extend */
 | 
			
		||||
      int32_t raw = (int32_t) (raw_energy << 8) >> 8; /* Sign-extend */
 | 
			
		||||
      power_sensor_->publish_state(raw * power_multiplier_);
 | 
			
		||||
      prev_raw_energy_ = raw_energy;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,10 @@ void CTClampSensor::update() {
 | 
			
		||||
 | 
			
		||||
    const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_;
 | 
			
		||||
    const float rms_dc = this->sample_sum_ / this->num_samples_;
 | 
			
		||||
    const float rms_ac = std::sqrt(rms_ac_dc_squared - rms_dc * rms_dc);
 | 
			
		||||
    const float rms_ac_squared = rms_ac_dc_squared - rms_dc * rms_dc;
 | 
			
		||||
    float rms_ac = 0;
 | 
			
		||||
    if (rms_ac_squared > 0)
 | 
			
		||||
      rms_ac = std::sqrt(rms_ac_squared);
 | 
			
		||||
    ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac,
 | 
			
		||||
             this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
 | 
			
		||||
    this->publish_state(rms_ac);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CustomSensorConstructor),
 | 
			
		||||
        cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
        cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA),
 | 
			
		||||
        cv.Required(CONF_SENSORS): cv.ensure_list(sensor.sensor_schema()),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -61,8 +61,8 @@ void DalyBmsComponent::request_data_(uint8_t data_id) {
 | 
			
		||||
  request_message[9] = 0x00;     //     |
 | 
			
		||||
  request_message[10] = 0x00;    //     |
 | 
			
		||||
  request_message[11] = 0x00;    // Empty Data
 | 
			
		||||
  request_message[12] = (uint8_t)(request_message[0] + request_message[1] + request_message[2] +
 | 
			
		||||
                                  request_message[3]);  // Checksum (Lower byte of the other bytes sum)
 | 
			
		||||
  request_message[12] = (uint8_t) (request_message[0] + request_message[1] + request_message[2] +
 | 
			
		||||
                                   request_message[3]);  // Checksum (Lower byte of the other bytes sum)
 | 
			
		||||
 | 
			
		||||
  this->write_array(request_message, sizeof(request_message));
 | 
			
		||||
  this->flush();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,11 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome.components import logger
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BLOCK,
 | 
			
		||||
    CONF_DEVICE,
 | 
			
		||||
    CONF_FRAGMENTATION,
 | 
			
		||||
    CONF_FREE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_LEVEL,
 | 
			
		||||
    CONF_LOGGER,
 | 
			
		||||
    CONF_LOOP_TIME,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -21,38 +17,29 @@ debug_ns = cg.esphome_ns.namespace("debug")
 | 
			
		||||
DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(DebugComponent),
 | 
			
		||||
        cv.Optional(CONF_DEVICE): cv.invalid(
 | 
			
		||||
            "The 'device' option has been moved to the 'debug' text_sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_FREE): cv.invalid(
 | 
			
		||||
            "The 'free' option has been moved to the 'debug' sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_BLOCK): cv.invalid(
 | 
			
		||||
            "The 'block' option has been moved to the 'debug' sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_FRAGMENTATION): cv.invalid(
 | 
			
		||||
            "The 'fragmentation' option has been moved to the 'debug' sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_LOOP_TIME): cv.invalid(
 | 
			
		||||
            "The 'loop_time' option has been moved to the 'debug' sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("60s"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _final_validate(_):
 | 
			
		||||
    logger_conf = fv.full_config.get()[CONF_LOGGER]
 | 
			
		||||
    severity = logger.LOG_LEVEL_SEVERITY.index(logger_conf[CONF_LEVEL])
 | 
			
		||||
    if severity < logger.LOG_LEVEL_SEVERITY.index("DEBUG"):
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "The debug component requires the logger to be at least at DEBUG level"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = _final_validate
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(DebugComponent),
 | 
			
		||||
            cv.Optional(CONF_DEVICE): cv.invalid(
 | 
			
		||||
                "The 'device' option has been moved to the 'debug' text_sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_FREE): cv.invalid(
 | 
			
		||||
                "The 'free' option has been moved to the 'debug' sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_BLOCK): cv.invalid(
 | 
			
		||||
                "The 'block' option has been moved to the 'debug' sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_FRAGMENTATION): cv.invalid(
 | 
			
		||||
                "The 'fragmentation' option has been moved to the 'debug' sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_LOOP_TIME): cv.invalid(
 | 
			
		||||
                "The 'loop_time' option has been moved to the 'debug' sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.polling_component_schema("60s")),
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,10 @@ static uint32_t get_free_heap() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DebugComponent::dump_config() {
 | 
			
		||||
#ifndef ESPHOME_LOG_HAS_DEBUG
 | 
			
		||||
  return;  // Can't log below if debug logging is disabled
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  std::string device_info;
 | 
			
		||||
  std::string reset_reason;
 | 
			
		||||
  device_info.reserve(256);
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ from esphome.components.esp32.const import (
 | 
			
		||||
    VARIANT_ESP32,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32S2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
WAKEUP_PINS = {
 | 
			
		||||
@@ -69,6 +70,30 @@ WAKEUP_PINS = {
 | 
			
		||||
        20,
 | 
			
		||||
        21,
 | 
			
		||||
    ],
 | 
			
		||||
    VARIANT_ESP32S3: [
 | 
			
		||||
        0,
 | 
			
		||||
        1,
 | 
			
		||||
        2,
 | 
			
		||||
        3,
 | 
			
		||||
        4,
 | 
			
		||||
        5,
 | 
			
		||||
        6,
 | 
			
		||||
        7,
 | 
			
		||||
        8,
 | 
			
		||||
        9,
 | 
			
		||||
        10,
 | 
			
		||||
        11,
 | 
			
		||||
        12,
 | 
			
		||||
        13,
 | 
			
		||||
        14,
 | 
			
		||||
        15,
 | 
			
		||||
        16,
 | 
			
		||||
        17,
 | 
			
		||||
        18,
 | 
			
		||||
        19,
 | 
			
		||||
        20,
 | 
			
		||||
        21,
 | 
			
		||||
    ],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -284,9 +284,10 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ): [
 | 
			
		||||
            number.NUMBER_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
 | 
			
		||||
            number.number_schema(DemoNumber)
 | 
			
		||||
            .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(): cv.declare_id(DemoNumber),
 | 
			
		||||
                    cv.Required(CONF_TYPE): cv.enum(NUMBER_TYPES, int=True),
 | 
			
		||||
                    cv.Required(CONF_MIN_VALUE): cv.float_,
 | 
			
		||||
                    cv.Required(CONF_MAX_VALUE): cv.float_,
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ void DFPlayer::play_folder(uint16_t folder, uint16_t file) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) {
 | 
			
		||||
  uint8_t buffer[10]{0x7e, 0xff, 0x06, cmd, 0x01, (uint8_t)(argument >> 8), (uint8_t) argument, 0x00, 0x00, 0xef};
 | 
			
		||||
  uint8_t buffer[10]{0x7e, 0xff, 0x06, cmd, 0x01, (uint8_t) (argument >> 8), (uint8_t) argument, 0x00, 0x00, 0xef};
 | 
			
		||||
  uint16_t checksum = 0;
 | 
			
		||||
  for (uint8_t i = 1; i < 7; i++)
 | 
			
		||||
    checksum += buffer[i];
 | 
			
		||||
 
 | 
			
		||||
@@ -32,9 +32,11 @@ void Rect::extend(Rect rect) {
 | 
			
		||||
    this->h = rect.h;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->x > rect.x) {
 | 
			
		||||
      this->w = this->w + (this->x - rect.x);
 | 
			
		||||
      this->x = rect.x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y > rect.y) {
 | 
			
		||||
      this->h = this->h + (this->y - rect.y);
 | 
			
		||||
      this->y = rect.y;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->x2() < rect.x2()) {
 | 
			
		||||
@@ -49,29 +51,35 @@ void Rect::shrink(Rect rect) {
 | 
			
		||||
  if (!this->inside(rect)) {
 | 
			
		||||
    (*this) = Rect();
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->x < rect.x) {
 | 
			
		||||
      this->x = rect.x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y < rect.y) {
 | 
			
		||||
      this->y = rect.y;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->x2() > rect.x2()) {
 | 
			
		||||
      this->w = rect.x2() - this->x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->x < rect.x) {
 | 
			
		||||
      this->w = this->w + (this->x - rect.x);
 | 
			
		||||
      this->x = rect.x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y2() > rect.y2()) {
 | 
			
		||||
      this->h = rect.y2() - this->y;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y < rect.y) {
 | 
			
		||||
      this->h = this->h + (this->y - rect.y);
 | 
			
		||||
      this->y = rect.y;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Rect::inside(int16_t x, int16_t y, bool absolute) {  // NOLINT
 | 
			
		||||
bool Rect::equal(Rect rect) {
 | 
			
		||||
  return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) {  // NOLINT
 | 
			
		||||
  if (!this->is_set()) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  if (absolute) {
 | 
			
		||||
    return ((x >= 0) && (x <= this->w) && (y >= 0) && (y <= this->h));
 | 
			
		||||
    return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2()));
 | 
			
		||||
  } else {
 | 
			
		||||
    return ((x >= this->x) && (x <= this->x2()) && (y >= this->y) && (y <= this->y2()));
 | 
			
		||||
    return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -80,15 +88,16 @@ bool Rect::inside(Rect rect, bool absolute) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  if (absolute) {
 | 
			
		||||
    return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
 | 
			
		||||
  } else {
 | 
			
		||||
    return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
 | 
			
		||||
  } else {
 | 
			
		||||
    return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Rect::info(const std::string &prefix) {
 | 
			
		||||
  if (this->is_set()) {
 | 
			
		||||
    ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d]", prefix.c_str(), this->x, this->y, this->w, this->h);
 | 
			
		||||
    ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(),
 | 
			
		||||
             this->y2());
 | 
			
		||||
  } else
 | 
			
		||||
    ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
 | 
			
		||||
}
 | 
			
		||||
@@ -603,10 +612,10 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
 | 
			
		||||
  *x_offset = min_x;
 | 
			
		||||
  *width = x - min_x;
 | 
			
		||||
}
 | 
			
		||||
const std::vector<Glyph> &Font::get_glyphs() const { return this->glyphs_; }
 | 
			
		||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
 | 
			
		||||
  glyphs_.reserve(data_nr);
 | 
			
		||||
  for (int i = 0; i < data_nr; ++i)
 | 
			
		||||
    glyphs_.emplace_back(data + i);
 | 
			
		||||
    glyphs_.emplace_back(&data[i]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Image::get_pixel(int x, int y) const {
 | 
			
		||||
@@ -655,7 +664,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 >= (uint32_t)(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));
 | 
			
		||||
@@ -664,7 +673,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 >= (uint32_t)(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) |
 | 
			
		||||
@@ -676,7 +685,7 @@ Color Animation::get_rgb565_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 >= (uint32_t)(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) * 2;
 | 
			
		||||
  uint16_t rgb565 =
 | 
			
		||||
@@ -690,7 +699,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 >= (uint32_t)(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);
 | 
			
		||||
 
 | 
			
		||||
@@ -120,8 +120,9 @@ class Rect {
 | 
			
		||||
  void extend(Rect rect);
 | 
			
		||||
  void shrink(Rect rect);
 | 
			
		||||
 | 
			
		||||
  bool inside(Rect rect, bool absolute = false);
 | 
			
		||||
  bool inside(int16_t x, int16_t y, bool absolute = false);
 | 
			
		||||
  bool inside(Rect rect, bool absolute = true);
 | 
			
		||||
  bool inside(int16_t test_x, int16_t test_y, bool absolute = true);
 | 
			
		||||
  bool equal(Rect rect);
 | 
			
		||||
  void info(const std::string &prefix = "rect info:");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -526,10 +527,10 @@ class Font {
 | 
			
		||||
  inline int get_baseline() { return this->baseline_; }
 | 
			
		||||
  inline int get_height() { return this->height_; }
 | 
			
		||||
 | 
			
		||||
  const std::vector<Glyph> &get_glyphs() const;
 | 
			
		||||
  const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<Glyph> glyphs_;
 | 
			
		||||
  std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_;
 | 
			
		||||
  int baseline_;
 | 
			
		||||
  int height_;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -168,7 +168,7 @@ void ENS210Component::update() {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // Pack bytes for humidity
 | 
			
		||||
    h_val_data = (uint32_t)((uint32_t) data[5] << 16 | (uint32_t) data[4] << 8 | (uint32_t) data[3]);
 | 
			
		||||
    h_val_data = (uint32_t) ((uint32_t) data[5] << 16 | (uint32_t) data[4] << 8 | (uint32_t) data[3]);
 | 
			
		||||
    // Extract humidity data and update the status
 | 
			
		||||
    extract_measurement_(h_val_data, &humidity_data, &humidity_status);
 | 
			
		||||
 | 
			
		||||
@@ -183,7 +183,7 @@ void ENS210Component::update() {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // Pack bytes for temperature
 | 
			
		||||
    t_val_data = (uint32_t)((uint32_t) data[2] << 16 | (uint32_t) data[1] << 8 | (uint32_t) data[0]);
 | 
			
		||||
    t_val_data = (uint32_t) ((uint32_t) data[2] << 16 | (uint32_t) data[1] << 8 | (uint32_t) data[0]);
 | 
			
		||||
    // Extract temperature data and update the status
 | 
			
		||||
    extract_measurement_(t_val_data, &temperature_data, &temperature_status);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,29 +4,43 @@ from pathlib import Path
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from esphome.helpers import copy_file_if_changed, write_file_if_changed
 | 
			
		||||
from esphome.helpers import copy_file_if_changed, write_file_if_changed, mkdir_p
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BOARD,
 | 
			
		||||
    CONF_COMPONENTS,
 | 
			
		||||
    CONF_FRAMEWORK,
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
    CONF_SOURCE,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_VARIANT,
 | 
			
		||||
    CONF_VERSION,
 | 
			
		||||
    CONF_ADVANCED,
 | 
			
		||||
    CONF_REFRESH,
 | 
			
		||||
    CONF_PATH,
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
    CONF_REF,
 | 
			
		||||
    CONF_IGNORE_EFUSE_MAC_CRC,
 | 
			
		||||
    KEY_CORE,
 | 
			
		||||
    KEY_FRAMEWORK_VERSION,
 | 
			
		||||
    KEY_TARGET_FRAMEWORK,
 | 
			
		||||
    KEY_TARGET_PLATFORM,
 | 
			
		||||
    TYPE_GIT,
 | 
			
		||||
    TYPE_LOCAL,
 | 
			
		||||
    __version__,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, HexInt
 | 
			
		||||
from esphome.core import CORE, HexInt, TimePeriod
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome import git
 | 
			
		||||
 | 
			
		||||
from .const import (  # noqa
 | 
			
		||||
    KEY_BOARD,
 | 
			
		||||
    KEY_COMPONENTS,
 | 
			
		||||
    KEY_ESP32,
 | 
			
		||||
    KEY_PATH,
 | 
			
		||||
    KEY_REF,
 | 
			
		||||
    KEY_REFRESH,
 | 
			
		||||
    KEY_REPO,
 | 
			
		||||
    KEY_SDKCONFIG_OPTIONS,
 | 
			
		||||
    KEY_VARIANT,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
@@ -51,6 +65,7 @@ def set_core_data(config):
 | 
			
		||||
    if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
 | 
			
		||||
        CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf"
 | 
			
		||||
        CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {}
 | 
			
		||||
        CORE.data[KEY_ESP32][KEY_COMPONENTS] = {}
 | 
			
		||||
    elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
 | 
			
		||||
        CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
 | 
			
		||||
    CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
 | 
			
		||||
@@ -104,6 +119,21 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
 | 
			
		||||
    CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_idf_component(
 | 
			
		||||
    name: str, repo: str, ref: str = None, path: str = None, refresh: TimePeriod = None
 | 
			
		||||
):
 | 
			
		||||
    """Add an esp-idf component to the project."""
 | 
			
		||||
    if not CORE.using_esp_idf:
 | 
			
		||||
        raise ValueError("Not an esp-idf project")
 | 
			
		||||
    if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]:
 | 
			
		||||
        CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
 | 
			
		||||
            KEY_REPO: repo,
 | 
			
		||||
            KEY_REF: ref,
 | 
			
		||||
            KEY_PATH: path,
 | 
			
		||||
            KEY_REFRESH: refresh,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _format_framework_arduino_version(ver: cv.Version) -> str:
 | 
			
		||||
    # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
 | 
			
		||||
    # a PIO platformio/framework-arduinoespressif32 value
 | 
			
		||||
@@ -133,23 +163,23 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5)
 | 
			
		||||
# The platformio/espressif32 version to use for arduino frameworks
 | 
			
		||||
#  - https://github.com/platformio/platform-espressif32/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
 | 
			
		||||
ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0)
 | 
			
		||||
ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 0)
 | 
			
		||||
 | 
			
		||||
# The default/recommended esp-idf framework version
 | 
			
		||||
#  - https://github.com/espressif/esp-idf/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
 | 
			
		||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 2)
 | 
			
		||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 4)
 | 
			
		||||
# The platformio/espressif32 version to use for esp-idf frameworks
 | 
			
		||||
#  - https://github.com/platformio/platform-espressif32/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
 | 
			
		||||
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 2, 0)
 | 
			
		||||
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 3, 0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _arduino_check_versions(value):
 | 
			
		||||
    value = value.copy()
 | 
			
		||||
    lookups = {
 | 
			
		||||
        "dev": (cv.Version(2, 0, 5), "https://github.com/espressif/arduino-esp32.git"),
 | 
			
		||||
        "latest": (cv.Version(2, 0, 5), None),
 | 
			
		||||
        "dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"),
 | 
			
		||||
        "latest": (cv.Version(2, 0, 7), None),
 | 
			
		||||
        "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -183,8 +213,8 @@ def _arduino_check_versions(value):
 | 
			
		||||
def _esp_idf_check_versions(value):
 | 
			
		||||
    value = value.copy()
 | 
			
		||||
    lookups = {
 | 
			
		||||
        "dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"),
 | 
			
		||||
        "latest": (cv.Version(4, 4, 2), None),
 | 
			
		||||
        "dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"),
 | 
			
		||||
        "latest": (cv.Version(5, 0, 1), None),
 | 
			
		||||
        "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -270,6 +300,18 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
 | 
			
		||||
                    cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
 | 
			
		||||
                cv.Schema(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.Required(CONF_NAME): cv.string_strict,
 | 
			
		||||
                        cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA,
 | 
			
		||||
                        cv.Optional(CONF_PATH): cv.string,
 | 
			
		||||
                        cv.Optional(CONF_REFRESH, default="1d"): cv.All(
 | 
			
		||||
                            cv.string, cv.source_refresh
 | 
			
		||||
                        ),
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    _esp_idf_check_versions,
 | 
			
		||||
@@ -372,6 +414,19 @@ async def to_code(config):
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        for component in conf[CONF_COMPONENTS]:
 | 
			
		||||
            source = component[CONF_SOURCE]
 | 
			
		||||
            if source[CONF_TYPE] == TYPE_GIT:
 | 
			
		||||
                add_idf_component(
 | 
			
		||||
                    name=component[CONF_NAME],
 | 
			
		||||
                    repo=source[CONF_URL],
 | 
			
		||||
                    ref=source.get(CONF_REF),
 | 
			
		||||
                    path=component.get(CONF_PATH),
 | 
			
		||||
                    refresh=component[CONF_REFRESH],
 | 
			
		||||
                )
 | 
			
		||||
            elif source[CONF_TYPE] == TYPE_LOCAL:
 | 
			
		||||
                _LOGGER.warning("Local components are not implemented yet.")
 | 
			
		||||
 | 
			
		||||
    elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
 | 
			
		||||
        cg.add_platformio_option("framework", "arduino")
 | 
			
		||||
        cg.add_build_flag("-DUSE_ARDUINO")
 | 
			
		||||
@@ -468,6 +523,32 @@ def copy_files():
 | 
			
		||||
            __version__,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        import shutil
 | 
			
		||||
 | 
			
		||||
        shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True)
 | 
			
		||||
 | 
			
		||||
        if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
 | 
			
		||||
            components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
 | 
			
		||||
 | 
			
		||||
            for name, component in components.items():
 | 
			
		||||
                repo_dir, _ = git.clone_or_update(
 | 
			
		||||
                    url=component[KEY_REPO],
 | 
			
		||||
                    ref=component[KEY_REF],
 | 
			
		||||
                    refresh=component[KEY_REFRESH],
 | 
			
		||||
                    domain="idf_components",
 | 
			
		||||
                )
 | 
			
		||||
                mkdir_p(CORE.relative_build_path("components"))
 | 
			
		||||
                component_dir = repo_dir
 | 
			
		||||
                if component[KEY_PATH] is not None:
 | 
			
		||||
                    component_dir = component_dir / component[KEY_PATH]
 | 
			
		||||
 | 
			
		||||
                shutil.copytree(
 | 
			
		||||
                    component_dir,
 | 
			
		||||
                    CORE.relative_build_path(f"components/{name}"),
 | 
			
		||||
                    dirs_exist_ok=True,
 | 
			
		||||
                    ignore=shutil.ignore_patterns(".git", ".github"),
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    dir = os.path.dirname(__file__)
 | 
			
		||||
    post_build_file = os.path.join(dir, "post_build.py.script")
 | 
			
		||||
    copy_file_if_changed(
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,11 @@ KEY_ESP32 = "esp32"
 | 
			
		||||
KEY_BOARD = "board"
 | 
			
		||||
KEY_VARIANT = "variant"
 | 
			
		||||
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
 | 
			
		||||
KEY_COMPONENTS = "components"
 | 
			
		||||
KEY_REPO = "repo"
 | 
			
		||||
KEY_REF = "ref"
 | 
			
		||||
KEY_REFRESH = "refresh"
 | 
			
		||||
KEY_PATH = "path"
 | 
			
		||||
 | 
			
		||||
VARIANT_ESP32 = "ESP32"
 | 
			
		||||
VARIANT_ESP32S2 = "ESP32S2"
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ void loop();
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR HOT yield() { vPortYield(); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT millis() { return (uint32_t) (esp_timer_get_time() / 1000ULL); }
 | 
			
		||||
void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
 | 
			
		||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
#include "gpio.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32 {
 | 
			
		||||
@@ -74,7 +75,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
 | 
			
		||||
 | 
			
		||||
std::string ESP32InternalGPIOPin::dump_summary() const {
 | 
			
		||||
  char buffer[32];
 | 
			
		||||
  snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast<uint32_t>(pin_));
 | 
			
		||||
  snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(pin_));
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,25 @@
 | 
			
		||||
# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
 | 
			
		||||
    import esptool
 | 
			
		||||
else:
 | 
			
		||||
    import subprocess
 | 
			
		||||
from SCons.Script import ARGUMENTS
 | 
			
		||||
 | 
			
		||||
# pylint: disable=E0602
 | 
			
		||||
Import("env")  # noqa
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
 | 
			
		||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
 | 
			
		||||
    try:
 | 
			
		||||
        import esptool
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        env.Execute("$PYTHONEXE -m pip install esptool")
 | 
			
		||||
else:
 | 
			
		||||
    import subprocess
 | 
			
		||||
from SCons.Script import ARGUMENTS
 | 
			
		||||
 | 
			
		||||
# Copy over the default sdkconfig.
 | 
			
		||||
from os import path
 | 
			
		||||
if path.exists("./sdkconfig.defaults"):
 | 
			
		||||
    os.makedirs(".temp", exist_ok=True)
 | 
			
		||||
    shutil.copy("./sdkconfig.defaults", "./.temp/sdkconfig-esp32-idf")
 | 
			
		||||
 | 
			
		||||
def esp32_create_combined_bin(source, target, env):
 | 
			
		||||
    verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include <nvs_flash.h>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
@@ -101,7 +102,7 @@ class ESP32Preferences : public ESPPreferences {
 | 
			
		||||
    pref->nvs_handle = nvs_handle;
 | 
			
		||||
 | 
			
		||||
    uint32_t keyval = type;
 | 
			
		||||
    pref->key = str_sprintf("%u", keyval);
 | 
			
		||||
    pref->key = str_sprintf("%" PRIu32, keyval);
 | 
			
		||||
 | 
			
		||||
    return ESPPreferenceObject(pref);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
 | 
			
		||||
#include <esp_bt.h>
 | 
			
		||||
#include <esp_bt_main.h>
 | 
			
		||||
#include <esp_bt_device.h>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/FreeRTOSConfig.h>
 | 
			
		||||
@@ -211,7 +212,16 @@ void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if
 | 
			
		||||
 | 
			
		||||
float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
 | 
			
		||||
 | 
			
		||||
void ESP32BLE::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE:"); }
 | 
			
		||||
void ESP32BLE::dump_config() {
 | 
			
		||||
  const uint8_t *mac_address = esp_bt_dev_get_address();
 | 
			
		||||
  if (mac_address) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "ESP32 BLE:");
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2],
 | 
			
		||||
                  mac_address[3], mac_address[4], mac_address[5]);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ESP32BLE *global_ble = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
 | 
			
		||||
void BLEClientBase::connect() {
 | 
			
		||||
  ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           this->remote_addr_type_);
 | 
			
		||||
  this->paired_ = false;
 | 
			
		||||
  auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
@@ -72,6 +73,8 @@ void BLEClientBase::connect() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::disconnect() {
 | 
			
		||||
  if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING)
 | 
			
		||||
    return;
 | 
			
		||||
@@ -247,11 +250,15 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    // This event is sent by the server when it requests security
 | 
			
		||||
    case ESP_GAP_BLE_SEC_REQ_EVT:
 | 
			
		||||
      if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
 | 
			
		||||
        break;
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event);
 | 
			
		||||
      esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
 | 
			
		||||
      break;
 | 
			
		||||
    // This event is sent once authentication has completed
 | 
			
		||||
    case ESP_GAP_BLE_AUTH_CMPL_EVT:
 | 
			
		||||
      if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
 | 
			
		||||
        break;
 | 
			
		||||
      esp_bd_addr_t bd_addr;
 | 
			
		||||
      memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
 | 
			
		||||
      ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
@@ -260,6 +267,7 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
 | 
			
		||||
        ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
                 param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
      } else {
 | 
			
		||||
        this->paired_ = true;
 | 
			
		||||
        ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
 | 
			
		||||
                 param->ble_security.auth_cmpl.auth_mode);
 | 
			
		||||
@@ -308,18 +316,18 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
 | 
			
		||||
    case 0xD:  // int12.
 | 
			
		||||
    case 0xE:  // int16.
 | 
			
		||||
      if (length > 2) {
 | 
			
		||||
        return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
 | 
			
		||||
        return (float) ((int16_t) (value[1] << 8) + (int16_t) value[2]);
 | 
			
		||||
      }
 | 
			
		||||
      // fall through
 | 
			
		||||
    case 0xF:  // int24.
 | 
			
		||||
      if (length > 3) {
 | 
			
		||||
        return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
 | 
			
		||||
        return (float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3]));
 | 
			
		||||
      }
 | 
			
		||||
      // fall through
 | 
			
		||||
    case 0x10:  // int32.
 | 
			
		||||
      if (length > 4) {
 | 
			
		||||
        return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
 | 
			
		||||
                        (int32_t)(value[4]));
 | 
			
		||||
        return (float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) +
 | 
			
		||||
                        (int32_t) (value[4]));
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGW(TAG, "[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->connection_index_,
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
  void connect() override;
 | 
			
		||||
  esp_err_t pair();
 | 
			
		||||
  void disconnect();
 | 
			
		||||
  void release_services();
 | 
			
		||||
 | 
			
		||||
@@ -44,10 +45,11 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
      memset(this->remote_bda_, 0, sizeof(this->remote_bda_));
 | 
			
		||||
      this->address_str_ = "";
 | 
			
		||||
    } else {
 | 
			
		||||
      this->address_str_ = str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t)(this->address_ >> 40) & 0xff,
 | 
			
		||||
                                        (uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff,
 | 
			
		||||
                                        (uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff,
 | 
			
		||||
                                        (uint8_t)(this->address_ >> 0) & 0xff);
 | 
			
		||||
      this->address_str_ =
 | 
			
		||||
          str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t) (this->address_ >> 40) & 0xff,
 | 
			
		||||
                       (uint8_t) (this->address_ >> 32) & 0xff, (uint8_t) (this->address_ >> 24) & 0xff,
 | 
			
		||||
                       (uint8_t) (this->address_ >> 16) & 0xff, (uint8_t) (this->address_ >> 8) & 0xff,
 | 
			
		||||
                       (uint8_t) (this->address_ >> 0) & 0xff);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  std::string address_str() const { return this->address_str_; }
 | 
			
		||||
@@ -71,6 +73,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
  void set_remote_addr_type(esp_ble_addr_type_t address_type) { this->remote_addr_type_ = address_type; }
 | 
			
		||||
  uint16_t get_conn_id() const { return this->conn_id_; }
 | 
			
		||||
  uint64_t get_address() const { return this->address_; }
 | 
			
		||||
  bool is_paired() const { return this->paired_; }
 | 
			
		||||
 | 
			
		||||
  uint8_t get_connection_index() const { return this->connection_index_; }
 | 
			
		||||
 | 
			
		||||
@@ -86,6 +89,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
  uint8_t connection_index_;
 | 
			
		||||
  int16_t service_count_{0};
 | 
			
		||||
  uint16_t mtu_{23};
 | 
			
		||||
  bool paired_{false};
 | 
			
		||||
  espbt::ConnectionType connection_type_{espbt::ConnectionType::V1};
 | 
			
		||||
 | 
			
		||||
  std::vector<BLEService *> services_;
 | 
			
		||||
 
 | 
			
		||||
@@ -148,44 +148,44 @@ bool BLECharacteristic::is_failed() {
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::set_broadcast_property(bool value) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BLECharacteristic::set_indicate_property(bool value) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BLECharacteristic::set_notify_property(bool value) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BLECharacteristic::set_read_property(bool value) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BLECharacteristic::set_write_property(bool value) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BLECharacteristic::set_write_no_response_property(bool value) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
 | 
			
		||||
    this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,14 @@ void ESP32BLETracker::setup() {
 | 
			
		||||
    ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ExternalRAMAllocator<esp_ble_gap_cb_param_t::ble_scan_result_evt_param> allocator(
 | 
			
		||||
      ExternalRAMAllocator<esp_ble_gap_cb_param_t::ble_scan_result_evt_param>::ALLOW_FAILURE);
 | 
			
		||||
  this->scan_result_buffer_ = allocator.allocate(ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE);
 | 
			
		||||
 | 
			
		||||
  if (this->scan_result_buffer_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Could not allocate buffer for BLE Tracker!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  global_esp32_ble_tracker = this;
 | 
			
		||||
  this->scan_result_lock_ = xSemaphoreCreateMutex();
 | 
			
		||||
@@ -107,7 +115,7 @@ void ESP32BLETracker::loop() {
 | 
			
		||||
        xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
 | 
			
		||||
      uint32_t index = this->scan_result_index_;
 | 
			
		||||
      if (index) {
 | 
			
		||||
        if (index >= 16) {
 | 
			
		||||
        if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
 | 
			
		||||
          ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
 | 
			
		||||
        }
 | 
			
		||||
        for (size_t i = 0; i < index; i++) {
 | 
			
		||||
@@ -322,7 +330,7 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_
 | 
			
		||||
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
 | 
			
		||||
  if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
 | 
			
		||||
    if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
 | 
			
		||||
      if (this->scan_result_index_ < 16) {
 | 
			
		||||
      if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
 | 
			
		||||
        this->scan_result_buffer_[this->scan_result_index_++] = param;
 | 
			
		||||
      }
 | 
			
		||||
      xSemaphoreGive(this->scan_result_lock_);
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user