mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 21:44:55 +01:00
commit
04b268e319
1877 changed files with 2430 additions and 13033 deletions
|
@ -1,7 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "ESPHome Dev",
|
"name": "ESPHome Dev",
|
||||||
"image": "ghcr.io/esphome/esphome-lint:dev",
|
"image": "ghcr.io/esphome/esphome-lint:dev",
|
||||||
"postCreateCommand": ["script/devcontainer-post-create"],
|
"postCreateCommand": [
|
||||||
|
"script/devcontainer-post-create"
|
||||||
|
],
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
"DEVCONTAINER": "1",
|
"DEVCONTAINER": "1",
|
||||||
"PIP_BREAK_SYSTEM_PACKAGES": "1",
|
"PIP_BREAK_SYSTEM_PACKAGES": "1",
|
||||||
|
@ -27,6 +29,9 @@
|
||||||
"extensions": [
|
"extensions": [
|
||||||
// python
|
// python
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
|
"ms-python.pylint",
|
||||||
|
"ms-python.flake8",
|
||||||
|
"ms-python.black-formatter",
|
||||||
"visualstudioexptteam.vscodeintellicode",
|
"visualstudioexptteam.vscodeintellicode",
|
||||||
// yaml
|
// yaml
|
||||||
"redhat.vscode-yaml",
|
"redhat.vscode-yaml",
|
||||||
|
@ -38,9 +43,21 @@
|
||||||
"settings": {
|
"settings": {
|
||||||
"python.languageServer": "Pylance",
|
"python.languageServer": "Pylance",
|
||||||
"python.pythonPath": "/usr/bin/python3",
|
"python.pythonPath": "/usr/bin/python3",
|
||||||
"python.linting.pylintEnabled": true,
|
"pylint.args": [
|
||||||
"python.linting.enabled": true,
|
"--rcfile=${workspaceFolder}/pyproject.toml"
|
||||||
"python.formatting.provider": "black",
|
],
|
||||||
|
"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.formatOnPaste": false,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.formatOnType": 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
|
- name: Build and push to ghcr by digest
|
||||||
id: build-ghcr
|
id: build-ghcr
|
||||||
uses: docker/build-push-action@v6.0.1
|
uses: docker/build-push-action@v6.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./docker/Dockerfile
|
file: ./docker/Dockerfile
|
||||||
|
@ -69,7 +69,7 @@ runs:
|
||||||
|
|
||||||
- name: Build and push to dockerhub by digest
|
- name: Build and push to dockerhub by digest
|
||||||
id: build-dockerhub
|
id: build-dockerhub
|
||||||
uses: docker/build-push-action@v6.0.1
|
uses: docker/build-push-action@v6.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./docker/Dockerfile
|
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:
|
steps:
|
||||||
- name: Set up Python ${{ inputs.python-version }}
|
- name: Set up Python ${{ inputs.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v5.1.1
|
||||||
with:
|
with:
|
||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- 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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
|
|
6
.github/workflows/ci-docker.yml
vendored
6
.github/workflows/ci-docker.yml
vendored
|
@ -40,15 +40,15 @@ jobs:
|
||||||
arch: [amd64, armv7, aarch64]
|
arch: [amd64, armv7, aarch64]
|
||||||
build_type: ["ha-addon", "docker", "lint"]
|
build_type: ["ha-addon", "docker", "lint"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
- name: Set up Docker Buildx
|
- 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
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.1.0
|
||||||
|
|
||||||
- name: Set TAG
|
- name: Set TAG
|
||||||
run: |
|
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 }}
|
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Generate cache-key
|
- name: Generate cache-key
|
||||||
id: cache-key
|
id: cache-key
|
||||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
||||||
|
@ -66,7 +66,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -87,7 +87,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -108,7 +108,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -129,7 +129,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -150,7 +150,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -199,7 +199,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -229,7 +229,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -248,72 +248,6 @@ jobs:
|
||||||
run: script/ci-suggest-changes
|
run: script/ci-suggest-changes
|
||||||
if: always()
|
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:
|
clang-tidy:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -358,7 +292,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -387,6 +321,13 @@ jobs:
|
||||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||||
|
|
||||||
|
- name: 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
|
- name: Run clang-tidy
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
@ -410,7 +351,7 @@ jobs:
|
||||||
count: ${{ steps.list-components.outputs.count }}
|
count: ${{ steps.list-components.outputs.count }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
||||||
fetch-depth: 500
|
fetch-depth: 500
|
||||||
|
@ -458,7 +399,7 @@ jobs:
|
||||||
run: sudo apt-get install libsodium-dev libsdl2-dev
|
run: sudo apt-get install libsodium-dev libsdl2-dev
|
||||||
|
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -484,7 +425,7 @@ jobs:
|
||||||
matrix: ${{ steps.split.outputs.components }}
|
matrix: ${{ steps.split.outputs.components }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Split components into 20 groups
|
- name: Split components into 20 groups
|
||||||
id: split
|
id: split
|
||||||
run: |
|
run: |
|
||||||
|
@ -512,7 +453,7 @@ jobs:
|
||||||
run: sudo apt-get install libsodium-dev libsdl2-dev
|
run: sudo apt-get install libsodium-dev libsdl2-dev
|
||||||
|
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -543,7 +484,6 @@ jobs:
|
||||||
- pylint
|
- pylint
|
||||||
- pytest
|
- pytest
|
||||||
- pyupgrade
|
- pyupgrade
|
||||||
- compile-tests
|
|
||||||
- clang-tidy
|
- clang-tidy
|
||||||
- list-components
|
- list-components
|
||||||
- test-build-components
|
- test-build-components
|
||||||
|
|
18
.github/workflows/release.yml
vendored
18
.github/workflows/release.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
||||||
tag: ${{ steps.tag.outputs.tag }}
|
tag: ${{ steps.tag.outputs.tag }}
|
||||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
- name: Get tag
|
- name: Get tag
|
||||||
id: tag
|
id: tag
|
||||||
# yamllint disable rule:line-length
|
# yamllint disable rule:line-length
|
||||||
|
@ -51,7 +51,7 @@ jobs:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
|
@ -83,17 +83,17 @@ jobs:
|
||||||
- linux/arm/v7
|
- linux/arm/v7
|
||||||
- linux/arm64
|
- linux/arm64
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- 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
|
- name: Set up QEMU
|
||||||
if: matrix.platform != 'linux/amd64'
|
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
|
- name: Log in to docker hub
|
||||||
uses: docker/login-action@v3.2.0
|
uses: docker/login-action@v3.2.0
|
||||||
|
@ -141,7 +141,7 @@ jobs:
|
||||||
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
|
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload digests
|
- name: Upload digests
|
||||||
uses: actions/upload-artifact@v4.3.3
|
uses: actions/upload-artifact@v4.3.4
|
||||||
with:
|
with:
|
||||||
name: digests-${{ steps.sanitize.outputs.name }}
|
name: digests-${{ steps.sanitize.outputs.name }}
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
|
@ -174,17 +174,17 @@ jobs:
|
||||||
- ghcr
|
- ghcr
|
||||||
- dockerhub
|
- dockerhub
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v4.1.7
|
uses: actions/download-artifact@v4.1.8
|
||||||
with:
|
with:
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- 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
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
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'
|
if: github.repository == 'esphome/esphome'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Checkout Home Assistant
|
- name: Checkout Home Assistant
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
repository: home-assistant/core
|
repository: home-assistant/core
|
||||||
path: lib/home-assistant
|
path: lib/home-assistant
|
||||||
|
@ -36,7 +36,7 @@ jobs:
|
||||||
python ./script/sync-device_class.py
|
python ./script/sync-device_class.py
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
uses: peter-evans/create-pull-request@v6.0.5
|
uses: peter-evans/create-pull-request@v6.1.0
|
||||||
with:
|
with:
|
||||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||||
committer: esphomebot <esphome@nabucasa.com>
|
committer: esphomebot <esphome@nabucasa.com>
|
||||||
|
|
2
.github/workflows/yaml-lint.yml
vendored
2
.github/workflows/yaml-lint.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Run yamllint
|
- name: Run yamllint
|
||||||
uses: frenck/action-yamllint@v1.5.0
|
uses: frenck/action-yamllint@v1.5.0
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -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.
|
* 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();
|
void open();
|
||||||
/** Close the cover.
|
/** Close the cover.
|
||||||
*
|
*
|
||||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||||
*/
|
*/
|
||||||
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();
|
void close();
|
||||||
/** Stop the cover.
|
/** Stop the cover.
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,7 +17,7 @@ from esphome.const import (
|
||||||
CONF_VSYNC_PIN,
|
CONF_VSYNC_PIN,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
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
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
@ -290,8 +290,11 @@ async def to_code(config):
|
||||||
cg.add_define("USE_ESP32_CAMERA")
|
cg.add_define("USE_ESP32_CAMERA")
|
||||||
|
|
||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
cg.add_library("espressif/esp32-camera", "1.0.0")
|
add_idf_component(
|
||||||
add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
|
name="esp32-camera",
|
||||||
|
repo="https://github.com/espressif/esp32-camera.git",
|
||||||
|
ref="v2.0.9",
|
||||||
|
)
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_STREAM_START, []):
|
for conf in config.get(CONF_ON_STREAM_START, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
|
|
@ -65,7 +65,8 @@ void EthernetComponent::setup() {
|
||||||
.intr_flags = 0,
|
.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;
|
auto host = SPI2_HOST;
|
||||||
#else
|
#else
|
||||||
auto host = SPI3_HOST;
|
auto host = SPI3_HOST;
|
||||||
|
@ -631,7 +632,7 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi
|
||||||
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
|
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
|
||||||
|
|
||||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
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);
|
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");
|
ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ from esphome.helpers import (
|
||||||
cpp_string_escape,
|
cpp_string_escape,
|
||||||
)
|
)
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
__version__,
|
|
||||||
CONF_FAMILY,
|
CONF_FAMILY,
|
||||||
CONF_FILE,
|
CONF_FILE,
|
||||||
CONF_GLYPHS,
|
CONF_GLYPHS,
|
||||||
|
@ -185,31 +184,6 @@ def get_font_path(value, type) -> Path:
|
||||||
return None
|
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):
|
def download_gfont(value):
|
||||||
name = (
|
name = (
|
||||||
f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}"
|
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)
|
ttf_url = match.group(1)
|
||||||
_LOGGER.debug("download_gfont: ttf_url=%s", ttf_url)
|
_LOGGER.debug("download_gfont: ttf_url=%s", ttf_url)
|
||||||
|
|
||||||
download_content(ttf_url, path)
|
external_files.download_content(ttf_url, path)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@ -244,7 +218,7 @@ def download_web_font(value):
|
||||||
url = value[CONF_URL]
|
url = value[CONF_URL]
|
||||||
path = get_font_path(value, TYPE_WEB)
|
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)
|
_LOGGER.debug("download_web_font: path=%s", path)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ SENSOR_TYPES = {
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
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()})
|
||||||
|
|
||||||
|
@ -64,8 +64,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||||
|
|
||||||
for type, _ in SENSOR_TYPES.items():
|
for type_ in SENSOR_TYPES:
|
||||||
if conf := config.get(type):
|
if conf := config.get(type_):
|
||||||
sens = await binary_sensor.new_binary_sensor(conf)
|
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))
|
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(
|
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(
|
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
|
||||||
SelfCleaningButton,
|
SelfCleaningButton,
|
||||||
icon=ICON_SPRAY_BOTTLE,
|
icon=ICON_SPRAY_BOTTLE,
|
||||||
|
|
|
@ -38,6 +38,9 @@ PROTOCOL_MAX_TEMPERATURE = 30.0
|
||||||
PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0
|
PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0
|
||||||
PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5
|
PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5
|
||||||
PROTOCOL_CONTROL_PACKET_SIZE = 10
|
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"]
|
CODEOWNERS = ["@paveldn"]
|
||||||
DEPENDENCIES = ["climate", "uart"]
|
DEPENDENCIES = ["climate", "uart"]
|
||||||
|
@ -48,6 +51,9 @@ CONF_CONTROL_PACKET_SIZE = "control_packet_size"
|
||||||
CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow"
|
CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow"
|
||||||
CONF_ON_ALARM_START = "on_alarm_start"
|
CONF_ON_ALARM_START = "on_alarm_start"
|
||||||
CONF_ON_ALARM_END = "on_alarm_end"
|
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_VERTICAL_AIRFLOW = "vertical_airflow"
|
||||||
CONF_WIFI_SIGNAL = "wifi_signal"
|
CONF_WIFI_SIGNAL = "wifi_signal"
|
||||||
|
|
||||||
|
@ -129,6 +135,11 @@ HaierAlarmEndTrigger = haier_ns.class_(
|
||||||
automation.Trigger.template(cg.uint8, cg.const_char_ptr),
|
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):
|
def validate_visual(config):
|
||||||
if CONF_VISUAL in config:
|
if CONF_VISUAL in config:
|
||||||
|
@ -183,7 +194,6 @@ BASE_CONFIG_SCHEMA = (
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_SUPPORTED_SWING_MODES,
|
CONF_SUPPORTED_SWING_MODES,
|
||||||
default=[
|
default=[
|
||||||
"OFF",
|
|
||||||
"VERTICAL",
|
"VERTICAL",
|
||||||
"HORIZONTAL",
|
"HORIZONTAL",
|
||||||
"BOTH",
|
"BOTH",
|
||||||
|
@ -194,6 +204,11 @@ BASE_CONFIG_SCHEMA = (
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_ANSWER_TIMEOUT,
|
CONF_ANSWER_TIMEOUT,
|
||||||
): cv.positive_time_period_milliseconds,
|
): 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)
|
.extend(uart.UART_DEVICE_SCHEMA)
|
||||||
|
@ -211,7 +226,7 @@ CONFIG_SCHEMA = cv.All(
|
||||||
): cv.boolean,
|
): cv.boolean,
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_SUPPORTED_PRESETS,
|
CONF_SUPPORTED_PRESETS,
|
||||||
default=list(["BOOST", "COMFORT"]), # No AWAY by default
|
default=["BOOST", "COMFORT"], # No AWAY by default
|
||||||
): cv.ensure_list(
|
): cv.ensure_list(
|
||||||
cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True)
|
cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True)
|
||||||
),
|
),
|
||||||
|
@ -229,9 +244,17 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE
|
CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE
|
||||||
): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50),
|
): 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(
|
cv.Optional(
|
||||||
CONF_SUPPORTED_PRESETS,
|
CONF_SUPPORTED_PRESETS,
|
||||||
default=list(["BOOST", "ECO", "SLEEP"]), # No AWAY by default
|
default=["BOOST", "ECO", "SLEEP"], # No AWAY by default
|
||||||
): cv.ensure_list(
|
): cv.ensure_list(
|
||||||
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
|
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"
|
"No logger component found, logging for Haier protocol is disabled"
|
||||||
)
|
)
|
||||||
cg.add_build_flag("-DHAIER_LOG_LEVEL=0")
|
cg.add_build_flag("-DHAIER_LOG_LEVEL=0")
|
||||||
if (
|
if config.get(CONF_WIFI_SIGNAL) and CONF_WIFI not in full_config:
|
||||||
(CONF_WIFI_SIGNAL in config)
|
|
||||||
and (config[CONF_WIFI_SIGNAL])
|
|
||||||
and CONF_WIFI not in full_config
|
|
||||||
):
|
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"No WiFi configured, if you want to use haier climate without WiFi add {CONF_WIFI_SIGNAL}: false to climate configuration"
|
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
|
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, []):
|
for conf in config.get(CONF_ON_ALARM_START, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
|
@ -483,5 +512,10 @@ async def to_code(config):
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
|
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
|
# 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});
|
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::HandlerError HaierClimateBase::answer_preprocess_(
|
||||||
haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type,
|
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,
|
haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include "esphome/components/climate/climate.h"
|
#include "esphome/components/climate/climate.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
// HaierProtocol
|
// HaierProtocol
|
||||||
#include <protocol/haier_protocol.h>
|
#include <protocol/haier_protocol.h>
|
||||||
|
|
||||||
|
@ -56,6 +57,7 @@ class HaierClimateBase : public esphome::Component,
|
||||||
void set_answer_timeout(uint32_t timeout);
|
void set_answer_timeout(uint32_t timeout);
|
||||||
void set_send_wifi(bool send_wifi);
|
void set_send_wifi(bool send_wifi);
|
||||||
void send_custom_command(const haier_protocol::HaierMessage &message);
|
void send_custom_command(const haier_protocol::HaierMessage &message);
|
||||||
|
void add_status_message_callback(std::function<void(const char *, size_t)> &&callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum class ProtocolPhases {
|
enum class ProtocolPhases {
|
||||||
|
@ -140,11 +142,19 @@ class HaierClimateBase : public esphome::Component,
|
||||||
esphome::climate::ClimateTraits traits_;
|
esphome::climate::ClimateTraits traits_;
|
||||||
HvacSettings current_hvac_settings_;
|
HvacSettings current_hvac_settings_;
|
||||||
HvacSettings next_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_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_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_status_request_; // To request AC status
|
||||||
std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level
|
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
|
} // namespace haier
|
||||||
|
|
|
@ -18,12 +18,13 @@ constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64;
|
||||||
constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
|
constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
|
||||||
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
|
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
|
||||||
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
|
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()
|
HonClimate::HonClimate()
|
||||||
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
|
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 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->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID;
|
||||||
this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
|
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->action_request_.reset();
|
||||||
this->force_send_control_ = false;
|
this->force_send_control_ = false;
|
||||||
} else {
|
} else {
|
||||||
if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) {
|
if (!this->last_status_message_) {
|
||||||
memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl));
|
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 {
|
} else {
|
||||||
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size,
|
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_);
|
||||||
sizeof(hon_protocol::HaierPacketControl));
|
|
||||||
}
|
}
|
||||||
switch (this->protocol_phase_) {
|
switch (this->protocol_phase_) {
|
||||||
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
|
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
|
||||||
|
@ -479,8 +487,8 @@ void HonClimate::initialization() {
|
||||||
}
|
}
|
||||||
|
|
||||||
haier_protocol::HaierMessage HonClimate::get_control_message() {
|
haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||||
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
|
uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
|
||||||
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl));
|
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;
|
hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
|
||||||
control_out_buffer[4] = 0; // This byte should be cleared before setting values
|
control_out_buffer[4] = 0; // This byte should be cleared before setting values
|
||||||
bool has_hvac_settings = false;
|
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;
|
out_data->health_mode = this->health_mode_ ? 1 : 0;
|
||||||
return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||||
(uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
|
(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) {
|
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
|
#endif // USE_TEXT_SENSOR
|
||||||
|
|
||||||
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
|
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) +
|
size_t expected_size =
|
||||||
this->extra_control_packet_bytes_;
|
2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_;
|
||||||
if (size < expected_size)
|
if (size < expected_size) {
|
||||||
|
ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size);
|
||||||
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
|
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
|
||||||
|
}
|
||||||
uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
|
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
|
// Got BigData packet
|
||||||
const hon_protocol::HaierPacketBigData *bd_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
|
#ifdef USE_SENSOR
|
||||||
this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20);
|
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);
|
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::HaierPacketControl control;
|
||||||
hon_protocol::HaierPacketSensors sensors;
|
hon_protocol::HaierPacketSensors sensors;
|
||||||
} packet;
|
} packet;
|
||||||
memcpy(&packet.control, packet_buffer + 2, sizeof(hon_protocol::HaierPacketControl));
|
memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_,
|
||||||
memcpy(&packet.sensors,
|
sizeof(hon_protocol::HaierPacketControl));
|
||||||
packet_buffer + 2 + sizeof(hon_protocol::HaierPacketControl) + this->extra_control_packet_bytes_,
|
memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_,
|
||||||
sizeof(hon_protocol::HaierPacketSensors));
|
sizeof(hon_protocol::HaierPacketSensors));
|
||||||
if (packet.sensors.error_status != 0) {
|
if (packet.sensors.error_status != 0) {
|
||||||
ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
|
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_() {
|
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_)
|
if (!this->current_hvac_settings_.valid && !this->force_send_control_)
|
||||||
return;
|
return;
|
||||||
this->clear_control_messages_queue_();
|
this->clear_control_messages_queue_();
|
||||||
|
@ -1009,7 +1017,7 @@ void HonClimate::fill_control_messages_queue_() {
|
||||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||||
(uint8_t) hon_protocol::DataParameters::BEEPER_STATUS,
|
(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
|
// Health mode
|
||||||
{
|
{
|
||||||
|
@ -1017,7 +1025,7 @@ void HonClimate::fill_control_messages_queue_() {
|
||||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||||
(uint8_t) hon_protocol::DataParameters::HEALTH_MODE,
|
(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
|
// Climate mode
|
||||||
bool new_power = this->mode != CLIMATE_MODE_OFF;
|
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,
|
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||||
(uint8_t) hon_protocol::DataParameters::AC_POWER,
|
(uint8_t) hon_protocol::DataParameters::AC_POWER,
|
||||||
new_power ? one_buf : zero_buf, 2));
|
new_power ? ONE_BUF : ZERO_BUF, 2));
|
||||||
}
|
}
|
||||||
// CLimate preset
|
// CLimate preset
|
||||||
{
|
{
|
||||||
|
@ -1165,6 +1173,35 @@ void HonClimate::fill_control_messages_queue_() {
|
||||||
(uint8_t) hon_protocol::DataParameters::SET_POINT,
|
(uint8_t) hon_protocol::DataParameters::SET_POINT,
|
||||||
buffer, 2));
|
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
|
// Fan mode
|
||||||
if (climate_control.fan_mode.has_value()) {
|
if (climate_control.fan_mode.has_value()) {
|
||||||
switch (climate_control.fan_mode.value()) {
|
switch (climate_control.fan_mode.value()) {
|
||||||
|
@ -1202,40 +1239,56 @@ void HonClimate::clear_control_messages_queue_() {
|
||||||
|
|
||||||
bool HonClimate::prepare_pending_action() {
|
bool HonClimate::prepare_pending_action() {
|
||||||
switch (this->action_request_.value().action) {
|
switch (this->action_request_.value().action) {
|
||||||
case ActionRequest::START_SELF_CLEAN: {
|
case ActionRequest::START_SELF_CLEAN:
|
||||||
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
|
if (this->control_method_ == HonControlMethod::SET_GROUP_PARAMETERS) {
|
||||||
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl));
|
uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
|
||||||
hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
|
memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
|
||||||
out_data->self_cleaning_status = 1;
|
hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
|
||||||
out_data->steri_clean = 0;
|
out_data->self_cleaning_status = 1;
|
||||||
out_data->set_point = 0x06;
|
out_data->steri_clean = 0;
|
||||||
out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER;
|
out_data->set_point = 0x06;
|
||||||
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER;
|
out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER;
|
||||||
out_data->ac_power = 1;
|
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER;
|
||||||
out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
|
out_data->ac_power = 1;
|
||||||
out_data->light_status = 0;
|
out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
|
||||||
this->action_request_.value().message = haier_protocol::HaierMessage(
|
out_data->light_status = 0;
|
||||||
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
|
this->action_request_.value().message = haier_protocol::HaierMessage(
|
||||||
control_out_buffer, sizeof(hon_protocol::HaierPacketControl));
|
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
|
||||||
}
|
control_out_buffer, this->real_control_packet_size_);
|
||||||
return true;
|
return true;
|
||||||
case ActionRequest::START_STERI_CLEAN: {
|
} else if (this->control_method_ == HonControlMethod::SET_SINGLE_PARAMETER) {
|
||||||
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
|
this->action_request_.value().message =
|
||||||
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl));
|
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||||
hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
|
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||||
out_data->self_cleaning_status = 0;
|
(uint8_t) hon_protocol::DataParameters::SELF_CLEANING,
|
||||||
out_data->steri_clean = 1;
|
ONE_BUF, 2);
|
||||||
out_data->set_point = 0x06;
|
return true;
|
||||||
out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER;
|
} else {
|
||||||
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER;
|
this->action_request_.reset();
|
||||||
out_data->ac_power = 1;
|
return false;
|
||||||
out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
|
}
|
||||||
out_data->light_status = 0;
|
case ActionRequest::START_STERI_CLEAN:
|
||||||
this->action_request_.value().message = haier_protocol::HaierMessage(
|
if (this->control_method_ == HonControlMethod::SET_GROUP_PARAMETERS) {
|
||||||
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
|
uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
|
||||||
control_out_buffer, sizeof(hon_protocol::HaierPacketControl));
|
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;
|
||||||
return true;
|
out_data->self_cleaning_status = 0;
|
||||||
|
out_data->steri_clean = 1;
|
||||||
|
out_data->set_point = 0x06;
|
||||||
|
out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER;
|
||||||
|
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER;
|
||||||
|
out_data->ac_power = 1;
|
||||||
|
out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
|
||||||
|
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, this->real_control_packet_size_);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// No Steri clean support (yet?) in SET_SINGLE_PARAMETER
|
||||||
|
this->action_request_.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return HaierClimateBase::prepare_pending_action();
|
return HaierClimateBase::prepare_pending_action();
|
||||||
}
|
}
|
||||||
|
@ -1251,6 +1304,7 @@ void HonClimate::process_protocol_reset() {
|
||||||
#endif // USE_SENSOR
|
#endif // USE_SENSOR
|
||||||
this->got_valid_outdoor_temp_ = false;
|
this->got_valid_outdoor_temp_ = false;
|
||||||
this->hvac_hardware_info_.reset();
|
this->hvac_hardware_info_.reset();
|
||||||
|
this->last_status_message_.reset(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HonClimate::should_get_big_data_() {
|
bool HonClimate::should_get_big_data_() {
|
||||||
|
|
|
@ -104,6 +104,8 @@ class HonClimate : public HaierClimateBase {
|
||||||
void start_self_cleaning();
|
void start_self_cleaning();
|
||||||
void start_steri_cleaning();
|
void start_steri_cleaning();
|
||||||
void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; };
|
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 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_start_callback(std::function<void(uint8_t, const char *)> &&callback);
|
||||||
void add_alarm_end_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<hon_protocol::HorizontalSwingMode> pending_horizontal_direction_{};
|
||||||
esphome::optional<HardwareInfo> hvac_hardware_info_{};
|
esphome::optional<HardwareInfo> hvac_hardware_info_{};
|
||||||
uint8_t active_alarms_[8];
|
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_;
|
HonControlMethod control_method_;
|
||||||
std::queue<haier_protocol::HaierMessage> control_messages_queue_;
|
std::queue<haier_protocol::HaierMessage> control_messages_queue_;
|
||||||
CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{};
|
CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{};
|
||||||
|
|
|
@ -41,15 +41,20 @@ enum class ConditioningMode : uint8_t {
|
||||||
enum class DataParameters : uint8_t {
|
enum class DataParameters : uint8_t {
|
||||||
AC_POWER = 0x01,
|
AC_POWER = 0x01,
|
||||||
SET_POINT = 0x02,
|
SET_POINT = 0x02,
|
||||||
|
VERTICAL_SWING_MODE = 0x03,
|
||||||
AC_MODE = 0x04,
|
AC_MODE = 0x04,
|
||||||
FAN_MODE = 0x05,
|
FAN_MODE = 0x05,
|
||||||
USE_FAHRENHEIT = 0x07,
|
USE_FAHRENHEIT = 0x07,
|
||||||
|
DISPLAY_STATUS = 0x09,
|
||||||
TEN_DEGREE = 0x0A,
|
TEN_DEGREE = 0x0A,
|
||||||
HEALTH_MODE = 0x0B,
|
HEALTH_MODE = 0x0B,
|
||||||
|
HORIZONTAL_SWING_MODE = 0x0C,
|
||||||
|
SELF_CLEANING = 0x0D,
|
||||||
BEEPER_STATUS = 0x16,
|
BEEPER_STATUS = 0x16,
|
||||||
LOCK_REMOTE = 0x17,
|
LOCK_REMOTE = 0x17,
|
||||||
QUIET_MODE = 0x19,
|
QUIET_MODE = 0x19,
|
||||||
FAST_MODE = 0x1A,
|
FAST_MODE = 0x1A,
|
||||||
|
SLEEP_MODE = 0x1B,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 };
|
enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 };
|
||||||
|
|
|
@ -137,16 +137,16 @@ SENSOR_TYPES = {
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
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):
|
async def to_code(config):
|
||||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||||
|
|
||||||
for type, _ in SENSOR_TYPES.items():
|
for type_ in SENSOR_TYPES:
|
||||||
if conf := config.get(type):
|
if conf := config.get(type_):
|
||||||
sens = await sensor.new_sensor(conf)
|
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))
|
cg.add(paren.set_sub_sensor(sensor_type, sens))
|
||||||
|
|
|
@ -37,6 +37,7 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(haier_protocol::F
|
||||||
} else {
|
} else {
|
||||||
if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) {
|
if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) {
|
||||||
memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl));
|
memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl));
|
||||||
|
this->status_message_callback_.call((const char *) data, data_size);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size,
|
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size,
|
||||||
sizeof(smartair2_protocol::HaierPacketControl));
|
sizeof(smartair2_protocol::HaierPacketControl));
|
||||||
|
|
|
@ -39,7 +39,7 @@ TEXT_SENSOR_TYPES = {
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
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()})
|
).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):
|
async def to_code(config):
|
||||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||||
|
|
||||||
for type, _ in TEXT_SENSOR_TYPES.items():
|
for type_ in TEXT_SENSOR_TYPES:
|
||||||
if conf := config.get(type):
|
if conf := config.get(type_):
|
||||||
sens = await text_sensor.new_text_sensor(conf)
|
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))
|
cg.add(paren.set_sub_text_sensor(text_sensor_type, sens))
|
||||||
|
|
|
@ -34,6 +34,7 @@ PROTOCOLS = {
|
||||||
"greeyan": Protocol.PROTOCOL_GREEYAN,
|
"greeyan": Protocol.PROTOCOL_GREEYAN,
|
||||||
"greeyac": Protocol.PROTOCOL_GREEYAC,
|
"greeyac": Protocol.PROTOCOL_GREEYAC,
|
||||||
"greeyt": Protocol.PROTOCOL_GREEYT,
|
"greeyt": Protocol.PROTOCOL_GREEYT,
|
||||||
|
"greeyap": Protocol.PROTOCOL_GREEYAP,
|
||||||
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
|
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
|
||||||
"hitachi": Protocol.PROTOCOL_HITACHI,
|
"hitachi": Protocol.PROTOCOL_HITACHI,
|
||||||
"hyundai": Protocol.PROTOCOL_HYUNDAI,
|
"hyundai": Protocol.PROTOCOL_HYUNDAI,
|
||||||
|
@ -61,6 +62,11 @@ PROTOCOLS = {
|
||||||
"toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
|
"toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
|
||||||
"toshiba": Protocol.PROTOCOL_TOSHIBA,
|
"toshiba": Protocol.PROTOCOL_TOSHIBA,
|
||||||
"zhlt01": Protocol.PROTOCOL_ZHLT01,
|
"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,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
|
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
|
||||||
|
@ -116,7 +122,7 @@ def to_code(config):
|
||||||
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||||
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||||
|
|
||||||
cg.add_library("tonia/HeatpumpIR", "1.0.23")
|
cg.add_library("tonia/HeatpumpIR", "1.0.26")
|
||||||
|
|
||||||
if CORE.is_esp8266 or CORE.is_esp32:
|
if CORE.is_esp8266 or CORE.is_esp32:
|
||||||
cg.add_library("crankyoldgit/IRremoteESP8266", "2.8.4")
|
cg.add_library("crankyoldgit/IRremoteESP8266", "2.8.6")
|
||||||
|
|
|
@ -28,6 +28,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
|
||||||
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_GREEYT, []() { return new GreeYTHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_GREEYT, []() { return new GreeYTHeatpumpIR(); }}, // NOLINT
|
||||||
|
{PROTOCOL_GREEYAP, []() { return new GreeYAPHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
|
||||||
|
@ -55,6 +56,11 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
|
||||||
{PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }}, // 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
|
||||||
};
|
};
|
||||||
|
|
||||||
void HeatpumpIRClimate::setup() {
|
void HeatpumpIRClimate::setup() {
|
||||||
|
|
|
@ -28,6 +28,7 @@ enum Protocol {
|
||||||
PROTOCOL_GREEYAN,
|
PROTOCOL_GREEYAN,
|
||||||
PROTOCOL_GREEYAC,
|
PROTOCOL_GREEYAC,
|
||||||
PROTOCOL_GREEYT,
|
PROTOCOL_GREEYT,
|
||||||
|
PROTOCOL_GREEYAP,
|
||||||
PROTOCOL_HISENSE_AUD,
|
PROTOCOL_HISENSE_AUD,
|
||||||
PROTOCOL_HITACHI,
|
PROTOCOL_HITACHI,
|
||||||
PROTOCOL_HYUNDAI,
|
PROTOCOL_HYUNDAI,
|
||||||
|
@ -55,6 +56,11 @@ enum Protocol {
|
||||||
PROTOCOL_TOSHIBA_DAISEIKAI,
|
PROTOCOL_TOSHIBA_DAISEIKAI,
|
||||||
PROTOCOL_TOSHIBA,
|
PROTOCOL_TOSHIBA,
|
||||||
PROTOCOL_ZHLT01,
|
PROTOCOL_ZHLT01,
|
||||||
|
PROTOCOL_NIBE,
|
||||||
|
PROTOCOL_QLIMA_1,
|
||||||
|
PROTOCOL_QLIMA_2,
|
||||||
|
PROTOCOL_SAMSUNG_AQV12MSAN,
|
||||||
|
PROTOCOL_ZHJG01,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simple enum to represent horizontal directios
|
// Simple enum to represent horizontal directios
|
||||||
|
|
|
@ -116,19 +116,18 @@ void HttpRequestUpdate::update() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string current_version = this->current_version_;
|
std::string current_version;
|
||||||
if (current_version.empty()) {
|
|
||||||
#ifdef ESPHOME_PROJECT_VERSION
|
#ifdef ESPHOME_PROJECT_VERSION
|
||||||
current_version = ESPHOME_PROJECT_VERSION;
|
current_version = ESPHOME_PROJECT_VERSION;
|
||||||
#else
|
#else
|
||||||
current_version = ESPHOME_VERSION;
|
current_version = ESPHOME_VERSION;
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
this->update_info_.current_version = current_version;
|
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;
|
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||||
} else if (this->update_info_.latest_version != this->current_version_) {
|
} else {
|
||||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
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_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
|
||||||
void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_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; }
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
HttpRequestComponent *request_parent_;
|
HttpRequestComponent *request_parent_;
|
||||||
OtaHttpRequestComponent *ota_parent_;
|
OtaHttpRequestComponent *ota_parent_;
|
||||||
std::string source_url_;
|
std::string source_url_;
|
||||||
std::string current_version_{""};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace http_request
|
} // namespace http_request
|
||||||
|
|
|
@ -6,7 +6,6 @@ import hashlib
|
||||||
import io
|
import io
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import requests
|
|
||||||
from magic import Magic
|
from magic import Magic
|
||||||
|
|
||||||
from esphome import core
|
from esphome import core
|
||||||
|
@ -15,7 +14,6 @@ from esphome import external_files
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
__version__,
|
|
||||||
CONF_DITHER,
|
CONF_DITHER,
|
||||||
CONF_FILE,
|
CONF_FILE,
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
|
@ -75,31 +73,6 @@ def compute_local_image_path(value: dict) -> Path:
|
||||||
return base_dir / key
|
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):
|
def download_mdi(value):
|
||||||
validate_cairosvg_installed(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"
|
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
|
return value
|
||||||
|
|
||||||
|
@ -117,7 +90,7 @@ def download_image(value):
|
||||||
url = value[CONF_URL]
|
url = value[CONF_URL]
|
||||||
path = compute_local_image_path(value)
|
path = compute_local_image_path(value)
|
||||||
|
|
||||||
download_content(url, path)
|
external_files.download_content(url, path, IMAGE_DOWNLOAD_TIMEOUT)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
|
#ifdef USE_LOGGER_USB_CDC
|
||||||
case logger::UART_SELECTION_USB_CDC:
|
case logger::UART_SELECTION_USB_CDC:
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||||
if (esp_usb_console_available_for_read()) {
|
if (esp_usb_console_available_for_read()) {
|
||||||
|
@ -68,15 +68,15 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
|
||||||
byte = data;
|
byte = data;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
#endif // USE_LOGGER_USB_CDC
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||||
case logger::UART_SELECTION_USB_SERIAL_JTAG: {
|
case logger::UART_SELECTION_USB_SERIAL_JTAG: {
|
||||||
if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
|
if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
|
||||||
byte = data;
|
byte = data;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3
|
#endif // USE_LOGGER_USB_SERIAL_JTAG
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -99,19 +99,19 @@ void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
|
||||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
|
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
|
||||||
uart_write_bytes(this->uart_num_, data.data(), data.size());
|
uart_write_bytes(this->uart_num_, data.data(), data.size());
|
||||||
break;
|
break;
|
||||||
#if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
|
#ifdef USE_LOGGER_USB_CDC
|
||||||
case logger::UART_SELECTION_USB_CDC: {
|
case logger::UART_SELECTION_USB_CDC: {
|
||||||
const char *msg = (char *) data.data();
|
const char *msg = (char *) data.data();
|
||||||
esp_usb_console_write_buf(msg, data.size());
|
esp_usb_console_write_buf(msg, data.size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
#endif // USE_LOGGER_USB_CDC
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||||
case logger::UART_SELECTION_USB_SERIAL_JTAG:
|
case logger::UART_SELECTION_USB_SERIAL_JTAG:
|
||||||
usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
|
usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
|
||||||
usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
|
usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
|
||||||
break;
|
break;
|
||||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
#endif // USE_LOGGER_USB_SERIAL_JTAG
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,9 @@ def mdns_service(
|
||||||
|
|
||||||
@coroutine_with_priority(55.0)
|
@coroutine_with_priority(55.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
if config[CONF_DISABLED] is True:
|
||||||
|
return
|
||||||
|
|
||||||
if CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
cg.add_library("ESPmDNS", None)
|
cg.add_library("ESPmDNS", None)
|
||||||
|
@ -92,9 +95,6 @@ async def to_code(config):
|
||||||
path="components/mdns",
|
path="components/mdns",
|
||||||
)
|
)
|
||||||
|
|
||||||
if config[CONF_DISABLED]:
|
|
||||||
return
|
|
||||||
|
|
||||||
cg.add_define("USE_MDNS")
|
cg.add_define("USE_MDNS")
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "mdns_component.h"
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_MDNS
|
||||||
|
#include "mdns_component.h"
|
||||||
#include "esphome/core/version.h"
|
#include "esphome/core/version.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
@ -125,3 +126,4 @@ void MDNSComponent::dump_config() {
|
||||||
|
|
||||||
} // namespace mdns
|
} // namespace mdns
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_MDNS
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
@ -46,3 +47,4 @@ class MDNSComponent : public Component {
|
||||||
|
|
||||||
} // namespace mdns
|
} // namespace mdns
|
||||||
} // namespace esphome
|
} // 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 <mdns.h>
|
||||||
#include <cstring>
|
#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 <ESP8266mDNS.h>
|
||||||
#include "esphome/components/network/ip_address.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/ip_address.h"
|
||||||
#include "esphome/components/network/util.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/ip_address.h"
|
||||||
#include "esphome/components/network/util.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/ip_address.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
|
|
|
@ -9,7 +9,7 @@ import requests
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
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.components import esp32, microphone
|
||||||
from esphome import automation, git, external_files
|
from esphome import automation, git, external_files
|
||||||
|
@ -41,9 +41,15 @@ CODEOWNERS = ["@kahrendt", "@jesserockz"]
|
||||||
DEPENDENCIES = ["microphone"]
|
DEPENDENCIES = ["microphone"]
|
||||||
DOMAIN = "micro_wake_word"
|
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_PROBABILITY_CUTOFF = "probability_cutoff"
|
||||||
CONF_SLIDING_WINDOW_AVERAGE_SIZE = "sliding_window_average_size"
|
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"
|
TYPE_HTTP = "http"
|
||||||
|
|
||||||
|
@ -98,12 +104,14 @@ GIT_SCHEMA = cv.All(
|
||||||
_process_git_source,
|
_process_git_source,
|
||||||
)
|
)
|
||||||
|
|
||||||
KEY_WAKE_WORD = "wake_word"
|
|
||||||
KEY_AUTHOR = "author"
|
KEY_AUTHOR = "author"
|
||||||
KEY_WEBSITE = "website"
|
|
||||||
KEY_VERSION = "version"
|
|
||||||
KEY_MICRO = "micro"
|
KEY_MICRO = "micro"
|
||||||
KEY_MINIMUM_ESPHOME_VERSION = "minimum_esphome_version"
|
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(
|
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:
|
def _compute_local_file_path(config: dict) -> Path:
|
||||||
url = config[CONF_URL]
|
url = config[CONF_URL]
|
||||||
|
@ -135,6 +166,24 @@ def _compute_local_file_path(config: dict) -> Path:
|
||||||
return base_dir / key
|
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:
|
def _download_file(url: str, path: Path) -> bytes:
|
||||||
if not external_files.has_remote_file_changed(url, path):
|
if not external_files.has_remote_file_changed(url, path):
|
||||||
_LOGGER.debug("Remote file has not changed, skipping download")
|
_LOGGER.debug("Remote file has not changed, skipping download")
|
||||||
|
@ -155,6 +204,24 @@ def _download_file(url: str, path: Path) -> bytes:
|
||||||
return req.content
|
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):
|
def _process_http_source(config):
|
||||||
url = config[CONF_URL]
|
url = config[CONF_URL]
|
||||||
path = _compute_local_file_path(config)
|
path = _compute_local_file_path(config)
|
||||||
|
@ -167,11 +234,6 @@ def _process_http_source(config):
|
||||||
if not isinstance(manifest_data, dict):
|
if not isinstance(manifest_data, dict):
|
||||||
raise cv.Invalid("Manifest file must contain a JSON object")
|
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 = manifest_data[CONF_MODEL]
|
||||||
model_url = urljoin(url, model)
|
model_url = urljoin(url, model)
|
||||||
|
|
||||||
|
@ -206,7 +268,7 @@ def _validate_source_model_name(value):
|
||||||
return MODEL_SOURCE_SCHEMA(
|
return MODEL_SOURCE_SCHEMA(
|
||||||
{
|
{
|
||||||
CONF_TYPE: TYPE_HTTP,
|
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,55 @@ MODEL_SOURCE_SCHEMA = cv.Any(
|
||||||
msg="Not a valid model name, local path, http(s) url, or github shorthand",
|
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(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(MicroWakeWord),
|
cv.GenerateID(): cv.declare_id(MicroWakeWord),
|
||||||
cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone),
|
cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone),
|
||||||
cv.Optional(CONF_PROBABILITY_CUTOFF): cv.percentage,
|
cv.Required(CONF_MODELS): cv.ensure_list(MODEL_SCHEMA),
|
||||||
cv.Optional(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int,
|
|
||||||
cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation(
|
cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
cv.Required(CONF_MODEL): MODEL_SOURCE_SCHEMA,
|
cv.Optional(CONF_VAD): _maybe_empty_vad_schema,
|
||||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
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),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_with_esp_idf,
|
cv.only_with_esp_idf,
|
||||||
|
@ -282,44 +381,20 @@ def _load_model_data(manifest_path: Path):
|
||||||
with open(manifest_path, encoding="utf-8") as f:
|
with open(manifest_path, encoding="utf-8") as f:
|
||||||
manifest = json.load(f)
|
manifest = json.load(f)
|
||||||
|
|
||||||
try:
|
_validate_manifest_version(manifest)
|
||||||
MANIFEST_SCHEMA_V1(manifest)
|
|
||||||
except cv.Invalid as e:
|
|
||||||
raise EsphomeError(f"Invalid manifest file: {e}") from e
|
|
||||||
|
|
||||||
model_path = manifest_path.parent / manifest[CONF_MODEL]
|
model_path = manifest_path.parent / manifest[CONF_MODEL]
|
||||||
|
|
||||||
with open(model_path, "rb") as f:
|
with open(model_path, "rb") as f:
|
||||||
model = f.read()
|
model = f.read()
|
||||||
|
|
||||||
|
if manifest.get(KEY_VERSION) == 1:
|
||||||
|
manifest = _convert_manifest_v1_to_v2(manifest)
|
||||||
|
|
||||||
return manifest, model
|
return manifest, model
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
def _model_config_to_manifest_data(model_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 = []
|
|
||||||
if model_config[CONF_TYPE] == TYPE_GIT:
|
if model_config[CONF_TYPE] == TYPE_GIT:
|
||||||
# compute path to model file
|
# compute path to model file
|
||||||
key = f"{model_config[CONF_URL]}@{model_config.get(CONF_REF)}"
|
key = f"{model_config[CONF_URL]}@{model_config.get(CONF_REF)}"
|
||||||
|
@ -337,23 +412,95 @@ async def to_code(config):
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unsupported config type: {model_config[CONF_TYPE]}")
|
raise ValueError("Unsupported config type: {model_config[CONF_TYPE]}")
|
||||||
|
|
||||||
manifest, data = _load_model_data(file)
|
return _load_model_data(file)
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
probability_cutoff = config.get(
|
def _feature_step_size_validate(config):
|
||||||
CONF_PROBABILITY_CUTOFF, manifest[KEY_MICRO][CONF_PROBABILITY_CUTOFF]
|
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(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],
|
|
||||||
)
|
|
||||||
cg.add(var.set_sliding_window_average_size(sliding_window_average_size))
|
|
||||||
|
|
||||||
cg.add(var.set_wake_word(manifest[KEY_WAKE_WORD]))
|
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(model_parameters[CONF_RAW_DATA_ID], rhs)
|
||||||
|
|
||||||
|
probability_cutoff = model_parameters.get(
|
||||||
|
CONF_PROBABILITY_CUTOFF, manifest[KEY_MICRO][CONF_PROBABILITY_CUTOFF]
|
||||||
|
)
|
||||||
|
sliding_window_size = model_parameters.get(
|
||||||
|
CONF_SLIDING_WINDOW_SIZE,
|
||||||
|
manifest[KEY_MICRO][CONF_SLIDING_WINDOW_SIZE],
|
||||||
|
)
|
||||||
|
|
||||||
|
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)})
|
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"
|
#include "micro_wake_word.h"
|
||||||
|
#include "streaming_model.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
|
|
||||||
|
|
||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
|
|
||||||
|
@ -14,13 +7,13 @@
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.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/core/c/common.h>
|
||||||
#include <tensorflow/lite/micro/micro_interpreter.h>
|
#include <tensorflow/lite/micro/micro_interpreter.h>
|
||||||
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
|
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
|
||||||
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -29,9 +22,9 @@ namespace micro_wake_word {
|
||||||
static const char *const TAG = "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 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 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; }
|
float MicroWakeWord::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
||||||
|
|
||||||
|
@ -56,58 +49,56 @@ static const LogString *micro_wake_word_state_to_string(State state) {
|
||||||
|
|
||||||
void MicroWakeWord::dump_config() {
|
void MicroWakeWord::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "microWakeWord:");
|
ESP_LOGCONFIG(TAG, "microWakeWord:");
|
||||||
ESP_LOGCONFIG(TAG, " Wake Word: %s", this->get_wake_word().c_str());
|
ESP_LOGCONFIG(TAG, " models:");
|
||||||
ESP_LOGCONFIG(TAG, " Probability cutoff: %.3f", this->probability_cutoff_);
|
for (auto &model : this->wake_word_models_) {
|
||||||
ESP_LOGCONFIG(TAG, " Sliding window size: %d", this->sliding_window_average_size_);
|
model.log_model_config();
|
||||||
|
}
|
||||||
|
#ifdef USE_MICRO_WAKE_WORD_VAD
|
||||||
|
this->vad_model_->log_model_config();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroWakeWord::setup() {
|
void MicroWakeWord::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up microWakeWord...");
|
ESP_LOGCONFIG(TAG, "Setting up microWakeWord...");
|
||||||
|
|
||||||
if (!this->initialize_models()) {
|
if (!this->register_streaming_ops_(this->streaming_op_resolver_)) {
|
||||||
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");
|
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, "Micro Wake Word initialized");
|
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_() {
|
void MicroWakeWord::add_wake_word_model(const uint8_t *model_start, float probability_cutoff,
|
||||||
size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
|
size_t sliding_window_average_size, const std::string &wake_word,
|
||||||
if (bytes_read == 0) {
|
size_t tensor_arena_size) {
|
||||||
return 0;
|
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() {
|
void MicroWakeWord::loop() {
|
||||||
switch (this->state_) {
|
switch (this->state_) {
|
||||||
case State::IDLE:
|
case State::IDLE:
|
||||||
|
@ -124,9 +115,12 @@ void MicroWakeWord::loop() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case State::DETECTING_WAKE_WORD:
|
case State::DETECTING_WAKE_WORD:
|
||||||
this->read_microphone_();
|
while (!this->has_enough_samples_()) {
|
||||||
if (this->detect_wake_word_()) {
|
this->read_microphone_();
|
||||||
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->detected_ = true;
|
||||||
this->set_state_(State::STOP_MICROPHONE);
|
this->set_state_(State::STOP_MICROPHONE);
|
||||||
}
|
}
|
||||||
|
@ -136,13 +130,16 @@ void MicroWakeWord::loop() {
|
||||||
this->microphone_->stop();
|
this->microphone_->stop();
|
||||||
this->set_state_(State::STOPPING_MICROPHONE);
|
this->set_state_(State::STOPPING_MICROPHONE);
|
||||||
this->high_freq_.stop();
|
this->high_freq_.stop();
|
||||||
|
this->unload_models_();
|
||||||
|
this->deallocate_buffers_();
|
||||||
break;
|
break;
|
||||||
case State::STOPPING_MICROPHONE:
|
case State::STOPPING_MICROPHONE:
|
||||||
if (this->microphone_->is_stopped()) {
|
if (this->microphone_->is_stopped()) {
|
||||||
this->set_state_(State::IDLE);
|
this->set_state_(State::IDLE);
|
||||||
if (this->detected_) {
|
if (this->detected_) {
|
||||||
|
this->wake_word_detected_trigger_->trigger(this->detected_wake_word_);
|
||||||
this->detected_ = false;
|
this->detected_ = false;
|
||||||
this->wake_word_detected_trigger_->trigger(this->wake_word_);
|
this->detected_wake_word_ = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -150,14 +147,34 @@ void MicroWakeWord::loop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroWakeWord::start() {
|
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()) {
|
if (this->is_failed()) {
|
||||||
ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs");
|
ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs");
|
||||||
return;
|
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) {
|
if (this->state_ != State::IDLE) {
|
||||||
ESP_LOGW(TAG, "Wake word is already running");
|
ESP_LOGW(TAG, "Wake word is already running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->reset_states_();
|
||||||
this->set_state_(State::START_MICROPHONE);
|
this->set_state_(State::START_MICROPHONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,289 +196,218 @@ void MicroWakeWord::set_state_(State state) {
|
||||||
this->state_ = state;
|
this->state_ = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MicroWakeWord::initialize_models() {
|
size_t MicroWakeWord::read_microphone_() {
|
||||||
ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
|
||||||
ExternalRAMAllocator<int8_t> features_allocator(ExternalRAMAllocator<int8_t>::ALLOW_FAILURE);
|
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);
|
ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
|
||||||
|
|
||||||
this->streaming_tensor_arena_ = arena_allocator.allocate(STREAMING_MODEL_ARENA_SIZE);
|
if (this->input_buffer_ == nullptr) {
|
||||||
if (this->streaming_tensor_arena_ == nullptr) {
|
this->input_buffer_ = audio_samples_allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t));
|
||||||
ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena.");
|
if (this->input_buffer_ == nullptr) {
|
||||||
return false;
|
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) {
|
if (this->preprocessor_audio_buffer_ == nullptr) {
|
||||||
ESP_LOGE(TAG, "Could not allocate the audio preprocessor's buffer.");
|
this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(this->new_samples_to_get_());
|
||||||
return false;
|
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->ring_buffer_ == nullptr) {
|
||||||
if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) {
|
this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t));
|
||||||
ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported");
|
if (this->ring_buffer_ == nullptr) {
|
||||||
return false;
|
ESP_LOGE(TAG, "Could not allocate ring buffer");
|
||||||
}
|
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");
|
|
||||||
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float MicroWakeWord::perform_streaming_inference_() {
|
void MicroWakeWord::deallocate_buffers_() {
|
||||||
TfLiteTensor *input = this->streaming_interpreter_->input(0);
|
ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
|
||||||
|
audio_samples_allocator.deallocate(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
|
||||||
size_t bytes_to_copy = input->bytes;
|
this->input_buffer_ = nullptr;
|
||||||
|
audio_samples_allocator.deallocate(this->preprocessor_audio_buffer_, this->new_samples_to_get_());
|
||||||
memcpy((void *) (tflite::GetTensorData<int8_t>(input)), (const void *) (this->new_features_data_), bytes_to_copy);
|
this->preprocessor_audio_buffer_ = nullptr;
|
||||||
|
|
||||||
uint32_t prior_invoke = millis();
|
|
||||||
|
|
||||||
TfLiteStatus invoke_status = this->streaming_interpreter_->Invoke();
|
|
||||||
if (invoke_status != kTfLiteOk) {
|
|
||||||
ESP_LOGW(TAG, "Streaming Interpreter Invoke failed");
|
|
||||||
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_() {
|
bool MicroWakeWord::load_models_() {
|
||||||
// Preprocess the newest audio samples into features
|
// Setup preprocesor feature generator
|
||||||
if (!this->update_features_()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform inference
|
// Setup streaming models
|
||||||
float streaming_prob = this->perform_streaming_inference_();
|
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
|
||||||
|
|
||||||
// Add the most recent probability to the sliding window
|
return true;
|
||||||
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;
|
void MicroWakeWord::unload_models_() {
|
||||||
for (auto &prob : this->recent_streaming_probabilities_) {
|
FrontendFreeStateContents(&this->frontend_state_);
|
||||||
sum += prob;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
float sliding_window_average = sum / static_cast<float>(this->sliding_window_average_size_);
|
// Increase the counter since the last positive detection
|
||||||
|
|
||||||
// Ensure we have enough samples since the last positive detection
|
|
||||||
this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
|
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) {
|
if (this->ignore_windows_ < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect the wake word if the sliding window average is above the cutoff
|
#ifdef USE_MICRO_WAKE_WORD_VAD
|
||||||
if (sliding_window_average > this->probability_cutoff_) {
|
bool vad_state = this->vad_model_->determine_detected();
|
||||||
this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION;
|
#endif
|
||||||
for (auto &prob : this->recent_streaming_probabilities_) {
|
|
||||||
prob = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Wake word sliding average probability is %.3f and most recent probability is %.3f",
|
for (auto &model : this->wake_word_models_) {
|
||||||
sliding_window_average, streaming_prob);
|
if (model.determine_detected()) {
|
||||||
return true;
|
#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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroWakeWord::set_sliding_window_average_size(size_t size) {
|
bool MicroWakeWord::has_enough_samples_() {
|
||||||
this->sliding_window_average_size_ = size;
|
return this->ring_buffer_->available() >=
|
||||||
this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0);
|
(this->features_step_size_ * (AUDIO_SAMPLE_FREQUENCY / 1000)) * sizeof(int16_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MicroWakeWord::slice_available_() {
|
bool MicroWakeWord::generate_features_for_window_(int8_t features[PREPROCESSOR_FEATURE_SIZE]) {
|
||||||
size_t available = this->ring_buffer_->available();
|
// Ensure we have enough new audio samples in the ring buffer for a full window
|
||||||
|
if (!this->has_enough_samples_()) {
|
||||||
return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) {
|
|
||||||
if (!this->slice_available_()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer to the start of the audio buffer
|
size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_),
|
||||||
memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET),
|
this->new_samples_to_get_() * sizeof(int16_t), pdMS_TO_TICKS(200));
|
||||||
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));
|
|
||||||
|
|
||||||
if (bytes_read == 0) {
|
if (bytes_read == 0) {
|
||||||
ESP_LOGE(TAG, "Could not read data from Ring Buffer");
|
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, "Partial Read of Data by Model");
|
||||||
ESP_LOGD(TAG, "Could only read %d bytes when required %d bytes ", bytes_read,
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*audio_samples = this->preprocessor_audio_buffer_;
|
size_t num_samples_read;
|
||||||
return true;
|
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,
|
for (size_t i = 0; i < frontend_output.size; ++i) {
|
||||||
int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]) {
|
// These scaling values are set to match the TFLite audio frontend int8 output.
|
||||||
TfLiteTensor *input = this->preprocessor_interperter_->input(0);
|
// The feature pipeline outputs 16-bit signed integers in roughly a 0 to 670
|
||||||
TfLiteTensor *output = this->preprocessor_interperter_->output(0);
|
// range. In training, these are then arbitrarily divided by 25.6 to get
|
||||||
std::copy_n(audio_data, audio_data_size, tflite::GetTensorData<int16_t>(input));
|
// 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
|
||||||
if (this->preprocessor_interperter_->Invoke() != kTfLiteOk) {
|
// generators.
|
||||||
ESP_LOGE(TAG, "Failed to preprocess audio for local wake word.");
|
// The process is then further complicated when we quantize the model. This
|
||||||
return false;
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver) {
|
void MicroWakeWord::reset_states_() {
|
||||||
if (op_resolver.AddReshape() != kTfLiteOk)
|
ESP_LOGD(TAG, "Resetting buffers and probabilities");
|
||||||
return false;
|
this->ring_buffer_->reset();
|
||||||
if (op_resolver.AddCast() != kTfLiteOk)
|
this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION;
|
||||||
return false;
|
for (auto &model : this->wake_word_models_) {
|
||||||
if (op_resolver.AddStridedSlice() != kTfLiteOk)
|
model.reset_probabilities();
|
||||||
return false;
|
}
|
||||||
if (op_resolver.AddConcatenation() != kTfLiteOk)
|
#ifdef USE_MICRO_WAKE_WORD_VAD
|
||||||
return false;
|
this->vad_model_->reset_probabilities();
|
||||||
if (op_resolver.AddMul() != kTfLiteOk)
|
#endif
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
if (op_resolver.AddCallOnce() != kTfLiteOk)
|
||||||
return false;
|
return false;
|
||||||
if (op_resolver.AddVarHandle() != kTfLiteOk)
|
if (op_resolver.AddVarHandle() != kTfLiteOk)
|
||||||
|
@ -496,6 +442,12 @@ bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &
|
||||||
return false;
|
return false;
|
||||||
if (op_resolver.AddMaxPool2D() != kTfLiteOk)
|
if (op_resolver.AddMaxPool2D() != kTfLiteOk)
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -504,5 +456,3 @@ bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP_IDF
|
#endif // USE_ESP_IDF
|
||||||
|
|
||||||
#endif // CLANG_TIDY
|
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
#pragma once
|
#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
|
#ifdef USE_ESP_IDF
|
||||||
|
|
||||||
|
#include "preprocessor_settings.h"
|
||||||
|
#include "streaming_model.h"
|
||||||
|
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/ring_buffer.h"
|
#include "esphome/core/ring_buffer.h"
|
||||||
|
|
||||||
#include "esphome/components/microphone/microphone.h"
|
#include "esphome/components/microphone/microphone.h"
|
||||||
|
|
||||||
|
#include <frontend_util.h>
|
||||||
|
|
||||||
#include <tensorflow/lite/core/c/common.h>
|
#include <tensorflow/lite/core/c/common.h>
|
||||||
#include <tensorflow/lite/micro/micro_interpreter.h>
|
#include <tensorflow/lite/micro/micro_interpreter.h>
|
||||||
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
|
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
|
||||||
|
@ -23,35 +20,6 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace micro_wake_word {
|
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 {
|
enum State {
|
||||||
IDLE,
|
IDLE,
|
||||||
START_MICROPHONE,
|
START_MICROPHONE,
|
||||||
|
@ -61,6 +29,9 @@ enum State {
|
||||||
STOPPING_MICROPHONE,
|
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 {
|
class MicroWakeWord : public Component {
|
||||||
public:
|
public:
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
@ -73,28 +44,21 @@ class MicroWakeWord : public Component {
|
||||||
|
|
||||||
bool is_running() const { return this->state_ != State::IDLE; }
|
bool is_running() const { return this->state_ != State::IDLE; }
|
||||||
|
|
||||||
bool initialize_models();
|
void set_features_step_size(uint8_t step_size) { this->features_step_size_ = step_size; }
|
||||||
|
|
||||||
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_microphone(microphone::Microphone *microphone) { this->microphone_ = microphone; }
|
void set_microphone(microphone::Microphone *microphone) { this->microphone_ = microphone; }
|
||||||
|
|
||||||
Trigger<std::string> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; }
|
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 add_wake_word_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size,
|
||||||
void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
|
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:
|
protected:
|
||||||
void set_state_(State state);
|
|
||||||
int read_microphone_();
|
|
||||||
|
|
||||||
const uint8_t *model_start_;
|
|
||||||
std::string wake_word_;
|
|
||||||
|
|
||||||
microphone::Microphone *microphone_{nullptr};
|
microphone::Microphone *microphone_{nullptr};
|
||||||
Trigger<std::string> *wake_word_detected_trigger_ = new Trigger<std::string>();
|
Trigger<std::string> *wake_word_detected_trigger_ = new Trigger<std::string>();
|
||||||
State state_{State::IDLE};
|
State state_{State::IDLE};
|
||||||
|
@ -102,85 +66,93 @@ class MicroWakeWord : public Component {
|
||||||
|
|
||||||
std::unique_ptr<RingBuffer> ring_buffer_;
|
std::unique_ptr<RingBuffer> ring_buffer_;
|
||||||
|
|
||||||
int16_t *input_buffer_;
|
std::vector<WakeWordModel> wake_word_models_;
|
||||||
|
|
||||||
const tflite::Model *preprocessor_model_{nullptr};
|
#ifdef USE_MICRO_WAKE_WORD_VAD
|
||||||
const tflite::Model *streaming_model_{nullptr};
|
std::unique_ptr<VADModel> vad_model_;
|
||||||
tflite::MicroInterpreter *streaming_interpreter_{nullptr};
|
#endif
|
||||||
tflite::MicroInterpreter *preprocessor_interperter_{nullptr};
|
|
||||||
|
|
||||||
std::vector<float> recent_streaming_probabilities_;
|
tflite::MicroMutableOpResolver<20> streaming_op_resolver_;
|
||||||
size_t last_n_index_{0};
|
|
||||||
|
|
||||||
float probability_cutoff_{0.5};
|
// Audio frontend handles generating spectrogram features
|
||||||
size_t sliding_window_average_size_{10};
|
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
|
// When the wake word detection first starts, we ignore this many audio
|
||||||
// feature slices before accepting a positive detection again
|
// feature slices before accepting a positive detection
|
||||||
int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION};
|
int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION};
|
||||||
|
|
||||||
uint8_t *streaming_var_arena_{nullptr};
|
uint8_t features_step_size_;
|
||||||
uint8_t *streaming_tensor_arena_{nullptr};
|
|
||||||
uint8_t *preprocessor_tensor_arena_{nullptr};
|
|
||||||
int8_t *new_features_data_{nullptr};
|
|
||||||
|
|
||||||
tflite::MicroResourceVariables *mrv_{nullptr};
|
// Stores audio read from the microphone before being added to the ring buffer.
|
||||||
|
int16_t *input_buffer_{nullptr};
|
||||||
// Stores audio fed into feature generator preprocessor
|
// Stores audio to be fed into the audio frontend for generating features.
|
||||||
int16_t *preprocessor_audio_buffer_;
|
int16_t *preprocessor_audio_buffer_{nullptr};
|
||||||
|
|
||||||
bool detected_{false};
|
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 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.
|
* It then loops through and performs inference with each of the loaded models.
|
||||||
* @param ring_Buffer Ring buffer containing raw audio samples
|
|
||||||
* @return True if the wake word is detected, false otherwise
|
|
||||||
*/
|
*/
|
||||||
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
|
/** Checks every model's recent probabilities to determine if the wake word has been predicted
|
||||||
bool slice_available_();
|
|
||||||
|
|
||||||
/** Shifts previous feature slices over by one and generates a new slice of features
|
|
||||||
*
|
*
|
||||||
* @param ring_buffer ring buffer containing raw audio samples
|
* Verifies the models have processed enough new samples for accurate predictions.
|
||||||
* @return True if a new slice of features was generated, false otherwise
|
* 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
|
* Reads samples from the ring buffer and feeds them into the preprocessor frontend.
|
||||||
* @param audio_data Pointer to array with the audio samples
|
* Adapted from TFLite microspeech frontend.
|
||||||
* @param audio_data_size The number of samples to use as input to the preprocessor model
|
* @param features int8_t array to store the audio features
|
||||||
* @param feature_output Array that will store the features
|
|
||||||
* @return True if successful, false otherwise.
|
* @return True if successful, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool generate_single_feature_(const int16_t *audio_data, int audio_data_size,
|
bool generate_features_for_window_(int8_t features[PREPROCESSOR_FEATURE_SIZE]);
|
||||||
int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]);
|
|
||||||
|
|
||||||
/** Performs inference over the most recent feature slice with the streaming model
|
/// @brief Resets the ring buffer, ignore_windows_, and sliding window probabilities
|
||||||
*
|
void reset_states_();
|
||||||
* @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 Returns true if successfully registered the streaming model's TensorFlow operations
|
/// @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> {
|
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
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP_IDF
|
#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
|
|
@ -37,6 +37,7 @@ RAW_ENCODING = {
|
||||||
"NONE": RawEncoding.NONE,
|
"NONE": RawEncoding.NONE,
|
||||||
"HEXBYTES": RawEncoding.HEXBYTES,
|
"HEXBYTES": RawEncoding.HEXBYTES,
|
||||||
"COMMA": RawEncoding.COMMA,
|
"COMMA": RawEncoding.COMMA,
|
||||||
|
"ANSI": RawEncoding.ANSI,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
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_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
||||||
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
||||||
cv.Optional(CONF_RESPONSE_SIZE, default=2): 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,
|
validate_modbus_register,
|
||||||
|
|
|
@ -27,8 +27,11 @@ void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||||
sprintf(buffer, index != this->offset ? ",%d" : "%d", b);
|
sprintf(buffer, index != this->offset ? ",%d" : "%d", b);
|
||||||
output << buffer;
|
output << buffer;
|
||||||
break;
|
break;
|
||||||
|
case RawEncoding::ANSI:
|
||||||
|
if (b < 0x20)
|
||||||
|
break;
|
||||||
|
// FALLTHROUGH
|
||||||
// Anything else no encoding
|
// Anything else no encoding
|
||||||
case RawEncoding::NONE:
|
|
||||||
default:
|
default:
|
||||||
output << (char) b;
|
output << (char) b;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace modbus_controller {
|
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 {
|
class ModbusTextSensor : public Component, public text_sensor::TextSensor, public SensorItem {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -26,6 +26,7 @@ from esphome.const import (
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
|
DEVICE_CLASS_CONDUCTIVITY,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
DEVICE_CLASS_DATA_RATE,
|
DEVICE_CLASS_DATA_RATE,
|
||||||
DEVICE_CLASS_DATA_SIZE,
|
DEVICE_CLASS_DATA_SIZE,
|
||||||
|
@ -82,6 +83,7 @@ DEVICE_CLASSES = [
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
|
DEVICE_CLASS_CONDUCTIVITY,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
DEVICE_CLASS_DATA_RATE,
|
DEVICE_CLASS_DATA_RATE,
|
||||||
DEVICE_CLASS_DATA_SIZE,
|
DEVICE_CLASS_DATA_SIZE,
|
||||||
|
|
|
@ -42,6 +42,14 @@ COLOR_ORDERS = {
|
||||||
}
|
}
|
||||||
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
|
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(
|
CONFIG_SCHEMA = cv.All(
|
||||||
display.FULL_DISPLAY_SCHEMA.extend(
|
display.FULL_DISPLAY_SCHEMA.extend(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
|
@ -52,10 +60,14 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.dimensions,
|
cv.dimensions,
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_WIDTH): cv.int_,
|
cv.Required(CONF_WIDTH): validate_dimension,
|
||||||
cv.Required(CONF_HEIGHT): cv.int_,
|
cv.Required(CONF_HEIGHT): validate_dimension,
|
||||||
cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_,
|
cv.Optional(
|
||||||
cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_,
|
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() {
|
void QspiAmoLed::update() {
|
||||||
|
if (!this->setup_complete_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this->do_update_();
|
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 w = this->x_high_ - this->x_low_ + 1;
|
||||||
int h = this->y_high_ - this->y_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,
|
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_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
|
||||||
void set_enable_pin(GPIOPin *enable_pin) { this->enable_pin_ = enable_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) {
|
void set_dimensions(uint16_t width, uint16_t height) {
|
||||||
this->width_ = width;
|
this->width_ = width;
|
||||||
this->height_ = height;
|
this->height_ = height;
|
||||||
}
|
}
|
||||||
int get_width() override { return this->width_; }
|
|
||||||
int get_height() override { return this->height_; }
|
|
||||||
void set_invert_colors(bool invert_colors) {
|
void set_invert_colors(bool invert_colors) {
|
||||||
this->invert_colors_ = invert_colors;
|
this->invert_colors_ = invert_colors;
|
||||||
this->reset_params_();
|
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_HIGH_US = 5000;
|
||||||
static const uint32_t HEADER_LOW_US = 1500;
|
static const uint32_t HEADER_LOW_US = 1500;
|
||||||
static const uint32_t BIT_ZERO_HIGH_US = 750;
|
static const uint32_t BIT_ZERO_HIGH_US = 350;
|
||||||
static const uint32_t BIT_ZERO_LOW_US = 350;
|
static const uint32_t BIT_ZERO_LOW_US = 750;
|
||||||
static const uint32_t BIT_ONE_HIGH_US = 350;
|
static const uint32_t BIT_ONE_HIGH_US = 750;
|
||||||
static const uint32_t BIT_ONE_LOW_US = 750;
|
static const uint32_t BIT_ONE_LOW_US = 350;
|
||||||
|
|
||||||
void DooyaProtocol::encode(RemoteTransmitData *dst, const DooyaData &data) {
|
void DooyaProtocol::encode(RemoteTransmitData *dst, const DooyaData &data) {
|
||||||
dst->set_carrier_frequency(0);
|
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 {
|
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);
|
this->sync(dst);
|
||||||
for (int16_t i = len - 1; i >= 0; i--) {
|
for (int16_t i = len - 1; i >= 0; i--) {
|
||||||
if (code & ((uint64_t) 1 << i)) {
|
if (code & ((uint64_t) 1 << i)) {
|
||||||
|
|
|
@ -88,7 +88,7 @@ def validate_parameter_name(value):
|
||||||
raise cv.Invalid(f"Script's parameter name cannot be {CONF_ID}")
|
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):
|
def validate_parameter_type(value):
|
||||||
|
|
|
@ -43,6 +43,7 @@ from esphome.const import (
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
|
DEVICE_CLASS_CONDUCTIVITY,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
DEVICE_CLASS_DATA_RATE,
|
DEVICE_CLASS_DATA_RATE,
|
||||||
DEVICE_CLASS_DATA_SIZE,
|
DEVICE_CLASS_DATA_SIZE,
|
||||||
|
@ -103,6 +104,7 @@ DEVICE_CLASSES = [
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
|
DEVICE_CLASS_CONDUCTIVITY,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
DEVICE_CLASS_DATA_RATE,
|
DEVICE_CLASS_DATA_RATE,
|
||||||
DEVICE_CLASS_DATA_SIZE,
|
DEVICE_CLASS_DATA_SIZE,
|
||||||
|
|
|
@ -223,13 +223,19 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
break;
|
break;
|
||||||
case TuyaCommandType::DATAPOINT_DELIVER:
|
case TuyaCommandType::DATAPOINT_DELIVER:
|
||||||
break;
|
break;
|
||||||
case TuyaCommandType::DATAPOINT_REPORT:
|
case TuyaCommandType::DATAPOINT_REPORT_ASYNC:
|
||||||
|
case TuyaCommandType::DATAPOINT_REPORT_SYNC:
|
||||||
if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) {
|
if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) {
|
||||||
this->init_state_ = TuyaInitState::INIT_DONE;
|
this->init_state_ = TuyaInitState::INIT_DONE;
|
||||||
this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); });
|
this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); });
|
||||||
this->initialized_callback_.call();
|
this->initialized_callback_.call();
|
||||||
}
|
}
|
||||||
this->handle_datapoints_(buffer, len);
|
this->handle_datapoints_(buffer, len);
|
||||||
|
|
||||||
|
if (command_type == TuyaCommandType::DATAPOINT_REPORT_SYNC) {
|
||||||
|
this->send_command_(
|
||||||
|
TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_REPORT_ACK, .payload = std::vector<uint8_t>{0x01}});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case TuyaCommandType::DATAPOINT_QUERY:
|
case TuyaCommandType::DATAPOINT_QUERY:
|
||||||
break;
|
break;
|
||||||
|
@ -423,7 +429,7 @@ void Tuya::send_raw_command_(TuyaCommand command) {
|
||||||
break;
|
break;
|
||||||
case TuyaCommandType::DATAPOINT_DELIVER:
|
case TuyaCommandType::DATAPOINT_DELIVER:
|
||||||
case TuyaCommandType::DATAPOINT_QUERY:
|
case TuyaCommandType::DATAPOINT_QUERY:
|
||||||
this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT;
|
this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT_ASYNC;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -53,10 +53,12 @@ enum class TuyaCommandType : uint8_t {
|
||||||
WIFI_RESET = 0x04,
|
WIFI_RESET = 0x04,
|
||||||
WIFI_SELECT = 0x05,
|
WIFI_SELECT = 0x05,
|
||||||
DATAPOINT_DELIVER = 0x06,
|
DATAPOINT_DELIVER = 0x06,
|
||||||
DATAPOINT_REPORT = 0x07,
|
DATAPOINT_REPORT_ASYNC = 0x07,
|
||||||
DATAPOINT_QUERY = 0x08,
|
DATAPOINT_QUERY = 0x08,
|
||||||
WIFI_TEST = 0x0E,
|
WIFI_TEST = 0x0E,
|
||||||
LOCAL_TIME_QUERY = 0x1C,
|
LOCAL_TIME_QUERY = 0x1C,
|
||||||
|
DATAPOINT_REPORT_SYNC = 0x22,
|
||||||
|
DATAPOINT_REPORT_ACK = 0x23,
|
||||||
WIFI_RSSI = 0x24,
|
WIFI_RSSI = 0x24,
|
||||||
VACUUM_MAP_UPLOAD = 0x28,
|
VACUUM_MAP_UPLOAD = 0x28,
|
||||||
GET_NETWORK_STATUS = 0x2B,
|
GET_NETWORK_STATUS = 0x2B,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import re
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
|
@ -11,6 +11,7 @@ from esphome.const import (
|
||||||
CONF_NUMBER,
|
CONF_NUMBER,
|
||||||
CONF_RX_PIN,
|
CONF_RX_PIN,
|
||||||
CONF_TX_PIN,
|
CONF_TX_PIN,
|
||||||
|
CONF_PORT,
|
||||||
CONF_UART_ID,
|
CONF_UART_ID,
|
||||||
CONF_DATA,
|
CONF_DATA,
|
||||||
CONF_RX_BUFFER_SIZE,
|
CONF_RX_BUFFER_SIZE,
|
||||||
|
@ -27,6 +28,7 @@ from esphome.const import (
|
||||||
CONF_DUMMY_RECEIVER,
|
CONF_DUMMY_RECEIVER,
|
||||||
CONF_DUMMY_RECEIVER_ID,
|
CONF_DUMMY_RECEIVER_ID,
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
|
PLATFORM_HOST,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
|
@ -45,6 +47,7 @@ RP2040UartComponent = uart_ns.class_("RP2040UartComponent", UARTComponent, cg.Co
|
||||||
LibreTinyUARTComponent = uart_ns.class_(
|
LibreTinyUARTComponent = uart_ns.class_(
|
||||||
"LibreTinyUARTComponent", UARTComponent, cg.Component
|
"LibreTinyUARTComponent", UARTComponent, cg.Component
|
||||||
)
|
)
|
||||||
|
HostUartComponent = uart_ns.class_("HostUartComponent", UARTComponent, cg.Component)
|
||||||
|
|
||||||
NATIVE_UART_CLASSES = (
|
NATIVE_UART_CLASSES = (
|
||||||
str(IDFUARTComponent),
|
str(IDFUARTComponent),
|
||||||
|
@ -54,6 +57,39 @@ NATIVE_UART_CLASSES = (
|
||||||
str(LibreTinyUARTComponent),
|
str(LibreTinyUARTComponent),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
HOST_BAUD_RATES = [
|
||||||
|
50,
|
||||||
|
75,
|
||||||
|
110,
|
||||||
|
134,
|
||||||
|
150,
|
||||||
|
200,
|
||||||
|
300,
|
||||||
|
600,
|
||||||
|
1200,
|
||||||
|
1800,
|
||||||
|
2400,
|
||||||
|
4800,
|
||||||
|
9600,
|
||||||
|
19200,
|
||||||
|
38400,
|
||||||
|
57600,
|
||||||
|
115200,
|
||||||
|
230400,
|
||||||
|
460800,
|
||||||
|
500000,
|
||||||
|
576000,
|
||||||
|
921600,
|
||||||
|
1000000,
|
||||||
|
1152000,
|
||||||
|
1500000,
|
||||||
|
2000000,
|
||||||
|
2500000,
|
||||||
|
3000000,
|
||||||
|
3500000,
|
||||||
|
4000000,
|
||||||
|
]
|
||||||
|
|
||||||
UARTDevice = uart_ns.class_("UARTDevice")
|
UARTDevice = uart_ns.class_("UARTDevice")
|
||||||
UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
|
UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
|
||||||
UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action)
|
UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action)
|
||||||
|
@ -95,6 +131,20 @@ def validate_invert_esp32(config):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def validate_host_config(config):
|
||||||
|
if CORE.is_host:
|
||||||
|
if CONF_TX_PIN in config or CONF_RX_PIN in config:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"TX and RX pins are not supported for UART on host platform."
|
||||||
|
)
|
||||||
|
if config[CONF_BAUD_RATE] not in HOST_BAUD_RATES:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Host platform doesn't support baud rate {config[CONF_BAUD_RATE]}",
|
||||||
|
path=[CONF_BAUD_RATE],
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
def _uart_declare_type(value):
|
def _uart_declare_type(value):
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
return cv.declare_id(ESP8266UartComponent)(value)
|
return cv.declare_id(ESP8266UartComponent)(value)
|
||||||
|
@ -107,6 +157,8 @@ def _uart_declare_type(value):
|
||||||
return cv.declare_id(RP2040UartComponent)(value)
|
return cv.declare_id(RP2040UartComponent)(value)
|
||||||
if CORE.is_libretiny:
|
if CORE.is_libretiny:
|
||||||
return cv.declare_id(LibreTinyUARTComponent)(value)
|
return cv.declare_id(LibreTinyUARTComponent)(value)
|
||||||
|
if CORE.is_host:
|
||||||
|
return cv.declare_id(HostUartComponent)(value)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@ -149,6 +201,12 @@ def maybe_empty_debug(value):
|
||||||
return DEBUG_SCHEMA(value)
|
return DEBUG_SCHEMA(value)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_port(value):
|
||||||
|
if not re.match(r"^/(?:[^/]+/)[^/]+$", value):
|
||||||
|
raise cv.Invalid("Port must be a valid device path")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
DEBUG_SCHEMA = cv.Schema(
|
DEBUG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
|
||||||
|
@ -181,6 +239,7 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Required(CONF_BAUD_RATE): cv.int_range(min=1),
|
cv.Required(CONF_BAUD_RATE): cv.int_range(min=1),
|
||||||
cv.Optional(CONF_TX_PIN): pins.internal_gpio_output_pin_schema,
|
cv.Optional(CONF_TX_PIN): pins.internal_gpio_output_pin_schema,
|
||||||
cv.Optional(CONF_RX_PIN): validate_rx_pin,
|
cv.Optional(CONF_RX_PIN): validate_rx_pin,
|
||||||
|
cv.Optional(CONF_PORT): cv.All(validate_port, cv.only_on(PLATFORM_HOST)),
|
||||||
cv.Optional(CONF_RX_BUFFER_SIZE, default=256): cv.validate_bytes,
|
cv.Optional(CONF_RX_BUFFER_SIZE, default=256): cv.validate_bytes,
|
||||||
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
|
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
|
||||||
cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8),
|
cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8),
|
||||||
|
@ -193,8 +252,9 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_DEBUG): maybe_empty_debug,
|
cv.Optional(CONF_DEBUG): maybe_empty_debug,
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN),
|
cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN, CONF_PORT),
|
||||||
validate_invert_esp32,
|
validate_invert_esp32,
|
||||||
|
validate_host_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -236,6 +296,8 @@ async def to_code(config):
|
||||||
if CONF_RX_PIN in config:
|
if CONF_RX_PIN in config:
|
||||||
rx_pin = await cg.gpio_pin_expression(config[CONF_RX_PIN])
|
rx_pin = await cg.gpio_pin_expression(config[CONF_RX_PIN])
|
||||||
cg.add(var.set_rx_pin(rx_pin))
|
cg.add(var.set_rx_pin(rx_pin))
|
||||||
|
if CONF_PORT in config:
|
||||||
|
cg.add(var.set_name(config[CONF_PORT]))
|
||||||
cg.add(var.set_rx_buffer_size(config[CONF_RX_BUFFER_SIZE]))
|
cg.add(var.set_rx_buffer_size(config[CONF_RX_BUFFER_SIZE]))
|
||||||
cg.add(var.set_stop_bits(config[CONF_STOP_BITS]))
|
cg.add(var.set_stop_bits(config[CONF_STOP_BITS]))
|
||||||
cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
|
cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
|
||||||
|
@ -258,6 +320,7 @@ KEY_UART_DEVICES = "uart_devices"
|
||||||
def final_validate_device_schema(
|
def final_validate_device_schema(
|
||||||
name: str,
|
name: str,
|
||||||
*,
|
*,
|
||||||
|
uart_bus: str = CONF_UART_ID,
|
||||||
baud_rate: Optional[int] = None,
|
baud_rate: Optional[int] = None,
|
||||||
require_tx: bool = False,
|
require_tx: bool = False,
|
||||||
require_rx: bool = False,
|
require_rx: bool = False,
|
||||||
|
@ -268,7 +331,7 @@ def final_validate_device_schema(
|
||||||
def validate_baud_rate(value):
|
def validate_baud_rate(value):
|
||||||
if value != baud_rate:
|
if value != baud_rate:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"Component {name} requires baud rate {baud_rate} for the uart bus"
|
f"Component {name} requires baud rate {baud_rate} for the uart referenced by {uart_bus}"
|
||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -287,21 +350,21 @@ def final_validate_device_schema(
|
||||||
def validate_data_bits(value):
|
def validate_data_bits(value):
|
||||||
if value != data_bits:
|
if value != data_bits:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"Component {name} requires {data_bits} data bits for the uart bus"
|
f"Component {name} requires {data_bits} data bits for the uart referenced by {uart_bus}"
|
||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def validate_parity(value):
|
def validate_parity(value):
|
||||||
if value != parity:
|
if value != parity:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"Component {name} requires parity {parity} for the uart bus"
|
f"Component {name} requires parity {parity} for the uart referenced by {uart_bus}"
|
||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def validate_stop_bits(value):
|
def validate_stop_bits(value):
|
||||||
if value != stop_bits:
|
if value != stop_bits:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"Component {name} requires {stop_bits} stop bits for the uart bus"
|
f"Component {name} requires {stop_bits} stop bits for the uart referenced by {uart_bus}"
|
||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -316,14 +379,14 @@ def final_validate_device_schema(
|
||||||
hub_schema[
|
hub_schema[
|
||||||
cv.Required(
|
cv.Required(
|
||||||
CONF_TX_PIN,
|
CONF_TX_PIN,
|
||||||
msg=f"Component {name} requires this uart bus to declare a tx_pin",
|
msg=f"Component {name} requires uart referenced by {uart_bus} to declare a tx_pin",
|
||||||
)
|
)
|
||||||
] = validate_pin(CONF_TX_PIN, device)
|
] = validate_pin(CONF_TX_PIN, device)
|
||||||
if require_rx and uart_id_type_str in NATIVE_UART_CLASSES:
|
if require_rx and uart_id_type_str in NATIVE_UART_CLASSES:
|
||||||
hub_schema[
|
hub_schema[
|
||||||
cv.Required(
|
cv.Required(
|
||||||
CONF_RX_PIN,
|
CONF_RX_PIN,
|
||||||
msg=f"Component {name} requires this uart bus to declare a rx_pin",
|
msg=f"Component {name} requires uart referenced by {uart_bus} to declare a rx_pin",
|
||||||
)
|
)
|
||||||
] = validate_pin(CONF_RX_PIN, device)
|
] = validate_pin(CONF_RX_PIN, device)
|
||||||
if baud_rate is not None:
|
if baud_rate is not None:
|
||||||
|
@ -337,7 +400,7 @@ def final_validate_device_schema(
|
||||||
return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config)
|
return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config)
|
||||||
|
|
||||||
return cv.Schema(
|
return cv.Schema(
|
||||||
{cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(validate_hub)},
|
{cv.Required(uart_bus): fv.id_declaration_match_schema(validate_hub)},
|
||||||
extra=cv.ALLOW_EXTRA,
|
extra=cv.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -96,10 +96,24 @@ void ESP32ArduinoUARTComponent::setup() {
|
||||||
next_uart_num++;
|
next_uart_num++;
|
||||||
} else {
|
} else {
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
// The logger doesn't use this UART component, instead it targets the UARTs
|
bool logger_uses_hardware_uart = true;
|
||||||
// directly (i.e. Serial/Serial0, Serial1, and Serial2). If the logger is
|
|
||||||
// enabled, skip the UART that it is configured to use.
|
#ifdef USE_LOGGER_USB_CDC
|
||||||
if (logger::global_logger->get_baud_rate() > 0 && logger::global_logger->get_uart() == next_uart_num) {
|
if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_CDC) {
|
||||||
|
// this is not a hardware UART, ignore it
|
||||||
|
logger_uses_hardware_uart = false;
|
||||||
|
}
|
||||||
|
#endif // USE_LOGGER_USB_CDC
|
||||||
|
|
||||||
|
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||||
|
if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_SERIAL_JTAG) {
|
||||||
|
// this is not a hardware UART, ignore it
|
||||||
|
logger_uses_hardware_uart = false;
|
||||||
|
}
|
||||||
|
#endif // USE_LOGGER_USB_SERIAL_JTAG
|
||||||
|
|
||||||
|
if (logger_uses_hardware_uart && logger::global_logger->get_baud_rate() > 0 &&
|
||||||
|
logger::global_logger->get_uart() == next_uart_num) {
|
||||||
next_uart_num++;
|
next_uart_num++;
|
||||||
}
|
}
|
||||||
#endif // USE_LOGGER
|
#endif // USE_LOGGER
|
||||||
|
|
|
@ -60,10 +60,30 @@ uart_config_t IDFUARTComponent::get_config_() {
|
||||||
|
|
||||||
void IDFUARTComponent::setup() {
|
void IDFUARTComponent::setup() {
|
||||||
static uint8_t next_uart_num = 0;
|
static uint8_t next_uart_num = 0;
|
||||||
|
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
if (logger::global_logger->get_uart_num() == next_uart_num)
|
bool logger_uses_hardware_uart = true;
|
||||||
|
|
||||||
|
#ifdef USE_LOGGER_USB_CDC
|
||||||
|
if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_CDC) {
|
||||||
|
// this is not a hardware UART, ignore it
|
||||||
|
logger_uses_hardware_uart = false;
|
||||||
|
}
|
||||||
|
#endif // USE_LOGGER_USB_CDC
|
||||||
|
|
||||||
|
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||||
|
if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_SERIAL_JTAG) {
|
||||||
|
// this is not a hardware UART, ignore it
|
||||||
|
logger_uses_hardware_uart = false;
|
||||||
|
}
|
||||||
|
#endif // USE_LOGGER_USB_SERIAL_JTAG
|
||||||
|
|
||||||
|
if (logger_uses_hardware_uart && logger::global_logger->get_baud_rate() > 0 &&
|
||||||
|
logger::global_logger->get_uart_num() == next_uart_num) {
|
||||||
next_uart_num++;
|
next_uart_num++;
|
||||||
#endif
|
}
|
||||||
|
#endif // USE_LOGGER
|
||||||
|
|
||||||
if (next_uart_num >= UART_NUM_MAX) {
|
if (next_uart_num >= UART_NUM_MAX) {
|
||||||
ESP_LOGW(TAG, "Maximum number of UART components created already.");
|
ESP_LOGW(TAG, "Maximum number of UART components created already.");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
|
295
esphome/components/uart/uart_component_host.cpp
Normal file
295
esphome/components/uart/uart_component_host.cpp
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
#ifdef USE_HOST
|
||||||
|
#include "uart_component_host.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifndef __linux__
|
||||||
|
#error This HostUartComponent implementation is only for Linux
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#ifdef USE_LOGGER
|
||||||
|
#include "esphome/components/logger/logger.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
speed_t get_baud(int baud) {
|
||||||
|
switch (baud) {
|
||||||
|
case 50:
|
||||||
|
return B50;
|
||||||
|
case 75:
|
||||||
|
return B75;
|
||||||
|
case 110:
|
||||||
|
return B110;
|
||||||
|
case 134:
|
||||||
|
return B134;
|
||||||
|
case 150:
|
||||||
|
return B150;
|
||||||
|
case 200:
|
||||||
|
return B200;
|
||||||
|
case 300:
|
||||||
|
return B300;
|
||||||
|
case 600:
|
||||||
|
return B600;
|
||||||
|
case 1200:
|
||||||
|
return B1200;
|
||||||
|
case 1800:
|
||||||
|
return B1800;
|
||||||
|
case 2400:
|
||||||
|
return B2400;
|
||||||
|
case 4800:
|
||||||
|
return B4800;
|
||||||
|
case 9600:
|
||||||
|
return B9600;
|
||||||
|
case 19200:
|
||||||
|
return B19200;
|
||||||
|
case 38400:
|
||||||
|
return B38400;
|
||||||
|
case 57600:
|
||||||
|
return B57600;
|
||||||
|
case 115200:
|
||||||
|
return B115200;
|
||||||
|
case 230400:
|
||||||
|
return B230400;
|
||||||
|
case 460800:
|
||||||
|
return B460800;
|
||||||
|
case 500000:
|
||||||
|
return B500000;
|
||||||
|
case 576000:
|
||||||
|
return B576000;
|
||||||
|
case 921600:
|
||||||
|
return B921600;
|
||||||
|
case 1000000:
|
||||||
|
return B1000000;
|
||||||
|
case 1152000:
|
||||||
|
return B1152000;
|
||||||
|
case 1500000:
|
||||||
|
return B1500000;
|
||||||
|
case 2000000:
|
||||||
|
return B2000000;
|
||||||
|
case 2500000:
|
||||||
|
return B2500000;
|
||||||
|
case 3000000:
|
||||||
|
return B3000000;
|
||||||
|
case 3500000:
|
||||||
|
return B3500000;
|
||||||
|
case 4000000:
|
||||||
|
return B4000000;
|
||||||
|
default:
|
||||||
|
return B0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uart {
|
||||||
|
|
||||||
|
static const char *const TAG = "uart.host";
|
||||||
|
|
||||||
|
HostUartComponent::~HostUartComponent() {
|
||||||
|
if (this->file_descriptor_ != -1) {
|
||||||
|
close(this->file_descriptor_);
|
||||||
|
this->file_descriptor_ = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostUartComponent::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Opening UART port...");
|
||||||
|
speed_t baud = get_baud(this->baud_rate_);
|
||||||
|
if (baud == B0) {
|
||||||
|
ESP_LOGE(TAG, "Unsupported baud rate: %d", this->baud_rate_);
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->file_descriptor_ = ::open(this->port_name_.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
|
||||||
|
if (this->file_descriptor_ == -1) {
|
||||||
|
this->update_error_(strerror(errno));
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fcntl(this->file_descriptor_, F_SETFL, 0);
|
||||||
|
struct termios options;
|
||||||
|
tcgetattr(this->file_descriptor_, &options);
|
||||||
|
options.c_cflag &= ~CRTSCTS;
|
||||||
|
options.c_cflag |= CREAD | CLOCAL;
|
||||||
|
options.c_lflag &= ~ICANON;
|
||||||
|
options.c_lflag &= ~ECHO;
|
||||||
|
options.c_lflag &= ~ECHOE;
|
||||||
|
options.c_lflag &= ~ECHONL;
|
||||||
|
options.c_lflag &= ~ISIG;
|
||||||
|
options.c_iflag &= ~(IXON | IXOFF | IXANY);
|
||||||
|
options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
|
||||||
|
options.c_oflag &= ~OPOST;
|
||||||
|
options.c_oflag &= ~ONLCR;
|
||||||
|
// Set data bits
|
||||||
|
options.c_cflag &= ~CSIZE; // Mask the character size bits
|
||||||
|
switch (this->data_bits_) {
|
||||||
|
case 5:
|
||||||
|
options.c_cflag |= CS5;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
options.c_cflag |= CS6;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
options.c_cflag |= CS7;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
default:
|
||||||
|
options.c_cflag |= CS8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Set parity
|
||||||
|
switch (this->parity_) {
|
||||||
|
case UART_CONFIG_PARITY_NONE:
|
||||||
|
options.c_cflag &= ~PARENB;
|
||||||
|
break;
|
||||||
|
case UART_CONFIG_PARITY_EVEN:
|
||||||
|
options.c_cflag |= PARENB;
|
||||||
|
options.c_cflag &= ~PARODD;
|
||||||
|
break;
|
||||||
|
case UART_CONFIG_PARITY_ODD:
|
||||||
|
options.c_cflag |= PARENB;
|
||||||
|
options.c_cflag |= PARODD;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
// Set stop bits
|
||||||
|
if (this->stop_bits_ == 2) {
|
||||||
|
options.c_cflag |= CSTOPB;
|
||||||
|
} else {
|
||||||
|
options.c_cflag &= ~CSTOPB;
|
||||||
|
}
|
||||||
|
cfsetispeed(&options, baud);
|
||||||
|
cfsetospeed(&options, baud);
|
||||||
|
tcsetattr(this->file_descriptor_, TCSANOW, &options);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostUartComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "UART:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Port: %s", this->port_name_.c_str());
|
||||||
|
if (this->file_descriptor_ == -1) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Port status: Not opened");
|
||||||
|
if (!this->first_error_.empty()) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Error: %s", this->first_error_.c_str());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Port status: opened");
|
||||||
|
ESP_LOGCONFIG(TAG, " Baud Rate: %d", this->baud_rate_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Data Bits: %d", this->data_bits_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Parity: %s",
|
||||||
|
this->parity_ == UART_CONFIG_PARITY_NONE ? "None"
|
||||||
|
: this->parity_ == UART_CONFIG_PARITY_EVEN ? "Even"
|
||||||
|
: "Odd");
|
||||||
|
ESP_LOGCONFIG(TAG, " Stop Bits: %d", this->stop_bits_);
|
||||||
|
this->check_logger_conflict();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostUartComponent::write_array(const uint8_t *data, size_t len) {
|
||||||
|
if (this->file_descriptor_ == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t written = ::write(this->file_descriptor_, data, len);
|
||||||
|
if (written != len) {
|
||||||
|
this->update_error_(strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef USE_UART_DEBUGGER
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostUartComponent::peek_byte(uint8_t *data) {
|
||||||
|
if (this->file_descriptor_ == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this->has_peek_) {
|
||||||
|
if (!this->check_read_timeout_()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (::read(this->file_descriptor_, &this->peek_byte_, 1) != 1) {
|
||||||
|
this->update_error_(strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->has_peek_ = true;
|
||||||
|
}
|
||||||
|
*data = this->peek_byte_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostUartComponent::read_array(uint8_t *data, size_t len) {
|
||||||
|
if ((this->file_descriptor_ == -1) || (len == 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this->check_read_timeout_(len))
|
||||||
|
return false;
|
||||||
|
uint8_t *data_ptr = data;
|
||||||
|
size_t length_to_read = len;
|
||||||
|
if (this->has_peek_) {
|
||||||
|
length_to_read--;
|
||||||
|
*data_ptr = this->peek_byte_;
|
||||||
|
data_ptr++;
|
||||||
|
this->has_peek_ = false;
|
||||||
|
}
|
||||||
|
if (length_to_read > 0) {
|
||||||
|
int sz = ::read(this->file_descriptor_, data_ptr, length_to_read);
|
||||||
|
if (sz == -1) {
|
||||||
|
this->update_error_(strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef USE_UART_DEBUGGER
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HostUartComponent::available() {
|
||||||
|
if (this->file_descriptor_ == -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int available;
|
||||||
|
int res = ioctl(this->file_descriptor_, FIONREAD, &available);
|
||||||
|
if (res == -1) {
|
||||||
|
this->update_error_(strerror(errno));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (this->has_peek_)
|
||||||
|
available++;
|
||||||
|
return available;
|
||||||
|
};
|
||||||
|
|
||||||
|
void HostUartComponent::flush() {
|
||||||
|
if (this->file_descriptor_ == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tcflush(this->file_descriptor_, TCIOFLUSH);
|
||||||
|
ESP_LOGV(TAG, " Flushing...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostUartComponent::update_error_(const std::string &error) {
|
||||||
|
if (this->first_error_.empty()) {
|
||||||
|
this->first_error_ = error;
|
||||||
|
}
|
||||||
|
ESP_LOGE(TAG, "Port error: %s", error.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uart
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_HOST
|
38
esphome/components/uart/uart_component_host.h
Normal file
38
esphome/components/uart/uart_component_host.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_HOST
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "uart_component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uart {
|
||||||
|
|
||||||
|
class HostUartComponent : public UARTComponent, public Component {
|
||||||
|
public:
|
||||||
|
virtual ~HostUartComponent();
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||||
|
void write_array(const uint8_t *data, size_t len) override;
|
||||||
|
bool peek_byte(uint8_t *data) override;
|
||||||
|
bool read_array(uint8_t *data, size_t len) override;
|
||||||
|
int available() override;
|
||||||
|
void flush() override;
|
||||||
|
void set_name(std::string port_name) { port_name_ = port_name; };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void update_error_(const std::string &error);
|
||||||
|
void check_logger_conflict() override {}
|
||||||
|
std::string port_name_;
|
||||||
|
std::string first_error_{""};
|
||||||
|
int file_descriptor_ = -1;
|
||||||
|
bool has_peek_{false};
|
||||||
|
uint8_t peek_byte_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uart
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_HOST
|
|
@ -1,7 +1,9 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor
|
from esphome.components import sensor, time
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_TIME_ID,
|
||||||
|
DEVICE_CLASS_TIMESTAMP,
|
||||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
STATE_CLASS_TOTAL_INCREASING,
|
STATE_CLASS_TOTAL_INCREASING,
|
||||||
UNIT_SECOND,
|
UNIT_SECOND,
|
||||||
|
@ -10,19 +12,50 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
uptime_ns = cg.esphome_ns.namespace("uptime")
|
uptime_ns = cg.esphome_ns.namespace("uptime")
|
||||||
UptimeSensor = uptime_ns.class_("UptimeSensor", sensor.Sensor, cg.PollingComponent)
|
UptimeSecondsSensor = uptime_ns.class_(
|
||||||
|
"UptimeSecondsSensor", sensor.Sensor, cg.PollingComponent
|
||||||
|
)
|
||||||
|
UptimeTimestampSensor = uptime_ns.class_(
|
||||||
|
"UptimeTimestampSensor", sensor.Sensor, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
|
||||||
UptimeSensor,
|
CONFIG_SCHEMA = cv.typed_schema(
|
||||||
unit_of_measurement=UNIT_SECOND,
|
{
|
||||||
icon=ICON_TIMER,
|
"seconds": sensor.sensor_schema(
|
||||||
accuracy_decimals=0,
|
UptimeSecondsSensor,
|
||||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
unit_of_measurement=UNIT_SECOND,
|
||||||
device_class=DEVICE_CLASS_DURATION,
|
icon=ICON_TIMER,
|
||||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
accuracy_decimals=0,
|
||||||
).extend(cv.polling_component_schema("60s"))
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
|
device_class=DEVICE_CLASS_DURATION,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
).extend(cv.polling_component_schema("60s")),
|
||||||
|
"timestamp": sensor.sensor_schema(
|
||||||
|
UptimeTimestampSensor,
|
||||||
|
icon=ICON_TIMER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_TIMESTAMP,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TIME_ID): cv.All(
|
||||||
|
cv.requires_component("time"), cv.use_id(time.RealTimeClock)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
|
},
|
||||||
|
default_type="seconds",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = await sensor.new_sensor(config)
|
var = await sensor.new_sensor(config)
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
if time_id_config := config.get(CONF_TIME_ID):
|
||||||
|
time_id = await cg.get_variable(time_id_config)
|
||||||
|
cg.add(var.set_time(time_id))
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
#include "uptime_sensor.h"
|
#include "uptime_seconds_sensor.h"
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace uptime {
|
namespace uptime {
|
||||||
|
|
||||||
static const char *const TAG = "uptime.sensor";
|
static const char *const TAG = "uptime.sensor";
|
||||||
|
|
||||||
void UptimeSensor::update() {
|
void UptimeSecondsSensor::update() {
|
||||||
const uint32_t ms = millis();
|
const uint32_t ms = millis();
|
||||||
const uint64_t ms_mask = (1ULL << 32) - 1ULL;
|
const uint64_t ms_mask = (1ULL << 32) - 1ULL;
|
||||||
const uint32_t last_ms = this->uptime_ & ms_mask;
|
const uint32_t last_ms = this->uptime_ & ms_mask;
|
||||||
|
@ -26,9 +27,12 @@ void UptimeSensor::update() {
|
||||||
const float seconds = float(seconds_int) + (this->uptime_ % 1000ULL) / 1000.0f;
|
const float seconds = float(seconds_int) + (this->uptime_ % 1000ULL) / 1000.0f;
|
||||||
this->publish_state(seconds);
|
this->publish_state(seconds);
|
||||||
}
|
}
|
||||||
std::string UptimeSensor::unique_id() { return get_mac_address() + "-uptime"; }
|
std::string UptimeSecondsSensor::unique_id() { return get_mac_address() + "-uptime"; }
|
||||||
float UptimeSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float UptimeSecondsSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
void UptimeSensor::dump_config() { LOG_SENSOR("", "Uptime Sensor", this); }
|
void UptimeSecondsSensor::dump_config() {
|
||||||
|
LOG_SENSOR("", "Uptime Sensor", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Type: Seconds");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace uptime
|
} // namespace uptime
|
||||||
} // namespace esphome
|
} // namespace esphome
|
|
@ -1,12 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace uptime {
|
namespace uptime {
|
||||||
|
|
||||||
class UptimeSensor : public sensor::Sensor, public PollingComponent {
|
class UptimeSecondsSensor : public sensor::Sensor, public PollingComponent {
|
||||||
public:
|
public:
|
||||||
void update() override;
|
void update() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
39
esphome/components/uptime/uptime_timestamp_sensor.cpp
Normal file
39
esphome/components/uptime/uptime_timestamp_sensor.cpp
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#include "uptime_timestamp_sensor.h"
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uptime {
|
||||||
|
|
||||||
|
static const char *const TAG = "uptime.sensor";
|
||||||
|
|
||||||
|
void UptimeTimestampSensor::setup() {
|
||||||
|
this->time_->add_on_time_sync_callback([this]() {
|
||||||
|
if (this->has_state_)
|
||||||
|
return; // No need to update the timestamp if it's already set
|
||||||
|
|
||||||
|
auto now = this->time_->now();
|
||||||
|
const uint32_t ms = millis();
|
||||||
|
if (!now.is_valid())
|
||||||
|
return; // No need to update the timestamp if the time is not valid
|
||||||
|
|
||||||
|
time_t timestamp = now.timestamp;
|
||||||
|
uint32_t seconds = ms / 1000;
|
||||||
|
timestamp -= seconds;
|
||||||
|
this->publish_state(timestamp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
float UptimeTimestampSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
void UptimeTimestampSensor::dump_config() {
|
||||||
|
LOG_SENSOR("", "Uptime Sensor", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Type: Timestamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uptime
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_TIME
|
30
esphome/components/uptime/uptime_timestamp_sensor.h
Normal file
30
esphome/components/uptime/uptime_timestamp_sensor.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/time/real_time_clock.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uptime {
|
||||||
|
|
||||||
|
class UptimeTimestampSensor : public sensor::Sensor, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
void set_time(time::RealTimeClock *time) { this->time_ = time; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
time::RealTimeClock *time_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uptime
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_TIME
|
|
@ -243,7 +243,7 @@ ErrorCode VEML7700Component::configure_() {
|
||||||
}
|
}
|
||||||
|
|
||||||
PSMRegister psm{0};
|
PSMRegister psm{0};
|
||||||
psm.PSM = PSM::PSM_MODE_1;
|
psm.PSM = PSMMode::PSM_MODE_1;
|
||||||
psm.PSM_EN = false;
|
psm.PSM_EN = false;
|
||||||
ESP_LOGV(TAG, "Setting PSM to 0x%04X", psm.raw);
|
ESP_LOGV(TAG, "Setting PSM to 0x%04X", psm.raw);
|
||||||
err = this->write_register((uint8_t) CommandRegisters::PWR_SAVING, psm.raw_bytes, VEML_REG_SIZE);
|
err = this->write_register((uint8_t) CommandRegisters::PWR_SAVING, psm.raw_bytes, VEML_REG_SIZE);
|
||||||
|
|
|
@ -24,7 +24,7 @@ enum class CommandRegisters : uint8_t {
|
||||||
ALS_INT = 0x06 // R: ALS INT trigger event
|
ALS_INT = 0x06 // R: ALS INT trigger event
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Gain : uint8_t {
|
enum Gain : uint16_t {
|
||||||
X_1 = 0,
|
X_1 = 0,
|
||||||
X_2 = 1,
|
X_2 = 1,
|
||||||
X_1_8 = 2,
|
X_1_8 = 2,
|
||||||
|
@ -32,7 +32,7 @@ enum Gain : uint8_t {
|
||||||
};
|
};
|
||||||
const uint8_t GAINS_COUNT = 4;
|
const uint8_t GAINS_COUNT = 4;
|
||||||
|
|
||||||
enum IntegrationTime : uint8_t {
|
enum IntegrationTime : uint16_t {
|
||||||
INTEGRATION_TIME_25MS = 0b1100,
|
INTEGRATION_TIME_25MS = 0b1100,
|
||||||
INTEGRATION_TIME_50MS = 0b1000,
|
INTEGRATION_TIME_50MS = 0b1000,
|
||||||
INTEGRATION_TIME_100MS = 0b0000,
|
INTEGRATION_TIME_100MS = 0b0000,
|
||||||
|
@ -42,14 +42,14 @@ enum IntegrationTime : uint8_t {
|
||||||
};
|
};
|
||||||
const uint8_t INTEGRATION_TIMES_COUNT = 6;
|
const uint8_t INTEGRATION_TIMES_COUNT = 6;
|
||||||
|
|
||||||
enum Persistence : uint8_t {
|
enum Persistence : uint16_t {
|
||||||
PERSISTENCE_1 = 0,
|
PERSISTENCE_1 = 0,
|
||||||
PERSISTENCE_2 = 1,
|
PERSISTENCE_2 = 1,
|
||||||
PERSISTENCE_4 = 2,
|
PERSISTENCE_4 = 2,
|
||||||
PERSISTENCE_8 = 3,
|
PERSISTENCE_8 = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PSM : uint8_t {
|
enum PSMMode : uint16_t {
|
||||||
PSM_MODE_1 = 0,
|
PSM_MODE_1 = 0,
|
||||||
PSM_MODE_2 = 1,
|
PSM_MODE_2 = 1,
|
||||||
PSM_MODE_3 = 2,
|
PSM_MODE_3 = 2,
|
||||||
|
@ -92,7 +92,7 @@ union PSMRegister {
|
||||||
uint8_t raw_bytes[2];
|
uint8_t raw_bytes[2];
|
||||||
struct {
|
struct {
|
||||||
bool PSM_EN : 1;
|
bool PSM_EN : 1;
|
||||||
uint8_t PSM : 2;
|
PSMMode PSM : 2;
|
||||||
uint16_t reserved : 13;
|
uint16_t reserved : 13;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
};
|
};
|
||||||
|
|
|
@ -799,7 +799,7 @@ void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) {
|
||||||
this->speaker_buffer_index_ += msg.data.length();
|
this->speaker_buffer_index_ += msg.data.length();
|
||||||
this->speaker_buffer_size_ += msg.data.length();
|
this->speaker_buffer_size_ += msg.data.length();
|
||||||
this->speaker_bytes_received_ += msg.data.length();
|
this->speaker_bytes_received_ += msg.data.length();
|
||||||
ESP_LOGV(TAG, "Received audio: %" PRId32 " bytes from API", msg.data.length());
|
ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length());
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "Cannot receive audio, buffer is full");
|
ESP_LOGE(TAG, "Cannot receive audio, buffer is full");
|
||||||
}
|
}
|
||||||
|
|
|
@ -757,7 +757,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||||
|
|
||||||
WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() {
|
WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() {
|
||||||
if (s_sta_connected && this->got_ipv4_address_) {
|
if (s_sta_connected && this->got_ipv4_address_) {
|
||||||
#if USE_NETWORK_IPV6
|
#if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
|
||||||
if (this->num_ipv6_addresses_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT) {
|
if (this->num_ipv6_addresses_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT) {
|
||||||
return WiFiSTAConnectStatus::CONNECTED;
|
return WiFiSTAConnectStatus::CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,13 +141,29 @@ bool WiFiComponent::wifi_scan_start_(bool passive) {
|
||||||
|
|
||||||
#ifdef USE_WIFI_AP
|
#ifdef USE_WIFI_AP
|
||||||
bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||||
// TODO:
|
esphome::network::IPAddress ip_address, gateway, subnet, dns;
|
||||||
return false;
|
if (manual_ip.has_value()) {
|
||||||
|
ip_address = manual_ip->static_ip;
|
||||||
|
gateway = manual_ip->gateway;
|
||||||
|
subnet = manual_ip->subnet;
|
||||||
|
dns = manual_ip->static_ip;
|
||||||
|
} else {
|
||||||
|
ip_address = network::IPAddress(192, 168, 4, 1);
|
||||||
|
gateway = network::IPAddress(192, 168, 4, 1);
|
||||||
|
subnet = network::IPAddress(255, 255, 255, 0);
|
||||||
|
dns = network::IPAddress(192, 168, 4, 1);
|
||||||
|
}
|
||||||
|
WiFi.config(ip_address, dns, gateway, subnet);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||||
if (!this->wifi_mode_({}, true))
|
if (!this->wifi_mode_({}, true))
|
||||||
return false;
|
return false;
|
||||||
|
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||||
|
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.get_channel().value_or(1));
|
WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.get_channel().value_or(1));
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,7 @@ async def to_code(config):
|
||||||
# the '+1' modifier is relative to the device's own address that will
|
# the '+1' modifier is relative to the device's own address that will
|
||||||
# be automatically added to the provided list.
|
# be automatically added to the provided list.
|
||||||
cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}")
|
cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}")
|
||||||
cg.add_library("droscy/esp_wireguard", "0.4.1")
|
cg.add_library("droscy/esp_wireguard", "0.4.2")
|
||||||
|
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,13 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_INITIAL_VALUE, default=1.0): cv.float_range(
|
cv.Optional(CONF_INITIAL_VALUE, default=1.0): cv.float_range(
|
||||||
min=0.01, max=1.0
|
min=0.01, max=1.0
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_STEP_DELAY, default=1): cv.int_range(min=1, max=100),
|
cv.Optional(CONF_STEP_DELAY, default="1us"): cv.All(
|
||||||
|
cv.positive_time_period_microseconds,
|
||||||
|
cv.Range(
|
||||||
|
min=cv.TimePeriod(microseconds=1),
|
||||||
|
max=cv.TimePeriod(microseconds=100),
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2024.6.6"
|
__version__ = "2024.7.0b1"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
@ -1072,6 +1072,7 @@ DEVICE_CLASS_BUTTON = "button"
|
||||||
DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide"
|
DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide"
|
||||||
DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide"
|
DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide"
|
||||||
DEVICE_CLASS_COLD = "cold"
|
DEVICE_CLASS_COLD = "cold"
|
||||||
|
DEVICE_CLASS_CONDUCTIVITY = "conductivity"
|
||||||
DEVICE_CLASS_CONNECTIVITY = "connectivity"
|
DEVICE_CLASS_CONNECTIVITY = "connectivity"
|
||||||
DEVICE_CLASS_CURRENT = "current"
|
DEVICE_CLASS_CURRENT = "current"
|
||||||
DEVICE_CLASS_CURTAIN = "curtain"
|
DEVICE_CLASS_CURTAIN = "curtain"
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
#define USE_ESP32_BLE_SERVER
|
#define USE_ESP32_BLE_SERVER
|
||||||
#define USE_ESP32_CAMERA
|
#define USE_ESP32_CAMERA
|
||||||
#define USE_IMPROV
|
#define USE_IMPROV
|
||||||
|
#define USE_MICRO_WAKE_WORD_VAD
|
||||||
#define USE_MICROPHONE
|
#define USE_MICROPHONE
|
||||||
#define USE_PSRAM
|
#define USE_PSRAM
|
||||||
#define USE_SOCKET_IMPL_BSD_SOCKETS
|
#define USE_SOCKET_IMPL_BSD_SOCKETS
|
||||||
|
|
|
@ -93,7 +93,7 @@ std::string to_string(long double value) { return str_snprintf("%Lf", 32, value)
|
||||||
// Mathematics
|
// Mathematics
|
||||||
|
|
||||||
float lerp(float completion, float start, float end) { return start + (end - start) * completion; }
|
float lerp(float completion, float start, float end) { return start + (end - start) * completion; }
|
||||||
uint8_t crc8(uint8_t *data, uint8_t len) {
|
uint8_t crc8(const uint8_t *data, uint8_t len) {
|
||||||
uint8_t crc = 0;
|
uint8_t crc = 0;
|
||||||
|
|
||||||
while ((len--) != 0u) {
|
while ((len--) != 0u) {
|
||||||
|
|
|
@ -155,7 +155,7 @@ template<typename T, typename U> T remap(U value, U min, U max, T min_out, T max
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate a CRC-8 checksum of \p data with size \p len.
|
/// Calculate a CRC-8 checksum of \p data with size \p len.
|
||||||
uint8_t crc8(uint8_t *data, uint8_t len);
|
uint8_t crc8(const uint8_t *data, uint8_t len);
|
||||||
|
|
||||||
/// Calculate a CRC-16 checksum of \p data with size \p len.
|
/// Calculate a CRC-16 checksum of \p data with size \p len.
|
||||||
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc = 0xffff, uint16_t reverse_poly = 0xa001,
|
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc = 0xffff, uint16_t reverse_poly = 0xa001,
|
||||||
|
|
|
@ -7,6 +7,7 @@ from datetime import datetime
|
||||||
import requests
|
import requests
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.core import CORE, TimePeriodSeconds
|
from esphome.core import CORE, TimePeriodSeconds
|
||||||
|
from esphome.const import __version__
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
CODEOWNERS = ["@landonr"]
|
CODEOWNERS = ["@landonr"]
|
||||||
|
@ -75,3 +76,28 @@ def compute_local_file_dir(domain: str) -> Path:
|
||||||
base_directory.mkdir(parents=True, exist_ok=True)
|
base_directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
return base_directory
|
return base_directory
|
||||||
|
|
||||||
|
|
||||||
|
def download_content(url: str, path: Path, timeout=NETWORK_TIMEOUT) -> None:
|
||||||
|
if not 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=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)
|
||||||
|
|
13
esphome/idf_component.yml
Normal file
13
esphome/idf_component.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
dependencies:
|
||||||
|
esp-tflite-micro:
|
||||||
|
git: https://github.com/espressif/esp-tflite-micro.git
|
||||||
|
version: v1.3.1
|
||||||
|
esp32_camera:
|
||||||
|
git: https://github.com/espressif/esp32-camera.git
|
||||||
|
version: v2.0.9
|
||||||
|
mdns:
|
||||||
|
git: https://github.com/espressif/esp-protocols.git
|
||||||
|
version: mdns-v1.2.5
|
||||||
|
path: components/mdns
|
||||||
|
rules:
|
||||||
|
- if: "idf_version >=5.0"
|
|
@ -39,7 +39,7 @@ lib_deps =
|
||||||
bblanchon/ArduinoJson@6.18.5 ; json
|
bblanchon/ArduinoJson@6.18.5 ; json
|
||||||
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
||||||
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
||||||
pavlodn/HaierProtocol@0.9.28 ; haier
|
pavlodn/HaierProtocol@0.9.31 ; haier
|
||||||
; This is using the repository until a new release is published to PlatformIO
|
; This is using the repository until a new release is published to PlatformIO
|
||||||
https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library
|
https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library
|
||||||
build_flags =
|
build_flags =
|
||||||
|
@ -65,7 +65,7 @@ lib_deps =
|
||||||
glmnet/Dsmr@0.7 ; dsmr
|
glmnet/Dsmr@0.7 ; dsmr
|
||||||
rweather/Crypto@0.4.0 ; dsmr
|
rweather/Crypto@0.4.0 ; dsmr
|
||||||
dudanov/MideaUART@1.1.9 ; midea
|
dudanov/MideaUART@1.1.9 ; midea
|
||||||
tonia/HeatpumpIR@1.0.23 ; heatpumpir
|
tonia/HeatpumpIR@1.0.26 ; heatpumpir
|
||||||
build_flags =
|
build_flags =
|
||||||
${common.build_flags}
|
${common.build_flags}
|
||||||
-DUSE_ARDUINO
|
-DUSE_ARDUINO
|
||||||
|
@ -93,8 +93,8 @@ lib_deps =
|
||||||
ESP8266HTTPClient ; http_request (Arduino built-in)
|
ESP8266HTTPClient ; http_request (Arduino built-in)
|
||||||
ESP8266mDNS ; mdns (Arduino built-in)
|
ESP8266mDNS ; mdns (Arduino built-in)
|
||||||
DNSServer ; captive_portal (Arduino built-in)
|
DNSServer ; captive_portal (Arduino built-in)
|
||||||
crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir
|
crankyoldgit/IRremoteESP8266@2.8.6 ; heatpumpir
|
||||||
droscy/esp_wireguard@0.4.1 ; wireguard
|
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:arduino.build_flags}
|
${common:arduino.build_flags}
|
||||||
-Wno-nonnull-compare
|
-Wno-nonnull-compare
|
||||||
|
@ -123,8 +123,8 @@ lib_deps =
|
||||||
ESPmDNS ; mdns (Arduino built-in)
|
ESPmDNS ; mdns (Arduino built-in)
|
||||||
DNSServer ; captive_portal (Arduino built-in)
|
DNSServer ; captive_portal (Arduino built-in)
|
||||||
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
|
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
|
||||||
crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir
|
crankyoldgit/IRremoteESP8266@2.8.6 ; heatpumpir
|
||||||
droscy/esp_wireguard@0.4.1 ; wireguard
|
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:arduino.build_flags}
|
${common:arduino.build_flags}
|
||||||
-DUSE_ESP32
|
-DUSE_ESP32
|
||||||
|
@ -142,8 +142,8 @@ platform_packages =
|
||||||
framework = espidf
|
framework = espidf
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${common:idf.lib_deps}
|
${common:idf.lib_deps}
|
||||||
espressif/esp32-camera@1.0.0 ; esp32_camera
|
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||||
droscy/esp_wireguard@0.4.1 ; wireguard
|
kahrendt/ESPMicroSpeechFeatures@1.0.0 ; micro_wake_word
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:idf.build_flags}
|
${common:idf.build_flags}
|
||||||
-Wno-nonnull-compare
|
-Wno-nonnull-compare
|
||||||
|
@ -175,7 +175,7 @@ extends = common:arduino
|
||||||
platform = libretiny
|
platform = libretiny
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps =
|
lib_deps =
|
||||||
droscy/esp_wireguard@0.4.1 ; wireguard
|
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:arduino.build_flags}
|
${common:arduino.build_flags}
|
||||||
-DUSE_LIBRETINY
|
-DUSE_LIBRETINY
|
||||||
|
|
|
@ -101,8 +101,10 @@ def clang_options(idedata):
|
||||||
# add library include directories using -isystem to suppress their errors
|
# add library include directories using -isystem to suppress their errors
|
||||||
for directory in sorted(set(idedata["includes"]["build"])):
|
for directory in sorted(set(idedata["includes"]["build"])):
|
||||||
# skip our own directories, we add those later
|
# skip our own directories, we add those later
|
||||||
if not directory.startswith(f"{root_path}/") or directory.startswith(
|
if (
|
||||||
f"{root_path}/.pio/"
|
not directory.startswith(f"{root_path}/")
|
||||||
|
or directory.startswith(f"{root_path}/.pio/")
|
||||||
|
or directory.startswith(f"{root_path}/managed_components/")
|
||||||
):
|
):
|
||||||
cmd.extend(["-isystem", directory])
|
cmd.extend(["-isystem", directory])
|
||||||
|
|
||||||
|
|
25
script/extract_automations.py
Executable file
25
script/extract_automations.py
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from helpers import git_ls_files
|
||||||
|
|
||||||
|
from esphome.automation import ACTION_REGISTRY, CONDITION_REGISTRY
|
||||||
|
from esphome.pins import PIN_SCHEMA_REGISTRY
|
||||||
|
|
||||||
|
list_components = __import__("list-components")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
files = git_ls_files()
|
||||||
|
files = filter(list_components.filter_component_files, files)
|
||||||
|
|
||||||
|
components = list_components.get_components(files, True)
|
||||||
|
|
||||||
|
dump = {
|
||||||
|
"actions": sorted(list(ACTION_REGISTRY.keys())),
|
||||||
|
"conditions": sorted(list(CONDITION_REGISTRY.keys())),
|
||||||
|
"pin_providers": sorted(list(PIN_SCHEMA_REGISTRY.keys())),
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(dump, indent=2))
|
|
@ -50,6 +50,7 @@ def create_components_graph():
|
||||||
{KEY_TARGET_FRAMEWORK: "arduino", KEY_TARGET_PLATFORM: None},
|
{KEY_TARGET_FRAMEWORK: "arduino", KEY_TARGET_PLATFORM: None},
|
||||||
{KEY_TARGET_FRAMEWORK: "esp-idf", KEY_TARGET_PLATFORM: None},
|
{KEY_TARGET_FRAMEWORK: "esp-idf", KEY_TARGET_PLATFORM: None},
|
||||||
{KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: PLATFORM_ESP32},
|
{KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: PLATFORM_ESP32},
|
||||||
|
{KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: PLATFORM_ESP8266},
|
||||||
]
|
]
|
||||||
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
||||||
|
|
||||||
|
@ -119,10 +120,30 @@ def find_children_of_component(components_graph, component_name, depth=0):
|
||||||
return list(set(children))
|
return list(set(children))
|
||||||
|
|
||||||
|
|
||||||
|
def get_components(files: list[str], get_dependencies: bool = False):
|
||||||
|
components = extract_component_names_array_from_files_array(files)
|
||||||
|
|
||||||
|
if get_dependencies:
|
||||||
|
components_graph = create_components_graph()
|
||||||
|
|
||||||
|
all_components = components.copy()
|
||||||
|
for c in components:
|
||||||
|
all_components.extend(find_children_of_component(components_graph, c))
|
||||||
|
# Remove duplicate values
|
||||||
|
all_changed_components = list(set(all_components))
|
||||||
|
|
||||||
|
return sorted(all_changed_components)
|
||||||
|
|
||||||
|
return sorted(components)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c", "--changed", action="store_true", help="Only run on changed files"
|
"-c",
|
||||||
|
"--changed",
|
||||||
|
action="store_true",
|
||||||
|
help="List all components required for testing based on changes",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-b", "--branch", help="Branch to compare changed files against"
|
"-b", "--branch", help="Branch to compare changed files against"
|
||||||
|
@ -140,26 +161,12 @@ def main():
|
||||||
changed = changed_files(args.branch)
|
changed = changed_files(args.branch)
|
||||||
else:
|
else:
|
||||||
changed = changed_files()
|
changed = changed_files()
|
||||||
files = [f for f in files if f in changed]
|
# If any base test file(s) changed, there's no need to filter out components
|
||||||
|
if not any("tests/test_build_components" in file for file in changed):
|
||||||
|
files = [f for f in files if f in changed]
|
||||||
|
|
||||||
components = extract_component_names_array_from_files_array(files)
|
for c in get_components(files, args.changed):
|
||||||
|
print(c)
|
||||||
if args.changed:
|
|
||||||
components_graph = create_components_graph()
|
|
||||||
|
|
||||||
all_changed_components = components.copy()
|
|
||||||
for c in components:
|
|
||||||
all_changed_components.extend(
|
|
||||||
find_children_of_component(components_graph, c)
|
|
||||||
)
|
|
||||||
# Remove duplicate values
|
|
||||||
all_changed_components = list(set(all_changed_components))
|
|
||||||
|
|
||||||
for c in sorted(all_changed_components):
|
|
||||||
print(c)
|
|
||||||
else:
|
|
||||||
for c in sorted(components):
|
|
||||||
print(c)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -10,6 +10,7 @@ from homeassistant.components.event import EventDeviceClass
|
||||||
from homeassistant.components.number import NumberDeviceClass
|
from homeassistant.components.number import NumberDeviceClass
|
||||||
from homeassistant.components.sensor import SensorDeviceClass
|
from homeassistant.components.sensor import SensorDeviceClass
|
||||||
from homeassistant.components.switch import SwitchDeviceClass
|
from homeassistant.components.switch import SwitchDeviceClass
|
||||||
|
from homeassistant.components.update import UpdateDeviceClass
|
||||||
from homeassistant.components.valve import ValveDeviceClass
|
from homeassistant.components.valve import ValveDeviceClass
|
||||||
|
|
||||||
# pylint: enable=import-error
|
# pylint: enable=import-error
|
||||||
|
@ -27,6 +28,7 @@ DOMAINS = {
|
||||||
"number": NumberDeviceClass,
|
"number": NumberDeviceClass,
|
||||||
"sensor": SensorDeviceClass,
|
"sensor": SensorDeviceClass,
|
||||||
"switch": SwitchDeviceClass,
|
"switch": SwitchDeviceClass,
|
||||||
|
"update": UpdateDeviceClass,
|
||||||
"valve": ValveDeviceClass,
|
"valve": ValveDeviceClass,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,13 @@ set -e
|
||||||
# - `c` - Component folder name to test. Default `*`.
|
# - `c` - Component folder name to test. Default `*`.
|
||||||
esphome_command="compile"
|
esphome_command="compile"
|
||||||
target_component="*"
|
target_component="*"
|
||||||
while getopts e:c: flag
|
while getopts e:c:t: flag
|
||||||
do
|
do
|
||||||
case $flag in
|
case $flag in
|
||||||
e) esphome_command=${OPTARG};;
|
e) esphome_command=${OPTARG};;
|
||||||
c) target_component=${OPTARG};;
|
c) target_component=${OPTARG};;
|
||||||
\?) echo "Usage: $0 [-e <config|compile|clean>] [-c <string>]" 1>&2; exit 1;;
|
t) requested_target_platform=${OPTARG};;
|
||||||
|
\?) echo "Usage: $0 [-e <config|compile|clean>] [-c <string>] [-t <string>]" 1>&2; exit 1;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -23,9 +24,13 @@ if ! [ -d "./tests/test_build_components/build" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
start_esphome() {
|
start_esphome() {
|
||||||
|
if [ -n "$requested_target_platform" ] && [ "$requested_target_platform" != "$target_platform" ]; then
|
||||||
|
echo "Skiping $target_platform"
|
||||||
|
return
|
||||||
|
fi
|
||||||
# create dynamic yaml file in `build` folder.
|
# create dynamic yaml file in `build` folder.
|
||||||
# `./tests/test_build_components/build/[target_component].[test_name].[target_platform].yaml`
|
# `./tests/test_build_components/build/[target_component].[test_name].[target_platform_with_version].yaml`
|
||||||
component_test_file="./tests/test_build_components/build/$target_component.$test_name.$target_platform.yaml"
|
component_test_file="./tests/test_build_components/build/$target_component.$test_name.$target_platform_with_version.yaml"
|
||||||
|
|
||||||
cp $target_platform_file $component_test_file
|
cp $target_platform_file $component_test_file
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
@ -36,7 +41,7 @@ start_esphome() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start esphome process
|
# Start esphome process
|
||||||
echo "> [$target_component] [$test_name] [$target_platform]"
|
echo "> [$target_component] [$test_name] [$target_platform_with_version]"
|
||||||
set -x
|
set -x
|
||||||
# TODO: Validate escape of Command line substitution value
|
# TODO: Validate escape of Command line substitution value
|
||||||
python -m esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
|
python -m esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
|
||||||
|
@ -76,16 +81,17 @@ for f in ./tests/components/$target_component/*.*.yaml; do
|
||||||
# 2. `./tests/test_build_components/build_components_base.[target_platform]-ard.yaml`
|
# 2. `./tests/test_build_components/build_components_base.[target_platform]-ard.yaml`
|
||||||
target_platform_file="./tests/test_build_components/build_components_base.$target_platform.yaml"
|
target_platform_file="./tests/test_build_components/build_components_base.$target_platform.yaml"
|
||||||
if ! [ -f "$target_platform_file" ]; then
|
if ! [ -f "$target_platform_file" ]; then
|
||||||
# Try find arduino test framework as platform.
|
echo "No base test file [./tests/test_build_components/build_components_base.$target_platform.yaml] for component test [$f] found."
|
||||||
target_platform_ard="$target_platform-ard"
|
exit 1
|
||||||
target_platform_file="./tests/test_build_components/build_components_base.$target_platform_ard.yaml"
|
|
||||||
if ! [ -f "$target_platform_file" ]; then
|
|
||||||
echo "No base test file [./tests/test_build_components/build_components_base.$target_platform.yaml, ./tests/build_components_base.$target_platform_ard.yaml] for component test [$f] found."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
target_platform=$target_platform_ard
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
start_esphome
|
for target_platform_file in ./tests/test_build_components/build_components_base.$target_platform*.yaml; do
|
||||||
|
# trim off "./tests/test_build_components/build_components_base." prefix
|
||||||
|
target_platform_with_version=${target_platform_file:52}
|
||||||
|
# ...now remove suffix starting with "." leaving just the test target hardware and software platform (possibly with version)
|
||||||
|
# For example: "esp32-s3-idf-50"
|
||||||
|
target_platform_with_version=${target_platform_with_version%.*}
|
||||||
|
start_esphome
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
uart:
|
uart:
|
||||||
- id: uart_a01nyub
|
- id: uart_a01nyub
|
||||||
tx_pin:
|
tx_pin: ${tx_pin}
|
||||||
number: 4
|
rx_pin: ${rx_pin}
|
||||||
rx_pin:
|
|
||||||
number: 5
|
|
||||||
baud_rate: 9600
|
baud_rate: 9600
|
||||||
|
|
||||||
sensor:
|
sensor:
|
|
@ -1,13 +1,5 @@
|
||||||
uart:
|
substitutions:
|
||||||
- id: uart_a01nyub
|
tx_pin: GPIO4
|
||||||
tx_pin:
|
rx_pin: GPIO5
|
||||||
number: 4
|
|
||||||
rx_pin:
|
|
||||||
number: 5
|
|
||||||
baud_rate: 9600
|
|
||||||
|
|
||||||
sensor:
|
<<: !include common.yaml
|
||||||
- platform: a01nyub
|
|
||||||
id: a01nyub_sensor
|
|
||||||
name: a01nyub Distance
|
|
||||||
uart_id: uart_a01nyub
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
uart:
|
|
||||||
- id: uart_a01nyub
|
|
||||||
tx_pin:
|
|
||||||
number: 4
|
|
||||||
rx_pin:
|
|
||||||
number: 5
|
|
||||||
baud_rate: 9600
|
|
||||||
|
|
||||||
sensor:
|
|
||||||
- platform: a01nyub
|
|
||||||
id: a01nyub_sensor
|
|
||||||
name: a01nyub Distance
|
|
||||||
uart_id: uart_a01nyub
|
|
|
@ -1,13 +1,5 @@
|
||||||
uart:
|
substitutions:
|
||||||
- id: uart_a01nyub
|
tx_pin: GPIO17
|
||||||
tx_pin:
|
rx_pin: GPIO16
|
||||||
number: 17
|
|
||||||
rx_pin:
|
|
||||||
number: 16
|
|
||||||
baud_rate: 9600
|
|
||||||
|
|
||||||
sensor:
|
<<: !include common.yaml
|
||||||
- platform: a01nyub
|
|
||||||
id: a01nyub_sensor
|
|
||||||
name: a01nyub Distance
|
|
||||||
uart_id: uart_a01nyub
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
uart:
|
|
||||||
- id: uart_a01nyub
|
|
||||||
tx_pin:
|
|
||||||
number: 17
|
|
||||||
rx_pin:
|
|
||||||
number: 16
|
|
||||||
baud_rate: 9600
|
|
||||||
|
|
||||||
sensor:
|
|
||||||
- platform: a01nyub
|
|
||||||
id: a01nyub_sensor
|
|
||||||
name: a01nyub Distance
|
|
||||||
uart_id: uart_a01nyub
|
|
|
@ -1,13 +0,0 @@
|
||||||
uart:
|
|
||||||
- id: uart_a01nyub
|
|
||||||
tx_pin:
|
|
||||||
number: 4
|
|
||||||
rx_pin:
|
|
||||||
number: 5
|
|
||||||
baud_rate: 9600
|
|
||||||
|
|
||||||
sensor:
|
|
||||||
- platform: a01nyub
|
|
||||||
id: a01nyub_sensor
|
|
||||||
name: a01nyub Distance
|
|
||||||
uart_id: uart_a01nyub
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue