mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 00:21:56 +00:00 
			
		
		
		
	Compare commits
	
		
			191 Commits
		
	
	
		
			jesserockz
			...
			2023.6.0b5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					a40fa22055 | ||
| 
						 | 
					2047bba4f7 | ||
| 
						 | 
					a064ab5c2b | ||
| 
						 | 
					0c37d06936 | ||
| 
						 | 
					d919373853 | ||
| 
						 | 
					6c00be0a63 | ||
| 
						 | 
					0671287b80 | ||
| 
						 | 
					867b4719d1 | ||
| 
						 | 
					fc544dc389 | ||
| 
						 | 
					501fe83710 | ||
| 
						 | 
					5513b0e121 | ||
| 
						 | 
					959e7745a6 | ||
| 
						 | 
					76e947651d | ||
| 
						 | 
					5ba04eb620 | ||
| 
						 | 
					2d32e89b87 | ||
| 
						 | 
					88c13768e3 | ||
| 
						 | 
					29f4430658 | ||
| 
						 | 
					b558a1c9dd | ||
| 
						 | 
					a4ef26749b | ||
| 
						 | 
					6aa3092be0 | ||
| 
						 | 
					abca47f36f | ||
| 
						 | 
					407b5e199e | ||
| 
						 | 
					2ffd430b0b | ||
| 
						 | 
					81736447ee | ||
| 
						 | 
					5ebb468ccf | ||
| 
						 | 
					0ead802333 | ||
| 
						 | 
					54474e5b33 | ||
| 
						 | 
					0411d52420 | ||
| 
						 | 
					cef659b0de | ||
| 
						 | 
					035c3ef8fe | ||
| 
						 | 
					5afdb1e97f | ||
| 
						 | 
					f7c0ec6595 | ||
| 
						 | 
					0a407c5425 | ||
| 
						 | 
					b9e7fdd2b0 | ||
| 
						 | 
					c74105aad7 | ||
| 
						 | 
					5f0892dec4 | ||
| 
						 | 
					302dea4169 | ||
| 
						 | 
					ce13979690 | ||
| 
						 | 
					1cf210fa25 | ||
| 
						 | 
					d1253922c3 | ||
| 
						 | 
					6b00622329 | ||
| 
						 | 
					aeb94e166b | ||
| 
						 | 
					8bb4c65272 | ||
| 
						 | 
					09a4d869f6 | ||
| 
						 | 
					ae4c130a61 | ||
| 
						 | 
					5621d592b4 | ||
| 
						 | 
					4858882816 | ||
| 
						 | 
					b06bdc2da3 | ||
| 
						 | 
					1ea5d90ea3 | ||
| 
						 | 
					f9f335e692 | ||
| 
						 | 
					3ead48f0db | ||
| 
						 | 
					ccba94197d | ||
| 
						 | 
					8adb7dc97c | ||
| 
						 | 
					796b64541f | ||
| 
						 | 
					b8c27bc17d | ||
| 
						 | 
					7dcdf80f49 | ||
| 
						 | 
					57023457ee | ||
| 
						 | 
					1057ac4db7 | ||
| 
						 | 
					69f5674d9e | ||
| 
						 | 
					ebad407586 | ||
| 
						 | 
					71387846dc | ||
| 
						 | 
					97c1c34708 | ||
| 
						 | 
					79abd773a2 | ||
| 
						 | 
					9cd173ef83 | ||
| 
						 | 
					b723728177 | ||
| 
						 | 
					316171491f | ||
| 
						 | 
					91ff502872 | ||
| 
						 | 
					6e414180e0 | ||
| 
						 | 
					7d2ae4e252 | ||
| 
						 | 
					fb4cb07c6f | ||
| 
						 | 
					19f91a7deb | ||
| 
						 | 
					bb044a789c | ||
| 
						 | 
					2153cfc749 | ||
| 
						 | 
					2e8b4fbdc8 | ||
| 
						 | 
					4141100b1c | ||
| 
						 | 
					baa08160bb | ||
| 
						 | 
					35ef4aad60 | ||
| 
						 | 
					ffa5e29dab | ||
| 
						 | 
					28b5c535ec | ||
| 
						 | 
					3807350c61 | ||
| 
						 | 
					9d2467cf62 | ||
| 
						 | 
					d2480d3194 | ||
| 
						 | 
					148eb03d13 | ||
| 
						 | 
					ed8aec62fc | ||
| 
						 | 
					f7b5c6307c | ||
| 
						 | 
					40d110fc3f | ||
| 
						 | 
					70aa5d0f6c | ||
| 
						 | 
					8fcec8e2cb | ||
| 
						 | 
					2d3b48f86f | ||
| 
						 | 
					a15ac06771 | ||
| 
						 | 
					784cc3bc29 | ||
| 
						 | 
					8384bd7fc7 | ||
| 
						 | 
					8a518f0def | ||
| 
						 | 
					99c10bc6de | ||
| 
						 | 
					f30f20db86 | ||
| 
						 | 
					9bf946e196 | ||
| 
						 | 
					bfaad1f28d | ||
| 
						 | 
					c61a3bf431 | ||
| 
						 | 
					1b77996ccd | ||
| 
						 | 
					9e3ecc8372 | ||
| 
						 | 
					18f4e41550 | ||
| 
						 | 
					26cdbaa3df | ||
| 
						 | 
					c76c1337ba | ||
| 
						 | 
					fe3b779016 | ||
| 
						 | 
					682638d4de | ||
| 
						 | 
					c96663daca | ||
| 
						 | 
					b3ed988119 | ||
| 
						 | 
					77695aa55b | ||
| 
						 | 
					c5a45645a6 | ||
| 
						 | 
					0de47e2a4e | ||
| 
						 | 
					90598d2405 | ||
| 
						 | 
					f90e9ba871 | ||
| 
						 | 
					726bdd7be2 | ||
| 
						 | 
					52db40eb41 | ||
| 
						 | 
					3c371a0c59 | ||
| 
						 | 
					c941bc4109 | ||
| 
						 | 
					cc76e5353c | ||
| 
						 | 
					edfd82fd42 | ||
| 
						 | 
					492bad645b | ||
| 
						 | 
					ab4517f611 | ||
| 
						 | 
					11bb46e393 | ||
| 
						 | 
					2bfcfa6dae | ||
| 
						 | 
					71c4714a6e | ||
| 
						 | 
					a4e63c5f86 | ||
| 
						 | 
					c71e7d0132 | ||
| 
						 | 
					daa966975e | ||
| 
						 | 
					2e5757a3f0 | ||
| 
						 | 
					f66024b37c | ||
| 
						 | 
					c16ca7be13 | ||
| 
						 | 
					e13e754bc4 | ||
| 
						 | 
					6ccea59f71 | ||
| 
						 | 
					d7fd23d8a8 | ||
| 
						 | 
					d0ca69bc27 | ||
| 
						 | 
					2a7d6addde | ||
| 
						 | 
					8e4aeec3bd | ||
| 
						 | 
					7f83a6e667 | ||
| 
						 | 
					ae838b13a8 | ||
| 
						 | 
					ce5dc6f100 | ||
| 
						 | 
					b1551d0436 | ||
| 
						 | 
					b4c2433bac | ||
| 
						 | 
					71b28be3c8 | ||
| 
						 | 
					e25d92e1f5 | ||
| 
						 | 
					65cda10884 | ||
| 
						 | 
					625126df68 | ||
| 
						 | 
					e0ee8ca17c | ||
| 
						 | 
					af95e781f5 | ||
| 
						 | 
					1d8227788b | ||
| 
						 | 
					5fdd8440ac | ||
| 
						 | 
					5d8daa6990 | ||
| 
						 | 
					cf802d0d38 | ||
| 
						 | 
					9b230a7d93 | ||
| 
						 | 
					e2f3e7c3a6 | ||
| 
						 | 
					2fd2e5ceb2 | ||
| 
						 | 
					d88358be8e | ||
| 
						 | 
					d80885c7a8 | ||
| 
						 | 
					9cbf437509 | ||
| 
						 | 
					cd7e8e4bdd | ||
| 
						 | 
					ed024a0aa5 | ||
| 
						 | 
					c7c9c49f4e | ||
| 
						 | 
					75d0514d05 | ||
| 
						 | 
					5b02715c7b | ||
| 
						 | 
					2c160a8a25 | ||
| 
						 | 
					3b0eea69ce | ||
| 
						 | 
					cc576cf1a9 | ||
| 
						 | 
					97a71482a9 | ||
| 
						 | 
					8822b6c808 | ||
| 
						 | 
					5099595aee | ||
| 
						 | 
					39a650ee54 | ||
| 
						 | 
					b19c7d462b | ||
| 
						 | 
					a8b821c213 | ||
| 
						 | 
					535003014b | ||
| 
						 | 
					3c05ae4e1a | ||
| 
						 | 
					c835b67bac | ||
| 
						 | 
					8c32941428 | ||
| 
						 | 
					b5dac00dcb | ||
| 
						 | 
					ffdc721c79 | ||
| 
						 | 
					0828a9fc11 | ||
| 
						 | 
					4b664b6f09 | ||
| 
						 | 
					679633245d | ||
| 
						 | 
					ce8a77c765 | ||
| 
						 | 
					2f78c4acfa | ||
| 
						 | 
					72f6841aac | ||
| 
						 | 
					10bd9b14fc | ||
| 
						 | 
					3498aade85 | ||
| 
						 | 
					c4539e10fb | ||
| 
						 | 
					2b3052e9d7 | ||
| 
						 | 
					e725e15f7a | ||
| 
						 | 
					4d1113e265 | ||
| 
						 | 
					52352ac27a | ||
| 
						 | 
					f60b2b754d | ||
| 
						 | 
					4a3f9712b2 | 
@@ -40,6 +40,7 @@
 | 
				
			|||||||
        "yaml.customTags": [
 | 
					        "yaml.customTags": [
 | 
				
			||||||
          "!secret scalar",
 | 
					          "!secret scalar",
 | 
				
			||||||
          "!lambda scalar",
 | 
					          "!lambda scalar",
 | 
				
			||||||
 | 
					          "!extend scalar",
 | 
				
			||||||
          "!include_dir_named scalar",
 | 
					          "!include_dir_named scalar",
 | 
				
			||||||
          "!include_dir_list scalar",
 | 
					          "!include_dir_list scalar",
 | 
				
			||||||
          "!include_dir_merge_list scalar",
 | 
					          "!include_dir_merge_list scalar",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +1,3 @@
 | 
				
			|||||||
# Normalize line endings to LF in the repository
 | 
					# Normalize line endings to LF in the repository
 | 
				
			||||||
* text eol=lf
 | 
					* text eol=lf
 | 
				
			||||||
 | 
					*.png binary
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										398
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										398
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -12,60 +12,266 @@ on:
 | 
				
			|||||||
permissions:
 | 
					permissions:
 | 
				
			||||||
  contents: read
 | 
					  contents: read
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					env:
 | 
				
			||||||
 | 
					  DEFAULT_PYTHON: "3.9"
 | 
				
			||||||
 | 
					  PYUPGRADE_TARGET: "--py39-plus"
 | 
				
			||||||
 | 
					  CLANG_FORMAT_VERSION: "13.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
concurrency:
 | 
					concurrency:
 | 
				
			||||||
  # yamllint disable-line rule:line-length
 | 
					  # yamllint disable-line rule:line-length
 | 
				
			||||||
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
 | 
					  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
 | 
				
			||||||
  cancel-in-progress: true
 | 
					  cancel-in-progress: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  ci:
 | 
					  common:
 | 
				
			||||||
    name: ${{ matrix.name }}
 | 
					    name: Create common environment
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
 | 
					        uses: actions/setup-python@v4.6.0
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
 | 
					        id: cache-venv
 | 
				
			||||||
 | 
					        uses: actions/cache@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: venv
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
 | 
					      - name: Create Python virtual environment
 | 
				
			||||||
 | 
					        if: steps.cache-venv.outputs.cache-hit != 'true'
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          python -m venv venv
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          python --version
 | 
				
			||||||
 | 
					          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
				
			||||||
 | 
					          pip install -e .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  yamllint:
 | 
				
			||||||
 | 
					    name: yamllint
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Run yamllint
 | 
				
			||||||
 | 
					        uses: frenck/action-yamllint@v1.4.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  black:
 | 
				
			||||||
 | 
					    name: Check black
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
 | 
					        uses: actions/cache/restore@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: venv
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
 | 
					      - name: Run black
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          black --verbose esphome tests
 | 
				
			||||||
 | 
					      - name: Suggested changes
 | 
				
			||||||
 | 
					        run: script/ci-suggest-changes
 | 
				
			||||||
 | 
					        if: always()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  flake8:
 | 
				
			||||||
 | 
					    name: Check flake8
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
 | 
					        uses: actions/cache/restore@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: venv
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
 | 
					      - name: Run flake8
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          flake8 esphome
 | 
				
			||||||
 | 
					      - name: Suggested changes
 | 
				
			||||||
 | 
					        run: script/ci-suggest-changes
 | 
				
			||||||
 | 
					        if: always()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pylint:
 | 
				
			||||||
 | 
					    name: Check pylint
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
 | 
					        uses: actions/cache/restore@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: venv
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
 | 
					      - name: Run pylint
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          pylint -f parseable --persistent=n esphome
 | 
				
			||||||
 | 
					      - name: Suggested changes
 | 
				
			||||||
 | 
					        run: script/ci-suggest-changes
 | 
				
			||||||
 | 
					        if: always()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pyupgrade:
 | 
				
			||||||
 | 
					    name: Check pyupgrade
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
 | 
					        uses: actions/cache/restore@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: venv
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
 | 
					      - name: Run pyupgrade
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          pyupgrade ${{ env.PYUPGRADE_TARGET }} `find esphome -name "*.py" -type f`
 | 
				
			||||||
 | 
					      - name: Suggested changes
 | 
				
			||||||
 | 
					        run: script/ci-suggest-changes
 | 
				
			||||||
 | 
					        if: always()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ci-custom:
 | 
				
			||||||
 | 
					    name: Run script/ci-custom
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
 | 
					        uses: actions/cache/restore@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: venv
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
 | 
					      - name: Register matcher
 | 
				
			||||||
 | 
					        run: echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
				
			||||||
 | 
					      - name: Run script/ci-custom
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          script/ci-custom.py
 | 
				
			||||||
 | 
					          script/build_codeowners.py --check
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pytest:
 | 
				
			||||||
 | 
					    name: Run pytest
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
 | 
					        uses: actions/cache/restore@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: venv
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
 | 
					      - name: Register matcher
 | 
				
			||||||
 | 
					        run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
 | 
				
			||||||
 | 
					      - name: Run pytest
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          pytest -vv --tb=native tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  clang-format:
 | 
				
			||||||
 | 
					    name: Check clang-format
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
 | 
					        uses: actions/cache/restore@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: venv
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
 | 
					      - name: Install clang-format
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          pip install clang-format==${{ env.CLANG_FORMAT_VERSION }}
 | 
				
			||||||
 | 
					      - name: Run clang-format
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          script/clang-format -i
 | 
				
			||||||
 | 
					          git diff-index --quiet HEAD --
 | 
				
			||||||
 | 
					      - name: Suggested changes
 | 
				
			||||||
 | 
					        run: script/ci-suggest-changes
 | 
				
			||||||
 | 
					        if: always()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  compile-tests:
 | 
				
			||||||
 | 
					    name: Run YAML test ${{ matrix.file }}
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					      - black
 | 
				
			||||||
 | 
					      - ci-custom
 | 
				
			||||||
 | 
					      - clang-format
 | 
				
			||||||
 | 
					      - flake8
 | 
				
			||||||
 | 
					      - pylint
 | 
				
			||||||
 | 
					      - pytest
 | 
				
			||||||
 | 
					      - pyupgrade
 | 
				
			||||||
 | 
					      - yamllint
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      max-parallel: 5
 | 
					      max-parallel: 2
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8]
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
 | 
					        uses: actions/cache/restore@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: venv
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
 | 
					      - name: Cache platformio
 | 
				
			||||||
 | 
					        uses: actions/cache@v3.3.1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: ~/.platformio
 | 
				
			||||||
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 | 
					          key: platformio-test${{ matrix.file }}-${{ hashFiles('platformio.ini') }}
 | 
				
			||||||
 | 
					      - name: Run esphome compile tests/test${{ matrix.file }}.yaml
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
 | 
					          esphome compile tests/test${{ matrix.file }}.yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  clang-tidy:
 | 
				
			||||||
 | 
					    name: ${{ matrix.name }}
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					      - black
 | 
				
			||||||
 | 
					      - ci-custom
 | 
				
			||||||
 | 
					      - clang-format
 | 
				
			||||||
 | 
					      - flake8
 | 
				
			||||||
 | 
					      - pylint
 | 
				
			||||||
 | 
					      - pytest
 | 
				
			||||||
 | 
					      - pyupgrade
 | 
				
			||||||
 | 
					      - yamllint
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					      max-parallel: 2
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        include:
 | 
					        include:
 | 
				
			||||||
          - id: ci-custom
 | 
					 | 
				
			||||||
            name: Run script/ci-custom
 | 
					 | 
				
			||||||
          - id: lint-python
 | 
					 | 
				
			||||||
            name: Run script/lint-python
 | 
					 | 
				
			||||||
          - id: test
 | 
					 | 
				
			||||||
            file: tests/test1.yaml
 | 
					 | 
				
			||||||
            name: Test tests/test1.yaml
 | 
					 | 
				
			||||||
            pio_cache_key: test1
 | 
					 | 
				
			||||||
          - id: test
 | 
					 | 
				
			||||||
            file: tests/test2.yaml
 | 
					 | 
				
			||||||
            name: Test tests/test2.yaml
 | 
					 | 
				
			||||||
            pio_cache_key: test2
 | 
					 | 
				
			||||||
          - id: test
 | 
					 | 
				
			||||||
            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
 | 
					 | 
				
			||||||
            pio_cache_key: test4
 | 
					 | 
				
			||||||
          - id: test
 | 
					 | 
				
			||||||
            file: tests/test5.yaml
 | 
					 | 
				
			||||||
            name: Test tests/test5.yaml
 | 
					 | 
				
			||||||
            pio_cache_key: test5
 | 
					 | 
				
			||||||
          - id: test
 | 
					 | 
				
			||||||
            file: tests/test6.yaml
 | 
					 | 
				
			||||||
            name: Test tests/test6.yaml
 | 
					 | 
				
			||||||
            pio_cache_key: test6
 | 
					 | 
				
			||||||
          - id: test
 | 
					 | 
				
			||||||
            file: tests/test7.yaml
 | 
					 | 
				
			||||||
            name: Test tests/test7.yaml
 | 
					 | 
				
			||||||
            pio_cache_key: test7
 | 
					 | 
				
			||||||
          - id: pytest
 | 
					 | 
				
			||||||
            name: Run pytest
 | 
					 | 
				
			||||||
          - id: clang-format
 | 
					 | 
				
			||||||
            name: Run script/clang-format
 | 
					 | 
				
			||||||
          - id: clang-tidy
 | 
					          - id: clang-tidy
 | 
				
			||||||
            name: Run script/clang-tidy for ESP8266
 | 
					            name: Run script/clang-tidy for ESP8266
 | 
				
			||||||
            options: --environment esp8266-arduino-tidy --grep USE_ESP8266
 | 
					            options: --environment esp8266-arduino-tidy --grep USE_ESP8266
 | 
				
			||||||
@@ -90,119 +296,65 @@ jobs:
 | 
				
			|||||||
            name: Run script/clang-tidy for ESP32 IDF
 | 
					            name: Run script/clang-tidy for ESP32 IDF
 | 
				
			||||||
            options: --environment esp32-idf-tidy --grep USE_ESP_IDF
 | 
					            options: --environment esp32-idf-tidy --grep USE_ESP_IDF
 | 
				
			||||||
            pio_cache_key: tidyesp32-idf
 | 
					            pio_cache_key: tidyesp32-idf
 | 
				
			||||||
          - id: yamllint
 | 
					 | 
				
			||||||
            name: Run yamllint
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v3
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
      - name: Set up Python
 | 
					        uses: actions/checkout@v3.5.2
 | 
				
			||||||
        uses: actions/setup-python@v4
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
        id: python
 | 
					        uses: actions/cache/restore@v3.3.1
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.9"
 | 
					          path: venv
 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Cache virtualenv
 | 
					 | 
				
			||||||
        uses: actions/cache@v3
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: .venv
 | 
					 | 
				
			||||||
          # yamllint disable-line rule:line-length
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
          key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
 | 
					          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
				
			||||||
          restore-keys: |
 | 
					        # Use per check platformio cache because checks use different parts
 | 
				
			||||||
            venv-${{ steps.python.outputs.python-version }}-
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Set up virtualenv
 | 
					 | 
				
			||||||
        # yamllint disable rule:line-length
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          python -m venv .venv
 | 
					 | 
				
			||||||
          source .venv/bin/activate
 | 
					 | 
				
			||||||
          pip install -U pip
 | 
					 | 
				
			||||||
          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
					 | 
				
			||||||
          pip install -e .
 | 
					 | 
				
			||||||
          echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
 | 
					 | 
				
			||||||
          echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
 | 
					 | 
				
			||||||
        # yamllint enable rule:line-length
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # Use per check platformio cache because checks use different parts
 | 
					 | 
				
			||||||
      - name: Cache platformio
 | 
					      - name: Cache platformio
 | 
				
			||||||
        uses: actions/cache@v3
 | 
					        uses: actions/cache@v3.3.1
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: ~/.platformio
 | 
					          path: ~/.platformio
 | 
				
			||||||
          # yamllint disable-line rule:line-length
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
          key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
 | 
					          key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
 | 
				
			||||||
        if: matrix.id == 'test' || matrix.id == 'clang-tidy'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install clang tools
 | 
					      - name: Install clang-tidy
 | 
				
			||||||
        run: |
 | 
					        run: sudo apt-get install clang-tidy-11
 | 
				
			||||||
          sudo apt-get install \
 | 
					 | 
				
			||||||
              clang-format-13 \
 | 
					 | 
				
			||||||
              clang-tidy-11
 | 
					 | 
				
			||||||
        if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Register problem matchers
 | 
					      - name: Register problem matchers
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/pytest.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
					          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
					          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Lint Custom
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          script/ci-custom.py
 | 
					 | 
				
			||||||
          script/build_codeowners.py --check
 | 
					 | 
				
			||||||
        if: matrix.id == 'ci-custom'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Lint Python
 | 
					 | 
				
			||||||
        run: script/lint-python -a
 | 
					 | 
				
			||||||
        if: matrix.id == 'lint-python'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - run: esphome compile ${{ matrix.file }}
 | 
					 | 
				
			||||||
        if: matrix.id == 'test'
 | 
					 | 
				
			||||||
        env:
 | 
					 | 
				
			||||||
          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
					 | 
				
			||||||
          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Run pytest
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          pytest -vv --tb=native tests
 | 
					 | 
				
			||||||
        if: matrix.id == 'pytest'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # Also run git-diff-index so that the step is marked as failed on
 | 
					 | 
				
			||||||
      # formatting errors, since clang-format doesn't do anything but
 | 
					 | 
				
			||||||
      # change files if -i is passed.
 | 
					 | 
				
			||||||
      - name: Run clang-format
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          script/clang-format -i
 | 
					 | 
				
			||||||
          git diff-index --quiet HEAD --
 | 
					 | 
				
			||||||
        if: matrix.id == 'clang-format'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Run clang-tidy
 | 
					      - name: Run clang-tidy
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
 | 
					          . venv/bin/activate
 | 
				
			||||||
          script/clang-tidy --all-headers --fix ${{ matrix.options }}
 | 
					          script/clang-tidy --all-headers --fix ${{ matrix.options }}
 | 
				
			||||||
        if: matrix.id == 'clang-tidy'
 | 
					 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
					          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
				
			||||||
          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
					          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run yamllint
 | 
					 | 
				
			||||||
        if: matrix.id == 'yamllint'
 | 
					 | 
				
			||||||
        uses: frenck/action-yamllint@v1.4.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Suggested changes
 | 
					      - name: Suggested changes
 | 
				
			||||||
        run: script/ci-suggest-changes
 | 
					        run: script/ci-suggest-changes
 | 
				
			||||||
        # yamllint disable-line rule:line-length
 | 
					        # yamllint disable-line rule:line-length
 | 
				
			||||||
        if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python')
 | 
					        if: always()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ci-status:
 | 
					  ci-status:
 | 
				
			||||||
    name: CI Status
 | 
					    name: CI Status
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs: [ci]
 | 
					    needs:
 | 
				
			||||||
 | 
					      - common
 | 
				
			||||||
 | 
					      - black
 | 
				
			||||||
 | 
					      - ci-custom
 | 
				
			||||||
 | 
					      - clang-format
 | 
				
			||||||
 | 
					      - flake8
 | 
				
			||||||
 | 
					      - pylint
 | 
				
			||||||
 | 
					      - pytest
 | 
				
			||||||
 | 
					      - pyupgrade
 | 
				
			||||||
 | 
					      - yamllint
 | 
				
			||||||
 | 
					      - compile-tests
 | 
				
			||||||
 | 
					      - clang-tidy
 | 
				
			||||||
    if: always()
 | 
					    if: always()
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Successful deploy
 | 
					      - name: Success
 | 
				
			||||||
        if: ${{ !(contains(needs.*.result, 'failure')) }}
 | 
					        if: ${{ !(contains(needs.*.result, 'failure')) }}
 | 
				
			||||||
        run: exit 0
 | 
					        run: exit 0
 | 
				
			||||||
      - name: Failing deploy
 | 
					      - name: Failure
 | 
				
			||||||
        if: ${{ contains(needs.*.result, 'failure') }}
 | 
					        if: ${{ contains(needs.*.result, 'failure') }}
 | 
				
			||||||
        run: exit 1
 | 
					        run: exit 1
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -49,9 +49,11 @@ jobs:
 | 
				
			|||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.x"
 | 
					          python-version: "3.x"
 | 
				
			||||||
      - name: Set up python environment
 | 
					      - name: Set up python environment
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          ESPHOME_NO_VENV: 1
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          script/setup
 | 
					          script/setup
 | 
				
			||||||
          pip install setuptools wheel twine
 | 
					          pip install twine
 | 
				
			||||||
      - name: Build
 | 
					      - name: Build
 | 
				
			||||||
        run: python setup.py sdist bdist_wheel
 | 
					        run: python setup.py sdist bdist_wheel
 | 
				
			||||||
      - name: Upload
 | 
					      - name: Upload
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -26,7 +26,7 @@ jobs:
 | 
				
			|||||||
          days-before-issue-close: -1
 | 
					          days-before-issue-close: -1
 | 
				
			||||||
          remove-stale-when-updated: true
 | 
					          remove-stale-when-updated: true
 | 
				
			||||||
          stale-pr-label: "stale"
 | 
					          stale-pr-label: "stale"
 | 
				
			||||||
          exempt-pr-labels: "no-stale"
 | 
					          exempt-pr-labels: "not-stale"
 | 
				
			||||||
          stale-pr-message: >
 | 
					          stale-pr-message: >
 | 
				
			||||||
            There hasn't been any activity on this pull request recently. This
 | 
					            There hasn't been any activity on this pull request recently. This
 | 
				
			||||||
            pull request has been automatically marked as stale because of that
 | 
					            pull request has been automatically marked as stale because of that
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -53,8 +53,8 @@ jobs:
 | 
				
			|||||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
					          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
				
			||||||
          committer: esphomebot <esphome@nabucasa.com>
 | 
					          committer: esphomebot <esphome@nabucasa.com>
 | 
				
			||||||
          author: esphomebot <esphome@nabucasa.com>
 | 
					          author: esphomebot <esphome@nabucasa.com>
 | 
				
			||||||
          branch: sync/device-classes/
 | 
					          branch: sync/device-classes
 | 
				
			||||||
          branch-suffix: timestamp
 | 
					 | 
				
			||||||
          delete-branch: true
 | 
					          delete-branch: true
 | 
				
			||||||
          title: "Synchronise Device Classes from Home Assistant"
 | 
					          title: "Synchronise Device Classes from Home Assistant"
 | 
				
			||||||
          body: ${{ steps.pr-template-body.outputs.body }}
 | 
					          body: ${{ steps.pr-template-body.outputs.body }}
 | 
				
			||||||
 | 
					          token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ repos:
 | 
				
			|||||||
          - --branch=release
 | 
					          - --branch=release
 | 
				
			||||||
          - --branch=beta
 | 
					          - --branch=beta
 | 
				
			||||||
  - repo: https://github.com/asottile/pyupgrade
 | 
					  - repo: https://github.com/asottile/pyupgrade
 | 
				
			||||||
    rev: v3.3.2
 | 
					    rev: v3.4.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: pyupgrade
 | 
					      - id: pyupgrade
 | 
				
			||||||
        args: [--py39-plus]
 | 
					        args: [--py39-plus]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -36,6 +36,24 @@
 | 
				
			|||||||
          ]
 | 
					          ]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "label": "Generate proto files",
 | 
				
			||||||
 | 
					      "type": "shell",
 | 
				
			||||||
 | 
					      "command": "${command:python.interpreterPath}",
 | 
				
			||||||
 | 
					      "args": [
 | 
				
			||||||
 | 
					        "./script/api_protobuf/api_protobuf.py"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "group": {
 | 
				
			||||||
 | 
					        "kind": "build",
 | 
				
			||||||
 | 
					        "isDefault": true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "presentation": {
 | 
				
			||||||
 | 
					        "reveal": "never",
 | 
				
			||||||
 | 
					        "close": true,
 | 
				
			||||||
 | 
					        "panel": "new"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "problemMatcher": []
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -19,6 +19,7 @@ esphome/components/addressable_light/* @justfalter
 | 
				
			|||||||
esphome/components/airthings_ble/* @jeromelaban
 | 
					esphome/components/airthings_ble/* @jeromelaban
 | 
				
			||||||
esphome/components/airthings_wave_mini/* @ncareau
 | 
					esphome/components/airthings_wave_mini/* @ncareau
 | 
				
			||||||
esphome/components/airthings_wave_plus/* @jeromelaban
 | 
					esphome/components/airthings_wave_plus/* @jeromelaban
 | 
				
			||||||
 | 
					esphome/components/alarm_control_panel/* @grahambrown11
 | 
				
			||||||
esphome/components/am43/* @buxtronix
 | 
					esphome/components/am43/* @buxtronix
 | 
				
			||||||
esphome/components/am43/cover/* @buxtronix
 | 
					esphome/components/am43/cover/* @buxtronix
 | 
				
			||||||
esphome/components/am43/sensor/* @buxtronix
 | 
					esphome/components/am43/sensor/* @buxtronix
 | 
				
			||||||
@@ -84,6 +85,7 @@ esphome/components/esp32_ble_server/* @jesserockz
 | 
				
			|||||||
esphome/components/esp32_camera_web_server/* @ayufan
 | 
					esphome/components/esp32_camera_web_server/* @ayufan
 | 
				
			||||||
esphome/components/esp32_can/* @Sympatron
 | 
					esphome/components/esp32_can/* @Sympatron
 | 
				
			||||||
esphome/components/esp32_improv/* @jesserockz
 | 
					esphome/components/esp32_improv/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/esp32_rmt_led_strip/* @jesserockz
 | 
				
			||||||
esphome/components/esp8266/* @esphome/core
 | 
					esphome/components/esp8266/* @esphome/core
 | 
				
			||||||
esphome/components/ethernet_info/* @gtjadsonsantos
 | 
					esphome/components/ethernet_info/* @gtjadsonsantos
 | 
				
			||||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
					esphome/components/exposure_notifications/* @OttoWinter
 | 
				
			||||||
@@ -95,6 +97,7 @@ esphome/components/feedback/* @ianchi
 | 
				
			|||||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
 | 
					esphome/components/fingerprint_grow/* @OnFreund @loongyh
 | 
				
			||||||
esphome/components/fs3000/* @kahrendt
 | 
					esphome/components/fs3000/* @kahrendt
 | 
				
			||||||
esphome/components/globals/* @esphome/core
 | 
					esphome/components/globals/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/gp8403/* @jesserockz
 | 
				
			||||||
esphome/components/gpio/* @esphome/core
 | 
					esphome/components/gpio/* @esphome/core
 | 
				
			||||||
esphome/components/gps/* @coogle
 | 
					esphome/components/gps/* @coogle
 | 
				
			||||||
esphome/components/graph/* @synco
 | 
					esphome/components/graph/* @synco
 | 
				
			||||||
@@ -105,8 +108,10 @@ esphome/components/hbridge/fan/* @WeekendWarrior
 | 
				
			|||||||
esphome/components/hbridge/light/* @DotNetDann
 | 
					esphome/components/hbridge/light/* @DotNetDann
 | 
				
			||||||
esphome/components/heatpumpir/* @rob-deutsch
 | 
					esphome/components/heatpumpir/* @rob-deutsch
 | 
				
			||||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
					esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
				
			||||||
 | 
					esphome/components/hm3301/* @freekode
 | 
				
			||||||
esphome/components/homeassistant/* @OttoWinter
 | 
					esphome/components/homeassistant/* @OttoWinter
 | 
				
			||||||
esphome/components/honeywellabp/* @RubyBailey
 | 
					esphome/components/honeywellabp/* @RubyBailey
 | 
				
			||||||
 | 
					esphome/components/host/* @esphome/core
 | 
				
			||||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
					esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
				
			||||||
esphome/components/hte501/* @Stock-M
 | 
					esphome/components/hte501/* @Stock-M
 | 
				
			||||||
esphome/components/hydreon_rgxx/* @functionpointer
 | 
					esphome/components/hydreon_rgxx/* @functionpointer
 | 
				
			||||||
@@ -115,6 +120,7 @@ esphome/components/i2c/* @esphome/core
 | 
				
			|||||||
esphome/components/i2s_audio/* @jesserockz
 | 
					esphome/components/i2s_audio/* @jesserockz
 | 
				
			||||||
esphome/components/i2s_audio/media_player/* @jesserockz
 | 
					esphome/components/i2s_audio/media_player/* @jesserockz
 | 
				
			||||||
esphome/components/i2s_audio/microphone/* @jesserockz
 | 
					esphome/components/i2s_audio/microphone/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/i2s_audio/speaker/* @jesserockz
 | 
				
			||||||
esphome/components/ili9xxx/* @nielsnl68
 | 
					esphome/components/ili9xxx/* @nielsnl68
 | 
				
			||||||
esphome/components/improv_base/* @esphome/core
 | 
					esphome/components/improv_base/* @esphome/core
 | 
				
			||||||
esphome/components/improv_serial/* @esphome/core
 | 
					esphome/components/improv_serial/* @esphome/core
 | 
				
			||||||
@@ -216,6 +222,7 @@ esphome/components/restart/* @esphome/core
 | 
				
			|||||||
esphome/components/rf_bridge/* @jesserockz
 | 
					esphome/components/rf_bridge/* @jesserockz
 | 
				
			||||||
esphome/components/rgbct/* @jesserockz
 | 
					esphome/components/rgbct/* @jesserockz
 | 
				
			||||||
esphome/components/rp2040/* @jesserockz
 | 
					esphome/components/rp2040/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/rp2040_pio_led_strip/* @Papa-DMan
 | 
				
			||||||
esphome/components/rp2040_pwm/* @jesserockz
 | 
					esphome/components/rp2040_pwm/* @jesserockz
 | 
				
			||||||
esphome/components/rtttl/* @glmnet
 | 
					esphome/components/rtttl/* @glmnet
 | 
				
			||||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
 | 
					esphome/components/safe_mode/* @jsuanet @paulmonigatti
 | 
				
			||||||
@@ -237,7 +244,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet
 | 
				
			|||||||
esphome/components/sigma_delta_output/* @Cat-Ion
 | 
					esphome/components/sigma_delta_output/* @Cat-Ion
 | 
				
			||||||
esphome/components/sim800l/* @glmnet
 | 
					esphome/components/sim800l/* @glmnet
 | 
				
			||||||
esphome/components/sm10bit_base/* @Cossid
 | 
					esphome/components/sm10bit_base/* @Cossid
 | 
				
			||||||
esphome/components/sm2135/* @BoukeHaarsma23
 | 
					esphome/components/sm2135/* @BoukeHaarsma23 @dd32 @matika77
 | 
				
			||||||
esphome/components/sm2235/* @Cossid
 | 
					esphome/components/sm2235/* @Cossid
 | 
				
			||||||
esphome/components/sm2335/* @Cossid
 | 
					esphome/components/sm2335/* @Cossid
 | 
				
			||||||
esphome/components/sml/* @alengwenus
 | 
					esphome/components/sml/* @alengwenus
 | 
				
			||||||
@@ -245,6 +252,7 @@ esphome/components/smt100/* @piechade
 | 
				
			|||||||
esphome/components/sn74hc165/* @jesserockz
 | 
					esphome/components/sn74hc165/* @jesserockz
 | 
				
			||||||
esphome/components/socket/* @esphome/core
 | 
					esphome/components/socket/* @esphome/core
 | 
				
			||||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
					esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
				
			||||||
 | 
					esphome/components/speaker/* @jesserockz
 | 
				
			||||||
esphome/components/spi/* @esphome/core
 | 
					esphome/components/spi/* @esphome/core
 | 
				
			||||||
esphome/components/sprinkler/* @kbx81
 | 
					esphome/components/sprinkler/* @kbx81
 | 
				
			||||||
esphome/components/sps30/* @martgras
 | 
					esphome/components/sps30/* @martgras
 | 
				
			||||||
@@ -270,13 +278,16 @@ esphome/components/tca9548a/* @andreashergert1984
 | 
				
			|||||||
esphome/components/tcl112/* @glmnet
 | 
					esphome/components/tcl112/* @glmnet
 | 
				
			||||||
esphome/components/tee501/* @Stock-M
 | 
					esphome/components/tee501/* @Stock-M
 | 
				
			||||||
esphome/components/teleinfo/* @0hax
 | 
					esphome/components/teleinfo/* @0hax
 | 
				
			||||||
 | 
					esphome/components/template/alarm_control_panel/* @grahambrown11
 | 
				
			||||||
esphome/components/thermostat/* @kbx81
 | 
					esphome/components/thermostat/* @kbx81
 | 
				
			||||||
esphome/components/time/* @OttoWinter
 | 
					esphome/components/time/* @OttoWinter
 | 
				
			||||||
esphome/components/tlc5947/* @rnauber
 | 
					esphome/components/tlc5947/* @rnauber
 | 
				
			||||||
esphome/components/tm1621/* @Philippe12
 | 
					esphome/components/tm1621/* @Philippe12
 | 
				
			||||||
esphome/components/tm1637/* @glmnet
 | 
					esphome/components/tm1637/* @glmnet
 | 
				
			||||||
esphome/components/tm1638/* @skykingjwc
 | 
					esphome/components/tm1638/* @skykingjwc
 | 
				
			||||||
 | 
					esphome/components/tm1651/* @freekode
 | 
				
			||||||
esphome/components/tmp102/* @timsavage
 | 
					esphome/components/tmp102/* @timsavage
 | 
				
			||||||
 | 
					esphome/components/tmp1075/* @sybrenstuvel
 | 
				
			||||||
esphome/components/tmp117/* @Azimath
 | 
					esphome/components/tmp117/* @Azimath
 | 
				
			||||||
esphome/components/tof10120/* @wstrzalka
 | 
					esphome/components/tof10120/* @wstrzalka
 | 
				
			||||||
esphome/components/toshiba/* @kbx81
 | 
					esphome/components/toshiba/* @kbx81
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,6 +29,8 @@ RUN \
 | 
				
			|||||||
        git=1:2.30.2-1+deb11u2 \
 | 
					        git=1:2.30.2-1+deb11u2 \
 | 
				
			||||||
        curl=7.74.0-1.3+deb11u7 \
 | 
					        curl=7.74.0-1.3+deb11u7 \
 | 
				
			||||||
        openssh-client=1:8.4p1-5+deb11u1 \
 | 
					        openssh-client=1:8.4p1-5+deb11u1 \
 | 
				
			||||||
 | 
					        libcairo2=1.16.0-5 \
 | 
				
			||||||
 | 
					        python3-cffi=1.14.5-1 \
 | 
				
			||||||
    && rm -rf \
 | 
					    && rm -rf \
 | 
				
			||||||
        /tmp/* \
 | 
					        /tmp/* \
 | 
				
			||||||
        /var/{cache,log}/* \
 | 
					        /var/{cache,log}/* \
 | 
				
			||||||
@@ -52,7 +54,7 @@ RUN \
 | 
				
			|||||||
    # Ubuntu python3-pip is missing wheel
 | 
					    # Ubuntu python3-pip is missing wheel
 | 
				
			||||||
    pip3 install --no-cache-dir \
 | 
					    pip3 install --no-cache-dir \
 | 
				
			||||||
        wheel==0.37.1 \
 | 
					        wheel==0.37.1 \
 | 
				
			||||||
        platformio==6.1.6 \
 | 
					        platformio==6.1.7 \
 | 
				
			||||||
    # Change some platformio settings
 | 
					    # Change some platformio settings
 | 
				
			||||||
    && platformio settings set enable_telemetry No \
 | 
					    && platformio settings set enable_telemetry No \
 | 
				
			||||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
					    && platformio settings set check_platformio_interval 1000000 \
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,9 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_LOGGER,
 | 
					    CONF_LOGGER,
 | 
				
			||||||
    CONF_NAME,
 | 
					    CONF_NAME,
 | 
				
			||||||
    CONF_OTA,
 | 
					    CONF_OTA,
 | 
				
			||||||
 | 
					    CONF_MQTT,
 | 
				
			||||||
 | 
					    CONF_MDNS,
 | 
				
			||||||
 | 
					    CONF_DISABLED,
 | 
				
			||||||
    CONF_PASSWORD,
 | 
					    CONF_PASSWORD,
 | 
				
			||||||
    CONF_PORT,
 | 
					    CONF_PORT,
 | 
				
			||||||
    CONF_ESPHOME,
 | 
					    CONF_ESPHOME,
 | 
				
			||||||
@@ -42,7 +45,7 @@ from esphome.log import color, setup_log, Fore
 | 
				
			|||||||
_LOGGER = logging.getLogger(__name__)
 | 
					_LOGGER = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def choose_prompt(options):
 | 
					def choose_prompt(options, purpose: str = None):
 | 
				
			||||||
    if not options:
 | 
					    if not options:
 | 
				
			||||||
        raise EsphomeError(
 | 
					        raise EsphomeError(
 | 
				
			||||||
            "Found no valid options for upload/logging, please make sure relevant "
 | 
					            "Found no valid options for upload/logging, please make sure relevant "
 | 
				
			||||||
@@ -53,7 +56,9 @@ def choose_prompt(options):
 | 
				
			|||||||
    if len(options) == 1:
 | 
					    if len(options) == 1:
 | 
				
			||||||
        return options[0][1]
 | 
					        return options[0][1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    safe_print("Found multiple options, please choose one:")
 | 
					    safe_print(
 | 
				
			||||||
 | 
					        f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    for i, (desc, _) in enumerate(options):
 | 
					    for i, (desc, _) in enumerate(options):
 | 
				
			||||||
        safe_print(f"  [{i+1}] {desc}")
 | 
					        safe_print(f"  [{i+1}] {desc}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,7 +77,9 @@ def choose_prompt(options):
 | 
				
			|||||||
    return options[opt - 1][1]
 | 
					    return options[opt - 1][1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
 | 
					def choose_upload_log_host(
 | 
				
			||||||
 | 
					    default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
    options = []
 | 
					    options = []
 | 
				
			||||||
    for port in get_serial_ports():
 | 
					    for port in get_serial_ports():
 | 
				
			||||||
        options.append((f"{port.path} ({port.description})", port.path))
 | 
					        options.append((f"{port.path} ({port.description})", port.path))
 | 
				
			||||||
@@ -80,7 +87,7 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
 | 
				
			|||||||
        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
					        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
				
			||||||
        if default == "OTA":
 | 
					        if default == "OTA":
 | 
				
			||||||
            return CORE.address
 | 
					            return CORE.address
 | 
				
			||||||
    if show_mqtt and "mqtt" in CORE.config:
 | 
					    if show_mqtt and CONF_MQTT in CORE.config:
 | 
				
			||||||
        options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
 | 
					        options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
 | 
				
			||||||
        if default == "OTA":
 | 
					        if default == "OTA":
 | 
				
			||||||
            return "MQTT"
 | 
					            return "MQTT"
 | 
				
			||||||
@@ -88,7 +95,7 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
 | 
				
			|||||||
        return default
 | 
					        return default
 | 
				
			||||||
    if check_default is not None and check_default in [opt[1] for opt in options]:
 | 
					    if check_default is not None and check_default in [opt[1] for opt in options]:
 | 
				
			||||||
        return check_default
 | 
					        return check_default
 | 
				
			||||||
    return choose_prompt(options)
 | 
					    return choose_prompt(options, purpose=purpose)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_port_type(port):
 | 
					def get_port_type(port):
 | 
				
			||||||
@@ -288,19 +295,30 @@ def upload_program(config, args, host):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return 1  # Unknown target platform
 | 
					        return 1  # Unknown target platform
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from esphome import espota2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if CONF_OTA not in config:
 | 
					    if CONF_OTA not in config:
 | 
				
			||||||
        raise EsphomeError(
 | 
					        raise EsphomeError(
 | 
				
			||||||
            "Cannot upload Over the Air as the config does not include the ota: "
 | 
					            "Cannot upload Over the Air as the config does not include the ota: "
 | 
				
			||||||
            "component"
 | 
					            "component"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    from esphome import espota2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ota_conf = config[CONF_OTA]
 | 
					    ota_conf = config[CONF_OTA]
 | 
				
			||||||
    remote_port = ota_conf[CONF_PORT]
 | 
					    remote_port = ota_conf[CONF_PORT]
 | 
				
			||||||
    password = ota_conf.get(CONF_PASSWORD, "")
 | 
					    password = ota_conf.get(CONF_PASSWORD, "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED]
 | 
				
			||||||
 | 
					    ) and CONF_MQTT in config:
 | 
				
			||||||
 | 
					        from esphome import mqtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        host = mqtt.get_esphome_device_ip(
 | 
				
			||||||
 | 
					            config, args.username, args.password, args.client_id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if getattr(args, "file", None) is not None:
 | 
					    if getattr(args, "file", None) is not None:
 | 
				
			||||||
        return espota2.run_ota(host, remote_port, password, args.file)
 | 
					        return espota2.run_ota(host, remote_port, password, args.file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
 | 
					    return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -310,6 +328,13 @@ def show_logs(config, args, port):
 | 
				
			|||||||
    if get_port_type(port) == "SERIAL":
 | 
					    if get_port_type(port) == "SERIAL":
 | 
				
			||||||
        return run_miniterm(config, port)
 | 
					        return run_miniterm(config, port)
 | 
				
			||||||
    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
					    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
				
			||||||
 | 
					        if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
 | 
				
			||||||
 | 
					            from esphome import mqtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            port = mqtt.get_esphome_device_ip(
 | 
				
			||||||
 | 
					                config, args.username, args.password, args.client_id
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        from esphome.components.api.client import run_logs
 | 
					        from esphome.components.api.client import run_logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return run_logs(config, port)
 | 
					        return run_logs(config, port)
 | 
				
			||||||
@@ -374,6 +399,7 @@ def command_upload(args, config):
 | 
				
			|||||||
        show_ota=True,
 | 
					        show_ota=True,
 | 
				
			||||||
        show_mqtt=False,
 | 
					        show_mqtt=False,
 | 
				
			||||||
        show_api=False,
 | 
					        show_api=False,
 | 
				
			||||||
 | 
					        purpose="uploading",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    exit_code = upload_program(config, args, port)
 | 
					    exit_code = upload_program(config, args, port)
 | 
				
			||||||
    if exit_code != 0:
 | 
					    if exit_code != 0:
 | 
				
			||||||
@@ -382,6 +408,15 @@ def command_upload(args, config):
 | 
				
			|||||||
    return 0
 | 
					    return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def command_discover(args, config):
 | 
				
			||||||
 | 
					    if "mqtt" in config:
 | 
				
			||||||
 | 
					        from esphome import mqtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return mqtt.show_discover(config, args.username, args.password, args.client_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    raise EsphomeError("No discover method configured (mqtt)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def command_logs(args, config):
 | 
					def command_logs(args, config):
 | 
				
			||||||
    port = choose_upload_log_host(
 | 
					    port = choose_upload_log_host(
 | 
				
			||||||
        default=args.device,
 | 
					        default=args.device,
 | 
				
			||||||
@@ -389,6 +424,7 @@ def command_logs(args, config):
 | 
				
			|||||||
        show_ota=False,
 | 
					        show_ota=False,
 | 
				
			||||||
        show_mqtt=True,
 | 
					        show_mqtt=True,
 | 
				
			||||||
        show_api=True,
 | 
					        show_api=True,
 | 
				
			||||||
 | 
					        purpose="logging",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    return show_logs(config, args, port)
 | 
					    return show_logs(config, args, port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -407,6 +443,7 @@ def command_run(args, config):
 | 
				
			|||||||
        show_ota=True,
 | 
					        show_ota=True,
 | 
				
			||||||
        show_mqtt=False,
 | 
					        show_mqtt=False,
 | 
				
			||||||
        show_api=True,
 | 
					        show_api=True,
 | 
				
			||||||
 | 
					        purpose="uploading",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    exit_code = upload_program(config, args, port)
 | 
					    exit_code = upload_program(config, args, port)
 | 
				
			||||||
    if exit_code != 0:
 | 
					    if exit_code != 0:
 | 
				
			||||||
@@ -420,6 +457,7 @@ def command_run(args, config):
 | 
				
			|||||||
        show_ota=False,
 | 
					        show_ota=False,
 | 
				
			||||||
        show_mqtt=True,
 | 
					        show_mqtt=True,
 | 
				
			||||||
        show_api=True,
 | 
					        show_api=True,
 | 
				
			||||||
 | 
					        purpose="logging",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    return show_logs(config, args, port)
 | 
					    return show_logs(config, args, port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -623,6 +661,7 @@ POST_CONFIG_ACTIONS = {
 | 
				
			|||||||
    "clean": command_clean,
 | 
					    "clean": command_clean,
 | 
				
			||||||
    "idedata": command_idedata,
 | 
					    "idedata": command_idedata,
 | 
				
			||||||
    "rename": command_rename,
 | 
					    "rename": command_rename,
 | 
				
			||||||
 | 
					    "discover": command_discover,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -711,6 +750,15 @@ def parse_args(argv):
 | 
				
			|||||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
					        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser_discover = subparsers.add_parser(
 | 
				
			||||||
 | 
					        "discover",
 | 
				
			||||||
 | 
					        help="Validate the configuration and show all discovered devices.",
 | 
				
			||||||
 | 
					        parents=[mqtt_options],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser_discover.add_argument(
 | 
				
			||||||
 | 
					        "configuration", help="Your YAML configuration file.", nargs=1
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser_run = subparsers.add_parser(
 | 
					    parser_run = subparsers.add_parser(
 | 
				
			||||||
        "run",
 | 
					        "run",
 | 
				
			||||||
        help="Validate the configuration, create a binary, upload it, and start logs.",
 | 
					        help="Validate the configuration, create a binary, upload it, and start logs.",
 | 
				
			||||||
@@ -932,6 +980,8 @@ def run_esphome(argv):
 | 
				
			|||||||
            _LOGGER.error(e, exc_info=args.verbose)
 | 
					            _LOGGER.error(e, exc_info=args.verbose)
 | 
				
			||||||
            return 1
 | 
					            return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _LOGGER.info("ESPHome %s", const.__version__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for conf_path in args.configuration:
 | 
					    for conf_path in args.configuration:
 | 
				
			||||||
        if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
 | 
					        if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
 | 
				
			||||||
            _LOGGER.warning("Skipping secrets file %s", conf_path)
 | 
					            _LOGGER.warning("Skipping secrets file %s", conf_path)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1,118 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome import pins
 | 
				
			||||||
 | 
					from esphome.const import CONF_INPUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from esphome.core import CORE
 | 
				
			||||||
 | 
					from esphome.components.esp32 import get_esp32_variant
 | 
				
			||||||
 | 
					from esphome.components.esp32.const import (
 | 
				
			||||||
 | 
					    VARIANT_ESP32,
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3,
 | 
				
			||||||
 | 
					    VARIANT_ESP32H2,
 | 
				
			||||||
 | 
					    VARIANT_ESP32S2,
 | 
				
			||||||
 | 
					    VARIANT_ESP32S3,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@esphome/core"]
 | 
					CODEOWNERS = ["@esphome/core"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ATTENUATION_MODES = {
 | 
				
			||||||
 | 
					    "0db": cg.global_ns.ADC_ATTEN_DB_0,
 | 
				
			||||||
 | 
					    "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
 | 
				
			||||||
 | 
					    "6db": cg.global_ns.ADC_ATTEN_DB_6,
 | 
				
			||||||
 | 
					    "11db": cg.global_ns.ADC_ATTEN_DB_11,
 | 
				
			||||||
 | 
					    "auto": "auto",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
				
			||||||
 | 
					# pin to adc1 channel mapping
 | 
				
			||||||
 | 
					ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
				
			||||||
 | 
					    VARIANT_ESP32: {
 | 
				
			||||||
 | 
					        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        38: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        39: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        32: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					        33: adc1_channel_t.ADC1_CHANNEL_5,
 | 
				
			||||||
 | 
					        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
				
			||||||
 | 
					        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32S2: {
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
				
			||||||
 | 
					        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
				
			||||||
 | 
					        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
				
			||||||
 | 
					        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
				
			||||||
 | 
					        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32S3: {
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
				
			||||||
 | 
					        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
				
			||||||
 | 
					        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
				
			||||||
 | 
					        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
				
			||||||
 | 
					        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3: {
 | 
				
			||||||
 | 
					        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32H2: {
 | 
				
			||||||
 | 
					        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_adc_pin(value):
 | 
				
			||||||
 | 
					    if str(value).upper() == "VCC":
 | 
				
			||||||
 | 
					        return cv.only_on_esp8266("VCC")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if str(value).upper() == "TEMPERATURE":
 | 
				
			||||||
 | 
					        return cv.only_on_rp2040("TEMPERATURE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CORE.is_esp32:
 | 
				
			||||||
 | 
					        value = pins.internal_gpio_input_pin_number(value)
 | 
				
			||||||
 | 
					        variant = get_esp32_variant()
 | 
				
			||||||
 | 
					        if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
 | 
				
			||||||
 | 
					            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
 | 
				
			||||||
 | 
					            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
 | 
				
			||||||
 | 
					        return pins.internal_gpio_input_pin_schema(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CORE.is_esp8266:
 | 
				
			||||||
 | 
					        from esphome.components.esp8266.gpio import CONF_ANALOG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
 | 
				
			||||||
 | 
					            value
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if value != 17:  # A0
 | 
				
			||||||
 | 
					            raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
 | 
				
			||||||
 | 
					        return pins.gpio_pin_schema(
 | 
				
			||||||
 | 
					            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
 | 
				
			||||||
 | 
					        )(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CORE.is_rp2040:
 | 
				
			||||||
 | 
					        value = pins.internal_gpio_input_pin_number(value)
 | 
				
			||||||
 | 
					        if value not in (26, 27, 28, 29):
 | 
				
			||||||
 | 
					            raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
 | 
				
			||||||
 | 
					        return pins.internal_gpio_input_pin_schema(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    raise NotImplementedError
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,133 +1,27 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome import pins
 | 
					 | 
				
			||||||
from esphome.components import sensor, voltage_sampler
 | 
					from esphome.components import sensor, voltage_sampler
 | 
				
			||||||
 | 
					from esphome.components.esp32 import get_esp32_variant
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ATTENUATION,
 | 
					    CONF_ATTENUATION,
 | 
				
			||||||
    CONF_RAW,
 | 
					 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_INPUT,
 | 
					 | 
				
			||||||
    CONF_NUMBER,
 | 
					    CONF_NUMBER,
 | 
				
			||||||
    CONF_PIN,
 | 
					    CONF_PIN,
 | 
				
			||||||
 | 
					    CONF_RAW,
 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE
 | 
					from esphome.core import CORE
 | 
				
			||||||
from esphome.components.esp32 import get_esp32_variant
 | 
					
 | 
				
			||||||
from esphome.components.esp32.const import (
 | 
					from . import (
 | 
				
			||||||
    VARIANT_ESP32,
 | 
					    ATTENUATION_MODES,
 | 
				
			||||||
    VARIANT_ESP32C3,
 | 
					    ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
 | 
				
			||||||
    VARIANT_ESP32H2,
 | 
					    validate_adc_pin,
 | 
				
			||||||
    VARIANT_ESP32S2,
 | 
					 | 
				
			||||||
    VARIANT_ESP32S3,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
AUTO_LOAD = ["voltage_sampler"]
 | 
					AUTO_LOAD = ["voltage_sampler"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ATTENUATION_MODES = {
 | 
					 | 
				
			||||||
    "0db": cg.global_ns.ADC_ATTEN_DB_0,
 | 
					 | 
				
			||||||
    "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
 | 
					 | 
				
			||||||
    "6db": cg.global_ns.ADC_ATTEN_DB_6,
 | 
					 | 
				
			||||||
    "11db": cg.global_ns.ADC_ATTEN_DB_11,
 | 
					 | 
				
			||||||
    "auto": "auto",
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
					 | 
				
			||||||
# pin to adc1 channel mapping
 | 
					 | 
				
			||||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
					 | 
				
			||||||
    VARIANT_ESP32: {
 | 
					 | 
				
			||||||
        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					 | 
				
			||||||
        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					 | 
				
			||||||
        38: adc1_channel_t.ADC1_CHANNEL_2,
 | 
					 | 
				
			||||||
        39: adc1_channel_t.ADC1_CHANNEL_3,
 | 
					 | 
				
			||||||
        32: adc1_channel_t.ADC1_CHANNEL_4,
 | 
					 | 
				
			||||||
        33: adc1_channel_t.ADC1_CHANNEL_5,
 | 
					 | 
				
			||||||
        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
					 | 
				
			||||||
        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    VARIANT_ESP32S2: {
 | 
					 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					 | 
				
			||||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
					 | 
				
			||||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
					 | 
				
			||||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
					 | 
				
			||||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
					 | 
				
			||||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
					 | 
				
			||||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
					 | 
				
			||||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
					 | 
				
			||||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    VARIANT_ESP32S3: {
 | 
					 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					 | 
				
			||||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
					 | 
				
			||||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
					 | 
				
			||||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
					 | 
				
			||||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
					 | 
				
			||||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
					 | 
				
			||||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
					 | 
				
			||||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
					 | 
				
			||||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    VARIANT_ESP32C3: {
 | 
					 | 
				
			||||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
					 | 
				
			||||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
					 | 
				
			||||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    VARIANT_ESP32H2: {
 | 
					 | 
				
			||||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
					 | 
				
			||||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
					 | 
				
			||||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
					 | 
				
			||||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
					 | 
				
			||||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def validate_adc_pin(value):
 | 
					 | 
				
			||||||
    if str(value).upper() == "VCC":
 | 
					 | 
				
			||||||
        return cv.only_on_esp8266("VCC")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if str(value).upper() == "TEMPERATURE":
 | 
					 | 
				
			||||||
        return cv.only_on_rp2040("TEMPERATURE")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if CORE.is_esp32:
 | 
					 | 
				
			||||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
					 | 
				
			||||||
        variant = get_esp32_variant()
 | 
					 | 
				
			||||||
        if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
 | 
					 | 
				
			||||||
            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
 | 
					 | 
				
			||||||
            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
 | 
					 | 
				
			||||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if CORE.is_esp8266:
 | 
					 | 
				
			||||||
        from esphome.components.esp8266.gpio import CONF_ANALOG
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
 | 
					 | 
				
			||||||
            value
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if value != 17:  # A0
 | 
					 | 
				
			||||||
            raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
 | 
					 | 
				
			||||||
        return pins.gpio_pin_schema(
 | 
					 | 
				
			||||||
            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
 | 
					 | 
				
			||||||
        )(value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if CORE.is_rp2040:
 | 
					 | 
				
			||||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
					 | 
				
			||||||
        if value not in (26, 27, 28, 29):
 | 
					 | 
				
			||||||
            raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
 | 
					 | 
				
			||||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    raise NotImplementedError
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_config(config):
 | 
					def validate_config(config):
 | 
				
			||||||
    if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
 | 
					    if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										165
									
								
								esphome/components/alarm_control_panel/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								esphome/components/alarm_control_panel/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome import automation
 | 
				
			||||||
 | 
					from esphome.automation import maybe_simple_id
 | 
				
			||||||
 | 
					from esphome.core import CORE, coroutine_with_priority
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_ON_STATE,
 | 
				
			||||||
 | 
					    CONF_TRIGGER_ID,
 | 
				
			||||||
 | 
					    CONF_CODE,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from esphome.cpp_helpers import setup_entity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@grahambrown11"]
 | 
				
			||||||
 | 
					IS_PLATFORM_COMPONENT = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_ON_TRIGGERED = "on_triggered"
 | 
				
			||||||
 | 
					CONF_ON_CLEARED = "on_cleared"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
 | 
				
			||||||
 | 
					AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					StateTrigger = alarm_control_panel_ns.class_(
 | 
				
			||||||
 | 
					    "StateTrigger", automation.Trigger.template()
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					TriggeredTrigger = alarm_control_panel_ns.class_(
 | 
				
			||||||
 | 
					    "TriggeredTrigger", automation.Trigger.template()
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					ClearedTrigger = alarm_control_panel_ns.class_(
 | 
				
			||||||
 | 
					    "ClearedTrigger", automation.Trigger.template()
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
 | 
				
			||||||
 | 
					ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
 | 
				
			||||||
 | 
					DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action)
 | 
				
			||||||
 | 
					PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action)
 | 
				
			||||||
 | 
					TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action)
 | 
				
			||||||
 | 
					AlarmControlPanelCondition = alarm_control_panel_ns.class_(
 | 
				
			||||||
 | 
					    "AlarmControlPanelCondition", automation.Condition
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.declare_id(AlarmControlPanel),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.use_id(AlarmControlPanel),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_CODE): cv.templatable(cv.string),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.use_id(AlarmControlPanel),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def setup_alarm_control_panel_core_(var, config):
 | 
				
			||||||
 | 
					    await setup_entity(var, config)
 | 
				
			||||||
 | 
					    for conf in config.get(CONF_ON_STATE, []):
 | 
				
			||||||
 | 
					        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
				
			||||||
 | 
					        await automation.build_automation(trigger, [], conf)
 | 
				
			||||||
 | 
					    for conf in config.get(CONF_ON_TRIGGERED, []):
 | 
				
			||||||
 | 
					        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
				
			||||||
 | 
					        await automation.build_automation(trigger, [], conf)
 | 
				
			||||||
 | 
					    for conf in config.get(CONF_ON_CLEARED, []):
 | 
				
			||||||
 | 
					        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
				
			||||||
 | 
					        await automation.build_automation(trigger, [], conf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def register_alarm_control_panel(var, config):
 | 
				
			||||||
 | 
					    if not CORE.has_id(config[CONF_ID]):
 | 
				
			||||||
 | 
					        var = cg.Pvariable(config[CONF_ID], var)
 | 
				
			||||||
 | 
					    cg.add(cg.App.register_alarm_control_panel(var))
 | 
				
			||||||
 | 
					    await setup_alarm_control_panel_core_(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@automation.register_action(
 | 
				
			||||||
 | 
					    "alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
 | 
					    paren = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
				
			||||||
 | 
					    if CONF_CODE in config:
 | 
				
			||||||
 | 
					        templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
 | 
				
			||||||
 | 
					        cg.add(var.set_code(templatable_))
 | 
				
			||||||
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@automation.register_action(
 | 
				
			||||||
 | 
					    "alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
 | 
					    paren = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
				
			||||||
 | 
					    if CONF_CODE in config:
 | 
				
			||||||
 | 
					        templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
 | 
				
			||||||
 | 
					        cg.add(var.set_code(templatable_))
 | 
				
			||||||
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@automation.register_action(
 | 
				
			||||||
 | 
					    "alarm_control_panel.disarm", DisarmAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def alarm_action_disarm_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
 | 
					    paren = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
				
			||||||
 | 
					    if CONF_CODE in config:
 | 
				
			||||||
 | 
					        templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
 | 
				
			||||||
 | 
					        cg.add(var.set_code(templatable_))
 | 
				
			||||||
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@automation.register_action(
 | 
				
			||||||
 | 
					    "alarm_control_panel.pending", PendingAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def alarm_action_pending_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
 | 
					    paren = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
				
			||||||
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@automation.register_action(
 | 
				
			||||||
 | 
					    "alarm_control_panel.triggered", TriggeredAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def alarm_action_trigger_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
 | 
					    paren = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
				
			||||||
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@automation.register_condition(
 | 
				
			||||||
 | 
					    "alarm_control_panel.is_armed",
 | 
				
			||||||
 | 
					    AlarmControlPanelCondition,
 | 
				
			||||||
 | 
					    ALARM_CONTROL_PANEL_CONDITION_SCHEMA,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def alarm_control_panel_is_armed_to_code(
 | 
				
			||||||
 | 
					    config, condition_id, template_arg, args
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    paren = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
 | 
					    return cg.new_Pvariable(condition_id, template_arg, paren)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@coroutine_with_priority(100.0)
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    cg.add_global(alarm_control_panel_ns.using)
 | 
				
			||||||
 | 
					    cg.add_define("USE_ALARM_CONTROL_PANEL")
 | 
				
			||||||
							
								
								
									
										111
									
								
								esphome/components/alarm_control_panel/alarm_control_panel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								esphome/components/alarm_control_panel/alarm_control_panel.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "alarm_control_panel.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace alarm_control_panel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "alarm_control_panel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall AlarmControlPanel::make_call() { return AlarmControlPanelCall(this); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AlarmControlPanel::is_state_armed(AlarmControlPanelState state) {
 | 
				
			||||||
 | 
					  switch (state) {
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_AWAY:
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_HOME:
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_NIGHT:
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_VACATION:
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_CUSTOM_BYPASS:
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
 | 
				
			||||||
 | 
					  this->last_update_ = millis();
 | 
				
			||||||
 | 
					  if (state != this->current_state_) {
 | 
				
			||||||
 | 
					    auto prev_state = this->current_state_;
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
 | 
				
			||||||
 | 
					             LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
 | 
				
			||||||
 | 
					    this->current_state_ = state;
 | 
				
			||||||
 | 
					    this->state_callback_.call();
 | 
				
			||||||
 | 
					    if (state == ACP_STATE_TRIGGERED) {
 | 
				
			||||||
 | 
					      this->triggered_callback_.call();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (prev_state == ACP_STATE_TRIGGERED) {
 | 
				
			||||||
 | 
					      this->cleared_callback_.call();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (state == this->desired_state_) {
 | 
				
			||||||
 | 
					      // only store when in the desired state
 | 
				
			||||||
 | 
					      this->pref_.save(&state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::add_on_state_callback(std::function<void()> &&callback) {
 | 
				
			||||||
 | 
					  this->state_callback_.add(std::move(callback));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callback) {
 | 
				
			||||||
 | 
					  this->triggered_callback_.add(std::move(callback));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
 | 
				
			||||||
 | 
					  this->cleared_callback_.add(std::move(callback));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::arm_away(optional<std::string> code) {
 | 
				
			||||||
 | 
					  auto call = this->make_call();
 | 
				
			||||||
 | 
					  call.arm_away();
 | 
				
			||||||
 | 
					  if (code.has_value())
 | 
				
			||||||
 | 
					    call.set_code(code.value());
 | 
				
			||||||
 | 
					  call.perform();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::arm_home(optional<std::string> code) {
 | 
				
			||||||
 | 
					  auto call = this->make_call();
 | 
				
			||||||
 | 
					  call.arm_home();
 | 
				
			||||||
 | 
					  if (code.has_value())
 | 
				
			||||||
 | 
					    call.set_code(code.value());
 | 
				
			||||||
 | 
					  call.perform();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::arm_night(optional<std::string> code) {
 | 
				
			||||||
 | 
					  auto call = this->make_call();
 | 
				
			||||||
 | 
					  call.arm_night();
 | 
				
			||||||
 | 
					  if (code.has_value())
 | 
				
			||||||
 | 
					    call.set_code(code.value());
 | 
				
			||||||
 | 
					  call.perform();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::arm_vacation(optional<std::string> code) {
 | 
				
			||||||
 | 
					  auto call = this->make_call();
 | 
				
			||||||
 | 
					  call.arm_vacation();
 | 
				
			||||||
 | 
					  if (code.has_value())
 | 
				
			||||||
 | 
					    call.set_code(code.value());
 | 
				
			||||||
 | 
					  call.perform();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::arm_custom_bypass(optional<std::string> code) {
 | 
				
			||||||
 | 
					  auto call = this->make_call();
 | 
				
			||||||
 | 
					  call.arm_custom_bypass();
 | 
				
			||||||
 | 
					  if (code.has_value())
 | 
				
			||||||
 | 
					    call.set_code(code.value());
 | 
				
			||||||
 | 
					  call.perform();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanel::disarm(optional<std::string> code) {
 | 
				
			||||||
 | 
					  auto call = this->make_call();
 | 
				
			||||||
 | 
					  call.disarm();
 | 
				
			||||||
 | 
					  if (code.has_value())
 | 
				
			||||||
 | 
					    call.set_code(code.value());
 | 
				
			||||||
 | 
					  call.perform();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace alarm_control_panel
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										136
									
								
								esphome/components/alarm_control_panel/alarm_control_panel.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								esphome/components/alarm_control_panel/alarm_control_panel.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "alarm_control_panel_call.h"
 | 
				
			||||||
 | 
					#include "alarm_control_panel_state.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/automation.h"
 | 
				
			||||||
 | 
					#include "esphome/core/entity_base.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace alarm_control_panel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum AlarmControlPanelFeature : uint8_t {
 | 
				
			||||||
 | 
					  // Matches Home Assistant values
 | 
				
			||||||
 | 
					  ACP_FEAT_ARM_HOME = 1 << 0,
 | 
				
			||||||
 | 
					  ACP_FEAT_ARM_AWAY = 1 << 1,
 | 
				
			||||||
 | 
					  ACP_FEAT_ARM_NIGHT = 1 << 2,
 | 
				
			||||||
 | 
					  ACP_FEAT_TRIGGER = 1 << 3,
 | 
				
			||||||
 | 
					  ACP_FEAT_ARM_CUSTOM_BYPASS = 1 << 4,
 | 
				
			||||||
 | 
					  ACP_FEAT_ARM_VACATION = 1 << 5,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AlarmControlPanel : public EntityBase {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  /** Make a AlarmControlPanelCall
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  AlarmControlPanelCall make_call();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Set the state of the alarm_control_panel.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param state The AlarmControlPanelState.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void publish_state(AlarmControlPanelState state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Add a callback for when the state of the alarm_control_panel changes
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param callback The callback function
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void add_on_state_callback(std::function<void()> &&callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Add a callback for when the state of the alarm_control_panel chanes to triggered
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param callback The callback function
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void add_on_triggered_callback(std::function<void()> &&callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Add a callback for when the state of the alarm_control_panel clears from triggered
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param callback The callback function
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void add_on_cleared_callback(std::function<void()> &&callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** A numeric representation of the supported features as per HomeAssistant
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  virtual uint32_t get_supported_features() const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Returns if the alarm_control_panel has a code
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  virtual bool get_requires_code() const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Returns if the alarm_control_panel requires a code to arm
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  virtual bool get_requires_code_to_arm() const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** arm the alarm in away mode
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param code The code
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void arm_away(optional<std::string> code = nullopt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** arm the alarm in home mode
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param code The code
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void arm_home(optional<std::string> code = nullopt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** arm the alarm in night mode
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param code The code
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void arm_night(optional<std::string> code = nullopt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** arm the alarm in vacation mode
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param code The code
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void arm_vacation(optional<std::string> code = nullopt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** arm the alarm in custom bypass mode
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param code The code
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void arm_custom_bypass(optional<std::string> code = nullopt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** disarm the alarm
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param code The code
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void disarm(optional<std::string> code = nullopt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Get the state
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  AlarmControlPanelState get_state() const { return this->current_state_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // is the state one of the armed states
 | 
				
			||||||
 | 
					  bool is_state_armed(AlarmControlPanelState state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  friend AlarmControlPanelCall;
 | 
				
			||||||
 | 
					  // in order to store last panel state in flash
 | 
				
			||||||
 | 
					  ESPPreferenceObject pref_;
 | 
				
			||||||
 | 
					  // current state
 | 
				
			||||||
 | 
					  AlarmControlPanelState current_state_;
 | 
				
			||||||
 | 
					  // the desired (or previous) state
 | 
				
			||||||
 | 
					  AlarmControlPanelState desired_state_;
 | 
				
			||||||
 | 
					  // last time the state was updated
 | 
				
			||||||
 | 
					  uint32_t last_update_;
 | 
				
			||||||
 | 
					  // the call control function
 | 
				
			||||||
 | 
					  virtual void control(const AlarmControlPanelCall &call) = 0;
 | 
				
			||||||
 | 
					  // state callback
 | 
				
			||||||
 | 
					  CallbackManager<void()> state_callback_{};
 | 
				
			||||||
 | 
					  // trigger callback
 | 
				
			||||||
 | 
					  CallbackManager<void()> triggered_callback_{};
 | 
				
			||||||
 | 
					  // clear callback
 | 
				
			||||||
 | 
					  CallbackManager<void()> cleared_callback_{};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace alarm_control_panel
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					#include "alarm_control_panel_call.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "alarm_control_panel.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace alarm_control_panel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "alarm_control_panel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall::AlarmControlPanelCall(AlarmControlPanel *parent) : parent_(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall &AlarmControlPanelCall::set_code(const std::string &code) {
 | 
				
			||||||
 | 
					  this->code_ = code;
 | 
				
			||||||
 | 
					  return *this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall &AlarmControlPanelCall::arm_away() {
 | 
				
			||||||
 | 
					  this->state_ = ACP_STATE_ARMED_AWAY;
 | 
				
			||||||
 | 
					  return *this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall &AlarmControlPanelCall::arm_home() {
 | 
				
			||||||
 | 
					  this->state_ = ACP_STATE_ARMED_HOME;
 | 
				
			||||||
 | 
					  return *this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall &AlarmControlPanelCall::arm_night() {
 | 
				
			||||||
 | 
					  this->state_ = ACP_STATE_ARMED_NIGHT;
 | 
				
			||||||
 | 
					  return *this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall &AlarmControlPanelCall::arm_vacation() {
 | 
				
			||||||
 | 
					  this->state_ = ACP_STATE_ARMED_VACATION;
 | 
				
			||||||
 | 
					  return *this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall &AlarmControlPanelCall::arm_custom_bypass() {
 | 
				
			||||||
 | 
					  this->state_ = ACP_STATE_ARMED_CUSTOM_BYPASS;
 | 
				
			||||||
 | 
					  return *this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall &AlarmControlPanelCall::disarm() {
 | 
				
			||||||
 | 
					  this->state_ = ACP_STATE_DISARMED;
 | 
				
			||||||
 | 
					  return *this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall &AlarmControlPanelCall::pending() {
 | 
				
			||||||
 | 
					  this->state_ = ACP_STATE_PENDING;
 | 
				
			||||||
 | 
					  return *this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AlarmControlPanelCall &AlarmControlPanelCall::triggered() {
 | 
				
			||||||
 | 
					  this->state_ = ACP_STATE_TRIGGERED;
 | 
				
			||||||
 | 
					  return *this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const optional<AlarmControlPanelState> &AlarmControlPanelCall::get_state() const { return this->state_; }
 | 
				
			||||||
 | 
					const optional<std::string> &AlarmControlPanelCall::get_code() const { return this->code_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanelCall::validate_() {
 | 
				
			||||||
 | 
					  if (this->state_.has_value()) {
 | 
				
			||||||
 | 
					    auto state = *this->state_;
 | 
				
			||||||
 | 
					    if (this->parent_->is_state_armed(state) && this->parent_->get_state() != ACP_STATE_DISARMED) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Cannot arm when not disarmed");
 | 
				
			||||||
 | 
					      this->state_.reset();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (state == ACP_STATE_PENDING && this->parent_->get_state() == ACP_STATE_DISARMED) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Cannot trip alarm when disarmed");
 | 
				
			||||||
 | 
					      this->state_.reset();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (state == ACP_STATE_DISARMED &&
 | 
				
			||||||
 | 
					        !(this->parent_->is_state_armed(this->parent_->get_state()) ||
 | 
				
			||||||
 | 
					          this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING ||
 | 
				
			||||||
 | 
					          this->parent_->get_state() == ACP_STATE_TRIGGERED)) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Cannot disarm when not armed");
 | 
				
			||||||
 | 
					      this->state_.reset();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (state == ACP_STATE_ARMED_HOME && (this->parent_->get_supported_features() & ACP_FEAT_ARM_HOME) == 0) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Cannot arm home when not supported");
 | 
				
			||||||
 | 
					      this->state_.reset();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AlarmControlPanelCall::perform() {
 | 
				
			||||||
 | 
					  this->validate_();
 | 
				
			||||||
 | 
					  if (this->state_) {
 | 
				
			||||||
 | 
					    this->parent_->control(*this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace alarm_control_panel
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "alarm_control_panel_state.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace alarm_control_panel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AlarmControlPanel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AlarmControlPanelCall {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  AlarmControlPanelCall(AlarmControlPanel *parent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  AlarmControlPanelCall &set_code(const std::string &code);
 | 
				
			||||||
 | 
					  AlarmControlPanelCall &arm_away();
 | 
				
			||||||
 | 
					  AlarmControlPanelCall &arm_home();
 | 
				
			||||||
 | 
					  AlarmControlPanelCall &arm_night();
 | 
				
			||||||
 | 
					  AlarmControlPanelCall &arm_vacation();
 | 
				
			||||||
 | 
					  AlarmControlPanelCall &arm_custom_bypass();
 | 
				
			||||||
 | 
					  AlarmControlPanelCall &disarm();
 | 
				
			||||||
 | 
					  AlarmControlPanelCall &pending();
 | 
				
			||||||
 | 
					  AlarmControlPanelCall &triggered();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void perform();
 | 
				
			||||||
 | 
					  const optional<AlarmControlPanelState> &get_state() const;
 | 
				
			||||||
 | 
					  const optional<std::string> &get_code() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  AlarmControlPanel *parent_;
 | 
				
			||||||
 | 
					  optional<std::string> code_{};
 | 
				
			||||||
 | 
					  optional<AlarmControlPanelState> state_{};
 | 
				
			||||||
 | 
					  void validate_();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace alarm_control_panel
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					#include "alarm_control_panel_state.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace alarm_control_panel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
 | 
				
			||||||
 | 
					  switch (state) {
 | 
				
			||||||
 | 
					    case ACP_STATE_DISARMED:
 | 
				
			||||||
 | 
					      return LOG_STR("DISARMED");
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_HOME:
 | 
				
			||||||
 | 
					      return LOG_STR("ARMED_HOME");
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_AWAY:
 | 
				
			||||||
 | 
					      return LOG_STR("ARMED_AWAY");
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_NIGHT:
 | 
				
			||||||
 | 
					      return LOG_STR("NIGHT");
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_VACATION:
 | 
				
			||||||
 | 
					      return LOG_STR("ARMED_VACATION");
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMED_CUSTOM_BYPASS:
 | 
				
			||||||
 | 
					      return LOG_STR("ARMED_CUSTOM_BYPASS");
 | 
				
			||||||
 | 
					    case ACP_STATE_PENDING:
 | 
				
			||||||
 | 
					      return LOG_STR("PENDING");
 | 
				
			||||||
 | 
					    case ACP_STATE_ARMING:
 | 
				
			||||||
 | 
					      return LOG_STR("ARMING");
 | 
				
			||||||
 | 
					    case ACP_STATE_DISARMING:
 | 
				
			||||||
 | 
					      return LOG_STR("DISARMING");
 | 
				
			||||||
 | 
					    case ACP_STATE_TRIGGERED:
 | 
				
			||||||
 | 
					      return LOG_STR("TRIGGERED");
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return LOG_STR("UNKNOWN");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace alarm_control_panel
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace alarm_control_panel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum AlarmControlPanelState : uint8_t {
 | 
				
			||||||
 | 
					  ACP_STATE_DISARMED = 0,
 | 
				
			||||||
 | 
					  ACP_STATE_ARMED_HOME = 1,
 | 
				
			||||||
 | 
					  ACP_STATE_ARMED_AWAY = 2,
 | 
				
			||||||
 | 
					  ACP_STATE_ARMED_NIGHT = 3,
 | 
				
			||||||
 | 
					  ACP_STATE_ARMED_VACATION = 4,
 | 
				
			||||||
 | 
					  ACP_STATE_ARMED_CUSTOM_BYPASS = 5,
 | 
				
			||||||
 | 
					  ACP_STATE_PENDING = 6,
 | 
				
			||||||
 | 
					  ACP_STATE_ARMING = 7,
 | 
				
			||||||
 | 
					  ACP_STATE_DISARMING = 8,
 | 
				
			||||||
 | 
					  ACP_STATE_TRIGGERED = 9
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Returns a string representation of the state.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param state The AlarmControlPanelState.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace alarm_control_panel
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										115
									
								
								esphome/components/alarm_control_panel/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								esphome/components/alarm_control_panel/automation.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include "esphome/core/automation.h"
 | 
				
			||||||
 | 
					#include "alarm_control_panel.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace alarm_control_panel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StateTrigger : public Trigger<> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
 | 
				
			||||||
 | 
					    alarm_control_panel->add_on_state_callback([this]() { this->trigger(); });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TriggeredTrigger : public Trigger<> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) {
 | 
				
			||||||
 | 
					    alarm_control_panel->add_on_triggered_callback([this]() { this->trigger(); });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ClearedTrigger : public Trigger<> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
 | 
				
			||||||
 | 
					    alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  explicit ArmAwayAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TEMPLATABLE_VALUE(std::string, code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void play(Ts... x) override {
 | 
				
			||||||
 | 
					    auto call = this->alarm_control_panel_->make_call();
 | 
				
			||||||
 | 
					    auto code = this->code_.optional_value(x...);
 | 
				
			||||||
 | 
					    if (code.has_value()) {
 | 
				
			||||||
 | 
					      call.set_code(code.value());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    call.arm_away();
 | 
				
			||||||
 | 
					    call.perform();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  AlarmControlPanel *alarm_control_panel_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  explicit ArmHomeAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TEMPLATABLE_VALUE(std::string, code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void play(Ts... x) override {
 | 
				
			||||||
 | 
					    auto call = this->alarm_control_panel_->make_call();
 | 
				
			||||||
 | 
					    auto code = this->code_.optional_value(x...);
 | 
				
			||||||
 | 
					    if (code.has_value()) {
 | 
				
			||||||
 | 
					      call.set_code(code.value());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    call.arm_home();
 | 
				
			||||||
 | 
					    call.perform();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  AlarmControlPanel *alarm_control_panel_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename... Ts> class DisarmAction : public Action<Ts...> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TEMPLATABLE_VALUE(std::string, code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void play(Ts... x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  AlarmControlPanel *alarm_control_panel_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename... Ts> class PendingAction : public Action<Ts...> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  explicit PendingAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void play(Ts... x) override { this->alarm_control_panel_->make_call().pending().perform(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  AlarmControlPanel *alarm_control_panel_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename... Ts> class TriggeredAction : public Action<Ts...> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  explicit TriggeredAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void play(Ts... x) override { this->alarm_control_panel_->make_call().triggered().perform(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  AlarmControlPanel *alarm_control_panel_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts...> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  AlarmControlPanelCondition(AlarmControlPanel *parent) : parent_(parent) {}
 | 
				
			||||||
 | 
					  bool check(Ts... x) override {
 | 
				
			||||||
 | 
					    return this->parent_->is_state_armed(this->parent_->get_state()) ||
 | 
				
			||||||
 | 
					           this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_TRIGGERED;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  AlarmControlPanel *parent_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace alarm_control_panel
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -3,9 +3,17 @@ import logging
 | 
				
			|||||||
from esphome import core
 | 
					from esphome import core
 | 
				
			||||||
from esphome.components import display, font
 | 
					from esphome.components import display, font
 | 
				
			||||||
import esphome.components.image as espImage
 | 
					import esphome.components.image as espImage
 | 
				
			||||||
 | 
					from esphome.components.image import CONF_USE_TRANSPARENCY
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_FILE,
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_RAW_DATA_ID,
 | 
				
			||||||
 | 
					    CONF_REPEAT,
 | 
				
			||||||
 | 
					    CONF_RESIZE,
 | 
				
			||||||
 | 
					    CONF_TYPE,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, HexInt
 | 
					from esphome.core import CORE, HexInt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_LOGGER = logging.getLogger(__name__)
 | 
					_LOGGER = logging.getLogger(__name__)
 | 
				
			||||||
@@ -13,18 +21,55 @@ _LOGGER = logging.getLogger(__name__)
 | 
				
			|||||||
DEPENDENCIES = ["display"]
 | 
					DEPENDENCIES = ["display"]
 | 
				
			||||||
MULTI_CONF = True
 | 
					MULTI_CONF = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_LOOP = "loop"
 | 
				
			||||||
 | 
					CONF_START_FRAME = "start_frame"
 | 
				
			||||||
 | 
					CONF_END_FRAME = "end_frame"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Animation_ = display.display_ns.class_("Animation", espImage.Image_)
 | 
					Animation_ = display.display_ns.class_("Animation", espImage.Image_)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_cross_dependencies(config):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Validate fields whose possible values depend on other fields.
 | 
				
			||||||
 | 
					    For example, validate that explicitly transparent image types
 | 
				
			||||||
 | 
					    have "use_transparency" set to True.
 | 
				
			||||||
 | 
					    Also set the default value for those kind of dependent fields.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    image_type = config[CONF_TYPE]
 | 
				
			||||||
 | 
					    is_transparent_type = image_type in ["TRANSPARENT_BINARY", "RGBA"]
 | 
				
			||||||
 | 
					    # If the use_transparency option was not specified, set the default depending on the image type
 | 
				
			||||||
 | 
					    if CONF_USE_TRANSPARENCY not in config:
 | 
				
			||||||
 | 
					        config[CONF_USE_TRANSPARENCY] = is_transparent_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if is_transparent_type and not config[CONF_USE_TRANSPARENCY]:
 | 
				
			||||||
 | 
					        raise cv.Invalid(f"Image type {image_type} must always be transparent.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ANIMATION_SCHEMA = cv.Schema(
 | 
					ANIMATION_SCHEMA = cv.Schema(
 | 
				
			||||||
    {
 | 
					    cv.All(
 | 
				
			||||||
        cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
					        {
 | 
				
			||||||
        cv.Required(CONF_FILE): cv.file_,
 | 
					            cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
				
			||||||
        cv.Optional(CONF_RESIZE): cv.dimensions,
 | 
					            cv.Required(CONF_FILE): cv.file_,
 | 
				
			||||||
        cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
 | 
					            cv.Optional(CONF_RESIZE): cv.dimensions,
 | 
				
			||||||
            espImage.IMAGE_TYPE, upper=True
 | 
					            cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
 | 
				
			||||||
        ),
 | 
					                espImage.IMAGE_TYPE, upper=True
 | 
				
			||||||
        cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
					            ),
 | 
				
			||||||
    }
 | 
					            # Not setting default here on purpose; the default depends on the image type,
 | 
				
			||||||
 | 
					            # and thus will be set in the "validate_cross_dependencies" validator.
 | 
				
			||||||
 | 
					            cv.Optional(CONF_USE_TRANSPARENCY): cv.boolean,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_LOOP): cv.All(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
 | 
				
			||||||
 | 
					                    cv.Optional(CONF_END_FRAME): cv.positive_int,
 | 
				
			||||||
 | 
					                    cv.Optional(CONF_REPEAT): cv.positive_int,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        validate_cross_dependencies,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
 | 
					CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
 | 
				
			||||||
@@ -50,16 +95,19 @@ async def to_code(config):
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        if width > 500 or height > 500:
 | 
					        if width > 500 or height > 500:
 | 
				
			||||||
            _LOGGER.warning(
 | 
					            _LOGGER.warning(
 | 
				
			||||||
                "The image you requested is very big. Please consider using"
 | 
					                'The image "%s" you requested is very big. Please consider'
 | 
				
			||||||
                " the resize parameter."
 | 
					                " using the resize parameter.",
 | 
				
			||||||
 | 
					                path,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    transparent = config[CONF_USE_TRANSPARENCY]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if config[CONF_TYPE] == "GRAYSCALE":
 | 
					    if config[CONF_TYPE] == "GRAYSCALE":
 | 
				
			||||||
        data = [0 for _ in range(height * width * frames)]
 | 
					        data = [0 for _ in range(height * width * frames)]
 | 
				
			||||||
        pos = 0
 | 
					        pos = 0
 | 
				
			||||||
        for frameIndex in range(frames):
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
            image.seek(frameIndex)
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
            frame = image.convert("L", dither=Image.NONE)
 | 
					            frame = image.convert("LA", dither=Image.NONE)
 | 
				
			||||||
            if CONF_RESIZE in config:
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
                frame = frame.resize([width, height])
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
            pixels = list(frame.getdata())
 | 
					            pixels = list(frame.getdata())
 | 
				
			||||||
@@ -67,16 +115,22 @@ async def to_code(config):
 | 
				
			|||||||
                raise core.EsphomeError(
 | 
					                raise core.EsphomeError(
 | 
				
			||||||
                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
					                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            for pix in pixels:
 | 
					            for pix, a in pixels:
 | 
				
			||||||
 | 
					                if transparent:
 | 
				
			||||||
 | 
					                    if pix == 1:
 | 
				
			||||||
 | 
					                        pix = 0
 | 
				
			||||||
 | 
					                    if a < 0x80:
 | 
				
			||||||
 | 
					                        pix = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                data[pos] = pix
 | 
					                data[pos] = pix
 | 
				
			||||||
                pos += 1
 | 
					                pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif config[CONF_TYPE] == "RGB24":
 | 
					    elif config[CONF_TYPE] == "RGBA":
 | 
				
			||||||
        data = [0 for _ in range(height * width * 3 * frames)]
 | 
					        data = [0 for _ in range(height * width * 4 * frames)]
 | 
				
			||||||
        pos = 0
 | 
					        pos = 0
 | 
				
			||||||
        for frameIndex in range(frames):
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
            image.seek(frameIndex)
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
            frame = image.convert("RGB")
 | 
					            frame = image.convert("RGBA")
 | 
				
			||||||
            if CONF_RESIZE in config:
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
                frame = frame.resize([width, height])
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
            pixels = list(frame.getdata())
 | 
					            pixels = list(frame.getdata())
 | 
				
			||||||
@@ -91,13 +145,15 @@ async def to_code(config):
 | 
				
			|||||||
                pos += 1
 | 
					                pos += 1
 | 
				
			||||||
                data[pos] = pix[2]
 | 
					                data[pos] = pix[2]
 | 
				
			||||||
                pos += 1
 | 
					                pos += 1
 | 
				
			||||||
 | 
					                data[pos] = pix[3]
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif config[CONF_TYPE] == "RGB565":
 | 
					    elif config[CONF_TYPE] == "RGB24":
 | 
				
			||||||
        data = [0 for _ in range(height * width * 2 * frames)]
 | 
					        data = [0 for _ in range(height * width * 3 * frames)]
 | 
				
			||||||
        pos = 0
 | 
					        pos = 0
 | 
				
			||||||
        for frameIndex in range(frames):
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
            image.seek(frameIndex)
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
            frame = image.convert("RGB")
 | 
					            frame = image.convert("RGBA")
 | 
				
			||||||
            if CONF_RESIZE in config:
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
                frame = frame.resize([width, height])
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
            pixels = list(frame.getdata())
 | 
					            pixels = list(frame.getdata())
 | 
				
			||||||
@@ -105,14 +161,50 @@ async def to_code(config):
 | 
				
			|||||||
                raise core.EsphomeError(
 | 
					                raise core.EsphomeError(
 | 
				
			||||||
                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
					                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            for pix in pixels:
 | 
					            for r, g, b, a in pixels:
 | 
				
			||||||
                R = pix[0] >> 3
 | 
					                if transparent:
 | 
				
			||||||
                G = pix[1] >> 2
 | 
					                    if r == 0 and g == 0 and b == 1:
 | 
				
			||||||
                B = pix[2] >> 3
 | 
					                        b = 0
 | 
				
			||||||
 | 
					                    if a < 0x80:
 | 
				
			||||||
 | 
					                        r = 0
 | 
				
			||||||
 | 
					                        g = 0
 | 
				
			||||||
 | 
					                        b = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                data[pos] = r
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					                data[pos] = g
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					                data[pos] = b
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif config[CONF_TYPE] in ["RGB565", "TRANSPARENT_IMAGE"]:
 | 
				
			||||||
 | 
					        data = [0 for _ in range(height * width * 2 * frames)]
 | 
				
			||||||
 | 
					        pos = 0
 | 
				
			||||||
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
 | 
					            frame = image.convert("RGBA")
 | 
				
			||||||
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
 | 
					            pixels = list(frame.getdata())
 | 
				
			||||||
 | 
					            if len(pixels) != height * width:
 | 
				
			||||||
 | 
					                raise core.EsphomeError(
 | 
				
			||||||
 | 
					                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            for r, g, b, a in pixels:
 | 
				
			||||||
 | 
					                R = r >> 3
 | 
				
			||||||
 | 
					                G = g >> 2
 | 
				
			||||||
 | 
					                B = b >> 3
 | 
				
			||||||
                rgb = (R << 11) | (G << 5) | B
 | 
					                rgb = (R << 11) | (G << 5) | B
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if transparent:
 | 
				
			||||||
 | 
					                    if rgb == 0x0020:
 | 
				
			||||||
 | 
					                        rgb = 0
 | 
				
			||||||
 | 
					                    if a < 0x80:
 | 
				
			||||||
 | 
					                        rgb = 0x0020
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                data[pos] = rgb >> 8
 | 
					                data[pos] = rgb >> 8
 | 
				
			||||||
                pos += 1
 | 
					                pos += 1
 | 
				
			||||||
                data[pos] = rgb & 255
 | 
					                data[pos] = rgb & 0xFF
 | 
				
			||||||
                pos += 1
 | 
					                pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]:
 | 
					    elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]:
 | 
				
			||||||
@@ -120,19 +212,31 @@ async def to_code(config):
 | 
				
			|||||||
        data = [0 for _ in range((height * width8 // 8) * frames)]
 | 
					        data = [0 for _ in range((height * width8 // 8) * frames)]
 | 
				
			||||||
        for frameIndex in range(frames):
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
            image.seek(frameIndex)
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
 | 
					            if transparent:
 | 
				
			||||||
 | 
					                alpha = image.split()[-1]
 | 
				
			||||||
 | 
					                has_alpha = alpha.getextrema()[0] < 0xFF
 | 
				
			||||||
            frame = image.convert("1", dither=Image.NONE)
 | 
					            frame = image.convert("1", dither=Image.NONE)
 | 
				
			||||||
            if CONF_RESIZE in config:
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
                frame = frame.resize([width, height])
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
            for y in range(height):
 | 
					                if transparent:
 | 
				
			||||||
                for x in range(width):
 | 
					                    alpha = alpha.resize([width, height])
 | 
				
			||||||
                    if frame.getpixel((x, y)):
 | 
					            for x, y in [(i, j) for i in range(width) for j in range(height)]:
 | 
				
			||||||
 | 
					                if transparent and has_alpha:
 | 
				
			||||||
 | 
					                    if not alpha.getpixel((x, y)):
 | 
				
			||||||
                        continue
 | 
					                        continue
 | 
				
			||||||
                    pos = x + y * width8 + (height * width8 * frameIndex)
 | 
					                elif frame.getpixel((x, y)):
 | 
				
			||||||
                    data[pos // 8] |= 0x80 >> (pos % 8)
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                pos = x + y * width8 + (height * width8 * frameIndex)
 | 
				
			||||||
 | 
					                data[pos // 8] |= 0x80 >> (pos % 8)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        raise core.EsphomeError(
 | 
				
			||||||
 | 
					            f"Animation f{config[CONF_ID]} has not supported type {config[CONF_TYPE]}."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rhs = [HexInt(x) for x in data]
 | 
					    rhs = [HexInt(x) for x in data]
 | 
				
			||||||
    prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
					    prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
				
			||||||
    cg.new_Pvariable(
 | 
					    var = cg.new_Pvariable(
 | 
				
			||||||
        config[CONF_ID],
 | 
					        config[CONF_ID],
 | 
				
			||||||
        prog_arr,
 | 
					        prog_arr,
 | 
				
			||||||
        width,
 | 
					        width,
 | 
				
			||||||
@@ -140,3 +244,9 @@ async def to_code(config):
 | 
				
			|||||||
        frames,
 | 
					        frames,
 | 
				
			||||||
        espImage.IMAGE_TYPE[config[CONF_TYPE]],
 | 
					        espImage.IMAGE_TYPE[config[CONF_TYPE]],
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    cg.add(var.set_transparency(transparent))
 | 
				
			||||||
 | 
					    if CONF_LOOP in config:
 | 
				
			||||||
 | 
					        start = config[CONF_LOOP][CONF_START_FRAME]
 | 
				
			||||||
 | 
					        end = config[CONF_LOOP].get(CONF_END_FRAME, frames)
 | 
				
			||||||
 | 
					        count = config[CONF_LOOP].get(CONF_REPEAT, -1)
 | 
				
			||||||
 | 
					        cg.add(var.set_loop(start, end, count))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,6 @@ from esphome.components import i2c
 | 
				
			|||||||
from esphome.const import CONF_ID
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["i2c"]
 | 
					DEPENDENCIES = ["i2c"]
 | 
				
			||||||
AUTO_LOAD = ["sensor", "binary_sensor"]
 | 
					 | 
				
			||||||
MULTI_CONF = True
 | 
					MULTI_CONF = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_APDS9960_ID = "apds9960_id"
 | 
					CONF_APDS9960_ID = "apds9960_id"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -116,8 +116,12 @@ void APDS9960::setup() {
 | 
				
			|||||||
  APDS9960_WRITE_BYTE(0x80, val);
 | 
					  APDS9960_WRITE_BYTE(0x80, val);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
bool APDS9960::is_color_enabled_() const {
 | 
					bool APDS9960::is_color_enabled_() const {
 | 
				
			||||||
  return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr ||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
         this->clear_channel_ != nullptr;
 | 
					  return this->red_sensor_ != nullptr || this->green_sensor_ != nullptr || this->blue_sensor_ != nullptr ||
 | 
				
			||||||
 | 
					         this->clear_sensor_ != nullptr;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  return false;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void APDS9960::dump_config() {
 | 
					void APDS9960::dump_config() {
 | 
				
			||||||
@@ -125,6 +129,15 @@ void APDS9960::dump_config() {
 | 
				
			|||||||
  LOG_I2C_DEVICE(this);
 | 
					  LOG_I2C_DEVICE(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  LOG_UPDATE_INTERVAL(this);
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Red channel", this->red_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Green channel", this->green_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Blue channel", this->blue_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Clear channel", this->clear_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Proximity", this->proximity_sensor_);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (this->is_failed()) {
 | 
					  if (this->is_failed()) {
 | 
				
			||||||
    switch (this->error_code_) {
 | 
					    switch (this->error_code_) {
 | 
				
			||||||
      case COMMUNICATION_FAILED:
 | 
					      case COMMUNICATION_FAILED:
 | 
				
			||||||
@@ -181,17 +194,22 @@ void APDS9960::read_color_data_(uint8_t status) {
 | 
				
			|||||||
  float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
 | 
					  float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
 | 
					  ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
 | 
				
			||||||
  if (this->clear_channel_ != nullptr)
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
    this->clear_channel_->publish_state(clear_perc);
 | 
					  if (this->clear_sensor_ != nullptr)
 | 
				
			||||||
  if (this->red_channel_ != nullptr)
 | 
					    this->clear_sensor_->publish_state(clear_perc);
 | 
				
			||||||
    this->red_channel_->publish_state(red_perc);
 | 
					  if (this->red_sensor_ != nullptr)
 | 
				
			||||||
  if (this->green_channel_ != nullptr)
 | 
					    this->red_sensor_->publish_state(red_perc);
 | 
				
			||||||
    this->green_channel_->publish_state(green_perc);
 | 
					  if (this->green_sensor_ != nullptr)
 | 
				
			||||||
  if (this->blue_channel_ != nullptr)
 | 
					    this->green_sensor_->publish_state(green_perc);
 | 
				
			||||||
    this->blue_channel_->publish_state(blue_perc);
 | 
					  if (this->blue_sensor_ != nullptr)
 | 
				
			||||||
 | 
					    this->blue_sensor_->publish_state(blue_perc);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APDS9960::read_proximity_data_(uint8_t status) {
 | 
					void APDS9960::read_proximity_data_(uint8_t status) {
 | 
				
			||||||
  if (this->proximity_ == nullptr)
 | 
					#ifndef USE_SENSOR
 | 
				
			||||||
 | 
					  return;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  if (this->proximity_sensor_ == nullptr)
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if ((status & 0b10) == 0x00) {
 | 
					  if ((status & 0b10) == 0x00) {
 | 
				
			||||||
@@ -204,7 +222,8 @@ void APDS9960::read_proximity_data_(uint8_t status) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
 | 
					  float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
 | 
				
			||||||
  ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
 | 
					  ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
 | 
				
			||||||
  this->proximity_->publish_state(prox_perc);
 | 
					  this->proximity_sensor_->publish_state(prox_perc);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APDS9960::read_gesture_data_() {
 | 
					void APDS9960::read_gesture_data_() {
 | 
				
			||||||
  if (!this->is_gesture_enabled_())
 | 
					  if (!this->is_gesture_enabled_())
 | 
				
			||||||
@@ -256,28 +275,29 @@ void APDS9960::read_gesture_data_() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APDS9960::report_gesture_(int gesture) {
 | 
					void APDS9960::report_gesture_(int gesture) {
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
  binary_sensor::BinarySensor *bin;
 | 
					  binary_sensor::BinarySensor *bin;
 | 
				
			||||||
  switch (gesture) {
 | 
					  switch (gesture) {
 | 
				
			||||||
    case 1:
 | 
					    case 1:
 | 
				
			||||||
      bin = this->up_direction_;
 | 
					      bin = this->up_direction_binary_sensor_;
 | 
				
			||||||
      this->gesture_up_started_ = false;
 | 
					      this->gesture_up_started_ = false;
 | 
				
			||||||
      this->gesture_down_started_ = false;
 | 
					      this->gesture_down_started_ = false;
 | 
				
			||||||
      ESP_LOGD(TAG, "Got gesture UP");
 | 
					      ESP_LOGD(TAG, "Got gesture UP");
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case 2:
 | 
					    case 2:
 | 
				
			||||||
      bin = this->down_direction_;
 | 
					      bin = this->down_direction_binary_sensor_;
 | 
				
			||||||
      this->gesture_up_started_ = false;
 | 
					      this->gesture_up_started_ = false;
 | 
				
			||||||
      this->gesture_down_started_ = false;
 | 
					      this->gesture_down_started_ = false;
 | 
				
			||||||
      ESP_LOGD(TAG, "Got gesture DOWN");
 | 
					      ESP_LOGD(TAG, "Got gesture DOWN");
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case 3:
 | 
					    case 3:
 | 
				
			||||||
      bin = this->left_direction_;
 | 
					      bin = this->left_direction_binary_sensor_;
 | 
				
			||||||
      this->gesture_left_started_ = false;
 | 
					      this->gesture_left_started_ = false;
 | 
				
			||||||
      this->gesture_right_started_ = false;
 | 
					      this->gesture_right_started_ = false;
 | 
				
			||||||
      ESP_LOGD(TAG, "Got gesture LEFT");
 | 
					      ESP_LOGD(TAG, "Got gesture LEFT");
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case 4:
 | 
					    case 4:
 | 
				
			||||||
      bin = this->right_direction_;
 | 
					      bin = this->right_direction_binary_sensor_;
 | 
				
			||||||
      this->gesture_left_started_ = false;
 | 
					      this->gesture_left_started_ = false;
 | 
				
			||||||
      this->gesture_right_started_ = false;
 | 
					      this->gesture_right_started_ = false;
 | 
				
			||||||
      ESP_LOGD(TAG, "Got gesture RIGHT");
 | 
					      ESP_LOGD(TAG, "Got gesture RIGHT");
 | 
				
			||||||
@@ -290,6 +310,7 @@ void APDS9960::report_gesture_(int gesture) {
 | 
				
			|||||||
    bin->publish_state(true);
 | 
					    bin->publish_state(true);
 | 
				
			||||||
    bin->publish_state(false);
 | 
					    bin->publish_state(false);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APDS9960::process_dataset_(int up, int down, int left, int right) {
 | 
					void APDS9960::process_dataset_(int up, int down, int left, int right) {
 | 
				
			||||||
  /* Algorithm: (see Figure 11 in datasheet)
 | 
					  /* Algorithm: (see Figure 11 in datasheet)
 | 
				
			||||||
@@ -365,10 +386,22 @@ void APDS9960::process_dataset_(int up, int down, int left, int right) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
 | 
					float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
 | 
				
			||||||
bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); }
 | 
					bool APDS9960::is_proximity_enabled_() const {
 | 
				
			||||||
 | 
					  return
 | 
				
			||||||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
 | 
					      this->proximity_sensor_ != nullptr
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					      false
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      || this->is_gesture_enabled_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
bool APDS9960::is_gesture_enabled_() const {
 | 
					bool APDS9960::is_gesture_enabled_() const {
 | 
				
			||||||
  return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr ||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
         this->right_direction_ != nullptr;
 | 
					  return this->up_direction_binary_sensor_ != nullptr || this->left_direction_binary_sensor_ != nullptr ||
 | 
				
			||||||
 | 
					         this->down_direction_binary_sensor_ != nullptr || this->right_direction_binary_sensor_ != nullptr;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  return false;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace apds9960
 | 
					}  // namespace apds9960
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,34 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					 | 
				
			||||||
#include "esphome/components/i2c/i2c.h"
 | 
					#include "esphome/components/i2c/i2c.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
#include "esphome/components/sensor/sensor.h"
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
					#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace apds9960 {
 | 
					namespace apds9960 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
					class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
				
			||||||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
 | 
					  SUB_SENSOR(red)
 | 
				
			||||||
 | 
					  SUB_SENSOR(green)
 | 
				
			||||||
 | 
					  SUB_SENSOR(blue)
 | 
				
			||||||
 | 
					  SUB_SENSOR(clear)
 | 
				
			||||||
 | 
					  SUB_SENSOR(proximity)
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					  SUB_BINARY_SENSOR(up_direction)
 | 
				
			||||||
 | 
					  SUB_BINARY_SENSOR(right_direction)
 | 
				
			||||||
 | 
					  SUB_BINARY_SENSOR(down_direction)
 | 
				
			||||||
 | 
					  SUB_BINARY_SENSOR(left_direction)
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
@@ -23,16 +43,6 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
				
			|||||||
  void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; }
 | 
					  void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; }
 | 
				
			||||||
  void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; }
 | 
					  void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; }
 | 
					 | 
				
			||||||
  void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; }
 | 
					 | 
				
			||||||
  void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; }
 | 
					 | 
				
			||||||
  void set_clear_channel(sensor::Sensor *clear_channel) { clear_channel_ = clear_channel; }
 | 
					 | 
				
			||||||
  void set_up_direction(binary_sensor::BinarySensor *up_direction) { up_direction_ = up_direction; }
 | 
					 | 
				
			||||||
  void set_right_direction(binary_sensor::BinarySensor *right_direction) { right_direction_ = right_direction; }
 | 
					 | 
				
			||||||
  void set_down_direction(binary_sensor::BinarySensor *down_direction) { down_direction_ = down_direction; }
 | 
					 | 
				
			||||||
  void set_left_direction(binary_sensor::BinarySensor *left_direction) { left_direction_ = left_direction; }
 | 
					 | 
				
			||||||
  void set_proximity(sensor::Sensor *proximity) { proximity_ = proximity; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  bool is_color_enabled_() const;
 | 
					  bool is_color_enabled_() const;
 | 
				
			||||||
  bool is_proximity_enabled_() const;
 | 
					  bool is_proximity_enabled_() const;
 | 
				
			||||||
@@ -50,15 +60,6 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
				
			|||||||
  uint8_t gesture_gain_;
 | 
					  uint8_t gesture_gain_;
 | 
				
			||||||
  uint8_t gesture_wait_time_;
 | 
					  uint8_t gesture_wait_time_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sensor::Sensor *red_channel_{nullptr};
 | 
					 | 
				
			||||||
  sensor::Sensor *green_channel_{nullptr};
 | 
					 | 
				
			||||||
  sensor::Sensor *blue_channel_{nullptr};
 | 
					 | 
				
			||||||
  sensor::Sensor *clear_channel_{nullptr};
 | 
					 | 
				
			||||||
  binary_sensor::BinarySensor *up_direction_{nullptr};
 | 
					 | 
				
			||||||
  binary_sensor::BinarySensor *right_direction_{nullptr};
 | 
					 | 
				
			||||||
  binary_sensor::BinarySensor *down_direction_{nullptr};
 | 
					 | 
				
			||||||
  binary_sensor::BinarySensor *left_direction_{nullptr};
 | 
					 | 
				
			||||||
  sensor::Sensor *proximity_{nullptr};
 | 
					 | 
				
			||||||
  enum ErrorCode {
 | 
					  enum ErrorCode {
 | 
				
			||||||
    NONE = 0,
 | 
					    NONE = 0,
 | 
				
			||||||
    COMMUNICATION_FAILED,
 | 
					    COMMUNICATION_FAILED,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,19 +6,14 @@ from . import APDS9960, CONF_APDS9960_ID
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["apds9960"]
 | 
					DEPENDENCIES = ["apds9960"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DIRECTIONS = {
 | 
					DIRECTIONS = ["up", "down", "left", "right"]
 | 
				
			||||||
    "UP": "set_up_direction",
 | 
					 | 
				
			||||||
    "DOWN": "set_down_direction",
 | 
					 | 
				
			||||||
    "LEFT": "set_left_direction",
 | 
					 | 
				
			||||||
    "RIGHT": "set_right_direction",
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
 | 
					CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
 | 
				
			||||||
    device_class=DEVICE_CLASS_MOVING
 | 
					    device_class=DEVICE_CLASS_MOVING
 | 
				
			||||||
).extend(
 | 
					).extend(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
					        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
				
			||||||
        cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
 | 
					        cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, lower=True),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,5 +21,5 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
 | 
				
			|||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    hub = await cg.get_variable(config[CONF_APDS9960_ID])
 | 
					    hub = await cg.get_variable(config[CONF_APDS9960_ID])
 | 
				
			||||||
    var = await binary_sensor.new_binary_sensor(config)
 | 
					    var = await binary_sensor.new_binary_sensor(config)
 | 
				
			||||||
    func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
 | 
					    func = getattr(hub, f"set_{config[CONF_DIRECTION]}_direction_binary_sensor")
 | 
				
			||||||
    cg.add(func(var))
 | 
					    cg.add(func(var))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,13 +11,7 @@ from . import APDS9960, CONF_APDS9960_ID
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["apds9960"]
 | 
					DEPENDENCIES = ["apds9960"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TYPES = {
 | 
					TYPES = ["clear", "red", "green", "blue", "proximity"]
 | 
				
			||||||
    "CLEAR": "set_clear_channel",
 | 
					 | 
				
			||||||
    "RED": "set_red_channel",
 | 
					 | 
				
			||||||
    "GREEN": "set_green_channel",
 | 
					 | 
				
			||||||
    "BLUE": "set_blue_channel",
 | 
					 | 
				
			||||||
    "PROXIMITY": "set_proximity",
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = sensor.sensor_schema(
 | 
					CONFIG_SCHEMA = sensor.sensor_schema(
 | 
				
			||||||
    unit_of_measurement=UNIT_PERCENT,
 | 
					    unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
@@ -26,7 +20,7 @@ CONFIG_SCHEMA = sensor.sensor_schema(
 | 
				
			|||||||
    state_class=STATE_CLASS_MEASUREMENT,
 | 
					    state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
).extend(
 | 
					).extend(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
 | 
					        cv.Required(CONF_TYPE): cv.one_of(*TYPES, lower=True),
 | 
				
			||||||
        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
					        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -35,5 +29,5 @@ CONFIG_SCHEMA = sensor.sensor_schema(
 | 
				
			|||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    hub = await cg.get_variable(config[CONF_APDS9960_ID])
 | 
					    hub = await cg.get_variable(config[CONF_APDS9960_ID])
 | 
				
			||||||
    var = await sensor.new_sensor(config)
 | 
					    var = await sensor.new_sensor(config)
 | 
				
			||||||
    func = getattr(hub, TYPES[config[CONF_TYPE]])
 | 
					    func = getattr(hub, f"set_{config[CONF_TYPE]}_sensor")
 | 
				
			||||||
    cg.add(func(var))
 | 
					    cg.add(func(var))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,6 +56,8 @@ service APIConnection {
 | 
				
			|||||||
  rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
					  rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
 | 
					  rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -206,7 +208,8 @@ message DeviceInfoResponse {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  uint32 webserver_port = 10;
 | 
					  uint32 webserver_port = 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint32 bluetooth_proxy_version = 11;
 | 
					  uint32 legacy_bluetooth_proxy_version = 11;
 | 
				
			||||||
 | 
					  uint32 bluetooth_proxy_feature_flags = 15;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  string manufacturer = 12;
 | 
					  string manufacturer = 12;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1130,6 +1133,8 @@ message SubscribeBluetoothLEAdvertisementsRequest {
 | 
				
			|||||||
  option (id) = 66;
 | 
					  option (id) = 66;
 | 
				
			||||||
  option (source) = SOURCE_CLIENT;
 | 
					  option (source) = SOURCE_CLIENT;
 | 
				
			||||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
					  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32 flags = 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message BluetoothServiceData {
 | 
					message BluetoothServiceData {
 | 
				
			||||||
@@ -1154,6 +1159,23 @@ message BluetoothLEAdvertisementResponse {
 | 
				
			|||||||
  uint32 address_type = 7;
 | 
					  uint32 address_type = 7;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message BluetoothLERawAdvertisement {
 | 
				
			||||||
 | 
					  uint64 address = 1;
 | 
				
			||||||
 | 
					  sint32 rssi = 2;
 | 
				
			||||||
 | 
					  uint32 address_type = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bytes data = 4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message BluetoothLERawAdvertisementsResponse {
 | 
				
			||||||
 | 
					  option (id) = 93;
 | 
				
			||||||
 | 
					  option (source) = SOURCE_SERVER;
 | 
				
			||||||
 | 
					  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
				
			||||||
 | 
					  option (no_delay) = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  repeated BluetoothLERawAdvertisement advertisements = 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum BluetoothDeviceRequestType {
 | 
					enum BluetoothDeviceRequestType {
 | 
				
			||||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
 | 
					  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
 | 
				
			||||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
 | 
					  BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
 | 
				
			||||||
@@ -1397,6 +1419,7 @@ message VoiceAssistantRequest {
 | 
				
			|||||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
					  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool start = 1;
 | 
					  bool start = 1;
 | 
				
			||||||
 | 
					  string conversation_id = 2;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message VoiceAssistantResponse {
 | 
					message VoiceAssistantResponse {
 | 
				
			||||||
@@ -1433,3 +1456,63 @@ message VoiceAssistantEventResponse {
 | 
				
			|||||||
  VoiceAssistantEvent event_type = 1;
 | 
					  VoiceAssistantEvent event_type = 1;
 | 
				
			||||||
  repeated VoiceAssistantEventData data = 2;
 | 
					  repeated VoiceAssistantEventData data = 2;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ==================== ALARM CONTROL PANEL ====================
 | 
				
			||||||
 | 
					enum AlarmControlPanelState {
 | 
				
			||||||
 | 
					  ALARM_STATE_DISARMED = 0;
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_HOME = 1;
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_AWAY = 2;
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_NIGHT = 3;
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_VACATION = 4;
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_CUSTOM_BYPASS = 5;
 | 
				
			||||||
 | 
					  ALARM_STATE_PENDING = 6;
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMING = 7;
 | 
				
			||||||
 | 
					  ALARM_STATE_DISARMING = 8;
 | 
				
			||||||
 | 
					  ALARM_STATE_TRIGGERED = 9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum AlarmControlPanelStateCommand {
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_DISARM = 0;
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_AWAY = 1;
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_HOME = 2;
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_NIGHT = 3;
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_VACATION = 4;
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5;
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_TRIGGER = 6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message ListEntitiesAlarmControlPanelResponse {
 | 
				
			||||||
 | 
					  option (id) = 94;
 | 
				
			||||||
 | 
					  option (source) = SOURCE_SERVER;
 | 
				
			||||||
 | 
					  option (ifdef) = "USE_ALARM_CONTROL_PANEL";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  string object_id = 1;
 | 
				
			||||||
 | 
					  fixed32 key = 2;
 | 
				
			||||||
 | 
					  string name = 3;
 | 
				
			||||||
 | 
					  string unique_id = 4;
 | 
				
			||||||
 | 
					  string icon = 5;
 | 
				
			||||||
 | 
					  bool disabled_by_default = 6;
 | 
				
			||||||
 | 
					  EntityCategory entity_category = 7;
 | 
				
			||||||
 | 
					  uint32 supported_features = 8;
 | 
				
			||||||
 | 
					  bool requires_code = 9;
 | 
				
			||||||
 | 
					  bool requires_code_to_arm = 10;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message AlarmControlPanelStateResponse {
 | 
				
			||||||
 | 
					  option (id) = 95;
 | 
				
			||||||
 | 
					  option (source) = SOURCE_SERVER;
 | 
				
			||||||
 | 
					  option (ifdef) = "USE_ALARM_CONTROL_PANEL";
 | 
				
			||||||
 | 
					  option (no_delay) = true;
 | 
				
			||||||
 | 
					  fixed32 key = 1;
 | 
				
			||||||
 | 
					  AlarmControlPanelState state = 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message AlarmControlPanelCommandRequest {
 | 
				
			||||||
 | 
					  option (id) = 96;
 | 
				
			||||||
 | 
					  option (source) = SOURCE_CLIENT;
 | 
				
			||||||
 | 
					  option (ifdef) = "USE_ALARM_CONTROL_PANEL";
 | 
				
			||||||
 | 
					  option (no_delay) = true;
 | 
				
			||||||
 | 
					  fixed32 key = 1;
 | 
				
			||||||
 | 
					  AlarmControlPanelStateCommand command = 2;
 | 
				
			||||||
 | 
					  string code = 3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,6 +51,14 @@ void APIConnection::start() {
 | 
				
			|||||||
  helper_->set_log_info(client_info_);
 | 
					  helper_->set_log_info(client_info_);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					APIConnection::~APIConnection() {
 | 
				
			||||||
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
 | 
					  if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
 | 
				
			||||||
 | 
					    bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void APIConnection::loop() {
 | 
					void APIConnection::loop() {
 | 
				
			||||||
  if (this->remove_)
 | 
					  if (this->remove_)
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -223,6 +231,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
 | 
				
			|||||||
  msg.assumed_state = traits.get_is_assumed_state();
 | 
					  msg.assumed_state = traits.get_is_assumed_state();
 | 
				
			||||||
  msg.supports_position = traits.get_supports_position();
 | 
					  msg.supports_position = traits.get_supports_position();
 | 
				
			||||||
  msg.supports_tilt = traits.get_supports_tilt();
 | 
					  msg.supports_tilt = traits.get_supports_tilt();
 | 
				
			||||||
 | 
					  msg.supports_stop = traits.get_supports_stop();
 | 
				
			||||||
  msg.device_class = cover->get_device_class();
 | 
					  msg.device_class = cover->get_device_class();
 | 
				
			||||||
  msg.disabled_by_default = cover->is_disabled_by_default();
 | 
					  msg.disabled_by_default = cover->is_disabled_by_default();
 | 
				
			||||||
  msg.icon = cover->get_icon();
 | 
					  msg.icon = cover->get_icon();
 | 
				
			||||||
@@ -844,9 +853,13 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
 | 
					void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
				
			||||||
 | 
					  bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void APIConnection::unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
				
			||||||
 | 
					  bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
 | 
					bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
 | 
				
			||||||
  if (!this->bluetooth_le_advertisement_subscription_)
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
 | 
					  if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
 | 
				
			||||||
    BluetoothLEAdvertisementResponse resp = msg;
 | 
					    BluetoothLEAdvertisementResponse resp = msg;
 | 
				
			||||||
    for (auto &service : resp.service_data) {
 | 
					    for (auto &service : resp.service_data) {
 | 
				
			||||||
@@ -894,11 +907,12 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
bool APIConnection::request_voice_assistant(bool start) {
 | 
					bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id) {
 | 
				
			||||||
  if (!this->voice_assistant_subscription_)
 | 
					  if (!this->voice_assistant_subscription_)
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  VoiceAssistantRequest msg;
 | 
					  VoiceAssistantRequest msg;
 | 
				
			||||||
  msg.start = start;
 | 
					  msg.start = start;
 | 
				
			||||||
 | 
					  msg.conversation_id = conversation_id;
 | 
				
			||||||
  return this->send_voice_assistant_request(msg);
 | 
					  return this->send_voice_assistant_request(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
 | 
					void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
 | 
				
			||||||
@@ -917,6 +931,64 @@ void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventR
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
 | 
				
			||||||
 | 
					  if (!this->state_subscription_)
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  AlarmControlPanelStateResponse resp{};
 | 
				
			||||||
 | 
					  resp.key = a_alarm_control_panel->get_object_id_hash();
 | 
				
			||||||
 | 
					  resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
 | 
				
			||||||
 | 
					  return this->send_alarm_control_panel_state_response(resp);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
 | 
				
			||||||
 | 
					  ListEntitiesAlarmControlPanelResponse msg;
 | 
				
			||||||
 | 
					  msg.key = a_alarm_control_panel->get_object_id_hash();
 | 
				
			||||||
 | 
					  msg.object_id = a_alarm_control_panel->get_object_id();
 | 
				
			||||||
 | 
					  msg.name = a_alarm_control_panel->get_name();
 | 
				
			||||||
 | 
					  msg.unique_id = get_default_unique_id("alarm_control_panel", a_alarm_control_panel);
 | 
				
			||||||
 | 
					  msg.icon = a_alarm_control_panel->get_icon();
 | 
				
			||||||
 | 
					  msg.disabled_by_default = a_alarm_control_panel->is_disabled_by_default();
 | 
				
			||||||
 | 
					  msg.entity_category = static_cast<enums::EntityCategory>(a_alarm_control_panel->get_entity_category());
 | 
				
			||||||
 | 
					  msg.supported_features = a_alarm_control_panel->get_supported_features();
 | 
				
			||||||
 | 
					  msg.requires_code = a_alarm_control_panel->get_requires_code();
 | 
				
			||||||
 | 
					  msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
 | 
				
			||||||
 | 
					  return this->send_list_entities_alarm_control_panel_response(msg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
 | 
				
			||||||
 | 
					  alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
 | 
				
			||||||
 | 
					  if (a_alarm_control_panel == nullptr)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto call = a_alarm_control_panel->make_call();
 | 
				
			||||||
 | 
					  switch (msg.command) {
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_DISARM:
 | 
				
			||||||
 | 
					      call.disarm();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
 | 
				
			||||||
 | 
					      call.arm_away();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_HOME:
 | 
				
			||||||
 | 
					      call.arm_home();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
 | 
				
			||||||
 | 
					      call.arm_night();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
 | 
				
			||||||
 | 
					      call.arm_vacation();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
 | 
				
			||||||
 | 
					      call.arm_custom_bypass();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_TRIGGER:
 | 
				
			||||||
 | 
					      call.pending();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  call.set_code(msg.code);
 | 
				
			||||||
 | 
					  call.perform();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
 | 
					bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
 | 
				
			||||||
  if (this->log_subscription_ < level)
 | 
					  if (this->log_subscription_ < level)
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
@@ -941,7 +1013,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  HelloResponse resp;
 | 
					  HelloResponse resp;
 | 
				
			||||||
  resp.api_version_major = 1;
 | 
					  resp.api_version_major = 1;
 | 
				
			||||||
  resp.api_version_minor = 8;
 | 
					  resp.api_version_minor = 9;
 | 
				
			||||||
  resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
 | 
					  resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
 | 
				
			||||||
  resp.name = App.get_name();
 | 
					  resp.name = App.get_name();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -978,6 +1050,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
				
			|||||||
  resp.manufacturer = "Espressif";
 | 
					  resp.manufacturer = "Espressif";
 | 
				
			||||||
#elif defined(USE_RP2040)
 | 
					#elif defined(USE_RP2040)
 | 
				
			||||||
  resp.manufacturer = "Raspberry Pi";
 | 
					  resp.manufacturer = "Raspberry Pi";
 | 
				
			||||||
 | 
					#elif defined(USE_HOST)
 | 
				
			||||||
 | 
					  resp.manufacturer = "Host";
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  resp.model = ESPHOME_BOARD;
 | 
					  resp.model = ESPHOME_BOARD;
 | 
				
			||||||
#ifdef USE_DEEP_SLEEP
 | 
					#ifdef USE_DEEP_SLEEP
 | 
				
			||||||
@@ -991,12 +1065,11 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
				
			|||||||
  resp.webserver_port = USE_WEBSERVER_PORT;
 | 
					  resp.webserver_port = USE_WEBSERVER_PORT;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
  resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active()
 | 
					  resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version();
 | 
				
			||||||
                                     ? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION
 | 
					  resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
 | 
				
			||||||
                                     : bluetooth_proxy::PASSIVE_ONLY_VERSION;
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
  resp.voice_assistant_version = 1;
 | 
					  resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version();
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  return resp;
 | 
					  return resp;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ namespace api {
 | 
				
			|||||||
class APIConnection : public APIServerConnection {
 | 
					class APIConnection : public APIServerConnection {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
 | 
					  APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
 | 
				
			||||||
  virtual ~APIConnection() = default;
 | 
					  virtual ~APIConnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void start();
 | 
					  void start();
 | 
				
			||||||
  void loop();
 | 
					  void loop();
 | 
				
			||||||
@@ -98,12 +98,8 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
    this->send_homeassistant_service_response(call);
 | 
					    this->send_homeassistant_service_response(call);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
  void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override {
 | 
					  void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
				
			||||||
    this->bluetooth_le_advertisement_subscription_ = true;
 | 
					  void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override {
 | 
					 | 
				
			||||||
    this->bluetooth_le_advertisement_subscription_ = false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
 | 
					  bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
 | 
					  void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
 | 
				
			||||||
@@ -128,11 +124,17 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
 | 
					  void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
 | 
				
			||||||
    this->voice_assistant_subscription_ = msg.subscribe;
 | 
					    this->voice_assistant_subscription_ = msg.subscribe;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  bool request_voice_assistant(bool start);
 | 
					  bool request_voice_assistant(bool start, const std::string &conversation_id);
 | 
				
			||||||
  void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
 | 
					  void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
 | 
				
			||||||
  void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
 | 
					  void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					  bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
				
			||||||
 | 
					  bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
				
			||||||
 | 
					  void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void on_disconnect_response(const DisconnectResponse &value) override;
 | 
					  void on_disconnect_response(const DisconnectResponse &value) override;
 | 
				
			||||||
  void on_ping_response(const PingResponse &value) override {
 | 
					  void on_ping_response(const PingResponse &value) override {
 | 
				
			||||||
    // we initiated ping
 | 
					    // we initiated ping
 | 
				
			||||||
@@ -211,9 +213,6 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  uint32_t last_traffic_;
 | 
					  uint32_t last_traffic_;
 | 
				
			||||||
  bool sent_ping_{false};
 | 
					  bool sent_ping_{false};
 | 
				
			||||||
  bool service_call_subscription_{false};
 | 
					  bool service_call_subscription_{false};
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					 | 
				
			||||||
  bool bluetooth_le_advertisement_subscription_{false};
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
  bool voice_assistant_subscription_{false};
 | 
					  bool voice_assistant_subscription_{false};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -433,6 +433,57 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					template<> const char *proto_enum_to_string<enums::AlarmControlPanelState>(enums::AlarmControlPanelState value) {
 | 
				
			||||||
 | 
					  switch (value) {
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_DISARMED:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_DISARMED";
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_ARMED_HOME:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_ARMED_HOME";
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_ARMED_AWAY:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_ARMED_AWAY";
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_ARMED_NIGHT:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_ARMED_NIGHT";
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_ARMED_VACATION:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_ARMED_VACATION";
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_ARMED_CUSTOM_BYPASS:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_ARMED_CUSTOM_BYPASS";
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_PENDING:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_PENDING";
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_ARMING:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_ARMING";
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_DISARMING:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_DISARMING";
 | 
				
			||||||
 | 
					    case enums::ALARM_STATE_TRIGGERED:
 | 
				
			||||||
 | 
					      return "ALARM_STATE_TRIGGERED";
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return "UNKNOWN";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					template<>
 | 
				
			||||||
 | 
					const char *proto_enum_to_string<enums::AlarmControlPanelStateCommand>(enums::AlarmControlPanelStateCommand value) {
 | 
				
			||||||
 | 
					  switch (value) {
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_DISARM:
 | 
				
			||||||
 | 
					      return "ALARM_CONTROL_PANEL_DISARM";
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
 | 
				
			||||||
 | 
					      return "ALARM_CONTROL_PANEL_ARM_AWAY";
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_HOME:
 | 
				
			||||||
 | 
					      return "ALARM_CONTROL_PANEL_ARM_HOME";
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
 | 
				
			||||||
 | 
					      return "ALARM_CONTROL_PANEL_ARM_NIGHT";
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
 | 
				
			||||||
 | 
					      return "ALARM_CONTROL_PANEL_ARM_VACATION";
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
 | 
				
			||||||
 | 
					      return "ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS";
 | 
				
			||||||
 | 
					    case enums::ALARM_CONTROL_PANEL_TRIGGER:
 | 
				
			||||||
 | 
					      return "ALARM_CONTROL_PANEL_TRIGGER";
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return "UNKNOWN";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
					bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
  switch (field_id) {
 | 
					  switch (field_id) {
 | 
				
			||||||
    case 2: {
 | 
					    case 2: {
 | 
				
			||||||
@@ -617,7 +668,11 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			|||||||
      return true;
 | 
					      return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case 11: {
 | 
					    case 11: {
 | 
				
			||||||
      this->bluetooth_proxy_version = value.as_uint32();
 | 
					      this->legacy_bluetooth_proxy_version = value.as_uint32();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 15: {
 | 
				
			||||||
 | 
					      this->bluetooth_proxy_feature_flags = value.as_uint32();
 | 
				
			||||||
      return true;
 | 
					      return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case 14: {
 | 
					    case 14: {
 | 
				
			||||||
@@ -681,7 +736,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
 | 
				
			|||||||
  buffer.encode_string(8, this->project_name);
 | 
					  buffer.encode_string(8, this->project_name);
 | 
				
			||||||
  buffer.encode_string(9, this->project_version);
 | 
					  buffer.encode_string(9, this->project_version);
 | 
				
			||||||
  buffer.encode_uint32(10, this->webserver_port);
 | 
					  buffer.encode_uint32(10, this->webserver_port);
 | 
				
			||||||
  buffer.encode_uint32(11, this->bluetooth_proxy_version);
 | 
					  buffer.encode_uint32(11, this->legacy_bluetooth_proxy_version);
 | 
				
			||||||
 | 
					  buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags);
 | 
				
			||||||
  buffer.encode_string(12, this->manufacturer);
 | 
					  buffer.encode_string(12, this->manufacturer);
 | 
				
			||||||
  buffer.encode_string(13, this->friendly_name);
 | 
					  buffer.encode_string(13, this->friendly_name);
 | 
				
			||||||
  buffer.encode_uint32(14, this->voice_assistant_version);
 | 
					  buffer.encode_uint32(14, this->voice_assistant_version);
 | 
				
			||||||
@@ -731,8 +787,13 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
				
			|||||||
  out.append(buffer);
 | 
					  out.append(buffer);
 | 
				
			||||||
  out.append("\n");
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  out.append("  bluetooth_proxy_version: ");
 | 
					  out.append("  legacy_bluetooth_proxy_version: ");
 | 
				
			||||||
  sprintf(buffer, "%u", this->bluetooth_proxy_version);
 | 
					  sprintf(buffer, "%u", this->legacy_bluetooth_proxy_version);
 | 
				
			||||||
 | 
					  out.append(buffer);
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  bluetooth_proxy_feature_flags: ");
 | 
				
			||||||
 | 
					  sprintf(buffer, "%u", this->bluetooth_proxy_feature_flags);
 | 
				
			||||||
  out.append(buffer);
 | 
					  out.append(buffer);
 | 
				
			||||||
  out.append("\n");
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -5041,10 +5102,28 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
 | 
				
			|||||||
  out.append("}");
 | 
					  out.append("}");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {}
 | 
					bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1: {
 | 
				
			||||||
 | 
					      this->flags = value.as_uint32();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
 | 
					  buffer.encode_uint32(1, this->flags);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
 | 
					void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
 | 
				
			||||||
  out.append("SubscribeBluetoothLEAdvertisementsRequest {}");
 | 
					  __attribute__((unused)) char buffer[64];
 | 
				
			||||||
 | 
					  out.append("SubscribeBluetoothLEAdvertisementsRequest {\n");
 | 
				
			||||||
 | 
					  out.append("  flags: ");
 | 
				
			||||||
 | 
					  sprintf(buffer, "%u", this->flags);
 | 
				
			||||||
 | 
					  out.append(buffer);
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					  out.append("}");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
					bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
@@ -5197,6 +5276,92 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
 | 
				
			|||||||
  out.append("}");
 | 
					  out.append("}");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					bool BluetoothLERawAdvertisement::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1: {
 | 
				
			||||||
 | 
					      this->address = value.as_uint64();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 2: {
 | 
				
			||||||
 | 
					      this->rssi = value.as_sint32();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 3: {
 | 
				
			||||||
 | 
					      this->address_type = value.as_uint32();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool BluetoothLERawAdvertisement::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 4: {
 | 
				
			||||||
 | 
					      this->data = value.as_string();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
 | 
					  buffer.encode_uint64(1, this->address);
 | 
				
			||||||
 | 
					  buffer.encode_sint32(2, this->rssi);
 | 
				
			||||||
 | 
					  buffer.encode_uint32(3, this->address_type);
 | 
				
			||||||
 | 
					  buffer.encode_string(4, this->data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  __attribute__((unused)) char buffer[64];
 | 
				
			||||||
 | 
					  out.append("BluetoothLERawAdvertisement {\n");
 | 
				
			||||||
 | 
					  out.append("  address: ");
 | 
				
			||||||
 | 
					  sprintf(buffer, "%llu", this->address);
 | 
				
			||||||
 | 
					  out.append(buffer);
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  rssi: ");
 | 
				
			||||||
 | 
					  sprintf(buffer, "%d", this->rssi);
 | 
				
			||||||
 | 
					  out.append(buffer);
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  address_type: ");
 | 
				
			||||||
 | 
					  sprintf(buffer, "%u", this->address_type);
 | 
				
			||||||
 | 
					  out.append(buffer);
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  data: ");
 | 
				
			||||||
 | 
					  out.append("'").append(this->data).append("'");
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					  out.append("}");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					bool BluetoothLERawAdvertisementsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1: {
 | 
				
			||||||
 | 
					      this->advertisements.push_back(value.as_message<BluetoothLERawAdvertisement>());
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
 | 
					  for (auto &it : this->advertisements) {
 | 
				
			||||||
 | 
					    buffer.encode_message<BluetoothLERawAdvertisement>(1, it, true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  __attribute__((unused)) char buffer[64];
 | 
				
			||||||
 | 
					  out.append("BluetoothLERawAdvertisementsResponse {\n");
 | 
				
			||||||
 | 
					  for (const auto &it : this->advertisements) {
 | 
				
			||||||
 | 
					    out.append("  advertisements: ");
 | 
				
			||||||
 | 
					    it.dump_to(out);
 | 
				
			||||||
 | 
					    out.append("\n");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  out.append("}");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
					bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
  switch (field_id) {
 | 
					  switch (field_id) {
 | 
				
			||||||
    case 1: {
 | 
					    case 1: {
 | 
				
			||||||
@@ -6187,7 +6352,20 @@ bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
 | 
				
			|||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); }
 | 
					bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 2: {
 | 
				
			||||||
 | 
					      this->conversation_id = value.as_string();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
 | 
					  buffer.encode_bool(1, this->start);
 | 
				
			||||||
 | 
					  buffer.encode_string(2, this->conversation_id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
void VoiceAssistantRequest::dump_to(std::string &out) const {
 | 
					void VoiceAssistantRequest::dump_to(std::string &out) const {
 | 
				
			||||||
  __attribute__((unused)) char buffer[64];
 | 
					  __attribute__((unused)) char buffer[64];
 | 
				
			||||||
@@ -6195,6 +6373,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
 | 
				
			|||||||
  out.append("  start: ");
 | 
					  out.append("  start: ");
 | 
				
			||||||
  out.append(YESNO(this->start));
 | 
					  out.append(YESNO(this->start));
 | 
				
			||||||
  out.append("\n");
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  conversation_id: ");
 | 
				
			||||||
 | 
					  out.append("'").append(this->conversation_id).append("'");
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
  out.append("}");
 | 
					  out.append("}");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -6305,6 +6487,217 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
 | 
				
			|||||||
  out.append("}");
 | 
					  out.append("}");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 6: {
 | 
				
			||||||
 | 
					      this->disabled_by_default = value.as_bool();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 7: {
 | 
				
			||||||
 | 
					      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 8: {
 | 
				
			||||||
 | 
					      this->supported_features = value.as_uint32();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 9: {
 | 
				
			||||||
 | 
					      this->requires_code = value.as_bool();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 10: {
 | 
				
			||||||
 | 
					      this->requires_code_to_arm = value.as_bool();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool ListEntitiesAlarmControlPanelResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1: {
 | 
				
			||||||
 | 
					      this->object_id = value.as_string();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 3: {
 | 
				
			||||||
 | 
					      this->name = value.as_string();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 4: {
 | 
				
			||||||
 | 
					      this->unique_id = value.as_string();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 5: {
 | 
				
			||||||
 | 
					      this->icon = value.as_string();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool ListEntitiesAlarmControlPanelResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 2: {
 | 
				
			||||||
 | 
					      this->key = value.as_fixed32();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
 | 
					  buffer.encode_string(1, this->object_id);
 | 
				
			||||||
 | 
					  buffer.encode_fixed32(2, this->key);
 | 
				
			||||||
 | 
					  buffer.encode_string(3, this->name);
 | 
				
			||||||
 | 
					  buffer.encode_string(4, this->unique_id);
 | 
				
			||||||
 | 
					  buffer.encode_string(5, this->icon);
 | 
				
			||||||
 | 
					  buffer.encode_bool(6, this->disabled_by_default);
 | 
				
			||||||
 | 
					  buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
 | 
				
			||||||
 | 
					  buffer.encode_uint32(8, this->supported_features);
 | 
				
			||||||
 | 
					  buffer.encode_bool(9, this->requires_code);
 | 
				
			||||||
 | 
					  buffer.encode_bool(10, this->requires_code_to_arm);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  __attribute__((unused)) char buffer[64];
 | 
				
			||||||
 | 
					  out.append("ListEntitiesAlarmControlPanelResponse {\n");
 | 
				
			||||||
 | 
					  out.append("  object_id: ");
 | 
				
			||||||
 | 
					  out.append("'").append(this->object_id).append("'");
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  key: ");
 | 
				
			||||||
 | 
					  sprintf(buffer, "%u", this->key);
 | 
				
			||||||
 | 
					  out.append(buffer);
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  name: ");
 | 
				
			||||||
 | 
					  out.append("'").append(this->name).append("'");
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  unique_id: ");
 | 
				
			||||||
 | 
					  out.append("'").append(this->unique_id).append("'");
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  icon: ");
 | 
				
			||||||
 | 
					  out.append("'").append(this->icon).append("'");
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  disabled_by_default: ");
 | 
				
			||||||
 | 
					  out.append(YESNO(this->disabled_by_default));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  entity_category: ");
 | 
				
			||||||
 | 
					  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  supported_features: ");
 | 
				
			||||||
 | 
					  sprintf(buffer, "%u", this->supported_features);
 | 
				
			||||||
 | 
					  out.append(buffer);
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  requires_code: ");
 | 
				
			||||||
 | 
					  out.append(YESNO(this->requires_code));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  requires_code_to_arm: ");
 | 
				
			||||||
 | 
					  out.append(YESNO(this->requires_code_to_arm));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					  out.append("}");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					bool AlarmControlPanelStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 2: {
 | 
				
			||||||
 | 
					      this->state = value.as_enum<enums::AlarmControlPanelState>();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool AlarmControlPanelStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1: {
 | 
				
			||||||
 | 
					      this->key = value.as_fixed32();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
 | 
					  buffer.encode_fixed32(1, this->key);
 | 
				
			||||||
 | 
					  buffer.encode_enum<enums::AlarmControlPanelState>(2, this->state);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					void AlarmControlPanelStateResponse::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  __attribute__((unused)) char buffer[64];
 | 
				
			||||||
 | 
					  out.append("AlarmControlPanelStateResponse {\n");
 | 
				
			||||||
 | 
					  out.append("  key: ");
 | 
				
			||||||
 | 
					  sprintf(buffer, "%u", this->key);
 | 
				
			||||||
 | 
					  out.append(buffer);
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  state: ");
 | 
				
			||||||
 | 
					  out.append(proto_enum_to_string<enums::AlarmControlPanelState>(this->state));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					  out.append("}");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 2: {
 | 
				
			||||||
 | 
					      this->command = value.as_enum<enums::AlarmControlPanelStateCommand>();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 3: {
 | 
				
			||||||
 | 
					      this->code = value.as_string();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1: {
 | 
				
			||||||
 | 
					      this->key = value.as_fixed32();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void AlarmControlPanelCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
 | 
					  buffer.encode_fixed32(1, this->key);
 | 
				
			||||||
 | 
					  buffer.encode_enum<enums::AlarmControlPanelStateCommand>(2, this->command);
 | 
				
			||||||
 | 
					  buffer.encode_string(3, this->code);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  __attribute__((unused)) char buffer[64];
 | 
				
			||||||
 | 
					  out.append("AlarmControlPanelCommandRequest {\n");
 | 
				
			||||||
 | 
					  out.append("  key: ");
 | 
				
			||||||
 | 
					  sprintf(buffer, "%u", this->key);
 | 
				
			||||||
 | 
					  out.append(buffer);
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  command: ");
 | 
				
			||||||
 | 
					  out.append(proto_enum_to_string<enums::AlarmControlPanelStateCommand>(this->command));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  out.append("  code: ");
 | 
				
			||||||
 | 
					  out.append("'").append(this->code).append("'");
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					  out.append("}");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -176,6 +176,27 @@ enum VoiceAssistantEvent : uint32_t {
 | 
				
			|||||||
  VOICE_ASSISTANT_TTS_START = 7,
 | 
					  VOICE_ASSISTANT_TTS_START = 7,
 | 
				
			||||||
  VOICE_ASSISTANT_TTS_END = 8,
 | 
					  VOICE_ASSISTANT_TTS_END = 8,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					enum AlarmControlPanelState : uint32_t {
 | 
				
			||||||
 | 
					  ALARM_STATE_DISARMED = 0,
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_HOME = 1,
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_AWAY = 2,
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_NIGHT = 3,
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_VACATION = 4,
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMED_CUSTOM_BYPASS = 5,
 | 
				
			||||||
 | 
					  ALARM_STATE_PENDING = 6,
 | 
				
			||||||
 | 
					  ALARM_STATE_ARMING = 7,
 | 
				
			||||||
 | 
					  ALARM_STATE_DISARMING = 8,
 | 
				
			||||||
 | 
					  ALARM_STATE_TRIGGERED = 9,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					enum AlarmControlPanelStateCommand : uint32_t {
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_DISARM = 0,
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_AWAY = 1,
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_HOME = 2,
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_NIGHT = 3,
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_VACATION = 4,
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5,
 | 
				
			||||||
 | 
					  ALARM_CONTROL_PANEL_TRIGGER = 6,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace enums
 | 
					}  // namespace enums
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -287,7 +308,8 @@ class DeviceInfoResponse : public ProtoMessage {
 | 
				
			|||||||
  std::string project_name{};
 | 
					  std::string project_name{};
 | 
				
			||||||
  std::string project_version{};
 | 
					  std::string project_version{};
 | 
				
			||||||
  uint32_t webserver_port{0};
 | 
					  uint32_t webserver_port{0};
 | 
				
			||||||
  uint32_t bluetooth_proxy_version{0};
 | 
					  uint32_t legacy_bluetooth_proxy_version{0};
 | 
				
			||||||
 | 
					  uint32_t bluetooth_proxy_feature_flags{0};
 | 
				
			||||||
  std::string manufacturer{};
 | 
					  std::string manufacturer{};
 | 
				
			||||||
  std::string friendly_name{};
 | 
					  std::string friendly_name{};
 | 
				
			||||||
  uint32_t voice_assistant_version{0};
 | 
					  uint32_t voice_assistant_version{0};
 | 
				
			||||||
@@ -1247,12 +1269,14 @@ class MediaPlayerCommandRequest : public ProtoMessage {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
 | 
					class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
 | 
					  uint32_t flags{0};
 | 
				
			||||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
					  void encode(ProtoWriteBuffer buffer) const override;
 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
  void dump_to(std::string &out) const override;
 | 
					  void dump_to(std::string &out) const override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
class BluetoothServiceData : public ProtoMessage {
 | 
					class BluetoothServiceData : public ProtoMessage {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
@@ -1286,6 +1310,32 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage {
 | 
				
			|||||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
					  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
				
			||||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
					  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					class BluetoothLERawAdvertisement : public ProtoMessage {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  uint64_t address{0};
 | 
				
			||||||
 | 
					  int32_t rssi{0};
 | 
				
			||||||
 | 
					  uint32_t address_type{0};
 | 
				
			||||||
 | 
					  std::string 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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					class BluetoothLERawAdvertisementsResponse : public ProtoMessage {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  std::vector<BluetoothLERawAdvertisement> advertisements{};
 | 
				
			||||||
 | 
					  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 BluetoothDeviceRequest : public ProtoMessage {
 | 
					class BluetoothDeviceRequest : public ProtoMessage {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  uint64_t address{0};
 | 
					  uint64_t address{0};
 | 
				
			||||||
@@ -1604,12 +1654,14 @@ class SubscribeVoiceAssistantRequest : public ProtoMessage {
 | 
				
			|||||||
class VoiceAssistantRequest : public ProtoMessage {
 | 
					class VoiceAssistantRequest : public ProtoMessage {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  bool start{false};
 | 
					  bool start{false};
 | 
				
			||||||
 | 
					  std::string conversation_id{};
 | 
				
			||||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
					  void encode(ProtoWriteBuffer buffer) const override;
 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
  void dump_to(std::string &out) const override;
 | 
					  void dump_to(std::string &out) const override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
				
			||||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
					  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
class VoiceAssistantResponse : public ProtoMessage {
 | 
					class VoiceAssistantResponse : public ProtoMessage {
 | 
				
			||||||
@@ -1649,6 +1701,56 @@ class VoiceAssistantEventResponse : public ProtoMessage {
 | 
				
			|||||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
					  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
				
			||||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
					  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  std::string object_id{};
 | 
				
			||||||
 | 
					  uint32_t key{0};
 | 
				
			||||||
 | 
					  std::string name{};
 | 
				
			||||||
 | 
					  std::string unique_id{};
 | 
				
			||||||
 | 
					  std::string icon{};
 | 
				
			||||||
 | 
					  bool disabled_by_default{false};
 | 
				
			||||||
 | 
					  enums::EntityCategory entity_category{};
 | 
				
			||||||
 | 
					  uint32_t supported_features{0};
 | 
				
			||||||
 | 
					  bool requires_code{false};
 | 
				
			||||||
 | 
					  bool requires_code_to_arm{false};
 | 
				
			||||||
 | 
					  void encode(ProtoWriteBuffer buffer) const override;
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					  void dump_to(std::string &out) const override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
				
			||||||
 | 
					  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
				
			||||||
 | 
					  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					class AlarmControlPanelStateResponse : public ProtoMessage {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  uint32_t key{0};
 | 
				
			||||||
 | 
					  enums::AlarmControlPanelState state{};
 | 
				
			||||||
 | 
					  void encode(ProtoWriteBuffer buffer) const override;
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					  void dump_to(std::string &out) const override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
				
			||||||
 | 
					  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					class AlarmControlPanelCommandRequest : public ProtoMessage {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  uint32_t key{0};
 | 
				
			||||||
 | 
					  enums::AlarmControlPanelStateCommand command{};
 | 
				
			||||||
 | 
					  std::string code{};
 | 
				
			||||||
 | 
					  void encode(ProtoWriteBuffer buffer) const override;
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					  void dump_to(std::string &out) const override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
				
			||||||
 | 
					  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
				
			||||||
 | 
					  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -339,6 +339,15 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
 | 
					bool APIServerConnectionBase::send_bluetooth_le_raw_advertisements_response(
 | 
				
			||||||
 | 
					    const BluetoothLERawAdvertisementsResponse &msg) {
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "send_bluetooth_le_raw_advertisements_response: %s", msg.dump().c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  return this->send_message_<BluetoothLERawAdvertisementsResponse>(msg, 93);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) {
 | 
					bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) {
 | 
				
			||||||
@@ -467,6 +476,25 @@ bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantR
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
 | 
				
			||||||
 | 
					    const ListEntitiesAlarmControlPanelResponse &msg) {
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "send_list_entities_alarm_control_panel_response: %s", msg.dump().c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  return this->send_message_<ListEntitiesAlarmControlPanelResponse>(msg, 94);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					bool APIServerConnectionBase::send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg) {
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "send_alarm_control_panel_state_response: %s", msg.dump().c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  return this->send_message_<AlarmControlPanelStateResponse>(msg, 95);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
					bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
				
			||||||
  switch (msg_type) {
 | 
					  switch (msg_type) {
 | 
				
			||||||
    case 1: {
 | 
					    case 1: {
 | 
				
			||||||
@@ -874,6 +902,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
				
			|||||||
      ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str());
 | 
					      ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str());
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
      this->on_voice_assistant_event_response(msg);
 | 
					      this->on_voice_assistant_event_response(msg);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case 96: {
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					      AlarmControlPanelCommandRequest msg;
 | 
				
			||||||
 | 
					      msg.decode(msg_data, msg_size);
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					      ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      this->on_alarm_control_panel_command_request(msg);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -1286,6 +1325,19 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
 | 
				
			|||||||
  this->subscribe_voice_assistant(msg);
 | 
					  this->subscribe_voice_assistant(msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
 | 
				
			||||||
 | 
					  if (!this->is_connection_setup()) {
 | 
				
			||||||
 | 
					    this->on_no_setup_connection();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!this->is_authenticated()) {
 | 
				
			||||||
 | 
					    this->on_unauthenticated_access();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->alarm_control_panel_command(msg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -161,6 +161,9 @@ class APIServerConnectionBase : public ProtoService {
 | 
				
			|||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
  bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
 | 
					  bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
 | 
					  bool send_bluetooth_le_raw_advertisements_response(const BluetoothLERawAdvertisementsResponse &msg);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
  virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
 | 
					  virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -236,6 +239,15 @@ class APIServerConnectionBase : public ProtoService {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
  virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
 | 
					  virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					  bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					  bool send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					  virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
					  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
				
			||||||
@@ -321,6 +333,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
  virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
 | 
					  virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					  virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  void on_hello_request(const HelloRequest &msg) override;
 | 
					  void on_hello_request(const HelloRequest &msg) override;
 | 
				
			||||||
@@ -402,6 +417,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
				
			|||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
  void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
 | 
					  void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					  void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -291,112 +291,7 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon
 | 
				
			|||||||
    client->send_homeassistant_service_call(call);
 | 
					    client->send_homeassistant_service_call(call);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					 | 
				
			||||||
void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) {
 | 
					 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_le_advertisement(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
 | 
					 | 
				
			||||||
  BluetoothDeviceConnectionResponse call;
 | 
					 | 
				
			||||||
  call.address = address;
 | 
					 | 
				
			||||||
  call.connected = connected;
 | 
					 | 
				
			||||||
  call.mtu = mtu;
 | 
					 | 
				
			||||||
  call.error = error;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_device_connection_response(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
  call.limit = limit;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_connections_free_response(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) {
 | 
					 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_gatt_read_response(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) {
 | 
					 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_gatt_write_response(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) {
 | 
					 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_gatt_notify_data_response(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) {
 | 
					 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_gatt_notify_response(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) {
 | 
					 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_gatt_get_services_response(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
 | 
					 | 
				
			||||||
  BluetoothGATTGetServicesDoneResponse call;
 | 
					 | 
				
			||||||
  call.address = address;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_gatt_get_services_done_response(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
 | 
					 | 
				
			||||||
  BluetoothGATTErrorResponse call;
 | 
					 | 
				
			||||||
  call.address = address;
 | 
					 | 
				
			||||||
  call.handle = handle;
 | 
					 | 
				
			||||||
  call.error = error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					 | 
				
			||||||
    client->send_bluetooth_gatt_error_response(call);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
APIServer::APIServer() { global_api_server = this; }
 | 
					APIServer::APIServer() { global_api_server = this; }
 | 
				
			||||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
					void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
				
			||||||
                                               std::function<void(std::string)> f) {
 | 
					                                               std::function<void(std::string)> f) {
 | 
				
			||||||
@@ -428,20 +323,29 @@ void APIServer::on_shutdown() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
bool APIServer::start_voice_assistant() {
 | 
					bool APIServer::start_voice_assistant(const std::string &conversation_id) {
 | 
				
			||||||
  for (auto &c : this->clients_) {
 | 
					  for (auto &c : this->clients_) {
 | 
				
			||||||
    if (c->request_voice_assistant(true))
 | 
					    if (c->request_voice_assistant(true, conversation_id))
 | 
				
			||||||
      return true;
 | 
					      return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return false;
 | 
					  return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APIServer::stop_voice_assistant() {
 | 
					void APIServer::stop_voice_assistant() {
 | 
				
			||||||
  for (auto &c : this->clients_) {
 | 
					  for (auto &c : this->clients_) {
 | 
				
			||||||
    if (c->request_voice_assistant(false))
 | 
					    if (c->request_voice_assistant(false, ""))
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
 | 
				
			||||||
 | 
					  if (obj->is_internal())
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  for (auto &c : this->clients_)
 | 
				
			||||||
 | 
					    c->send_alarm_control_panel_state(obj);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -75,31 +75,20 @@ class APIServer : public Component, public Controller {
 | 
				
			|||||||
  void on_media_player_update(media_player::MediaPlayer *obj) override;
 | 
					  void on_media_player_update(media_player::MediaPlayer *obj) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
 | 
					  void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
 | 
				
			||||||
#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);
 | 
					 | 
				
			||||||
  void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call);
 | 
					 | 
				
			||||||
  void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call);
 | 
					 | 
				
			||||||
  void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call);
 | 
					 | 
				
			||||||
  void send_bluetooth_gatt_services_done(uint64_t address);
 | 
					 | 
				
			||||||
  void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
  void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
 | 
					  void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
 | 
				
			||||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
					#ifdef USE_HOMEASSISTANT_TIME
 | 
				
			||||||
  void request_time();
 | 
					  void request_time();
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
  bool start_voice_assistant();
 | 
					  bool start_voice_assistant(const std::string &conversation_id);
 | 
				
			||||||
  void stop_voice_assistant();
 | 
					  void stop_voice_assistant();
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					  void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool is_connected() const;
 | 
					  bool is_connected() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  struct HomeAssistantStateSubscription {
 | 
					  struct HomeAssistantStateSubscription {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,6 +69,11 @@ bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_play
 | 
				
			|||||||
  return this->client_->send_media_player_info(media_player);
 | 
					  return this->client_->send_media_player_info(media_player);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
 | 
				
			||||||
 | 
					  return this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,9 @@ class ListEntitiesIterator : public ComponentIterator {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_MEDIA_PLAYER
 | 
					#ifdef USE_MEDIA_PLAYER
 | 
				
			||||||
  bool on_media_player(media_player::MediaPlayer *media_player) override;
 | 
					  bool on_media_player(media_player::MediaPlayer *media_player) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					  bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  bool on_end() override;
 | 
					  bool on_end() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,6 +55,11 @@ bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_play
 | 
				
			|||||||
  return this->client_->send_media_player_state(media_player);
 | 
					  return this->client_->send_media_player_state(media_player);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
 | 
				
			||||||
 | 
					  return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
 | 
					InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,6 +51,9 @@ class InitialStateIterator : public ComponentIterator {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_MEDIA_PLAYER
 | 
					#ifdef USE_MEDIA_PLAYER
 | 
				
			||||||
  bool on_media_player(media_player::MediaPlayer *media_player) override;
 | 
					  bool on_media_player(media_player::MediaPlayer *media_player) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
 | 
					  bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  APIConnection *client_;
 | 
					  APIConnection *client_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -442,7 +442,7 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
 | 
				
			|||||||
void BedJetHub::send_local_time() {
 | 
					void BedJetHub::send_local_time() {
 | 
				
			||||||
  if (this->time_id_.has_value()) {
 | 
					  if (this->time_id_.has_value()) {
 | 
				
			||||||
    auto *time_id = *this->time_id_;
 | 
					    auto *time_id = *this->time_id_;
 | 
				
			||||||
    time::ESPTime now = time_id->now();
 | 
					    ESPTime now = time_id->now();
 | 
				
			||||||
    if (now.is_valid()) {
 | 
					    if (now.is_valid()) {
 | 
				
			||||||
      this->set_clock(now.hour, now.minute);
 | 
					      this->set_clock(now.hour, now.minute);
 | 
				
			||||||
      ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
 | 
					      ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#ifdef USE_TIME
 | 
					#ifdef USE_TIME
 | 
				
			||||||
#include "esphome/components/time/real_time_clock.h"
 | 
					#include "esphome/components/time/real_time_clock.h"
 | 
				
			||||||
 | 
					#include "esphome/core/time.h"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <esp_gattc_api.h>
 | 
					#include <esp_gattc_api.h>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_IBEACON_MAJOR,
 | 
					    CONF_IBEACON_MAJOR,
 | 
				
			||||||
    CONF_IBEACON_MINOR,
 | 
					    CONF_IBEACON_MINOR,
 | 
				
			||||||
    CONF_IBEACON_UUID,
 | 
					    CONF_IBEACON_UUID,
 | 
				
			||||||
 | 
					    CONF_MIN_RSSI,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
					DEPENDENCIES = ["esp32_ble_tracker"]
 | 
				
			||||||
@@ -37,6 +38,9 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
            cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
 | 
					            cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
 | 
				
			||||||
            cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
 | 
					            cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
 | 
				
			||||||
            cv.Optional(CONF_IBEACON_UUID): cv.uuid,
 | 
					            cv.Optional(CONF_IBEACON_UUID): cv.uuid,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_MIN_RSSI): cv.All(
 | 
				
			||||||
 | 
					                cv.decibel, cv.int_range(min=-90, max=-30)
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
					    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
				
			||||||
@@ -51,6 +55,9 @@ async def to_code(config):
 | 
				
			|||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
    await esp32_ble_tracker.register_ble_device(var, config)
 | 
					    await esp32_ble_tracker.register_ble_device(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_MIN_RSSI in config:
 | 
				
			||||||
 | 
					        cg.add(var.set_minimum_rssi(config[CONF_MIN_RSSI]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if CONF_MAC_ADDRESS in config:
 | 
					    if CONF_MAC_ADDRESS in config:
 | 
				
			||||||
        cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
 | 
					        cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,12 +41,19 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
 | 
				
			|||||||
    this->check_ibeacon_minor_ = true;
 | 
					    this->check_ibeacon_minor_ = true;
 | 
				
			||||||
    this->ibeacon_minor_ = minor;
 | 
					    this->ibeacon_minor_ = minor;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  void set_minimum_rssi(int rssi) {
 | 
				
			||||||
 | 
					    this->check_minimum_rssi_ = true;
 | 
				
			||||||
 | 
					    this->minimum_rssi_ = rssi;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  void on_scan_end() override {
 | 
					  void on_scan_end() override {
 | 
				
			||||||
    if (!this->found_)
 | 
					    if (!this->found_)
 | 
				
			||||||
      this->publish_state(false);
 | 
					      this->publish_state(false);
 | 
				
			||||||
    this->found_ = false;
 | 
					    this->found_ = false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
 | 
					  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
 | 
				
			||||||
 | 
					    if (this->check_minimum_rssi_ && this->minimum_rssi_ <= device.get_rssi()) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    switch (this->match_by_) {
 | 
					    switch (this->match_by_) {
 | 
				
			||||||
      case MATCH_BY_MAC_ADDRESS:
 | 
					      case MATCH_BY_MAC_ADDRESS:
 | 
				
			||||||
        if (device.address_uint64() == this->address_) {
 | 
					        if (device.address_uint64() == this->address_) {
 | 
				
			||||||
@@ -96,17 +103,21 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
 | 
				
			|||||||
  enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
 | 
					  enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
 | 
				
			||||||
  MatchType match_by_;
 | 
					  MatchType match_by_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool found_{false};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  uint64_t address_;
 | 
					  uint64_t address_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  esp32_ble_tracker::ESPBTUUID uuid_;
 | 
					  esp32_ble_tracker::ESPBTUUID uuid_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  esp32_ble_tracker::ESPBTUUID ibeacon_uuid_;
 | 
					  esp32_ble_tracker::ESPBTUUID ibeacon_uuid_;
 | 
				
			||||||
  uint16_t ibeacon_major_;
 | 
					  uint16_t ibeacon_major_{0};
 | 
				
			||||||
  bool check_ibeacon_major_;
 | 
					  uint16_t ibeacon_minor_{0};
 | 
				
			||||||
  uint16_t ibeacon_minor_;
 | 
					
 | 
				
			||||||
  bool check_ibeacon_minor_;
 | 
					  int minimum_rssi_{0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool check_ibeacon_major_{false};
 | 
				
			||||||
 | 
					  bool check_ibeacon_minor_{false};
 | 
				
			||||||
 | 
					  bool check_minimum_rssi_{false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool found_{false};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace ble_presence
 | 
					}  // namespace ble_presence
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -102,8 +102,9 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  esp32_ble_tracker::ESPBTUUID ibeacon_uuid_;
 | 
					  esp32_ble_tracker::ESPBTUUID ibeacon_uuid_;
 | 
				
			||||||
  uint16_t ibeacon_major_;
 | 
					  uint16_t ibeacon_major_;
 | 
				
			||||||
  bool check_ibeacon_major_;
 | 
					 | 
				
			||||||
  uint16_t ibeacon_minor_;
 | 
					  uint16_t ibeacon_minor_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool check_ibeacon_major_;
 | 
				
			||||||
  bool check_ibeacon_minor_;
 | 
					  bool check_ibeacon_minor_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
#include "bluetooth_connection.h"
 | 
					#include "bluetooth_connection.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/components/api/api_server.h"
 | 
					#include "esphome/components/api/api_pb2.h"
 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,24 +20,21 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  switch (event) {
 | 
					  switch (event) {
 | 
				
			||||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
					    case ESP_GATTC_DISCONNECT_EVT: {
 | 
				
			||||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->disconnect.reason);
 | 
					      this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
 | 
				
			||||||
      this->set_address(0);
 | 
					      this->set_address(0);
 | 
				
			||||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
					      this->proxy_->send_connections_free();
 | 
				
			||||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
					 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ESP_GATTC_OPEN_EVT: {
 | 
					    case ESP_GATTC_OPEN_EVT: {
 | 
				
			||||||
      if (param->open.conn_id != this->conn_id_)
 | 
					      if (param->open.conn_id != this->conn_id_)
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
					      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
				
			||||||
        api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->open.status);
 | 
					        this->proxy_->send_device_connection(this->address_, false, 0, param->open.status);
 | 
				
			||||||
        this->set_address(0);
 | 
					        this->set_address(0);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
					        this->proxy_->send_connections_free();
 | 
				
			||||||
                                                                this->proxy_->get_bluetooth_connections_limit());
 | 
					 | 
				
			||||||
      } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
 | 
					      } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
 | 
				
			||||||
        api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
					        this->proxy_->send_device_connection(this->address_, true, this->mtu_);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
					        this->proxy_->send_connections_free();
 | 
				
			||||||
                                                                this->proxy_->get_bluetooth_connections_limit());
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this->seen_mtu_or_services_ = false;
 | 
					      this->seen_mtu_or_services_ = false;
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
@@ -52,9 +49,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
				
			|||||||
        this->seen_mtu_or_services_ = true;
 | 
					        this->seen_mtu_or_services_ = true;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
					      this->proxy_->send_device_connection(this->address_, true, this->mtu_);
 | 
				
			||||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
					      this->proxy_->send_connections_free();
 | 
				
			||||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
					 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
					    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
				
			||||||
@@ -67,9 +63,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
				
			|||||||
        this->seen_mtu_or_services_ = true;
 | 
					        this->seen_mtu_or_services_ = true;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
					      this->proxy_->send_device_connection(this->address_, true, this->mtu_);
 | 
				
			||||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
					      this->proxy_->send_connections_free();
 | 
				
			||||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
					 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ESP_GATTC_READ_DESCR_EVT:
 | 
					    case ESP_GATTC_READ_DESCR_EVT:
 | 
				
			||||||
@@ -79,7 +74,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
				
			|||||||
      if (param->read.status != ESP_GATT_OK) {
 | 
					      if (param->read.status != ESP_GATT_OK) {
 | 
				
			||||||
        ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
 | 
					        ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
 | 
				
			||||||
                 this->address_str_.c_str(), param->read.handle, param->read.status);
 | 
					                 this->address_str_.c_str(), param->read.handle, param->read.status);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status);
 | 
					        this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      api::BluetoothGATTReadResponse resp;
 | 
					      api::BluetoothGATTReadResponse resp;
 | 
				
			||||||
@@ -89,7 +84,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
				
			|||||||
      for (uint16_t i = 0; i < param->read.value_len; i++) {
 | 
					      for (uint16_t i = 0; i < param->read.value_len; i++) {
 | 
				
			||||||
        resp.data.push_back(param->read.value[i]);
 | 
					        resp.data.push_back(param->read.value[i]);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      api::global_api_server->send_bluetooth_gatt_read_response(resp);
 | 
					      this->proxy_->get_api_connection()->send_bluetooth_gatt_read_response(resp);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ESP_GATTC_WRITE_CHAR_EVT:
 | 
					    case ESP_GATTC_WRITE_CHAR_EVT:
 | 
				
			||||||
@@ -99,13 +94,13 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
				
			|||||||
      if (param->write.status != ESP_GATT_OK) {
 | 
					      if (param->write.status != ESP_GATT_OK) {
 | 
				
			||||||
        ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
 | 
					        ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
 | 
				
			||||||
                 this->address_str_.c_str(), param->write.handle, param->write.status);
 | 
					                 this->address_str_.c_str(), param->write.handle, param->write.status);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status);
 | 
					        this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      api::BluetoothGATTWriteResponse resp;
 | 
					      api::BluetoothGATTWriteResponse resp;
 | 
				
			||||||
      resp.address = this->address_;
 | 
					      resp.address = this->address_;
 | 
				
			||||||
      resp.handle = param->write.handle;
 | 
					      resp.handle = param->write.handle;
 | 
				
			||||||
      api::global_api_server->send_bluetooth_gatt_write_response(resp);
 | 
					      this->proxy_->get_api_connection()->send_bluetooth_gatt_write_response(resp);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
 | 
					    case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
 | 
				
			||||||
@@ -113,28 +108,26 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
				
			|||||||
        ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
 | 
					        ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
 | 
				
			||||||
                 this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
 | 
					                 this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
 | 
				
			||||||
                 param->unreg_for_notify.status);
 | 
					                 param->unreg_for_notify.status);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle,
 | 
					        this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
 | 
				
			||||||
                                                          param->unreg_for_notify.status);
 | 
					 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      api::BluetoothGATTNotifyResponse resp;
 | 
					      api::BluetoothGATTNotifyResponse resp;
 | 
				
			||||||
      resp.address = this->address_;
 | 
					      resp.address = this->address_;
 | 
				
			||||||
      resp.handle = param->unreg_for_notify.handle;
 | 
					      resp.handle = param->unreg_for_notify.handle;
 | 
				
			||||||
      api::global_api_server->send_bluetooth_gatt_notify_response(resp);
 | 
					      this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
					    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
				
			||||||
      if (param->reg_for_notify.status != ESP_GATT_OK) {
 | 
					      if (param->reg_for_notify.status != ESP_GATT_OK) {
 | 
				
			||||||
        ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
 | 
					        ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
 | 
				
			||||||
                 this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
 | 
					                 this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle,
 | 
					        this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
 | 
				
			||||||
                                                          param->reg_for_notify.status);
 | 
					 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      api::BluetoothGATTNotifyResponse resp;
 | 
					      api::BluetoothGATTNotifyResponse resp;
 | 
				
			||||||
      resp.address = this->address_;
 | 
					      resp.address = this->address_;
 | 
				
			||||||
      resp.handle = param->reg_for_notify.handle;
 | 
					      resp.handle = param->reg_for_notify.handle;
 | 
				
			||||||
      api::global_api_server->send_bluetooth_gatt_notify_response(resp);
 | 
					      this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
					    case ESP_GATTC_NOTIFY_EVT: {
 | 
				
			||||||
@@ -149,7 +142,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
				
			|||||||
      for (uint16_t i = 0; i < param->notify.value_len; i++) {
 | 
					      for (uint16_t i = 0; i < param->notify.value_len; i++) {
 | 
				
			||||||
        resp.data.push_back(param->notify.value[i]);
 | 
					        resp.data.push_back(param->notify.value[i]);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      api::global_api_server->send_bluetooth_gatt_notify_data_response(resp);
 | 
					      this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_data_response(resp);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
@@ -166,10 +159,9 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
 | 
				
			|||||||
      if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
 | 
					      if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      if (param->ble_security.auth_cmpl.success) {
 | 
					      if (param->ble_security.auth_cmpl.success) {
 | 
				
			||||||
        api::global_api_server->send_bluetooth_device_pairing(this->address_, true);
 | 
					        this->proxy_->send_device_pairing(this->address_, true);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        api::global_api_server->send_bluetooth_device_pairing(this->address_, false,
 | 
					        this->proxy_->send_device_pairing(this->address_, false, param->ble_security.auth_cmpl.fail_reason);
 | 
				
			||||||
                                                              param->ble_security.auth_cmpl.fail_reason);
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,10 @@
 | 
				
			|||||||
#include "bluetooth_proxy.h"
 | 
					#include "bluetooth_proxy.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/macros.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/components/api/api_server.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace bluetooth_proxy {
 | 
					namespace bluetooth_proxy {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,15 +26,39 @@ std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
 | 
				
			|||||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
 | 
					BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
					bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
				
			||||||
  if (!api::global_api_server->is_connected())
 | 
					  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
 | 
					  ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
 | 
				
			||||||
           device.get_rssi());
 | 
					           device.get_rssi());
 | 
				
			||||||
  this->send_api_packet_(device);
 | 
					  this->send_api_packet_(device);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
 | 
				
			||||||
 | 
					  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  api::BluetoothLERawAdvertisementsResponse resp;
 | 
				
			||||||
 | 
					  for (size_t i = 0; i < count; i++) {
 | 
				
			||||||
 | 
					    auto &result = advertisements[i];
 | 
				
			||||||
 | 
					    api::BluetoothLERawAdvertisement adv;
 | 
				
			||||||
 | 
					    adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
 | 
				
			||||||
 | 
					    adv.rssi = result.rssi;
 | 
				
			||||||
 | 
					    adv.address_type = result.ble_addr_type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t length = result.adv_data_len + result.scan_rsp_len;
 | 
				
			||||||
 | 
					    adv.data.reserve(length);
 | 
				
			||||||
 | 
					    for (uint16_t i = 0; i < length; i++) {
 | 
				
			||||||
 | 
					      adv.data.push_back(result.ble_adv[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resp.advertisements.push_back(std::move(adv));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "Proxying %d packets", count);
 | 
				
			||||||
 | 
					  this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
					void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
				
			||||||
  api::BluetoothLEAdvertisementResponse resp;
 | 
					  api::BluetoothLEAdvertisementResponse resp;
 | 
				
			||||||
  resp.address = device.address_uint64();
 | 
					  resp.address = device.address_uint64();
 | 
				
			||||||
@@ -58,7 +81,7 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
 | 
				
			|||||||
    manufacturer_data.data.assign(data.data.begin(), data.data.end());
 | 
					    manufacturer_data.data.assign(data.data.begin(), data.data.end());
 | 
				
			||||||
    resp.manufacturer_data.push_back(std::move(manufacturer_data));
 | 
					    resp.manufacturer_data.push_back(std::move(manufacturer_data));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  api::global_api_server->send_bluetooth_le_advertisement(resp);
 | 
					  this->api_connection_->send_bluetooth_le_advertisement(resp);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BluetoothProxy::dump_config() {
 | 
					void BluetoothProxy::dump_config() {
 | 
				
			||||||
@@ -81,7 +104,7 @@ int BluetoothProxy::get_bluetooth_connections_free() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BluetoothProxy::loop() {
 | 
					void BluetoothProxy::loop() {
 | 
				
			||||||
  if (!api::global_api_server->is_connected()) {
 | 
					  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
 | 
				
			||||||
    for (auto *connection : this->connections_) {
 | 
					    for (auto *connection : this->connections_) {
 | 
				
			||||||
      if (connection->get_address() != 0) {
 | 
					      if (connection->get_address() != 0) {
 | 
				
			||||||
        connection->disconnect();
 | 
					        connection->disconnect();
 | 
				
			||||||
@@ -92,7 +115,7 @@ void BluetoothProxy::loop() {
 | 
				
			|||||||
  for (auto *connection : this->connections_) {
 | 
					  for (auto *connection : this->connections_) {
 | 
				
			||||||
    if (connection->send_service_ == connection->service_count_) {
 | 
					    if (connection->send_service_ == connection->service_count_) {
 | 
				
			||||||
      connection->send_service_ = DONE_SENDING_SERVICES;
 | 
					      connection->send_service_ = DONE_SENDING_SERVICES;
 | 
				
			||||||
      api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address());
 | 
					      this->send_gatt_services_done(connection->get_address());
 | 
				
			||||||
      if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
 | 
					      if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
 | 
				
			||||||
          connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
 | 
					          connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
 | 
				
			||||||
        connection->release_services();
 | 
					        connection->release_services();
 | 
				
			||||||
@@ -170,7 +193,7 @@ void BluetoothProxy::loop() {
 | 
				
			|||||||
        service_resp.characteristics.push_back(std::move(characteristic_resp));
 | 
					        service_resp.characteristics.push_back(std::move(characteristic_resp));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      resp.services.push_back(std::move(service_resp));
 | 
					      resp.services.push_back(std::move(service_resp));
 | 
				
			||||||
      api::global_api_server->send_bluetooth_gatt_services(resp);
 | 
					      this->api_connection_->send_bluetooth_gatt_get_services_response(resp);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -208,16 +231,15 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
				
			|||||||
      auto *connection = this->get_connection_(msg.address, true);
 | 
					      auto *connection = this->get_connection_(msg.address, true);
 | 
				
			||||||
      if (connection == nullptr) {
 | 
					      if (connection == nullptr) {
 | 
				
			||||||
        ESP_LOGW(TAG, "No free connections available");
 | 
					        ESP_LOGW(TAG, "No free connections available");
 | 
				
			||||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
					        this->send_device_connection(msg.address, false);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (connection->state() == espbt::ClientState::CONNECTED ||
 | 
					      if (connection->state() == espbt::ClientState::CONNECTED ||
 | 
				
			||||||
          connection->state() == espbt::ClientState::ESTABLISHED) {
 | 
					          connection->state() == espbt::ClientState::ESTABLISHED) {
 | 
				
			||||||
        ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(),
 | 
					        ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(),
 | 
				
			||||||
                 connection->address_str().c_str());
 | 
					                 connection->address_str().c_str());
 | 
				
			||||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, true);
 | 
					        this->send_device_connection(msg.address, true);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
					        this->send_connections_free();
 | 
				
			||||||
                                                                this->get_bluetooth_connections_limit());
 | 
					 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      } else if (connection->state() == espbt::ClientState::SEARCHING) {
 | 
					      } else if (connection->state() == espbt::ClientState::SEARCHING) {
 | 
				
			||||||
        ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device",
 | 
					        ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device",
 | 
				
			||||||
@@ -263,25 +285,22 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
				
			|||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        connection->set_state(espbt::ClientState::SEARCHING);
 | 
					        connection->set_state(espbt::ClientState::SEARCHING);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
					      this->send_connections_free();
 | 
				
			||||||
                                                              this->get_bluetooth_connections_limit());
 | 
					 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
 | 
					    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
 | 
				
			||||||
      auto *connection = this->get_connection_(msg.address, false);
 | 
					      auto *connection = this->get_connection_(msg.address, false);
 | 
				
			||||||
      if (connection == nullptr) {
 | 
					      if (connection == nullptr) {
 | 
				
			||||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
					        this->send_device_connection(msg.address, false);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
					        this->send_connections_free();
 | 
				
			||||||
                                                                this->get_bluetooth_connections_limit());
 | 
					 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (connection->state() != espbt::ClientState::IDLE) {
 | 
					      if (connection->state() != espbt::ClientState::IDLE) {
 | 
				
			||||||
        connection->disconnect();
 | 
					        connection->disconnect();
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        connection->set_address(0);
 | 
					        connection->set_address(0);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
					        this->send_device_connection(msg.address, false);
 | 
				
			||||||
        api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
					        this->send_connections_free();
 | 
				
			||||||
                                                                this->get_bluetooth_connections_limit());
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -291,10 +310,10 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
				
			|||||||
        if (!connection->is_paired()) {
 | 
					        if (!connection->is_paired()) {
 | 
				
			||||||
          auto err = connection->pair();
 | 
					          auto err = connection->pair();
 | 
				
			||||||
          if (err != ESP_OK) {
 | 
					          if (err != ESP_OK) {
 | 
				
			||||||
            api::global_api_server->send_bluetooth_device_pairing(msg.address, false, err);
 | 
					            this->send_device_pairing(msg.address, false, err);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          api::global_api_server->send_bluetooth_device_pairing(msg.address, true);
 | 
					          this->send_device_pairing(msg.address, true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
@@ -303,14 +322,20 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
				
			|||||||
      esp_bd_addr_t address;
 | 
					      esp_bd_addr_t address;
 | 
				
			||||||
      uint64_to_bd_addr(msg.address, address);
 | 
					      uint64_to_bd_addr(msg.address, address);
 | 
				
			||||||
      esp_err_t ret = esp_ble_remove_bond_device(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);
 | 
					      this->send_device_pairing(msg.address, ret == ESP_OK, ret);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: {
 | 
					    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: {
 | 
				
			||||||
      esp_bd_addr_t address;
 | 
					      esp_bd_addr_t address;
 | 
				
			||||||
      uint64_to_bd_addr(msg.address, address);
 | 
					      uint64_to_bd_addr(msg.address, address);
 | 
				
			||||||
      esp_err_t ret = esp_ble_gattc_cache_clean(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);
 | 
					      api::BluetoothDeviceClearCacheResponse call;
 | 
				
			||||||
 | 
					      call.address = msg.address;
 | 
				
			||||||
 | 
					      call.success = ret == ESP_OK;
 | 
				
			||||||
 | 
					      call.error = ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this->api_connection_->send_bluetooth_device_clear_cache_response(call);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -320,13 +345,13 @@ void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &ms
 | 
				
			|||||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
					  auto *connection = this->get_connection_(msg.address, false);
 | 
				
			||||||
  if (connection == nullptr) {
 | 
					  if (connection == nullptr) {
 | 
				
			||||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
 | 
					    ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
					    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  auto err = connection->read_characteristic(msg.handle);
 | 
					  auto err = connection->read_characteristic(msg.handle);
 | 
				
			||||||
  if (err != ESP_OK) {
 | 
					  if (err != ESP_OK) {
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
					    this->send_gatt_error(msg.address, msg.handle, err);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -334,13 +359,13 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
 | 
				
			|||||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
					  auto *connection = this->get_connection_(msg.address, false);
 | 
				
			||||||
  if (connection == nullptr) {
 | 
					  if (connection == nullptr) {
 | 
				
			||||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
 | 
					    ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
					    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
 | 
					  auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
 | 
				
			||||||
  if (err != ESP_OK) {
 | 
					  if (err != ESP_OK) {
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
					    this->send_gatt_error(msg.address, msg.handle, err);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -348,13 +373,13 @@ void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTRead
 | 
				
			|||||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
					  auto *connection = this->get_connection_(msg.address, false);
 | 
				
			||||||
  if (connection == nullptr) {
 | 
					  if (connection == nullptr) {
 | 
				
			||||||
    ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
 | 
					    ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
					    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  auto err = connection->read_descriptor(msg.handle);
 | 
					  auto err = connection->read_descriptor(msg.handle);
 | 
				
			||||||
  if (err != ESP_OK) {
 | 
					  if (err != ESP_OK) {
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
					    this->send_gatt_error(msg.address, msg.handle, err);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -362,13 +387,13 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
 | 
				
			|||||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
					  auto *connection = this->get_connection_(msg.address, false);
 | 
				
			||||||
  if (connection == nullptr) {
 | 
					  if (connection == nullptr) {
 | 
				
			||||||
    ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
 | 
					    ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
					    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  auto err = connection->write_descriptor(msg.handle, msg.data, true);
 | 
					  auto err = connection->write_descriptor(msg.handle, msg.data, true);
 | 
				
			||||||
  if (err != ESP_OK) {
 | 
					  if (err != ESP_OK) {
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
					    this->send_gatt_error(msg.address, msg.handle, err);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -376,12 +401,12 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer
 | 
				
			|||||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
					  auto *connection = this->get_connection_(msg.address, false);
 | 
				
			||||||
  if (connection == nullptr || !connection->connected()) {
 | 
					  if (connection == nullptr || !connection->connected()) {
 | 
				
			||||||
    ESP_LOGW(TAG, "Cannot get GATT services, not connected");
 | 
					    ESP_LOGW(TAG, "Cannot get GATT services, not connected");
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
 | 
					    this->send_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (!connection->service_count_) {
 | 
					  if (!connection->service_count_) {
 | 
				
			||||||
    ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
 | 
					    ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_services_done(msg.address);
 | 
					    this->send_gatt_services_done(msg.address);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (connection->send_service_ ==
 | 
					  if (connection->send_service_ ==
 | 
				
			||||||
@@ -393,16 +418,89 @@ void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest
 | 
				
			|||||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
					  auto *connection = this->get_connection_(msg.address, false);
 | 
				
			||||||
  if (connection == nullptr) {
 | 
					  if (connection == nullptr) {
 | 
				
			||||||
    ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
 | 
					    ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
					    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  auto err = connection->notify_characteristic(msg.handle, msg.enable);
 | 
					  auto err = connection->notify_characteristic(msg.handle, msg.enable);
 | 
				
			||||||
  if (err != ESP_OK) {
 | 
					  if (err != ESP_OK) {
 | 
				
			||||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
					    this->send_gatt_error(msg.address, msg.handle, err);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) {
 | 
				
			||||||
 | 
					  if (this->api_connection_ != nullptr) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->api_connection_ = api_connection;
 | 
				
			||||||
 | 
					  this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
 | 
				
			||||||
 | 
					  if (this->api_connection_ != api_connection) {
 | 
				
			||||||
 | 
					    ESP_LOGV(TAG, "API connection is not subscribed");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->api_connection_ = nullptr;
 | 
				
			||||||
 | 
					  this->raw_advertisements_ = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
 | 
				
			||||||
 | 
					  if (this->api_connection_ == nullptr)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  api::BluetoothDeviceConnectionResponse call;
 | 
				
			||||||
 | 
					  call.address = address;
 | 
				
			||||||
 | 
					  call.connected = connected;
 | 
				
			||||||
 | 
					  call.mtu = mtu;
 | 
				
			||||||
 | 
					  call.error = error;
 | 
				
			||||||
 | 
					  this->api_connection_->send_bluetooth_device_connection_response(call);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void BluetoothProxy::send_connections_free() {
 | 
				
			||||||
 | 
					  if (this->api_connection_ == nullptr)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  api::BluetoothConnectionsFreeResponse call;
 | 
				
			||||||
 | 
					  call.free = this->get_bluetooth_connections_free();
 | 
				
			||||||
 | 
					  call.limit = this->get_bluetooth_connections_limit();
 | 
				
			||||||
 | 
					  this->api_connection_->send_bluetooth_connections_free_response(call);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothProxy::send_gatt_services_done(uint64_t address) {
 | 
				
			||||||
 | 
					  if (this->api_connection_ == nullptr)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  api::BluetoothGATTGetServicesDoneResponse call;
 | 
				
			||||||
 | 
					  call.address = address;
 | 
				
			||||||
 | 
					  this->api_connection_->send_bluetooth_gatt_get_services_done_response(call);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
 | 
				
			||||||
 | 
					  if (this->api_connection_ == nullptr)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  api::BluetoothGATTErrorResponse call;
 | 
				
			||||||
 | 
					  call.address = address;
 | 
				
			||||||
 | 
					  call.handle = handle;
 | 
				
			||||||
 | 
					  call.error = error;
 | 
				
			||||||
 | 
					  this->api_connection_->send_bluetooth_gatt_error_response(call);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) {
 | 
				
			||||||
 | 
					  api::BluetoothDevicePairingResponse call;
 | 
				
			||||||
 | 
					  call.address = address;
 | 
				
			||||||
 | 
					  call.paired = paired;
 | 
				
			||||||
 | 
					  call.error = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->api_connection_->send_bluetooth_device_pairing_response(call);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) {
 | 
				
			||||||
 | 
					  api::BluetoothDeviceUnpairingResponse call;
 | 
				
			||||||
 | 
					  call.address = address;
 | 
				
			||||||
 | 
					  call.success = success;
 | 
				
			||||||
 | 
					  call.error = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->api_connection_->send_bluetooth_device_unpairing_response(call);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BluetoothProxy *global_bluetooth_proxy = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
					BluetoothProxy *global_bluetooth_proxy = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace bluetooth_proxy
 | 
					}  // namespace bluetooth_proxy
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@
 | 
				
			|||||||
#include <map>
 | 
					#include <map>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/components/api/api_connection.h"
 | 
				
			||||||
#include "esphome/components/api/api_pb2.h"
 | 
					#include "esphome/components/api/api_pb2.h"
 | 
				
			||||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
 | 
					#include "esphome/components/esp32_ble_client/ble_client_base.h"
 | 
				
			||||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
					#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
				
			||||||
@@ -21,10 +22,33 @@ static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace esp32_ble_client;
 | 
					using namespace esp32_ble_client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Legacy versions:
 | 
				
			||||||
 | 
					// 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 LEGACY_ACTIVE_CONNECTIONS_VERSION = 5;
 | 
				
			||||||
 | 
					static const uint32_t LEGACY_PASSIVE_ONLY_VERSION = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BluetoothProxyFeature : uint32_t {
 | 
				
			||||||
 | 
					  FEATURE_PASSIVE_SCAN = 1 << 0,
 | 
				
			||||||
 | 
					  FEATURE_ACTIVE_CONNECTIONS = 1 << 1,
 | 
				
			||||||
 | 
					  FEATURE_REMOTE_CACHING = 1 << 2,
 | 
				
			||||||
 | 
					  FEATURE_PAIRING = 1 << 3,
 | 
				
			||||||
 | 
					  FEATURE_CACHE_CLEARING = 1 << 4,
 | 
				
			||||||
 | 
					  FEATURE_RAW_ADVERTISEMENTS = 1 << 5,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BluetoothProxySubscriptionFlag : uint32_t {
 | 
				
			||||||
 | 
					  SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
 | 
					class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  BluetoothProxy();
 | 
					  BluetoothProxy();
 | 
				
			||||||
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
 | 
					  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
 | 
				
			||||||
 | 
					  bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
  void loop() override;
 | 
					  void loop() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,6 +68,18 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
				
			|||||||
  int get_bluetooth_connections_free();
 | 
					  int get_bluetooth_connections_free();
 | 
				
			||||||
  int get_bluetooth_connections_limit() { return this->connections_.size(); }
 | 
					  int get_bluetooth_connections_limit() { return this->connections_.size(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags);
 | 
				
			||||||
 | 
					  void unsubscribe_api_connection(api::APIConnection *api_connection);
 | 
				
			||||||
 | 
					  api::APIConnection *get_api_connection() { return this->api_connection_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void send_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
 | 
				
			||||||
 | 
					  void send_connections_free();
 | 
				
			||||||
 | 
					  void send_gatt_services_done(uint64_t address);
 | 
				
			||||||
 | 
					  void send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
 | 
				
			||||||
 | 
					  void send_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
 | 
				
			||||||
 | 
					  void send_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
				
			||||||
 | 
					  void send_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
 | 
					  static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
 | 
				
			||||||
    bd_addr[0] = (address >> 40) & 0xff;
 | 
					    bd_addr[0] = (address >> 40) & 0xff;
 | 
				
			||||||
    bd_addr[1] = (address >> 32) & 0xff;
 | 
					    bd_addr[1] = (address >> 32) & 0xff;
 | 
				
			||||||
@@ -56,6 +92,27 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
				
			|||||||
  void set_active(bool active) { this->active_ = active; }
 | 
					  void set_active(bool active) { this->active_ = active; }
 | 
				
			||||||
  bool has_active() { return this->active_; }
 | 
					  bool has_active() { return this->active_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32_t get_legacy_version() const {
 | 
				
			||||||
 | 
					    if (this->active_) {
 | 
				
			||||||
 | 
					      return LEGACY_ACTIVE_CONNECTIONS_VERSION;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return LEGACY_PASSIVE_ONLY_VERSION;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32_t get_feature_flags() const {
 | 
				
			||||||
 | 
					    uint32_t flags = 0;
 | 
				
			||||||
 | 
					    flags |= BluetoothProxyFeature::FEATURE_PASSIVE_SCAN;
 | 
				
			||||||
 | 
					    flags |= BluetoothProxyFeature::FEATURE_RAW_ADVERTISEMENTS;
 | 
				
			||||||
 | 
					    if (this->active_) {
 | 
				
			||||||
 | 
					      flags |= BluetoothProxyFeature::FEATURE_ACTIVE_CONNECTIONS;
 | 
				
			||||||
 | 
					      flags |= BluetoothProxyFeature::FEATURE_REMOTE_CACHING;
 | 
				
			||||||
 | 
					      flags |= BluetoothProxyFeature::FEATURE_PAIRING;
 | 
				
			||||||
 | 
					      flags |= BluetoothProxyFeature::FEATURE_CACHE_CLEARING;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return flags;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
 | 
					  void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,18 +121,12 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
				
			|||||||
  bool active_;
 | 
					  bool active_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::vector<BluetoothConnection *> connections_{};
 | 
					  std::vector<BluetoothConnection *> connections_{};
 | 
				
			||||||
 | 
					  api::APIConnection *api_connection_{nullptr};
 | 
				
			||||||
 | 
					  bool raw_advertisements_{false};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
					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 bluetooth_proxy
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -94,3 +94,5 @@ async def to_code(config):
 | 
				
			|||||||
        sens = await sensor.new_sensor(conf)
 | 
					        sens = await sensor.new_sensor(conf)
 | 
				
			||||||
        cg.add(var.set_pressure_sensor(sens))
 | 
					        cg.add(var.set_pressure_sensor(sens))
 | 
				
			||||||
        cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
 | 
					        cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -140,8 +140,9 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
 | 
				
			|||||||
  /** Stop the cover.
 | 
					  /** Stop the cover.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * This is a legacy method and may be removed later, please use `.make_call()` instead.
 | 
					   * This is a legacy method and may be removed later, please use `.make_call()` instead.
 | 
				
			||||||
 | 
					   * As per solution from issue #2885 the call should include perform()
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop() instead.", "2021.9")
 | 
					  ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop().perform() instead.", "2021.9")
 | 
				
			||||||
  void stop();
 | 
					  void stop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void add_on_state_callback(std::function<void()> &&f);
 | 
					  void add_on_state_callback(std::function<void()> &&f);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,10 @@ import requests
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					import esphome.final_validate as fv
 | 
				
			||||||
from esphome import git
 | 
					from esphome import git
 | 
				
			||||||
from esphome.components.packages import validate_source_shorthand
 | 
					from esphome.components.packages import validate_source_shorthand
 | 
				
			||||||
from esphome.const import CONF_REF, CONF_WIFI
 | 
					from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT
 | 
				
			||||||
from esphome.wizard import wizard_file
 | 
					from esphome.wizard import wizard_file
 | 
				
			||||||
from esphome.yaml_util import dump
 | 
					from esphome.yaml_util import dump
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,6 +53,17 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
    validate_full_url,
 | 
					    validate_full_url,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _final_validate(config):
 | 
				
			||||||
 | 
					    full_config = fv.full_config.get()[CONF_ESPHOME]
 | 
				
			||||||
 | 
					    if CONF_PROJECT not in full_config:
 | 
				
			||||||
 | 
					        raise cv.Invalid(
 | 
				
			||||||
 | 
					            "Dashboard import requires the `esphome` -> `project` information to be provided."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FINAL_VALIDATE_SCHEMA = _final_validate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WIFI_CONFIG = """
 | 
					WIFI_CONFIG = """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
wifi:
 | 
					wifi:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					 | 
				
			||||||
#include "esphome/core/automation.h"
 | 
					#include "esphome/core/automation.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/core/hal.h"
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
#include <esp_sleep.h>
 | 
					#include <esp_sleep.h>
 | 
				
			||||||
@@ -11,6 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#ifdef USE_TIME
 | 
					#ifdef USE_TIME
 | 
				
			||||||
#include "esphome/components/time/real_time_clock.h"
 | 
					#include "esphome/components/time/real_time_clock.h"
 | 
				
			||||||
 | 
					#include "esphome/core/time.h"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
@@ -170,7 +171,7 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> {
 | 
				
			|||||||
      if (after_time)
 | 
					      if (after_time)
 | 
				
			||||||
        timestamp += 60 * 60 * 24;
 | 
					        timestamp += 60 * 60 * 24;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      int32_t offset = time::ESPTime::timezone_offset();
 | 
					      int32_t offset = ESPTime::timezone_offset();
 | 
				
			||||||
      timestamp -= offset;  // Change timestamp to utc
 | 
					      timestamp -= offset;  // Change timestamp to utc
 | 
				
			||||||
      const uint32_t ms_left = (timestamp - timestamp_now) * 1000;
 | 
					      const uint32_t ms_left = (timestamp - timestamp_now) * 1000;
 | 
				
			||||||
      this->deep_sleep_->set_sleep_duration(ms_left);
 | 
					      this->deep_sleep_->set_sleep_duration(ms_left);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										69
									
								
								esphome/components/display/animation.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								esphome/components/display/animation.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					#include "animation.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace display {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
 | 
				
			||||||
 | 
					    : Image(data_start, width, height, type),
 | 
				
			||||||
 | 
					      animation_data_start_(data_start),
 | 
				
			||||||
 | 
					      current_frame_(0),
 | 
				
			||||||
 | 
					      animation_frame_count_(animation_frame_count),
 | 
				
			||||||
 | 
					      loop_start_frame_(0),
 | 
				
			||||||
 | 
					      loop_end_frame_(animation_frame_count_),
 | 
				
			||||||
 | 
					      loop_count_(0),
 | 
				
			||||||
 | 
					      loop_current_iteration_(1) {}
 | 
				
			||||||
 | 
					void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) {
 | 
				
			||||||
 | 
					  loop_start_frame_ = std::min(start_frame, animation_frame_count_);
 | 
				
			||||||
 | 
					  loop_end_frame_ = std::min(end_frame, animation_frame_count_);
 | 
				
			||||||
 | 
					  loop_count_ = count;
 | 
				
			||||||
 | 
					  loop_current_iteration_ = 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; }
 | 
				
			||||||
 | 
					int Animation::get_current_frame() const { return this->current_frame_; }
 | 
				
			||||||
 | 
					void Animation::next_frame() {
 | 
				
			||||||
 | 
					  this->current_frame_++;
 | 
				
			||||||
 | 
					  if (loop_count_ && this->current_frame_ == loop_end_frame_ &&
 | 
				
			||||||
 | 
					      (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
 | 
				
			||||||
 | 
					    this->current_frame_ = loop_start_frame_;
 | 
				
			||||||
 | 
					    this->loop_current_iteration_++;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (this->current_frame_ >= animation_frame_count_) {
 | 
				
			||||||
 | 
					    this->loop_current_iteration_ = 1;
 | 
				
			||||||
 | 
					    this->current_frame_ = 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->update_data_start_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void Animation::prev_frame() {
 | 
				
			||||||
 | 
					  this->current_frame_--;
 | 
				
			||||||
 | 
					  if (this->current_frame_ < 0) {
 | 
				
			||||||
 | 
					    this->current_frame_ = this->animation_frame_count_ - 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->update_data_start_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Animation::set_frame(int frame) {
 | 
				
			||||||
 | 
					  unsigned abs_frame = abs(frame);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (abs_frame < this->animation_frame_count_) {
 | 
				
			||||||
 | 
					    if (frame >= 0) {
 | 
				
			||||||
 | 
					      this->current_frame_ = frame;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->current_frame_ = this->animation_frame_count_ - abs_frame;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->update_data_start_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Animation::update_data_start_() {
 | 
				
			||||||
 | 
					  const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_;
 | 
				
			||||||
 | 
					  this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace display
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										37
									
								
								esphome/components/display/animation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/display/animation.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include "image.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace display {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Animation : public Image {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32_t get_animation_frame_count() const;
 | 
				
			||||||
 | 
					  int get_current_frame() const;
 | 
				
			||||||
 | 
					  void next_frame();
 | 
				
			||||||
 | 
					  void prev_frame();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Selects a specific frame within the animation.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void set_frame(int frame);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_loop(uint32_t start_frame, uint32_t end_frame, int count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  void update_data_start_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const uint8_t *animation_data_start_;
 | 
				
			||||||
 | 
					  int current_frame_;
 | 
				
			||||||
 | 
					  uint32_t animation_frame_count_;
 | 
				
			||||||
 | 
					  uint32_t loop_start_frame_;
 | 
				
			||||||
 | 
					  uint32_t loop_end_frame_;
 | 
				
			||||||
 | 
					  int loop_count_;
 | 
				
			||||||
 | 
					  int loop_current_iteration_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace display
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -3,16 +3,20 @@
 | 
				
			|||||||
#include <utility>
 | 
					#include <utility>
 | 
				
			||||||
#include "esphome/core/application.h"
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
#include "esphome/core/color.h"
 | 
					#include "esphome/core/color.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					 | 
				
			||||||
#include "esphome/core/hal.h"
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "animation.h"
 | 
				
			||||||
 | 
					#include "image.h"
 | 
				
			||||||
 | 
					#include "font.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace display {
 | 
					namespace display {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "display";
 | 
					static const char *const TAG = "display";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Color COLOR_OFF(0, 0, 0, 0);
 | 
					const Color COLOR_OFF(0, 0, 0, 255);
 | 
				
			||||||
const Color COLOR_ON(255, 255, 255, 255);
 | 
					const Color COLOR_ON(255, 255, 255, 255);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Rect::expand(int16_t horizontal, int16_t vertical) {
 | 
					void Rect::expand(int16_t horizontal, int16_t vertical) {
 | 
				
			||||||
@@ -282,10 +286,14 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align
 | 
				
			|||||||
    int scan_x1, scan_y1, scan_width, scan_height;
 | 
					    int scan_x1, scan_y1, scan_width, scan_height;
 | 
				
			||||||
    glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
 | 
					    glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (int glyph_x = scan_x1; glyph_x < scan_x1 + scan_width; glyph_x++) {
 | 
					    {
 | 
				
			||||||
      for (int glyph_y = scan_y1; glyph_y < scan_y1 + scan_height; glyph_y++) {
 | 
					      const int glyph_x_max = scan_x1 + scan_width;
 | 
				
			||||||
        if (glyph.get_pixel(glyph_x, glyph_y)) {
 | 
					      const int glyph_y_max = scan_y1 + scan_height;
 | 
				
			||||||
          this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
 | 
					      for (int glyph_x = scan_x1; glyph_x < glyph_x_max; glyph_x++) {
 | 
				
			||||||
 | 
					        for (int glyph_y = scan_y1; glyph_y < glyph_y_max; glyph_y++) {
 | 
				
			||||||
 | 
					          if (glyph.get_pixel(glyph_x, glyph_y)) {
 | 
				
			||||||
 | 
					            this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -302,45 +310,39 @@ void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign al
 | 
				
			|||||||
    this->print(x, y, font, color, align, buffer);
 | 
					    this->print(x, y, font, color, align, buffer);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) {
 | 
					void DisplayBuffer::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
 | 
				
			||||||
  switch (image->get_type()) {
 | 
					  this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off);
 | 
				
			||||||
    case IMAGE_TYPE_BINARY:
 | 
					}
 | 
				
			||||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
					
 | 
				
			||||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
					void DisplayBuffer::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) {
 | 
				
			||||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color_on : color_off);
 | 
					  auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT)));
 | 
				
			||||||
        }
 | 
					  auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT)));
 | 
				
			||||||
      }
 | 
					
 | 
				
			||||||
 | 
					  switch (x_align) {
 | 
				
			||||||
 | 
					    case ImageAlign::RIGHT:
 | 
				
			||||||
 | 
					      x -= image->get_width();
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case IMAGE_TYPE_GRAYSCALE:
 | 
					    case ImageAlign::CENTER_HORIZONTAL:
 | 
				
			||||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
					      x -= image->get_width() / 2;
 | 
				
			||||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
					 | 
				
			||||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case IMAGE_TYPE_RGB24:
 | 
					    case ImageAlign::LEFT:
 | 
				
			||||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
					    default:
 | 
				
			||||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
					 | 
				
			||||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case IMAGE_TYPE_TRANSPARENT_BINARY:
 | 
					 | 
				
			||||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
					 | 
				
			||||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
					 | 
				
			||||||
          if (image->get_pixel(img_x, img_y))
 | 
					 | 
				
			||||||
            this->draw_pixel_at(x + img_x, y + img_y, color_on);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case IMAGE_TYPE_RGB565:
 | 
					 | 
				
			||||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
					 | 
				
			||||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
					 | 
				
			||||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_rgb565_pixel(img_x, img_y));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  switch (y_align) {
 | 
				
			||||||
 | 
					    case ImageAlign::BOTTOM:
 | 
				
			||||||
 | 
					      y -= image->get_height();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ImageAlign::CENTER_VERTICAL:
 | 
				
			||||||
 | 
					      y -= image->get_height() / 2;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ImageAlign::TOP:
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  image->draw(x, y, this, color_on, color_off);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_GRAPH
 | 
					#ifdef USE_GRAPH
 | 
				
			||||||
@@ -468,24 +470,21 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
 | 
				
			|||||||
  if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
 | 
					  if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
 | 
				
			||||||
    this->trigger(from, to);
 | 
					    this->trigger(from, to);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#ifdef USE_TIME
 | 
					void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, ESPTime time) {
 | 
				
			||||||
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format,
 | 
					 | 
				
			||||||
                             time::ESPTime time) {
 | 
					 | 
				
			||||||
  char buffer[64];
 | 
					  char buffer[64];
 | 
				
			||||||
  size_t ret = time.strftime(buffer, sizeof(buffer), format);
 | 
					  size_t ret = time.strftime(buffer, sizeof(buffer), format);
 | 
				
			||||||
  if (ret > 0)
 | 
					  if (ret > 0)
 | 
				
			||||||
    this->print(x, y, font, color, align, buffer);
 | 
					    this->print(x, y, font, color, align, buffer);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) {
 | 
					void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, ESPTime time) {
 | 
				
			||||||
  this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
 | 
					  this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) {
 | 
					void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, ESPTime time) {
 | 
				
			||||||
  this->strftime(x, y, font, COLOR_ON, align, format, time);
 | 
					  this->strftime(x, y, font, COLOR_ON, align, format, time);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time::ESPTime time) {
 | 
					void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, ESPTime time) {
 | 
				
			||||||
  this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
 | 
					  this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DisplayBuffer::start_clipping(Rect rect) {
 | 
					void DisplayBuffer::start_clipping(Rect rect) {
 | 
				
			||||||
  if (!this->clipping_rectangle_.empty()) {
 | 
					  if (!this->clipping_rectangle_.empty()) {
 | 
				
			||||||
@@ -522,217 +521,6 @@ Rect DisplayBuffer::get_clipping() {
 | 
				
			|||||||
    return this->clipping_rectangle_.back();
 | 
					    return this->clipping_rectangle_.back();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
bool Glyph::get_pixel(int x, int y) const {
 | 
					 | 
				
			||||||
  const int x_data = x - this->glyph_data_->offset_x;
 | 
					 | 
				
			||||||
  const int y_data = y - this->glyph_data_->offset_y;
 | 
					 | 
				
			||||||
  if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height)
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u;
 | 
					 | 
				
			||||||
  const uint32_t pos = x_data + y_data * width_8;
 | 
					 | 
				
			||||||
  return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const char *Glyph::get_char() const { return this->glyph_data_->a_char; }
 | 
					 | 
				
			||||||
bool Glyph::compare_to(const char *str) const {
 | 
					 | 
				
			||||||
  // 1 -> this->char_
 | 
					 | 
				
			||||||
  // 2 -> str
 | 
					 | 
				
			||||||
  for (uint32_t i = 0;; i++) {
 | 
					 | 
				
			||||||
    if (this->glyph_data_->a_char[i] == '\0')
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
    if (str[i] == '\0')
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    if (this->glyph_data_->a_char[i] > str[i])
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    if (this->glyph_data_->a_char[i] < str[i])
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // this should not happen
 | 
					 | 
				
			||||||
  return false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
int Glyph::match_length(const char *str) const {
 | 
					 | 
				
			||||||
  for (uint32_t i = 0;; i++) {
 | 
					 | 
				
			||||||
    if (this->glyph_data_->a_char[i] == '\0')
 | 
					 | 
				
			||||||
      return i;
 | 
					 | 
				
			||||||
    if (str[i] != this->glyph_data_->a_char[i])
 | 
					 | 
				
			||||||
      return 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // this should not happen
 | 
					 | 
				
			||||||
  return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
 | 
					 | 
				
			||||||
  *x1 = this->glyph_data_->offset_x;
 | 
					 | 
				
			||||||
  *y1 = this->glyph_data_->offset_y;
 | 
					 | 
				
			||||||
  *width = this->glyph_data_->width;
 | 
					 | 
				
			||||||
  *height = this->glyph_data_->height;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
int Font::match_next_glyph(const char *str, int *match_length) {
 | 
					 | 
				
			||||||
  int lo = 0;
 | 
					 | 
				
			||||||
  int hi = this->glyphs_.size() - 1;
 | 
					 | 
				
			||||||
  while (lo != hi) {
 | 
					 | 
				
			||||||
    int mid = (lo + hi + 1) / 2;
 | 
					 | 
				
			||||||
    if (this->glyphs_[mid].compare_to(str)) {
 | 
					 | 
				
			||||||
      lo = mid;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      hi = mid - 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  *match_length = this->glyphs_[lo].match_length(str);
 | 
					 | 
				
			||||||
  if (*match_length <= 0)
 | 
					 | 
				
			||||||
    return -1;
 | 
					 | 
				
			||||||
  return lo;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
 | 
					 | 
				
			||||||
  *baseline = this->baseline_;
 | 
					 | 
				
			||||||
  *height = this->height_;
 | 
					 | 
				
			||||||
  int i = 0;
 | 
					 | 
				
			||||||
  int min_x = 0;
 | 
					 | 
				
			||||||
  bool has_char = false;
 | 
					 | 
				
			||||||
  int x = 0;
 | 
					 | 
				
			||||||
  while (str[i] != '\0') {
 | 
					 | 
				
			||||||
    int match_length;
 | 
					 | 
				
			||||||
    int glyph_n = this->match_next_glyph(str + i, &match_length);
 | 
					 | 
				
			||||||
    if (glyph_n < 0) {
 | 
					 | 
				
			||||||
      // Unknown char, skip
 | 
					 | 
				
			||||||
      if (!this->get_glyphs().empty())
 | 
					 | 
				
			||||||
        x += this->get_glyphs()[0].glyph_data_->width;
 | 
					 | 
				
			||||||
      i++;
 | 
					 | 
				
			||||||
      continue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const Glyph &glyph = this->glyphs_[glyph_n];
 | 
					 | 
				
			||||||
    if (!has_char) {
 | 
					 | 
				
			||||||
      min_x = glyph.glyph_data_->offset_x;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    i += match_length;
 | 
					 | 
				
			||||||
    has_char = true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  *x_offset = min_x;
 | 
					 | 
				
			||||||
  *width = x - min_x;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
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]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool Image::get_pixel(int x, int y) const {
 | 
					 | 
				
			||||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
 | 
					 | 
				
			||||||
  const uint32_t pos = x + y * width_8;
 | 
					 | 
				
			||||||
  return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Color Image::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 pos = (x + y * this->width_) * 3;
 | 
					 | 
				
			||||||
  const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
 | 
					 | 
				
			||||||
                           (progmem_read_byte(this->data_start_ + pos + 1) << 8) |
 | 
					 | 
				
			||||||
                           (progmem_read_byte(this->data_start_ + pos + 0) << 16);
 | 
					 | 
				
			||||||
  return Color(color32);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Color Image::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 pos = (x + y * this->width_) * 2;
 | 
					 | 
				
			||||||
  uint16_t rgb565 =
 | 
					 | 
				
			||||||
      progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
 | 
					 | 
				
			||||||
  auto r = (rgb565 & 0xF800) >> 11;
 | 
					 | 
				
			||||||
  auto g = (rgb565 & 0x07E0) >> 5;
 | 
					 | 
				
			||||||
  auto b = rgb565 & 0x001F;
 | 
					 | 
				
			||||||
  return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Color Image::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 pos = (x + y * this->width_);
 | 
					 | 
				
			||||||
  const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
 | 
					 | 
				
			||||||
  return Color(gray | gray << 8 | gray << 16 | gray << 24);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
int Image::get_width() const { return this->width_; }
 | 
					 | 
				
			||||||
int Image::get_height() const { return this->height_; }
 | 
					 | 
				
			||||||
ImageType Image::get_type() const { return this->type_; }
 | 
					 | 
				
			||||||
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
 | 
					 | 
				
			||||||
    : width_(width), height_(height), type_(type), data_start_(data_start) {}
 | 
					 | 
				
			||||||
int Image::get_current_frame() const { return 0; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool Animation::get_pixel(int x, int y) const {
 | 
					 | 
				
			||||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
					 | 
				
			||||||
    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_))
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  const uint32_t pos = x + y * width_8 + frame_index;
 | 
					 | 
				
			||||||
  return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
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_))
 | 
					 | 
				
			||||||
    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) |
 | 
					 | 
				
			||||||
                           (progmem_read_byte(this->data_start_ + pos + 1) << 8) |
 | 
					 | 
				
			||||||
                           (progmem_read_byte(this->data_start_ + pos + 0) << 16);
 | 
					 | 
				
			||||||
  return Color(color32);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
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_))
 | 
					 | 
				
			||||||
    return Color::BLACK;
 | 
					 | 
				
			||||||
  const uint32_t pos = (x + y * this->width_ + frame_index) * 2;
 | 
					 | 
				
			||||||
  uint16_t rgb565 =
 | 
					 | 
				
			||||||
      progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
 | 
					 | 
				
			||||||
  auto r = (rgb565 & 0xF800) >> 11;
 | 
					 | 
				
			||||||
  auto g = (rgb565 & 0x07E0) >> 5;
 | 
					 | 
				
			||||||
  auto b = rgb565 & 0x001F;
 | 
					 | 
				
			||||||
  return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
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_))
 | 
					 | 
				
			||||||
    return Color::BLACK;
 | 
					 | 
				
			||||||
  const uint32_t pos = (x + y * this->width_ + frame_index);
 | 
					 | 
				
			||||||
  const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
 | 
					 | 
				
			||||||
  return Color(gray | gray << 8 | gray << 16 | gray << 24);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
 | 
					 | 
				
			||||||
    : Image(data_start, width, height, type), current_frame_(0), animation_frame_count_(animation_frame_count) {}
 | 
					 | 
				
			||||||
int Animation::get_animation_frame_count() const { return this->animation_frame_count_; }
 | 
					 | 
				
			||||||
int Animation::get_current_frame() const { return this->current_frame_; }
 | 
					 | 
				
			||||||
void Animation::next_frame() {
 | 
					 | 
				
			||||||
  this->current_frame_++;
 | 
					 | 
				
			||||||
  if (this->current_frame_ >= animation_frame_count_) {
 | 
					 | 
				
			||||||
    this->current_frame_ = 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void Animation::prev_frame() {
 | 
					 | 
				
			||||||
  this->current_frame_--;
 | 
					 | 
				
			||||||
  if (this->current_frame_ < 0) {
 | 
					 | 
				
			||||||
    this->current_frame_ = this->animation_frame_count_ - 1;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Animation::set_frame(int frame) {
 | 
					 | 
				
			||||||
  unsigned abs_frame = abs(frame);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (abs_frame < this->animation_frame_count_) {
 | 
					 | 
				
			||||||
    if (frame >= 0) {
 | 
					 | 
				
			||||||
      this->current_frame_ = frame;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      this->current_frame_ = this->animation_frame_count_ - abs_frame;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
 | 
					DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
 | 
				
			||||||
void DisplayPage::show() { this->parent_->show_page(this); }
 | 
					void DisplayPage::show() { this->parent_->show_page(this); }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,12 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					 | 
				
			||||||
#include "esphome/core/defines.h"
 | 
					 | 
				
			||||||
#include "esphome/core/automation.h"
 | 
					 | 
				
			||||||
#include "display_color_utils.h"
 | 
					 | 
				
			||||||
#include <cstdarg>
 | 
					#include <cstdarg>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include "display_color_utils.h"
 | 
				
			||||||
#ifdef USE_TIME
 | 
					#include "esphome/core/automation.h"
 | 
				
			||||||
#include "esphome/components/time/real_time_clock.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#endif
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
 | 
					#include "esphome/core/time.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_GRAPH
 | 
					#ifdef USE_GRAPH
 | 
				
			||||||
#include "esphome/components/graph/graph.h"
 | 
					#include "esphome/components/graph/graph.h"
 | 
				
			||||||
@@ -19,6 +16,10 @@
 | 
				
			|||||||
#include "esphome/components/qr_code/qr_code.h"
 | 
					#include "esphome/components/qr_code/qr_code.h"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "animation.h"
 | 
				
			||||||
 | 
					#include "font.h"
 | 
				
			||||||
 | 
					#include "image.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace display {
 | 
					namespace display {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,17 +74,52 @@ enum class TextAlign {
 | 
				
			|||||||
  BOTTOM_RIGHT = BOTTOM | RIGHT,
 | 
					  BOTTOM_RIGHT = BOTTOM | RIGHT,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Turn the pixel OFF.
 | 
					/** ImageAlign is used to tell the display class how to position a image. By default
 | 
				
			||||||
extern const Color COLOR_OFF;
 | 
					 * the coordinates you enter for the image() functions take the upper left corner of the image
 | 
				
			||||||
/// Turn the pixel ON.
 | 
					 * as the "anchor" point. You can customize this behavior to, for example, make the coordinates
 | 
				
			||||||
extern const Color COLOR_ON;
 | 
					 * refer to the *center* of the image.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
 | 
				
			||||||
 | 
					 * these options are allowed:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * - LEFT (x-coordinate of anchor point is on left)
 | 
				
			||||||
 | 
					 * - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image)
 | 
				
			||||||
 | 
					 * - RIGHT (x-coordinate of anchor point is on right)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * For the Y-Axis alignment these options are allowed:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * - TOP (y-coordinate of anchor is on the top of the image)
 | 
				
			||||||
 | 
					 * - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image)
 | 
				
			||||||
 | 
					 * - BOTTOM (y-coordinate of anchor is on the bottom of the image)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * These options are then combined to create combined TextAlignment options like:
 | 
				
			||||||
 | 
					 * - TOP_LEFT (default)
 | 
				
			||||||
 | 
					 * - CENTER (anchor point is in the middle of the image bounds)
 | 
				
			||||||
 | 
					 * - ...
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					enum class ImageAlign {
 | 
				
			||||||
 | 
					  TOP = 0x00,
 | 
				
			||||||
 | 
					  CENTER_VERTICAL = 0x01,
 | 
				
			||||||
 | 
					  BOTTOM = 0x02,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum ImageType {
 | 
					  LEFT = 0x00,
 | 
				
			||||||
  IMAGE_TYPE_BINARY = 0,
 | 
					  CENTER_HORIZONTAL = 0x04,
 | 
				
			||||||
  IMAGE_TYPE_GRAYSCALE = 1,
 | 
					  RIGHT = 0x08,
 | 
				
			||||||
  IMAGE_TYPE_RGB24 = 2,
 | 
					
 | 
				
			||||||
  IMAGE_TYPE_TRANSPARENT_BINARY = 3,
 | 
					  TOP_LEFT = TOP | LEFT,
 | 
				
			||||||
  IMAGE_TYPE_RGB565 = 4,
 | 
					  TOP_CENTER = TOP | CENTER_HORIZONTAL,
 | 
				
			||||||
 | 
					  TOP_RIGHT = TOP | RIGHT,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CENTER_LEFT = CENTER_VERTICAL | LEFT,
 | 
				
			||||||
 | 
					  CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
 | 
				
			||||||
 | 
					  CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BOTTOM_LEFT = BOTTOM | LEFT,
 | 
				
			||||||
 | 
					  BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
 | 
				
			||||||
 | 
					  BOTTOM_RIGHT = BOTTOM | RIGHT,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT,
 | 
				
			||||||
 | 
					  VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum DisplayType {
 | 
					enum DisplayType {
 | 
				
			||||||
@@ -126,8 +162,6 @@ class Rect {
 | 
				
			|||||||
  void info(const std::string &prefix = "rect info:");
 | 
					  void info(const std::string &prefix = "rect info:");
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Font;
 | 
					 | 
				
			||||||
class Image;
 | 
					 | 
				
			||||||
class DisplayBuffer;
 | 
					class DisplayBuffer;
 | 
				
			||||||
class DisplayPage;
 | 
					class DisplayPage;
 | 
				
			||||||
class DisplayOnPageChangeTrigger;
 | 
					class DisplayOnPageChangeTrigger;
 | 
				
			||||||
@@ -263,7 +297,6 @@ class DisplayBuffer {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  void printf(int x, int y, Font *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
 | 
					  void printf(int x, int y, Font *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_TIME
 | 
					 | 
				
			||||||
  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
					  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
					   * @param x The x coordinate of the text alignment anchor point.
 | 
				
			||||||
@@ -274,7 +307,7 @@ class DisplayBuffer {
 | 
				
			|||||||
   * @param format The strftime format to use.
 | 
					   * @param format The strftime format to use.
 | 
				
			||||||
   * @param time The time to format.
 | 
					   * @param time The time to format.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, time::ESPTime time)
 | 
					  void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, ESPTime time)
 | 
				
			||||||
      __attribute__((format(strftime, 7, 0)));
 | 
					      __attribute__((format(strftime, 7, 0)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
 | 
					  /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
 | 
				
			||||||
@@ -286,7 +319,7 @@ class DisplayBuffer {
 | 
				
			|||||||
   * @param format The strftime format to use.
 | 
					   * @param format The strftime format to use.
 | 
				
			||||||
   * @param time The time to format.
 | 
					   * @param time The time to format.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  void strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time)
 | 
					  void strftime(int x, int y, Font *font, Color color, const char *format, ESPTime time)
 | 
				
			||||||
      __attribute__((format(strftime, 6, 0)));
 | 
					      __attribute__((format(strftime, 6, 0)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
					  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
				
			||||||
@@ -298,7 +331,7 @@ class DisplayBuffer {
 | 
				
			|||||||
   * @param format The strftime format to use.
 | 
					   * @param format The strftime format to use.
 | 
				
			||||||
   * @param time The time to format.
 | 
					   * @param time The time to format.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  void strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time)
 | 
					  void strftime(int x, int y, Font *font, TextAlign align, const char *format, ESPTime time)
 | 
				
			||||||
      __attribute__((format(strftime, 6, 0)));
 | 
					      __attribute__((format(strftime, 6, 0)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
 | 
					  /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
 | 
				
			||||||
@@ -309,19 +342,28 @@ class DisplayBuffer {
 | 
				
			|||||||
   * @param format The strftime format to use.
 | 
					   * @param format The strftime format to use.
 | 
				
			||||||
   * @param time The time to format.
 | 
					   * @param time The time to format.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  void strftime(int x, int y, Font *font, const char *format, time::ESPTime time)
 | 
					  void strftime(int x, int y, Font *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0)));
 | 
				
			||||||
      __attribute__((format(strftime, 5, 0)));
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Draw the `image` with the top-left corner at [x,y] to the screen.
 | 
					  /** Draw the `image` with the top-left corner at [x,y] to the screen.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * @param x The x coordinate of the upper left corner.
 | 
					   * @param x The x coordinate of the upper left corner.
 | 
				
			||||||
   * @param y The y coordinate of the upper left corner.
 | 
					   * @param y The y coordinate of the upper left corner.
 | 
				
			||||||
   * @param image The image to draw
 | 
					   * @param image The image to draw.
 | 
				
			||||||
   * @param color_on The color to replace in binary images for the on bits.
 | 
					   * @param color_on The color to replace in binary images for the on bits.
 | 
				
			||||||
   * @param color_off The color to replace in binary images for the off bits.
 | 
					   * @param color_off The color to replace in binary images for the off bits.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
 | 
					  void image(int x, int y, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Draw the `image` at [x,y] to the screen.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param x The x coordinate of the upper left corner.
 | 
				
			||||||
 | 
					   * @param y The y coordinate of the upper left corner.
 | 
				
			||||||
 | 
					   * @param image The image to draw.
 | 
				
			||||||
 | 
					   * @param align The alignment of the image.
 | 
				
			||||||
 | 
					   * @param color_on The color to replace in binary images for the on bits.
 | 
				
			||||||
 | 
					   * @param color_off The color to replace in binary images for the off bits.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void image(int x, int y, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_GRAPH
 | 
					#ifdef USE_GRAPH
 | 
				
			||||||
  /** Draw the `graph` with the top-left corner at [x,y] to the screen.
 | 
					  /** Draw the `graph` with the top-left corner at [x,y] to the screen.
 | 
				
			||||||
@@ -481,104 +523,6 @@ class DisplayPage {
 | 
				
			|||||||
  DisplayPage *next_{nullptr};
 | 
					  DisplayPage *next_{nullptr};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct GlyphData {
 | 
					 | 
				
			||||||
  const char *a_char;
 | 
					 | 
				
			||||||
  const uint8_t *data;
 | 
					 | 
				
			||||||
  int offset_x;
 | 
					 | 
				
			||||||
  int offset_y;
 | 
					 | 
				
			||||||
  int width;
 | 
					 | 
				
			||||||
  int height;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Glyph {
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
  Glyph(const GlyphData *data) : glyph_data_(data) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool get_pixel(int x, int y) const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const char *get_char() const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool compare_to(const char *str) const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int match_length(const char *str) const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void scan_area(int *x1, int *y1, int *width, int *height) const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  friend Font;
 | 
					 | 
				
			||||||
  friend DisplayBuffer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const GlyphData *glyph_data_;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Font {
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
  /** Construct the font with the given glyphs.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * @param glyphs A vector of glyphs, must be sorted lexicographically.
 | 
					 | 
				
			||||||
   * @param baseline The y-offset from the top of the text to the baseline.
 | 
					 | 
				
			||||||
   * @param bottom The y-offset from the top of the text to the bottom (i.e. height).
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  Font(const GlyphData *data, int data_nr, int baseline, int height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int match_next_glyph(const char *str, int *match_length);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void measure(const char *str, int *width, int *x_offset, int *baseline, int *height);
 | 
					 | 
				
			||||||
  inline int get_baseline() { return this->baseline_; }
 | 
					 | 
				
			||||||
  inline int get_height() { return this->height_; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_;
 | 
					 | 
				
			||||||
  int baseline_;
 | 
					 | 
				
			||||||
  int height_;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Image {
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
  Image(const uint8_t *data_start, int width, int height, ImageType type);
 | 
					 | 
				
			||||||
  virtual bool get_pixel(int x, int y) const;
 | 
					 | 
				
			||||||
  virtual Color get_color_pixel(int x, int y) const;
 | 
					 | 
				
			||||||
  virtual Color get_rgb565_pixel(int x, int y) const;
 | 
					 | 
				
			||||||
  virtual Color get_grayscale_pixel(int x, int y) const;
 | 
					 | 
				
			||||||
  int get_width() const;
 | 
					 | 
				
			||||||
  int get_height() const;
 | 
					 | 
				
			||||||
  ImageType get_type() const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  virtual int get_current_frame() const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  int width_;
 | 
					 | 
				
			||||||
  int height_;
 | 
					 | 
				
			||||||
  ImageType type_;
 | 
					 | 
				
			||||||
  const uint8_t *data_start_;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Animation : public Image {
 | 
					 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
  Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
 | 
					 | 
				
			||||||
  bool get_pixel(int x, int y) const override;
 | 
					 | 
				
			||||||
  Color get_color_pixel(int x, int y) const override;
 | 
					 | 
				
			||||||
  Color get_rgb565_pixel(int x, int y) const override;
 | 
					 | 
				
			||||||
  Color get_grayscale_pixel(int x, int y) const override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int get_animation_frame_count() const;
 | 
					 | 
				
			||||||
  int get_current_frame() const override;
 | 
					 | 
				
			||||||
  void next_frame();
 | 
					 | 
				
			||||||
  void prev_frame();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /** Selects a specific frame within the animation.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  void set_frame(int frame);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  int current_frame_;
 | 
					 | 
				
			||||||
  int animation_frame_count_;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
 | 
					template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  TEMPLATABLE_VALUE(DisplayPage *, page)
 | 
					  TEMPLATABLE_VALUE(DisplayPage *, page)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										105
									
								
								esphome/components/display/font.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								esphome/components/display/font.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					#include "font.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace display {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Glyph::get_pixel(int x, int y) const {
 | 
				
			||||||
 | 
					  const int x_data = x - this->glyph_data_->offset_x;
 | 
				
			||||||
 | 
					  const int y_data = y - this->glyph_data_->offset_y;
 | 
				
			||||||
 | 
					  if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height)
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u;
 | 
				
			||||||
 | 
					  const uint32_t pos = x_data + y_data * width_8;
 | 
				
			||||||
 | 
					  return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const char *Glyph::get_char() const { return this->glyph_data_->a_char; }
 | 
				
			||||||
 | 
					bool Glyph::compare_to(const char *str) const {
 | 
				
			||||||
 | 
					  // 1 -> this->char_
 | 
				
			||||||
 | 
					  // 2 -> str
 | 
				
			||||||
 | 
					  for (uint32_t i = 0;; i++) {
 | 
				
			||||||
 | 
					    if (this->glyph_data_->a_char[i] == '\0')
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    if (str[i] == '\0')
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    if (this->glyph_data_->a_char[i] > str[i])
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    if (this->glyph_data_->a_char[i] < str[i])
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // this should not happen
 | 
				
			||||||
 | 
					  return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					int Glyph::match_length(const char *str) const {
 | 
				
			||||||
 | 
					  for (uint32_t i = 0;; i++) {
 | 
				
			||||||
 | 
					    if (this->glyph_data_->a_char[i] == '\0')
 | 
				
			||||||
 | 
					      return i;
 | 
				
			||||||
 | 
					    if (str[i] != this->glyph_data_->a_char[i])
 | 
				
			||||||
 | 
					      return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // this should not happen
 | 
				
			||||||
 | 
					  return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
 | 
				
			||||||
 | 
					  *x1 = this->glyph_data_->offset_x;
 | 
				
			||||||
 | 
					  *y1 = this->glyph_data_->offset_y;
 | 
				
			||||||
 | 
					  *width = this->glyph_data_->width;
 | 
				
			||||||
 | 
					  *height = this->glyph_data_->height;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					int Font::match_next_glyph(const char *str, int *match_length) {
 | 
				
			||||||
 | 
					  int lo = 0;
 | 
				
			||||||
 | 
					  int hi = this->glyphs_.size() - 1;
 | 
				
			||||||
 | 
					  while (lo != hi) {
 | 
				
			||||||
 | 
					    int mid = (lo + hi + 1) / 2;
 | 
				
			||||||
 | 
					    if (this->glyphs_[mid].compare_to(str)) {
 | 
				
			||||||
 | 
					      lo = mid;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      hi = mid - 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  *match_length = this->glyphs_[lo].match_length(str);
 | 
				
			||||||
 | 
					  if (*match_length <= 0)
 | 
				
			||||||
 | 
					    return -1;
 | 
				
			||||||
 | 
					  return lo;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
 | 
				
			||||||
 | 
					  *baseline = this->baseline_;
 | 
				
			||||||
 | 
					  *height = this->height_;
 | 
				
			||||||
 | 
					  int i = 0;
 | 
				
			||||||
 | 
					  int min_x = 0;
 | 
				
			||||||
 | 
					  bool has_char = false;
 | 
				
			||||||
 | 
					  int x = 0;
 | 
				
			||||||
 | 
					  while (str[i] != '\0') {
 | 
				
			||||||
 | 
					    int match_length;
 | 
				
			||||||
 | 
					    int glyph_n = this->match_next_glyph(str + i, &match_length);
 | 
				
			||||||
 | 
					    if (glyph_n < 0) {
 | 
				
			||||||
 | 
					      // Unknown char, skip
 | 
				
			||||||
 | 
					      if (!this->get_glyphs().empty())
 | 
				
			||||||
 | 
					        x += this->get_glyphs()[0].glyph_data_->width;
 | 
				
			||||||
 | 
					      i++;
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Glyph &glyph = this->glyphs_[glyph_n];
 | 
				
			||||||
 | 
					    if (!has_char) {
 | 
				
			||||||
 | 
					      min_x = glyph.glyph_data_->offset_x;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    i += match_length;
 | 
				
			||||||
 | 
					    has_char = true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  *x_offset = min_x;
 | 
				
			||||||
 | 
					  *width = x - min_x;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					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]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace display
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										66
									
								
								esphome/components/display/font.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/display/font.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/datatypes.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace display {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DisplayBuffer;
 | 
				
			||||||
 | 
					class Font;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct GlyphData {
 | 
				
			||||||
 | 
					  const char *a_char;
 | 
				
			||||||
 | 
					  const uint8_t *data;
 | 
				
			||||||
 | 
					  int offset_x;
 | 
				
			||||||
 | 
					  int offset_y;
 | 
				
			||||||
 | 
					  int width;
 | 
				
			||||||
 | 
					  int height;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Glyph {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  Glyph(const GlyphData *data) : glyph_data_(data) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get_pixel(int x, int y) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const char *get_char() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool compare_to(const char *str) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int match_length(const char *str) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void scan_area(int *x1, int *y1, int *width, int *height) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  friend Font;
 | 
				
			||||||
 | 
					  friend DisplayBuffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const GlyphData *glyph_data_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Font {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  /** Construct the font with the given glyphs.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param glyphs A vector of glyphs, must be sorted lexicographically.
 | 
				
			||||||
 | 
					   * @param baseline The y-offset from the top of the text to the baseline.
 | 
				
			||||||
 | 
					   * @param bottom The y-offset from the top of the text to the bottom (i.e. height).
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  Font(const GlyphData *data, int data_nr, int baseline, int height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int match_next_glyph(const char *str, int *match_length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void measure(const char *str, int *width, int *x_offset, int *baseline, int *height);
 | 
				
			||||||
 | 
					  inline int get_baseline() { return this->baseline_; }
 | 
				
			||||||
 | 
					  inline int get_height() { return this->height_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_;
 | 
				
			||||||
 | 
					  int baseline_;
 | 
				
			||||||
 | 
					  int height_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace display
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										135
									
								
								esphome/components/display/image.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								esphome/components/display/image.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					#include "image.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					#include "display_buffer.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace display {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) {
 | 
				
			||||||
 | 
					  switch (type_) {
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_BINARY: {
 | 
				
			||||||
 | 
					      for (int img_x = 0; img_x < width_; img_x++) {
 | 
				
			||||||
 | 
					        for (int img_y = 0; img_y < height_; img_y++) {
 | 
				
			||||||
 | 
					          if (this->get_binary_pixel_(img_x, img_y)) {
 | 
				
			||||||
 | 
					            display->draw_pixel_at(x + img_x, y + img_y, color_on);
 | 
				
			||||||
 | 
					          } else if (!this->transparent_) {
 | 
				
			||||||
 | 
					            display->draw_pixel_at(x + img_x, y + img_y, color_off);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_GRAYSCALE:
 | 
				
			||||||
 | 
					      for (int img_x = 0; img_x < width_; img_x++) {
 | 
				
			||||||
 | 
					        for (int img_y = 0; img_y < height_; img_y++) {
 | 
				
			||||||
 | 
					          auto color = this->get_grayscale_pixel_(img_x, img_y);
 | 
				
			||||||
 | 
					          if (color.w >= 0x80) {
 | 
				
			||||||
 | 
					            display->draw_pixel_at(x + img_x, y + img_y, color);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGB565:
 | 
				
			||||||
 | 
					      for (int img_x = 0; img_x < width_; img_x++) {
 | 
				
			||||||
 | 
					        for (int img_y = 0; img_y < height_; img_y++) {
 | 
				
			||||||
 | 
					          auto color = this->get_rgb565_pixel_(img_x, img_y);
 | 
				
			||||||
 | 
					          if (color.w >= 0x80) {
 | 
				
			||||||
 | 
					            display->draw_pixel_at(x + img_x, y + img_y, color);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGB24:
 | 
				
			||||||
 | 
					      for (int img_x = 0; img_x < width_; img_x++) {
 | 
				
			||||||
 | 
					        for (int img_y = 0; img_y < height_; img_y++) {
 | 
				
			||||||
 | 
					          auto color = this->get_rgb24_pixel_(img_x, img_y);
 | 
				
			||||||
 | 
					          if (color.w >= 0x80) {
 | 
				
			||||||
 | 
					            display->draw_pixel_at(x + img_x, y + img_y, color);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGBA:
 | 
				
			||||||
 | 
					      for (int img_x = 0; img_x < width_; img_x++) {
 | 
				
			||||||
 | 
					        for (int img_y = 0; img_y < height_; img_y++) {
 | 
				
			||||||
 | 
					          auto color = this->get_rgba_pixel_(img_x, img_y);
 | 
				
			||||||
 | 
					          if (color.w >= 0x80) {
 | 
				
			||||||
 | 
					            display->draw_pixel_at(x + img_x, y + img_y, color);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const {
 | 
				
			||||||
 | 
					  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
				
			||||||
 | 
					    return color_off;
 | 
				
			||||||
 | 
					  switch (this->type_) {
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_BINARY:
 | 
				
			||||||
 | 
					      return this->get_binary_pixel_(x, y) ? color_on : color_off;
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_GRAYSCALE:
 | 
				
			||||||
 | 
					      return this->get_grayscale_pixel_(x, y);
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGB565:
 | 
				
			||||||
 | 
					      return this->get_rgb565_pixel_(x, y);
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGB24:
 | 
				
			||||||
 | 
					      return this->get_rgb24_pixel_(x, y);
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGBA:
 | 
				
			||||||
 | 
					      return this->get_rgba_pixel_(x, y);
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return color_off;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool Image::get_binary_pixel_(int x, int y) const {
 | 
				
			||||||
 | 
					  const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
 | 
				
			||||||
 | 
					  const uint32_t pos = x + y * width_8;
 | 
				
			||||||
 | 
					  return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					Color Image::get_rgba_pixel_(int x, int y) const {
 | 
				
			||||||
 | 
					  const uint32_t pos = (x + y * this->width_) * 4;
 | 
				
			||||||
 | 
					  return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
 | 
				
			||||||
 | 
					               progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					Color Image::get_rgb24_pixel_(int x, int y) const {
 | 
				
			||||||
 | 
					  const uint32_t pos = (x + y * this->width_) * 3;
 | 
				
			||||||
 | 
					  Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
 | 
				
			||||||
 | 
					                      progmem_read_byte(this->data_start_ + pos + 2));
 | 
				
			||||||
 | 
					  if (color.b == 1 && color.r == 0 && color.g == 0 && transparent_) {
 | 
				
			||||||
 | 
					    // (0, 0, 1) has been defined as transparent color for non-alpha images.
 | 
				
			||||||
 | 
					    // putting blue == 1 as a first condition for performance reasons (least likely value to short-cut the if)
 | 
				
			||||||
 | 
					    color.w = 0;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    color.w = 0xFF;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return color;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					Color Image::get_rgb565_pixel_(int x, int y) const {
 | 
				
			||||||
 | 
					  const uint32_t pos = (x + y * this->width_) * 2;
 | 
				
			||||||
 | 
					  uint16_t rgb565 =
 | 
				
			||||||
 | 
					      progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
 | 
				
			||||||
 | 
					  auto r = (rgb565 & 0xF800) >> 11;
 | 
				
			||||||
 | 
					  auto g = (rgb565 & 0x07E0) >> 5;
 | 
				
			||||||
 | 
					  auto b = rgb565 & 0x001F;
 | 
				
			||||||
 | 
					  Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
 | 
				
			||||||
 | 
					  if (rgb565 == 0x0020 && transparent_) {
 | 
				
			||||||
 | 
					    // darkest green has been defined as transparent color for transparent RGB565 images.
 | 
				
			||||||
 | 
					    color.w = 0;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    color.w = 0xFF;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return color;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					Color Image::get_grayscale_pixel_(int x, int y) const {
 | 
				
			||||||
 | 
					  const uint32_t pos = (x + y * this->width_);
 | 
				
			||||||
 | 
					  const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
 | 
				
			||||||
 | 
					  uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF;
 | 
				
			||||||
 | 
					  return Color(gray, gray, gray, alpha);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					int Image::get_width() const { return this->width_; }
 | 
				
			||||||
 | 
					int Image::get_height() const { return this->height_; }
 | 
				
			||||||
 | 
					ImageType Image::get_type() const { return this->type_; }
 | 
				
			||||||
 | 
					Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
 | 
				
			||||||
 | 
					    : width_(width), height_(height), type_(type), data_start_(data_start) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace display
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										75
									
								
								esphome/components/display/image.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								esphome/components/display/image.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include "esphome/core/color.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace display {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ImageType {
 | 
				
			||||||
 | 
					  IMAGE_TYPE_BINARY = 0,
 | 
				
			||||||
 | 
					  IMAGE_TYPE_GRAYSCALE = 1,
 | 
				
			||||||
 | 
					  IMAGE_TYPE_RGB24 = 2,
 | 
				
			||||||
 | 
					  IMAGE_TYPE_RGB565 = 3,
 | 
				
			||||||
 | 
					  IMAGE_TYPE_RGBA = 4,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline int image_type_to_bpp(ImageType type) {
 | 
				
			||||||
 | 
					  switch (type) {
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_BINARY:
 | 
				
			||||||
 | 
					      return 1;
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_GRAYSCALE:
 | 
				
			||||||
 | 
					      return 8;
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGB565:
 | 
				
			||||||
 | 
					      return 16;
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGB24:
 | 
				
			||||||
 | 
					      return 24;
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGBA:
 | 
				
			||||||
 | 
					      return 32;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Turn the pixel OFF.
 | 
				
			||||||
 | 
					extern const Color COLOR_OFF;
 | 
				
			||||||
 | 
					/// Turn the pixel ON.
 | 
				
			||||||
 | 
					extern const Color COLOR_ON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DisplayBuffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseImage {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0;
 | 
				
			||||||
 | 
					  virtual int get_width() const = 0;
 | 
				
			||||||
 | 
					  virtual int get_height() const = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Image : public BaseImage {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  Image(const uint8_t *data_start, int width, int height, ImageType type);
 | 
				
			||||||
 | 
					  Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const;
 | 
				
			||||||
 | 
					  int get_width() const override;
 | 
				
			||||||
 | 
					  int get_height() const override;
 | 
				
			||||||
 | 
					  ImageType get_type() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_transparency(bool transparent) { transparent_ = transparent; }
 | 
				
			||||||
 | 
					  bool has_transparency() const { return transparent_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool get_binary_pixel_(int x, int y) const;
 | 
				
			||||||
 | 
					  Color get_rgb24_pixel_(int x, int y) const;
 | 
				
			||||||
 | 
					  Color get_rgba_pixel_(int x, int y) const;
 | 
				
			||||||
 | 
					  Color get_rgb565_pixel_(int x, int y) const;
 | 
				
			||||||
 | 
					  Color get_grayscale_pixel_(int x, int y) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int width_;
 | 
				
			||||||
 | 
					  int height_;
 | 
				
			||||||
 | 
					  ImageType type_;
 | 
				
			||||||
 | 
					  const uint8_t *data_start_;
 | 
				
			||||||
 | 
					  bool transparent_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace display
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -37,14 +37,14 @@ void DS1307Component::read_time() {
 | 
				
			|||||||
    ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
 | 
					    ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  time::ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10),
 | 
					  ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10),
 | 
				
			||||||
                         .minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10),
 | 
					                   .minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10),
 | 
				
			||||||
                         .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
 | 
					                   .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
 | 
				
			||||||
                         .day_of_week = uint8_t(ds1307_.reg.weekday),
 | 
					                   .day_of_week = uint8_t(ds1307_.reg.weekday),
 | 
				
			||||||
                         .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
 | 
					                   .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
 | 
				
			||||||
                         .day_of_year = 1,  // ignored by recalc_timestamp_utc(false)
 | 
					                   .day_of_year = 1,  // ignored by recalc_timestamp_utc(false)
 | 
				
			||||||
                         .month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10),
 | 
					                   .month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10),
 | 
				
			||||||
                         .year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000)};
 | 
					                   .year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000)};
 | 
				
			||||||
  rtc_time.recalc_timestamp_utc(false);
 | 
					  rtc_time.recalc_timestamp_utc(false);
 | 
				
			||||||
  if (!rtc_time.is_valid()) {
 | 
					  if (!rtc_time.is_valid()) {
 | 
				
			||||||
    ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
 | 
					    ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ from esphome.components.light.types import AddressableLightEffect
 | 
				
			|||||||
from esphome.components.light.effects import register_addressable_effect
 | 
					from esphome.components.light.effects import register_addressable_effect
 | 
				
			||||||
from esphome.const import CONF_ID, CONF_NAME, CONF_METHOD, CONF_CHANNELS
 | 
					from esphome.const import CONF_ID, CONF_NAME, CONF_METHOD, CONF_CHANNELS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AUTO_LOAD = ["socket"]
 | 
				
			||||||
DEPENDENCIES = ["network"]
 | 
					DEPENDENCIES = ["network"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
e131_ns = cg.esphome_ns.namespace("e131")
 | 
					e131_ns = cg.esphome_ns.namespace("e131")
 | 
				
			||||||
@@ -23,16 +24,11 @@ CHANNELS = {
 | 
				
			|||||||
CONF_UNIVERSE = "universe"
 | 
					CONF_UNIVERSE = "universe"
 | 
				
			||||||
CONF_E131_ID = "e131_id"
 | 
					CONF_E131_ID = "e131_id"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = cv.All(
 | 
					CONFIG_SCHEMA = cv.Schema(
 | 
				
			||||||
    cv.Schema(
 | 
					    {
 | 
				
			||||||
        {
 | 
					        cv.GenerateID(): cv.declare_id(E131Component),
 | 
				
			||||||
            cv.GenerateID(): cv.declare_id(E131Component),
 | 
					        cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of(*METHODS, upper=True),
 | 
				
			||||||
            cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of(
 | 
					    }
 | 
				
			||||||
                *METHODS, upper=True
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    cv.only_with_arduino,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,7 @@
 | 
				
			|||||||
#ifdef USE_ARDUINO
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "e131.h"
 | 
					#include "e131.h"
 | 
				
			||||||
#include "e131_addressable_light_effect.h"
 | 
					#include "e131_addressable_light_effect.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ESP32
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <WiFiUdp.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace e131 {
 | 
					namespace e131 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,17 +11,41 @@ static const int PORT = 5568;
 | 
				
			|||||||
E131Component::E131Component() {}
 | 
					E131Component::E131Component() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
E131Component::~E131Component() {
 | 
					E131Component::~E131Component() {
 | 
				
			||||||
  if (udp_) {
 | 
					  if (this->socket_) {
 | 
				
			||||||
    udp_->stop();
 | 
					    this->socket_->close();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void E131Component::setup() {
 | 
					void E131Component::setup() {
 | 
				
			||||||
  udp_ = make_unique<WiFiUDP>();
 | 
					  this->socket_ = socket::socket_ip(SOCK_DGRAM, IPPROTO_IP);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!udp_->begin(PORT)) {
 | 
					  int enable = 1;
 | 
				
			||||||
    ESP_LOGE(TAG, "Cannot bind E131 to %d.", PORT);
 | 
					  int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
 | 
				
			||||||
    mark_failed();
 | 
					  if (err != 0) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
 | 
				
			||||||
 | 
					    // we can still continue
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  err = this->socket_->setblocking(false);
 | 
				
			||||||
 | 
					  if (err != 0) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct sockaddr_storage server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), PORT);
 | 
				
			||||||
 | 
					  if (sl == 0) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  server.ss_family = AF_INET;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  err = this->socket_->bind((struct sockaddr *) &server, sizeof(server));
 | 
				
			||||||
 | 
					  if (err != 0) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -43,22 +56,22 @@ void E131Component::loop() {
 | 
				
			|||||||
  std::vector<uint8_t> payload;
 | 
					  std::vector<uint8_t> payload;
 | 
				
			||||||
  E131Packet packet;
 | 
					  E131Packet packet;
 | 
				
			||||||
  int universe = 0;
 | 
					  int universe = 0;
 | 
				
			||||||
 | 
					  uint8_t buf[1460];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  while (uint16_t packet_size = udp_->parsePacket()) {
 | 
					  ssize_t len = this->socket_->read(buf, sizeof(buf));
 | 
				
			||||||
    payload.resize(packet_size);
 | 
					  if (len == -1) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  payload.resize(len);
 | 
				
			||||||
 | 
					  memmove(&payload[0], buf, len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!udp_->read(&payload[0], payload.size())) {
 | 
					  if (!this->packet_(payload, universe, packet)) {
 | 
				
			||||||
      continue;
 | 
					    ESP_LOGV(TAG, "Invalid packet received of size %zu.", payload.size());
 | 
				
			||||||
    }
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!packet_(payload, universe, packet)) {
 | 
					  if (!this->process_(universe, packet)) {
 | 
				
			||||||
      ESP_LOGV(TAG, "Invalid packet received of size %zu.", payload.size());
 | 
					    ESP_LOGV(TAG, "Ignored packet for %d universe of size %d.", universe, packet.count);
 | 
				
			||||||
      continue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!process_(universe, packet)) {
 | 
					 | 
				
			||||||
      ESP_LOGV(TAG, "Ignored packet for %d universe of size %d.", universe, packet.count);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -106,5 +119,3 @@ bool E131Component::process_(int universe, const E131Packet &packet) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace e131
 | 
					}  // namespace e131
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_ARDUINO
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ARDUINO
 | 
					#include "esphome/components/socket/socket.h"
 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <map>
 | 
					#include <map>
 | 
				
			||||||
@@ -9,8 +8,6 @@
 | 
				
			|||||||
#include <set>
 | 
					#include <set>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UDP;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace e131 {
 | 
					namespace e131 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,7 +44,7 @@ class E131Component : public esphome::Component {
 | 
				
			|||||||
  void leave_(int universe);
 | 
					  void leave_(int universe);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  E131ListenMethod listen_method_{E131_MULTICAST};
 | 
					  E131ListenMethod listen_method_{E131_MULTICAST};
 | 
				
			||||||
  std::unique_ptr<UDP> udp_;
 | 
					  std::unique_ptr<socket::Socket> socket_;
 | 
				
			||||||
  std::set<E131AddressableLightEffect *> light_effects_;
 | 
					  std::set<E131AddressableLightEffect *> light_effects_;
 | 
				
			||||||
  std::map<int, int> universe_consumers_;
 | 
					  std::map<int, int> universe_consumers_;
 | 
				
			||||||
  std::map<int, E131Packet> universe_packets_;
 | 
					  std::map<int, E131Packet> universe_packets_;
 | 
				
			||||||
@@ -55,5 +52,3 @@ class E131Component : public esphome::Component {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace e131
 | 
					}  // namespace e131
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_ARDUINO
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
#ifdef USE_ARDUINO
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "e131.h"
 | 
					 | 
				
			||||||
#include "e131_addressable_light_effect.h"
 | 
					#include "e131_addressable_light_effect.h"
 | 
				
			||||||
 | 
					#include "e131.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
@@ -92,5 +90,3 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace e131
 | 
					}  // namespace e131
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_ARDUINO
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ARDUINO
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/components/light/addressable_light_effect.h"
 | 
					#include "esphome/components/light/addressable_light_effect.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,5 +42,3 @@ class E131AddressableLightEffect : public light::AddressableLightEffect {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace e131
 | 
					}  // namespace e131
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_ARDUINO
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,13 @@
 | 
				
			|||||||
#ifdef USE_ARDUINO
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "e131.h"
 | 
					#include "e131.h"
 | 
				
			||||||
 | 
					#include "esphome/components/network/ip_address.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
#include "esphome/core/util.h"
 | 
					#include "esphome/core/util.h"
 | 
				
			||||||
#include "esphome/components/network/ip_address.h"
 | 
					 | 
				
			||||||
#include <cstring>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <lwip/init.h>
 | 
					 | 
				
			||||||
#include <lwip/ip_addr.h>
 | 
					 | 
				
			||||||
#include <lwip/ip4_addr.h>
 | 
					 | 
				
			||||||
#include <lwip/igmp.h>
 | 
					#include <lwip/igmp.h>
 | 
				
			||||||
 | 
					#include <lwip/init.h>
 | 
				
			||||||
 | 
					#include <lwip/ip4_addr.h>
 | 
				
			||||||
 | 
					#include <lwip/ip_addr.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace e131 {
 | 
					namespace e131 {
 | 
				
			||||||
@@ -62,7 +60,7 @@ const size_t E131_MIN_PACKET_SIZE = reinterpret_cast<size_t>(&((E131RawPacket *)
 | 
				
			|||||||
bool E131Component::join_igmp_groups_() {
 | 
					bool E131Component::join_igmp_groups_() {
 | 
				
			||||||
  if (listen_method_ != E131_MULTICAST)
 | 
					  if (listen_method_ != E131_MULTICAST)
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  if (!udp_)
 | 
					  if (this->socket_ == nullptr)
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (auto universe : universe_consumers_) {
 | 
					  for (auto universe : universe_consumers_) {
 | 
				
			||||||
@@ -140,5 +138,3 @@ bool E131Component::packet_(const std::vector<uint8_t> &data, int &universe, E13
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace e131
 | 
					}  // namespace e131
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_ARDUINO
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,39 @@ ESP32_BASE_PINS = {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ESP32_BOARD_PINS = {
 | 
					ESP32_BOARD_PINS = {
 | 
				
			||||||
 | 
					    "adafruit_feather_esp32s2_tft": {
 | 
				
			||||||
 | 
					        "BUTTON": 0,
 | 
				
			||||||
 | 
					        "A0": 18,
 | 
				
			||||||
 | 
					        "A1": 17,
 | 
				
			||||||
 | 
					        "A2": 16,
 | 
				
			||||||
 | 
					        "A3": 15,
 | 
				
			||||||
 | 
					        "A4": 14,
 | 
				
			||||||
 | 
					        "A5": 8,
 | 
				
			||||||
 | 
					        "SCK": 36,
 | 
				
			||||||
 | 
					        "MOSI": 35,
 | 
				
			||||||
 | 
					        "MISO": 37,
 | 
				
			||||||
 | 
					        "RX": 2,
 | 
				
			||||||
 | 
					        "TX": 1,
 | 
				
			||||||
 | 
					        "D13": 13,
 | 
				
			||||||
 | 
					        "D12": 12,
 | 
				
			||||||
 | 
					        "D11": 11,
 | 
				
			||||||
 | 
					        "D10": 10,
 | 
				
			||||||
 | 
					        "D9": 9,
 | 
				
			||||||
 | 
					        "D6": 6,
 | 
				
			||||||
 | 
					        "D5": 5,
 | 
				
			||||||
 | 
					        "NEOPIXEL": 33,
 | 
				
			||||||
 | 
					        "PIN_NEOPIXEL": 33,
 | 
				
			||||||
 | 
					        "NEOPIXEL_POWER": 34,
 | 
				
			||||||
 | 
					        "SCL": 41,
 | 
				
			||||||
 | 
					        "SDA": 42,
 | 
				
			||||||
 | 
					        "TFT_I2C_POWER": 21,
 | 
				
			||||||
 | 
					        "TFT_CS": 7,
 | 
				
			||||||
 | 
					        "TFT_DC": 39,
 | 
				
			||||||
 | 
					        "TFT_RESET": 40,
 | 
				
			||||||
 | 
					        "TFT_BACKLIGHT": 45,
 | 
				
			||||||
 | 
					        "LED": 13,
 | 
				
			||||||
 | 
					        "LED_BUILTIN": 13,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "adafruit_qtpy_esp32c3": {
 | 
					    "adafruit_qtpy_esp32c3": {
 | 
				
			||||||
        "A0": 4,
 | 
					        "A0": 4,
 | 
				
			||||||
        "A1": 3,
 | 
					        "A1": 3,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -244,6 +244,17 @@ void ESP32BLE::dump_config() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
 | 
				
			||||||
 | 
					  uint64_t u = 0;
 | 
				
			||||||
 | 
					  u |= uint64_t(address[0] & 0xFF) << 40;
 | 
				
			||||||
 | 
					  u |= uint64_t(address[1] & 0xFF) << 32;
 | 
				
			||||||
 | 
					  u |= uint64_t(address[2] & 0xFF) << 24;
 | 
				
			||||||
 | 
					  u |= uint64_t(address[3] & 0xFF) << 16;
 | 
				
			||||||
 | 
					  u |= uint64_t(address[4] & 0xFF) << 8;
 | 
				
			||||||
 | 
					  u |= uint64_t(address[5] & 0xFF) << 0;
 | 
				
			||||||
 | 
					  return u;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ESP32BLE *global_ble = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
					ESP32BLE *global_ble = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace esp32_ble
 | 
					}  // namespace esp32_ble
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,8 @@
 | 
				
			|||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace esp32_ble {
 | 
					namespace esp32_ble {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint64_t ble_addr_to_uint64(const esp_bd_addr_t address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NOLINTNEXTLINE(modernize-use-using)
 | 
					// NOLINTNEXTLINE(modernize-use-using)
 | 
				
			||||||
typedef struct {
 | 
					typedef struct {
 | 
				
			||||||
  void *peer_device;
 | 
					  void *peer_device;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -167,7 +167,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
				
			|||||||
        cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation(
 | 
					        cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation(
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger),
 | 
					                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger),
 | 
				
			||||||
                cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
 | 
					                cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address),
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation(
 | 
					        cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation(
 | 
				
			||||||
@@ -223,7 +223,10 @@ async def to_code(config):
 | 
				
			|||||||
    for conf in config.get(CONF_ON_BLE_ADVERTISE, []):
 | 
					    for conf in config.get(CONF_ON_BLE_ADVERTISE, []):
 | 
				
			||||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
					        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
				
			||||||
        if CONF_MAC_ADDRESS in conf:
 | 
					        if CONF_MAC_ADDRESS in conf:
 | 
				
			||||||
            cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex))
 | 
					            addr_list = []
 | 
				
			||||||
 | 
					            for it in conf[CONF_MAC_ADDRESS]:
 | 
				
			||||||
 | 
					                addr_list.append(it.as_hex)
 | 
				
			||||||
 | 
					            cg.add(trigger.set_addresses(addr_list))
 | 
				
			||||||
        await automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf)
 | 
					        await automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf)
 | 
				
			||||||
    for conf in config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE, []):
 | 
					    for conf in config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE, []):
 | 
				
			||||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
					        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,18 +10,22 @@ namespace esp32_ble_tracker {
 | 
				
			|||||||
class ESPBTAdvertiseTrigger : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener {
 | 
					class ESPBTAdvertiseTrigger : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
 | 
					  explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
 | 
				
			||||||
  void set_address(uint64_t address) { this->address_ = address; }
 | 
					  void set_addresses(const std::vector<uint64_t> &addresses) { this->address_vec_ = addresses; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool parse_device(const ESPBTDevice &device) override {
 | 
					  bool parse_device(const ESPBTDevice &device) override {
 | 
				
			||||||
    if (this->address_ && device.address_uint64() != this->address_) {
 | 
					    uint64_t u64_addr = device.address_uint64();
 | 
				
			||||||
      return false;
 | 
					    if (!address_vec_.empty()) {
 | 
				
			||||||
 | 
					      if (std::find(address_vec_.begin(), address_vec_.end(), u64_addr) == address_vec_.end()) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this->trigger(device);
 | 
					    this->trigger(device);
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  uint64_t address_ = 0;
 | 
					  std::vector<uint64_t> address_vec_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BLEServiceDataAdvertiseTrigger : public Trigger<const adv_data_t &>, public ESPBTDeviceListener {
 | 
					class BLEServiceDataAdvertiseTrigger : public Trigger<const adv_data_t &>, public ESPBTDeviceListener {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,17 +34,6 @@ static const char *const TAG = "esp32_ble_tracker";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
ESP32BLETracker *global_esp32_ble_tracker = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
					ESP32BLETracker *global_esp32_ble_tracker = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
 | 
					 | 
				
			||||||
  uint64_t u = 0;
 | 
					 | 
				
			||||||
  u |= uint64_t(address[0] & 0xFF) << 40;
 | 
					 | 
				
			||||||
  u |= uint64_t(address[1] & 0xFF) << 32;
 | 
					 | 
				
			||||||
  u |= uint64_t(address[2] & 0xFF) << 24;
 | 
					 | 
				
			||||||
  u |= uint64_t(address[3] & 0xFF) << 16;
 | 
					 | 
				
			||||||
  u |= uint64_t(address[4] & 0xFF) << 8;
 | 
					 | 
				
			||||||
  u |= uint64_t(address[5] & 0xFF) << 0;
 | 
					 | 
				
			||||||
  return u;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
float ESP32BLETracker::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
 | 
					float ESP32BLETracker::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ESP32BLETracker::setup() {
 | 
					void ESP32BLETracker::setup() {
 | 
				
			||||||
@@ -114,10 +103,20 @@ void ESP32BLETracker::loop() {
 | 
				
			|||||||
    if (this->scan_result_index_ &&  // if it looks like we have a scan result we will take the lock
 | 
					    if (this->scan_result_index_ &&  // if it looks like we have a scan result we will take the lock
 | 
				
			||||||
        xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
 | 
					        xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
 | 
				
			||||||
      uint32_t index = this->scan_result_index_;
 | 
					      uint32_t index = this->scan_result_index_;
 | 
				
			||||||
      if (index) {
 | 
					      if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
 | 
				
			||||||
        if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
 | 
					        ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
 | 
				
			||||||
          ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
 | 
					      }
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
 | 
					      bool bulk_parsed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (auto *listener : this->listeners_) {
 | 
				
			||||||
 | 
					        bulk_parsed |= listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      for (auto *client : this->clients_) {
 | 
				
			||||||
 | 
					        bulk_parsed |= client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!bulk_parsed) {
 | 
				
			||||||
        for (size_t i = 0; i < index; i++) {
 | 
					        for (size_t i = 0; i < index; i++) {
 | 
				
			||||||
          ESPBTDevice device;
 | 
					          ESPBTDevice device;
 | 
				
			||||||
          device.parse_scan_rst(this->scan_result_buffer_[i]);
 | 
					          device.parse_scan_rst(this->scan_result_buffer_[i]);
 | 
				
			||||||
@@ -141,8 +140,8 @@ void ESP32BLETracker::loop() {
 | 
				
			|||||||
            this->print_bt_device_info(device);
 | 
					            this->print_bt_device_info(device);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this->scan_result_index_ = 0;
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      this->scan_result_index_ = 0;
 | 
				
			||||||
      xSemaphoreGive(this->scan_result_lock_);
 | 
					      xSemaphoreGive(this->scan_result_lock_);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -585,7 +584,7 @@ std::string ESPBTDevice::address_str() const {
 | 
				
			|||||||
           this->address_[3], this->address_[4], this->address_[5]);
 | 
					           this->address_[3], this->address_[4], this->address_[5]);
 | 
				
			||||||
  return mac;
 | 
					  return mac;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
uint64_t ESPBTDevice::address_uint64() const { return ble_addr_to_uint64(this->address_); }
 | 
					uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ESP32BLETracker::dump_config() {
 | 
					void ESP32BLETracker::dump_config() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "BLE Tracker:");
 | 
					  ESP_LOGCONFIG(TAG, "BLE Tracker:");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,6 +113,9 @@ class ESPBTDeviceListener {
 | 
				
			|||||||
 public:
 | 
					 public:
 | 
				
			||||||
  virtual void on_scan_end() {}
 | 
					  virtual void on_scan_end() {}
 | 
				
			||||||
  virtual bool parse_device(const ESPBTDevice &device) = 0;
 | 
					  virtual bool parse_device(const ESPBTDevice &device) = 0;
 | 
				
			||||||
 | 
					  virtual bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
  void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
 | 
					  void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								esphome/components/esp32_rmt_led_strip/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/esp32_rmt_led_strip/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										208
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,208 @@
 | 
				
			|||||||
 | 
					#include <cinttypes>
 | 
				
			||||||
 | 
					#include "led_strip.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <esp_attr.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace esp32_rmt_led_strip {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "esp32_rmt_led_strip";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const uint8_t RMT_CLK_DIV = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ESP32RMTLEDStripLightOutput::setup() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size_t buffer_size = this->get_buffer_size_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
				
			||||||
 | 
					  this->buf_ = allocator.allocate(buffer_size);
 | 
				
			||||||
 | 
					  if (this->buf_ == nullptr) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Cannot allocate LED buffer!");
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->effect_data_ = allocator.allocate(this->num_leds_);
 | 
				
			||||||
 | 
					  if (this->effect_data_ == nullptr) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Cannot allocate effect data!");
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE);
 | 
				
			||||||
 | 
					  this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8);  // 8 bits per byte, 1 rmt_item32_t per bit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rmt_config_t config;
 | 
				
			||||||
 | 
					  memset(&config, 0, sizeof(config));
 | 
				
			||||||
 | 
					  config.channel = this->channel_;
 | 
				
			||||||
 | 
					  config.rmt_mode = RMT_MODE_TX;
 | 
				
			||||||
 | 
					  config.gpio_num = gpio_num_t(this->pin_);
 | 
				
			||||||
 | 
					  config.mem_block_num = 1;
 | 
				
			||||||
 | 
					  config.clk_div = RMT_CLK_DIV;
 | 
				
			||||||
 | 
					  config.tx_config.loop_en = false;
 | 
				
			||||||
 | 
					  config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
 | 
				
			||||||
 | 
					  config.tx_config.carrier_en = false;
 | 
				
			||||||
 | 
					  config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
 | 
				
			||||||
 | 
					  config.tx_config.idle_output_en = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (rmt_config(&config) != ESP_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Cannot initialize RMT!");
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (rmt_driver_install(config.channel, 0, 0) != ESP_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Cannot install RMT driver!");
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
 | 
				
			||||||
 | 
					                                                 uint32_t bit1_low) {
 | 
				
			||||||
 | 
					  float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // 0-bit
 | 
				
			||||||
 | 
					  this->bit0_.duration0 = (uint32_t) (ratio * bit0_high);
 | 
				
			||||||
 | 
					  this->bit0_.level0 = 1;
 | 
				
			||||||
 | 
					  this->bit0_.duration1 = (uint32_t) (ratio * bit0_low);
 | 
				
			||||||
 | 
					  this->bit0_.level1 = 0;
 | 
				
			||||||
 | 
					  // 1-bit
 | 
				
			||||||
 | 
					  this->bit1_.duration0 = (uint32_t) (ratio * bit1_high);
 | 
				
			||||||
 | 
					  this->bit1_.level0 = 1;
 | 
				
			||||||
 | 
					  this->bit1_.duration1 = (uint32_t) (ratio * bit1_low);
 | 
				
			||||||
 | 
					  this->bit1_.level1 = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
 | 
				
			||||||
 | 
					  // protect from refreshing too often
 | 
				
			||||||
 | 
					  uint32_t now = micros();
 | 
				
			||||||
 | 
					  if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
 | 
				
			||||||
 | 
					    // try again next loop iteration, so that this change won't get lost
 | 
				
			||||||
 | 
					    this->schedule_show();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->last_refresh_ = now;
 | 
				
			||||||
 | 
					  this->mark_shown_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "Writing RGB values to bus...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "RMT TX timeout");
 | 
				
			||||||
 | 
					    this->status_set_warning();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  delayMicroseconds(50);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size_t buffer_size = this->get_buffer_size_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size_t size = 0;
 | 
				
			||||||
 | 
					  size_t len = 0;
 | 
				
			||||||
 | 
					  uint8_t *psrc = this->buf_;
 | 
				
			||||||
 | 
					  rmt_item32_t *pdest = this->rmt_buf_;
 | 
				
			||||||
 | 
					  while (size < buffer_size) {
 | 
				
			||||||
 | 
					    uint8_t b = *psrc;
 | 
				
			||||||
 | 
					    for (int i = 0; i < 8; i++) {
 | 
				
			||||||
 | 
					      pdest->val = b & (1 << (7 - i)) ? this->bit1_.val : this->bit0_.val;
 | 
				
			||||||
 | 
					      pdest++;
 | 
				
			||||||
 | 
					      len++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    size++;
 | 
				
			||||||
 | 
					    psrc++;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "RMT TX error");
 | 
				
			||||||
 | 
					    this->status_set_warning();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->status_clear_warning();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index) const {
 | 
				
			||||||
 | 
					  int32_t r = 0, g = 0, b = 0;
 | 
				
			||||||
 | 
					  switch (this->rgb_order_) {
 | 
				
			||||||
 | 
					    case ORDER_RGB:
 | 
				
			||||||
 | 
					      r = 0;
 | 
				
			||||||
 | 
					      g = 1;
 | 
				
			||||||
 | 
					      b = 2;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_RBG:
 | 
				
			||||||
 | 
					      r = 0;
 | 
				
			||||||
 | 
					      g = 2;
 | 
				
			||||||
 | 
					      b = 1;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_GRB:
 | 
				
			||||||
 | 
					      r = 1;
 | 
				
			||||||
 | 
					      g = 0;
 | 
				
			||||||
 | 
					      b = 2;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_GBR:
 | 
				
			||||||
 | 
					      r = 2;
 | 
				
			||||||
 | 
					      g = 0;
 | 
				
			||||||
 | 
					      b = 1;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_BGR:
 | 
				
			||||||
 | 
					      r = 2;
 | 
				
			||||||
 | 
					      g = 1;
 | 
				
			||||||
 | 
					      b = 0;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_BRG:
 | 
				
			||||||
 | 
					      r = 1;
 | 
				
			||||||
 | 
					      g = 2;
 | 
				
			||||||
 | 
					      b = 0;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
 | 
				
			||||||
 | 
					  return {this->buf_ + (index * multiplier) + r,
 | 
				
			||||||
 | 
					          this->buf_ + (index * multiplier) + g,
 | 
				
			||||||
 | 
					          this->buf_ + (index * multiplier) + b,
 | 
				
			||||||
 | 
					          this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
 | 
				
			||||||
 | 
					          &this->effect_data_[index],
 | 
				
			||||||
 | 
					          &this->correction_};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ESP32RMTLEDStripLightOutput::dump_config() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:");
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_);
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_);
 | 
				
			||||||
 | 
					  const char *rgb_order;
 | 
				
			||||||
 | 
					  switch (this->rgb_order_) {
 | 
				
			||||||
 | 
					    case ORDER_RGB:
 | 
				
			||||||
 | 
					      rgb_order = "RGB";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_RBG:
 | 
				
			||||||
 | 
					      rgb_order = "RBG";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_GRB:
 | 
				
			||||||
 | 
					      rgb_order = "GRB";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_GBR:
 | 
				
			||||||
 | 
					      rgb_order = "GBR";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_BGR:
 | 
				
			||||||
 | 
					      rgb_order = "BGR";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case ORDER_BRG:
 | 
				
			||||||
 | 
					      rgb_order = "BRG";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      rgb_order = "UNKNOWN";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  RGB Order: %s", rgb_order);
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Max refresh rate: %" PRIu32, *this->max_refresh_rate_);
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Number of LEDs: %u", this->num_leds_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float ESP32RMTLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace esp32_rmt_led_strip
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
							
								
								
									
										87
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/components/light/addressable_light.h"
 | 
				
			||||||
 | 
					#include "esphome/components/light/light_output.h"
 | 
				
			||||||
 | 
					#include "esphome/core/color.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <driver/gpio.h>
 | 
				
			||||||
 | 
					#include <driver/rmt.h>
 | 
				
			||||||
 | 
					#include <esp_err.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace esp32_rmt_led_strip {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum RGBOrder : uint8_t {
 | 
				
			||||||
 | 
					  ORDER_RGB,
 | 
				
			||||||
 | 
					  ORDER_RBG,
 | 
				
			||||||
 | 
					  ORDER_GRB,
 | 
				
			||||||
 | 
					  ORDER_GBR,
 | 
				
			||||||
 | 
					  ORDER_BGR,
 | 
				
			||||||
 | 
					  ORDER_BRG,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void write_state(light::LightState *state) override;
 | 
				
			||||||
 | 
					  float get_setup_priority() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int32_t size() const override { return this->num_leds_; }
 | 
				
			||||||
 | 
					  light::LightTraits get_traits() override {
 | 
				
			||||||
 | 
					    auto traits = light::LightTraits();
 | 
				
			||||||
 | 
					    if (this->is_rgbw_) {
 | 
				
			||||||
 | 
					      traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE});
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      traits.set_supported_color_modes({light::ColorMode::RGB});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return traits;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_pin(uint8_t pin) { this->pin_ = pin; }
 | 
				
			||||||
 | 
					  void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
 | 
				
			||||||
 | 
					  void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Set a maximum refresh rate in µs as some lights do not like being updated too often.
 | 
				
			||||||
 | 
					  void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
 | 
				
			||||||
 | 
					  void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void clear_effect_data() override {
 | 
				
			||||||
 | 
					    for (int i = 0; i < this->size(); i++)
 | 
				
			||||||
 | 
					      this->effect_data_[i] = 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  light::ESPColorView get_view_internal(int32_t index) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t *buf_{nullptr};
 | 
				
			||||||
 | 
					  uint8_t *effect_data_{nullptr};
 | 
				
			||||||
 | 
					  rmt_item32_t *rmt_buf_{nullptr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t pin_;
 | 
				
			||||||
 | 
					  uint16_t num_leds_;
 | 
				
			||||||
 | 
					  bool is_rgbw_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rmt_item32_t bit0_, bit1_;
 | 
				
			||||||
 | 
					  RGBOrder rgb_order_;
 | 
				
			||||||
 | 
					  rmt_channel_t channel_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32_t last_refresh_{0};
 | 
				
			||||||
 | 
					  optional<uint32_t> max_refresh_rate_{};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace esp32_rmt_led_strip
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
							
								
								
									
										151
									
								
								esphome/components/esp32_rmt_led_strip/light.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								esphome/components/esp32_rmt_led_strip/light.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
				
			|||||||
 | 
					from dataclasses import dataclass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome import pins
 | 
				
			||||||
 | 
					from esphome.components import esp32, light
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_CHIPSET,
 | 
				
			||||||
 | 
					    CONF_MAX_REFRESH_RATE,
 | 
				
			||||||
 | 
					    CONF_NUM_LEDS,
 | 
				
			||||||
 | 
					    CONF_OUTPUT_ID,
 | 
				
			||||||
 | 
					    CONF_PIN,
 | 
				
			||||||
 | 
					    CONF_RGB_ORDER,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@jesserockz"]
 | 
				
			||||||
 | 
					DEPENDENCIES = ["esp32"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					esp32_rmt_led_strip_ns = cg.esphome_ns.namespace("esp32_rmt_led_strip")
 | 
				
			||||||
 | 
					ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_(
 | 
				
			||||||
 | 
					    "ESP32RMTLEDStripLightOutput", light.AddressableLight
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RGB_ORDERS = {
 | 
				
			||||||
 | 
					    "RGB": RGBOrder.ORDER_RGB,
 | 
				
			||||||
 | 
					    "RBG": RGBOrder.ORDER_RBG,
 | 
				
			||||||
 | 
					    "GRB": RGBOrder.ORDER_GRB,
 | 
				
			||||||
 | 
					    "GBR": RGBOrder.ORDER_GBR,
 | 
				
			||||||
 | 
					    "BGR": RGBOrder.ORDER_BGR,
 | 
				
			||||||
 | 
					    "BRG": RGBOrder.ORDER_BRG,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class LEDStripTimings:
 | 
				
			||||||
 | 
					    bit0_high: int
 | 
				
			||||||
 | 
					    bit0_low: int
 | 
				
			||||||
 | 
					    bit1_high: int
 | 
				
			||||||
 | 
					    bit1_low: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CHIPSETS = {
 | 
				
			||||||
 | 
					    "WS2812": LEDStripTimings(400, 1000, 1000, 400),
 | 
				
			||||||
 | 
					    "SK6812": LEDStripTimings(300, 900, 600, 600),
 | 
				
			||||||
 | 
					    "APA106": LEDStripTimings(350, 1360, 1360, 350),
 | 
				
			||||||
 | 
					    "SM16703": LEDStripTimings(300, 900, 1360, 350),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_IS_RGBW = "is_rgbw"
 | 
				
			||||||
 | 
					CONF_BIT0_HIGH = "bit0_high"
 | 
				
			||||||
 | 
					CONF_BIT0_LOW = "bit0_low"
 | 
				
			||||||
 | 
					CONF_BIT1_HIGH = "bit1_high"
 | 
				
			||||||
 | 
					CONF_BIT1_LOW = "bit1_low"
 | 
				
			||||||
 | 
					CONF_RMT_CHANNEL = "rmt_channel"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RMT_CHANNELS = {
 | 
				
			||||||
 | 
					    esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7],
 | 
				
			||||||
 | 
					    esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3],
 | 
				
			||||||
 | 
					    esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3],
 | 
				
			||||||
 | 
					    esp32.const.VARIANT_ESP32C3: [0, 1],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _validate_rmt_channel(value):
 | 
				
			||||||
 | 
					    variant = esp32.get_esp32_variant()
 | 
				
			||||||
 | 
					    if variant not in RMT_CHANNELS:
 | 
				
			||||||
 | 
					        raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.")
 | 
				
			||||||
 | 
					    if value not in RMT_CHANNELS[variant]:
 | 
				
			||||||
 | 
					        raise cv.Invalid(
 | 
				
			||||||
 | 
					            f"RMT channel {value} is not supported for ESP32 variant {variant}."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    light.ADDRESSABLE_LIGHT_SCHEMA.extend(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput),
 | 
				
			||||||
 | 
					            cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
 | 
				
			||||||
 | 
					            cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
 | 
				
			||||||
 | 
					            cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
 | 
				
			||||||
 | 
					            cv.Required(CONF_RMT_CHANNEL): _validate_rmt_channel,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
 | 
				
			||||||
 | 
					            cv.Inclusive(
 | 
				
			||||||
 | 
					                CONF_BIT0_HIGH,
 | 
				
			||||||
 | 
					                "custom",
 | 
				
			||||||
 | 
					            ): cv.positive_time_period_microseconds,
 | 
				
			||||||
 | 
					            cv.Inclusive(
 | 
				
			||||||
 | 
					                CONF_BIT0_LOW,
 | 
				
			||||||
 | 
					                "custom",
 | 
				
			||||||
 | 
					            ): cv.positive_time_period_microseconds,
 | 
				
			||||||
 | 
					            cv.Inclusive(
 | 
				
			||||||
 | 
					                CONF_BIT1_HIGH,
 | 
				
			||||||
 | 
					                "custom",
 | 
				
			||||||
 | 
					            ): cv.positive_time_period_microseconds,
 | 
				
			||||||
 | 
					            cv.Inclusive(
 | 
				
			||||||
 | 
					                CONF_BIT1_LOW,
 | 
				
			||||||
 | 
					                "custom",
 | 
				
			||||||
 | 
					            ): cv.positive_time_period_microseconds,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
 | 
				
			||||||
 | 
					    await light.register_light(var, config)
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
 | 
				
			||||||
 | 
					    cg.add(var.set_pin(config[CONF_PIN]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_MAX_REFRESH_RATE in config:
 | 
				
			||||||
 | 
					        cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_CHIPSET in config:
 | 
				
			||||||
 | 
					        chipset = CHIPSETS[config[CONF_CHIPSET]]
 | 
				
			||||||
 | 
					        cg.add(
 | 
				
			||||||
 | 
					            var.set_led_params(
 | 
				
			||||||
 | 
					                chipset.bit0_high,
 | 
				
			||||||
 | 
					                chipset.bit0_low,
 | 
				
			||||||
 | 
					                chipset.bit1_high,
 | 
				
			||||||
 | 
					                chipset.bit1_low,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        cg.add(
 | 
				
			||||||
 | 
					            var.set_led_params(
 | 
				
			||||||
 | 
					                config[CONF_BIT0_HIGH],
 | 
				
			||||||
 | 
					                config[CONF_BIT0_LOW],
 | 
				
			||||||
 | 
					                config[CONF_BIT1_HIGH],
 | 
				
			||||||
 | 
					                config[CONF_BIT1_LOW],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
 | 
				
			||||||
 | 
					    cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cg.add(
 | 
				
			||||||
 | 
					        var.set_rmt_channel(
 | 
				
			||||||
 | 
					            getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}")
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
@@ -106,20 +106,18 @@ void EZOSensor::loop() {
 | 
				
			|||||||
      break;
 | 
					      break;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", buf, EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
 | 
					  ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1], EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if ((buf[0] == 1) || (to_run->command_type == EzoCommandType::EZO_CALIBRATION)) {  // EZO_CALIBRATION returns 0-3
 | 
					  if (buf[0] == 1) {
 | 
				
			||||||
    // some sensors return multiple comma-separated values, terminate string after first one
 | 
					 | 
				
			||||||
    for (size_t i = 1; i < sizeof(buf) - 1; i++) {
 | 
					 | 
				
			||||||
      if (buf[i] == ',') {
 | 
					 | 
				
			||||||
        buf[i] = '\0';
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std::string payload = reinterpret_cast<char *>(&buf[1]);
 | 
					    std::string payload = reinterpret_cast<char *>(&buf[1]);
 | 
				
			||||||
    if (!payload.empty()) {
 | 
					    if (!payload.empty()) {
 | 
				
			||||||
      switch (to_run->command_type) {
 | 
					      switch (to_run->command_type) {
 | 
				
			||||||
        case EzoCommandType::EZO_READ: {
 | 
					        case EzoCommandType::EZO_READ: {
 | 
				
			||||||
 | 
					          // some sensors return multiple comma-separated values, terminate string after first one
 | 
				
			||||||
 | 
					          int start_location = 0;
 | 
				
			||||||
 | 
					          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
				
			||||||
 | 
					            payload.erase(start_location);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          auto val = parse_number<float>(payload);
 | 
					          auto val = parse_number<float>(payload);
 | 
				
			||||||
          if (!val.has_value()) {
 | 
					          if (!val.has_value()) {
 | 
				
			||||||
            ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
 | 
					            ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
 | 
				
			||||||
@@ -154,7 +152,10 @@ void EZOSensor::loop() {
 | 
				
			|||||||
          break;
 | 
					          break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case EzoCommandType::EZO_T: {
 | 
					        case EzoCommandType::EZO_T: {
 | 
				
			||||||
          this->t_callback_.call(payload);
 | 
					          int start_location = 0;
 | 
				
			||||||
 | 
					          if ((start_location = payload.find(',')) != std::string::npos) {
 | 
				
			||||||
 | 
					            this->t_callback_.call(payload.substr(start_location + 1));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case EzoCommandType::EZO_CUSTOM: {
 | 
					        case EzoCommandType::EZO_CUSTOM: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -151,11 +151,13 @@ void FujitsuGeneralClimate::transmit_state() {
 | 
				
			|||||||
    case climate::CLIMATE_FAN_LOW:
 | 
					    case climate::CLIMATE_FAN_LOW:
 | 
				
			||||||
      SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_LOW);
 | 
					      SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_LOW);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_FAN_QUIET:
 | 
				
			||||||
 | 
					      SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_SILENT);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
    case climate::CLIMATE_FAN_AUTO:
 | 
					    case climate::CLIMATE_FAN_AUTO:
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_AUTO);
 | 
					      SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_AUTO);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
      // TODO Quiet / Silent
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Set swing
 | 
					  // Set swing
 | 
				
			||||||
@@ -345,8 +347,9 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
				
			|||||||
    const uint8_t recv_fan_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_FAN_NIBBLE);
 | 
					    const uint8_t recv_fan_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_FAN_NIBBLE);
 | 
				
			||||||
    ESP_LOGV(TAG, "Received fan mode %X", recv_fan_mode);
 | 
					    ESP_LOGV(TAG, "Received fan mode %X", recv_fan_mode);
 | 
				
			||||||
    switch (recv_fan_mode) {
 | 
					    switch (recv_fan_mode) {
 | 
				
			||||||
      // TODO No Quiet / Silent in ESPH
 | 
					 | 
				
			||||||
      case FUJITSU_GENERAL_FAN_SILENT:
 | 
					      case FUJITSU_GENERAL_FAN_SILENT:
 | 
				
			||||||
 | 
					        this->fan_mode = climate::CLIMATE_FAN_QUIET;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
      case FUJITSU_GENERAL_FAN_LOW:
 | 
					      case FUJITSU_GENERAL_FAN_LOW:
 | 
				
			||||||
        this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
					        this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,7 +52,7 @@ class FujitsuGeneralClimate : public climate_ir::ClimateIR {
 | 
				
			|||||||
  FujitsuGeneralClimate()
 | 
					  FujitsuGeneralClimate()
 | 
				
			||||||
      : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true,
 | 
					      : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true,
 | 
				
			||||||
                  {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
 | 
					                  {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
 | 
				
			||||||
                   climate::CLIMATE_FAN_HIGH},
 | 
					                   climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_QUIET},
 | 
				
			||||||
                  {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL,
 | 
					                  {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL,
 | 
				
			||||||
                   climate::CLIMATE_SWING_BOTH}) {}
 | 
					                   climate::CLIMATE_SWING_BOTH}) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										40
									
								
								esphome/components/gp8403/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/gp8403/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from esphome.components import i2c
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID, CONF_VOLTAGE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@jesserockz"]
 | 
				
			||||||
 | 
					DEPENDENCIES = ["i2c"]
 | 
				
			||||||
 | 
					MULTI_CONF = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gp8403_ns = cg.esphome_ns.namespace("gp8403")
 | 
				
			||||||
 | 
					GP8403 = gp8403_ns.class_("GP8403", cg.Component, i2c.I2CDevice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GP8403Voltage = gp8403_ns.enum("GP8403Voltage")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_GP8403_ID = "gp8403_id"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VOLTAGES = {
 | 
				
			||||||
 | 
					    "5V": GP8403Voltage.GP8403_VOLTAGE_5V,
 | 
				
			||||||
 | 
					    "10V": GP8403Voltage.GP8403_VOLTAGE_10V,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
 | 
					    cv.Schema(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(GP8403),
 | 
				
			||||||
 | 
					            cv.Required(CONF_VOLTAGE): cv.enum(VOLTAGES, upper=True),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(cv.COMPONENT_SCHEMA)
 | 
				
			||||||
 | 
					    .extend(i2c.i2c_device_schema(0x58))
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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_voltage(config[CONF_VOLTAGE]))
 | 
				
			||||||
							
								
								
									
										21
									
								
								esphome/components/gp8403/gp8403.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								esphome/components/gp8403/gp8403.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					#include "gp8403.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace gp8403 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "gp8403";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const uint8_t RANGE_REGISTER = 0x01;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void GP8403::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void GP8403::dump_config() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "GP8403:");
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Voltage: %dV", this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10);
 | 
				
			||||||
 | 
					  LOG_I2C_DEVICE(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace gp8403
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										27
									
								
								esphome/components/gp8403/gp8403.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/gp8403/gp8403.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/components/i2c/i2c.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace gp8403 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum GP8403Voltage {
 | 
				
			||||||
 | 
					  GP8403_VOLTAGE_5V = 0x00,
 | 
				
			||||||
 | 
					  GP8403_VOLTAGE_10V = 0x11,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GP8403 : public Component, public i2c::I2CDevice {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_voltage(gp8403::GP8403Voltage voltage) { this->voltage_ = voltage; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  GP8403Voltage voltage_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace gp8403
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										31
									
								
								esphome/components/gp8403/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/gp8403/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from esphome.components import i2c, output
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID, CONF_CHANNEL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .. import gp8403_ns, GP8403, CONF_GP8403_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["gp8403"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GP8403Output = gp8403_ns.class_(
 | 
				
			||||||
 | 
					    "GP8403Output", cg.Component, i2c.I2CDevice, output.FloatOutput
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.declare_id(GP8403Output),
 | 
				
			||||||
 | 
					        cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403),
 | 
				
			||||||
 | 
					        cv.Required(CONF_CHANNEL): cv.one_of(0, 1),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					).extend(cv.COMPONENT_SCHEMA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await output.register_output(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await cg.register_parented(var, config[CONF_GP8403_ID])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cg.add(var.set_channel(config[CONF_CHANNEL]))
 | 
				
			||||||
							
								
								
									
										26
									
								
								esphome/components/gp8403/output/gp8403_output.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/gp8403/output/gp8403_output.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					#include "gp8403_output.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace gp8403 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "gp8403.output";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const uint8_t OUTPUT_REGISTER = 0x02;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void GP8403Output::dump_config() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "GP8403 Output:");
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void GP8403Output::write_state(float state) {
 | 
				
			||||||
 | 
					  uint16_t value = ((uint16_t) (state * 4095)) << 4;
 | 
				
			||||||
 | 
					  i2c::ErrorCode err = this->parent_->write_register(OUTPUT_REGISTER + (2 * this->channel_), (uint8_t *) &value, 2);
 | 
				
			||||||
 | 
					  if (err != i2c::ERROR_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Error writing to GP8403, code %d", err);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace gp8403
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										25
									
								
								esphome/components/gp8403/output/gp8403_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/gp8403/output/gp8403_output.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/components/output/float_output.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/components/gp8403/gp8403.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace gp8403 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GP8403Output : public Component, public output::FloatOutput, public Parented<GP8403> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  float get_setup_priority() const override { return setup_priority::DATA - 1; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_channel(uint8_t channel) { this->channel_ = channel; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void write_state(float state) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  uint8_t channel_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace gp8403
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -16,7 +16,7 @@ void GPSTime::from_tiny_gps_(TinyGPSPlus &tiny_gps) {
 | 
				
			|||||||
  if (tiny_gps.date.year() < 2019)
 | 
					  if (tiny_gps.date.year() < 2019)
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  time::ESPTime val{};
 | 
					  ESPTime val{};
 | 
				
			||||||
  val.year = tiny_gps.date.year();
 | 
					  val.year = tiny_gps.date.year();
 | 
				
			||||||
  val.month = tiny_gps.date.month();
 | 
					  val.month = tiny_gps.date.month();
 | 
				
			||||||
  val.day_of_month = tiny_gps.date.day();
 | 
					  val.day_of_month = tiny_gps.date.day();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,11 +9,42 @@ static const char *const TAG = "growatt_solar";
 | 
				
			|||||||
static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
 | 
					static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
 | 
				
			||||||
static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95};  // indexed with enum GrowattProtocolVersion
 | 
					static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95};  // indexed with enum GrowattProtocolVersion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void GrowattSolar::loop() {
 | 
				
			||||||
 | 
					  // If update() was unable to send we retry until we can send.
 | 
				
			||||||
 | 
					  if (!this->waiting_to_update_)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  update();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void GrowattSolar::update() {
 | 
					void GrowattSolar::update() {
 | 
				
			||||||
 | 
					  // If our last send has had no reply yet, and it wasn't that long ago, do nothing.
 | 
				
			||||||
 | 
					  uint32_t now = millis();
 | 
				
			||||||
 | 
					  if (now - this->last_send_ < this->get_update_interval() / 2) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // The bus might be slow, or there might be other devices, or other components might be talking to our device.
 | 
				
			||||||
 | 
					  if (this->waiting_for_response()) {
 | 
				
			||||||
 | 
					    this->waiting_to_update_ = true;
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->waiting_to_update_ = false;
 | 
				
			||||||
  this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT[this->protocol_version_]);
 | 
					  this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT[this->protocol_version_]);
 | 
				
			||||||
 | 
					  this->last_send_ = millis();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
 | 
					void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
 | 
				
			||||||
 | 
					  // Other components might be sending commands to our device. But we don't get called with enough
 | 
				
			||||||
 | 
					  // context to know what is what. So if we didn't do a send, we ignore the data.
 | 
				
			||||||
 | 
					  if (!this->last_send_)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  this->last_send_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Also ignore the data if the message is too short. Otherwise we will publish invalid values.
 | 
				
			||||||
 | 
					  if (data.size() < MODBUS_REGISTER_COUNT[this->protocol_version_] * 2)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void {
 | 
					  auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void {
 | 
				
			||||||
    if (sensor == nullptr)
 | 
					    if (sensor == nullptr)
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ enum GrowattProtocolVersion {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
 | 
					class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
 | 
					  void loop() override;
 | 
				
			||||||
  void update() override;
 | 
					  void update() override;
 | 
				
			||||||
  void on_modbus_data(const std::vector<uint8_t> &data) override;
 | 
					  void on_modbus_data(const std::vector<uint8_t> &data) override;
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
@@ -55,6 +56,9 @@ class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool waiting_to_update_;
 | 
				
			||||||
 | 
					  uint32_t last_send_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  struct GrowattPhase {
 | 
					  struct GrowattPhase {
 | 
				
			||||||
    sensor::Sensor *voltage_sensor_{nullptr};
 | 
					    sensor::Sensor *voltage_sensor_{nullptr};
 | 
				
			||||||
    sensor::Sensor *current_sensor_{nullptr};
 | 
					    sensor::Sensor *current_sensor_{nullptr};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ from esphome.const import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["i2c"]
 | 
					DEPENDENCIES = ["i2c"]
 | 
				
			||||||
 | 
					CODEOWNERS = ["@freekode"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
hm3301_ns = cg.esphome_ns.namespace("hm3301")
 | 
					hm3301_ns = cg.esphome_ns.namespace("hm3301")
 | 
				
			||||||
HM3301Component = hm3301_ns.class_(
 | 
					HM3301Component = hm3301_ns.class_(
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										38
									
								
								esphome/components/host/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								esphome/components/host/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    KEY_CORE,
 | 
				
			||||||
 | 
					    KEY_FRAMEWORK_VERSION,
 | 
				
			||||||
 | 
					    KEY_TARGET_FRAMEWORK,
 | 
				
			||||||
 | 
					    KEY_TARGET_PLATFORM,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from esphome.core import CORE
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .const import KEY_HOST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# force import gpio to register pin schema
 | 
				
			||||||
 | 
					from .gpio import host_pin_to_code  # noqa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@esphome/core"]
 | 
				
			||||||
 | 
					AUTO_LOAD = ["network"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_core_data(config):
 | 
				
			||||||
 | 
					    CORE.data[KEY_HOST] = {}
 | 
				
			||||||
 | 
					    CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host"
 | 
				
			||||||
 | 
					    CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host"
 | 
				
			||||||
 | 
					    CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0)
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    cv.Schema({}),
 | 
				
			||||||
 | 
					    set_core_data,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    cg.add_build_flag("-DUSE_HOST")
 | 
				
			||||||
 | 
					    cg.add_define("ESPHOME_BOARD", "host")
 | 
				
			||||||
 | 
					    cg.add_platformio_option("platform", "platformio/native")
 | 
				
			||||||
							
								
								
									
										5
									
								
								esphome/components/host/const.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								esphome/components/host/const.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					KEY_HOST = "host"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					host_ns = cg.esphome_ns.namespace("host")
 | 
				
			||||||
							
								
								
									
										77
									
								
								esphome/components/host/core.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								esphome/components/host/core.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					#ifdef USE_HOST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					#include "preferences.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <sched.h>
 | 
				
			||||||
 | 
					#include <time.h>
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					#include <cstdlib>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IRAM_ATTR HOT yield() { ::sched_yield(); }
 | 
				
			||||||
 | 
					uint32_t IRAM_ATTR HOT millis() {
 | 
				
			||||||
 | 
					  struct timespec spec;
 | 
				
			||||||
 | 
					  clock_gettime(CLOCK_MONOTONIC, &spec);
 | 
				
			||||||
 | 
					  time_t seconds = spec.tv_sec;
 | 
				
			||||||
 | 
					  uint32_t ms = round(spec.tv_nsec / 1e6);
 | 
				
			||||||
 | 
					  return ((uint32_t) seconds) * 1000U + ms;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void IRAM_ATTR HOT delay(uint32_t ms) {
 | 
				
			||||||
 | 
					  struct timespec ts;
 | 
				
			||||||
 | 
					  ts.tv_sec = ms / 1000;
 | 
				
			||||||
 | 
					  ts.tv_nsec = (ms % 1000) * 1000000;
 | 
				
			||||||
 | 
					  int res;
 | 
				
			||||||
 | 
					  do {
 | 
				
			||||||
 | 
					    res = nanosleep(&ts, &ts);
 | 
				
			||||||
 | 
					  } while (res != 0 && errno == EINTR);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					uint32_t IRAM_ATTR HOT micros() {
 | 
				
			||||||
 | 
					  struct timespec spec;
 | 
				
			||||||
 | 
					  clock_gettime(CLOCK_MONOTONIC, &spec);
 | 
				
			||||||
 | 
					  time_t seconds = spec.tv_sec;
 | 
				
			||||||
 | 
					  uint32_t us = round(spec.tv_nsec / 1e3);
 | 
				
			||||||
 | 
					  return ((uint32_t) seconds) * 1000000U + us;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
 | 
				
			||||||
 | 
					  struct timespec ts;
 | 
				
			||||||
 | 
					  ts.tv_sec = us / 1000000U;
 | 
				
			||||||
 | 
					  ts.tv_nsec = (us % 1000000U) * 1000U;
 | 
				
			||||||
 | 
					  int res;
 | 
				
			||||||
 | 
					  do {
 | 
				
			||||||
 | 
					    res = nanosleep(&ts, &ts);
 | 
				
			||||||
 | 
					  } while (res != 0 && errno == EINTR);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void arch_restart() { exit(0); }
 | 
				
			||||||
 | 
					void arch_init() {
 | 
				
			||||||
 | 
					  // pass
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void IRAM_ATTR HOT arch_feed_wdt() {
 | 
				
			||||||
 | 
					  // pass
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
 | 
				
			||||||
 | 
					uint32_t arch_get_cpu_cycle_count() {
 | 
				
			||||||
 | 
					  struct timespec spec;
 | 
				
			||||||
 | 
					  clock_gettime(CLOCK_MONOTONIC, &spec);
 | 
				
			||||||
 | 
					  time_t seconds = spec.tv_sec;
 | 
				
			||||||
 | 
					  uint32_t us = spec.tv_nsec;
 | 
				
			||||||
 | 
					  return ((uint32_t) seconds) * 1000000000U + us;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					uint32_t arch_get_cpu_freq_hz() { return 1000000000U; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void setup();
 | 
				
			||||||
 | 
					void loop();
 | 
				
			||||||
 | 
					int main() {
 | 
				
			||||||
 | 
					  esphome::host::setup_preferences();
 | 
				
			||||||
 | 
					  setup();
 | 
				
			||||||
 | 
					  while (true) {
 | 
				
			||||||
 | 
					    loop();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_HOST
 | 
				
			||||||
							
								
								
									
										59
									
								
								esphome/components/host/gpio.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								esphome/components/host/gpio.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					#ifdef USE_HOST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "gpio.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace host {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "host";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ISRPinArg {
 | 
				
			||||||
 | 
					  uint8_t pin;
 | 
				
			||||||
 | 
					  bool inverted;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ISRInternalGPIOPin HostGPIOPin::to_isr() const {
 | 
				
			||||||
 | 
					  auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory)
 | 
				
			||||||
 | 
					  arg->pin = pin_;
 | 
				
			||||||
 | 
					  arg->inverted = inverted_;
 | 
				
			||||||
 | 
					  return ISRInternalGPIOPin((void *) arg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "Attaching interrupt %p to pin %d and mode %d", func, pin_, (uint32_t) type);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string HostGPIOPin::dump_summary() const {
 | 
				
			||||||
 | 
					  char buffer[32];
 | 
				
			||||||
 | 
					  snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
 | 
				
			||||||
 | 
					  return buffer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool HostGPIOPin::digital_read() { return inverted_; }
 | 
				
			||||||
 | 
					void HostGPIOPin::digital_write(bool value) {
 | 
				
			||||||
 | 
					  // pass
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "Setting pin %d to %s", pin_, value != inverted_ ? "HIGH" : "LOW");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void HostGPIOPin::detach_interrupt() const {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace host
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace host;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
 | 
				
			||||||
 | 
					  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
				
			||||||
 | 
					  return arg->inverted;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
 | 
				
			||||||
 | 
					  // pass
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
 | 
				
			||||||
 | 
					  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "Clearing interrupt for pin %d", arg->pin);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_HOST
 | 
				
			||||||
							
								
								
									
										37
									
								
								esphome/components/host/gpio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/host/gpio.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_HOST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace host {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HostGPIOPin : public InternalGPIOPin {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void set_pin(uint8_t pin) { pin_ = pin; }
 | 
				
			||||||
 | 
					  void set_inverted(bool inverted) { inverted_ = inverted; }
 | 
				
			||||||
 | 
					  void set_flags(gpio::Flags flags) { flags_ = flags; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void setup() override { pin_mode(flags_); }
 | 
				
			||||||
 | 
					  void pin_mode(gpio::Flags flags) override;
 | 
				
			||||||
 | 
					  bool digital_read() override;
 | 
				
			||||||
 | 
					  void digital_write(bool value) override;
 | 
				
			||||||
 | 
					  std::string dump_summary() const override;
 | 
				
			||||||
 | 
					  void detach_interrupt() const override;
 | 
				
			||||||
 | 
					  ISRInternalGPIOPin to_isr() const override;
 | 
				
			||||||
 | 
					  uint8_t get_pin() const override { return pin_; }
 | 
				
			||||||
 | 
					  bool is_inverted() const override { return inverted_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t pin_;
 | 
				
			||||||
 | 
					  bool inverted_;
 | 
				
			||||||
 | 
					  gpio::Flags flags_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace host
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_HOST
 | 
				
			||||||
							
								
								
									
										73
									
								
								esphome/components/host/gpio.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								esphome/components/host/gpio.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_INPUT,
 | 
				
			||||||
 | 
					    CONF_INVERTED,
 | 
				
			||||||
 | 
					    CONF_MODE,
 | 
				
			||||||
 | 
					    CONF_NUMBER,
 | 
				
			||||||
 | 
					    CONF_OPEN_DRAIN,
 | 
				
			||||||
 | 
					    CONF_OUTPUT,
 | 
				
			||||||
 | 
					    CONF_PULLDOWN,
 | 
				
			||||||
 | 
					    CONF_PULLUP,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from esphome import pins
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .const import host_ns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_LOGGER = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HostGPIOPin = host_ns.class_("HostGPIOPin", cg.InternalGPIOPin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _translate_pin(value):
 | 
				
			||||||
 | 
					    if isinstance(value, dict) or value is None:
 | 
				
			||||||
 | 
					        raise cv.Invalid(
 | 
				
			||||||
 | 
					            "This variable only supports pin numbers, not full pin schemas "
 | 
				
			||||||
 | 
					            "(with inverted and mode)."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    if isinstance(value, int):
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return int(value)
 | 
				
			||||||
 | 
					    except ValueError:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					    if value.startswith("GPIO"):
 | 
				
			||||||
 | 
					        return cv.int_(value[len("GPIO") :].strip())
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_gpio_pin(value):
 | 
				
			||||||
 | 
					    return _translate_pin(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HOST_PIN_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.declare_id(HostGPIOPin),
 | 
				
			||||||
 | 
					        cv.Required(CONF_NUMBER): validate_gpio_pin,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_MODE, default={}): cv.Schema(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                cv.Optional(CONF_INPUT, default=False): cv.boolean,
 | 
				
			||||||
 | 
					                cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
 | 
				
			||||||
 | 
					                cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
 | 
				
			||||||
 | 
					                cv.Optional(CONF_PULLUP, default=False): cv.boolean,
 | 
				
			||||||
 | 
					                cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_INVERTED, default=False): cv.boolean,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pins.PIN_SCHEMA_REGISTRY.register("host", HOST_PIN_SCHEMA)
 | 
				
			||||||
 | 
					async def host_pin_to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    num = config[CONF_NUMBER]
 | 
				
			||||||
 | 
					    cg.add(var.set_pin(num))
 | 
				
			||||||
 | 
					    cg.add(var.set_inverted(config[CONF_INVERTED]))
 | 
				
			||||||
 | 
					    cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
 | 
				
			||||||
 | 
					    return var
 | 
				
			||||||
							
								
								
									
										36
									
								
								esphome/components/host/preferences.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/host/preferences.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					#ifdef USE_HOST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "preferences.h"
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					#include "esphome/core/preferences.h"
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace host {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "host.preferences";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HostPreferences : public ESPPreferences {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool sync() override { return true; }
 | 
				
			||||||
 | 
					  bool reset() override { return true; }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void setup_preferences() {
 | 
				
			||||||
 | 
					  auto *pref = new HostPreferences();  // NOLINT(cppcoreguidelines-owning-memory)
 | 
				
			||||||
 | 
					  global_preferences = pref;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace host
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ESPPreferences *global_preferences;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_HOST
 | 
				
			||||||
							
								
								
									
										13
									
								
								esphome/components/host/preferences.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								esphome/components/host/preferences.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_HOST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace host {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void setup_preferences();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace host
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_HOST
 | 
				
			||||||
@@ -7,6 +7,30 @@ namespace i2c {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "i2c";
 | 
					static const char *const TAG = "i2c";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ErrorCode I2CDevice::read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop) {
 | 
				
			||||||
 | 
					  ErrorCode err = this->write(&a_register, 1, stop);
 | 
				
			||||||
 | 
					  if (err != ERROR_OK)
 | 
				
			||||||
 | 
					    return err;
 | 
				
			||||||
 | 
					  return bus_->read(address_, data, len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop) {
 | 
				
			||||||
 | 
					  WriteBuffer buffers[2];
 | 
				
			||||||
 | 
					  buffers[0].data = &a_register;
 | 
				
			||||||
 | 
					  buffers[0].len = 1;
 | 
				
			||||||
 | 
					  buffers[1].data = data;
 | 
				
			||||||
 | 
					  buffers[1].len = len;
 | 
				
			||||||
 | 
					  return bus_->writev(address_, buffers, 2, stop);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
 | 
				
			||||||
 | 
					  if (read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2) != ERROR_OK)
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  for (size_t i = 0; i < len; i++)
 | 
				
			||||||
 | 
					    data[i] = i2ctohs(data[i]);
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) {
 | 
					bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) {
 | 
				
			||||||
  // we have to copy in order to be able to change byte order
 | 
					  // we have to copy in order to be able to change byte order
 | 
				
			||||||
  std::unique_ptr<uint16_t[]> temp{new uint16_t[len]};
 | 
					  std::unique_ptr<uint16_t[]> temp{new uint16_t[len]};
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user