mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 08:31:47 +00:00 
			
		
		
		
	Compare commits
	
		
			188 Commits
		
	
	
		
			2024.6.0b4
			...
			2024.7.0b4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					93e0c71c2f | ||
| 
						 | 
					c512d5ebb6 | ||
| 
						 | 
					f153a7b0fd | ||
| 
						 | 
					de43c4e6ab | ||
| 
						 | 
					4af8230b4f | ||
| 
						 | 
					0bbefb5b2a | ||
| 
						 | 
					41baf70660 | ||
| 
						 | 
					eaf2bb70d9 | ||
| 
						 | 
					71236b170d | ||
| 
						 | 
					bb92ab01d7 | ||
| 
						 | 
					316a0e1c96 | ||
| 
						 | 
					0c2f9b9dbb | ||
| 
						 | 
					c6c1d3a3ad | ||
| 
						 | 
					fbab0aceb0 | ||
| 
						 | 
					54b77a1174 | ||
| 
						 | 
					a34cec217e | ||
| 
						 | 
					91bb38553d | ||
| 
						 | 
					531f33a158 | ||
| 
						 | 
					2d826768b0 | ||
| 
						 | 
					d7f6d4436e | ||
| 
						 | 
					bdd0a36aa3 | ||
| 
						 | 
					8a89dac5d5 | ||
| 
						 | 
					8d28c53fd3 | ||
| 
						 | 
					114476d8b1 | ||
| 
						 | 
					d1bfad9890 | ||
| 
						 | 
					04b268e319 | ||
| 
						 | 
					6417f1f907 | ||
| 
						 | 
					2f669c99f8 | ||
| 
						 | 
					aa8c963c50 | ||
| 
						 | 
					2873c6bbaf | ||
| 
						 | 
					2da939c81c | ||
| 
						 | 
					ee398441b6 | ||
| 
						 | 
					894d81c577 | ||
| 
						 | 
					4c6a17e304 | ||
| 
						 | 
					ddaa84683b | ||
| 
						 | 
					dd1e480142 | ||
| 
						 | 
					6ca7b30f75 | ||
| 
						 | 
					b0a3b5e080 | ||
| 
						 | 
					5fa54b0885 | ||
| 
						 | 
					803f3f2e13 | ||
| 
						 | 
					de19588d10 | ||
| 
						 | 
					bc2bc13eb1 | ||
| 
						 | 
					6c96281a1d | ||
| 
						 | 
					3727342bce | ||
| 
						 | 
					fc3f806555 | ||
| 
						 | 
					c013c3bf61 | ||
| 
						 | 
					849a98d5b4 | ||
| 
						 | 
					dd1a72e4d9 | ||
| 
						 | 
					e4e404d54f | ||
| 
						 | 
					ee6f2bfecb | ||
| 
						 | 
					995db1d0e1 | ||
| 
						 | 
					5cb80619dd | ||
| 
						 | 
					0914dc7198 | ||
| 
						 | 
					12f00a9d3d | ||
| 
						 | 
					3fb9c93a24 | ||
| 
						 | 
					d8f0dce08f | ||
| 
						 | 
					5e6c69b930 | ||
| 
						 | 
					83f9664efb | ||
| 
						 | 
					582386d3a2 | ||
| 
						 | 
					7aaa5ce9c8 | ||
| 
						 | 
					5278ae4b5e | ||
| 
						 | 
					b89dea97d9 | ||
| 
						 | 
					715184070d | ||
| 
						 | 
					6294c3b913 | ||
| 
						 | 
					e9cf3623d1 | ||
| 
						 | 
					d0ab2a16a6 | ||
| 
						 | 
					1f5442f1ba | ||
| 
						 | 
					7b3d6747d5 | ||
| 
						 | 
					7904d3b157 | ||
| 
						 | 
					3a48b10757 | ||
| 
						 | 
					0e50cac399 | ||
| 
						 | 
					dc4a93f5d0 | ||
| 
						 | 
					e23153d090 | ||
| 
						 | 
					9a26cdb336 | ||
| 
						 | 
					decf50ed49 | ||
| 
						 | 
					bfdf63055f | ||
| 
						 | 
					cd7894ae8f | ||
| 
						 | 
					10504c4d68 | ||
| 
						 | 
					192718fee6 | ||
| 
						 | 
					855d154439 | ||
| 
						 | 
					300d48a55e | ||
| 
						 | 
					7174cf35dd | ||
| 
						 | 
					0b3145a6df | ||
| 
						 | 
					04225d5717 | ||
| 
						 | 
					86791422f0 | ||
| 
						 | 
					9c2af6318c | ||
| 
						 | 
					c747d7d45d | ||
| 
						 | 
					bbd7c9cf86 | ||
| 
						 | 
					169fb79c97 | ||
| 
						 | 
					1579dfeb80 | ||
| 
						 | 
					d8a6d8594a | ||
| 
						 | 
					7be071a0e9 | ||
| 
						 | 
					01bcf5fb97 | ||
| 
						 | 
					91766afb64 | ||
| 
						 | 
					cc4f1c667e | ||
| 
						 | 
					bc26de2d68 | ||
| 
						 | 
					0179358f9c | ||
| 
						 | 
					d8a5c1ea0c | ||
| 
						 | 
					fb9844463b | ||
| 
						 | 
					481cf7384a | ||
| 
						 | 
					c9a0daf4b6 | ||
| 
						 | 
					8a25bedaf9 | ||
| 
						 | 
					11b8e2e1af | ||
| 
						 | 
					53cfa8d3a1 | ||
| 
						 | 
					0262a99274 | ||
| 
						 | 
					09a947beaa | ||
| 
						 | 
					a6e1ef2dd1 | ||
| 
						 | 
					c5aae8ee25 | ||
| 
						 | 
					5bd5b777a6 | ||
| 
						 | 
					e39961f7f1 | ||
| 
						 | 
					0d3cf5cb78 | ||
| 
						 | 
					96d63de292 | ||
| 
						 | 
					ae2962259e | ||
| 
						 | 
					7dbc20b776 | ||
| 
						 | 
					a21dab334c | ||
| 
						 | 
					78450da6f3 | ||
| 
						 | 
					b1868123db | ||
| 
						 | 
					f7af51b92c | ||
| 
						 | 
					7ee1406f64 | ||
| 
						 | 
					0f49b58e0a | ||
| 
						 | 
					17204baac0 | ||
| 
						 | 
					1e05bcaa61 | ||
| 
						 | 
					18690d51f5 | ||
| 
						 | 
					2aacf14e96 | ||
| 
						 | 
					9c5507ab46 | ||
| 
						 | 
					0a9703bff9 | ||
| 
						 | 
					67bd5db6d6 | ||
| 
						 | 
					6c11f0bd51 | ||
| 
						 | 
					e7556271e7 | ||
| 
						 | 
					8045b889d3 | ||
| 
						 | 
					6f074d3692 | ||
| 
						 | 
					b09781afa5 | ||
| 
						 | 
					1863523cfd | ||
| 
						 | 
					a7a9eb6f71 | ||
| 
						 | 
					c868dae44a | ||
| 
						 | 
					ad8cf69897 | ||
| 
						 | 
					96f1a146a6 | ||
| 
						 | 
					775e03cfd9 | ||
| 
						 | 
					80e5e19956 | ||
| 
						 | 
					8f16268572 | ||
| 
						 | 
					0fe18a6144 | ||
| 
						 | 
					a6d1aa91de | ||
| 
						 | 
					ba11f2ab0c | ||
| 
						 | 
					9747811b82 | ||
| 
						 | 
					ff803aa108 | ||
| 
						 | 
					8bac82f804 | ||
| 
						 | 
					6682451ee0 | ||
| 
						 | 
					c17090c1e5 | ||
| 
						 | 
					acf69bb56f | ||
| 
						 | 
					fd7a212562 | ||
| 
						 | 
					8567877f07 | ||
| 
						 | 
					310f850ee4 | ||
| 
						 | 
					896cdab22d | ||
| 
						 | 
					ed6462fa00 | ||
| 
						 | 
					65b05af014 | ||
| 
						 | 
					c18056bdda | ||
| 
						 | 
					65a79acfb9 | ||
| 
						 | 
					18d331d284 | ||
| 
						 | 
					7d642147c1 | ||
| 
						 | 
					4c313bc198 | ||
| 
						 | 
					a78b2d0128 | ||
| 
						 | 
					f6848fe24d | ||
| 
						 | 
					a59c9b4f77 | ||
| 
						 | 
					c30913ccde | ||
| 
						 | 
					41f810f828 | ||
| 
						 | 
					d604c8ae64 | ||
| 
						 | 
					67d8c7c691 | ||
| 
						 | 
					015cd42a2e | ||
| 
						 | 
					51c5d1714c | ||
| 
						 | 
					1ff302b341 | ||
| 
						 | 
					6b89763ad6 | ||
| 
						 | 
					253303f3a9 | ||
| 
						 | 
					d49f2cbec8 | ||
| 
						 | 
					290816be11 | ||
| 
						 | 
					2fc43fa9c7 | ||
| 
						 | 
					5adadeaa07 | ||
| 
						 | 
					761aae6f89 | ||
| 
						 | 
					b29e1acab8 | ||
| 
						 | 
					49d4260cfe | ||
| 
						 | 
					c4c46c206f | ||
| 
						 | 
					8453d9a70d | ||
| 
						 | 
					68dbf35b09 | ||
| 
						 | 
					1a242f94db | ||
| 
						 | 
					df52bc3493 | ||
| 
						 | 
					2044c7e4d4 | ||
| 
						 | 
					b401b5eca8 | ||
| 
						 | 
					67f41a0c72 | ||
| 
						 | 
					bd7e8fbf86 | 
@@ -1,7 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "ESPHome Dev",
 | 
			
		||||
  "image": "ghcr.io/esphome/esphome-lint:dev",
 | 
			
		||||
  "postCreateCommand": ["script/devcontainer-post-create"],
 | 
			
		||||
  "postCreateCommand": [
 | 
			
		||||
    "script/devcontainer-post-create"
 | 
			
		||||
  ],
 | 
			
		||||
  "containerEnv": {
 | 
			
		||||
    "DEVCONTAINER": "1",
 | 
			
		||||
    "PIP_BREAK_SYSTEM_PACKAGES": "1",
 | 
			
		||||
@@ -27,6 +29,9 @@
 | 
			
		||||
      "extensions": [
 | 
			
		||||
        // python
 | 
			
		||||
        "ms-python.python",
 | 
			
		||||
        "ms-python.pylint",
 | 
			
		||||
        "ms-python.flake8",
 | 
			
		||||
        "ms-python.black-formatter",
 | 
			
		||||
        "visualstudioexptteam.vscodeintellicode",
 | 
			
		||||
        // yaml
 | 
			
		||||
        "redhat.vscode-yaml",
 | 
			
		||||
@@ -38,9 +43,21 @@
 | 
			
		||||
      "settings": {
 | 
			
		||||
        "python.languageServer": "Pylance",
 | 
			
		||||
        "python.pythonPath": "/usr/bin/python3",
 | 
			
		||||
        "python.linting.pylintEnabled": true,
 | 
			
		||||
        "python.linting.enabled": true,
 | 
			
		||||
        "python.formatting.provider": "black",
 | 
			
		||||
        "pylint.args": [
 | 
			
		||||
          "--rcfile=${workspaceFolder}/pyproject.toml"
 | 
			
		||||
        ],
 | 
			
		||||
        "flake8.args": [
 | 
			
		||||
          "--config=${workspaceFolder}/.flake8"
 | 
			
		||||
        ],
 | 
			
		||||
        "black-formatter.args": [
 | 
			
		||||
          "--config",
 | 
			
		||||
          "${workspaceFolder}/pyproject.toml"
 | 
			
		||||
        ],
 | 
			
		||||
        "[python]": {
 | 
			
		||||
          // VS will say "Value is not accepted" before building the devcontainer, but the warning
 | 
			
		||||
          // should go away after build is completed.
 | 
			
		||||
          "editor.defaultFormatter": "ms-python.black-formatter"
 | 
			
		||||
        },
 | 
			
		||||
        "editor.formatOnPaste": false,
 | 
			
		||||
        "editor.formatOnSave": true,
 | 
			
		||||
        "editor.formatOnType": true,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							@@ -46,7 +46,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to ghcr by digest
 | 
			
		||||
      id: build-ghcr
 | 
			
		||||
      uses: docker/build-push-action@v5.4.0
 | 
			
		||||
      uses: docker/build-push-action@v6.3.0
 | 
			
		||||
      with:
 | 
			
		||||
        context: .
 | 
			
		||||
        file: ./docker/Dockerfile
 | 
			
		||||
@@ -69,7 +69,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to dockerhub by digest
 | 
			
		||||
      id: build-dockerhub
 | 
			
		||||
      uses: docker/build-push-action@v5.4.0
 | 
			
		||||
      uses: docker/build-push-action@v6.3.0
 | 
			
		||||
      with:
 | 
			
		||||
        context: .
 | 
			
		||||
        file: ./docker/Dockerfile
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							@@ -17,7 +17,7 @@ runs:
 | 
			
		||||
  steps:
 | 
			
		||||
    - name: Set up Python ${{ inputs.python-version }}
 | 
			
		||||
      id: python
 | 
			
		||||
      uses: actions/setup-python@v5.1.0
 | 
			
		||||
      uses: actions/setup-python@v5.1.1
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: ${{ inputs.python-version }}
 | 
			
		||||
    - name: Restore Python virtual environment
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							@@ -21,7 +21,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.1.0
 | 
			
		||||
        with:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -40,15 +40,15 @@ jobs:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["ha-addon", "docker", "lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.1.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.9"
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.3.0
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.4.0
 | 
			
		||||
      - name: Set up QEMU
 | 
			
		||||
        uses: docker/setup-qemu-action@v3.0.0
 | 
			
		||||
        uses: docker/setup-qemu-action@v3.1.0
 | 
			
		||||
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										100
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -34,7 +34,7 @@ jobs:
 | 
			
		||||
      cache-key: ${{ steps.cache-key.outputs.key }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Generate cache-key
 | 
			
		||||
        id: cache-key
 | 
			
		||||
        run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
 | 
			
		||||
@@ -66,7 +66,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -87,7 +87,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -108,7 +108,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -129,7 +129,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -150,7 +150,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -199,7 +199,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -229,7 +229,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -248,72 +248,6 @@ jobs:
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
  compile-tests-list:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    outputs:
 | 
			
		||||
      matrix: ${{ steps.set-matrix.outputs.matrix }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
      - name: Find all YAML test files
 | 
			
		||||
        id: set-matrix
 | 
			
		||||
        run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
  validate-tests:
 | 
			
		||||
    name: Validate YAML test ${{ matrix.file }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - compile-tests-list
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Run esphome config ${{ matrix.file }}
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          esphome config ${{ matrix.file }}
 | 
			
		||||
 | 
			
		||||
  compile-tests:
 | 
			
		||||
    name: Run YAML test ${{ matrix.file }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - black
 | 
			
		||||
      - ci-custom
 | 
			
		||||
      - clang-format
 | 
			
		||||
      - flake8
 | 
			
		||||
      - pylint
 | 
			
		||||
      - pytest
 | 
			
		||||
      - pyupgrade
 | 
			
		||||
      - compile-tests-list
 | 
			
		||||
      - validate-tests
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      max-parallel: 2
 | 
			
		||||
      matrix:
 | 
			
		||||
        file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Run esphome compile ${{ matrix.file }}
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          esphome compile ${{ matrix.file }}
 | 
			
		||||
 | 
			
		||||
  clang-tidy:
 | 
			
		||||
    name: ${{ matrix.name }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
@@ -358,7 +292,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -387,6 +321,13 @@ jobs:
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
 | 
			
		||||
      - name: Run 'pio run --list-targets -e esp32-idf-tidy'
 | 
			
		||||
        if: matrix.name == 'Run script/clang-tidy for ESP32 IDF'
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          mkdir -p .temp
 | 
			
		||||
          pio run --list-targets -e esp32-idf-tidy
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
@@ -410,7 +351,7 @@ jobs:
 | 
			
		||||
      count: ${{ steps.list-components.outputs.count }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        with:
 | 
			
		||||
          # Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
 | 
			
		||||
          fetch-depth: 500
 | 
			
		||||
@@ -458,7 +399,7 @@ jobs:
 | 
			
		||||
        run: sudo apt-get install libsodium-dev libsdl2-dev
 | 
			
		||||
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -484,7 +425,7 @@ jobs:
 | 
			
		||||
      matrix: ${{ steps.split.outputs.components }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Split components into 20 groups
 | 
			
		||||
        id: split
 | 
			
		||||
        run: |
 | 
			
		||||
@@ -512,7 +453,7 @@ jobs:
 | 
			
		||||
        run: sudo apt-get install libsodium-dev libsdl2-dev
 | 
			
		||||
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -543,7 +484,6 @@ jobs:
 | 
			
		||||
      - pylint
 | 
			
		||||
      - pytest
 | 
			
		||||
      - pyupgrade
 | 
			
		||||
      - compile-tests
 | 
			
		||||
      - clang-tidy
 | 
			
		||||
      - list-components
 | 
			
		||||
      - test-build-components
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -19,7 +19,7 @@ jobs:
 | 
			
		||||
      tag: ${{ steps.tag.outputs.tag }}
 | 
			
		||||
      branch_build: ${{ steps.tag.outputs.branch_build }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Get tag
 | 
			
		||||
        id: tag
 | 
			
		||||
        # yamllint disable rule:line-length
 | 
			
		||||
@@ -51,7 +51,7 @@ jobs:
 | 
			
		||||
      contents: read
 | 
			
		||||
      id-token: write
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.1.0
 | 
			
		||||
        with:
 | 
			
		||||
@@ -65,7 +65,7 @@ jobs:
 | 
			
		||||
          pip3 install build
 | 
			
		||||
          python3 -m build
 | 
			
		||||
      - name: Publish
 | 
			
		||||
        uses: pypa/gh-action-pypi-publish@v1.8.14
 | 
			
		||||
        uses: pypa/gh-action-pypi-publish@v1.9.0
 | 
			
		||||
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build ESPHome ${{ matrix.platform }}
 | 
			
		||||
@@ -83,17 +83,17 @@ jobs:
 | 
			
		||||
          - linux/arm/v7
 | 
			
		||||
          - linux/arm64
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.1.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.9"
 | 
			
		||||
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.3.0
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.4.0
 | 
			
		||||
      - name: Set up QEMU
 | 
			
		||||
        if: matrix.platform != 'linux/amd64'
 | 
			
		||||
        uses: docker/setup-qemu-action@v3.0.0
 | 
			
		||||
        uses: docker/setup-qemu-action@v3.1.0
 | 
			
		||||
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        uses: docker/login-action@v3.2.0
 | 
			
		||||
@@ -141,7 +141,7 @@ jobs:
 | 
			
		||||
          echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - name: Upload digests
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.3
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.4
 | 
			
		||||
        with:
 | 
			
		||||
          name: digests-${{ steps.sanitize.outputs.name }}
 | 
			
		||||
          path: /tmp/digests
 | 
			
		||||
@@ -174,17 +174,17 @@ jobs:
 | 
			
		||||
          - ghcr
 | 
			
		||||
          - dockerhub
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Download digests
 | 
			
		||||
        uses: actions/download-artifact@v4.1.7
 | 
			
		||||
        uses: actions/download-artifact@v4.1.8
 | 
			
		||||
        with:
 | 
			
		||||
          pattern: digests-*
 | 
			
		||||
          path: /tmp/digests
 | 
			
		||||
          merge-multiple: true
 | 
			
		||||
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.3.0
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.4.0
 | 
			
		||||
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        if: matrix.registry == 'dockerhub'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -13,10 +13,10 @@ jobs:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Checkout Home Assistant
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        with:
 | 
			
		||||
          repository: home-assistant/core
 | 
			
		||||
          path: lib/home-assistant
 | 
			
		||||
@@ -36,7 +36,7 @@ jobs:
 | 
			
		||||
          python ./script/sync-device_class.py
 | 
			
		||||
 | 
			
		||||
      - name: Commit changes
 | 
			
		||||
        uses: peter-evans/create-pull-request@v6.0.5
 | 
			
		||||
        uses: peter-evans/create-pull-request@v6.1.0
 | 
			
		||||
        with:
 | 
			
		||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          committer: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/yaml-lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/yaml-lint.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Run yamllint
 | 
			
		||||
        uses: frenck/action-yamllint@v1.5.0
 | 
			
		||||
        with:
 | 
			
		||||
 
 | 
			
		||||
@@ -214,7 +214,7 @@ esphome/components/lightwaverf/* @max246
 | 
			
		||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
			
		||||
esphome/components/lock/* @esphome/core
 | 
			
		||||
esphome/components/logger/* @esphome/core
 | 
			
		||||
esphome/components/ltr390/* @sjtrny
 | 
			
		||||
esphome/components/ltr390/* @latonita @sjtrny
 | 
			
		||||
esphome/components/ltr_als_ps/* @latonita
 | 
			
		||||
esphome/components/matrix_keypad/* @ssieb
 | 
			
		||||
esphome/components/max31865/* @DAVe3283
 | 
			
		||||
 
 | 
			
		||||
@@ -34,28 +34,32 @@ RUN \
 | 
			
		||||
        python3-wheel=0.38.4-2 \
 | 
			
		||||
        iputils-ping=3:20221126-1 \
 | 
			
		||||
        git=1:2.39.2-1.1 \
 | 
			
		||||
        curl=7.88.1-10+deb12u5 \
 | 
			
		||||
        curl=7.88.1-10+deb12u6 \
 | 
			
		||||
        openssh-client=1:9.2p1-2+deb12u2 \
 | 
			
		||||
        python3-cffi=1.15.1-5 \
 | 
			
		||||
        libcairo2=1.16.0-7 \
 | 
			
		||||
        libmagic1=1:5.44-3 \
 | 
			
		||||
        patch=2.7.6-7; \
 | 
			
		||||
    if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
 | 
			
		||||
        patch=2.7.6-7 \
 | 
			
		||||
    && ( \
 | 
			
		||||
        ( \
 | 
			
		||||
            [ "$TARGETARCH$TARGETVARIANT" = "armv7" ] && \
 | 
			
		||||
                apt-get install -y --no-install-recommends \
 | 
			
		||||
                build-essential=12.9 \
 | 
			
		||||
                python3-dev=3.11.2-1+b1 \
 | 
			
		||||
                zlib1g-dev=1:1.2.13.dfsg-1 \
 | 
			
		||||
                libjpeg-dev=1:2.1.5-2 \
 | 
			
		||||
          libfreetype-dev=2.12.1+dfsg-5 \
 | 
			
		||||
          libssl-dev=3.0.11-1~deb12u2 \
 | 
			
		||||
                libfreetype-dev=2.12.1+dfsg-5+deb12u3 \
 | 
			
		||||
                libssl-dev=3.0.13-1~deb12u1 \
 | 
			
		||||
                libffi-dev=3.4.4-1 \
 | 
			
		||||
                libopenjp2-7=2.5.0-2 \
 | 
			
		||||
                libtiff6=4.5.0-6+deb12u1 \
 | 
			
		||||
                cargo=0.66.0+ds1-1 \
 | 
			
		||||
                pkg-config=1.8.1-1 \
 | 
			
		||||
          gcc-arm-linux-gnueabihf=4:12.2.0-3; \
 | 
			
		||||
    fi; \
 | 
			
		||||
    rm -rf \
 | 
			
		||||
                gcc-arm-linux-gnueabihf=4:12.2.0-3 \
 | 
			
		||||
        ) \
 | 
			
		||||
        || [ "$TARGETARCH$TARGETVARIANT" != "armv7" ] \
 | 
			
		||||
    ) \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
        /var/lib/apt/lists/*
 | 
			
		||||
@@ -190,8 +194,8 @@ RUN \
 | 
			
		||||
        clang-format-13=1:13.0.1-11+b2 \
 | 
			
		||||
        clang-tidy-14=1:14.0.6-12 \
 | 
			
		||||
        patch=2.7.6-7 \
 | 
			
		||||
        software-properties-common=0.99.30-4 \
 | 
			
		||||
        nano=7.2-1 \
 | 
			
		||||
        software-properties-common=0.99.30-4.1~deb12u1 \
 | 
			
		||||
        nano=7.2-1+deb12u1 \
 | 
			
		||||
        build-essential=12.9 \
 | 
			
		||||
        python3-dev=3.11.2-1+b1 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,7 @@ from esphome.cpp_types import (  # noqa
 | 
			
		||||
    std_ns,
 | 
			
		||||
    std_shared_ptr,
 | 
			
		||||
    std_string,
 | 
			
		||||
    std_string_ref,
 | 
			
		||||
    std_vector,
 | 
			
		||||
    uint8,
 | 
			
		||||
    uint16,
 | 
			
		||||
 
 | 
			
		||||
@@ -93,8 +93,9 @@ void AHT10Component::restart_read_() {
 | 
			
		||||
 | 
			
		||||
void AHT10Component::read_data_() {
 | 
			
		||||
  uint8_t data[6];
 | 
			
		||||
  if (this->read_count_ > 1)
 | 
			
		||||
  if (this->read_count_ > 1) {
 | 
			
		||||
    ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
 | 
			
		||||
  }
 | 
			
		||||
  if (this->read(data, 6) != i2c::ERROR_OK) {
 | 
			
		||||
    this->status_set_warning("AHT10 read failed, retrying soon");
 | 
			
		||||
    this->restart_read_();
 | 
			
		||||
@@ -119,8 +120,9 @@ void AHT10Component::read_data_() {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (this->read_count_ > 1)
 | 
			
		||||
  if (this->read_count_ > 1) {
 | 
			
		||||
    ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_));
 | 
			
		||||
  }
 | 
			
		||||
  uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
 | 
			
		||||
  uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, esp32
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TEMPERATURE_OFFSET
 | 
			
		||||
from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@trvrnrth"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
@@ -11,7 +11,6 @@ MULTI_CONF = True
 | 
			
		||||
CONF_BME680_BSEC_ID = "bme680_bsec_id"
 | 
			
		||||
CONF_IAQ_MODE = "iaq_mode"
 | 
			
		||||
CONF_SUPPLY_VOLTAGE = "supply_voltage"
 | 
			
		||||
CONF_SAMPLE_RATE = "sample_rate"
 | 
			
		||||
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
 | 
			
		||||
 | 
			
		||||
bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec")
 | 
			
		||||
 
 | 
			
		||||
@@ -4,33 +4,33 @@ from esphome.components import sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_GAS_RESISTANCE,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_IAQ_ACCURACY,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_SAMPLE_RATE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    ICON_GAS_CYLINDER,
 | 
			
		||||
    ICON_GAUGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_HECTOPASCAL,
 | 
			
		||||
    UNIT_OHM,
 | 
			
		||||
    UNIT_PARTS_PER_MILLION,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
    ICON_GAS_CYLINDER,
 | 
			
		||||
    ICON_GAUGE,
 | 
			
		||||
)
 | 
			
		||||
from . import (
 | 
			
		||||
    BME680BSECComponent,
 | 
			
		||||
    CONF_BME680_BSEC_ID,
 | 
			
		||||
    CONF_SAMPLE_RATE,
 | 
			
		||||
    SAMPLE_RATE_OPTIONS,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["bme680_bsec"]
 | 
			
		||||
 | 
			
		||||
CONF_IAQ = "iaq"
 | 
			
		||||
CONF_IAQ_ACCURACY = "iaq_accuracy"
 | 
			
		||||
CONF_CO2_EQUIVALENT = "co2_equivalent"
 | 
			
		||||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
 | 
			
		||||
UNIT_IAQ = "IAQ"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import text_sensor
 | 
			
		||||
from esphome.const import CONF_IAQ_ACCURACY
 | 
			
		||||
from . import BME680BSECComponent, CONF_BME680_BSEC_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["bme680_bsec"]
 | 
			
		||||
 | 
			
		||||
CONF_IAQ_ACCURACY = "iaq_accuracy"
 | 
			
		||||
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
 | 
			
		||||
 | 
			
		||||
TYPES = [CONF_IAQ_ACCURACY]
 | 
			
		||||
 
 | 
			
		||||
@@ -574,21 +574,25 @@ void Climate::dump_traits_(const char *tag) {
 | 
			
		||||
  ESP_LOGCONFIG(tag, "      - Max temperature: %.1f", traits.get_visual_max_temperature());
 | 
			
		||||
  ESP_LOGCONFIG(tag, "      - Temperature step:");
 | 
			
		||||
  ESP_LOGCONFIG(tag, "          Target: %.1f", traits.get_visual_target_temperature_step());
 | 
			
		||||
  if (traits.get_supports_current_temperature()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "          Current: %.1f", traits.get_visual_current_temperature_step());
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_target_humidity() || traits.get_supports_current_humidity()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "      - Min humidity: %.0f", traits.get_visual_min_humidity());
 | 
			
		||||
    ESP_LOGCONFIG(tag, "      - Max humidity: %.0f", traits.get_visual_max_humidity());
 | 
			
		||||
  if (traits.get_supports_current_temperature()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports current temperature");
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_current_humidity()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports current humidity");
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_two_point_target_temperature()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports two-point target temperature");
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_current_temperature()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports current temperature");
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_target_humidity()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports target humidity");
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_current_humidity()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports current humidity");
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_action()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports action");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ class ClimateTraits {
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
 | 
			
		||||
  void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
 | 
			
		||||
  bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
 | 
			
		||||
  std::set<ClimateMode> get_supported_modes() const { return supported_modes_; }
 | 
			
		||||
  const std::set<ClimateMode> &get_supported_modes() const { return supported_modes_; }
 | 
			
		||||
 | 
			
		||||
  void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
 | 
			
		||||
  bool get_supports_action() const { return supports_action_; }
 | 
			
		||||
@@ -101,7 +101,7 @@ class ClimateTraits {
 | 
			
		||||
  void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
 | 
			
		||||
  bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
 | 
			
		||||
  bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); }
 | 
			
		||||
  std::set<ClimateFanMode> get_supported_fan_modes() const { return supported_fan_modes_; }
 | 
			
		||||
  const std::set<ClimateFanMode> &get_supported_fan_modes() const { return supported_fan_modes_; }
 | 
			
		||||
 | 
			
		||||
  void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
 | 
			
		||||
    supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
 | 
			
		||||
@@ -140,7 +140,7 @@ class ClimateTraits {
 | 
			
		||||
  }
 | 
			
		||||
  bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
 | 
			
		||||
  bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
 | 
			
		||||
  std::set<ClimateSwingMode> get_supported_swing_modes() const { return supported_swing_modes_; }
 | 
			
		||||
  const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return supported_swing_modes_; }
 | 
			
		||||
 | 
			
		||||
  float get_visual_min_temperature() const { return visual_min_temperature_; }
 | 
			
		||||
  void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
 | 
			
		||||
 
 | 
			
		||||
@@ -129,13 +129,13 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
 | 
			
		||||
   *
 | 
			
		||||
   * This is a legacy method and may be removed later, please use `.make_call()` instead.
 | 
			
		||||
   */
 | 
			
		||||
  ESPDEPRECATED("open() is deprecated, use make_call().set_command_open() instead.", "2021.9")
 | 
			
		||||
  ESPDEPRECATED("open() is deprecated, use make_call().set_command_open().perform() instead.", "2021.9")
 | 
			
		||||
  void open();
 | 
			
		||||
  /** Close the cover.
 | 
			
		||||
   *
 | 
			
		||||
   * This is a legacy method and may be removed later, please use `.make_call()` instead.
 | 
			
		||||
   */
 | 
			
		||||
  ESPDEPRECATED("close() is deprecated, use make_call().set_command_close() instead.", "2021.9")
 | 
			
		||||
  ESPDEPRECATED("close() is deprecated, use make_call().set_command_close().perform() instead.", "2021.9")
 | 
			
		||||
  void close();
 | 
			
		||||
  /** Stop the cover.
 | 
			
		||||
   *
 | 
			
		||||
 
 | 
			
		||||
@@ -145,10 +145,8 @@ bool DallasTemperatureSensor::check_scratch_pad_() {
 | 
			
		||||
float DallasTemperatureSensor::get_temp_c_() {
 | 
			
		||||
  int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0];
 | 
			
		||||
  if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
 | 
			
		||||
    if (this->scratch_pad_[7] != 0x10)
 | 
			
		||||
      ESP_LOGE(TAG, "unexpected COUNT_PER_C value: %u", this->scratch_pad_[7]);
 | 
			
		||||
    temp = ((temp & 0xfff7) << 3) + (0x10 - this->scratch_pad_[6]) - 4;
 | 
			
		||||
  } else {
 | 
			
		||||
    return (temp >> 1) + (this->scratch_pad_[7] - this->scratch_pad_[6]) / float(this->scratch_pad_[7]) - 0.25;
 | 
			
		||||
  }
 | 
			
		||||
  switch (this->resolution_) {
 | 
			
		||||
    case 9:
 | 
			
		||||
      temp &= 0xfff8;
 | 
			
		||||
@@ -163,7 +161,6 @@ float DallasTemperatureSensor::get_temp_c_() {
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return temp / 16.0f;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_na
 | 
			
		||||
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
 | 
			
		||||
 | 
			
		||||
void DebugComponent::get_device_info_(std::string &device_info) {
 | 
			
		||||
  str::string reset_reason = get_reset_reason_();
 | 
			
		||||
  std::string reset_reason = get_reset_reason_();
 | 
			
		||||
  ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
 | 
			
		||||
  ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
 | 
			
		||||
  ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());
 | 
			
		||||
 
 | 
			
		||||
@@ -37,14 +37,18 @@ void DS1307Component::read_time() {
 | 
			
		||||
    ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  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),
 | 
			
		||||
      .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
 | 
			
		||||
      .day_of_week = uint8_t(ds1307_.reg.weekday),
 | 
			
		||||
      .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
 | 
			
		||||
      .day_of_year = 1,  // ignored by recalc_timestamp_utc(false)
 | 
			
		||||
      .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),
 | 
			
		||||
      .is_dst = false,  // not used
 | 
			
		||||
      .timestamp = 0    // overwritten by recalc_timestamp_utc(false)
 | 
			
		||||
  };
 | 
			
		||||
  rtc_time.recalc_timestamp_utc(false);
 | 
			
		||||
  if (!rtc_time.is_valid()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_VSYNC_PIN,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
from esphome.components.esp32 import add_idf_component
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
@@ -290,8 +290,11 @@ async def to_code(config):
 | 
			
		||||
    cg.add_define("USE_ESP32_CAMERA")
 | 
			
		||||
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        cg.add_library("espressif/esp32-camera", "1.0.0")
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
 | 
			
		||||
        add_idf_component(
 | 
			
		||||
            name="esp32-camera",
 | 
			
		||||
            repo="https://github.com/espressif/esp32-camera.git",
 | 
			
		||||
            ref="v2.0.9",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_STREAM_START, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ from esphome.components.esp32.const import (
 | 
			
		||||
    VARIANT_ESP32S2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32C6,
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -47,6 +48,7 @@ CAN_SPEEDS_ESP32_S2 = {
 | 
			
		||||
 | 
			
		||||
CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2}
 | 
			
		||||
CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2}
 | 
			
		||||
CAN_SPEEDS_ESP32_C6 = {**CAN_SPEEDS_ESP32_S2}
 | 
			
		||||
CAN_SPEEDS_ESP32_H2 = {**CAN_SPEEDS_ESP32_S2}
 | 
			
		||||
 | 
			
		||||
CAN_SPEEDS = {
 | 
			
		||||
@@ -54,6 +56,7 @@ CAN_SPEEDS = {
 | 
			
		||||
    VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2,
 | 
			
		||||
    VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3,
 | 
			
		||||
    VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3,
 | 
			
		||||
    VARIANT_ESP32C6: CAN_SPEEDS_ESP32_C6,
 | 
			
		||||
    VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,17 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
 | 
			
		||||
from esphome.config_helpers import merge_config
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ESPHOME,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_NUM_ATTEMPTS,
 | 
			
		||||
    CONF_OTA,
 | 
			
		||||
    CONF_PASSWORD,
 | 
			
		||||
    CONF_PLATFORM,
 | 
			
		||||
    CONF_PORT,
 | 
			
		||||
    CONF_REBOOT_TIMEOUT,
 | 
			
		||||
    CONF_SAFE_MODE,
 | 
			
		||||
@@ -12,6 +19,8 @@ from esphome.const import (
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
AUTO_LOAD = ["md5", "socket"]
 | 
			
		||||
@@ -21,6 +30,65 @@ esphome = cg.esphome_ns.namespace("esphome")
 | 
			
		||||
ESPHomeOTAComponent = esphome.class_("ESPHomeOTAComponent", OTAComponent)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ota_esphome_final_validate(config):
 | 
			
		||||
    full_conf = fv.full_config.get()
 | 
			
		||||
    full_ota_conf = full_conf[CONF_OTA]
 | 
			
		||||
    new_ota_conf = []
 | 
			
		||||
    merged_ota_esphome_configs_by_port = {}
 | 
			
		||||
    ports_with_merged_configs = []
 | 
			
		||||
    for ota_conf in full_ota_conf:
 | 
			
		||||
        if ota_conf.get(CONF_PLATFORM) == CONF_ESPHOME:
 | 
			
		||||
            if (
 | 
			
		||||
                conf_port := ota_conf.get(CONF_PORT)
 | 
			
		||||
            ) not in merged_ota_esphome_configs_by_port:
 | 
			
		||||
                merged_ota_esphome_configs_by_port[conf_port] = ota_conf
 | 
			
		||||
            else:
 | 
			
		||||
                if merged_ota_esphome_configs_by_port[conf_port][
 | 
			
		||||
                    CONF_VERSION
 | 
			
		||||
                ] != ota_conf.get(CONF_VERSION):
 | 
			
		||||
                    raise cv.Invalid(
 | 
			
		||||
                        f"Found multiple configurations but {CONF_VERSION} is inconsistent"
 | 
			
		||||
                    )
 | 
			
		||||
                if (
 | 
			
		||||
                    merged_ota_esphome_configs_by_port[conf_port][CONF_ID].is_manual
 | 
			
		||||
                    and ota_conf.get(CONF_ID).is_manual
 | 
			
		||||
                ):
 | 
			
		||||
                    raise cv.Invalid(
 | 
			
		||||
                        f"Found multiple configurations but {CONF_ID} is inconsistent"
 | 
			
		||||
                    )
 | 
			
		||||
                if (
 | 
			
		||||
                    CONF_PASSWORD in merged_ota_esphome_configs_by_port[conf_port]
 | 
			
		||||
                    and CONF_PASSWORD in ota_conf
 | 
			
		||||
                    and merged_ota_esphome_configs_by_port[conf_port][CONF_PASSWORD]
 | 
			
		||||
                    != ota_conf.get(CONF_PASSWORD)
 | 
			
		||||
                ):
 | 
			
		||||
                    raise cv.Invalid(
 | 
			
		||||
                        f"Found multiple configurations but {CONF_PASSWORD} is inconsistent"
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                ports_with_merged_configs.append(conf_port)
 | 
			
		||||
                merged_ota_esphome_configs_by_port[conf_port] = merge_config(
 | 
			
		||||
                    merged_ota_esphome_configs_by_port[conf_port], ota_conf
 | 
			
		||||
                )
 | 
			
		||||
        else:
 | 
			
		||||
            new_ota_conf.append(ota_conf)
 | 
			
		||||
 | 
			
		||||
    for port_conf in merged_ota_esphome_configs_by_port.values():
 | 
			
		||||
        new_ota_conf.append(port_conf)
 | 
			
		||||
 | 
			
		||||
    full_conf[CONF_OTA] = new_ota_conf
 | 
			
		||||
    fv.full_config.set(full_conf)
 | 
			
		||||
 | 
			
		||||
    if len(ports_with_merged_configs) > 0:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "Found and merged multiple configurations for %s %s %s port(s) %s",
 | 
			
		||||
            CONF_OTA,
 | 
			
		||||
            CONF_PLATFORM,
 | 
			
		||||
            CONF_ESPHOME,
 | 
			
		||||
            ports_with_merged_configs,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -50,6 +118,8 @@ CONFIG_SCHEMA = (
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(52.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,8 @@ void EthernetComponent::setup() {
 | 
			
		||||
      .intr_flags = 0,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
 | 
			
		||||
    defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
  auto host = SPI2_HOST;
 | 
			
		||||
#else
 | 
			
		||||
  auto host = SPI3_HOST;
 | 
			
		||||
@@ -393,7 +394,7 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b
 | 
			
		||||
  const esp_netif_ip_info_t *ip_info = &event->ip_info;
 | 
			
		||||
  ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip));
 | 
			
		||||
  global_eth_component->got_ipv4_address_ = true;
 | 
			
		||||
#if USE_NETWORK_IPV6
 | 
			
		||||
#if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
 | 
			
		||||
  global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT;
 | 
			
		||||
#else
 | 
			
		||||
  global_eth_component->connected_ = true;
 | 
			
		||||
@@ -406,8 +407,12 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
 | 
			
		||||
  ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
 | 
			
		||||
  ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip));
 | 
			
		||||
  global_eth_component->ipv6_count_ += 1;
 | 
			
		||||
#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
 | 
			
		||||
  global_eth_component->connected_ =
 | 
			
		||||
      global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
 | 
			
		||||
#else
 | 
			
		||||
  global_eth_component->connected_ = global_eth_component->got_ipv4_address_;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
#endif /* USE_NETWORK_IPV6 */
 | 
			
		||||
 | 
			
		||||
@@ -631,7 +636,7 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
 | 
			
		||||
 | 
			
		||||
  if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
 | 
			
		||||
    ESP_LOGD(TAG, "Select PHY Register Page 0x%02" PRIX32, 0x0);
 | 
			
		||||
    ESP_LOGD(TAG, "Select PHY Register Page 0x00");
 | 
			
		||||
    err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
 | 
			
		||||
    ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ from esphome.helpers import (
 | 
			
		||||
    cpp_string_escape,
 | 
			
		||||
)
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    __version__,
 | 
			
		||||
    CONF_FAMILY,
 | 
			
		||||
    CONF_FILE,
 | 
			
		||||
    CONF_GLYPHS,
 | 
			
		||||
@@ -185,31 +184,6 @@ def get_font_path(value, type) -> Path:
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def download_content(url: str, path: Path) -> None:
 | 
			
		||||
    if not external_files.has_remote_file_changed(url, path):
 | 
			
		||||
        _LOGGER.debug("Remote file has not changed %s", url)
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    _LOGGER.debug(
 | 
			
		||||
        "Remote file has changed, downloading from %s to %s",
 | 
			
		||||
        url,
 | 
			
		||||
        path,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        req = requests.get(
 | 
			
		||||
            url,
 | 
			
		||||
            timeout=external_files.NETWORK_TIMEOUT,
 | 
			
		||||
            headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
 | 
			
		||||
        )
 | 
			
		||||
        req.raise_for_status()
 | 
			
		||||
    except requests.exceptions.RequestException as e:
 | 
			
		||||
        raise cv.Invalid(f"Could not download from {url}: {e}")
 | 
			
		||||
 | 
			
		||||
    path.parent.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
    path.write_bytes(req.content)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def download_gfont(value):
 | 
			
		||||
    name = (
 | 
			
		||||
        f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}"
 | 
			
		||||
@@ -236,7 +210,7 @@ def download_gfont(value):
 | 
			
		||||
    ttf_url = match.group(1)
 | 
			
		||||
    _LOGGER.debug("download_gfont: ttf_url=%s", ttf_url)
 | 
			
		||||
 | 
			
		||||
    download_content(ttf_url, path)
 | 
			
		||||
    external_files.download_content(ttf_url, path)
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -244,7 +218,7 @@ def download_web_font(value):
 | 
			
		||||
    url = value[CONF_URL]
 | 
			
		||||
    path = get_font_path(value, TYPE_WEB)
 | 
			
		||||
 | 
			
		||||
    download_content(url, path)
 | 
			
		||||
    external_files.download_content(url, path)
 | 
			
		||||
    _LOGGER.debug("download_web_font: path=%s", path)
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,10 @@ static const char *const TAG = "gpio.one_wire";
 | 
			
		||||
 | 
			
		||||
void GPIOOneWireBus::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up 1-wire bus...");
 | 
			
		||||
  this->t_pin_->setup();
 | 
			
		||||
  // clear bus with 480µs high, otherwise initial reset in search might fail
 | 
			
		||||
  this->t_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
  this->search();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -90,13 +94,15 @@ bool HOT IRAM_ATTR GPIOOneWireBus::read_bit_() {
 | 
			
		||||
 | 
			
		||||
  // measure from start value directly, to get best accurate timing no matter
 | 
			
		||||
  // how long pin_mode/delayMicroseconds took
 | 
			
		||||
  delayMicroseconds(12 - (micros() - start));
 | 
			
		||||
  uint32_t now = micros();
 | 
			
		||||
  if (now - start < 12)
 | 
			
		||||
    delayMicroseconds(12 - (now - start));
 | 
			
		||||
 | 
			
		||||
  // sample bus to read bit from peer
 | 
			
		||||
  bool r = pin_.digital_read();
 | 
			
		||||
 | 
			
		||||
  // read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
 | 
			
		||||
  uint32_t now = micros();
 | 
			
		||||
  now = micros();
 | 
			
		||||
  if (now - start < 60)
 | 
			
		||||
    delayMicroseconds(60 - (now - start));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ MODELS = {
 | 
			
		||||
    "yan": Model.GREE_YAN,
 | 
			
		||||
    "yaa": Model.GREE_YAA,
 | 
			
		||||
    "yac": Model.GREE_YAC,
 | 
			
		||||
    "yac1fb9": Model.GREE_YAC1FB9,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ void GreeClimate::transmit_state() {
 | 
			
		||||
    remote_state[4] |= (this->horizontal_swing_() << 4);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->model_ == GREE_YAA || this->model_ == GREE_YAC) {
 | 
			
		||||
  if (this->model_ == GREE_YAA || this->model_ == GREE_YAC || this->model_ == GREE_YAC1FB9) {
 | 
			
		||||
    remote_state[2] = 0x20;  // bits 0..3 always 0000, bits 4..7 TURBO,LIGHT,HEALTH,X-FAN
 | 
			
		||||
    remote_state[3] = 0x50;  // bits 4..7 always 0101
 | 
			
		||||
    remote_state[6] = 0x20;  // YAA1FB, FAA1FB1, YB1F2 bits 4..7 always 0010
 | 
			
		||||
@@ -53,7 +53,11 @@ void GreeClimate::transmit_state() {
 | 
			
		||||
  data->set_carrier_frequency(GREE_IR_FREQUENCY);
 | 
			
		||||
 | 
			
		||||
  data->mark(GREE_HEADER_MARK);
 | 
			
		||||
  if (this->model_ == GREE_YAC1FB9) {
 | 
			
		||||
    data->space(GREE_YAC1FB9_HEADER_SPACE);
 | 
			
		||||
  } else {
 | 
			
		||||
    data->space(GREE_HEADER_SPACE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i < 4; i++) {
 | 
			
		||||
    for (uint8_t mask = 1; mask > 0; mask <<= 1) {  // iterate through bit mask
 | 
			
		||||
@@ -71,7 +75,11 @@ void GreeClimate::transmit_state() {
 | 
			
		||||
  data->space(GREE_ZERO_SPACE);
 | 
			
		||||
 | 
			
		||||
  data->mark(GREE_BIT_MARK);
 | 
			
		||||
  if (this->model_ == GREE_YAC1FB9) {
 | 
			
		||||
    data->space(GREE_YAC1FB9_MESSAGE_SPACE);
 | 
			
		||||
  } else {
 | 
			
		||||
    data->space(GREE_MESSAGE_SPACE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (int i = 4; i < 8; i++) {
 | 
			
		||||
    for (uint8_t mask = 1; mask > 0; mask <<= 1) {  // iterate through bit mask
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,10 @@ const uint32_t GREE_YAC_HEADER_MARK = 6000;
 | 
			
		||||
const uint32_t GREE_YAC_HEADER_SPACE = 3000;
 | 
			
		||||
const uint32_t GREE_YAC_BIT_MARK = 650;
 | 
			
		||||
 | 
			
		||||
// Timing specific to YAC1FB9
 | 
			
		||||
const uint32_t GREE_YAC1FB9_HEADER_SPACE = 4500;
 | 
			
		||||
const uint32_t GREE_YAC1FB9_MESSAGE_SPACE = 19980;
 | 
			
		||||
 | 
			
		||||
// State Frame size
 | 
			
		||||
const uint8_t GREE_STATE_FRAME_SIZE = 8;
 | 
			
		||||
 | 
			
		||||
@@ -67,7 +71,7 @@ const uint8_t GREE_HDIR_MRIGHT = 0x05;
 | 
			
		||||
const uint8_t GREE_HDIR_RIGHT = 0x06;
 | 
			
		||||
 | 
			
		||||
// Model codes
 | 
			
		||||
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC };
 | 
			
		||||
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9 };
 | 
			
		||||
 | 
			
		||||
class GreeClimate : public climate_ir::ClimateIR {
 | 
			
		||||
 public:
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ SENSOR_TYPES = {
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
 | 
			
		||||
        cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
 | 
			
		||||
    }
 | 
			
		||||
).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()})
 | 
			
		||||
 | 
			
		||||
@@ -64,8 +64,8 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_HAIER_ID])
 | 
			
		||||
 | 
			
		||||
    for type, _ in SENSOR_TYPES.items():
 | 
			
		||||
        if conf := config.get(type):
 | 
			
		||||
    for type_ in SENSOR_TYPES:
 | 
			
		||||
        if conf := config.get(type_):
 | 
			
		||||
            sens = await binary_sensor.new_binary_sensor(conf)
 | 
			
		||||
            binary_sensor_type = getattr(BinarySensorTypeEnum, type.upper())
 | 
			
		||||
            binary_sensor_type = getattr(BinarySensorTypeEnum, type_.upper())
 | 
			
		||||
            cg.add(paren.set_sub_binary_sensor(binary_sensor_type, sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ ICON_SPRAY_BOTTLE = "mdi:spray-bottle"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
 | 
			
		||||
        cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
 | 
			
		||||
        cv.Optional(CONF_SELF_CLEANING): button.button_schema(
 | 
			
		||||
            SelfCleaningButton,
 | 
			
		||||
            icon=ICON_SPRAY_BOTTLE,
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,9 @@ PROTOCOL_MAX_TEMPERATURE = 30.0
 | 
			
		||||
PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0
 | 
			
		||||
PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5
 | 
			
		||||
PROTOCOL_CONTROL_PACKET_SIZE = 10
 | 
			
		||||
PROTOCOL_MIN_SENSORS_PACKET_SIZE = 18
 | 
			
		||||
PROTOCOL_DEFAULT_SENSORS_PACKET_SIZE = 22
 | 
			
		||||
PROTOCOL_STATUS_MESSAGE_HEADER_SIZE = 0
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@paveldn"]
 | 
			
		||||
DEPENDENCIES = ["climate", "uart"]
 | 
			
		||||
@@ -48,6 +51,9 @@ CONF_CONTROL_PACKET_SIZE = "control_packet_size"
 | 
			
		||||
CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow"
 | 
			
		||||
CONF_ON_ALARM_START = "on_alarm_start"
 | 
			
		||||
CONF_ON_ALARM_END = "on_alarm_end"
 | 
			
		||||
CONF_ON_STATUS_MESSAGE = "on_status_message"
 | 
			
		||||
CONF_SENSORS_PACKET_SIZE = "sensors_packet_size"
 | 
			
		||||
CONF_STATUS_MESSAGE_HEADER_SIZE = "status_message_header_size"
 | 
			
		||||
CONF_VERTICAL_AIRFLOW = "vertical_airflow"
 | 
			
		||||
CONF_WIFI_SIGNAL = "wifi_signal"
 | 
			
		||||
 | 
			
		||||
@@ -129,6 +135,11 @@ HaierAlarmEndTrigger = haier_ns.class_(
 | 
			
		||||
    automation.Trigger.template(cg.uint8, cg.const_char_ptr),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
StatusMessageTrigger = haier_ns.class_(
 | 
			
		||||
    "StatusMessageTrigger",
 | 
			
		||||
    automation.Trigger.template(cg.const_char_ptr, cg.size_t),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_visual(config):
 | 
			
		||||
    if CONF_VISUAL in config:
 | 
			
		||||
@@ -183,7 +194,6 @@ BASE_CONFIG_SCHEMA = (
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_SUPPORTED_SWING_MODES,
 | 
			
		||||
                default=[
 | 
			
		||||
                    "OFF",
 | 
			
		||||
                    "VERTICAL",
 | 
			
		||||
                    "HORIZONTAL",
 | 
			
		||||
                    "BOTH",
 | 
			
		||||
@@ -194,6 +204,11 @@ BASE_CONFIG_SCHEMA = (
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_ANSWER_TIMEOUT,
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StatusMessageTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(uart.UART_DEVICE_SCHEMA)
 | 
			
		||||
@@ -211,7 +226,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                    ): cv.boolean,
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_SUPPORTED_PRESETS,
 | 
			
		||||
                        default=list(["BOOST", "COMFORT"]),  # No AWAY by default
 | 
			
		||||
                        default=["BOOST", "COMFORT"],  # No AWAY by default
 | 
			
		||||
                    ): cv.ensure_list(
 | 
			
		||||
                        cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True)
 | 
			
		||||
                    ),
 | 
			
		||||
@@ -229,9 +244,17 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE
 | 
			
		||||
                    ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50),
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_SENSORS_PACKET_SIZE,
 | 
			
		||||
                        default=PROTOCOL_DEFAULT_SENSORS_PACKET_SIZE,
 | 
			
		||||
                    ): cv.int_range(min=PROTOCOL_MIN_SENSORS_PACKET_SIZE, max=50),
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_STATUS_MESSAGE_HEADER_SIZE,
 | 
			
		||||
                        default=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE,
 | 
			
		||||
                    ): cv.int_range(min=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE),
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_SUPPORTED_PRESETS,
 | 
			
		||||
                        default=list(["BOOST", "ECO", "SLEEP"]),  # No AWAY by default
 | 
			
		||||
                        default=["BOOST", "ECO", "SLEEP"],  # No AWAY by default
 | 
			
		||||
                    ): cv.ensure_list(
 | 
			
		||||
                        cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
 | 
			
		||||
                    ),
 | 
			
		||||
@@ -427,11 +450,7 @@ def _final_validate(config):
 | 
			
		||||
            "No logger component found, logging for Haier protocol is disabled"
 | 
			
		||||
        )
 | 
			
		||||
        cg.add_build_flag("-DHAIER_LOG_LEVEL=0")
 | 
			
		||||
    if (
 | 
			
		||||
        (CONF_WIFI_SIGNAL in config)
 | 
			
		||||
        and (config[CONF_WIFI_SIGNAL])
 | 
			
		||||
        and CONF_WIFI not in full_config
 | 
			
		||||
    ):
 | 
			
		||||
    if config.get(CONF_WIFI_SIGNAL) and CONF_WIFI not in full_config:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"No WiFi configured, if you want to use haier climate without WiFi add {CONF_WIFI_SIGNAL}: false to climate configuration"
 | 
			
		||||
        )
 | 
			
		||||
@@ -473,6 +492,16 @@ async def to_code(config):
 | 
			
		||||
                config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    if CONF_SENSORS_PACKET_SIZE in config:
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_extra_sensors_packet_bytes_size(
 | 
			
		||||
                config[CONF_SENSORS_PACKET_SIZE] - PROTOCOL_MIN_SENSORS_PACKET_SIZE
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    if CONF_STATUS_MESSAGE_HEADER_SIZE in config:
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_status_message_header_size(config[CONF_STATUS_MESSAGE_HEADER_SIZE])
 | 
			
		||||
        )
 | 
			
		||||
    for conf in config.get(CONF_ON_ALARM_START, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
@@ -483,5 +512,10 @@ async def to_code(config):
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
 | 
			
		||||
        )
 | 
			
		||||
    for conf in config.get(CONF_ON_STATUS_MESSAGE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            trigger, [(cg.const_char_ptr, "data"), (cg.size_t, "data_size")], conf
 | 
			
		||||
        )
 | 
			
		||||
    # https://github.com/paveldn/HaierProtocol
 | 
			
		||||
    cg.add_library("pavlodn/HaierProtocol", "0.9.28")
 | 
			
		||||
    cg.add_library("pavlodn/HaierProtocol", "0.9.31")
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,10 @@ void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &m
 | 
			
		||||
  this->action_request_ = PendingAction({ActionRequest::SEND_CUSTOM_COMMAND, message});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HaierClimateBase::add_status_message_callback(std::function<void(const char *, size_t)> &&callback) {
 | 
			
		||||
  this->status_message_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(
 | 
			
		||||
    haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type,
 | 
			
		||||
    haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
#include <set>
 | 
			
		||||
#include "esphome/components/climate/climate.h"
 | 
			
		||||
#include "esphome/components/uart/uart.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
// HaierProtocol
 | 
			
		||||
#include <protocol/haier_protocol.h>
 | 
			
		||||
 | 
			
		||||
@@ -56,6 +57,7 @@ class HaierClimateBase : public esphome::Component,
 | 
			
		||||
  void set_answer_timeout(uint32_t timeout);
 | 
			
		||||
  void set_send_wifi(bool send_wifi);
 | 
			
		||||
  void send_custom_command(const haier_protocol::HaierMessage &message);
 | 
			
		||||
  void add_status_message_callback(std::function<void(const char *, size_t)> &&callback);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  enum class ProtocolPhases {
 | 
			
		||||
@@ -140,11 +142,19 @@ class HaierClimateBase : public esphome::Component,
 | 
			
		||||
  esphome::climate::ClimateTraits traits_;
 | 
			
		||||
  HvacSettings current_hvac_settings_;
 | 
			
		||||
  HvacSettings next_hvac_settings_;
 | 
			
		||||
  std::unique_ptr<uint8_t[]> last_status_message_;
 | 
			
		||||
  std::unique_ptr<uint8_t[]> last_status_message_{nullptr};
 | 
			
		||||
  std::chrono::steady_clock::time_point last_request_timestamp_;       // For interval between messages
 | 
			
		||||
  std::chrono::steady_clock::time_point last_valid_status_timestamp_;  // For protocol timeout
 | 
			
		||||
  std::chrono::steady_clock::time_point last_status_request_;          // To request AC status
 | 
			
		||||
  std::chrono::steady_clock::time_point last_signal_request_;          // To send WiFI signal level
 | 
			
		||||
  CallbackManager<void(const char *, size_t)> status_message_callback_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class StatusMessageTrigger : public Trigger<const char *, size_t> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit StatusMessageTrigger(HaierClimateBase *parent) {
 | 
			
		||||
    parent->add_status_message_callback([this](const char *data, size_t data_size) { this->trigger(data, data_size); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace haier
 | 
			
		||||
 
 | 
			
		||||
@@ -18,12 +18,13 @@ constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64;
 | 
			
		||||
constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
 | 
			
		||||
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
 | 
			
		||||
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
 | 
			
		||||
const uint8_t ONE_BUF[] = {0x00, 0x01};
 | 
			
		||||
const uint8_t ZERO_BUF[] = {0x00, 0x00};
 | 
			
		||||
 | 
			
		||||
HonClimate::HonClimate()
 | 
			
		||||
    : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
 | 
			
		||||
                                                                                                   0x00, 0x00, 0x00,
 | 
			
		||||
                                                                                                   0x00, 0x00} {
 | 
			
		||||
  last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]);
 | 
			
		||||
  this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID;
 | 
			
		||||
  this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
 | 
			
		||||
}
 | 
			
		||||
@@ -169,11 +170,18 @@ haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameTy
 | 
			
		||||
      this->action_request_.reset();
 | 
			
		||||
      this->force_send_control_ = false;
 | 
			
		||||
    } else {
 | 
			
		||||
      if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) {
 | 
			
		||||
        memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
      if (!this->last_status_message_) {
 | 
			
		||||
        this->real_control_packet_size_ = sizeof(hon_protocol::HaierPacketControl) + this->extra_control_packet_bytes_;
 | 
			
		||||
        this->real_sensors_packet_size_ = sizeof(hon_protocol::HaierPacketSensors) + this->extra_sensors_packet_bytes_;
 | 
			
		||||
        this->last_status_message_.reset();
 | 
			
		||||
        this->last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[this->real_control_packet_size_]);
 | 
			
		||||
      };
 | 
			
		||||
      if (data_size >= this->real_control_packet_size_ + 2) {
 | 
			
		||||
        memcpy(this->last_status_message_.get(), data + 2 + this->status_message_header_size_,
 | 
			
		||||
               this->real_control_packet_size_);
 | 
			
		||||
        this->status_message_callback_.call((const char *) data, data_size);
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size,
 | 
			
		||||
                 sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
        ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_);
 | 
			
		||||
      }
 | 
			
		||||
      switch (this->protocol_phase_) {
 | 
			
		||||
        case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
 | 
			
		||||
@@ -479,8 +487,8 @@ void HonClimate::initialization() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
haier_protocol::HaierMessage HonClimate::get_control_message() {
 | 
			
		||||
  uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
 | 
			
		||||
  memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
  uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
 | 
			
		||||
  memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
 | 
			
		||||
  hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
 | 
			
		||||
  control_out_buffer[4] = 0;  // This byte should be cleared before setting values
 | 
			
		||||
  bool has_hvac_settings = false;
 | 
			
		||||
@@ -636,7 +644,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
 | 
			
		||||
  out_data->health_mode = this->health_mode_ ? 1 : 0;
 | 
			
		||||
  return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                      (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
 | 
			
		||||
                                      control_out_buffer, sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
                                      control_out_buffer, this->real_control_packet_size_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) {
 | 
			
		||||
@@ -758,15 +766,17 @@ void HonClimate::update_sub_text_sensor_(SubTextSensorType type, const std::stri
 | 
			
		||||
#endif  // USE_TEXT_SENSOR
 | 
			
		||||
 | 
			
		||||
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
 | 
			
		||||
  size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) +
 | 
			
		||||
                         this->extra_control_packet_bytes_;
 | 
			
		||||
  if (size < expected_size)
 | 
			
		||||
  size_t expected_size =
 | 
			
		||||
      2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_;
 | 
			
		||||
  if (size < expected_size) {
 | 
			
		||||
    ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size);
 | 
			
		||||
    return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
 | 
			
		||||
  }
 | 
			
		||||
  uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
 | 
			
		||||
  if ((subtype == 0x7D01) && (size >= expected_size + 4 + sizeof(hon_protocol::HaierPacketBigData))) {
 | 
			
		||||
  if ((subtype == 0x7D01) && (size >= expected_size + sizeof(hon_protocol::HaierPacketBigData))) {
 | 
			
		||||
    // Got BigData packet
 | 
			
		||||
    const hon_protocol::HaierPacketBigData *bd_packet =
 | 
			
		||||
        (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size + 4]);
 | 
			
		||||
        (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size]);
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
    this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20);
 | 
			
		||||
    this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
 | 
			
		||||
@@ -795,9 +805,9 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
 | 
			
		||||
    hon_protocol::HaierPacketControl control;
 | 
			
		||||
    hon_protocol::HaierPacketSensors sensors;
 | 
			
		||||
  } packet;
 | 
			
		||||
  memcpy(&packet.control, packet_buffer + 2, sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
  memcpy(&packet.sensors,
 | 
			
		||||
         packet_buffer + 2 + sizeof(hon_protocol::HaierPacketControl) + this->extra_control_packet_bytes_,
 | 
			
		||||
  memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_,
 | 
			
		||||
         sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
  memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_,
 | 
			
		||||
         sizeof(hon_protocol::HaierPacketSensors));
 | 
			
		||||
  if (packet.sensors.error_status != 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
 | 
			
		||||
@@ -996,8 +1006,6 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
  static uint8_t one_buf[] = {0x00, 0x01};
 | 
			
		||||
  static uint8_t zero_buf[] = {0x00, 0x00};
 | 
			
		||||
  if (!this->current_hvac_settings_.valid && !this->force_send_control_)
 | 
			
		||||
    return;
 | 
			
		||||
  this->clear_control_messages_queue_();
 | 
			
		||||
@@ -1009,7 +1017,7 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS,
 | 
			
		||||
                                     this->beeper_status_ ? zero_buf : one_buf, 2));
 | 
			
		||||
                                     this->beeper_status_ ? ZERO_BUF : ONE_BUF, 2));
 | 
			
		||||
  }
 | 
			
		||||
  // Health mode
 | 
			
		||||
  {
 | 
			
		||||
@@ -1017,7 +1025,7 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::HEALTH_MODE,
 | 
			
		||||
                                     this->health_mode_ ? one_buf : zero_buf, 2));
 | 
			
		||||
                                     this->health_mode_ ? ONE_BUF : ZERO_BUF, 2));
 | 
			
		||||
  }
 | 
			
		||||
  // Climate mode
 | 
			
		||||
  bool new_power = this->mode != CLIMATE_MODE_OFF;
 | 
			
		||||
@@ -1092,7 +1100,7 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::AC_POWER,
 | 
			
		||||
                                     new_power ? one_buf : zero_buf, 2));
 | 
			
		||||
                                     new_power ? ONE_BUF : ZERO_BUF, 2));
 | 
			
		||||
  }
 | 
			
		||||
  // CLimate preset
 | 
			
		||||
  {
 | 
			
		||||
@@ -1165,6 +1173,35 @@ void HonClimate::fill_control_messages_queue_() {
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::SET_POINT,
 | 
			
		||||
                                     buffer, 2));
 | 
			
		||||
  }
 | 
			
		||||
  // Vertical swing mode
 | 
			
		||||
  if (climate_control.swing_mode.has_value()) {
 | 
			
		||||
    uint8_t vertical_swing_buf[] = {0x00, (uint8_t) hon_protocol::VerticalSwingMode::AUTO};
 | 
			
		||||
    uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) hon_protocol::HorizontalSwingMode::AUTO};
 | 
			
		||||
    switch (climate_control.swing_mode.value()) {
 | 
			
		||||
      case CLIMATE_SWING_OFF:
 | 
			
		||||
        horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
 | 
			
		||||
        vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
 | 
			
		||||
        break;
 | 
			
		||||
      case CLIMATE_SWING_VERTICAL:
 | 
			
		||||
        horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
 | 
			
		||||
        break;
 | 
			
		||||
      case CLIMATE_SWING_HORIZONTAL:
 | 
			
		||||
        vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
 | 
			
		||||
        break;
 | 
			
		||||
      case CLIMATE_SWING_BOTH:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    this->control_messages_queue_.push(
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE,
 | 
			
		||||
                                     horizontal_swing_buf, 2));
 | 
			
		||||
    this->control_messages_queue_.push(
 | 
			
		||||
        haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                     (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                         (uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE,
 | 
			
		||||
                                     vertical_swing_buf, 2));
 | 
			
		||||
  }
 | 
			
		||||
  // Fan mode
 | 
			
		||||
  if (climate_control.fan_mode.has_value()) {
 | 
			
		||||
    switch (climate_control.fan_mode.value()) {
 | 
			
		||||
@@ -1202,9 +1239,10 @@ void HonClimate::clear_control_messages_queue_() {
 | 
			
		||||
 | 
			
		||||
bool HonClimate::prepare_pending_action() {
 | 
			
		||||
  switch (this->action_request_.value().action) {
 | 
			
		||||
    case ActionRequest::START_SELF_CLEAN: {
 | 
			
		||||
      uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
 | 
			
		||||
      memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
    case ActionRequest::START_SELF_CLEAN:
 | 
			
		||||
      if (this->control_method_ == HonControlMethod::SET_GROUP_PARAMETERS) {
 | 
			
		||||
        uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
 | 
			
		||||
        memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
 | 
			
		||||
        hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
 | 
			
		||||
        out_data->self_cleaning_status = 1;
 | 
			
		||||
        out_data->steri_clean = 0;
 | 
			
		||||
@@ -1216,12 +1254,23 @@ bool HonClimate::prepare_pending_action() {
 | 
			
		||||
        out_data->light_status = 0;
 | 
			
		||||
        this->action_request_.value().message = haier_protocol::HaierMessage(
 | 
			
		||||
            haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
 | 
			
		||||
          control_out_buffer, sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
    }
 | 
			
		||||
            control_out_buffer, this->real_control_packet_size_);
 | 
			
		||||
        return true;
 | 
			
		||||
    case ActionRequest::START_STERI_CLEAN: {
 | 
			
		||||
      uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
 | 
			
		||||
      memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
      } else if (this->control_method_ == HonControlMethod::SET_SINGLE_PARAMETER) {
 | 
			
		||||
        this->action_request_.value().message =
 | 
			
		||||
            haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
 | 
			
		||||
                                         (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
 | 
			
		||||
                                             (uint8_t) hon_protocol::DataParameters::SELF_CLEANING,
 | 
			
		||||
                                         ONE_BUF, 2);
 | 
			
		||||
        return true;
 | 
			
		||||
      } else {
 | 
			
		||||
        this->action_request_.reset();
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    case ActionRequest::START_STERI_CLEAN:
 | 
			
		||||
      if (this->control_method_ == HonControlMethod::SET_GROUP_PARAMETERS) {
 | 
			
		||||
        uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
 | 
			
		||||
        memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
 | 
			
		||||
        hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
 | 
			
		||||
        out_data->self_cleaning_status = 0;
 | 
			
		||||
        out_data->steri_clean = 1;
 | 
			
		||||
@@ -1233,9 +1282,13 @@ bool HonClimate::prepare_pending_action() {
 | 
			
		||||
        out_data->light_status = 0;
 | 
			
		||||
        this->action_request_.value().message = haier_protocol::HaierMessage(
 | 
			
		||||
            haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
 | 
			
		||||
          control_out_buffer, sizeof(hon_protocol::HaierPacketControl));
 | 
			
		||||
    }
 | 
			
		||||
            control_out_buffer, this->real_control_packet_size_);
 | 
			
		||||
        return true;
 | 
			
		||||
      } else {
 | 
			
		||||
        // No Steri clean support (yet?) in SET_SINGLE_PARAMETER
 | 
			
		||||
        this->action_request_.reset();
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    default:
 | 
			
		||||
      return HaierClimateBase::prepare_pending_action();
 | 
			
		||||
  }
 | 
			
		||||
@@ -1251,6 +1304,7 @@ void HonClimate::process_protocol_reset() {
 | 
			
		||||
#endif  // USE_SENSOR
 | 
			
		||||
  this->got_valid_outdoor_temp_ = false;
 | 
			
		||||
  this->hvac_hardware_info_.reset();
 | 
			
		||||
  this->last_status_message_.reset(nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HonClimate::should_get_big_data_() {
 | 
			
		||||
 
 | 
			
		||||
@@ -104,6 +104,8 @@ class HonClimate : public HaierClimateBase {
 | 
			
		||||
  void start_self_cleaning();
 | 
			
		||||
  void start_steri_cleaning();
 | 
			
		||||
  void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; };
 | 
			
		||||
  void set_extra_sensors_packet_bytes_size(size_t size) { this->extra_sensors_packet_bytes_ = size; };
 | 
			
		||||
  void set_status_message_header_size(size_t size) { this->status_message_header_size_ = size; };
 | 
			
		||||
  void set_control_method(HonControlMethod method) { this->control_method_ = method; };
 | 
			
		||||
  void add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback);
 | 
			
		||||
  void add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback);
 | 
			
		||||
@@ -158,7 +160,11 @@ class HonClimate : public HaierClimateBase {
 | 
			
		||||
  esphome::optional<hon_protocol::HorizontalSwingMode> pending_horizontal_direction_{};
 | 
			
		||||
  esphome::optional<HardwareInfo> hvac_hardware_info_{};
 | 
			
		||||
  uint8_t active_alarms_[8];
 | 
			
		||||
  int extra_control_packet_bytes_;
 | 
			
		||||
  int extra_control_packet_bytes_{0};
 | 
			
		||||
  int extra_sensors_packet_bytes_{4};
 | 
			
		||||
  int status_message_header_size_{0};
 | 
			
		||||
  int real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)};
 | 
			
		||||
  int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4};
 | 
			
		||||
  HonControlMethod control_method_;
 | 
			
		||||
  std::queue<haier_protocol::HaierMessage> control_messages_queue_;
 | 
			
		||||
  CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{};
 | 
			
		||||
 
 | 
			
		||||
@@ -41,15 +41,20 @@ enum class ConditioningMode : uint8_t {
 | 
			
		||||
enum class DataParameters : uint8_t {
 | 
			
		||||
  AC_POWER = 0x01,
 | 
			
		||||
  SET_POINT = 0x02,
 | 
			
		||||
  VERTICAL_SWING_MODE = 0x03,
 | 
			
		||||
  AC_MODE = 0x04,
 | 
			
		||||
  FAN_MODE = 0x05,
 | 
			
		||||
  USE_FAHRENHEIT = 0x07,
 | 
			
		||||
  DISPLAY_STATUS = 0x09,
 | 
			
		||||
  TEN_DEGREE = 0x0A,
 | 
			
		||||
  HEALTH_MODE = 0x0B,
 | 
			
		||||
  HORIZONTAL_SWING_MODE = 0x0C,
 | 
			
		||||
  SELF_CLEANING = 0x0D,
 | 
			
		||||
  BEEPER_STATUS = 0x16,
 | 
			
		||||
  LOCK_REMOTE = 0x17,
 | 
			
		||||
  QUIET_MODE = 0x19,
 | 
			
		||||
  FAST_MODE = 0x1A,
 | 
			
		||||
  SLEEP_MODE = 0x1B,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 };
 | 
			
		||||
 
 | 
			
		||||
@@ -137,16 +137,16 @@ SENSOR_TYPES = {
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
 | 
			
		||||
        cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
 | 
			
		||||
    }
 | 
			
		||||
).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()})
 | 
			
		||||
).extend({cv.Optional(type_): schema for type_, schema in SENSOR_TYPES.items()})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_HAIER_ID])
 | 
			
		||||
 | 
			
		||||
    for type, _ in SENSOR_TYPES.items():
 | 
			
		||||
        if conf := config.get(type):
 | 
			
		||||
    for type_ in SENSOR_TYPES:
 | 
			
		||||
        if conf := config.get(type_):
 | 
			
		||||
            sens = await sensor.new_sensor(conf)
 | 
			
		||||
            sensor_type = getattr(SensorTypeEnum, type.upper())
 | 
			
		||||
            sensor_type = getattr(SensorTypeEnum, type_.upper())
 | 
			
		||||
            cg.add(paren.set_sub_sensor(sensor_type, sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(haier_protocol::F
 | 
			
		||||
    } else {
 | 
			
		||||
      if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) {
 | 
			
		||||
        memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl));
 | 
			
		||||
        this->status_message_callback_.call((const char *) data, data_size);
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size,
 | 
			
		||||
                 sizeof(smartair2_protocol::HaierPacketControl));
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ TEXT_SENSOR_TYPES = {
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
 | 
			
		||||
        cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
 | 
			
		||||
    }
 | 
			
		||||
).extend({cv.Optional(type): schema for type, schema in TEXT_SENSOR_TYPES.items()})
 | 
			
		||||
 | 
			
		||||
@@ -47,8 +47,8 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_HAIER_ID])
 | 
			
		||||
 | 
			
		||||
    for type, _ in TEXT_SENSOR_TYPES.items():
 | 
			
		||||
        if conf := config.get(type):
 | 
			
		||||
    for type_ in TEXT_SENSOR_TYPES:
 | 
			
		||||
        if conf := config.get(type_):
 | 
			
		||||
            sens = await text_sensor.new_text_sensor(conf)
 | 
			
		||||
            text_sensor_type = getattr(TextSensorTypeEnum, type.upper())
 | 
			
		||||
            text_sensor_type = getattr(TextSensorTypeEnum, type_.upper())
 | 
			
		||||
            cg.add(paren.set_sub_text_sensor(text_sensor_type, sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_PROTOCOL,
 | 
			
		||||
    CONF_VISUAL,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@rob-deutsch"]
 | 
			
		||||
 | 
			
		||||
@@ -34,6 +33,7 @@ PROTOCOLS = {
 | 
			
		||||
    "greeyan": Protocol.PROTOCOL_GREEYAN,
 | 
			
		||||
    "greeyac": Protocol.PROTOCOL_GREEYAC,
 | 
			
		||||
    "greeyt": Protocol.PROTOCOL_GREEYT,
 | 
			
		||||
    "greeyap": Protocol.PROTOCOL_GREEYAP,
 | 
			
		||||
    "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
 | 
			
		||||
    "hitachi": Protocol.PROTOCOL_HITACHI,
 | 
			
		||||
    "hyundai": Protocol.PROTOCOL_HYUNDAI,
 | 
			
		||||
@@ -61,6 +61,16 @@ PROTOCOLS = {
 | 
			
		||||
    "toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
 | 
			
		||||
    "toshiba": Protocol.PROTOCOL_TOSHIBA,
 | 
			
		||||
    "zhlt01": Protocol.PROTOCOL_ZHLT01,
 | 
			
		||||
    "nibe": Protocol.PROTOCOL_NIBE,
 | 
			
		||||
    "carrier_qlima_1": Protocol.PROTOCOL_QLIMA_1,
 | 
			
		||||
    "carrier_qlima_2": Protocol.PROTOCOL_QLIMA_2,
 | 
			
		||||
    "samsung_aqv12msan": Protocol.PROTOCOL_SAMSUNG_AQV12MSAN,
 | 
			
		||||
    "zhjg01": Protocol.PROTOCOL_ZHJG01,
 | 
			
		||||
    "airway": Protocol.PROTOCOL_AIRWAY,
 | 
			
		||||
    "bgh_aud": Protocol.PROTOCOL_BGH_AUD,
 | 
			
		||||
    "panasonic_altdke": Protocol.PROTOCOL_PANASONIC_ALTDKE,
 | 
			
		||||
    "vaillantvai8": Protocol.PROTOCOL_VAILLANTVAI8,
 | 
			
		||||
    "r51m": Protocol.PROTOCOL_R51M,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
 | 
			
		||||
@@ -116,7 +126,4 @@ def to_code(config):
 | 
			
		||||
    cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
 | 
			
		||||
    cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
 | 
			
		||||
 | 
			
		||||
    cg.add_library("tonia/HeatpumpIR", "1.0.23")
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266 or CORE.is_esp32:
 | 
			
		||||
        cg.add_library("crankyoldgit/IRremoteESP8266", "2.8.4")
 | 
			
		||||
    cg.add_library("tonia/HeatpumpIR", "1.0.27")
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
 | 
			
		||||
    {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_GREEYT, []() { return new GreeYTHeatpumpIR(); }},                              // NOLINT
 | 
			
		||||
    {PROTOCOL_GREEYAP, []() { return new GreeYAPHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }},                        // NOLINT
 | 
			
		||||
    {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
@@ -55,6 +56,16 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
 | 
			
		||||
    {PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }},         // NOLINT
 | 
			
		||||
    {PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }},                              // NOLINT
 | 
			
		||||
    {PROTOCOL_NIBE, []() { return new NibeHeatpumpIR(); }},                                  // NOLINT
 | 
			
		||||
    {PROTOCOL_QLIMA_1, []() { return new Qlima1HeatpumpIR(); }},                             // NOLINT
 | 
			
		||||
    {PROTOCOL_QLIMA_2, []() { return new Qlima2HeatpumpIR(); }},                             // NOLINT
 | 
			
		||||
    {PROTOCOL_SAMSUNG_AQV12MSAN, []() { return new SamsungAQV12MSANHeatpumpIR(); }},         // NOLINT
 | 
			
		||||
    {PROTOCOL_ZHJG01, []() { return new ZHJG01HeatpumpIR(); }},                              // NOLINT
 | 
			
		||||
    {PROTOCOL_AIRWAY, []() { return new AIRWAYHeatpumpIR(); }},                              // NOLINT
 | 
			
		||||
    {PROTOCOL_BGH_AUD, []() { return new BGHHeatpumpIR(); }},                                // NOLINT
 | 
			
		||||
    {PROTOCOL_PANASONIC_ALTDKE, []() { return new PanasonicAltDKEHeatpumpIR(); }},           // NOLINT
 | 
			
		||||
    {PROTOCOL_VAILLANTVAI8, []() { return new VaillantHeatpumpIR(); }},                      // NOLINT
 | 
			
		||||
    {PROTOCOL_R51M, []() { return new R51MHeatpumpIR(); }},                                  // NOLINT
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void HeatpumpIRClimate::setup() {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ enum Protocol {
 | 
			
		||||
  PROTOCOL_GREEYAN,
 | 
			
		||||
  PROTOCOL_GREEYAC,
 | 
			
		||||
  PROTOCOL_GREEYT,
 | 
			
		||||
  PROTOCOL_GREEYAP,
 | 
			
		||||
  PROTOCOL_HISENSE_AUD,
 | 
			
		||||
  PROTOCOL_HITACHI,
 | 
			
		||||
  PROTOCOL_HYUNDAI,
 | 
			
		||||
@@ -55,6 +56,16 @@ enum Protocol {
 | 
			
		||||
  PROTOCOL_TOSHIBA_DAISEIKAI,
 | 
			
		||||
  PROTOCOL_TOSHIBA,
 | 
			
		||||
  PROTOCOL_ZHLT01,
 | 
			
		||||
  PROTOCOL_NIBE,
 | 
			
		||||
  PROTOCOL_QLIMA_1,
 | 
			
		||||
  PROTOCOL_QLIMA_2,
 | 
			
		||||
  PROTOCOL_SAMSUNG_AQV12MSAN,
 | 
			
		||||
  PROTOCOL_ZHJG01,
 | 
			
		||||
  PROTOCOL_AIRWAY,
 | 
			
		||||
  PROTOCOL_BGH_AUD,
 | 
			
		||||
  PROTOCOL_PANASONIC_ALTDKE,
 | 
			
		||||
  PROTOCOL_VAILLANTVAI8,
 | 
			
		||||
  PROTOCOL_R51M,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Simple enum to represent horizontal directios
 | 
			
		||||
 
 | 
			
		||||
@@ -257,7 +257,7 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
            trigger,
 | 
			
		||||
            [
 | 
			
		||||
                (cg.std_shared_ptr.template(HttpContainer), "response"),
 | 
			
		||||
                (cg.std_string, "body"),
 | 
			
		||||
                (cg.std_string_ref, "body"),
 | 
			
		||||
            ],
 | 
			
		||||
            conf,
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -43,10 +43,10 @@ class HttpContainer : public Parented<HttpRequestComponent> {
 | 
			
		||||
  bool secure_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string> {
 | 
			
		||||
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
 | 
			
		||||
 public:
 | 
			
		||||
  void process(std::shared_ptr<HttpContainer> container, std::string response_body) {
 | 
			
		||||
    this->trigger(std::move(container), std::move(response_body));
 | 
			
		||||
  void process(std::shared_ptr<HttpContainer> container, std::string &response_body) {
 | 
			
		||||
    this->trigger(std::move(container), response_body);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -149,11 +149,21 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
        }
 | 
			
		||||
        response_body.reserve(read_index);
 | 
			
		||||
        response_body.assign((char *) buf, read_index);
 | 
			
		||||
        allocator.deallocate(buf, max_length);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this->response_triggers_.size() == 1) {
 | 
			
		||||
      // if there is only one trigger, no need to copy the response body
 | 
			
		||||
      this->response_triggers_[0]->process(container, response_body);
 | 
			
		||||
    } else {
 | 
			
		||||
      for (auto *trigger : this->response_triggers_) {
 | 
			
		||||
      trigger->process(container, response_body);
 | 
			
		||||
        // with multiple triggers, pass a copy of the response body to each
 | 
			
		||||
        // one so that modifications made in one trigger are not visible to
 | 
			
		||||
        // the others
 | 
			
		||||
        auto response_body_copy = std::string(response_body);
 | 
			
		||||
        trigger->process(container, response_body_copy);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    container->end();
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,13 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s
 | 
			
		||||
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
  if (this->follow_redirects_) {
 | 
			
		||||
    container->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
 | 
			
		||||
    container->client_.setRedirectLimit(this->redirect_limit_);
 | 
			
		||||
  } else {
 | 
			
		||||
    container->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP8266)
 | 
			
		||||
  std::unique_ptr<WiFiClient> stream_ptr;
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
@@ -59,8 +66,6 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s
 | 
			
		||||
                  "in your YAML, or use HTTPS");
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ARDUINO_VERSION_CODE
 | 
			
		||||
 | 
			
		||||
  container->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
 | 
			
		||||
  bool status = container->client_.begin(*stream_ptr, url.c_str());
 | 
			
		||||
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
 | 
			
		||||
    int write_left = body_len;
 | 
			
		||||
    int write_index = 0;
 | 
			
		||||
    const char *buf = body.c_str();
 | 
			
		||||
    while (body_len > 0) {
 | 
			
		||||
    while (write_left > 0) {
 | 
			
		||||
      int written = esp_http_client_write(client, buf + write_index, write_left);
 | 
			
		||||
      if (written < 0) {
 | 
			
		||||
        err = ESP_FAIL;
 | 
			
		||||
 
 | 
			
		||||
@@ -116,19 +116,18 @@ void HttpRequestUpdate::update() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string current_version = this->current_version_;
 | 
			
		||||
  if (current_version.empty()) {
 | 
			
		||||
  std::string current_version;
 | 
			
		||||
#ifdef ESPHOME_PROJECT_VERSION
 | 
			
		||||
  current_version = ESPHOME_PROJECT_VERSION;
 | 
			
		||||
#else
 | 
			
		||||
  current_version = ESPHOME_VERSION;
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->update_info_.current_version = current_version;
 | 
			
		||||
 | 
			
		||||
  if (this->update_info_.latest_version.empty()) {
 | 
			
		||||
  if (this->update_info_.latest_version.empty() || this->update_info_.latest_version == update_info_.current_version) {
 | 
			
		||||
    this->state_ = update::UPDATE_STATE_NO_UPDATE;
 | 
			
		||||
  } else if (this->update_info_.latest_version != this->current_version_) {
 | 
			
		||||
  } else {
 | 
			
		||||
    this->state_ = update::UPDATE_STATE_AVAILABLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,15 +22,12 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
 | 
			
		||||
  void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
 | 
			
		||||
  void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; }
 | 
			
		||||
 | 
			
		||||
  void set_current_version(const std::string ¤t_version) { this->current_version_ = current_version; }
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  HttpRequestComponent *request_parent_;
 | 
			
		||||
  OtaHttpRequestComponent *ota_parent_;
 | 
			
		||||
  std::string source_url_;
 | 
			
		||||
  std::string current_version_{""};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
 | 
			
		||||
  };
 | 
			
		||||
  esp_task_wdt_reconfigure(&wdt_config);
 | 
			
		||||
#else
 | 
			
		||||
  esp_task_wdt_init(timeout_ms, true);
 | 
			
		||||
  esp_task_wdt_init(timeout_ms / 1000, true);
 | 
			
		||||
#endif  // ESP_IDF_VERSION_MAJOR
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,10 @@ CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin"
 | 
			
		||||
CONF_I2S_AUDIO = "i2s_audio"
 | 
			
		||||
CONF_I2S_AUDIO_ID = "i2s_audio_id"
 | 
			
		||||
 | 
			
		||||
CONF_I2S_MODE = "i2s_mode"
 | 
			
		||||
CONF_PRIMARY = "primary"
 | 
			
		||||
CONF_SECONDARY = "secondary"
 | 
			
		||||
 | 
			
		||||
i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio")
 | 
			
		||||
I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component)
 | 
			
		||||
I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent))
 | 
			
		||||
@@ -32,6 +36,12 @@ I2SAudioOut = i2s_audio_ns.class_(
 | 
			
		||||
    "I2SAudioOut", cg.Parented.template(I2SAudioComponent)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
i2s_mode_t = cg.global_ns.enum("i2s_mode_t")
 | 
			
		||||
I2S_MODE_OPTIONS = {
 | 
			
		||||
    CONF_PRIMARY: i2s_mode_t.I2S_MODE_MASTER,  # NOLINT
 | 
			
		||||
    CONF_SECONDARY: i2s_mode_t.I2S_MODE_SLAVE,  # NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h
 | 
			
		||||
I2S_PORTS = {
 | 
			
		||||
    VARIANT_ESP32: 2,
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,14 @@ import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER
 | 
			
		||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER, CONF_SAMPLE_RATE
 | 
			
		||||
from esphome.components import microphone, esp32
 | 
			
		||||
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
 | 
			
		||||
 | 
			
		||||
from .. import (
 | 
			
		||||
    CONF_I2S_MODE,
 | 
			
		||||
    CONF_PRIMARY,
 | 
			
		||||
    I2S_MODE_OPTIONS,
 | 
			
		||||
    i2s_audio_ns,
 | 
			
		||||
    I2SAudioComponent,
 | 
			
		||||
    I2SAudioIn,
 | 
			
		||||
@@ -20,7 +23,6 @@ DEPENDENCIES = ["i2s_audio"]
 | 
			
		||||
CONF_ADC_PIN = "adc_pin"
 | 
			
		||||
CONF_ADC_TYPE = "adc_type"
 | 
			
		||||
CONF_PDM = "pdm"
 | 
			
		||||
CONF_SAMPLE_RATE = "sample_rate"
 | 
			
		||||
CONF_BITS_PER_SAMPLE = "bits_per_sample"
 | 
			
		||||
CONF_USE_APLL = "use_apll"
 | 
			
		||||
 | 
			
		||||
@@ -69,6 +71,9 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
 | 
			
		||||
            _validate_bits, cv.enum(BITS_PER_SAMPLE)
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
 | 
			
		||||
            I2S_MODE_OPTIONS, lower=True
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
@@ -108,6 +113,7 @@ async def to_code(config):
 | 
			
		||||
        cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
 | 
			
		||||
        cg.add(var.set_pdm(config[CONF_PDM]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_i2s_mode(config[CONF_I2S_MODE]))
 | 
			
		||||
    cg.add(var.set_channel(config[CONF_CHANNEL]))
 | 
			
		||||
    cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
 | 
			
		||||
    cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ void I2SAudioMicrophone::start_() {
 | 
			
		||||
    return;  // Waiting for another i2s to return lock
 | 
			
		||||
  }
 | 
			
		||||
  i2s_driver_config_t config = {
 | 
			
		||||
      .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX),
 | 
			
		||||
      .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
 | 
			
		||||
      .sample_rate = this->sample_rate_,
 | 
			
		||||
      .bits_per_sample = this->bits_per_sample_,
 | 
			
		||||
      .channel_format = this->channel_,
 | 
			
		||||
@@ -174,8 +174,7 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
 | 
			
		||||
    size_t samples_read = bytes_read / sizeof(int32_t);
 | 
			
		||||
    samples.resize(samples_read);
 | 
			
		||||
    for (size_t i = 0; i < samples_read; i++) {
 | 
			
		||||
      int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
 | 
			
		||||
      samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
 | 
			
		||||
      samples[i] = reinterpret_cast<int32_t *>(buf)[i] >> 16;
 | 
			
		||||
    }
 | 
			
		||||
    memcpy(buf, samples.data(), samples_read * sizeof(int16_t));
 | 
			
		||||
    return samples_read * sizeof(int16_t);
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; }
 | 
			
		||||
 | 
			
		||||
  void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
 | 
			
		||||
  void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
 | 
			
		||||
  void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
 | 
			
		||||
@@ -46,6 +48,7 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
 | 
			
		||||
  bool adc_{false};
 | 
			
		||||
#endif
 | 
			
		||||
  bool pdm_{false};
 | 
			
		||||
  i2s_mode_t i2s_mode_{};
 | 
			
		||||
  i2s_channel_fmt_t channel_;
 | 
			
		||||
  uint32_t sample_rate_;
 | 
			
		||||
  i2s_bits_per_sample_t bits_per_sample_;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@ import hashlib
 | 
			
		||||
import io
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import re
 | 
			
		||||
import requests
 | 
			
		||||
from magic import Magic
 | 
			
		||||
 | 
			
		||||
from esphome import core
 | 
			
		||||
@@ -15,7 +14,6 @@ from esphome import external_files
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    __version__,
 | 
			
		||||
    CONF_DITHER,
 | 
			
		||||
    CONF_FILE,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
@@ -75,31 +73,6 @@ def compute_local_image_path(value: dict) -> Path:
 | 
			
		||||
    return base_dir / key
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def download_content(url: str, path: Path) -> None:
 | 
			
		||||
    if not external_files.has_remote_file_changed(url, path):
 | 
			
		||||
        _LOGGER.debug("Remote file has not changed %s", url)
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    _LOGGER.debug(
 | 
			
		||||
        "Remote file has changed, downloading from %s to %s",
 | 
			
		||||
        url,
 | 
			
		||||
        path,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        req = requests.get(
 | 
			
		||||
            url,
 | 
			
		||||
            timeout=IMAGE_DOWNLOAD_TIMEOUT,
 | 
			
		||||
            headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
 | 
			
		||||
        )
 | 
			
		||||
        req.raise_for_status()
 | 
			
		||||
    except requests.exceptions.RequestException as e:
 | 
			
		||||
        raise cv.Invalid(f"Could not download from {url}: {e}")
 | 
			
		||||
 | 
			
		||||
    path.parent.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
    path.write_bytes(req.content)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def download_mdi(value):
 | 
			
		||||
    validate_cairosvg_installed(value)
 | 
			
		||||
 | 
			
		||||
@@ -108,7 +81,7 @@ def download_mdi(value):
 | 
			
		||||
 | 
			
		||||
    url = f"https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/{mdi_id}.svg"
 | 
			
		||||
 | 
			
		||||
    download_content(url, path)
 | 
			
		||||
    external_files.download_content(url, path, IMAGE_DOWNLOAD_TIMEOUT)
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
@@ -117,7 +90,7 @@ def download_image(value):
 | 
			
		||||
    url = value[CONF_URL]
 | 
			
		||||
    path = compute_local_image_path(value)
 | 
			
		||||
 | 
			
		||||
    download_content(url, path)
 | 
			
		||||
    external_files.download_content(url, path, IMAGE_DOWNLOAD_TIMEOUT)
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
#if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
 | 
			
		||||
#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
 | 
			
		||||
    case logger::UART_SELECTION_USB_CDC:
 | 
			
		||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
 | 
			
		||||
      if (esp_usb_console_available_for_read()) {
 | 
			
		||||
@@ -68,15 +68,15 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
 | 
			
		||||
        byte = data;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
#endif  // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
#endif  // USE_LOGGER_USB_CDC
 | 
			
		||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
 | 
			
		||||
    case logger::UART_SELECTION_USB_SERIAL_JTAG: {
 | 
			
		||||
      if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
 | 
			
		||||
        byte = data;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
#endif  // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#endif  // USE_LOGGER_USB_SERIAL_JTAG
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
@@ -99,19 +99,20 @@ void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
 | 
			
		||||
#endif  // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
      uart_write_bytes(this->uart_num_, data.data(), data.size());
 | 
			
		||||
      break;
 | 
			
		||||
#if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
 | 
			
		||||
#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
 | 
			
		||||
    case logger::UART_SELECTION_USB_CDC: {
 | 
			
		||||
      const char *msg = (char *) data.data();
 | 
			
		||||
      esp_usb_console_write_buf(msg, data.size());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
#endif  // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
#endif  // USE_LOGGER_USB_CDC
 | 
			
		||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
 | 
			
		||||
    case logger::UART_SELECTION_USB_SERIAL_JTAG:
 | 
			
		||||
      usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
 | 
			
		||||
      delay(10);
 | 
			
		||||
      usb_serial_jtag_ll_txfifo_flush();  // fixes for issue in IDF 4.4.7
 | 
			
		||||
      break;
 | 
			
		||||
#endif  // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
 | 
			
		||||
#endif  // USE_LOGGER_USB_SERIAL_JTAG
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -115,12 +115,15 @@ void LEDCOutput::write_state(float state) {
 | 
			
		||||
  const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1;
 | 
			
		||||
  const float duty_rounded = roundf(state * max_duty);
 | 
			
		||||
  auto duty = static_cast<uint32_t>(duty_rounded);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  ESP_LOGV(TAG, "Setting duty: %u on channel %u", duty, this->channel_);
 | 
			
		||||
  ledcWrite(this->channel_, duty);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  // ensure that 100% on is not 99.975% on
 | 
			
		||||
  if ((duty == max_duty) && (max_duty != 1)) {
 | 
			
		||||
    duty = max_duty + 1;
 | 
			
		||||
  }
 | 
			
		||||
  auto speed_mode = get_speed_mode(channel_);
 | 
			
		||||
  auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
 | 
			
		||||
  int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ static const uint8_t LTR390_MAIN_STATUS = 0x07;
 | 
			
		||||
 | 
			
		||||
static const float GAINVALUES[5] = {1.0, 3.0, 6.0, 9.0, 18.0};
 | 
			
		||||
static const float RESOLUTIONVALUE[6] = {4.0, 2.0, 1.0, 0.5, 0.25, 0.125};
 | 
			
		||||
static const uint8_t RESOLUTION_BITS[6] = {20, 19, 18, 17, 16, 13};
 | 
			
		||||
 | 
			
		||||
// Request fastest measurement rate - will be slowed by device if conversion rate is slower.
 | 
			
		||||
static const float RESOLUTION_SETTING[6] = {0x00, 0x10, 0x20, 0x30, 0x40, 0x50};
 | 
			
		||||
@@ -74,7 +75,7 @@ void LTR390Component::read_als_() {
 | 
			
		||||
  uint32_t als = *val;
 | 
			
		||||
 | 
			
		||||
  if (this->light_sensor_ != nullptr) {
 | 
			
		||||
    float lux = ((0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_])) * this->wfac_;
 | 
			
		||||
    float lux = ((0.6 * als) / (GAINVALUES[this->gain_als_] * RESOLUTIONVALUE[this->res_als_])) * this->wfac_;
 | 
			
		||||
    this->light_sensor_->publish_state(lux);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -90,7 +91,7 @@ void LTR390Component::read_uvs_() {
 | 
			
		||||
  uint32_t uv = *val;
 | 
			
		||||
 | 
			
		||||
  if (this->uvi_sensor_ != nullptr) {
 | 
			
		||||
    this->uvi_sensor_->publish_state((uv / this->sensitivity_) * this->wfac_);
 | 
			
		||||
    this->uvi_sensor_->publish_state((uv / this->sensitivity_uv_) * this->wfac_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->uv_sensor_ != nullptr) {
 | 
			
		||||
@@ -107,9 +108,23 @@ void LTR390Component::read_mode_(int mode_index) {
 | 
			
		||||
  ctrl[LTR390_CTRL_EN] = true;
 | 
			
		||||
  this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong();
 | 
			
		||||
 | 
			
		||||
  uint32_t int_time{0};
 | 
			
		||||
  // Set gain, resolution and measurement rate
 | 
			
		||||
  switch (mode) {
 | 
			
		||||
    case LTR390_MODE_ALS:
 | 
			
		||||
      this->reg(LTR390_GAIN) = this->gain_als_;
 | 
			
		||||
      this->reg(LTR390_MEAS_RATE) = RESOLUTION_SETTING[this->res_als_];
 | 
			
		||||
      int_time = ((uint32_t) RESOLUTIONVALUE[this->res_als_]) * 100;
 | 
			
		||||
      break;
 | 
			
		||||
    case LTR390_MODE_UVS:
 | 
			
		||||
      this->reg(LTR390_GAIN) = this->gain_uv_;
 | 
			
		||||
      this->reg(LTR390_MEAS_RATE) = RESOLUTION_SETTING[this->res_uv_];
 | 
			
		||||
      int_time = ((uint32_t) RESOLUTIONVALUE[this->res_uv_]) * 100;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // After the sensor integration time do the following
 | 
			
		||||
  this->set_timeout(((uint32_t) RESOLUTIONVALUE[this->res_]) * 100 + LTR390_WAKEUP_TIME + LTR390_SETTLE_TIME,
 | 
			
		||||
                    [this, mode_index]() {
 | 
			
		||||
  this->set_timeout(int_time + LTR390_WAKEUP_TIME + LTR390_SETTLE_TIME, [this, mode_index]() {
 | 
			
		||||
    // Read from the sensor
 | 
			
		||||
    std::get<1>(this->mode_funcs_[mode_index])();
 | 
			
		||||
 | 
			
		||||
@@ -151,16 +166,10 @@ void LTR390Component::setup() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set gain
 | 
			
		||||
  this->reg(LTR390_GAIN) = gain_;
 | 
			
		||||
 | 
			
		||||
  // Set resolution and measurement rate
 | 
			
		||||
  this->reg(LTR390_MEAS_RATE) = RESOLUTION_SETTING[this->res_];
 | 
			
		||||
 | 
			
		||||
  // Set sensitivity by linearly scaling against known value in the datasheet
 | 
			
		||||
  float gain_scale = GAINVALUES[this->gain_] / GAIN_MAX;
 | 
			
		||||
  float intg_scale = (RESOLUTIONVALUE[this->res_] * 100) / INTG_MAX;
 | 
			
		||||
  this->sensitivity_ = SENSITIVITY_MAX * gain_scale * intg_scale;
 | 
			
		||||
  float gain_scale_uv = GAINVALUES[this->gain_uv_] / GAIN_MAX;
 | 
			
		||||
  float intg_scale_uv = (RESOLUTIONVALUE[this->res_uv_] * 100) / INTG_MAX;
 | 
			
		||||
  this->sensitivity_uv_ = SENSITIVITY_MAX * gain_scale_uv * intg_scale_uv;
 | 
			
		||||
 | 
			
		||||
  // Set sensor read state
 | 
			
		||||
  this->reading_ = false;
 | 
			
		||||
@@ -176,7 +185,13 @@ void LTR390Component::setup() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LTR390Component::dump_config() { LOG_I2C_DEVICE(this); }
 | 
			
		||||
void LTR390Component::dump_config() {
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  ALS Gain: X%.0f", GAINVALUES[this->gain_als_]);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  ALS Resolution: %u-bit", RESOLUTION_BITS[this->res_als_]);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  UV Gain: X%.0f", GAINVALUES[this->gain_uv_]);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  UV Resolution: %u-bit", RESOLUTION_BITS[this->res_uv_]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LTR390Component::update() {
 | 
			
		||||
  if (!this->reading_ && !mode_funcs_.empty()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -49,8 +49,10 @@ class LTR390Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void set_gain_value(LTR390GAIN gain) { this->gain_ = gain; }
 | 
			
		||||
  void set_res_value(LTR390RESOLUTION res) { this->res_ = res; }
 | 
			
		||||
  void set_als_gain_value(LTR390GAIN gain) { this->gain_als_ = gain; }
 | 
			
		||||
  void set_uv_gain_value(LTR390GAIN gain) { this->gain_uv_ = gain; }
 | 
			
		||||
  void set_als_res_value(LTR390RESOLUTION res) { this->res_als_ = res; }
 | 
			
		||||
  void set_uv_res_value(LTR390RESOLUTION res) { this->res_uv_ = res; }
 | 
			
		||||
  void set_wfac_value(float wfac) { this->wfac_ = wfac; }
 | 
			
		||||
 | 
			
		||||
  void set_light_sensor(sensor::Sensor *light_sensor) { this->light_sensor_ = light_sensor; }
 | 
			
		||||
@@ -71,9 +73,11 @@ class LTR390Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  // a list of modes and corresponding read functions
 | 
			
		||||
  std::vector<std::tuple<LTR390MODE, std::function<void()>>> mode_funcs_;
 | 
			
		||||
 | 
			
		||||
  LTR390GAIN gain_;
 | 
			
		||||
  LTR390RESOLUTION res_;
 | 
			
		||||
  float sensitivity_;
 | 
			
		||||
  LTR390GAIN gain_als_;
 | 
			
		||||
  LTR390GAIN gain_uv_;
 | 
			
		||||
  LTR390RESOLUTION res_als_;
 | 
			
		||||
  LTR390RESOLUTION res_uv_;
 | 
			
		||||
  float sensitivity_uv_;
 | 
			
		||||
  float wfac_;
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *light_sensor_{nullptr};
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ from esphome.const import (
 | 
			
		||||
    UNIT_LUX,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@sjtrny"]
 | 
			
		||||
CODEOWNERS = ["@sjtrny", "@latonita"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
ltr390_ns = cg.esphome_ns.namespace("ltr390")
 | 
			
		||||
@@ -76,8 +76,24 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_EMPTY,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_GAIN, default="X18"): cv.enum(GAIN_OPTIONS),
 | 
			
		||||
            cv.Optional(CONF_RESOLUTION, default=20): cv.enum(RES_OPTIONS),
 | 
			
		||||
            cv.Optional(CONF_GAIN, default="X18"): cv.Any(
 | 
			
		||||
                cv.enum(GAIN_OPTIONS),
 | 
			
		||||
                cv.Schema(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.Required(CONF_AMBIENT_LIGHT): cv.enum(GAIN_OPTIONS),
 | 
			
		||||
                        cv.Required(CONF_UV): cv.enum(GAIN_OPTIONS),
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_RESOLUTION, default=20): cv.Any(
 | 
			
		||||
                cv.enum(RES_OPTIONS),
 | 
			
		||||
                cv.Schema(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.Required(CONF_AMBIENT_LIGHT): cv.enum(RES_OPTIONS),
 | 
			
		||||
                        cv.Required(CONF_UV): cv.enum(RES_OPTIONS),
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_WINDOW_CORRECTION_FACTOR, default=1.0): cv.float_range(
 | 
			
		||||
                min=1.0
 | 
			
		||||
            ),
 | 
			
		||||
@@ -101,11 +117,25 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_gain_value(config[CONF_GAIN]))
 | 
			
		||||
    cg.add(var.set_res_value(config[CONF_RESOLUTION]))
 | 
			
		||||
    cg.add(var.set_wfac_value(config[CONF_WINDOW_CORRECTION_FACTOR]))
 | 
			
		||||
 | 
			
		||||
    for key, funcName in TYPES.items():
 | 
			
		||||
        if key in config:
 | 
			
		||||
            sens = await sensor.new_sensor(config[key])
 | 
			
		||||
            cg.add(getattr(var, funcName)(sens))
 | 
			
		||||
 | 
			
		||||
    gain_value = config[CONF_GAIN]
 | 
			
		||||
    if isinstance(gain_value, dict):
 | 
			
		||||
        cg.add(var.set_als_gain_value(gain_value[CONF_AMBIENT_LIGHT]))
 | 
			
		||||
        cg.add(var.set_uv_gain_value(gain_value[CONF_UV]))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_als_gain_value(gain_value))
 | 
			
		||||
        cg.add(var.set_uv_gain_value(gain_value))
 | 
			
		||||
 | 
			
		||||
    res_value = config[CONF_RESOLUTION]
 | 
			
		||||
    if isinstance(res_value, dict):
 | 
			
		||||
        cg.add(var.set_als_res_value(res_value[CONF_AMBIENT_LIGHT]))
 | 
			
		||||
        cg.add(var.set_uv_res_value(res_value[CONF_UV]))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_als_res_value(res_value))
 | 
			
		||||
        cg.add(var.set_uv_res_value(res_value))
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,9 @@ def mdns_service(
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(55.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    if config[CONF_DISABLED] is True:
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if CORE.using_arduino:
 | 
			
		||||
        if CORE.is_esp32:
 | 
			
		||||
            cg.add_library("ESPmDNS", None)
 | 
			
		||||
@@ -92,9 +95,6 @@ async def to_code(config):
 | 
			
		||||
            path="components/mdns",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if config[CONF_DISABLED]:
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_MDNS")
 | 
			
		||||
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#include "mdns_component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#ifdef USE_MDNS
 | 
			
		||||
#include "mdns_component.h"
 | 
			
		||||
#include "esphome/core/version.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
@@ -125,3 +126,4 @@ void MDNSComponent::dump_config() {
 | 
			
		||||
 | 
			
		||||
}  // namespace mdns
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#ifdef USE_MDNS
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
@@ -46,3 +47,4 @@ class MDNSComponent : public Component {
 | 
			
		||||
 | 
			
		||||
}  // namespace mdns
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#if defined(USE_ESP32) && defined(USE_MDNS)
 | 
			
		||||
 | 
			
		||||
#include <mdns.h>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#if defined(USE_ESP8266) && defined(USE_ARDUINO)
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#if defined(USE_ESP8266) && defined(USE_ARDUINO) && defined(USE_MDNS)
 | 
			
		||||
 | 
			
		||||
#include <ESP8266mDNS.h>
 | 
			
		||||
#include "esphome/components/network/ip_address.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#if defined(USE_HOST) && defined(USE_MDNS)
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/network/ip_address.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#ifdef USE_LIBRETINY
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#if defined(USE_LIBRETINY) && defined(USE_MDNS)
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/network/ip_address.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#if defined(USE_RP2040) && defined(USE_MDNS)
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/network/ip_address.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import requests
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome.core import CORE, HexInt, EsphomeError
 | 
			
		||||
from esphome.core import CORE, HexInt
 | 
			
		||||
 | 
			
		||||
from esphome.components import esp32, microphone
 | 
			
		||||
from esphome import automation, git, external_files
 | 
			
		||||
@@ -41,9 +41,15 @@ CODEOWNERS = ["@kahrendt", "@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["microphone"]
 | 
			
		||||
DOMAIN = "micro_wake_word"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_FEATURE_STEP_SIZE = "feature_step_size"
 | 
			
		||||
CONF_MODELS = "models"
 | 
			
		||||
CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected"
 | 
			
		||||
CONF_PROBABILITY_CUTOFF = "probability_cutoff"
 | 
			
		||||
CONF_SLIDING_WINDOW_AVERAGE_SIZE = "sliding_window_average_size"
 | 
			
		||||
CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected"
 | 
			
		||||
CONF_SLIDING_WINDOW_SIZE = "sliding_window_size"
 | 
			
		||||
CONF_TENSOR_ARENA_SIZE = "tensor_arena_size"
 | 
			
		||||
CONF_VAD = "vad"
 | 
			
		||||
 | 
			
		||||
TYPE_HTTP = "http"
 | 
			
		||||
 | 
			
		||||
@@ -98,12 +104,14 @@ GIT_SCHEMA = cv.All(
 | 
			
		||||
    _process_git_source,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
KEY_WAKE_WORD = "wake_word"
 | 
			
		||||
 | 
			
		||||
KEY_AUTHOR = "author"
 | 
			
		||||
KEY_WEBSITE = "website"
 | 
			
		||||
KEY_VERSION = "version"
 | 
			
		||||
KEY_MICRO = "micro"
 | 
			
		||||
KEY_MINIMUM_ESPHOME_VERSION = "minimum_esphome_version"
 | 
			
		||||
KEY_TRAINED_LANGUAGES = "trained_languages"
 | 
			
		||||
KEY_VERSION = "version"
 | 
			
		||||
KEY_WAKE_WORD = "wake_word"
 | 
			
		||||
KEY_WEBSITE = "website"
 | 
			
		||||
 | 
			
		||||
MANIFEST_SCHEMA_V1 = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
@@ -125,6 +133,29 @@ MANIFEST_SCHEMA_V1 = cv.Schema(
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MANIFEST_SCHEMA_V2 = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_TYPE): "micro",
 | 
			
		||||
        cv.Required(CONF_MODEL): cv.string,
 | 
			
		||||
        cv.Required(KEY_AUTHOR): cv.string,
 | 
			
		||||
        cv.Required(KEY_VERSION): cv.All(cv.int_, 2),
 | 
			
		||||
        cv.Required(KEY_WAKE_WORD): cv.string,
 | 
			
		||||
        cv.Required(KEY_TRAINED_LANGUAGES): cv.ensure_list(cv.string),
 | 
			
		||||
        cv.Optional(KEY_WEBSITE): cv.url,
 | 
			
		||||
        cv.Required(KEY_MICRO): cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Required(CONF_FEATURE_STEP_SIZE): cv.int_range(min=0, max=30),
 | 
			
		||||
                cv.Required(CONF_TENSOR_ARENA_SIZE): cv.int_,
 | 
			
		||||
                cv.Required(CONF_PROBABILITY_CUTOFF): cv.float_,
 | 
			
		||||
                cv.Required(CONF_SLIDING_WINDOW_SIZE): cv.positive_int,
 | 
			
		||||
                cv.Required(KEY_MINIMUM_ESPHOME_VERSION): cv.All(
 | 
			
		||||
                    cv.version_number, cv.validate_esphome_version
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _compute_local_file_path(config: dict) -> Path:
 | 
			
		||||
    url = config[CONF_URL]
 | 
			
		||||
@@ -135,6 +166,24 @@ def _compute_local_file_path(config: dict) -> Path:
 | 
			
		||||
    return base_dir / key
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _convert_manifest_v1_to_v2(v1_manifest):
 | 
			
		||||
    v2_manifest = v1_manifest.copy()
 | 
			
		||||
 | 
			
		||||
    v2_manifest[KEY_VERSION] = 2
 | 
			
		||||
    v2_manifest[KEY_MICRO][CONF_SLIDING_WINDOW_SIZE] = v1_manifest[KEY_MICRO][
 | 
			
		||||
        CONF_SLIDING_WINDOW_AVERAGE_SIZE
 | 
			
		||||
    ]
 | 
			
		||||
    del v2_manifest[KEY_MICRO][CONF_SLIDING_WINDOW_AVERAGE_SIZE]
 | 
			
		||||
    v2_manifest[KEY_MICRO][
 | 
			
		||||
        CONF_TENSOR_ARENA_SIZE
 | 
			
		||||
    ] = 45672  # Original Inception-based V1 manifest models require a minimum of 45672 bytes
 | 
			
		||||
    v2_manifest[KEY_MICRO][
 | 
			
		||||
        CONF_FEATURE_STEP_SIZE
 | 
			
		||||
    ] = 20  # Original Inception-based V1 manifest models use a 20 ms feature step size
 | 
			
		||||
 | 
			
		||||
    return v2_manifest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _download_file(url: str, path: Path) -> bytes:
 | 
			
		||||
    if not external_files.has_remote_file_changed(url, path):
 | 
			
		||||
        _LOGGER.debug("Remote file has not changed, skipping download")
 | 
			
		||||
@@ -155,6 +204,24 @@ def _download_file(url: str, path: Path) -> bytes:
 | 
			
		||||
    return req.content
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _validate_manifest_version(manifest_data):
 | 
			
		||||
    if manifest_version := manifest_data.get(KEY_VERSION):
 | 
			
		||||
        if manifest_version == 1:
 | 
			
		||||
            try:
 | 
			
		||||
                MANIFEST_SCHEMA_V1(manifest_data)
 | 
			
		||||
            except cv.Invalid as e:
 | 
			
		||||
                raise cv.Invalid(f"Invalid manifest file: {e}") from e
 | 
			
		||||
        elif manifest_version == 2:
 | 
			
		||||
            try:
 | 
			
		||||
                MANIFEST_SCHEMA_V2(manifest_data)
 | 
			
		||||
            except cv.Invalid as e:
 | 
			
		||||
                raise cv.Invalid(f"Invalid manifest file: {e}") from e
 | 
			
		||||
        else:
 | 
			
		||||
            raise cv.Invalid("Invalid manifest version")
 | 
			
		||||
    else:
 | 
			
		||||
        raise cv.Invalid("Invalid manifest file, missing 'version' key.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _process_http_source(config):
 | 
			
		||||
    url = config[CONF_URL]
 | 
			
		||||
    path = _compute_local_file_path(config)
 | 
			
		||||
@@ -167,11 +234,6 @@ def _process_http_source(config):
 | 
			
		||||
    if not isinstance(manifest_data, dict):
 | 
			
		||||
        raise cv.Invalid("Manifest file must contain a JSON object")
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        MANIFEST_SCHEMA_V1(manifest_data)
 | 
			
		||||
    except cv.Invalid as e:
 | 
			
		||||
        raise cv.Invalid(f"Invalid manifest file: {e}") from e
 | 
			
		||||
 | 
			
		||||
    model = manifest_data[CONF_MODEL]
 | 
			
		||||
    model_url = urljoin(url, model)
 | 
			
		||||
 | 
			
		||||
@@ -206,7 +268,7 @@ def _validate_source_model_name(value):
 | 
			
		||||
    return MODEL_SOURCE_SCHEMA(
 | 
			
		||||
        {
 | 
			
		||||
            CONF_TYPE: TYPE_HTTP,
 | 
			
		||||
            CONF_URL: f"https://github.com/esphome/micro-wake-word-models/raw/main/models/{value}.json",
 | 
			
		||||
            CONF_URL: f"https://github.com/esphome/micro-wake-word-models/raw/main/models/v2/{value}.json",
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@@ -260,18 +322,57 @@ MODEL_SOURCE_SCHEMA = cv.Any(
 | 
			
		||||
    msg="Not a valid model name, local path, http(s) url, or github shorthand",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MODEL_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_MODEL): MODEL_SOURCE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_PROBABILITY_CUTOFF): cv.percentage,
 | 
			
		||||
        cv.Optional(CONF_SLIDING_WINDOW_SIZE): cv.positive_int,
 | 
			
		||||
        cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Provide a default VAD model that could be overridden
 | 
			
		||||
VAD_MODEL_SCHEMA = MODEL_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_MODEL,
 | 
			
		||||
                default="vad",
 | 
			
		||||
            ): MODEL_SOURCE_SCHEMA,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _maybe_empty_vad_schema(value):
 | 
			
		||||
    # Idea borrowed from uart/__init__.py's ``maybe_empty_debug`` function. Accessed 2 July 2024.
 | 
			
		||||
    # Loads a default VAD model without any parameters overridden.
 | 
			
		||||
    if value is None:
 | 
			
		||||
        value = {}
 | 
			
		||||
    return VAD_MODEL_SCHEMA(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(MicroWakeWord),
 | 
			
		||||
            cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone),
 | 
			
		||||
            cv.Optional(CONF_PROBABILITY_CUTOFF): cv.percentage,
 | 
			
		||||
            cv.Optional(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int,
 | 
			
		||||
            cv.Required(CONF_MODELS): cv.ensure_list(
 | 
			
		||||
                cv.maybe_simple_value(MODEL_SCHEMA, key=CONF_MODEL)
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation(
 | 
			
		||||
                single=True
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Required(CONF_MODEL): MODEL_SOURCE_SCHEMA,
 | 
			
		||||
            cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
			
		||||
            cv.Optional(CONF_VAD): _maybe_empty_vad_schema,
 | 
			
		||||
            cv.Optional(CONF_MODEL): cv.invalid(
 | 
			
		||||
                f"The {CONF_MODEL} parameter has moved to be a list element under the {CONF_MODELS} parameter."
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PROBABILITY_CUTOFF): cv.invalid(
 | 
			
		||||
                f"The {CONF_PROBABILITY_CUTOFF} parameter has moved to be a list element under the {CONF_MODELS} parameter."
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.invalid(
 | 
			
		||||
                f"The {CONF_SLIDING_WINDOW_AVERAGE_SIZE} parameter has been renamed to {CONF_SLIDING_WINDOW_SIZE} and moved to be a list element under the {CONF_MODELS} parameter."
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.only_with_esp_idf,
 | 
			
		||||
@@ -282,44 +383,20 @@ def _load_model_data(manifest_path: Path):
 | 
			
		||||
    with open(manifest_path, encoding="utf-8") as f:
 | 
			
		||||
        manifest = json.load(f)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        MANIFEST_SCHEMA_V1(manifest)
 | 
			
		||||
    except cv.Invalid as e:
 | 
			
		||||
        raise EsphomeError(f"Invalid manifest file: {e}") from e
 | 
			
		||||
    _validate_manifest_version(manifest)
 | 
			
		||||
 | 
			
		||||
    model_path = manifest_path.parent / manifest[CONF_MODEL]
 | 
			
		||||
 | 
			
		||||
    with open(model_path, "rb") as f:
 | 
			
		||||
        model = f.read()
 | 
			
		||||
 | 
			
		||||
    if manifest.get(KEY_VERSION) == 1:
 | 
			
		||||
        manifest = _convert_manifest_v1_to_v2(manifest)
 | 
			
		||||
 | 
			
		||||
    return manifest, model
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    mic = await cg.get_variable(config[CONF_MICROPHONE])
 | 
			
		||||
    cg.add(var.set_microphone(mic))
 | 
			
		||||
 | 
			
		||||
    if on_wake_word_detection_config := config.get(CONF_ON_WAKE_WORD_DETECTED):
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            var.get_wake_word_detected_trigger(),
 | 
			
		||||
            [(cg.std_string, "wake_word")],
 | 
			
		||||
            on_wake_word_detection_config,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    esp32.add_idf_component(
 | 
			
		||||
        name="esp-tflite-micro",
 | 
			
		||||
        repo="https://github.com/espressif/esp-tflite-micro",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    cg.add_build_flag("-DTF_LITE_STATIC_MEMORY")
 | 
			
		||||
    cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON")
 | 
			
		||||
    cg.add_build_flag("-DESP_NN")
 | 
			
		||||
 | 
			
		||||
    model_config = config.get(CONF_MODEL)
 | 
			
		||||
    data = []
 | 
			
		||||
def _model_config_to_manifest_data(model_config):
 | 
			
		||||
    if model_config[CONF_TYPE] == TYPE_GIT:
 | 
			
		||||
        # compute path to model file
 | 
			
		||||
        key = f"{model_config[CONF_URL]}@{model_config.get(CONF_REF)}"
 | 
			
		||||
@@ -337,23 +414,95 @@ async def to_code(config):
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValueError("Unsupported config type: {model_config[CONF_TYPE]}")
 | 
			
		||||
 | 
			
		||||
    manifest, data = _load_model_data(file)
 | 
			
		||||
    return _load_model_data(file)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _feature_step_size_validate(config):
 | 
			
		||||
    features_step_size = None
 | 
			
		||||
 | 
			
		||||
    for model_parameters in config[CONF_MODELS]:
 | 
			
		||||
        model_config = model_parameters.get(CONF_MODEL)
 | 
			
		||||
        manifest, _ = _model_config_to_manifest_data(model_config)
 | 
			
		||||
 | 
			
		||||
        model_step_size = manifest[KEY_MICRO][CONF_FEATURE_STEP_SIZE]
 | 
			
		||||
 | 
			
		||||
        if features_step_size is None:
 | 
			
		||||
            features_step_size = model_step_size
 | 
			
		||||
        elif features_step_size != model_step_size:
 | 
			
		||||
            raise cv.Invalid("Cannot load models with different features step sizes.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = _feature_step_size_validate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    mic = await cg.get_variable(config[CONF_MICROPHONE])
 | 
			
		||||
    cg.add(var.set_microphone(mic))
 | 
			
		||||
 | 
			
		||||
    esp32.add_idf_component(
 | 
			
		||||
        name="esp-tflite-micro",
 | 
			
		||||
        repo="https://github.com/espressif/esp-tflite-micro",
 | 
			
		||||
        ref="v1.3.1",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    cg.add_build_flag("-DTF_LITE_STATIC_MEMORY")
 | 
			
		||||
    cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON")
 | 
			
		||||
    cg.add_build_flag("-DESP_NN")
 | 
			
		||||
 | 
			
		||||
    if on_wake_word_detection_config := config.get(CONF_ON_WAKE_WORD_DETECTED):
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            var.get_wake_word_detected_trigger(),
 | 
			
		||||
            [(cg.std_string, "wake_word")],
 | 
			
		||||
            on_wake_word_detection_config,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if vad_model := config.get(CONF_VAD):
 | 
			
		||||
        cg.add_define("USE_MICRO_WAKE_WORD_VAD")
 | 
			
		||||
 | 
			
		||||
        # Use the general model loading code for the VAD codegen
 | 
			
		||||
        config[CONF_MODELS].append(vad_model)
 | 
			
		||||
 | 
			
		||||
    for model_parameters in config[CONF_MODELS]:
 | 
			
		||||
        model_config = model_parameters.get(CONF_MODEL)
 | 
			
		||||
        data = []
 | 
			
		||||
        manifest, data = _model_config_to_manifest_data(model_config)
 | 
			
		||||
 | 
			
		||||
        rhs = [HexInt(x) for x in data]
 | 
			
		||||
    prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
			
		||||
    cg.add(var.set_model_start(prog_arr))
 | 
			
		||||
        prog_arr = cg.progmem_array(model_parameters[CONF_RAW_DATA_ID], rhs)
 | 
			
		||||
 | 
			
		||||
    probability_cutoff = config.get(
 | 
			
		||||
        probability_cutoff = model_parameters.get(
 | 
			
		||||
            CONF_PROBABILITY_CUTOFF, manifest[KEY_MICRO][CONF_PROBABILITY_CUTOFF]
 | 
			
		||||
        )
 | 
			
		||||
    cg.add(var.set_probability_cutoff(probability_cutoff))
 | 
			
		||||
    sliding_window_average_size = config.get(
 | 
			
		||||
        CONF_SLIDING_WINDOW_AVERAGE_SIZE,
 | 
			
		||||
        manifest[KEY_MICRO][CONF_SLIDING_WINDOW_AVERAGE_SIZE],
 | 
			
		||||
        sliding_window_size = model_parameters.get(
 | 
			
		||||
            CONF_SLIDING_WINDOW_SIZE,
 | 
			
		||||
            manifest[KEY_MICRO][CONF_SLIDING_WINDOW_SIZE],
 | 
			
		||||
        )
 | 
			
		||||
    cg.add(var.set_sliding_window_average_size(sliding_window_average_size))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_wake_word(manifest[KEY_WAKE_WORD]))
 | 
			
		||||
        if manifest[KEY_WAKE_WORD] == "vad":
 | 
			
		||||
            cg.add(
 | 
			
		||||
                var.add_vad_model(
 | 
			
		||||
                    prog_arr,
 | 
			
		||||
                    probability_cutoff,
 | 
			
		||||
                    sliding_window_size,
 | 
			
		||||
                    manifest[KEY_MICRO][CONF_TENSOR_ARENA_SIZE],
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            cg.add(
 | 
			
		||||
                var.add_wake_word_model(
 | 
			
		||||
                    prog_arr,
 | 
			
		||||
                    probability_cutoff,
 | 
			
		||||
                    sliding_window_size,
 | 
			
		||||
                    manifest[KEY_WAKE_WORD],
 | 
			
		||||
                    manifest[KEY_MICRO][CONF_TENSOR_ARENA_SIZE],
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_features_step_size(manifest[KEY_MICRO][CONF_FEATURE_STEP_SIZE]))
 | 
			
		||||
    cg.add_library("kahrendt/ESPMicroSpeechFeatures", "1.0.0")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MICRO_WAKE_WORD_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(MicroWakeWord)})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,493 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
// Converted audio_preprocessor_int8.tflite
 | 
			
		||||
// From https://github.com/tensorflow/tflite-micro/tree/main/tensorflow/lite/micro/examples/micro_speech/models accessed
 | 
			
		||||
// January 2024
 | 
			
		||||
//
 | 
			
		||||
// Copyright 2023 The TensorFlow Authors. All Rights Reserved.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace micro_wake_word {
 | 
			
		||||
 | 
			
		||||
const unsigned char G_AUDIO_PREPROCESSOR_INT8_TFLITE[] = {
 | 
			
		||||
    0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x14, 0x00, 0x20, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10,
 | 
			
		||||
    0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x88, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x80, 0x0e, 0x00, 0x00, 0x90, 0x0e, 0x00, 0x00, 0xcc, 0x1f, 0x00, 0x00, 0x03,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xe2, 0xeb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67,
 | 
			
		||||
    0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x94, 0xff,
 | 
			
		||||
    0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75,
 | 
			
		||||
    0x74, 0x5f, 0x30, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc2, 0xf5, 0xff, 0xff,
 | 
			
		||||
    0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
 | 
			
		||||
    0x00, 0x02, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xdc, 0xff, 0xff, 0xff, 0x2d, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f,
 | 
			
		||||
    0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00,
 | 
			
		||||
    0x08, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e,
 | 
			
		||||
    0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x2e, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x9c, 0x0d, 0x00, 0x00, 0x94, 0x0d, 0x00, 0x00, 0xc4, 0x09, 0x00, 0x00, 0x6c, 0x09, 0x00, 0x00, 0x48,
 | 
			
		||||
    0x09, 0x00, 0x00, 0x34, 0x09, 0x00, 0x00, 0x20, 0x09, 0x00, 0x00, 0x0c, 0x09, 0x00, 0x00, 0xf8, 0x08, 0x00, 0x00,
 | 
			
		||||
    0xec, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x00, 0x38, 0x04, 0x00,
 | 
			
		||||
    0x00, 0xb0, 0x01, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x60, 0x01,
 | 
			
		||||
    0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, 0x2c,
 | 
			
		||||
    0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00,
 | 
			
		||||
    0x04, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00,
 | 
			
		||||
    0x00, 0xdc, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xbc, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x94,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff,
 | 
			
		||||
    0x04, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04,
 | 
			
		||||
    0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00,
 | 
			
		||||
    0x07, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x31, 0x32, 0x2e, 0x30, 0x00,
 | 
			
		||||
    0x00, 0x56, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x38, 0x2e, 0x30, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0xe1, 0xff, 0xff, 0xd8, 0xe1, 0xff, 0xff, 0xdc,
 | 
			
		||||
    0xe1, 0xff, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe4, 0xe1, 0xff, 0xff, 0xe8, 0xe1, 0xff, 0xff, 0xec, 0xe1, 0xff, 0xff,
 | 
			
		||||
    0xf0, 0xe1, 0xff, 0xff, 0xf4, 0xe1, 0xff, 0xff, 0xf8, 0xe1, 0xff, 0xff, 0xfc, 0xe1, 0xff, 0xff, 0x00, 0xe2, 0xff,
 | 
			
		||||
    0xff, 0x04, 0xe2, 0xff, 0xff, 0x08, 0xe2, 0xff, 0xff, 0x0c, 0xe2, 0xff, 0xff, 0x10, 0xe2, 0xff, 0xff, 0x14, 0xe2,
 | 
			
		||||
    0xff, 0xff, 0x18, 0xe2, 0xff, 0xff, 0x1c, 0xe2, 0xff, 0xff, 0x20, 0xe2, 0xff, 0xff, 0x24, 0xe2, 0xff, 0xff, 0x28,
 | 
			
		||||
    0xe2, 0xff, 0xff, 0x2c, 0xe2, 0xff, 0xff, 0x30, 0xe2, 0xff, 0xff, 0xd2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x04, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0xe2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x01, 0x00, 0x00, 0xf2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0xff,
 | 
			
		||||
    0xff, 0xff, 0x02, 0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x12,
 | 
			
		||||
    0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x22, 0xf8, 0xff, 0xff,
 | 
			
		||||
    0x04, 0x00, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x61, 0x05, 0x00, 0x00, 0x00, 0x00, 0x23, 0x0b, 0x41,
 | 
			
		||||
    0x01, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x0e, 0x80, 0x05,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0xd1, 0x0c, 0x63, 0x04, 0x00, 0x00, 0x00, 0x00, 0x34, 0x0c, 0x3f, 0x04, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x81, 0x0c, 0xf7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x0d, 0x77, 0x06, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x0f,
 | 
			
		||||
    0xa9, 0x08, 0x01, 0x02, 0x7f, 0x0b, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0e, 0xd1, 0x08, 0xdb, 0x02, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x03, 0x0d, 0x4a, 0x07, 0xad, 0x01, 0x2c, 0x0c, 0xc6, 0x06, 0x79, 0x01, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x45, 0x0c, 0x29, 0x07, 0x23, 0x02, 0x34, 0x0d, 0x5b, 0x08, 0x96, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x0e, 0x48,
 | 
			
		||||
    0x0a, 0xbd, 0x05, 0x45, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x0c, 0x88, 0x08, 0x43, 0x04,
 | 
			
		||||
    0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0b, 0xd3, 0x07, 0xcb, 0x03, 0xd2, 0x0f, 0xe7,
 | 
			
		||||
    0x0b, 0x09, 0x08, 0x39, 0x04, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x0c, 0x14, 0x09,
 | 
			
		||||
    0x75, 0x05, 0xe2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x0e, 0xdd, 0x0a, 0x6b, 0x07, 0x03,
 | 
			
		||||
    0x04, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x0d, 0x09, 0x0a, 0xc9, 0x06, 0x93, 0x03, 0x65, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x0d, 0x25, 0x0a, 0x12, 0x07, 0x07, 0x04, 0x05, 0x01, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x0a, 0x0e, 0x17, 0x0b, 0x2c, 0x08, 0x49, 0x05, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x98, 0x0f, 0xcb, 0x0c, 0x04, 0x0a, 0x44, 0x07, 0x8b, 0x04, 0xd8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x0f, 0x87,
 | 
			
		||||
    0x0c, 0xe7, 0x09, 0x4e, 0x07, 0xba, 0x04, 0x2d, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x0f, 0x23, 0x0d, 0xa7, 0x0a,
 | 
			
		||||
    0x30, 0x08, 0xbe, 0x05, 0x52, 0x03, 0xeb, 0x00, 0x89, 0x0e, 0x2c, 0x0c, 0xd4, 0x09, 0x81, 0x07, 0x33, 0x05, 0xe9,
 | 
			
		||||
    0x02, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x0e, 0x29, 0x0c, 0xf1, 0x09, 0xbe, 0x07, 0x90, 0x05, 0x65, 0x03,
 | 
			
		||||
    0x3f, 0x01, 0x1d, 0x0f, 0xff, 0x0c, 0xe5, 0x0a, 0xcf, 0x08, 0xbc, 0x06, 0xae, 0x04, 0xa3, 0x02, 0x9c, 0x00, 0x99,
 | 
			
		||||
    0x0e, 0x99, 0x0c, 0x9d, 0x0a, 0xa4, 0x08, 0xaf, 0x06, 0xbd, 0x04, 0xcf, 0x02, 0xe4, 0x00, 0xfc, 0x0e, 0x17, 0x0d,
 | 
			
		||||
    0x36, 0x0b, 0x57, 0x09, 0x7c, 0x07, 0xa4, 0x05, 0xcf, 0x03, 0xfd, 0x01, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x62, 0x0e, 0x98, 0x0c, 0xd2, 0x0a, 0x0e, 0x09, 0x4d, 0x07, 0x8f, 0x05, 0xd4, 0x03, 0x1b, 0x02,
 | 
			
		||||
    0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x0e, 0x00, 0x0d, 0x52, 0x0b, 0xa6, 0x09, 0xfd, 0x07, 0x56, 0x06, 0xb1,
 | 
			
		||||
    0x04, 0x0f, 0x03, 0x6f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x0f, 0x37, 0x0e, 0x9e, 0x0c,
 | 
			
		||||
    0x08, 0x0b, 0x73, 0x09, 0xe1, 0x07, 0x52, 0x06, 0xc4, 0x04, 0x38, 0x03, 0xaf, 0x01, 0x28, 0x00, 0xa3, 0x0e, 0x1f,
 | 
			
		||||
    0x0d, 0x9e, 0x0b, 0x1f, 0x0a, 0xa2, 0x08, 0x27, 0x07, 0xae, 0x05, 0x37, 0x04, 0xc2, 0x02, 0x4e, 0x01, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xdd, 0x0f, 0x6d, 0x0e, 0xff, 0x0c, 0x93, 0x0b, 0x29, 0x0a, 0xc1, 0x08, 0x5a, 0x07, 0xf5, 0x05, 0x92,
 | 
			
		||||
    0x04, 0x30, 0x03, 0xd1, 0x01, 0x73, 0x00, 0x16, 0x0f, 0xbc, 0x0d, 0x62, 0x0c, 0x0b, 0x0b, 0xb5, 0x09, 0x61, 0x08,
 | 
			
		||||
    0x0e, 0x07, 0xbd, 0x05, 0x6d, 0x04, 0x1f, 0x03, 0xd3, 0x01, 0x88, 0x00, 0x3e, 0x0f, 0xf6, 0x0d, 0xaf, 0x0c, 0x6a,
 | 
			
		||||
    0x0b, 0x27, 0x0a, 0xe4, 0x08, 0xa3, 0x07, 0x64, 0x06, 0x26, 0x05, 0xe9, 0x03, 0xae, 0x02, 0x74, 0x01, 0x3b, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0f, 0xce, 0x0d, 0x99, 0x0c, 0x66, 0x0b, 0x34, 0x0a, 0x03,
 | 
			
		||||
    0x09, 0xd3, 0x07, 0xa5, 0x06, 0x78, 0x05, 0x4c, 0x04, 0x22, 0x03, 0xf8, 0x01, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0xa9, 0x0f, 0x83, 0x0e, 0x5f, 0x0d, 0x3b, 0x0c, 0x19, 0x0b, 0xf8, 0x09, 0xd8, 0x08, 0xb9, 0x07, 0x9b, 0x06, 0x7e,
 | 
			
		||||
    0x05, 0x63, 0x04, 0x48, 0x03, 0x2f, 0x02, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xfa, 0xff, 0xff, 0x04, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x04, 0xbe, 0x0e, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x01, 0x7f, 0x0a, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x2e, 0x03, 0x9c, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x03, 0xc0, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x7e,
 | 
			
		||||
    0x03, 0x08, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x60, 0x02, 0x88, 0x09, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x56, 0x07,
 | 
			
		||||
    0xfe, 0x0d, 0x80, 0x04, 0xdd, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x2e, 0x07, 0x24, 0x0d, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0xfc, 0x02, 0xb5, 0x08, 0x52, 0x0e, 0xd3, 0x03, 0x39, 0x09, 0x86, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xba, 0x03,
 | 
			
		||||
    0xd6, 0x08, 0xdc, 0x0d, 0xcb, 0x02, 0xa4, 0x07, 0x69, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0xb7, 0x05, 0x42,
 | 
			
		||||
    0x0a, 0xba, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x03, 0x77, 0x07, 0xbc, 0x0b, 0xf1, 0x0f,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x04, 0x2c, 0x08, 0x34, 0x0c, 0x2d, 0x00, 0x18, 0x04, 0xf6,
 | 
			
		||||
    0x07, 0xc6, 0x0b, 0x89, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0xeb, 0x06, 0x8a, 0x0a,
 | 
			
		||||
    0x1d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x22, 0x05, 0x94, 0x08, 0xfc, 0x0b, 0x59,
 | 
			
		||||
    0x0f, 0x00, 0x00, 0x00, 0x00, 0xac, 0x02, 0xf6, 0x05, 0x36, 0x09, 0x6c, 0x0c, 0x9a, 0x0f, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0xbe, 0x02, 0xda, 0x05, 0xed, 0x08, 0xf8, 0x0b, 0xfa, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xf5,
 | 
			
		||||
    0x01, 0xe8, 0x04, 0xd3, 0x07, 0xb6, 0x0a, 0x92, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00,
 | 
			
		||||
    0x34, 0x03, 0xfb, 0x05, 0xbb, 0x08, 0x74, 0x0b, 0x27, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x78, 0x03, 0x18,
 | 
			
		||||
    0x06, 0xb1, 0x08, 0x45, 0x0b, 0xd2, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0xdc, 0x02, 0x58, 0x05, 0xcf, 0x07,
 | 
			
		||||
    0x41, 0x0a, 0xad, 0x0c, 0x14, 0x0f, 0x76, 0x01, 0xd3, 0x03, 0x2b, 0x06, 0x7e, 0x08, 0xcc, 0x0a, 0x16, 0x0d, 0x5a,
 | 
			
		||||
    0x0f, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x01, 0xd6, 0x03, 0x0e, 0x06, 0x41, 0x08, 0x6f, 0x0a, 0x9a, 0x0c, 0xc0, 0x0e,
 | 
			
		||||
    0xe2, 0x00, 0x00, 0x03, 0x1a, 0x05, 0x30, 0x07, 0x43, 0x09, 0x51, 0x0b, 0x5c, 0x0d, 0x63, 0x0f, 0x66, 0x01, 0x66,
 | 
			
		||||
    0x03, 0x62, 0x05, 0x5b, 0x07, 0x50, 0x09, 0x42, 0x0b, 0x30, 0x0d, 0x1b, 0x0f, 0x03, 0x01, 0xe8, 0x02, 0xc9, 0x04,
 | 
			
		||||
    0xa8, 0x06, 0x83, 0x08, 0x5b, 0x0a, 0x30, 0x0c, 0x02, 0x0e, 0xd1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x9d, 0x01, 0x67, 0x03, 0x2d, 0x05, 0xf1, 0x06, 0xb2, 0x08, 0x70, 0x0a, 0x2b, 0x0c, 0xe4, 0x0d, 0x9a, 0x0f,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x4e, 0x01, 0xff, 0x02, 0xad, 0x04, 0x59, 0x06, 0x02, 0x08, 0xa9, 0x09, 0x4e, 0x0b, 0xf0,
 | 
			
		||||
    0x0c, 0x90, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0xc8, 0x01, 0x61, 0x03, 0xf7, 0x04,
 | 
			
		||||
    0x8c, 0x06, 0x1e, 0x08, 0xad, 0x09, 0x3b, 0x0b, 0xc7, 0x0c, 0x50, 0x0e, 0xd7, 0x0f, 0x5c, 0x01, 0xe0, 0x02, 0x61,
 | 
			
		||||
    0x04, 0xe0, 0x05, 0x5d, 0x07, 0xd8, 0x08, 0x51, 0x0a, 0xc8, 0x0b, 0x3d, 0x0d, 0xb1, 0x0e, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x22, 0x00, 0x92, 0x01, 0x00, 0x03, 0x6c, 0x04, 0xd6, 0x05, 0x3e, 0x07, 0xa5, 0x08, 0x0a, 0x0a, 0x6d, 0x0b, 0xcf,
 | 
			
		||||
    0x0c, 0x2e, 0x0e, 0x8c, 0x0f, 0xe9, 0x00, 0x43, 0x02, 0x9d, 0x03, 0xf4, 0x04, 0x4a, 0x06, 0x9e, 0x07, 0xf1, 0x08,
 | 
			
		||||
    0x42, 0x0a, 0x92, 0x0b, 0xe0, 0x0c, 0x2c, 0x0e, 0x77, 0x0f, 0xc1, 0x00, 0x09, 0x02, 0x50, 0x03, 0x95, 0x04, 0xd8,
 | 
			
		||||
    0x05, 0x1b, 0x07, 0x5c, 0x08, 0x9b, 0x09, 0xd9, 0x0a, 0x16, 0x0c, 0x51, 0x0d, 0x8b, 0x0e, 0xc4, 0x0f, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x00, 0x31, 0x02, 0x66, 0x03, 0x99, 0x04, 0xcb, 0x05, 0xfc, 0x06, 0x2c,
 | 
			
		||||
    0x08, 0x5a, 0x09, 0x87, 0x0a, 0xb3, 0x0b, 0xdd, 0x0c, 0x07, 0x0e, 0x2f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00,
 | 
			
		||||
    0x7c, 0x01, 0xa0, 0x02, 0xc4, 0x03, 0xe6, 0x04, 0x07, 0x06, 0x27, 0x07, 0x46, 0x08, 0x64, 0x09, 0x81, 0x0a, 0x9c,
 | 
			
		||||
    0x0b, 0xb7, 0x0c, 0xd0, 0x0d, 0xe8, 0x0e, 0x00, 0x10, 0x00, 0x00, 0x2a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0e, 0x00, 0x10,
 | 
			
		||||
    0x00, 0x12, 0x00, 0x16, 0x00, 0x18, 0x00, 0x1a, 0x00, 0x1e, 0x00, 0x20, 0x00, 0x24, 0x00, 0x26, 0x00, 0x2a, 0x00,
 | 
			
		||||
    0x2e, 0x00, 0x32, 0x00, 0x36, 0x00, 0x3a, 0x00, 0x40, 0x00, 0x44, 0x00, 0x4a, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x5a,
 | 
			
		||||
    0x00, 0x62, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, 0x92, 0x00, 0x9a, 0x00, 0xa6, 0x00,
 | 
			
		||||
    0xb0, 0x00, 0xbc, 0x00, 0xc8, 0x00, 0xd4, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x8a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, 0x18, 0x00,
 | 
			
		||||
    0x1c, 0x00, 0x20, 0x00, 0x24, 0x00, 0x28, 0x00, 0x2c, 0x00, 0x30, 0x00, 0x34, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x44,
 | 
			
		||||
    0x00, 0x4c, 0x00, 0x50, 0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00,
 | 
			
		||||
    0x90, 0x00, 0x98, 0x00, 0xa0, 0x00, 0xa8, 0x00, 0xb0, 0x00, 0xb8, 0x00, 0xc4, 0x00, 0xd0, 0x00, 0xdc, 0x00, 0xe8,
 | 
			
		||||
    0x00, 0xf4, 0x00, 0x00, 0x01, 0x0c, 0x01, 0x1c, 0x01, 0x2c, 0x01, 0x00, 0x00, 0xea, 0xfd, 0xff, 0xff, 0x04, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04,
 | 
			
		||||
    0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00,
 | 
			
		||||
    0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08,
 | 
			
		||||
    0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00,
 | 
			
		||||
    0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4a, 0xfe, 0xff, 0xff, 0x04,
 | 
			
		||||
    0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x7c, 0x7f, 0x79, 0x7f, 0x76, 0x7f, 0xfa, 0xff, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x70, 0x7f, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0xe9, 0xff, 0xfe, 0xff, 0x00, 0x00, 0x4b, 0x7f, 0xd0,
 | 
			
		||||
    0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x7f, 0xa0, 0xff, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x7e, 0x42, 0xff, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xfd, 0x7d, 0x86, 0xfe, 0x04, 0x00, 0x00, 0x00, 0x87, 0x7c, 0x1d, 0xfd, 0x12, 0x00, 0x00, 0x00, 0xb6,
 | 
			
		||||
    0x79, 0x7f, 0xfa, 0x3e, 0x00, 0x00, 0x00, 0x73, 0x74, 0xf9, 0xf5, 0xca, 0x00, 0x00, 0x00, 0x36, 0x6b, 0x33, 0xef,
 | 
			
		||||
    0x32, 0x02, 0x00, 0x00, 0x9b, 0x5c, 0x87, 0xe7, 0xce, 0x04, 0x00, 0x00, 0xf0, 0x48, 0xde, 0xe2, 0xa0, 0x07, 0x00,
 | 
			
		||||
    0x00, 0x6e, 0x33, 0x8a, 0xe4, 0xa4, 0x08, 0x00, 0x00, 0x9c, 0x20, 0x22, 0xeb, 0x4c, 0x07, 0x00, 0x00, 0x0a, 0x13,
 | 
			
		||||
    0x7d, 0xf2, 0x02, 0x05, 0x00, 0x00, 0x89, 0x0a, 0x17, 0xf8, 0x06, 0x03, 0x00, 0x00, 0xa6, 0x05, 0xa0, 0xfb, 0xb4,
 | 
			
		||||
    0x01, 0x00, 0x00, 0xfa, 0x02, 0xac, 0xfd, 0xe8, 0x00, 0x00, 0x00, 0x8e, 0x01, 0xc7, 0xfe, 0x7a, 0x00, 0x00, 0x00,
 | 
			
		||||
    0xcf, 0x00, 0x5c, 0xff, 0x40, 0x00, 0x00, 0x00, 0x6b, 0x00, 0xab, 0xff, 0x22, 0x00, 0x00, 0x00, 0x38, 0x00, 0xd3,
 | 
			
		||||
    0xff, 0x12, 0x00, 0x00, 0x00, 0x1d, 0x00, 0xea, 0xff, 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xf3, 0xff, 0x06, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x08, 0x00, 0xf8, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x02,
 | 
			
		||||
    0x00, 0xfd, 0xff, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfd, 0xff,
 | 
			
		||||
    0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x72, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x82, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4d, 0x01, 0x00, 0x00,
 | 
			
		||||
    0x92, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xff, 0xff, 0xff, 0x04, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00,
 | 
			
		||||
    0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
 | 
			
		||||
    0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0a, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x13, 0x00, 0x17, 0x00,
 | 
			
		||||
    0x1b, 0x00, 0x20, 0x00, 0x25, 0x00, 0x2a, 0x00, 0x30, 0x00, 0x35, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x49, 0x00, 0x51,
 | 
			
		||||
    0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x71, 0x00, 0x7a, 0x00, 0x83, 0x00, 0x8d, 0x00, 0x97, 0x00, 0xa1, 0x00,
 | 
			
		||||
    0xac, 0x00, 0xb7, 0x00, 0xc2, 0x00, 0xcd, 0x00, 0xd9, 0x00, 0xe5, 0x00, 0xf2, 0x00, 0xff, 0x00, 0x0c, 0x01, 0x19,
 | 
			
		||||
    0x01, 0x27, 0x01, 0x35, 0x01, 0x43, 0x01, 0x52, 0x01, 0x61, 0x01, 0x70, 0x01, 0x7f, 0x01, 0x8f, 0x01, 0x9f, 0x01,
 | 
			
		||||
    0xaf, 0x01, 0xc0, 0x01, 0xd1, 0x01, 0xe2, 0x01, 0xf3, 0x01, 0x05, 0x02, 0x17, 0x02, 0x29, 0x02, 0x3c, 0x02, 0x4e,
 | 
			
		||||
    0x02, 0x61, 0x02, 0x75, 0x02, 0x88, 0x02, 0x9c, 0x02, 0xb0, 0x02, 0xc4, 0x02, 0xd8, 0x02, 0xed, 0x02, 0x02, 0x03,
 | 
			
		||||
    0x17, 0x03, 0x2c, 0x03, 0x41, 0x03, 0x57, 0x03, 0x6d, 0x03, 0x83, 0x03, 0x99, 0x03, 0xb0, 0x03, 0xc7, 0x03, 0xdd,
 | 
			
		||||
    0x03, 0xf4, 0x03, 0x0c, 0x04, 0x23, 0x04, 0x3b, 0x04, 0x52, 0x04, 0x6a, 0x04, 0x82, 0x04, 0x9a, 0x04, 0xb3, 0x04,
 | 
			
		||||
    0xcb, 0x04, 0xe4, 0x04, 0xfd, 0x04, 0x16, 0x05, 0x2f, 0x05, 0x48, 0x05, 0x61, 0x05, 0x7a, 0x05, 0x94, 0x05, 0xad,
 | 
			
		||||
    0x05, 0xc7, 0x05, 0xe1, 0x05, 0xfb, 0x05, 0x15, 0x06, 0x2f, 0x06, 0x49, 0x06, 0x63, 0x06, 0x7e, 0x06, 0x98, 0x06,
 | 
			
		||||
    0xb2, 0x06, 0xcd, 0x06, 0xe7, 0x06, 0x02, 0x07, 0x1d, 0x07, 0x37, 0x07, 0x52, 0x07, 0x6d, 0x07, 0x87, 0x07, 0xa2,
 | 
			
		||||
    0x07, 0xbd, 0x07, 0xd8, 0x07, 0xf3, 0x07, 0x0d, 0x08, 0x28, 0x08, 0x43, 0x08, 0x5e, 0x08, 0x79, 0x08, 0x93, 0x08,
 | 
			
		||||
    0xae, 0x08, 0xc9, 0x08, 0xe3, 0x08, 0xfe, 0x08, 0x19, 0x09, 0x33, 0x09, 0x4e, 0x09, 0x68, 0x09, 0x82, 0x09, 0x9d,
 | 
			
		||||
    0x09, 0xb7, 0x09, 0xd1, 0x09, 0xeb, 0x09, 0x05, 0x0a, 0x1f, 0x0a, 0x39, 0x0a, 0x53, 0x0a, 0x6c, 0x0a, 0x86, 0x0a,
 | 
			
		||||
    0x9f, 0x0a, 0xb8, 0x0a, 0xd1, 0x0a, 0xea, 0x0a, 0x03, 0x0b, 0x1c, 0x0b, 0x35, 0x0b, 0x4d, 0x0b, 0x66, 0x0b, 0x7e,
 | 
			
		||||
    0x0b, 0x96, 0x0b, 0xae, 0x0b, 0xc5, 0x0b, 0xdd, 0x0b, 0xf4, 0x0b, 0x0c, 0x0c, 0x23, 0x0c, 0x39, 0x0c, 0x50, 0x0c,
 | 
			
		||||
    0x67, 0x0c, 0x7d, 0x0c, 0x93, 0x0c, 0xa9, 0x0c, 0xbf, 0x0c, 0xd4, 0x0c, 0xe9, 0x0c, 0xfe, 0x0c, 0x13, 0x0d, 0x28,
 | 
			
		||||
    0x0d, 0x3c, 0x0d, 0x50, 0x0d, 0x64, 0x0d, 0x78, 0x0d, 0x8b, 0x0d, 0x9f, 0x0d, 0xb2, 0x0d, 0xc4, 0x0d, 0xd7, 0x0d,
 | 
			
		||||
    0xe9, 0x0d, 0xfb, 0x0d, 0x0d, 0x0e, 0x1e, 0x0e, 0x2f, 0x0e, 0x40, 0x0e, 0x51, 0x0e, 0x61, 0x0e, 0x71, 0x0e, 0x81,
 | 
			
		||||
    0x0e, 0x90, 0x0e, 0x9f, 0x0e, 0xae, 0x0e, 0xbd, 0x0e, 0xcb, 0x0e, 0xd9, 0x0e, 0xe7, 0x0e, 0xf4, 0x0e, 0x01, 0x0f,
 | 
			
		||||
    0x0e, 0x0f, 0x1b, 0x0f, 0x27, 0x0f, 0x33, 0x0f, 0x3e, 0x0f, 0x49, 0x0f, 0x54, 0x0f, 0x5f, 0x0f, 0x69, 0x0f, 0x73,
 | 
			
		||||
    0x0f, 0x7d, 0x0f, 0x86, 0x0f, 0x8f, 0x0f, 0x98, 0x0f, 0xa0, 0x0f, 0xa8, 0x0f, 0xaf, 0x0f, 0xb7, 0x0f, 0xbe, 0x0f,
 | 
			
		||||
    0xc4, 0x0f, 0xcb, 0x0f, 0xd0, 0x0f, 0xd6, 0x0f, 0xdb, 0x0f, 0xe0, 0x0f, 0xe5, 0x0f, 0xe9, 0x0f, 0xed, 0x0f, 0xf0,
 | 
			
		||||
    0x0f, 0xf3, 0x0f, 0xf6, 0x0f, 0xf9, 0x0f, 0xfb, 0x0f, 0xfc, 0x0f, 0xfe, 0x0f, 0xff, 0x0f, 0x00, 0x10, 0x00, 0x10,
 | 
			
		||||
    0x00, 0x10, 0x00, 0x10, 0xff, 0x0f, 0xfe, 0x0f, 0xfc, 0x0f, 0xfb, 0x0f, 0xf9, 0x0f, 0xf6, 0x0f, 0xf3, 0x0f, 0xf0,
 | 
			
		||||
    0x0f, 0xed, 0x0f, 0xe9, 0x0f, 0xe5, 0x0f, 0xe0, 0x0f, 0xdb, 0x0f, 0xd6, 0x0f, 0xd0, 0x0f, 0xcb, 0x0f, 0xc4, 0x0f,
 | 
			
		||||
    0xbe, 0x0f, 0xb7, 0x0f, 0xaf, 0x0f, 0xa8, 0x0f, 0xa0, 0x0f, 0x98, 0x0f, 0x8f, 0x0f, 0x86, 0x0f, 0x7d, 0x0f, 0x73,
 | 
			
		||||
    0x0f, 0x69, 0x0f, 0x5f, 0x0f, 0x54, 0x0f, 0x49, 0x0f, 0x3e, 0x0f, 0x33, 0x0f, 0x27, 0x0f, 0x1b, 0x0f, 0x0e, 0x0f,
 | 
			
		||||
    0x01, 0x0f, 0xf4, 0x0e, 0xe7, 0x0e, 0xd9, 0x0e, 0xcb, 0x0e, 0xbd, 0x0e, 0xae, 0x0e, 0x9f, 0x0e, 0x90, 0x0e, 0x81,
 | 
			
		||||
    0x0e, 0x71, 0x0e, 0x61, 0x0e, 0x51, 0x0e, 0x40, 0x0e, 0x2f, 0x0e, 0x1e, 0x0e, 0x0d, 0x0e, 0xfb, 0x0d, 0xe9, 0x0d,
 | 
			
		||||
    0xd7, 0x0d, 0xc4, 0x0d, 0xb2, 0x0d, 0x9f, 0x0d, 0x8b, 0x0d, 0x78, 0x0d, 0x64, 0x0d, 0x50, 0x0d, 0x3c, 0x0d, 0x28,
 | 
			
		||||
    0x0d, 0x13, 0x0d, 0xfe, 0x0c, 0xe9, 0x0c, 0xd4, 0x0c, 0xbf, 0x0c, 0xa9, 0x0c, 0x93, 0x0c, 0x7d, 0x0c, 0x67, 0x0c,
 | 
			
		||||
    0x50, 0x0c, 0x39, 0x0c, 0x23, 0x0c, 0x0c, 0x0c, 0xf4, 0x0b, 0xdd, 0x0b, 0xc5, 0x0b, 0xae, 0x0b, 0x96, 0x0b, 0x7e,
 | 
			
		||||
    0x0b, 0x66, 0x0b, 0x4d, 0x0b, 0x35, 0x0b, 0x1c, 0x0b, 0x03, 0x0b, 0xea, 0x0a, 0xd1, 0x0a, 0xb8, 0x0a, 0x9f, 0x0a,
 | 
			
		||||
    0x86, 0x0a, 0x6c, 0x0a, 0x53, 0x0a, 0x39, 0x0a, 0x1f, 0x0a, 0x05, 0x0a, 0xeb, 0x09, 0xd1, 0x09, 0xb7, 0x09, 0x9d,
 | 
			
		||||
    0x09, 0x82, 0x09, 0x68, 0x09, 0x4e, 0x09, 0x33, 0x09, 0x19, 0x09, 0xfe, 0x08, 0xe3, 0x08, 0xc9, 0x08, 0xae, 0x08,
 | 
			
		||||
    0x93, 0x08, 0x79, 0x08, 0x5e, 0x08, 0x43, 0x08, 0x28, 0x08, 0x0d, 0x08, 0xf3, 0x07, 0xd8, 0x07, 0xbd, 0x07, 0xa2,
 | 
			
		||||
    0x07, 0x87, 0x07, 0x6d, 0x07, 0x52, 0x07, 0x37, 0x07, 0x1d, 0x07, 0x02, 0x07, 0xe7, 0x06, 0xcd, 0x06, 0xb2, 0x06,
 | 
			
		||||
    0x98, 0x06, 0x7e, 0x06, 0x63, 0x06, 0x49, 0x06, 0x2f, 0x06, 0x15, 0x06, 0xfb, 0x05, 0xe1, 0x05, 0xc7, 0x05, 0xad,
 | 
			
		||||
    0x05, 0x94, 0x05, 0x7a, 0x05, 0x61, 0x05, 0x48, 0x05, 0x2f, 0x05, 0x16, 0x05, 0xfd, 0x04, 0xe4, 0x04, 0xcb, 0x04,
 | 
			
		||||
    0xb3, 0x04, 0x9a, 0x04, 0x82, 0x04, 0x6a, 0x04, 0x52, 0x04, 0x3b, 0x04, 0x23, 0x04, 0x0c, 0x04, 0xf4, 0x03, 0xdd,
 | 
			
		||||
    0x03, 0xc7, 0x03, 0xb0, 0x03, 0x99, 0x03, 0x83, 0x03, 0x6d, 0x03, 0x57, 0x03, 0x41, 0x03, 0x2c, 0x03, 0x17, 0x03,
 | 
			
		||||
    0x02, 0x03, 0xed, 0x02, 0xd8, 0x02, 0xc4, 0x02, 0xb0, 0x02, 0x9c, 0x02, 0x88, 0x02, 0x75, 0x02, 0x61, 0x02, 0x4e,
 | 
			
		||||
    0x02, 0x3c, 0x02, 0x29, 0x02, 0x17, 0x02, 0x05, 0x02, 0xf3, 0x01, 0xe2, 0x01, 0xd1, 0x01, 0xc0, 0x01, 0xaf, 0x01,
 | 
			
		||||
    0x9f, 0x01, 0x8f, 0x01, 0x7f, 0x01, 0x70, 0x01, 0x61, 0x01, 0x52, 0x01, 0x43, 0x01, 0x35, 0x01, 0x27, 0x01, 0x19,
 | 
			
		||||
    0x01, 0x0c, 0x01, 0xff, 0x00, 0xf2, 0x00, 0xe5, 0x00, 0xd9, 0x00, 0xcd, 0x00, 0xc2, 0x00, 0xb7, 0x00, 0xac, 0x00,
 | 
			
		||||
    0xa1, 0x00, 0x97, 0x00, 0x8d, 0x00, 0x83, 0x00, 0x7a, 0x00, 0x71, 0x00, 0x68, 0x00, 0x60, 0x00, 0x58, 0x00, 0x51,
 | 
			
		||||
    0x00, 0x49, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2a, 0x00, 0x25, 0x00, 0x20, 0x00, 0x1b, 0x00,
 | 
			
		||||
    0x17, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xee, 0xff, 0xff, 0x38, 0xee, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x4d, 0x4c,
 | 
			
		||||
    0x49, 0x52, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00,
 | 
			
		||||
    0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0xf4, 0x05, 0x00, 0x00, 0xf8, 0x05, 0x00,
 | 
			
		||||
    0x00, 0xfc, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x68, 0x05, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, 0xc8, 0x04, 0x00, 0x00, 0x70,
 | 
			
		||||
    0x04, 0x00, 0x00, 0x4c, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0xc8, 0x03, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x00,
 | 
			
		||||
    0x4c, 0x03, 0x00, 0x00, 0x14, 0x03, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, 0x6c, 0x01, 0x00,
 | 
			
		||||
    0x00, 0x48, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0x78, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf6, 0xfa, 0xff, 0xff, 0x0c,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x16, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3a, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x0e, 0x00, 0x00, 0x00, 0xa6, 0xfc, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x68, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xd6, 0xfc, 0xff, 0xff, 0x14,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x98, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x12, 0x00, 0x00, 0x00, 0x06, 0xfd, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xc8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x25,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0xfd, 0xff, 0xff,
 | 
			
		||||
    0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00,
 | 
			
		||||
    0x00, 0xf8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1e, 0xfc, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x84, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x30, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x69,
 | 
			
		||||
    0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x61, 0x6c,
 | 
			
		||||
    0x65, 0x00, 0x02, 0x24, 0x0f, 0x02, 0x01, 0x02, 0x03, 0x40, 0x04, 0x04, 0x04, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xdc, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x24, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x73, 0x6e,
 | 
			
		||||
    0x72, 0x5f, 0x73, 0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x06, 0x04, 0x02, 0x24, 0x01, 0x01,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x08, 0x00, 0x00, 0x00, 0x20, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x0a, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f,
 | 
			
		||||
    0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67,
 | 
			
		||||
    0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e,
 | 
			
		||||
    0x67, 0x00, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x00, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e,
 | 
			
		||||
    0x61, 0x6c, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68,
 | 
			
		||||
    0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d,
 | 
			
		||||
    0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73,
 | 
			
		||||
    0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x73, 0x70, 0x65, 0x63, 0x74,
 | 
			
		||||
    0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74,
 | 
			
		||||
    0x73, 0x00, 0x09, 0xa5, 0x88, 0x75, 0x6d, 0x59, 0x4d, 0x3a, 0x31, 0x23, 0x09, 0x00, 0x01, 0x00, 0x09, 0x00, 0x29,
 | 
			
		||||
    0x3c, 0xd7, 0x03, 0x00, 0x00, 0x33, 0x03, 0x28, 0x00, 0x67, 0x3e, 0x99, 0x01, 0x0a, 0x00, 0x0e, 0x00, 0x05, 0x05,
 | 
			
		||||
    0x69, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1b, 0x25, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0xfe, 0xff, 0xff, 0x10, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x1d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x54, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x2c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68,
 | 
			
		||||
    0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x01, 0x0e, 0x01, 0x01, 0x01, 0x28, 0x04, 0x02, 0x24, 0x01, 0x00, 0x01,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x0c, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0xfe, 0xff,
 | 
			
		||||
    0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0xca, 0xff, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8c, 0xf2, 0xff, 0xff,
 | 
			
		||||
    0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x0b, 0x00,
 | 
			
		||||
    0x04, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x14,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0xf2, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x04, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
 | 
			
		||||
    0x00, 0xfe, 0xfe, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x64, 0xff, 0xff, 0xff, 0x10,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64,
 | 
			
		||||
    0x65, 0x78, 0x00, 0x02, 0x17, 0x0e, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0xf1, 0x00, 0x05, 0x00, 0x05, 0x05,
 | 
			
		||||
    0x06, 0x25, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17,
 | 
			
		||||
    0x00, 0x00, 0x00, 0xb8, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x54, 0x00, 0x66, 0x66, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74,
 | 
			
		||||
    0x68, 0x00, 0x02, 0x0e, 0x0d, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x02, 0x05, 0x05, 0x06, 0x25,
 | 
			
		||||
    0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x10,
 | 
			
		||||
    0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08,
 | 
			
		||||
    0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73,
 | 
			
		||||
    0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x07, 0x01, 0x01, 0x01, 0x0c, 0x04, 0x02, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0xc4, 0x0a,
 | 
			
		||||
    0x00, 0x00, 0x74, 0x0a, 0x00, 0x00, 0x3c, 0x0a, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x88,
 | 
			
		||||
    0x09, 0x00, 0x00, 0x40, 0x09, 0x00, 0x00, 0xfc, 0x08, 0x00, 0x00, 0xb8, 0x08, 0x00, 0x00, 0x6c, 0x08, 0x00, 0x00,
 | 
			
		||||
    0x20, 0x08, 0x00, 0x00, 0xd4, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x3c, 0x07, 0x00, 0x00, 0xf8, 0x06, 0x00,
 | 
			
		||||
    0x00, 0xb8, 0x06, 0x00, 0x00, 0x84, 0x06, 0x00, 0x00, 0x50, 0x06, 0x00, 0x00, 0x1c, 0x06, 0x00, 0x00, 0xd8, 0x05,
 | 
			
		||||
    0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x58, 0x05, 0x00, 0x00, 0x14, 0x05, 0x00, 0x00, 0xd8, 0x04, 0x00, 0x00, 0x98,
 | 
			
		||||
    0x04, 0x00, 0x00, 0x60, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xb0, 0x03, 0x00, 0x00,
 | 
			
		||||
    0x6c, 0x03, 0x00, 0x00, 0x1c, 0x03, 0x00, 0x00, 0xc4, 0x02, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x2c, 0x02, 0x00,
 | 
			
		||||
    0x00, 0xe4, 0x01, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x08, 0x01,
 | 
			
		||||
    0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xfe,
 | 
			
		||||
    0xf5, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x44, 0xf5, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x50, 0x61, 0x72,
 | 
			
		||||
    0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3e, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x84, 0xf5, 0xff, 0xff,
 | 
			
		||||
    0x0d, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x7a, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0xc0,
 | 
			
		||||
    0xf5, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75,
 | 
			
		||||
    0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
 | 
			
		||||
    0x00, 0xbe, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x04, 0xf6, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x61,
 | 
			
		||||
    0x64, 0x64, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x02, 0x18, 0x00, 0x00, 0x00, 0x38, 0xf6, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61,
 | 
			
		||||
    0x74, 0x65, 0x44, 0x69, 0x76, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2a, 0xf7, 0xff, 0xff, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
 | 
			
		||||
    0x10, 0x00, 0x00, 0x00, 0x70, 0xf6, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x61, 0x64, 0x64, 0x00, 0x01, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x28, 0x00, 0x00, 0x00, 0x5a, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x00, 0xa0, 0xf6, 0xff, 0xff, 0x03,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x6d, 0x75, 0x6c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x8a, 0xf7, 0xff, 0xff,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x02, 0x14, 0x00, 0x00, 0x00, 0xd0, 0xf6, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x32,
 | 
			
		||||
    0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xbe, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x04, 0xf7, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74,
 | 
			
		||||
    0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x6c, 0x6f, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x02, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x22,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x18, 0x00, 0x00, 0x00, 0x48, 0xf7, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x3a, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x21, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x38, 0x00, 0x00, 0x00, 0x80, 0xf7, 0xff, 0xff, 0x28, 0x00, 0x00, 0x00, 0x73,
 | 
			
		||||
    0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73,
 | 
			
		||||
    0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
 | 
			
		||||
    0x31, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x92, 0xf8, 0xff, 0xff, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x34,
 | 
			
		||||
    0x00, 0x00, 0x00, 0xd8, 0xf7, 0xff, 0xff, 0x27, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66,
 | 
			
		||||
    0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c,
 | 
			
		||||
    0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xe6, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1f,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x2c, 0x00, 0x00, 0x00, 0x2c, 0xf8, 0xff, 0xff, 0x1e, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f,
 | 
			
		||||
    0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x32, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1e,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x78, 0xf8, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x00,
 | 
			
		||||
    0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x72, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x14, 0x00, 0x00, 0x00, 0xb8,
 | 
			
		||||
    0xf8, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x01, 0x01, 0x00, 0x00, 0xa6, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xec, 0xf8, 0xff, 0xff, 0x06, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xda,
 | 
			
		||||
    0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x20, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72,
 | 
			
		||||
    0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xec, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x16, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1a,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xf9, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x43, 0x61, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x4a, 0xfa, 0xff,
 | 
			
		||||
    0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x0f, 0x1c, 0x00, 0x00, 0x00, 0x90, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61,
 | 
			
		||||
    0x6c, 0x5f, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
 | 
			
		||||
    0x86, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x00, 0x00, 0x00, 0xcc, 0xf9, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x69,
 | 
			
		||||
    0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x66, 0x66, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0xbe,
 | 
			
		||||
    0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x04, 0xfa, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67,
 | 
			
		||||
    0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x31,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, 0x44, 0xfa, 0xff, 0xff,
 | 
			
		||||
    0x15, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f,
 | 
			
		||||
    0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x42, 0xfb,
 | 
			
		||||
    0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x88, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68,
 | 
			
		||||
    0x61, 0x70, 0x65, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x76, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1c, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xbc, 0xfa, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x77, 0x69,
 | 
			
		||||
    0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00,
 | 
			
		||||
    0xb6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xfc, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, 0x6f,
 | 
			
		||||
    0x6e, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x2c, 0xfb, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x16, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xfb, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43,
 | 
			
		||||
    0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
 | 
			
		||||
    0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x8c, 0xfb, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2f, 0x73, 0x68,
 | 
			
		||||
    0x61, 0x70, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x82, 0xfc, 0xff, 0xff, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
 | 
			
		||||
    0x24, 0x00, 0x00, 0x00, 0xc8, 0xfb, 0xff, 0xff, 0x17, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79,
 | 
			
		||||
    0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x2f, 0x79, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xc2, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x08, 0xfc, 0xff, 0xff, 0x18, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f,
 | 
			
		||||
    0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x0a, 0xfd,
 | 
			
		||||
    0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x50, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e,
 | 
			
		||||
    0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73,
 | 
			
		||||
    0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x52, 0xfd, 0xff, 0xff, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x98, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66,
 | 
			
		||||
    0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00,
 | 
			
		||||
    0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x9a, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0xe0,
 | 
			
		||||
    0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65,
 | 
			
		||||
    0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x33, 0x00, 0x00, 0x01, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x29, 0x00, 0x00, 0x00, 0xe2, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x28, 0xfd, 0xff, 0xff, 0x1a,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61,
 | 
			
		||||
    0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x34, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x2a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x70, 0xfd, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x73,
 | 
			
		||||
    0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x01, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x6a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0xb0, 0xfd,
 | 
			
		||||
    0xff, 0xff, 0x13, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65,
 | 
			
		||||
    0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xaa, 0xfe, 0xff, 0xff,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x02, 0x24, 0x00, 0x00, 0x00, 0xf0, 0xfd, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65,
 | 
			
		||||
    0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xee, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x34, 0xfe, 0xff,
 | 
			
		||||
    0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f,
 | 
			
		||||
    0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32,
 | 
			
		||||
    0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x78, 0xfe, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73,
 | 
			
		||||
    0x74, 0x5f, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xa8,
 | 
			
		||||
    0xfe, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x05, 0x00, 0x00, 0x00, 0x96, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xdc, 0xfe, 0xff, 0xff, 0x07, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x5f, 0x31, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xca,
 | 
			
		||||
    0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e,
 | 
			
		||||
    0x73, 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x1c, 0x00,
 | 
			
		||||
    0x18, 0x00, 0x17, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x16,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x07, 0x2c, 0x00, 0x00, 0x00, 0x5c, 0xff, 0xff, 0xff, 0x1d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72,
 | 
			
		||||
    0x76, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f,
 | 
			
		||||
    0x66, 0x72, 0x61, 0x6d, 0x65, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0,
 | 
			
		||||
    0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1c, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00,
 | 
			
		||||
    0xa4, 0x01, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00,
 | 
			
		||||
    0x00, 0x10, 0x01, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x50, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x50, 0xfe, 0xff, 0xff, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x5c, 0xfe, 0xff, 0xff,
 | 
			
		||||
    0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x68, 0xfe, 0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x2a, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7c, 0xfe, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x12, 0x70, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x13,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b,
 | 
			
		||||
    0x4c, 0x6f, 0x67, 0x00, 0x98, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x20, 0x0a, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x50, 0x43, 0x41, 0x4e, 0x00, 0x00, 0xb8, 0xfe,
 | 
			
		||||
    0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x23, 0x00, 0x00, 0x00, 0x53,
 | 
			
		||||
    0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63,
 | 
			
		||||
    0x74, 0x72, 0x61, 0x6c, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0xf0, 0xfe, 0xff,
 | 
			
		||||
    0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x1a, 0x00, 0x00, 0x00, 0x53, 0x69,
 | 
			
		||||
    0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x71, 0x75, 0x61, 0x72,
 | 
			
		||||
    0x65, 0x52, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65,
 | 
			
		||||
    0x72, 0x42, 0x61, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x02, 0x6c, 0xff, 0xff, 0xff, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x0c, 0x00, 0x10, 0x00, 0x0f,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x35, 0x7c, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0xa0, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0a,
 | 
			
		||||
    0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x66, 0x66, 0x74, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff,
 | 
			
		||||
    0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x12, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67,
 | 
			
		||||
    0x6e, 0x61, 0x6c, 0x46, 0x66, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x0c, 0x00,
 | 
			
		||||
    0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x00, 0x00, 0x16, 0x0c, 0x00, 0x10, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00,
 | 
			
		||||
    0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67,
 | 
			
		||||
    0x6e, 0x61, 0x6c, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x00};
 | 
			
		||||
 | 
			
		||||
}  // namespace micro_wake_word
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
@@ -1,12 +1,5 @@
 | 
			
		||||
#include "micro_wake_word.h"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This is a workaround until we can figure out a way to get
 | 
			
		||||
 * the tflite-micro idf component code available in CI
 | 
			
		||||
 *
 | 
			
		||||
 * */
 | 
			
		||||
//
 | 
			
		||||
#ifndef CLANG_TIDY
 | 
			
		||||
#include "streaming_model.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
@@ -14,13 +7,13 @@
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include "audio_preprocessor_int8_model_data.h"
 | 
			
		||||
#include <frontend.h>
 | 
			
		||||
#include <frontend_util.h>
 | 
			
		||||
 | 
			
		||||
#include <tensorflow/lite/core/c/common.h>
 | 
			
		||||
#include <tensorflow/lite/micro/micro_interpreter.h>
 | 
			
		||||
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -29,9 +22,9 @@ namespace micro_wake_word {
 | 
			
		||||
static const char *const TAG = "micro_wake_word";
 | 
			
		||||
 | 
			
		||||
static const size_t SAMPLE_RATE_HZ = 16000;  // 16 kHz
 | 
			
		||||
static const size_t BUFFER_LENGTH = 500;     // 0.5 seconds
 | 
			
		||||
static const size_t BUFFER_LENGTH = 64;      // 0.064 seconds
 | 
			
		||||
static const size_t BUFFER_SIZE = SAMPLE_RATE_HZ / 1000 * BUFFER_LENGTH;
 | 
			
		||||
static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000;  // 32ms * 16kHz / 1000ms
 | 
			
		||||
static const size_t INPUT_BUFFER_SIZE = 16 * SAMPLE_RATE_HZ / 1000;  // 16ms * 16kHz / 1000ms
 | 
			
		||||
 | 
			
		||||
float MicroWakeWord::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
 | 
			
		||||
 | 
			
		||||
@@ -56,57 +49,55 @@ static const LogString *micro_wake_word_state_to_string(State state) {
 | 
			
		||||
 | 
			
		||||
void MicroWakeWord::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "microWakeWord:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Wake Word: %s", this->get_wake_word().c_str());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Probability cutoff: %.3f", this->probability_cutoff_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Sliding window size: %d", this->sliding_window_average_size_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  models:");
 | 
			
		||||
  for (auto &model : this->wake_word_models_) {
 | 
			
		||||
    model.log_model_config();
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
  this->vad_model_->log_model_config();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicroWakeWord::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up microWakeWord...");
 | 
			
		||||
 | 
			
		||||
  if (!this->initialize_models()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to initialize models");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ExternalRAMAllocator<int16_t> allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
 | 
			
		||||
  this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t));
 | 
			
		||||
  if (this->input_buffer_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Could not allocate input buffer");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t));
 | 
			
		||||
  if (this->ring_buffer_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Could not allocate ring buffer");
 | 
			
		||||
  if (!this->register_streaming_ops_(this->streaming_op_resolver_)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Micro Wake Word initialized");
 | 
			
		||||
 | 
			
		||||
  this->frontend_config_.window.size_ms = FEATURE_DURATION_MS;
 | 
			
		||||
  this->frontend_config_.window.step_size_ms = this->features_step_size_;
 | 
			
		||||
  this->frontend_config_.filterbank.num_channels = PREPROCESSOR_FEATURE_SIZE;
 | 
			
		||||
  this->frontend_config_.filterbank.lower_band_limit = 125.0;
 | 
			
		||||
  this->frontend_config_.filterbank.upper_band_limit = 7500.0;
 | 
			
		||||
  this->frontend_config_.noise_reduction.smoothing_bits = 10;
 | 
			
		||||
  this->frontend_config_.noise_reduction.even_smoothing = 0.025;
 | 
			
		||||
  this->frontend_config_.noise_reduction.odd_smoothing = 0.06;
 | 
			
		||||
  this->frontend_config_.noise_reduction.min_signal_remaining = 0.05;
 | 
			
		||||
  this->frontend_config_.pcan_gain_control.enable_pcan = 1;
 | 
			
		||||
  this->frontend_config_.pcan_gain_control.strength = 0.95;
 | 
			
		||||
  this->frontend_config_.pcan_gain_control.offset = 80.0;
 | 
			
		||||
  this->frontend_config_.pcan_gain_control.gain_bits = 21;
 | 
			
		||||
  this->frontend_config_.log_scale.enable_log = 1;
 | 
			
		||||
  this->frontend_config_.log_scale.scale_shift = 6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int MicroWakeWord::read_microphone_() {
 | 
			
		||||
  size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
 | 
			
		||||
  if (bytes_read == 0) {
 | 
			
		||||
    return 0;
 | 
			
		||||
void MicroWakeWord::add_wake_word_model(const uint8_t *model_start, float probability_cutoff,
 | 
			
		||||
                                        size_t sliding_window_average_size, const std::string &wake_word,
 | 
			
		||||
                                        size_t tensor_arena_size) {
 | 
			
		||||
  this->wake_word_models_.emplace_back(model_start, probability_cutoff, sliding_window_average_size, wake_word,
 | 
			
		||||
                                       tensor_arena_size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  size_t bytes_free = this->ring_buffer_->free();
 | 
			
		||||
 | 
			
		||||
  if (bytes_free < bytes_read) {
 | 
			
		||||
    ESP_LOGW(TAG,
 | 
			
		||||
             "Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). "
 | 
			
		||||
             "Resetting the ring buffer. Wake word detection accuracy will be reduced.",
 | 
			
		||||
             bytes_free, bytes_read);
 | 
			
		||||
 | 
			
		||||
    this->ring_buffer_->reset();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
void MicroWakeWord::add_vad_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size,
 | 
			
		||||
                                  size_t tensor_arena_size) {
 | 
			
		||||
  this->vad_model_ = make_unique<VADModel>(model_start, probability_cutoff, sliding_window_size, tensor_arena_size);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void MicroWakeWord::loop() {
 | 
			
		||||
  switch (this->state_) {
 | 
			
		||||
@@ -124,9 +115,12 @@ void MicroWakeWord::loop() {
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case State::DETECTING_WAKE_WORD:
 | 
			
		||||
      while (!this->has_enough_samples_()) {
 | 
			
		||||
        this->read_microphone_();
 | 
			
		||||
      if (this->detect_wake_word_()) {
 | 
			
		||||
        ESP_LOGD(TAG, "Wake Word Detected");
 | 
			
		||||
      }
 | 
			
		||||
      this->update_model_probabilities_();
 | 
			
		||||
      if (this->detect_wake_words_()) {
 | 
			
		||||
        ESP_LOGD(TAG, "Wake Word '%s' Detected", (this->detected_wake_word_).c_str());
 | 
			
		||||
        this->detected_ = true;
 | 
			
		||||
        this->set_state_(State::STOP_MICROPHONE);
 | 
			
		||||
      }
 | 
			
		||||
@@ -136,13 +130,16 @@ void MicroWakeWord::loop() {
 | 
			
		||||
      this->microphone_->stop();
 | 
			
		||||
      this->set_state_(State::STOPPING_MICROPHONE);
 | 
			
		||||
      this->high_freq_.stop();
 | 
			
		||||
      this->unload_models_();
 | 
			
		||||
      this->deallocate_buffers_();
 | 
			
		||||
      break;
 | 
			
		||||
    case State::STOPPING_MICROPHONE:
 | 
			
		||||
      if (this->microphone_->is_stopped()) {
 | 
			
		||||
        this->set_state_(State::IDLE);
 | 
			
		||||
        if (this->detected_) {
 | 
			
		||||
          this->wake_word_detected_trigger_->trigger(this->detected_wake_word_);
 | 
			
		||||
          this->detected_ = false;
 | 
			
		||||
          this->wake_word_detected_trigger_->trigger(this->wake_word_);
 | 
			
		||||
          this->detected_wake_word_ = "";
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
@@ -150,14 +147,34 @@ void MicroWakeWord::loop() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicroWakeWord::start() {
 | 
			
		||||
  if (!this->is_ready()) {
 | 
			
		||||
    ESP_LOGW(TAG, "Wake word detection can't start as the component hasn't been setup yet");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!this->load_models_() || !this->allocate_buffers_()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to load the wake word model(s) or allocate buffers");
 | 
			
		||||
    this->status_set_error();
 | 
			
		||||
  } else {
 | 
			
		||||
    this->status_clear_error();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->status_has_error()) {
 | 
			
		||||
    ESP_LOGW(TAG, "Wake word component has an error. Please check logs");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->state_ != State::IDLE) {
 | 
			
		||||
    ESP_LOGW(TAG, "Wake word is already running");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->reset_states_();
 | 
			
		||||
  this->set_state_(State::START_MICROPHONE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -179,289 +196,218 @@ void MicroWakeWord::set_state_(State state) {
 | 
			
		||||
  this->state_ = state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::initialize_models() {
 | 
			
		||||
  ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
  ExternalRAMAllocator<int8_t> features_allocator(ExternalRAMAllocator<int8_t>::ALLOW_FAILURE);
 | 
			
		||||
size_t MicroWakeWord::read_microphone_() {
 | 
			
		||||
  size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
 | 
			
		||||
  if (bytes_read == 0) {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t bytes_free = this->ring_buffer_->free();
 | 
			
		||||
 | 
			
		||||
  if (bytes_free < bytes_read) {
 | 
			
		||||
    ESP_LOGW(TAG,
 | 
			
		||||
             "Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). "
 | 
			
		||||
             "Resetting the ring buffer. Wake word detection accuracy will be reduced.",
 | 
			
		||||
             bytes_free, bytes_read);
 | 
			
		||||
 | 
			
		||||
    this->ring_buffer_->reset();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::allocate_buffers_() {
 | 
			
		||||
  ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
 | 
			
		||||
 | 
			
		||||
  this->streaming_tensor_arena_ = arena_allocator.allocate(STREAMING_MODEL_ARENA_SIZE);
 | 
			
		||||
  if (this->streaming_tensor_arena_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena.");
 | 
			
		||||
  if (this->input_buffer_ == nullptr) {
 | 
			
		||||
    this->input_buffer_ = audio_samples_allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t));
 | 
			
		||||
    if (this->input_buffer_ == nullptr) {
 | 
			
		||||
      ESP_LOGE(TAG, "Could not allocate input buffer");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  this->streaming_var_arena_ = arena_allocator.allocate(STREAMING_MODEL_VARIABLE_ARENA_SIZE);
 | 
			
		||||
  if (this->streaming_var_arena_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Could not allocate the streaming model variable's tensor arena.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->preprocessor_tensor_arena_ = arena_allocator.allocate(PREPROCESSOR_ARENA_SIZE);
 | 
			
		||||
  if (this->preprocessor_tensor_arena_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Could not allocate the audio preprocessor model's tensor arena.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->new_features_data_ = features_allocator.allocate(PREPROCESSOR_FEATURE_SIZE);
 | 
			
		||||
  if (this->new_features_data_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Could not allocate the audio features buffer.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(SAMPLE_DURATION_COUNT);
 | 
			
		||||
  if (this->preprocessor_audio_buffer_ == nullptr) {
 | 
			
		||||
    this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(this->new_samples_to_get_());
 | 
			
		||||
    if (this->preprocessor_audio_buffer_ == nullptr) {
 | 
			
		||||
      ESP_LOGE(TAG, "Could not allocate the audio preprocessor's buffer.");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  this->preprocessor_model_ = tflite::GetModel(G_AUDIO_PREPROCESSOR_INT8_TFLITE);
 | 
			
		||||
  if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) {
 | 
			
		||||
    ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->streaming_model_ = tflite::GetModel(this->model_start_);
 | 
			
		||||
  if (this->streaming_model_->version() != TFLITE_SCHEMA_VERSION) {
 | 
			
		||||
    ESP_LOGE(TAG, "Wake word's streaming model's schema is not supported");
 | 
			
		||||
  if (this->ring_buffer_ == nullptr) {
 | 
			
		||||
    this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t));
 | 
			
		||||
    if (this->ring_buffer_ == nullptr) {
 | 
			
		||||
      ESP_LOGE(TAG, "Could not allocate ring buffer");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  static tflite::MicroMutableOpResolver<18> preprocessor_op_resolver;
 | 
			
		||||
  static tflite::MicroMutableOpResolver<17> streaming_op_resolver;
 | 
			
		||||
 | 
			
		||||
  if (!this->register_preprocessor_ops_(preprocessor_op_resolver))
 | 
			
		||||
    return false;
 | 
			
		||||
  if (!this->register_streaming_ops_(streaming_op_resolver))
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  tflite::MicroAllocator *ma =
 | 
			
		||||
      tflite::MicroAllocator::Create(this->streaming_var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE);
 | 
			
		||||
  this->mrv_ = tflite::MicroResourceVariables::Create(ma, 15);
 | 
			
		||||
 | 
			
		||||
  static tflite::MicroInterpreter static_preprocessor_interpreter(
 | 
			
		||||
      this->preprocessor_model_, preprocessor_op_resolver, this->preprocessor_tensor_arena_, PREPROCESSOR_ARENA_SIZE);
 | 
			
		||||
 | 
			
		||||
  static tflite::MicroInterpreter static_streaming_interpreter(this->streaming_model_, streaming_op_resolver,
 | 
			
		||||
                                                               this->streaming_tensor_arena_,
 | 
			
		||||
                                                               STREAMING_MODEL_ARENA_SIZE, this->mrv_);
 | 
			
		||||
 | 
			
		||||
  this->preprocessor_interperter_ = &static_preprocessor_interpreter;
 | 
			
		||||
  this->streaming_interpreter_ = &static_streaming_interpreter;
 | 
			
		||||
 | 
			
		||||
  // Allocate tensors for each models.
 | 
			
		||||
  if (this->preprocessor_interperter_->AllocateTensors() != kTfLiteOk) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to allocate tensors for the audio preprocessor");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->streaming_interpreter_->AllocateTensors() != kTfLiteOk) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to allocate tensors for the streaming model");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Verify input tensor matches expected values
 | 
			
		||||
  TfLiteTensor *input = this->streaming_interpreter_->input(0);
 | 
			
		||||
  if ((input->dims->size != 3) || (input->dims->data[0] != 1) || (input->dims->data[0] != 1) ||
 | 
			
		||||
      (input->dims->data[1] != 1) || (input->dims->data[2] != PREPROCESSOR_FEATURE_SIZE)) {
 | 
			
		||||
    ESP_LOGE(TAG, "Wake word detection model tensor input dimensions is not 1x1x%u", input->dims->data[2]);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (input->type != kTfLiteInt8) {
 | 
			
		||||
    ESP_LOGE(TAG, "Wake word detection model tensor input is not int8.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Verify output tensor matches expected values
 | 
			
		||||
  TfLiteTensor *output = this->streaming_interpreter_->output(0);
 | 
			
		||||
  if ((output->dims->size != 2) || (output->dims->data[0] != 1) || (output->dims->data[1] != 1)) {
 | 
			
		||||
    ESP_LOGE(TAG, "Wake word detection model tensor output dimensions is not 1x1.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (output->type != kTfLiteUInt8) {
 | 
			
		||||
    ESP_LOGE(TAG, "Wake word detection model tensor input is not uint8.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0);
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::update_features_() {
 | 
			
		||||
  // Retrieve strided audio samples
 | 
			
		||||
  int16_t *audio_samples = nullptr;
 | 
			
		||||
  if (!this->stride_audio_samples_(&audio_samples)) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Compute the features for the newest audio samples
 | 
			
		||||
  if (!this->generate_single_feature_(audio_samples, SAMPLE_DURATION_COUNT, this->new_features_data_)) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float MicroWakeWord::perform_streaming_inference_() {
 | 
			
		||||
  TfLiteTensor *input = this->streaming_interpreter_->input(0);
 | 
			
		||||
void MicroWakeWord::deallocate_buffers_() {
 | 
			
		||||
  ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
 | 
			
		||||
  audio_samples_allocator.deallocate(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
 | 
			
		||||
  this->input_buffer_ = nullptr;
 | 
			
		||||
  audio_samples_allocator.deallocate(this->preprocessor_audio_buffer_, this->new_samples_to_get_());
 | 
			
		||||
  this->preprocessor_audio_buffer_ = nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  size_t bytes_to_copy = input->bytes;
 | 
			
		||||
 | 
			
		||||
  memcpy((void *) (tflite::GetTensorData<int8_t>(input)), (const void *) (this->new_features_data_), bytes_to_copy);
 | 
			
		||||
 | 
			
		||||
  uint32_t prior_invoke = millis();
 | 
			
		||||
 | 
			
		||||
  TfLiteStatus invoke_status = this->streaming_interpreter_->Invoke();
 | 
			
		||||
  if (invoke_status != kTfLiteOk) {
 | 
			
		||||
    ESP_LOGW(TAG, "Streaming Interpreter Invoke failed");
 | 
			
		||||
bool MicroWakeWord::load_models_() {
 | 
			
		||||
  // Setup preprocesor feature generator
 | 
			
		||||
  if (!FrontendPopulateState(&this->frontend_config_, &this->frontend_state_, AUDIO_SAMPLE_FREQUENCY)) {
 | 
			
		||||
    ESP_LOGD(TAG, "Failed to populate frontend state");
 | 
			
		||||
    FrontendFreeStateContents(&this->frontend_state_);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Streaming Inference Latency=%" PRIu32 " ms", (millis() - prior_invoke));
 | 
			
		||||
 | 
			
		||||
  TfLiteTensor *output = this->streaming_interpreter_->output(0);
 | 
			
		||||
 | 
			
		||||
  return static_cast<float>(output->data.uint8[0]) / 255.0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::detect_wake_word_() {
 | 
			
		||||
  // Preprocess the newest audio samples into features
 | 
			
		||||
  if (!this->update_features_()) {
 | 
			
		||||
  // Setup streaming models
 | 
			
		||||
  for (auto &model : this->wake_word_models_) {
 | 
			
		||||
    if (!model.load_model(this->streaming_op_resolver_)) {
 | 
			
		||||
      ESP_LOGE(TAG, "Failed to initialize a wake word model.");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
  if (!this->vad_model_->load_model(this->streaming_op_resolver_)) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to initialize VAD model.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  // Perform inference
 | 
			
		||||
  float streaming_prob = this->perform_streaming_inference_();
 | 
			
		||||
 | 
			
		||||
  // Add the most recent probability to the sliding window
 | 
			
		||||
  this->recent_streaming_probabilities_[this->last_n_index_] = streaming_prob;
 | 
			
		||||
  ++this->last_n_index_;
 | 
			
		||||
  if (this->last_n_index_ == this->sliding_window_average_size_)
 | 
			
		||||
    this->last_n_index_ = 0;
 | 
			
		||||
 | 
			
		||||
  float sum = 0.0;
 | 
			
		||||
  for (auto &prob : this->recent_streaming_probabilities_) {
 | 
			
		||||
    sum += prob;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  float sliding_window_average = sum / static_cast<float>(this->sliding_window_average_size_);
 | 
			
		||||
void MicroWakeWord::unload_models_() {
 | 
			
		||||
  FrontendFreeStateContents(&this->frontend_state_);
 | 
			
		||||
 | 
			
		||||
  // Ensure we have enough samples since the last positive detection
 | 
			
		||||
  for (auto &model : this->wake_word_models_) {
 | 
			
		||||
    model.unload_model();
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
  this->vad_model_->unload_model();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicroWakeWord::update_model_probabilities_() {
 | 
			
		||||
  int8_t audio_features[PREPROCESSOR_FEATURE_SIZE];
 | 
			
		||||
 | 
			
		||||
  if (!this->generate_features_for_window_(audio_features)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Increase the counter since the last positive detection
 | 
			
		||||
  this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
 | 
			
		||||
 | 
			
		||||
  for (auto &model : this->wake_word_models_) {
 | 
			
		||||
    // Perform inference
 | 
			
		||||
    model.perform_streaming_inference(audio_features);
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
  this->vad_model_->perform_streaming_inference(audio_features);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::detect_wake_words_() {
 | 
			
		||||
  // Verify we have processed samples since the last positive detection
 | 
			
		||||
  if (this->ignore_windows_ < 0) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Detect the wake word if the sliding window average is above the cutoff
 | 
			
		||||
  if (sliding_window_average > this->probability_cutoff_) {
 | 
			
		||||
    this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION;
 | 
			
		||||
    for (auto &prob : this->recent_streaming_probabilities_) {
 | 
			
		||||
      prob = 0;
 | 
			
		||||
    }
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
  bool vad_state = this->vad_model_->determine_detected();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    ESP_LOGD(TAG, "Wake word sliding average probability is %.3f and most recent probability is %.3f",
 | 
			
		||||
             sliding_window_average, streaming_prob);
 | 
			
		||||
  for (auto &model : this->wake_word_models_) {
 | 
			
		||||
    if (model.determine_detected()) {
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
      if (vad_state) {
 | 
			
		||||
#endif
 | 
			
		||||
        this->detected_wake_word_ = model.get_wake_word();
 | 
			
		||||
        return true;
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGD(TAG, "Wake word model predicts %s, but VAD model doesn't.", model.get_wake_word().c_str());
 | 
			
		||||
      }
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicroWakeWord::set_sliding_window_average_size(size_t size) {
 | 
			
		||||
  this->sliding_window_average_size_ = size;
 | 
			
		||||
  this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0);
 | 
			
		||||
bool MicroWakeWord::has_enough_samples_() {
 | 
			
		||||
  return this->ring_buffer_->available() >=
 | 
			
		||||
         (this->features_step_size_ * (AUDIO_SAMPLE_FREQUENCY / 1000)) * sizeof(int16_t);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::slice_available_() {
 | 
			
		||||
  size_t available = this->ring_buffer_->available();
 | 
			
		||||
 | 
			
		||||
  return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) {
 | 
			
		||||
  if (!this->slice_available_()) {
 | 
			
		||||
bool MicroWakeWord::generate_features_for_window_(int8_t features[PREPROCESSOR_FEATURE_SIZE]) {
 | 
			
		||||
  // Ensure we have enough new audio samples in the ring buffer for a full window
 | 
			
		||||
  if (!this->has_enough_samples_()) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer to the start of the audio buffer
 | 
			
		||||
  memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET),
 | 
			
		||||
         HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t));
 | 
			
		||||
 | 
			
		||||
  // Copy 640 bytes (320 samples over 20 ms) from the ring buffer into the audio buffer offset 320 bytes (160 samples
 | 
			
		||||
  // over 10 ms)
 | 
			
		||||
  size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_ + HISTORY_SAMPLES_TO_KEEP),
 | 
			
		||||
                                               NEW_SAMPLES_TO_GET * sizeof(int16_t), pdMS_TO_TICKS(200));
 | 
			
		||||
  size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_),
 | 
			
		||||
                                               this->new_samples_to_get_() * sizeof(int16_t), pdMS_TO_TICKS(200));
 | 
			
		||||
 | 
			
		||||
  if (bytes_read == 0) {
 | 
			
		||||
    ESP_LOGE(TAG, "Could not read data from Ring Buffer");
 | 
			
		||||
  } else if (bytes_read < NEW_SAMPLES_TO_GET * sizeof(int16_t)) {
 | 
			
		||||
  } else if (bytes_read < this->new_samples_to_get_() * sizeof(int16_t)) {
 | 
			
		||||
    ESP_LOGD(TAG, "Partial Read of Data by Model");
 | 
			
		||||
    ESP_LOGD(TAG, "Could only read %d bytes when required %d bytes ", bytes_read,
 | 
			
		||||
             (int) (NEW_SAMPLES_TO_GET * sizeof(int16_t)));
 | 
			
		||||
             (int) (this->new_samples_to_get_() * sizeof(int16_t)));
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *audio_samples = this->preprocessor_audio_buffer_;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
  size_t num_samples_read;
 | 
			
		||||
  struct FrontendOutput frontend_output = FrontendProcessSamples(
 | 
			
		||||
      &this->frontend_state_, this->preprocessor_audio_buffer_, this->new_samples_to_get_(), &num_samples_read);
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::generate_single_feature_(const int16_t *audio_data, const int audio_data_size,
 | 
			
		||||
                                             int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]) {
 | 
			
		||||
  TfLiteTensor *input = this->preprocessor_interperter_->input(0);
 | 
			
		||||
  TfLiteTensor *output = this->preprocessor_interperter_->output(0);
 | 
			
		||||
  std::copy_n(audio_data, audio_data_size, tflite::GetTensorData<int16_t>(input));
 | 
			
		||||
 | 
			
		||||
  if (this->preprocessor_interperter_->Invoke() != kTfLiteOk) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to preprocess audio for local wake word.");
 | 
			
		||||
    return false;
 | 
			
		||||
  for (size_t i = 0; i < frontend_output.size; ++i) {
 | 
			
		||||
    // These scaling values are set to match the TFLite audio frontend int8 output.
 | 
			
		||||
    // The feature pipeline outputs 16-bit signed integers in roughly a 0 to 670
 | 
			
		||||
    // range. In training, these are then arbitrarily divided by 25.6 to get
 | 
			
		||||
    // float values in the rough range of 0.0 to 26.0. This scaling is performed
 | 
			
		||||
    // for historical reasons, to match up with the output of other feature
 | 
			
		||||
    // generators.
 | 
			
		||||
    // The process is then further complicated when we quantize the model. This
 | 
			
		||||
    // means we have to scale the 0.0 to 26.0 real values to the -128 to 127
 | 
			
		||||
    // signed integer numbers.
 | 
			
		||||
    // All this means that to get matching values from our integer feature
 | 
			
		||||
    // output into the tensor input, we have to perform:
 | 
			
		||||
    // input = (((feature / 25.6) / 26.0) * 256) - 128
 | 
			
		||||
    // To simplify this and perform it in 32-bit integer math, we rearrange to:
 | 
			
		||||
    // input = (feature * 256) / (25.6 * 26.0) - 128
 | 
			
		||||
    constexpr int32_t value_scale = 256;
 | 
			
		||||
    constexpr int32_t value_div = 666;  // 666 = 25.6 * 26.0 after rounding
 | 
			
		||||
    int32_t value = ((frontend_output.values[i] * value_scale) + (value_div / 2)) / value_div;
 | 
			
		||||
    value -= 128;
 | 
			
		||||
    if (value < -128) {
 | 
			
		||||
      value = -128;
 | 
			
		||||
    }
 | 
			
		||||
    if (value > 127) {
 | 
			
		||||
      value = 127;
 | 
			
		||||
    }
 | 
			
		||||
    features[i] = value;
 | 
			
		||||
  }
 | 
			
		||||
  std::memcpy(feature_output, tflite::GetTensorData<int8_t>(output), PREPROCESSOR_FEATURE_SIZE * sizeof(int8_t));
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver) {
 | 
			
		||||
  if (op_resolver.AddReshape() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddCast() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddStridedSlice() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddConcatenation() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddMul() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddAdd() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddDiv() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddMinimum() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddMaximum() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddWindow() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddFftAutoScale() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddRfft() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddEnergy() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddFilterBank() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddFilterBankSquareRoot() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddFilterBankSpectralSubtraction() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddPCAN() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddFilterBankLog() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
void MicroWakeWord::reset_states_() {
 | 
			
		||||
  ESP_LOGD(TAG, "Resetting buffers and probabilities");
 | 
			
		||||
  this->ring_buffer_->reset();
 | 
			
		||||
  this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION;
 | 
			
		||||
  for (auto &model : this->wake_word_models_) {
 | 
			
		||||
    model.reset_probabilities();
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
  this->vad_model_->reset_probabilities();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver) {
 | 
			
		||||
bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<20> &op_resolver) {
 | 
			
		||||
  if (op_resolver.AddCallOnce() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddVarHandle() != kTfLiteOk)
 | 
			
		||||
@@ -496,6 +442,12 @@ bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddMaxPool2D() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddPad() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddPack() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (op_resolver.AddSplitV() != kTfLiteOk)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
@@ -504,5 +456,3 @@ bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#endif  // CLANG_TIDY
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,18 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This is a workaround until we can figure out a way to get
 | 
			
		||||
 * the tflite-micro idf component code available in CI
 | 
			
		||||
 *
 | 
			
		||||
 * */
 | 
			
		||||
//
 | 
			
		||||
#ifndef CLANG_TIDY
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include "preprocessor_settings.h"
 | 
			
		||||
#include "streaming_model.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/ring_buffer.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/microphone/microphone.h"
 | 
			
		||||
 | 
			
		||||
#include <frontend_util.h>
 | 
			
		||||
 | 
			
		||||
#include <tensorflow/lite/core/c/common.h>
 | 
			
		||||
#include <tensorflow/lite/micro/micro_interpreter.h>
 | 
			
		||||
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
 | 
			
		||||
@@ -23,35 +20,6 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace micro_wake_word {
 | 
			
		||||
 | 
			
		||||
// The following are dictated by the preprocessor model
 | 
			
		||||
//
 | 
			
		||||
// The number of features the audio preprocessor generates per slice
 | 
			
		||||
static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40;
 | 
			
		||||
// How frequently the preprocessor generates a new set of features
 | 
			
		||||
static const uint8_t FEATURE_STRIDE_MS = 20;
 | 
			
		||||
// Duration of each slice used as input into the preprocessor
 | 
			
		||||
static const uint8_t FEATURE_DURATION_MS = 30;
 | 
			
		||||
// Audio sample frequency in hertz
 | 
			
		||||
static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000;
 | 
			
		||||
// The number of old audio samples that are saved to be part of the next feature window
 | 
			
		||||
static const uint16_t HISTORY_SAMPLES_TO_KEEP =
 | 
			
		||||
    ((FEATURE_DURATION_MS - FEATURE_STRIDE_MS) * (AUDIO_SAMPLE_FREQUENCY / 1000));
 | 
			
		||||
// The number of new audio samples to receive to be included with the next feature window
 | 
			
		||||
static const uint16_t NEW_SAMPLES_TO_GET = (FEATURE_STRIDE_MS * (AUDIO_SAMPLE_FREQUENCY / 1000));
 | 
			
		||||
// The total number of audio samples included in the feature window
 | 
			
		||||
static const uint16_t SAMPLE_DURATION_COUNT = FEATURE_DURATION_MS * AUDIO_SAMPLE_FREQUENCY / 1000;
 | 
			
		||||
// Number of bytes in memory needed for the preprocessor arena
 | 
			
		||||
static const uint32_t PREPROCESSOR_ARENA_SIZE = 9528;
 | 
			
		||||
 | 
			
		||||
// The following configure the streaming wake word model
 | 
			
		||||
//
 | 
			
		||||
// The number of audio slices to process before accepting a positive detection
 | 
			
		||||
static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74;
 | 
			
		||||
 | 
			
		||||
// Number of bytes in memory needed for the streaming wake word model
 | 
			
		||||
static const uint32_t STREAMING_MODEL_ARENA_SIZE = 64000;
 | 
			
		||||
static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024;
 | 
			
		||||
 | 
			
		||||
enum State {
 | 
			
		||||
  IDLE,
 | 
			
		||||
  START_MICROPHONE,
 | 
			
		||||
@@ -61,6 +29,9 @@ enum State {
 | 
			
		||||
  STOPPING_MICROPHONE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// The number of audio slices to process before accepting a positive detection
 | 
			
		||||
static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74;
 | 
			
		||||
 | 
			
		||||
class MicroWakeWord : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
@@ -73,28 +44,21 @@ class MicroWakeWord : public Component {
 | 
			
		||||
 | 
			
		||||
  bool is_running() const { return this->state_ != State::IDLE; }
 | 
			
		||||
 | 
			
		||||
  bool initialize_models();
 | 
			
		||||
 | 
			
		||||
  std::string get_wake_word() { return this->wake_word_; }
 | 
			
		||||
 | 
			
		||||
  // Increasing either of these will reduce the rate of false acceptances while increasing the false rejection rate
 | 
			
		||||
  void set_probability_cutoff(float probability_cutoff) { this->probability_cutoff_ = probability_cutoff; }
 | 
			
		||||
  void set_sliding_window_average_size(size_t size);
 | 
			
		||||
  void set_features_step_size(uint8_t step_size) { this->features_step_size_ = step_size; }
 | 
			
		||||
 | 
			
		||||
  void set_microphone(microphone::Microphone *microphone) { this->microphone_ = microphone; }
 | 
			
		||||
 | 
			
		||||
  Trigger<std::string> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; }
 | 
			
		||||
 | 
			
		||||
  void set_model_start(const uint8_t *model_start) { this->model_start_ = model_start; }
 | 
			
		||||
  void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
 | 
			
		||||
  void add_wake_word_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size,
 | 
			
		||||
                           const std::string &wake_word, size_t tensor_arena_size);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
  void add_vad_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size,
 | 
			
		||||
                     size_t tensor_arena_size);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void set_state_(State state);
 | 
			
		||||
  int read_microphone_();
 | 
			
		||||
 | 
			
		||||
  const uint8_t *model_start_;
 | 
			
		||||
  std::string wake_word_;
 | 
			
		||||
 | 
			
		||||
  microphone::Microphone *microphone_{nullptr};
 | 
			
		||||
  Trigger<std::string> *wake_word_detected_trigger_ = new Trigger<std::string>();
 | 
			
		||||
  State state_{State::IDLE};
 | 
			
		||||
@@ -102,85 +66,93 @@ class MicroWakeWord : public Component {
 | 
			
		||||
 | 
			
		||||
  std::unique_ptr<RingBuffer> ring_buffer_;
 | 
			
		||||
 | 
			
		||||
  int16_t *input_buffer_;
 | 
			
		||||
  std::vector<WakeWordModel> wake_word_models_;
 | 
			
		||||
 | 
			
		||||
  const tflite::Model *preprocessor_model_{nullptr};
 | 
			
		||||
  const tflite::Model *streaming_model_{nullptr};
 | 
			
		||||
  tflite::MicroInterpreter *streaming_interpreter_{nullptr};
 | 
			
		||||
  tflite::MicroInterpreter *preprocessor_interperter_{nullptr};
 | 
			
		||||
#ifdef USE_MICRO_WAKE_WORD_VAD
 | 
			
		||||
  std::unique_ptr<VADModel> vad_model_;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  std::vector<float> recent_streaming_probabilities_;
 | 
			
		||||
  size_t last_n_index_{0};
 | 
			
		||||
  tflite::MicroMutableOpResolver<20> streaming_op_resolver_;
 | 
			
		||||
 | 
			
		||||
  float probability_cutoff_{0.5};
 | 
			
		||||
  size_t sliding_window_average_size_{10};
 | 
			
		||||
  // Audio frontend handles generating spectrogram features
 | 
			
		||||
  struct FrontendConfig frontend_config_;
 | 
			
		||||
  struct FrontendState frontend_state_;
 | 
			
		||||
 | 
			
		||||
  // When the wake word detection first starts or after the word has been detected once, we ignore this many audio
 | 
			
		||||
  // feature slices before accepting a positive detection again
 | 
			
		||||
  // When the wake word detection first starts, we ignore this many audio
 | 
			
		||||
  // feature slices before accepting a positive detection
 | 
			
		||||
  int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION};
 | 
			
		||||
 | 
			
		||||
  uint8_t *streaming_var_arena_{nullptr};
 | 
			
		||||
  uint8_t *streaming_tensor_arena_{nullptr};
 | 
			
		||||
  uint8_t *preprocessor_tensor_arena_{nullptr};
 | 
			
		||||
  int8_t *new_features_data_{nullptr};
 | 
			
		||||
  uint8_t features_step_size_;
 | 
			
		||||
 | 
			
		||||
  tflite::MicroResourceVariables *mrv_{nullptr};
 | 
			
		||||
 | 
			
		||||
  // Stores audio fed into feature generator preprocessor
 | 
			
		||||
  int16_t *preprocessor_audio_buffer_;
 | 
			
		||||
  // Stores audio read from the microphone before being added to the ring buffer.
 | 
			
		||||
  int16_t *input_buffer_{nullptr};
 | 
			
		||||
  // Stores audio to be fed into the audio frontend for generating features.
 | 
			
		||||
  int16_t *preprocessor_audio_buffer_{nullptr};
 | 
			
		||||
 | 
			
		||||
  bool detected_{false};
 | 
			
		||||
  std::string detected_wake_word_{""};
 | 
			
		||||
 | 
			
		||||
  /** Detects if wake word has been said
 | 
			
		||||
  void set_state_(State state);
 | 
			
		||||
 | 
			
		||||
  /// @brief Tests if there are enough samples in the ring buffer to generate new features.
 | 
			
		||||
  /// @return True if enough samples, false otherwise.
 | 
			
		||||
  bool has_enough_samples_();
 | 
			
		||||
 | 
			
		||||
  /** Reads audio from microphone into the ring buffer
 | 
			
		||||
   *
 | 
			
		||||
   * Audio data (16000 kHz with int16 samples) is read into the input_buffer_.
 | 
			
		||||
   * Verifies the ring buffer has enough space for all audio data. If not, it logs
 | 
			
		||||
   * a warning and resets the ring buffer entirely.
 | 
			
		||||
   * @return Number of bytes written to the ring buffer
 | 
			
		||||
   */
 | 
			
		||||
  size_t read_microphone_();
 | 
			
		||||
 | 
			
		||||
  /// @brief Allocates memory for input_buffer_, preprocessor_audio_buffer_, and ring_buffer_
 | 
			
		||||
  /// @return True if successful, false otherwise
 | 
			
		||||
  bool allocate_buffers_();
 | 
			
		||||
 | 
			
		||||
  /// @brief Frees memory allocated for input_buffer_ and preprocessor_audio_buffer_
 | 
			
		||||
  void deallocate_buffers_();
 | 
			
		||||
 | 
			
		||||
  /// @brief Loads streaming models and prepares the feature generation frontend
 | 
			
		||||
  /// @return True if successful, false otherwise
 | 
			
		||||
  bool load_models_();
 | 
			
		||||
 | 
			
		||||
  /// @brief Deletes each model's TFLite interpreters and frees tensor arena memory. Frees memory used by the feature
 | 
			
		||||
  /// generation frontend.
 | 
			
		||||
  void unload_models_();
 | 
			
		||||
 | 
			
		||||
  /** Performs inference with each configured model
 | 
			
		||||
   *
 | 
			
		||||
   * If enough audio samples are available, it will generate one slice of new features.
 | 
			
		||||
   * If the streaming model predicts the wake word, then the nonstreaming model confirms it.
 | 
			
		||||
   * @param ring_Buffer Ring buffer containing raw audio samples
 | 
			
		||||
   * @return True if the wake word is detected, false otherwise
 | 
			
		||||
   * It then loops through and performs inference with each of the loaded models.
 | 
			
		||||
   */
 | 
			
		||||
  bool detect_wake_word_();
 | 
			
		||||
  void update_model_probabilities_();
 | 
			
		||||
 | 
			
		||||
  /// @brief Returns true if there are enough audio samples in the buffer to generate another slice of features
 | 
			
		||||
  bool slice_available_();
 | 
			
		||||
 | 
			
		||||
  /** Shifts previous feature slices over by one and generates a new slice of features
 | 
			
		||||
  /** Checks every model's recent probabilities to determine if the wake word has been predicted
 | 
			
		||||
   *
 | 
			
		||||
   * @param ring_buffer ring buffer containing raw audio samples
 | 
			
		||||
   * @return True if a new slice of features was generated, false otherwise
 | 
			
		||||
   * Verifies the models have processed enough new samples for accurate predictions.
 | 
			
		||||
   * Sets detected_wake_word_ to the wake word, if one is detected.
 | 
			
		||||
   * @return True if a wake word is predicted, false otherwise
 | 
			
		||||
   */
 | 
			
		||||
  bool update_features_();
 | 
			
		||||
  bool detect_wake_words_();
 | 
			
		||||
 | 
			
		||||
  /** Generates features from audio samples
 | 
			
		||||
  /** Generates features for a window of audio samples
 | 
			
		||||
   *
 | 
			
		||||
   * Adapted from TFLite micro speech example
 | 
			
		||||
   * @param audio_data Pointer to array with the audio samples
 | 
			
		||||
   * @param audio_data_size The number of samples to use as input to the preprocessor model
 | 
			
		||||
   * @param feature_output Array that will store the features
 | 
			
		||||
   * Reads samples from the ring buffer and feeds them into the preprocessor frontend.
 | 
			
		||||
   * Adapted from TFLite microspeech frontend.
 | 
			
		||||
   * @param features int8_t array to store the audio features
 | 
			
		||||
   * @return True if successful, false otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  bool generate_single_feature_(const int16_t *audio_data, int audio_data_size,
 | 
			
		||||
                                int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]);
 | 
			
		||||
  bool generate_features_for_window_(int8_t features[PREPROCESSOR_FEATURE_SIZE]);
 | 
			
		||||
 | 
			
		||||
  /** Performs inference over the most recent feature slice with the streaming model
 | 
			
		||||
   *
 | 
			
		||||
   * @return Probability of the wake word between 0.0 and 1.0
 | 
			
		||||
   */
 | 
			
		||||
  float perform_streaming_inference_();
 | 
			
		||||
 | 
			
		||||
  /** Strides the audio samples by keeping the last 10 ms of the previous slice
 | 
			
		||||
   *
 | 
			
		||||
   * Adapted from the TFLite micro speech example
 | 
			
		||||
   * @param ring_buffer Ring buffer containing raw audio samples
 | 
			
		||||
   * @param audio_samples Pointer to an array that will store the strided audio samples
 | 
			
		||||
   * @return True if successful, false otherwise
 | 
			
		||||
   */
 | 
			
		||||
  bool stride_audio_samples_(int16_t **audio_samples);
 | 
			
		||||
 | 
			
		||||
  /// @brief Returns true if successfully registered the preprocessor's TensorFlow operations
 | 
			
		||||
  bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver);
 | 
			
		||||
  /// @brief Resets the ring buffer, ignore_windows_, and sliding window probabilities
 | 
			
		||||
  void reset_states_();
 | 
			
		||||
 | 
			
		||||
  /// @brief Returns true if successfully registered the streaming model's TensorFlow operations
 | 
			
		||||
  bool register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver);
 | 
			
		||||
  bool register_streaming_ops_(tflite::MicroMutableOpResolver<20> &op_resolver);
 | 
			
		||||
 | 
			
		||||
  inline uint16_t new_samples_to_get_() { return (this->features_step_size_ * (AUDIO_SAMPLE_FREQUENCY / 1000)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> {
 | 
			
		||||
@@ -202,5 +174,3 @@ template<typename... Ts> class IsRunningCondition : public Condition<Ts...>, pub
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#endif  // CLANG_TIDY
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								esphome/components/micro_wake_word/preprocessor_settings.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/micro_wake_word/preprocessor_settings.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace micro_wake_word {
 | 
			
		||||
 | 
			
		||||
// The number of features the audio preprocessor generates per slice
 | 
			
		||||
static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40;
 | 
			
		||||
// Duration of each slice used as input into the preprocessor
 | 
			
		||||
static const uint8_t FEATURE_DURATION_MS = 30;
 | 
			
		||||
// Audio sample frequency in hertz
 | 
			
		||||
static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000;
 | 
			
		||||
 | 
			
		||||
}  // namespace micro_wake_word
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										189
									
								
								esphome/components/micro_wake_word/streaming_model.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								esphome/components/micro_wake_word/streaming_model.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include "streaming_model.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "micro_wake_word";
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace micro_wake_word {
 | 
			
		||||
 | 
			
		||||
void WakeWordModel::log_model_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "    - Wake Word: %s", this->wake_word_.c_str());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "      Probability cutoff: %.3f", this->probability_cutoff_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "      Sliding window size: %d", this->sliding_window_size_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void VADModel::log_model_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "    - VAD Model");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "      Probability cutoff: %.3f", this->probability_cutoff_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "      Sliding window size: %d", this->sliding_window_size_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool StreamingModel::load_model(tflite::MicroMutableOpResolver<20> &op_resolver) {
 | 
			
		||||
  ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
 | 
			
		||||
  if (this->tensor_arena_ == nullptr) {
 | 
			
		||||
    this->tensor_arena_ = arena_allocator.allocate(this->tensor_arena_size_);
 | 
			
		||||
    if (this->tensor_arena_ == nullptr) {
 | 
			
		||||
      ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena.");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->var_arena_ == nullptr) {
 | 
			
		||||
    this->var_arena_ = arena_allocator.allocate(STREAMING_MODEL_VARIABLE_ARENA_SIZE);
 | 
			
		||||
    if (this->var_arena_ == nullptr) {
 | 
			
		||||
      ESP_LOGE(TAG, "Could not allocate the streaming model's variable tensor arena.");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    this->ma_ = tflite::MicroAllocator::Create(this->var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE);
 | 
			
		||||
    this->mrv_ = tflite::MicroResourceVariables::Create(this->ma_, 20);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const tflite::Model *model = tflite::GetModel(this->model_start_);
 | 
			
		||||
  if (model->version() != TFLITE_SCHEMA_VERSION) {
 | 
			
		||||
    ESP_LOGE(TAG, "Streaming model's schema is not supported");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->interpreter_ == nullptr) {
 | 
			
		||||
    this->interpreter_ = make_unique<tflite::MicroInterpreter>(
 | 
			
		||||
        tflite::GetModel(this->model_start_), op_resolver, this->tensor_arena_, this->tensor_arena_size_, this->mrv_);
 | 
			
		||||
    if (this->interpreter_->AllocateTensors() != kTfLiteOk) {
 | 
			
		||||
      ESP_LOGE(TAG, "Failed to allocate tensors for the streaming model");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Verify input tensor matches expected values
 | 
			
		||||
    // Dimension 3 will represent the first layer stride, so skip it may vary
 | 
			
		||||
    TfLiteTensor *input = this->interpreter_->input(0);
 | 
			
		||||
    if ((input->dims->size != 3) || (input->dims->data[0] != 1) ||
 | 
			
		||||
        (input->dims->data[2] != PREPROCESSOR_FEATURE_SIZE)) {
 | 
			
		||||
      ESP_LOGE(TAG, "Streaming model tensor input dimensions has improper dimensions.");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (input->type != kTfLiteInt8) {
 | 
			
		||||
      ESP_LOGE(TAG, "Streaming model tensor input is not int8.");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Verify output tensor matches expected values
 | 
			
		||||
    TfLiteTensor *output = this->interpreter_->output(0);
 | 
			
		||||
    if ((output->dims->size != 2) || (output->dims->data[0] != 1) || (output->dims->data[1] != 1)) {
 | 
			
		||||
      ESP_LOGE(TAG, "Streaming model tensor output dimension is not 1x1.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (output->type != kTfLiteUInt8) {
 | 
			
		||||
      ESP_LOGE(TAG, "Streaming model tensor output is not uint8.");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StreamingModel::unload_model() {
 | 
			
		||||
  this->interpreter_.reset();
 | 
			
		||||
 | 
			
		||||
  ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
 | 
			
		||||
  arena_allocator.deallocate(this->tensor_arena_, this->tensor_arena_size_);
 | 
			
		||||
  this->tensor_arena_ = nullptr;
 | 
			
		||||
  arena_allocator.deallocate(this->var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE);
 | 
			
		||||
  this->var_arena_ = nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool StreamingModel::perform_streaming_inference(const int8_t features[PREPROCESSOR_FEATURE_SIZE]) {
 | 
			
		||||
  if (this->interpreter_ != nullptr) {
 | 
			
		||||
    TfLiteTensor *input = this->interpreter_->input(0);
 | 
			
		||||
 | 
			
		||||
    std::memmove(
 | 
			
		||||
        (int8_t *) (tflite::GetTensorData<int8_t>(input)) + PREPROCESSOR_FEATURE_SIZE * this->current_stride_step_,
 | 
			
		||||
        features, PREPROCESSOR_FEATURE_SIZE);
 | 
			
		||||
    ++this->current_stride_step_;
 | 
			
		||||
 | 
			
		||||
    uint8_t stride = this->interpreter_->input(0)->dims->data[1];
 | 
			
		||||
 | 
			
		||||
    if (this->current_stride_step_ >= stride) {
 | 
			
		||||
      this->current_stride_step_ = 0;
 | 
			
		||||
 | 
			
		||||
      TfLiteStatus invoke_status = this->interpreter_->Invoke();
 | 
			
		||||
      if (invoke_status != kTfLiteOk) {
 | 
			
		||||
        ESP_LOGW(TAG, "Streaming interpreter invoke failed");
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      TfLiteTensor *output = this->interpreter_->output(0);
 | 
			
		||||
 | 
			
		||||
      ++this->last_n_index_;
 | 
			
		||||
      if (this->last_n_index_ == this->sliding_window_size_)
 | 
			
		||||
        this->last_n_index_ = 0;
 | 
			
		||||
      this->recent_streaming_probabilities_[this->last_n_index_] = output->data.uint8[0];  // probability;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGE(TAG, "Streaming interpreter is not initialized.");
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StreamingModel::reset_probabilities() {
 | 
			
		||||
  for (auto &prob : this->recent_streaming_probabilities_) {
 | 
			
		||||
    prob = 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WakeWordModel::WakeWordModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size,
 | 
			
		||||
                             const std::string &wake_word, size_t tensor_arena_size) {
 | 
			
		||||
  this->model_start_ = model_start;
 | 
			
		||||
  this->probability_cutoff_ = probability_cutoff;
 | 
			
		||||
  this->sliding_window_size_ = sliding_window_average_size;
 | 
			
		||||
  this->recent_streaming_probabilities_.resize(sliding_window_average_size, 0);
 | 
			
		||||
  this->wake_word_ = wake_word;
 | 
			
		||||
  this->tensor_arena_size_ = tensor_arena_size;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool WakeWordModel::determine_detected() {
 | 
			
		||||
  int32_t sum = 0;
 | 
			
		||||
  for (auto &prob : this->recent_streaming_probabilities_) {
 | 
			
		||||
    sum += prob;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float sliding_window_average = static_cast<float>(sum) / static_cast<float>(255 * this->sliding_window_size_);
 | 
			
		||||
 | 
			
		||||
  // Detect the wake word if the sliding window average is above the cutoff
 | 
			
		||||
  if (sliding_window_average > this->probability_cutoff_) {
 | 
			
		||||
    ESP_LOGD(TAG, "The '%s' model sliding average probability is %.3f and most recent probability is %.3f",
 | 
			
		||||
             this->wake_word_.c_str(), sliding_window_average,
 | 
			
		||||
             this->recent_streaming_probabilities_[this->last_n_index_] / (255.0));
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VADModel::VADModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size,
 | 
			
		||||
                   size_t tensor_arena_size) {
 | 
			
		||||
  this->model_start_ = model_start;
 | 
			
		||||
  this->probability_cutoff_ = probability_cutoff;
 | 
			
		||||
  this->sliding_window_size_ = sliding_window_size;
 | 
			
		||||
  this->recent_streaming_probabilities_.resize(sliding_window_size, 0);
 | 
			
		||||
  this->tensor_arena_size_ = tensor_arena_size;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool VADModel::determine_detected() {
 | 
			
		||||
  uint8_t max = 0;
 | 
			
		||||
  for (auto &prob : this->recent_streaming_probabilities_) {
 | 
			
		||||
    max = std::max(prob, max);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return max > this->probability_cutoff_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace micro_wake_word
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										84
									
								
								esphome/components/micro_wake_word/streaming_model.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								esphome/components/micro_wake_word/streaming_model.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include "preprocessor_settings.h"
 | 
			
		||||
 | 
			
		||||
#include <tensorflow/lite/core/c/common.h>
 | 
			
		||||
#include <tensorflow/lite/micro/micro_interpreter.h>
 | 
			
		||||
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace micro_wake_word {
 | 
			
		||||
 | 
			
		||||
static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024;
 | 
			
		||||
 | 
			
		||||
class StreamingModel {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual void log_model_config() = 0;
 | 
			
		||||
  virtual bool determine_detected() = 0;
 | 
			
		||||
 | 
			
		||||
  bool perform_streaming_inference(const int8_t features[PREPROCESSOR_FEATURE_SIZE]);
 | 
			
		||||
 | 
			
		||||
  /// @brief Sets all recent_streaming_probabilities to 0
 | 
			
		||||
  void reset_probabilities();
 | 
			
		||||
 | 
			
		||||
  /// @brief Allocates tensor and variable arenas and sets up the model interpreter
 | 
			
		||||
  /// @param op_resolver MicroMutableOpResolver object that must exist until the model is unloaded
 | 
			
		||||
  /// @return True if successful, false otherwise
 | 
			
		||||
  bool load_model(tflite::MicroMutableOpResolver<20> &op_resolver);
 | 
			
		||||
 | 
			
		||||
  /// @brief Destroys the TFLite interpreter and frees the tensor and variable arenas' memory
 | 
			
		||||
  void unload_model();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t current_stride_step_{0};
 | 
			
		||||
 | 
			
		||||
  float probability_cutoff_;
 | 
			
		||||
  size_t sliding_window_size_;
 | 
			
		||||
  size_t last_n_index_{0};
 | 
			
		||||
  size_t tensor_arena_size_;
 | 
			
		||||
  std::vector<uint8_t> recent_streaming_probabilities_;
 | 
			
		||||
 | 
			
		||||
  const uint8_t *model_start_;
 | 
			
		||||
  uint8_t *tensor_arena_{nullptr};
 | 
			
		||||
  uint8_t *var_arena_{nullptr};
 | 
			
		||||
  std::unique_ptr<tflite::MicroInterpreter> interpreter_;
 | 
			
		||||
  tflite::MicroResourceVariables *mrv_{nullptr};
 | 
			
		||||
  tflite::MicroAllocator *ma_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class WakeWordModel final : public StreamingModel {
 | 
			
		||||
 public:
 | 
			
		||||
  WakeWordModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size,
 | 
			
		||||
                const std::string &wake_word, size_t tensor_arena_size);
 | 
			
		||||
 | 
			
		||||
  void log_model_config() override;
 | 
			
		||||
 | 
			
		||||
  /// @brief Checks for the wake word by comparing the mean probability in the sliding window with the probability
 | 
			
		||||
  /// cutoff
 | 
			
		||||
  /// @return True if wake word is detected, false otherwise
 | 
			
		||||
  bool determine_detected() override;
 | 
			
		||||
 | 
			
		||||
  const std::string &get_wake_word() const { return this->wake_word_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::string wake_word_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class VADModel final : public StreamingModel {
 | 
			
		||||
 public:
 | 
			
		||||
  VADModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size, size_t tensor_arena_size);
 | 
			
		||||
 | 
			
		||||
  void log_model_config() override;
 | 
			
		||||
 | 
			
		||||
  /// @brief Checks for voice activity by comparing the max probability in the sliding window with the probability
 | 
			
		||||
  /// cutoff
 | 
			
		||||
  /// @return True if voice activity is detected, false otherwise
 | 
			
		||||
  bool determine_detected() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace micro_wake_word
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -293,4 +293,4 @@ async def to_code(config):
 | 
			
		||||
    if CONF_HUMIDITY_SETPOINT in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT])
 | 
			
		||||
        cg.add(var.set_humidity_setpoint_sensor(sens))
 | 
			
		||||
    cg.add_library("dudanov/MideaUART", "1.1.8")
 | 
			
		||||
    cg.add_library("dudanov/MideaUART", "1.1.9")
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,7 @@ const uint8_t MITSUBISHI_BYTE16 = 0X00;
 | 
			
		||||
 | 
			
		||||
climate::ClimateTraits MitsubishiClimate::traits() {
 | 
			
		||||
  auto traits = climate::ClimateTraits();
 | 
			
		||||
  traits.set_supports_current_temperature(this->sensor_ != nullptr);
 | 
			
		||||
  traits.set_supports_action(false);
 | 
			
		||||
  traits.set_visual_min_temperature(MITSUBISHI_TEMP_MIN);
 | 
			
		||||
  traits.set_visual_max_temperature(MITSUBISHI_TEMP_MAX);
 | 
			
		||||
 
 | 
			
		||||
@@ -116,7 +116,8 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
 | 
			
		||||
        ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.",
 | 
			
		||||
                 server_register->address, static_cast<uint8_t>(server_register->value_type),
 | 
			
		||||
                 server_register->register_count, value);
 | 
			
		||||
        number_to_payload(sixteen_bit_response, value, server_register->value_type);
 | 
			
		||||
        std::vector<uint16_t> payload = float_to_payload(value, server_register->value_type);
 | 
			
		||||
        sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
 | 
			
		||||
        current_address += server_register->register_count;
 | 
			
		||||
        found = true;
 | 
			
		||||
        break;
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@ RAW_ENCODING = {
 | 
			
		||||
    "NONE": RawEncoding.NONE,
 | 
			
		||||
    "HEXBYTES": RawEncoding.HEXBYTES,
 | 
			
		||||
    "COMMA": RawEncoding.COMMA,
 | 
			
		||||
    "ANSI": RawEncoding.ANSI,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
@@ -49,7 +50,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
 | 
			
		||||
            cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
 | 
			
		||||
            cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int,
 | 
			
		||||
            cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING),
 | 
			
		||||
            cv.Optional(CONF_RAW_ENCODE, default="ANSI"): cv.enum(RAW_ENCODING),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    validate_modbus_register,
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
 | 
			
		||||
  std::ostringstream output;
 | 
			
		||||
  uint8_t items_left = this->response_bytes;
 | 
			
		||||
  uint8_t index = this->offset;
 | 
			
		||||
  char buffer[4];
 | 
			
		||||
  char buffer[5];
 | 
			
		||||
  while ((items_left > 0) && index < data.size()) {
 | 
			
		||||
    uint8_t b = data[index];
 | 
			
		||||
    switch (this->encode_) {
 | 
			
		||||
@@ -27,8 +27,11 @@ void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
 | 
			
		||||
        sprintf(buffer, index != this->offset ? ",%d" : "%d", b);
 | 
			
		||||
        output << buffer;
 | 
			
		||||
        break;
 | 
			
		||||
      case RawEncoding::ANSI:
 | 
			
		||||
        if (b < 0x20)
 | 
			
		||||
          break;
 | 
			
		||||
      // FALLTHROUGH
 | 
			
		||||
      // Anything else no encoding
 | 
			
		||||
      case RawEncoding::NONE:
 | 
			
		||||
      default:
 | 
			
		||||
        output << (char) b;
 | 
			
		||||
        break;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace modbus_controller {
 | 
			
		||||
 | 
			
		||||
enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2 };
 | 
			
		||||
enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2, ANSI = 3 };
 | 
			
		||||
 | 
			
		||||
class ModbusTextSensor : public Component, public text_sensor::TextSensor, public SensorItem {
 | 
			
		||||
 public:
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ async def to_code(config):
 | 
			
		||||
    var = await binary_sensor.new_binary_sensor(config)
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_MPR121_ID])
 | 
			
		||||
    cg.add(var.set_channel(config[CONF_CHANNEL]))
 | 
			
		||||
    cg.register_parented(var, hub)
 | 
			
		||||
    await cg.register_parented(var, hub)
 | 
			
		||||
 | 
			
		||||
    if CONF_TOUCH_THRESHOLD in config:
 | 
			
		||||
        cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,12 @@ IPAddress = network_ns.class_("IPAddress")
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.SplitDefault(CONF_ENABLE_IPV6): cv.All(
 | 
			
		||||
        cv.SplitDefault(
 | 
			
		||||
            CONF_ENABLE_IPV6,
 | 
			
		||||
            esp8266=False,
 | 
			
		||||
            esp32=False,
 | 
			
		||||
            rp2040=False,
 | 
			
		||||
        ): cv.All(
 | 
			
		||||
            cv.boolean, cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040])
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
 | 
			
		||||
@@ -28,18 +33,17 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    if CONF_ENABLE_IPV6 in config:
 | 
			
		||||
        cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6])
 | 
			
		||||
    if (enable_ipv6 := config.get(CONF_ENABLE_IPV6, None)) is not None:
 | 
			
		||||
        cg.add_define("USE_NETWORK_IPV6", enable_ipv6)
 | 
			
		||||
        if enable_ipv6:
 | 
			
		||||
            cg.add_define(
 | 
			
		||||
                "USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]
 | 
			
		||||
            )
 | 
			
		||||
        if CORE.using_esp_idf:
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6])
 | 
			
		||||
            add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6]
 | 
			
		||||
            )
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6)
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6)
 | 
			
		||||
        else:
 | 
			
		||||
            if config[CONF_ENABLE_IPV6]:
 | 
			
		||||
            if enable_ipv6:
 | 
			
		||||
                cg.add_build_flag("-DCONFIG_LWIP_IPV6")
 | 
			
		||||
                cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")
 | 
			
		||||
                if CORE.is_rp2040:
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CONDUCTIVITY,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_DATA_RATE,
 | 
			
		||||
    DEVICE_CLASS_DATA_SIZE,
 | 
			
		||||
@@ -82,6 +83,7 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CONDUCTIVITY,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_DATA_RATE,
 | 
			
		||||
    DEVICE_CLASS_DATA_SIZE,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,17 @@
 | 
			
		||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include "ota_backend_arduino_esp32.h"
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
#include "ota_backend_arduino_esp32.h"
 | 
			
		||||
 | 
			
		||||
#include <Update.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ota {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ota.arduino_esp32";
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP32OTABackend>(); }
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) {
 | 
			
		||||
@@ -20,6 +23,9 @@ OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) {
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  if (error == UPDATE_ERROR_SIZE)
 | 
			
		||||
    return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGE(TAG, "Begin error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_UNKNOWN;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -27,18 +33,27 @@ void ArduinoESP32OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) {
 | 
			
		||||
  size_t written = Update.write(data, len);
 | 
			
		||||
  if (written != len) {
 | 
			
		||||
    return OTA_RESPONSE_ERROR_WRITING_FLASH;
 | 
			
		||||
  }
 | 
			
		||||
  if (written == len) {
 | 
			
		||||
    return OTA_RESPONSE_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  ESP_LOGE(TAG, "Write error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_WRITING_FLASH;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoESP32OTABackend::end() {
 | 
			
		||||
  if (!Update.end())
 | 
			
		||||
    return OTA_RESPONSE_ERROR_UPDATE_END;
 | 
			
		||||
  if (Update.end()) {
 | 
			
		||||
    return OTA_RESPONSE_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  ESP_LOGE(TAG, "End error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_UPDATE_END;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoESP32OTABackend::abort() { Update.abort(); }
 | 
			
		||||
 | 
			
		||||
}  // namespace ota
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,19 @@
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
#include "ota_backend_arduino_esp8266.h"
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/components/esp8266/preferences.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <Updater.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ota {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ota.arduino_esp8266";
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP8266OTABackend>(); }
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
 | 
			
		||||
@@ -29,6 +32,9 @@ OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
 | 
			
		||||
    return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG;
 | 
			
		||||
  if (error == UPDATE_ERROR_SPACE)
 | 
			
		||||
    return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGE(TAG, "Begin error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_UNKNOWN;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -36,18 +42,27 @@ void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { Update.setMD5(m
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) {
 | 
			
		||||
  size_t written = Update.write(data, len);
 | 
			
		||||
  if (written != len) {
 | 
			
		||||
    return OTA_RESPONSE_ERROR_WRITING_FLASH;
 | 
			
		||||
  }
 | 
			
		||||
  if (written == len) {
 | 
			
		||||
    return OTA_RESPONSE_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  ESP_LOGE(TAG, "Write error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_WRITING_FLASH;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoESP8266OTABackend::end() {
 | 
			
		||||
  if (!Update.end())
 | 
			
		||||
    return OTA_RESPONSE_ERROR_UPDATE_END;
 | 
			
		||||
  if (Update.end()) {
 | 
			
		||||
    return OTA_RESPONSE_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  ESP_LOGE(TAG, "End error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_UPDATE_END;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoESP8266OTABackend::abort() {
 | 
			
		||||
  Update.end();
 | 
			
		||||
  esp8266::preferences_prevent_write(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,17 @@
 | 
			
		||||
#ifdef USE_LIBRETINY
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
#include "ota_backend_arduino_libretiny.h"
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <Update.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ota {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ota.arduino_libretiny";
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoLibreTinyOTABackend>(); }
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
 | 
			
		||||
@@ -20,6 +23,9 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  if (error == UPDATE_ERROR_SIZE)
 | 
			
		||||
    return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGE(TAG, "Begin error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_UNKNOWN;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -27,18 +33,27 @@ void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { Update.setMD5
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) {
 | 
			
		||||
  size_t written = Update.write(data, len);
 | 
			
		||||
  if (written != len) {
 | 
			
		||||
    return OTA_RESPONSE_ERROR_WRITING_FLASH;
 | 
			
		||||
  }
 | 
			
		||||
  if (written == len) {
 | 
			
		||||
    return OTA_RESPONSE_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  ESP_LOGE(TAG, "Write error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_WRITING_FLASH;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoLibreTinyOTABackend::end() {
 | 
			
		||||
  if (!Update.end())
 | 
			
		||||
    return OTA_RESPONSE_ERROR_UPDATE_END;
 | 
			
		||||
  if (Update.end()) {
 | 
			
		||||
    return OTA_RESPONSE_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  ESP_LOGE(TAG, "End error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_UPDATE_END;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoLibreTinyOTABackend::abort() { Update.abort(); }
 | 
			
		||||
 | 
			
		||||
}  // namespace ota
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,19 @@
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
#include "ota_backend_arduino_rp2040.h"
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/rp2040/preferences.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <Updater.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ota {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ota.arduino_rp2040";
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoRP2040OTABackend>(); }
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
 | 
			
		||||
@@ -29,6 +32,9 @@ OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
 | 
			
		||||
    return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG;
 | 
			
		||||
  if (error == UPDATE_ERROR_SPACE)
 | 
			
		||||
    return OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGE(TAG, "Begin error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_UNKNOWN;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -36,18 +42,27 @@ void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) {
 | 
			
		||||
  size_t written = Update.write(data, len);
 | 
			
		||||
  if (written != len) {
 | 
			
		||||
    return OTA_RESPONSE_ERROR_WRITING_FLASH;
 | 
			
		||||
  }
 | 
			
		||||
  if (written == len) {
 | 
			
		||||
    return OTA_RESPONSE_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  ESP_LOGE(TAG, "Write error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_WRITING_FLASH;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoRP2040OTABackend::end() {
 | 
			
		||||
  if (!Update.end())
 | 
			
		||||
    return OTA_RESPONSE_ERROR_UPDATE_END;
 | 
			
		||||
  if (Update.end()) {
 | 
			
		||||
    return OTA_RESPONSE_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  ESP_LOGE(TAG, "End error: %d", error);
 | 
			
		||||
 | 
			
		||||
  return OTA_RESPONSE_ERROR_UPDATE_END;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoRP2040OTABackend::abort() {
 | 
			
		||||
  Update.end();
 | 
			
		||||
  rp2040::preferences_prevent_write(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,15 @@ void PMSA003IComponent::setup() {
 | 
			
		||||
  PM25AQIData data;
 | 
			
		||||
  bool successful_read = this->read_data_(&data);
 | 
			
		||||
 | 
			
		||||
  if (!successful_read) {
 | 
			
		||||
    for (int i = 0; i < 3; i++) {
 | 
			
		||||
      successful_read = this->read_data_(&data);
 | 
			
		||||
      if (successful_read) {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!successful_read) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,14 @@ COLOR_ORDERS = {
 | 
			
		||||
}
 | 
			
		||||
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_dimension(value):
 | 
			
		||||
    value = cv.positive_int(value)
 | 
			
		||||
    if value % 2 != 0:
 | 
			
		||||
        raise cv.Invalid("Width/height/offset must be divisible by 2")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    display.FULL_DISPLAY_SCHEMA.extend(
 | 
			
		||||
        cv.Schema(
 | 
			
		||||
@@ -52,10 +60,14 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                    cv.dimensions,
 | 
			
		||||
                    cv.Schema(
 | 
			
		||||
                        {
 | 
			
		||||
                            cv.Required(CONF_WIDTH): cv.int_,
 | 
			
		||||
                            cv.Required(CONF_HEIGHT): cv.int_,
 | 
			
		||||
                            cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_,
 | 
			
		||||
                            cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_,
 | 
			
		||||
                            cv.Required(CONF_WIDTH): validate_dimension,
 | 
			
		||||
                            cv.Required(CONF_HEIGHT): validate_dimension,
 | 
			
		||||
                            cv.Optional(
 | 
			
		||||
                                CONF_OFFSET_HEIGHT, default=0
 | 
			
		||||
                            ): validate_dimension,
 | 
			
		||||
                            cv.Optional(
 | 
			
		||||
                                CONF_OFFSET_WIDTH, default=0
 | 
			
		||||
                            ): validate_dimension,
 | 
			
		||||
                        }
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,23 @@ void QspiAmoLed::setup() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QspiAmoLed::update() {
 | 
			
		||||
  if (!this->setup_complete_) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->do_update_();
 | 
			
		||||
  // Start addresses and widths/heights must be divisible by 2 (CASET/RASET restriction in datasheet)
 | 
			
		||||
  if (this->x_low_ % 2 == 1) {
 | 
			
		||||
    this->x_low_--;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->x_high_ % 2 == 0) {
 | 
			
		||||
    this->x_high_++;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->y_low_ % 2 == 1) {
 | 
			
		||||
    this->y_low_--;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->y_high_ % 2 == 0) {
 | 
			
		||||
    this->y_high_++;
 | 
			
		||||
  }
 | 
			
		||||
  int w = this->x_high_ - this->x_low_ + 1;
 | 
			
		||||
  int h = this->y_high_ - this->y_low_ + 1;
 | 
			
		||||
  this->draw_pixels_at(this->x_low_, this->y_low_, w, h, this->buffer_, this->color_mode_, display::COLOR_BITNESS_565,
 | 
			
		||||
 
 | 
			
		||||
@@ -65,13 +65,10 @@ class QspiAmoLed : public display::DisplayBuffer,
 | 
			
		||||
 | 
			
		||||
  void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
 | 
			
		||||
  void set_enable_pin(GPIOPin *enable_pin) { this->enable_pin_ = enable_pin; }
 | 
			
		||||
  void set_width(uint16_t width) { this->width_ = width; }
 | 
			
		||||
  void set_dimensions(uint16_t width, uint16_t height) {
 | 
			
		||||
    this->width_ = width;
 | 
			
		||||
    this->height_ = height;
 | 
			
		||||
  }
 | 
			
		||||
  int get_width() override { return this->width_; }
 | 
			
		||||
  int get_height() override { return this->height_; }
 | 
			
		||||
  void set_invert_colors(bool invert_colors) {
 | 
			
		||||
    this->invert_colors_ = invert_colors;
 | 
			
		||||
    this->reset_params_();
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,10 @@ static const char *const TAG = "remote.dooya";
 | 
			
		||||
 | 
			
		||||
static const uint32_t HEADER_HIGH_US = 5000;
 | 
			
		||||
static const uint32_t HEADER_LOW_US = 1500;
 | 
			
		||||
static const uint32_t BIT_ZERO_HIGH_US = 750;
 | 
			
		||||
static const uint32_t BIT_ZERO_LOW_US = 350;
 | 
			
		||||
static const uint32_t BIT_ONE_HIGH_US = 350;
 | 
			
		||||
static const uint32_t BIT_ONE_LOW_US = 750;
 | 
			
		||||
static const uint32_t BIT_ZERO_HIGH_US = 350;
 | 
			
		||||
static const uint32_t BIT_ZERO_LOW_US = 750;
 | 
			
		||||
static const uint32_t BIT_ONE_HIGH_US = 750;
 | 
			
		||||
static const uint32_t BIT_ONE_LOW_US = 350;
 | 
			
		||||
 | 
			
		||||
void DooyaProtocol::encode(RemoteTransmitData *dst, const DooyaData &data) {
 | 
			
		||||
  dst->set_carrier_frequency(0);
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ void RCSwitchBase::sync(RemoteTransmitData *dst) const {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void RCSwitchBase::transmit(RemoteTransmitData *dst, uint64_t code, uint8_t len) const {
 | 
			
		||||
  dst->set_carrier_frequency(0);
 | 
			
		||||
  dst->set_carrier_frequency(38000);
 | 
			
		||||
  this->sync(dst);
 | 
			
		||||
  for (int16_t i = len - 1; i >= 0; i--) {
 | 
			
		||||
    if (code & ((uint64_t) 1 << i)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    DEVICE_CLASS_RESTART,
 | 
			
		||||
    ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
    ICON_RESTART,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
restart_ns = cg.esphome_ns.namespace("restart")
 | 
			
		||||
@@ -12,6 +13,7 @@ RestartButton = restart_ns.class_("RestartButton", button.Button, cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = button.button_schema(
 | 
			
		||||
    RestartButton,
 | 
			
		||||
    icon=ICON_RESTART,
 | 
			
		||||
    device_class=DEVICE_CLASS_RESTART,
 | 
			
		||||
    entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 
 | 
			
		||||
@@ -56,9 +56,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(50.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    if config[CONF_DISABLED]:
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if not config[CONF_DISABLED]:
 | 
			
		||||
        var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
        await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
@@ -72,5 +70,6 @@ async def to_code(config):
 | 
			
		||||
            config[CONF_BOOT_IS_GOOD_AFTER],
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(RawExpression(f"if ({condition}) return"))
 | 
			
		||||
 | 
			
		||||
    CORE.data[CONF_SAFE_MODE] = {}
 | 
			
		||||
    CORE.data[CONF_SAFE_MODE][KEY_PAST_SAFE_MODE] = True
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,7 @@ def validate_parameter_name(value):
 | 
			
		||||
    raise cv.Invalid(f"Script's parameter name cannot be {CONF_ID}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ALLOWED_PARAM_TYPE_CHARSET = set("abcdefghijklmnopqrstuvwxyz0123456789_:*&[]")
 | 
			
		||||
ALLOWED_PARAM_TYPE_CHARSET = set("abcdefghijklmnopqrstuvwxyz0123456789_:*&[]<>")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_parameter_type(value):
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CONDUCTIVITY,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_DATA_RATE,
 | 
			
		||||
    DEVICE_CLASS_DATA_SIZE,
 | 
			
		||||
@@ -103,6 +104,7 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CONDUCTIVITY,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_DATA_RATE,
 | 
			
		||||
    DEVICE_CLASS_DATA_SIZE,
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user