Merge branch 'dev' into opt3001

This commit is contained in:
Jesse Hills 2024-09-27 14:14:05 +12:00 committed by GitHub
commit caa4937853
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3788 changed files with 61540 additions and 37751 deletions

View file

@ -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,

42
.flake8 Normal file
View file

@ -0,0 +1,42 @@
[flake8]
max-line-length = 120
# Following 4 for black compatibility
# E501: line too long
# W503: Line break occurred before a binary operator
# E203: Whitespace before ':'
# D202 No blank lines allowed after function docstring
# TODO fix flake8
# D100 Missing docstring in public module
# D101 Missing docstring in public class
# D102 Missing docstring in public method
# D103 Missing docstring in public function
# D104 Missing docstring in public package
# D105 Missing docstring in magic method
# D107 Missing docstring in __init__
# D200 One-line docstring should fit on one line with quotes
# D205 1 blank line required between summary line and description
# D209 Multi-line docstring closing quotes should be on a separate line
# D400 First line should end with a period
# D401 First line should be in imperative mood
ignore =
E501,
W503,
E203,
D202,
D100,
D101,
D102,
D103,
D104,
D105,
D107,
D200,
D205,
D209,
D400,
D401,
exclude = api_pb2.py

View file

@ -34,16 +34,29 @@ runs:
echo $l >> $GITHUB_OUTPUT echo $l >> $GITHUB_OUTPUT
done done
# set cache-to only if dev branch
- id: cache-to
shell: bash
run: |-
if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then
echo "value=type=gha,mode=max" >> $GITHUB_OUTPUT
else
echo "value=" >> $GITHUB_OUTPUT
fi
- 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@v5.3.0 uses: docker/build-push-action@v6.7.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
platforms: ${{ inputs.platform }} platforms: ${{ inputs.platform }}
target: ${{ inputs.target }} target: ${{ inputs.target }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: ${{ steps.cache-to.outputs.value }}
build-args: | build-args: |
BASEIMGTYPE=${{ inputs.baseimg }} BASEIMGTYPE=${{ inputs.baseimg }}
BUILD_VERSION=${{ inputs.version }} BUILD_VERSION=${{ inputs.version }}
@ -57,24 +70,19 @@ runs:
digest="${{ steps.build-ghcr.outputs.digest }}" digest="${{ steps.build-ghcr.outputs.digest }}"
touch "/tmp/digests/${{ inputs.target }}/ghcr/${digest#sha256:}" touch "/tmp/digests/${{ inputs.target }}/ghcr/${digest#sha256:}"
- name: Upload ghcr digest
uses: actions/upload-artifact@v3.1.3
with:
name: digests-${{ inputs.target }}-ghcr
path: /tmp/digests/${{ inputs.target }}/ghcr/*
if-no-files-found: error
retention-days: 1
- 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@v5.3.0 uses: docker/build-push-action@v6.7.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
platforms: ${{ inputs.platform }} platforms: ${{ inputs.platform }}
target: ${{ inputs.target }} target: ${{ inputs.target }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: ${{ steps.cache-to.outputs.value }}
build-args: | build-args: |
BASEIMGTYPE=${{ inputs.baseimg }} BASEIMGTYPE=${{ inputs.baseimg }}
BUILD_VERSION=${{ inputs.version }} BUILD_VERSION=${{ inputs.version }}
@ -87,11 +95,3 @@ runs:
mkdir -p /tmp/digests/${{ inputs.target }}/dockerhub mkdir -p /tmp/digests/${{ inputs.target }}/dockerhub
digest="${{ steps.build-dockerhub.outputs.digest }}" digest="${{ steps.build-dockerhub.outputs.digest }}"
touch "/tmp/digests/${{ inputs.target }}/dockerhub/${digest#sha256:}" touch "/tmp/digests/${{ inputs.target }}/dockerhub/${digest#sha256:}"
- name: Upload dockerhub digest
uses: actions/upload-artifact@v3.1.3
with:
name: digests-${{ inputs.target }}-dockerhub
path: /tmp/digests/${{ inputs.target }}/dockerhub/*
if-no-files-found: error
retention-days: 1

View file

@ -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.2.0
with: with:
python-version: ${{ inputs.python-version }} python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment

View file

@ -13,6 +13,13 @@ updates:
schedule: schedule:
interval: daily interval: daily
open-pull-requests-limit: 10 open-pull-requests-limit: 10
groups:
docker-actions:
applies-to: version-updates
patterns:
- "docker/setup-qemu-action"
- "docker/login-action"
- "docker/setup-buildx-action"
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: "/.github/actions/build-image" directory: "/.github/actions/build-image"
schedule: schedule:

View file

@ -21,9 +21,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.1 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.2.0
with: with:
python-version: "3.11" python-version: "3.11"

View file

@ -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.1 - 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.2.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.6.1
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.2.0
- name: Set TAG - name: Set TAG
run: | run: |

View file

@ -9,6 +9,7 @@ on:
paths: paths:
- "**" - "**"
- "!.github/workflows/*.yml" - "!.github/workflows/*.yml"
- "!.github/actions/build-image/*"
- ".github/workflows/ci.yml" - ".github/workflows/ci.yml"
- "!.yamllint" - "!.yamllint"
- "!.github/dependabot.yml" - "!.github/dependabot.yml"
@ -34,13 +35,13 @@ 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.1 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
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v5.1.0 uses: actions/setup-python@v5.2.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
@ -66,7 +67,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 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 +88,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 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 +109,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 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 +130,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 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 +151,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 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 +200,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 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 +230,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 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 +249,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.1
- 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.1
- 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.1
- 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,18 +293,26 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio - name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@v4.0.2 uses: actions/cache@v4.0.2
with: with:
path: ~/.platformio path: ~/.platformio
# yamllint disable-line rule:line-length key: platformio-${{ matrix.pio_cache_key }}
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@v4.0.2
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}
- name: Install clang-tidy - name: Install clang-tidy
run: sudo apt-get install clang-tidy-14 run: sudo apt-get install clang-tidy-14
@ -379,6 +322,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
@ -402,7 +352,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.1 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
@ -446,11 +396,11 @@ jobs:
matrix: matrix:
file: ${{ fromJson(needs.list-components.outputs.components) }} file: ${{ fromJson(needs.list-components.outputs.components) }}
steps: steps:
- name: Install libsodium - name: Install dependencies
run: sudo apt-get install libsodium-dev run: sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -476,7 +426,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.1 uses: actions/checkout@v4.1.7
- name: Split components into 20 groups - name: Split components into 20 groups
id: split id: split
run: | run: |
@ -500,11 +450,11 @@ jobs:
- name: List components - name: List components
run: echo ${{ matrix.components }} run: echo ${{ matrix.components }}
- name: Install libsodium - name: Install dependencies
run: sudo apt-get install libsodium-dev run: sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -519,6 +469,8 @@ jobs:
- name: Compile config - name: Compile config
run: | run: |
. venv/bin/activate . venv/bin/activate
mkdir build_cache
export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache
for component in ${{ matrix.components }}; do for component in ${{ matrix.components }}; do
./script/test_build_components -e compile -c $component ./script/test_build_components -e compile -c $component
done done
@ -535,7 +487,6 @@ jobs:
- pylint - pylint
- pytest - pytest
- pyupgrade - pyupgrade
- compile-tests
- clang-tidy - clang-tidy
- list-components - list-components
- test-build-components - test-build-components

91
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,91 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
workflow_dispatch:
schedule:
- cron: "30 18 * * 4"
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
# - language: c-cpp
# build-mode: autobuild
- language: python
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View file

@ -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.1 - 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,9 +51,9 @@ jobs:
contents: read contents: read
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4.1.1 - 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.2.0
with: with:
python-version: "3.x" python-version: "3.x"
- name: Set up python environment - name: Set up python environment
@ -61,9 +61,11 @@ jobs:
ESPHOME_NO_VENV: 1 ESPHOME_NO_VENV: 1
run: script/setup run: script/setup
- name: Build - name: Build
run: python setup.py sdist bdist_wheel run: |-
pip3 install build
python3 -m build
- name: Publish - name: Publish
uses: pypa/gh-action-pypi-publish@v1.8.14 uses: pypa/gh-action-pypi-publish@v1.10.1
deploy-docker: deploy-docker:
name: Build ESPHome ${{ matrix.platform }} name: Build ESPHome ${{ matrix.platform }}
@ -81,25 +83,25 @@ jobs:
- linux/arm/v7 - linux/arm/v7
- linux/arm64 - linux/arm64
steps: steps:
- uses: actions/checkout@v4.1.1 - 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.2.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.6.1
- 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.2.0
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@v3.1.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
uses: docker/login-action@v3.1.0 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@ -132,6 +134,19 @@ jobs:
suffix: lint suffix: lint
version: ${{ needs.init.outputs.tag }} version: ${{ needs.init.outputs.tag }}
- name: Sanitize platform name
id: sanitize
run: |
echo "${{ matrix.platform }}" | sed 's|/|-|g' > /tmp/platform
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests
uses: actions/upload-artifact@v4.4.0
with:
name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests
retention-days: 1
deploy-manifest: deploy-manifest:
name: Publish ESPHome ${{ matrix.image.title }} to ${{ matrix.registry }} name: Publish ESPHome ${{ matrix.image.title }} to ${{ matrix.registry }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -159,24 +174,27 @@ jobs:
- ghcr - ghcr
- dockerhub - dockerhub
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.1.7
- name: Download digests - name: Download digests
uses: actions/download-artifact@v3.0.2 uses: actions/download-artifact@v4.1.8
with: with:
name: digests-${{ matrix.image.target }}-${{ matrix.registry }} pattern: digests-*
path: /tmp/digests path: /tmp/digests
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.6.1
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'
uses: docker/login-action@v3.1.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
if: matrix.registry == 'ghcr' if: matrix.registry == 'ghcr'
uses: docker/login-action@v3.1.0 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@ -195,7 +213,7 @@ jobs:
done done
- name: Create manifest list and push - name: Create manifest list and push
working-directory: /tmp/digests working-directory: /tmp/digests/${{ matrix.image.target }}/${{ matrix.registry }}
run: | run: |
docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \ docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \
$(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *) $(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *)

View file

@ -13,16 +13,16 @@ jobs:
if: github.repository == 'esphome/esphome' if: github.repository == 'esphome/esphome'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.7
- name: Checkout Home Assistant - name: Checkout Home Assistant
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.7
with: with:
repository: home-assistant/core repository: home-assistant/core
path: lib/home-assistant path: lib/home-assistant
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5.1.0 uses: actions/setup-python@v5.2.0
with: with:
python-version: 3.12 python-version: 3.12
@ -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.4 uses: peter-evans/create-pull-request@v7.0.5
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>

View file

@ -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.1 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:

2
.gitignore vendored
View file

@ -138,3 +138,5 @@ sdkconfig.*
.tests/ .tests/
/components /components
/managed_components

View file

@ -2,8 +2,17 @@
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.5.4
hooks:
# Run the linter.
- id: ruff
args: [--fix]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.2.0 rev: 24.4.2
hooks: hooks:
- id: black - id: black
args: args:
@ -39,4 +48,11 @@ repos:
rev: v13.0.1 rev: v13.0.1
hooks: hooks:
- id: clang-format - id: clang-format
types_or: [c, c++]
- repo: local
hooks:
- id: pylint
name: pylint
entry: script/run-in-env.sh pylint
language: script
types: [python]

View file

@ -6,7 +6,7 @@
# the integration's code owner is automatically notified. # the integration's code owner is automatically notified.
# Core Code # Core Code
setup.py @esphome/core pyproject.toml @esphome/core
esphome/*.py @esphome/core esphome/*.py @esphome/core
esphome/core/* @esphome/core esphome/core/* @esphome/core
@ -37,6 +37,7 @@ esphome/components/am43/sensor/* @buxtronix
esphome/components/analog_threshold/* @ianchi esphome/components/analog_threshold/* @ianchi
esphome/components/animation/* @syndlex esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix esphome/components/anova/* @buxtronix
esphome/components/apds9306/* @aodrenah
esphome/components/api/* @OttoWinter esphome/components/api/* @OttoWinter
esphome/components/as5600/* @ammmze esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze esphome/components/as5600/sensor/* @ammmze
@ -45,25 +46,37 @@ esphome/components/async_tcp/* @OttoWinter
esphome/components/at581x/* @X-Ryl669 esphome/components/at581x/* @X-Ryl669
esphome/components/atc_mithermometer/* @ahpohl esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner esphome/components/atm90e26/* @danieltwagner
esphome/components/atm90e32/* @circuitsetup @descipher
esphome/components/b_parasite/* @rbaron esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter esphome/components/bang_bang/* @OttoWinter
esphome/components/bedjet/* @jhansche esphome/components/bedjet/* @jhansche
esphome/components/bedjet/climate/* @jhansche esphome/components/bedjet/climate/* @jhansche
esphome/components/bedjet/fan/* @jhansche esphome/components/bedjet/fan/* @jhansche
esphome/components/bedjet/sensor/* @javawizard @jhansche
esphome/components/beken_spi_led_strip/* @Mat931
esphome/components/bh1750/* @OttoWinter esphome/components/bh1750/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core esphome/components/binary_sensor/* @esphome/core
esphome/components/bk72xx/* @kuba2k2 esphome/components/bk72xx/* @kuba2k2
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
esphome/components/bl0939/* @ziceva esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @tobias- esphome/components/bl0940/* @tobias-
esphome/components/bl0942/* @dbuezas esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme280_base/* @esphome/core esphome/components/bme280_base/* @esphome/core
esphome/components/bme280_spi/* @apbodrov esphome/components/bme280_spi/* @apbodrov
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bme68x_bsec2/* @kbx81 @neffs
esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs
esphome/components/bmi160/* @flaviut esphome/components/bmi160/* @flaviut
esphome/components/bmp3xx/* @martgras esphome/components/bmp280_base/* @ademuri
esphome/components/bmp280_i2c/* @ademuri
esphome/components/bmp280_spi/* @ademuri
esphome/components/bmp3xx/* @latonita
esphome/components/bmp3xx_base/* @latonita @martgras
esphome/components/bmp3xx_i2c/* @latonita
esphome/components/bmp3xx_spi/* @latonita
esphome/components/bmp581/* @kahrendt esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid esphome/components/bp5758d/* @Cossid
@ -73,6 +86,7 @@ esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke esphome/components/cd74hc4067/* @asoehlke
esphome/components/ch422g/* @clydebarrow @jesterret
esphome/components/climate/* @esphome/core esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz esphome/components/color_temperature/* @jesserockz
@ -89,6 +103,7 @@ esphome/components/current_based/* @djwmarcx
esphome/components/dac7678/* @NickB1 esphome/components/dac7678/* @NickB1
esphome/components/daikin_arc/* @MagicBear esphome/components/daikin_arc/* @MagicBear
esphome/components/daikin_brc/* @hagak esphome/components/daikin_brc/* @hagak
esphome/components/dallas_temp/* @ssieb
esphome/components/daly_bms/* @s1lvi0 esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core esphome/components/dashboard_import/* @esphome/core
esphome/components/datetime/* @jesserockz @rfdarter esphome/components/datetime/* @jesserockz @rfdarter
@ -106,7 +121,10 @@ esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/touchscreen/* @jesserockz esphome/components/ektf2232/touchscreen/* @jesserockz
esphome/components/emc2101/* @ellull esphome/components/emc2101/* @ellull
esphome/components/emmeti/* @E440QF esphome/components/emmeti/* @E440QF
esphome/components/ens160/* @vincentscode esphome/components/ens160/* @latonita
esphome/components/ens160_base/* @latonita @vincentscode
esphome/components/ens160_i2c/* @latonita
esphome/components/ens160_spi/* @latonita
esphome/components/ens210/* @itn3rd77 esphome/components/ens210/* @itn3rd77
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @jesserockz esphome/components/esp32_ble/* @Rapsssito @jesserockz
@ -119,6 +137,7 @@ esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core esphome/components/esp8266/* @esphome/core
esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat
esphome/components/exposure_notifications/* @OttoWinter esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb esphome/components/ezo/* @ssieb
esphome/components/ezo_pmp/* @carlos-sarmiento esphome/components/ezo_pmp/* @carlos-sarmiento
@ -131,9 +150,11 @@ esphome/components/fs3000/* @kahrendt
esphome/components/ft5x06/* @clydebarrow esphome/components/ft5x06/* @clydebarrow
esphome/components/ft63x6/* @gpambrozio esphome/components/ft63x6/* @gpambrozio
esphome/components/gcja5/* @gcormier esphome/components/gcja5/* @gcormier
esphome/components/gdk101/* @Szewcson
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gpio/one_wire/* @ssieb
esphome/components/gps/* @coogle esphome/components/gps/* @coogle
esphome/components/graph/* @synco esphome/components/graph/* @synco
esphome/components/graphical_display_menu/* @MrMDavidson esphome/components/graphical_display_menu/* @MrMDavidson
@ -142,6 +163,11 @@ esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte esphome/components/growatt_solar/* @leeuwte
esphome/components/gt911/* @clydebarrow @jesserockz esphome/components/gt911/* @clydebarrow @jesserockz
esphome/components/haier/* @paveldn esphome/components/haier/* @paveldn
esphome/components/haier/binary_sensor/* @paveldn
esphome/components/haier/button/* @paveldn
esphome/components/haier/sensor/* @paveldn
esphome/components/haier/switch/* @paveldn
esphome/components/haier/text_sensor/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/light/* @DotNetDann
@ -149,13 +175,19 @@ esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hm3301/* @freekode esphome/components/hm3301/* @freekode
esphome/components/homeassistant/* @OttoWinter esphome/components/hmac_md5/* @dwmw2
esphome/components/homeassistant/* @OttoWinter @esphome/core
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004
esphome/components/honeywell_hih_i2c/* @Benichou34 esphome/components/honeywell_hih_i2c/* @Benichou34
esphome/components/honeywellabp/* @RubyBailey esphome/components/honeywellabp/* @RubyBailey
esphome/components/honeywellabp2_i2c/* @jpfaff esphome/components/honeywellabp2_i2c/* @jpfaff
esphome/components/host/* @esphome/core esphome/components/host/* @clydebarrow @esphome/core
esphome/components/host/time/* @clydebarrow
esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/hte501/* @Stock-M esphome/components/hte501/* @Stock-M
esphome/components/http_request/ota/* @oarcher
esphome/components/http_request/update/* @jesserockz
esphome/components/htu31d/* @betterengineering esphome/components/htu31d/* @betterengineering
esphome/components/hydreon_rgxx/* @functionpointer esphome/components/hydreon_rgxx/* @functionpointer
esphome/components/hyt271/* @Philippe12 esphome/components/hyt271/* @Philippe12
@ -170,6 +202,9 @@ esphome/components/improv_base/* @esphome/core
esphome/components/improv_serial/* @esphome/core esphome/components/improv_serial/* @esphome/core
esphome/components/ina226/* @Sergio303 @latonita esphome/components/ina226/* @Sergio303 @latonita
esphome/components/ina260/* @mreditor97 esphome/components/ina260/* @mreditor97
esphome/components/ina2xx_base/* @latonita
esphome/components/ina2xx_i2c/* @latonita
esphome/components/ina2xx_spi/* @latonita
esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter esphome/components/integration/* @OttoWinter
@ -192,7 +227,11 @@ esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lock/* @esphome/core esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @sjtrny esphome/components/ltr390/* @latonita @sjtrny
esphome/components/ltr501/* @latonita
esphome/components/ltr_als_ps/* @latonita
esphome/components/lvgl/* @clydebarrow
esphome/components/m5stack_8angle/* @rnauber
esphome/components/matrix_keypad/* @ssieb esphome/components/matrix_keypad/* @ssieb
esphome/components/max31865/* @DAVe3283 esphome/components/max31865/* @DAVe3283
esphome/components/max44009/* @berfenger esphome/components/max44009/* @berfenger
@ -241,7 +280,7 @@ esphome/components/mpl3115a2/* @kbickar
esphome/components/mpu6886/* @fabaff esphome/components/mpu6886/* @fabaff
esphome/components/ms8607/* @e28eta esphome/components/ms8607/* @e28eta
esphome/components/network/* @esphome/core esphome/components/network/* @esphome/core
esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/* @edwardtfn @senexcrenshaw
esphome/components/nextion/binary_sensor/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw
esphome/components/nextion/sensor/* @senexcrenshaw esphome/components/nextion/sensor/* @senexcrenshaw
esphome/components/nextion/switch/* @senexcrenshaw esphome/components/nextion/switch/* @senexcrenshaw
@ -249,6 +288,9 @@ esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz @kbx81 esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra esphome/components/noblex/* @AGalfra
esphome/components/number/* @esphome/core esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @guillempages
esphome/components/opentherm/* @olegtarasov
esphome/components/opt3001/* @ccutrer esphome/components/opt3001/* @ccutrer
esphome/components/ota/* @esphome/core esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
@ -294,9 +336,10 @@ esphome/components/rp2040_pwm/* @jesserockz
esphome/components/rpi_dpi_rgb/* @clydebarrow esphome/components/rpi_dpi_rgb/* @clydebarrow
esphome/components/rtl87xx/* @kuba2k2 esphome/components/rtl87xx/* @kuba2k2
esphome/components/rtttl/* @glmnet esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @jsuanet @paulmonigatti esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
esphome/components/scd4x/* @martgras @sjtrny esphome/components/scd4x/* @martgras @sjtrny
esphome/components/script/* @esphome/core esphome/components/script/* @esphome/core
esphome/components/sdl/* @clydebarrow
esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath esphome/components/sdp3x/* @Azimath
esphome/components/seeed_mr24hpc1/* @limengdu esphome/components/seeed_mr24hpc1/* @limengdu
@ -349,17 +392,20 @@ esphome/components/st7701s/* @clydebarrow
esphome/components/st7735/* @SenexCrenshaw esphome/components/st7735/* @SenexCrenshaw
esphome/components/st7789v/* @kbx81 esphome/components/st7789v/* @kbx81
esphome/components/st7920/* @marsjan155 esphome/components/st7920/* @marsjan155
esphome/components/statsd/* @Links2004
esphome/components/substitutions/* @esphome/core esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter esphome/components/sun/* @OttoWinter
esphome/components/sun_gtil2/* @Mat931 esphome/components/sun_gtil2/* @Mat931
esphome/components/switch/* @esphome/core esphome/components/switch/* @esphome/core
esphome/components/t6615/* @tylermenezes esphome/components/t6615/* @tylermenezes
esphome/components/tca9548a/* @andreashergert1984 esphome/components/tca9548a/* @andreashergert1984
esphome/components/tca9555/* @mobrembski
esphome/components/tcl112/* @glmnet esphome/components/tcl112/* @glmnet
esphome/components/tee501/* @Stock-M esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax esphome/components/teleinfo/* @0hax
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/template/datetime/* @rfdarter esphome/components/template/datetime/* @rfdarter
esphome/components/template/event/* @nohat
esphome/components/template/fan/* @ssieb esphome/components/template/fan/* @ssieb
esphome/components/text/* @mauritskorse esphome/components/text/* @mauritskorse
esphome/components/thermostat/* @kbx81 esphome/components/thermostat/* @kbx81
@ -387,9 +433,11 @@ esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb esphome/components/uart/button/* @ssieb
esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter esphome/components/ultrasonic/* @OttoWinter
esphome/components/update/* @jesserockz
esphome/components/uponor_smatrix/* @kroimon esphome/components/uponor_smatrix/* @kroimon
esphome/components/valve/* @esphome/core esphome/components/valve/* @esphome/core
esphome/components/vbus/* @ssieb esphome/components/vbus/* @ssieb
@ -397,18 +445,31 @@ esphome/components/veml3235/* @kbx81
esphome/components/veml7700/* @latonita esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @willwill2will54 esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/waveshare_epaper/* @clydebarrow esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra esphome/components/web_server_idf/* @dentra
esphome/components/weikai/* @DrCoolZic
esphome/components/weikai_i2c/* @DrCoolZic
esphome/components/weikai_spi/* @DrCoolZic
esphome/components/whirlpool/* @glmnet esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb esphome/components/wiegand/* @ssieb
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
esphome/components/wk2132_i2c/* @DrCoolZic
esphome/components/wk2132_spi/* @DrCoolZic
esphome/components/wk2168_i2c/* @DrCoolZic
esphome/components/wk2168_spi/* @DrCoolZic
esphome/components/wk2204_i2c/* @DrCoolZic
esphome/components/wk2204_spi/* @DrCoolZic
esphome/components/wk2212_i2c/* @DrCoolZic
esphome/components/wk2212_spi/* @DrCoolZic
esphome/components/wl_134/* @hobbypunk90 esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD esphome/components/x9c/* @EtienneMD
esphome/components/xgzp68xx/* @gcormier esphome/components/xgzp68xx/* @gcormier
esphome/components/xiaomi_hhccjcy10/* @fariouche esphome/components/xiaomi_hhccjcy10/* @fariouche
esphome/components/xiaomi_lywsd02mmc/* @juanluss31
esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_mhoc401/* @vevsvevs

View file

@ -33,29 +33,33 @@ RUN \
python3-venv=3.11.2-1+b1 \ python3-venv=3.11.2-1+b1 \
python3-wheel=0.38.4-2 \ python3-wheel=0.38.4-2 \
iputils-ping=3:20221126-1 \ iputils-ping=3:20221126-1 \
git=1:2.39.2-1.1 \ git=1:2.39.5-0+deb12u1 \
curl=7.88.1-10+deb12u5 \ curl=7.88.1-10+deb12u7 \
openssh-client=1:9.2p1-2+deb12u2 \ openssh-client=1:9.2p1-2+deb12u3 \
python3-cffi=1.15.1-5 \ python3-cffi=1.15.1-5 \
libcairo2=1.16.0-7 \ libcairo2=1.16.0-7 \
libmagic1=1:5.44-3 \ libmagic1=1:5.44-3 \
patch=2.7.6-7; \ patch=2.7.6-7 \
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ && ( \
apt-get install -y --no-install-recommends \ ( \
build-essential=12.9 \ [ "$TARGETARCH$TARGETVARIANT" = "armv7" ] && \
python3-dev=3.11.2-1+b1 \ apt-get install -y --no-install-recommends \
zlib1g-dev=1:1.2.13.dfsg-1 \ build-essential=12.9 \
libjpeg-dev=1:2.1.5-2 \ python3-dev=3.11.2-1+b1 \
libfreetype-dev=2.12.1+dfsg-5 \ zlib1g-dev=1:1.2.13.dfsg-1 \
libssl-dev=3.0.11-1~deb12u2 \ libjpeg-dev=1:2.1.5-2 \
libffi-dev=3.4.4-1 \ libfreetype-dev=2.12.1+dfsg-5+deb12u3 \
libopenjp2-7=2.5.0-2 \ libssl-dev=3.0.14-1~deb12u2 \
libtiff6=4.5.0-6+deb12u1 \ libffi-dev=3.4.4-1 \
cargo=0.66.0+ds1-1 \ libopenjp2-7=2.5.0-2 \
pkg-config=1.8.1-1 \ libtiff6=4.5.0-6+deb12u1 \
gcc-arm-linux-gnueabihf=4:12.2.0-3; \ cargo=0.66.0+ds1-1 \
fi; \ pkg-config=1.8.1-1 \
rm -rf \ gcc-arm-linux-gnueabihf=4:12.2.0-3 \
) \
|| [ "$TARGETARCH$TARGETVARIANT" != "armv7" ] \
) \
&& rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*
@ -81,7 +85,8 @@ RUN \
fi; \ fi; \
pip3 install \ pip3 install \
--break-system-packages --no-cache-dir \ --break-system-packages --no-cache-dir \
platformio==6.1.13 \ # Keep platformio version in sync with requirements.txt
platformio==6.1.15 \
# Change some platformio settings # Change some platformio settings
&& platformio settings set enable_telemetry No \ && platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \ && platformio settings set check_platformio_interval 1000000 \
@ -91,14 +96,22 @@ RUN \
# First install requirements to leverage caching when requirements don't change # First install requirements to leverage caching when requirements don't change
# tmpfs is for https://github.com/rust-lang/cargo/issues/8719 # tmpfs is for https://github.com/rust-lang/cargo/issues/8719
COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / COPY requirements.txt requirements_optional.txt /
RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ curl -L https://www.piwheels.org/cp311/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl -o /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \
&& pip3 install --break-system-packages --no-cache-dir /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \
&& rm /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \
&& export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \ fi; \
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \ CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \
pip3 install \ pip3 install \
--break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt
&& /platformio_install_deps.py /platformio.ini --libraries
COPY script/platformio_install_deps.py platformio.ini /
RUN /platformio_install_deps.py /platformio.ini --libraries
# Avoid unsafe git error when container user and file config volume permissions don't match
RUN git config --system --add safe.directory '*'
# ======================= docker-type image ======================= # ======================= docker-type image =======================
@ -110,7 +123,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \ fi; \
pip3 install \ pip3 install \
--break-system-packages --no-cache-dir --no-use-pep517 -e /esphome --break-system-packages --no-cache-dir -e /esphome
# Settings for dashboard # Settings for dashboard
ENV USERNAME="" PASSWORD="" ENV USERNAME="" PASSWORD=""
@ -160,7 +173,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \ fi; \
pip3 install \ pip3 install \
--break-system-packages --no-cache-dir --no-use-pep517 -e /esphome --break-system-packages --no-cache-dir -e /esphome
# Labels # Labels
LABEL \ LABEL \
@ -186,8 +199,8 @@ RUN \
clang-format-13=1:13.0.1-11+b2 \ clang-format-13=1:13.0.1-11+b2 \
clang-tidy-14=1:14.0.6-12 \ clang-tidy-14=1:14.0.6-12 \
patch=2.7.6-7 \ patch=2.7.6-7 \
software-properties-common=0.99.30-4 \ software-properties-common=0.99.30-4.1~deb12u1 \
nano=7.2-1 \ nano=7.2-1+deb12u1 \
build-essential=12.9 \ build-essential=12.9 \
python3-dev=3.11.2-1+b1 \ python3-dev=3.11.2-1+b1 \
&& rm -rf \ && rm -rf \

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# If /cache is mounted, use that as PIO's coredir # If /cache is mounted, use that as PIO's coredir
# otherwise use path in /config (so that PIO packages aren't downloaded on each compile) # otherwise use path in /config (so that PIO packages aren't downloaded on each compile)

View file

@ -1,12 +1,12 @@
# PYTHON_ARGCOMPLETE_OK # PYTHON_ARGCOMPLETE_OK
import argparse import argparse
from datetime import datetime
import functools import functools
import logging import logging
import os import os
import re import re
import sys import sys
import time import time
from datetime import datetime
import argcomplete import argcomplete
@ -18,34 +18,35 @@ from esphome.const import (
CONF_BAUD_RATE, CONF_BAUD_RATE,
CONF_BROKER, CONF_BROKER,
CONF_DEASSERT_RTS_DTR, CONF_DEASSERT_RTS_DTR,
CONF_DISABLED,
CONF_ESPHOME,
CONF_LOGGER, CONF_LOGGER,
CONF_MDNS,
CONF_MQTT,
CONF_NAME, CONF_NAME,
CONF_OTA, CONF_OTA,
CONF_MQTT,
CONF_MDNS,
CONF_DISABLED,
CONF_PASSWORD, CONF_PASSWORD,
CONF_PORT, CONF_PLATFORM,
CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
CONF_PORT,
CONF_SUBSTITUTIONS, CONF_SUBSTITUTIONS,
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_RTL87XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_RTL87XX,
SECRETS_FILES, SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent, is_ip_address from esphome.helpers import indent, is_ip_address, get_bool_env
from esphome.log import Fore, color, setup_log
from esphome.util import ( from esphome.util import (
get_serial_ports,
list_yaml_files,
run_external_command, run_external_command,
run_external_process, run_external_process,
safe_print, safe_print,
list_yaml_files,
get_serial_ports,
) )
from esphome.log import color, setup_log, Fore
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -65,7 +66,7 @@ def choose_prompt(options, purpose: str = None):
f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:' f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:'
) )
for i, (desc, _) in enumerate(options): for i, (desc, _) in enumerate(options):
safe_print(f" [{i+1}] {desc}") safe_print(f" [{i + 1}] {desc}")
while True: while True:
opt = input("(number): ") opt = input("(number): ")
@ -115,6 +116,7 @@ def get_port_type(port):
def run_miniterm(config, port): def run_miniterm(config, port):
import serial import serial
from esphome import platformio_api from esphome import platformio_api
if CONF_LOGGER not in config: if CONF_LOGGER not in config:
@ -330,22 +332,27 @@ def upload_program(config, args, host):
return 1 # Unknown target platform return 1 # Unknown target platform
if CONF_OTA not in config: ota_conf = {}
for ota_item in config.get(CONF_OTA, []):
if ota_item[CONF_PLATFORM] == CONF_ESPHOME:
ota_conf = ota_item
break
if not ota_conf:
raise EsphomeError( raise EsphomeError(
"Cannot upload Over the Air as the config does not include the ota: " f"Cannot upload Over the Air as the {CONF_OTA} configuration is not present or does not include {CONF_PLATFORM}: {CONF_ESPHOME}"
"component"
) )
from esphome import espota2 from esphome import espota2
ota_conf = config[CONF_OTA]
remote_port = ota_conf[CONF_PORT] remote_port = ota_conf[CONF_PORT]
password = ota_conf.get(CONF_PASSWORD, "") password = ota_conf.get(CONF_PASSWORD, "")
if ( if (
not is_ip_address(CORE.address) not is_ip_address(CORE.address) # pylint: disable=too-many-boolean-expressions
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED]) and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
and CONF_MQTT in config and CONF_MQTT in config
and (not args.device or args.device in ("MQTT", "OTA"))
): ):
from esphome import mqtt from esphome import mqtt
@ -482,6 +489,15 @@ def command_run(args, config):
if exit_code != 0: if exit_code != 0:
return exit_code return exit_code
_LOGGER.info("Successfully compiled program.") _LOGGER.info("Successfully compiled program.")
if CORE.is_host:
from esphome.platformio_api import get_idedata
idedata = get_idedata(config)
if idedata is None:
return 1
program_path = idedata.raw["prog_path"]
return run_external_process(program_path)
port = choose_upload_log_host( port = choose_upload_log_host(
default=args.device, default=args.device,
check_default=None, check_default=None,
@ -581,9 +597,10 @@ def command_update_all(args):
def command_idedata(args, config): def command_idedata(args, config):
from esphome import platformio_api
import json import json
from esphome import platformio_api
logging.disable(logging.INFO) logging.disable(logging.INFO)
logging.disable(logging.WARNING) logging.disable(logging.WARNING)
@ -680,7 +697,8 @@ def command_rename(args, config):
os.remove(new_path) os.remove(new_path)
return 1 return 1
os.remove(CORE.config_path) if CORE.config_path != new_path:
os.remove(CORE.config_path)
print(color(Fore.BOLD_GREEN, "SUCCESS")) print(color(Fore.BOLD_GREEN, "SUCCESS"))
print() print()
@ -713,7 +731,11 @@ POST_CONFIG_ACTIONS = {
def parse_args(argv): def parse_args(argv):
options_parser = argparse.ArgumentParser(add_help=False) options_parser = argparse.ArgumentParser(add_help=False)
options_parser.add_argument( options_parser.add_argument(
"-v", "--verbose", help="Enable verbose ESPHome logs.", action="store_true" "-v",
"--verbose",
help="Enable verbose ESPHome logs.",
action="store_true",
default=get_bool_env("ESPHOME_VERBOSE"),
) )
options_parser.add_argument( options_parser.add_argument(
"-q", "--quiet", help="Disable all ESPHome logs.", action="store_true" "-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
@ -731,7 +753,14 @@ def parse_args(argv):
) )
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=f"ESPHome v{const.__version__}", parents=[options_parser] description=f"ESPHome {const.__version__}", parents=[options_parser]
)
parser.add_argument(
"--version",
action="version",
version=f"Version: {const.__version__}",
help="Print the ESPHome version and exit.",
) )
mqtt_options = argparse.ArgumentParser(add_help=False) mqtt_options = argparse.ArgumentParser(add_help=False)
@ -768,7 +797,9 @@ def parse_args(argv):
) )
parser_upload = subparsers.add_parser( parser_upload = subparsers.add_parser(
"upload", help="Validate the configuration and upload the latest binary." "upload",
help="Validate the configuration and upload the latest binary.",
parents=[mqtt_options],
) )
parser_upload.add_argument( parser_upload.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs="+" "configuration", help="Your YAML configuration file(s).", nargs="+"
@ -930,67 +961,6 @@ def parse_args(argv):
# a deprecation warning). # a deprecation warning).
arguments = argv[1:] arguments = argv[1:]
# On Python 3.9+ we can simply set exit_on_error=False in the constructor
def _raise(x):
raise argparse.ArgumentError(None, x)
# First, try new-style parsing, but don't exit in case of failure
try:
# duplicate parser so that we can use the original one to raise errors later on
current_parser = argparse.ArgumentParser(add_help=False, parents=[parser])
current_parser.set_defaults(deprecated_argv_suggestion=None)
current_parser.error = _raise
return current_parser.parse_args(arguments)
except argparse.ArgumentError:
pass
# Second, try compat parsing and rearrange the command-line if it succeeds
# Disable argparse's built-in help option and add it manually to prevent this
# parser from printing the help messagefor the old format when invoked with -h.
compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
compat_parser.add_argument("-h", "--help", action="store_true")
compat_parser.add_argument("configuration", nargs="*")
compat_parser.add_argument(
"command",
choices=[
"config",
"compile",
"upload",
"logs",
"run",
"clean-mqtt",
"wizard",
"mqtt-fingerprint",
"version",
"clean",
"dashboard",
"vscode",
"update-all",
],
)
try:
compat_parser.error = _raise
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration)
unparsed = [
"--device" if arg in ("--upload-port", "--serial-port") else arg
for arg in unparsed
]
arguments = (
arguments[0:last_option]
+ [result.command]
+ result.configuration
+ unparsed
)
deprecated_argv_suggestion = arguments
except argparse.ArgumentError:
# old-style parsing failed, don't suggest any argument
deprecated_argv_suggestion = None
# Finally, run the new-style parser again with the possibly swapped arguments,
# and let it error out if the command is unparsable.
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
argcomplete.autocomplete(parser) argcomplete.autocomplete(parser)
return parser.parse_args(arguments) return parser.parse_args(arguments)
@ -1005,20 +975,6 @@ def run_esphome(argv):
# Show timestamp for dashboard access logs # Show timestamp for dashboard access logs
args.command == "dashboard", args.command == "dashboard",
) )
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
_LOGGER.warning(
"Calling ESPHome with the configuration before the command is deprecated "
"and will be removed in the future. "
)
_LOGGER.warning("Please instead use:")
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion))
if sys.version_info < (3, 8, 0):
_LOGGER.error(
"You're running ESPHome with Python <3.8. ESPHome is no longer compatible "
"with this Python version. Please reinstall ESPHome with Python 3.8+"
)
return 1
if args.command in PRE_CONFIG_ACTIONS: if args.command in PRE_CONFIG_ACTIONS:
try: try:

View file

@ -7,10 +7,10 @@ from esphome.const import (
CONF_ELSE, CONF_ELSE,
CONF_ID, CONF_ID,
CONF_THEN, CONF_THEN,
CONF_TIME,
CONF_TIMEOUT, CONF_TIMEOUT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE_ID, CONF_TYPE_ID,
CONF_TIME,
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
@ -18,10 +18,20 @@ from esphome.util import Registry
def maybe_simple_id(*validators): def maybe_simple_id(*validators):
"""Allow a raw ID to be specified in place of a config block.
If the value that's being validated is a dictionary, it's passed as-is to the specified validators. Otherwise, it's
wrapped in a dict that looks like ``{"id": <value>}``, and that dict is then handed off to the specified validators.
"""
return maybe_conf(CONF_ID, *validators) return maybe_conf(CONF_ID, *validators)
def maybe_conf(conf, *validators): def maybe_conf(conf, *validators):
"""Allow a raw value to be specified in place of a config block.
If the value that's being validated is a dictionary, it's passed as-is to the specified validators. Otherwise, it's
wrapped in a dict that looks like ``{<conf>: <value>}``, and that dict is then handed off to the specified
validators.
(This is a general case of ``maybe_simple_id`` that allows the wrapping key to be something other than ``id``.)
"""
validator = cv.All(*validators) validator = cv.All(*validators)
@schema_extractor("maybe") @schema_extractor("maybe")

View file

@ -8,84 +8,86 @@
# want to break suddenly due to a rename (this file will get backports for features). # want to break suddenly due to a rename (this file will get backports for features).
# pylint: disable=unused-import # pylint: disable=unused-import
from esphome.cpp_generator import ( # noqa from esphome.cpp_generator import ( # noqa: F401
ArrayInitializer,
Expression, Expression,
LineComment,
MockObj,
MockObjClass,
Pvariable,
RawExpression, RawExpression,
RawStatement, RawStatement,
TemplateArguments,
StructInitializer,
ArrayInitializer,
safe_exp,
Statement, Statement,
LineComment, StructInitializer,
progmem_array, TemplateArguments,
static_const_array,
statement,
variable,
with_local_variable,
new_variable,
Pvariable,
new_Pvariable,
add, add,
add_global,
add_library,
add_build_flag, add_build_flag,
add_define, add_define,
add_global,
add_library,
add_platformio_option, add_platformio_option,
get_variable, get_variable,
get_variable_with_full_id, get_variable_with_full_id,
process_lambda,
is_template, is_template,
new_Pvariable,
new_variable,
process_lambda,
progmem_array,
safe_exp,
statement,
static_const_array,
templatable, templatable,
MockObj, variable,
MockObjClass, with_local_variable,
) )
from esphome.cpp_helpers import ( # noqa from esphome.cpp_helpers import ( # noqa: F401
gpio_pin_expression,
register_component,
build_registry_entry, build_registry_entry,
build_registry_list, build_registry_list,
extract_registry_entry_config, extract_registry_entry_config,
register_parented, gpio_pin_expression,
past_safe_mode, past_safe_mode,
register_component,
register_parented,
) )
from esphome.cpp_types import ( # noqa from esphome.cpp_types import ( # noqa: F401
global_ns, NAN,
void, App,
nullptr, Application,
float_, Component,
double, ComponentPtr,
Controller,
EntityBase,
EntityCategory,
ESPTime,
GPIOPin,
InternalGPIOPin,
JsonObject,
JsonObjectConst,
Parented,
PollingComponent,
arduino_json_ns,
bool_, bool_,
const_char_ptr,
double,
esphome_ns,
float_,
global_ns,
gpio_Flags,
int16,
int32,
int64,
int_, int_,
nullptr,
optional,
size_t,
std_ns, std_ns,
std_shared_ptr,
std_string, std_string,
std_string_ref,
std_vector, std_vector,
uint8, uint8,
uint16, uint16,
uint32, uint32,
uint64, uint64,
int16, void,
int32,
int64,
size_t,
const_char_ptr,
NAN,
esphome_ns,
App,
EntityBase,
Component,
ComponentPtr,
PollingComponent,
Application,
optional,
arduino_json_ns,
JsonObject,
JsonObjectConst,
Controller,
GPIOPin,
InternalGPIOPin,
gpio_Flags,
EntityCategory,
Parented,
ESPTime,
) )

View file

@ -4,11 +4,11 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
ICON_ARROW_EXPAND_VERTICAL, ICON_ARROW_EXPAND_VERTICAL,
DEVICE_CLASS_DISTANCE, DEVICE_CLASS_DISTANCE,
UNIT_MILLIMETER,
) )
CODEOWNERS = ["@TH-Braemer"] CODEOWNERS = ["@TH-Braemer"]
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
UNIT_MILLIMETERS = "mm"
a02yyuw_ns = cg.esphome_ns.namespace("a02yyuw") a02yyuw_ns = cg.esphome_ns.namespace("a02yyuw")
A02yyuwComponent = a02yyuw_ns.class_( A02yyuwComponent = a02yyuw_ns.class_(
@ -17,7 +17,7 @@ A02yyuwComponent = a02yyuw_ns.class_(
CONFIG_SCHEMA = sensor.sensor_schema( CONFIG_SCHEMA = sensor.sensor_schema(
A02yyuwComponent, A02yyuwComponent,
unit_of_measurement=UNIT_MILLIMETERS, unit_of_measurement=UNIT_MILLIMETER,
icon=ICON_ARROW_EXPAND_VERTICAL, icon=ICON_ARROW_EXPAND_VERTICAL,
accuracy_decimals=0, accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,

View file

@ -18,11 +18,23 @@ from esphome.components.esp32.const import (
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
adc_ns = cg.esphome_ns.namespace("adc")
"""
From the below patch versions (and 5.2+) ADC_ATTEN_DB_11 is deprecated and replaced with ADC_ATTEN_DB_12.
4.4.7
5.0.5
5.1.3
5.2+
"""
ATTENUATION_MODES = { ATTENUATION_MODES = {
"0db": cg.global_ns.ADC_ATTEN_DB_0, "0db": cg.global_ns.ADC_ATTEN_DB_0,
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
"6db": cg.global_ns.ADC_ATTEN_DB_6, "6db": cg.global_ns.ADC_ATTEN_DB_6,
"11db": cg.global_ns.ADC_ATTEN_DB_11, "11db": adc_ns.ADC_ATTEN_DB_12_COMPAT,
"12db": adc_ns.ADC_ATTEN_DB_12_COMPAT,
"auto": "auto", "auto": "auto",
} }

View file

@ -46,27 +46,27 @@ extern "C"
ADCSensor::setup() { ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040) #if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
pin_->setup(); this->pin_->setup();
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
if (channel1_ != ADC1_CHANNEL_MAX) { if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
if (!autorange_) { if (!this->autorange_) {
adc1_config_channel_atten(channel1_, attenuation_); adc1_config_channel_atten(this->channel1_, this->attenuation_);
} }
} else if (channel2_ != ADC2_CHANNEL_MAX) { } else if (this->channel2_ != ADC2_CHANNEL_MAX) {
if (!autorange_) { if (!this->autorange_) {
adc2_config_channel_atten(channel2_, attenuation_); adc2_config_channel_atten(this->channel2_, this->attenuation_);
} }
} }
// load characteristics for each attenuation // load characteristics for each attenuation
for (int32_t i = 0; i <= ADC_ATTEN_DB_11; i++) { for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) {
auto adc_unit = channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2; auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
1100, // default vref 1100, // default vref
&cal_characteristics_[i]); &this->cal_characteristics_[i]);
switch (cal_value) { switch (cal_value) {
case ESP_ADC_CAL_VAL_EFUSE_VREF: case ESP_ADC_CAL_VAL_EFUSE_VREF:
ESP_LOGV(TAG, "Using eFuse Vref for calibration"); ESP_LOGV(TAG, "Using eFuse Vref for calibration");
@ -99,27 +99,27 @@ void ADCSensor::dump_config() {
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC"); ESP_LOGCONFIG(TAG, " Pin: VCC");
#else #else
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", this->pin_);
#endif #endif
#endif // USE_ESP8266 || USE_LIBRETINY #endif // USE_ESP8266 || USE_LIBRETINY
#ifdef USE_ESP32 #ifdef USE_ESP32
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", this->pin_);
if (autorange_) { if (this->autorange_) {
ESP_LOGCONFIG(TAG, " Attenuation: auto"); ESP_LOGCONFIG(TAG, " Attenuation: auto");
} else { } else {
switch (this->attenuation_) { switch (this->attenuation_) {
case ADC_ATTEN_DB_0: case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db"); ESP_LOGCONFIG(TAG, " Attenuation: 0db");
break; break;
case ADC_ATTEN_DB_2_5: case ADC_ATTEN_DB_2_5:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db"); ESP_LOGCONFIG(TAG, " Attenuation: 2.5db");
break; break;
case ADC_ATTEN_DB_6: case ADC_ATTEN_DB_6:
ESP_LOGCONFIG(TAG, " Attenuation: 6db"); ESP_LOGCONFIG(TAG, " Attenuation: 6db");
break; break;
case ADC_ATTEN_DB_11: case ADC_ATTEN_DB_12_COMPAT:
ESP_LOGCONFIG(TAG, " Attenuation: 11db"); ESP_LOGCONFIG(TAG, " Attenuation: 12db");
break; break;
default: // This is to satisfy the unused ADC_ATTEN_MAX default: // This is to satisfy the unused ADC_ATTEN_MAX
break; break;
@ -134,11 +134,11 @@ void ADCSensor::dump_config() {
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC"); ESP_LOGCONFIG(TAG, " Pin: VCC");
#else #else
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC #endif // USE_ADC_SENSOR_VCC
} }
#endif // USE_RP2040 #endif // USE_RP2040
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
@ -149,14 +149,24 @@ void ADCSensor::update() {
this->publish_state(value_v); this->publish_state(value_v);
} }
void ADCSensor::set_sample_count(uint8_t sample_count) {
if (sample_count != 0) {
this->sample_count_ = sample_count;
}
}
#ifdef USE_ESP8266 #ifdef USE_ESP8266
float ADCSensor::sample() { float ADCSensor::sample() {
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
int32_t raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else #else
int32_t raw = analogRead(this->pin_->get_pin()); // NOLINT raw += analogRead(this->pin_->get_pin()); // NOLINT
#endif #endif
if (output_raw_) { }
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
if (this->output_raw_) {
return raw; return raw;
} }
return raw / 1024.0f; return raw / 1024.0f;
@ -165,77 +175,81 @@ float ADCSensor::sample() {
#ifdef USE_ESP32 #ifdef USE_ESP32
float ADCSensor::sample() { float ADCSensor::sample() {
if (!autorange_) { if (!this->autorange_) {
int raw = -1; uint32_t sum = 0;
if (channel1_ != ADC1_CHANNEL_MAX) { for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw = adc1_get_raw(channel1_); int raw = -1;
} else if (channel2_ != ADC2_CHANNEL_MAX) { if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw); raw = adc1_get_raw(this->channel1_);
} else if (this->channel2_ != ADC2_CHANNEL_MAX) {
adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw);
}
if (raw == -1) {
return NAN;
}
sum += raw;
} }
sum = (sum + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
if (raw == -1) { if (this->output_raw_) {
return NAN; return sum;
} }
if (output_raw_) { uint32_t mv = esp_adc_cal_raw_to_voltage(sum, &this->cal_characteristics_[(int32_t) this->attenuation_]);
return raw;
}
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int32_t) attenuation_]);
return mv / 1000.0f; return mv / 1000.0f;
} }
int raw11 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX; int raw12 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
if (channel1_ != ADC1_CHANNEL_MAX) { if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_11); adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_12_COMPAT);
raw11 = adc1_get_raw(channel1_); raw12 = adc1_get_raw(this->channel1_);
if (raw11 < ADC_MAX) { if (raw12 < ADC_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_6); adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_6);
raw6 = adc1_get_raw(channel1_); raw6 = adc1_get_raw(this->channel1_);
if (raw6 < ADC_MAX) { if (raw6 < ADC_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_2_5); adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_2_5);
raw2 = adc1_get_raw(channel1_); raw2 = adc1_get_raw(this->channel1_);
if (raw2 < ADC_MAX) { if (raw2 < ADC_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_0); adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_0);
raw0 = adc1_get_raw(channel1_); raw0 = adc1_get_raw(this->channel1_);
} }
} }
} }
} else if (channel2_ != ADC2_CHANNEL_MAX) { } else if (this->channel2_ != ADC2_CHANNEL_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_11); adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_12_COMPAT);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw11); adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw12);
if (raw11 < ADC_MAX) { if (raw12 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_6); adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_6);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6); adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6);
if (raw6 < ADC_MAX) { if (raw6 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_2_5); adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_2_5);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2); adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2);
if (raw2 < ADC_MAX) { if (raw2 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_0); adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_0);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0); adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0);
} }
} }
} }
} }
if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) { if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw12 == -1) {
return NAN; return NAN;
} }
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_11]); uint32_t mv12 = esp_adc_cal_raw_to_voltage(raw12, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_12_COMPAT]);
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]); uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]);
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]); uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]); uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]);
// Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC) // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
uint32_t c11 = std::min(raw11, ADC_HALF); uint32_t c12 = std::min(raw12, ADC_HALF);
uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF); uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF);
// max theoretical csum value is 4096*4 = 16384 // max theoretical csum value is 4096*4 = 16384
uint32_t csum = c11 + c6 + c2 + c0; uint32_t csum = c12 + c6 + c2 + c0;
// each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32 // each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32
uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
return mv_scaled / (float) (csum * 1000U); return mv_scaled / (float) (csum * 1000U);
} }
#endif // USE_ESP32 #endif // USE_ESP32
@ -246,8 +260,11 @@ float ADCSensor::sample() {
adc_set_temp_sensor_enabled(true); adc_set_temp_sensor_enabled(true);
delay(1); delay(1);
adc_select_input(4); adc_select_input(4);
uint32_t raw = 0;
int32_t raw = adc_read(); for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read();
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
adc_set_temp_sensor_enabled(false); adc_set_temp_sensor_enabled(false);
if (this->output_raw_) { if (this->output_raw_) {
return raw; return raw;
@ -268,7 +285,11 @@ float ADCSensor::sample() {
adc_gpio_init(pin); adc_gpio_init(pin);
adc_select_input(pin - 26); adc_select_input(pin - 26);
int32_t raw = adc_read(); uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read();
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
#ifdef CYW43_USES_VSYS_PIN #ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) { if (pin == PICO_VSYS_PIN) {
@ -276,7 +297,7 @@ float ADCSensor::sample() {
} }
#endif // CYW43_USES_VSYS_PIN #endif // CYW43_USES_VSYS_PIN
if (output_raw_) { if (this->output_raw_) {
return raw; return raw;
} }
float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0; float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0;
@ -287,10 +308,19 @@ float ADCSensor::sample() {
#ifdef USE_LIBRETINY #ifdef USE_LIBRETINY
float ADCSensor::sample() { float ADCSensor::sample() {
if (output_raw_) { uint32_t raw = 0;
return analogRead(this->pin_->get_pin()); // NOLINT if (this->output_raw_) {
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogRead(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw;
} }
return analogReadVoltage(this->pin_->get_pin()) / 1000.0f; // NOLINT for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw / 1000.0f;
} }
#endif // USE_LIBRETINY #endif // USE_LIBRETINY

View file

@ -1,33 +1,48 @@
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/defines.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h" #include "esphome/components/voltage_sampler/voltage_sampler.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "driver/adc.h"
#include <esp_adc_cal.h> #include <esp_adc_cal.h>
#include "driver/adc.h"
#endif #endif
namespace esphome { namespace esphome {
namespace adc { namespace adc {
#ifdef USE_ESP32
// clang-format off
#if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 7)) || \
(ESP_IDF_VERSION_MAJOR == 5 && \
((ESP_IDF_VERSION_MINOR == 0 && ESP_IDF_VERSION_PATCH >= 5) || \
(ESP_IDF_VERSION_MINOR == 1 && ESP_IDF_VERSION_PATCH >= 3) || \
(ESP_IDF_VERSION_MINOR >= 2)) \
)
// clang-format on
static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_12;
#else
static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11;
#endif
#endif // USE_ESP32
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
public: public:
#ifdef USE_ESP32 #ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the ESP32. /// Set the attenuation for this pin. Only available on the ESP32.
void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; } void set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
void set_channel1(adc1_channel_t channel) { void set_channel1(adc1_channel_t channel) {
channel1_ = channel; this->channel1_ = channel;
channel2_ = ADC2_CHANNEL_MAX; this->channel2_ = ADC2_CHANNEL_MAX;
} }
void set_channel2(adc2_channel_t channel) { void set_channel2(adc2_channel_t channel) {
channel2_ = channel; this->channel2_ = channel;
channel1_ = ADC1_CHANNEL_MAX; this->channel1_ = ADC1_CHANNEL_MAX;
} }
void set_autorange(bool autorange) { autorange_ = autorange; } void set_autorange(bool autorange) { this->autorange_ = autorange; }
#endif #endif
/// Update ADC values /// Update ADC values
@ -38,7 +53,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
/// `HARDWARE_LATE` setup priority /// `HARDWARE_LATE` setup priority
float get_setup_priority() const override; float get_setup_priority() const override;
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { output_raw_ = output_raw; } void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; }
void set_sample_count(uint8_t sample_count);
float sample() override; float sample() override;
#ifdef USE_ESP8266 #ifdef USE_ESP8266
@ -46,12 +62,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#endif #endif
#ifdef USE_RP2040 #ifdef USE_RP2040
void set_is_temperature() { is_temperature_ = true; } void set_is_temperature() { this->is_temperature_ = true; }
#endif #endif
protected: protected:
InternalGPIOPin *pin_; InternalGPIOPin *pin_;
bool output_raw_{false}; bool output_raw_{false};
uint8_t sample_count_{1};
#ifdef USE_RP2040 #ifdef USE_RP2040
bool is_temperature_{false}; bool is_temperature_{false};

View file

@ -1,3 +1,5 @@
import logging
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
@ -19,16 +21,35 @@ from . import (
ATTENUATION_MODES, ATTENUATION_MODES,
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL, ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
adc_ns,
validate_adc_pin, validate_adc_pin,
) )
_LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["voltage_sampler"] AUTO_LOAD = ["voltage_sampler"]
CONF_SAMPLES = "samples"
_attenuation = cv.enum(ATTENUATION_MODES, lower=True)
def validate_config(config): def validate_config(config):
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set") raise cv.Invalid("Automatic attenuation cannot be used when raw output is set")
if config.get(CONF_ATTENUATION, None) == "auto" and config.get(CONF_SAMPLES, 1) > 1:
raise cv.Invalid(
"Automatic attenuation cannot be used when multisampling is set"
)
if config.get(CONF_ATTENUATION) == "11db":
_LOGGER.warning(
"`attenuation: 11db` is deprecated, use `attenuation: 12db` instead"
)
# Alter value here so `config` command prints the recommended change
config[CONF_ATTENUATION] = _attenuation("12db")
return config return config
@ -47,7 +68,6 @@ def final_validate_config(config):
return config return config
adc_ns = cg.esphome_ns.namespace("adc")
ADCSensor = adc_ns.class_( ADCSensor = adc_ns.class_(
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
) )
@ -65,8 +85,9 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_PIN): validate_adc_pin, cv.Required(CONF_PIN): validate_adc_pin,
cv.Optional(CONF_RAW, default=False): cv.boolean, cv.Optional(CONF_RAW, default=False): cv.boolean,
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) cv.only_on_esp32, _attenuation
), ),
cv.Optional(CONF_SAMPLES, default=1): cv.int_range(min=1, max=255),
} }
) )
.extend(cv.polling_component_schema("60s")), .extend(cv.polling_component_schema("60s")),
@ -90,6 +111,7 @@ async def to_code(config):
cg.add(var.set_pin(pin)) cg.add(var.set_pin(pin))
cg.add(var.set_output_raw(config[CONF_RAW])) cg.add(var.set_output_raw(config[CONF_RAW]))
cg.add(var.set_sample_count(config[CONF_SAMPLES]))
if attenuation := config.get(CONF_ATTENUATION): if attenuation := config.get(CONF_ATTENUATION):
if attenuation == "auto": if attenuation == "auto":

View file

@ -11,6 +11,8 @@
#include "ade7880_registers.h" #include "ade7880_registers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace ade7880 { namespace ade7880 {
@ -156,7 +158,7 @@ void ADE7880::update() {
}); });
} }
ESP_LOGD(TAG, "update took %u ms", millis() - start); ESP_LOGD(TAG, "update took %" PRIu32 " ms", millis() - start);
} }
void ADE7880::dump_config() { void ADE7880::dump_config() {
@ -176,9 +178,9 @@ void ADE7880::dump_config() {
LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy); LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy);
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy); LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy);
ESP_LOGCONFIG(TAG, " Calibration:"); ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_a_->current_gain_calibration); ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_a_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_a_->voltage_gain_calibration); ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_a_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_a_->power_gain_calibration); ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_a_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration); ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration);
} }
@ -192,9 +194,9 @@ void ADE7880::dump_config() {
LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy); LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy);
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy); LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy);
ESP_LOGCONFIG(TAG, " Calibration:"); ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_b_->current_gain_calibration); ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_b_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_b_->voltage_gain_calibration); ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_b_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_b_->power_gain_calibration); ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_b_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration); ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration);
} }
@ -208,9 +210,9 @@ void ADE7880::dump_config() {
LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy); LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy);
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy); LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy);
ESP_LOGCONFIG(TAG, " Calibration:"); ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_c_->current_gain_calibration); ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_c_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_c_->voltage_gain_calibration); ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_c_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_c_->power_gain_calibration); ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_c_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration); ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration);
} }
@ -218,7 +220,7 @@ void ADE7880::dump_config() {
ESP_LOGCONFIG(TAG, " Neutral:"); ESP_LOGCONFIG(TAG, " Neutral:");
LOG_SENSOR(" ", "Current", this->channel_n_->current); LOG_SENSOR(" ", "Current", this->channel_n_->current);
ESP_LOGCONFIG(TAG, " Calibration:"); ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_n_->current_gain_calibration); ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_n_->current_gain_calibration);
} }
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);

View file

@ -19,6 +19,7 @@ from esphome.const import (
CONF_RESET_PIN, CONF_RESET_PIN,
CONF_REVERSE_ACTIVE_ENERGY, CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE, CONF_VOLTAGE,
CONF_VOLTAGE_GAIN,
DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
@ -47,7 +48,6 @@ CONF_CURRENT_GAIN = "current_gain"
CONF_IRQ0_PIN = "irq0_pin" CONF_IRQ0_PIN = "irq0_pin"
CONF_IRQ1_PIN = "irq1_pin" CONF_IRQ1_PIN = "irq1_pin"
CONF_POWER_GAIN = "power_gain" CONF_POWER_GAIN = "power_gain"
CONF_VOLTAGE_GAIN = "voltage_gain"
CONF_NEUTRAL = "neutral" CONF_NEUTRAL = "neutral"

View file

@ -1,5 +1,5 @@
import esphome.config_validation as cv import esphome.config_validation as cv
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( CONFIG_SCHEMA = cv.invalid(
"The ade7953 sensor component has been renamed to ade7953_i2c." "The ade7953 sensor component has been renamed to ade7953_i2c."
) )

View file

@ -6,6 +6,7 @@ from esphome.const import (
CONF_IRQ_PIN, CONF_IRQ_PIN,
CONF_VOLTAGE, CONF_VOLTAGE,
CONF_FREQUENCY, CONF_FREQUENCY,
CONF_VOLTAGE_GAIN,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
@ -36,7 +37,6 @@ CONF_POWER_FACTOR_B = "power_factor_b"
CONF_VOLTAGE_PGA_GAIN = "voltage_pga_gain" CONF_VOLTAGE_PGA_GAIN = "voltage_pga_gain"
CONF_CURRENT_PGA_GAIN_A = "current_pga_gain_a" CONF_CURRENT_PGA_GAIN_A = "current_pga_gain_a"
CONF_CURRENT_PGA_GAIN_B = "current_pga_gain_b" CONF_CURRENT_PGA_GAIN_B = "current_pga_gain_b"
CONF_VOLTAGE_GAIN = "voltage_gain"
CONF_CURRENT_GAIN_A = "current_gain_a" CONF_CURRENT_GAIN_A = "current_gain_a"
CONF_CURRENT_GAIN_B = "current_gain_b" CONF_CURRENT_GAIN_B = "current_gain_b"
CONF_ACTIVE_POWER_GAIN_A = "active_power_gain_a" CONF_ACTIVE_POWER_GAIN_A = "active_power_gain_a"

View file

@ -1,6 +1,8 @@
#include "ade7953_base.h" #include "ade7953_base.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace ade7953_base { namespace ade7953_base {
@ -105,7 +107,7 @@ void ADE7953::update() {
this->last_update_ = now; this->last_update_ = now;
// prevent DIV/0 // prevent DIV/0
pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000; pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000;
ESP_LOGVV(TAG, "ADE7953::update() diff=%d pf=%f", diff, pf); ESP_LOGVV(TAG, "ADE7953::update() diff=%" PRIu32 " pf=%f", diff, pf);
} }
// Apparent power // Apparent power

View file

@ -60,7 +60,7 @@ bool AdE7953Spi::ade_read_16(uint16_t reg, uint16_t *value) {
this->write_byte16(reg); this->write_byte16(reg);
this->transfer_byte(0x80); this->transfer_byte(0x80);
uint8_t recv[2]; uint8_t recv[2];
this->read_array(recv, 4); this->read_array(recv, 2);
*value = encode_uint16(recv[0], recv[1]); *value = encode_uint16(recv[0], recv[1]);
this->disable(); this->disable();
return false; return false;

View file

@ -1,5 +1,7 @@
#include "ags10.h" #include "ags10.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace ags10 { namespace ags10 {
static const char *const TAG = "ags10"; static const char *const TAG = "ags10";
@ -35,7 +37,7 @@ void AGS10Component::setup() {
auto resistance = this->read_resistance_(); auto resistance = this->read_resistance_();
if (resistance) { if (resistance) {
ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08X", *resistance); ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08" PRIX32, *resistance);
if (this->resistance_ != nullptr) { if (this->resistance_ != nullptr) {
this->resistance_->publish_state(*resistance); this->resistance_->publish_state(*resistance);
} }

View file

@ -93,8 +93,9 @@ void AHT10Component::restart_read_() {
void AHT10Component::read_data_() { void AHT10Component::read_data_() {
uint8_t data[6]; uint8_t data[6];
if (this->read_count_ > 1) if (this->read_count_ > 1) {
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_)); ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
}
if (this->read(data, 6) != i2c::ERROR_OK) { if (this->read(data, 6) != i2c::ERROR_OK) {
this->status_set_warning("AHT10 read failed, retrying soon"); this->status_set_warning("AHT10 read failed, retrying soon");
this->restart_read_(); this->restart_read_();
@ -119,8 +120,9 @@ void AHT10Component::read_data_() {
return; return;
} }
} }
if (this->read_count_ > 1) if (this->read_count_ > 1) {
ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_)); ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_));
}
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;

View file

@ -14,8 +14,6 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
ESP_LOGD(TAG, "version = %d", value->version); ESP_LOGD(TAG, "version = %d", value->version);
if (value->version == 1) { if (value->version == 1) {
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
if (this->humidity_sensor_ != nullptr) { if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->publish_state(value->humidity / 2.0f); this->humidity_sensor_->publish_state(value->humidity / 2.0f);
} }
@ -43,6 +41,10 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc); this->tvoc_sensor_->publish_state(value->voc);
} }
if (this->illuminance_sensor_ != nullptr) {
this->illuminance_sensor_->publish_state(value->ambientLight);
}
} else { } else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
} }
@ -68,6 +70,7 @@ void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "Radon", this->radon_sensor_); LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_); LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
} }
AirthingsWavePlus::AirthingsWavePlus() { AirthingsWavePlus::AirthingsWavePlus() {

View file

@ -22,6 +22,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; }
protected: protected:
bool is_valid_radon_value_(uint16_t radon); bool is_valid_radon_value_(uint16_t radon);
@ -32,6 +33,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr};
sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *illuminance_sensor_{nullptr};
struct WavePlusReadings { struct WavePlusReadings {
uint8_t version; uint8_t version;

View file

@ -12,6 +12,9 @@ from esphome.const import (
CONF_CO2, CONF_CO2,
UNIT_BECQUEREL_PER_CUBIC_METER, UNIT_BECQUEREL_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_MILLION,
CONF_ILLUMINANCE,
UNIT_LUX,
DEVICE_CLASS_ILLUMINANCE,
) )
DEPENDENCIES = airthings_wave_base.DEPENDENCIES DEPENDENCIES = airthings_wave_base.DEPENDENCIES
@ -45,6 +48,12 @@ CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
device_class=DEVICE_CLASS_CARBON_DIOXIDE, device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
} }
) )
@ -62,3 +71,6 @@ async def to_code(config):
if config_co2 := config.get(CONF_CO2): if config_co2 := config.get(CONF_CO2):
sens = await sensor.new_sensor(config_co2) sens = await sensor.new_sensor(config_co2)
cg.add(var.set_co2(sens)) cg.add(var.set_co2(sens))
if config_illuminance := config.get(CONF_ILLUMINANCE):
sens = await sensor.new_sensor(config_illuminance)
cg.add(var.set_illuminance(sens))

View file

@ -1,14 +1,17 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import maybe_simple_id from esphome.automation import maybe_simple_id
from esphome.core import CORE, coroutine_with_priority import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_CODE,
CONF_ID, CONF_ID,
CONF_MQTT_ID,
CONF_ON_STATE, CONF_ON_STATE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_CODE, CONF_WEB_SERVER_ID,
) )
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11", "@hwstar"] CODEOWNERS = ["@grahambrown11", "@hwstar"]
@ -75,65 +78,72 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
"AlarmControlPanelCondition", automation.Condition "AlarmControlPanelCondition", automation.Condition
) )
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( ALARM_CONTROL_PANEL_SCHEMA = (
{ cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
cv.GenerateID(): cv.declare_id(AlarmControlPanel), .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
cv.Optional(CONF_ON_STATE): automation.validate_automation( .extend(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), cv.GenerateID(): cv.declare_id(AlarmControlPanel),
} cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
), mqtt.MQTTAlarmControlPanelComponent
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation( ),
{ cv.Optional(CONF_ON_STATE): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
), }
cv.Optional(CONF_ON_ARMING): automation.validate_automation( ),
{ cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
), }
cv.Optional(CONF_ON_PENDING): automation.validate_automation( ),
{ cv.Optional(CONF_ON_ARMING): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
), }
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation( ),
{ cv.Optional(CONF_ON_PENDING): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
), }
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation( ),
{ cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
), }
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation( ),
{ cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
), }
cv.Optional(CONF_ON_DISARMED): automation.validate_automation( ),
{ cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
), }
cv.Optional(CONF_ON_CLEARED): automation.validate_automation( ),
{ cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
), }
cv.Optional(CONF_ON_CHIME): automation.validate_automation( ),
{ cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
), }
cv.Optional(CONF_ON_READY): automation.validate_automation( ),
{ cv.Optional(CONF_ON_CHIME): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger),
), }
} ),
cv.Optional(CONF_ON_READY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger),
}
),
}
)
) )
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id( ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
@ -185,6 +195,12 @@ async def setup_alarm_control_panel_core_(var, config):
for conf in config.get(CONF_ON_READY, []): for conf in config.get(CONF_ON_READY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_alarm_control_panel(var, config): async def register_alarm_control_panel(var, config):

View file

@ -97,9 +97,11 @@ void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
switch (event) { switch (event) {
case ESP_GATTC_OPEN_EVT: { case ESP_GATTC_OPEN_EVT: {
this->response_offset_ = 0; if (param->open.status == ESP_GATT_OK) {
this->response_length_ = 0; this->response_offset_ = 0;
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str()); this->response_length_ = 0;
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
}
break; break;
} }
case ESP_GATTC_CONNECT_EVT: { case ESP_GATTC_CONNECT_EVT: {

View file

@ -26,7 +26,9 @@ void Am43::setup() {
void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
switch (event) { switch (event) {
case ESP_GATTC_OPEN_EVT: { case ESP_GATTC_OPEN_EVT: {
this->logged_in_ = false; if (param->open.status == ESP_GATT_OK) {
this->logged_in_ = false;
}
break; break;
} }
case ESP_GATTC_DISCONNECT_EVT: { case ESP_GATTC_DISCONNECT_EVT: {

View file

@ -1,18 +1,27 @@
import logging import logging
from esphome import automation, core from esphome import automation, core
import esphome.codegen as cg
from esphome.components import font from esphome.components import font
import esphome.components.image as espImage import esphome.components.image as espImage
from esphome.components.image import CONF_USE_TRANSPARENCY from esphome.components.image import (
CONF_USE_TRANSPARENCY,
LOCAL_SCHEMA,
SOURCE_LOCAL,
SOURCE_WEB,
WEB_SCHEMA,
)
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import ( from esphome.const import (
CONF_FILE, CONF_FILE,
CONF_ID, CONF_ID,
CONF_PATH,
CONF_RAW_DATA_ID, CONF_RAW_DATA_ID,
CONF_REPEAT, CONF_REPEAT,
CONF_RESIZE, CONF_RESIZE,
CONF_SOURCE,
CONF_TYPE, CONF_TYPE,
CONF_URL,
) )
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
@ -43,6 +52,40 @@ SetFrameAction = animation_ns.class_(
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_) "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
) )
TYPED_FILE_SCHEMA = cv.typed_schema(
{
SOURCE_LOCAL: LOCAL_SCHEMA,
SOURCE_WEB: WEB_SCHEMA,
},
key=CONF_SOURCE,
)
def _file_schema(value):
if isinstance(value, str):
return validate_file_shorthand(value)
return TYPED_FILE_SCHEMA(value)
FILE_SCHEMA = cv.Schema(_file_schema)
def validate_file_shorthand(value):
value = cv.string_strict(value)
if value.startswith("http://") or value.startswith("https://"):
return FILE_SCHEMA(
{
CONF_SOURCE: SOURCE_WEB,
CONF_URL: value,
}
)
return FILE_SCHEMA(
{
CONF_SOURCE: SOURCE_LOCAL,
CONF_PATH: value,
}
)
def validate_cross_dependencies(config): def validate_cross_dependencies(config):
""" """
@ -67,7 +110,7 @@ ANIMATION_SCHEMA = cv.Schema(
cv.All( cv.All(
{ {
cv.Required(CONF_ID): cv.declare_id(Animation_), cv.Required(CONF_ID): cv.declare_id(Animation_),
cv.Required(CONF_FILE): cv.file_, cv.Required(CONF_FILE): FILE_SCHEMA,
cv.Optional(CONF_RESIZE): cv.dimensions, cv.Optional(CONF_RESIZE): cv.dimensions,
cv.Optional(CONF_TYPE, default="BINARY"): cv.enum( cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
espImage.IMAGE_TYPE, upper=True espImage.IMAGE_TYPE, upper=True
@ -124,7 +167,14 @@ async def animation_action_to_code(config, action_id, template_arg, args):
async def to_code(config): async def to_code(config):
from PIL import Image from PIL import Image
path = CORE.relative_config_path(config[CONF_FILE]) conf_file = config[CONF_FILE]
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
path = CORE.relative_config_path(conf_file[CONF_PATH])
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
path = espImage.compute_local_image_path(conf_file).as_posix()
else:
raise core.EsphomeError(f"Unknown animation source: {conf_file[CONF_SOURCE]}")
try: try:
image = Image.open(path) image = Image.open(path)
except Exception as e: except Exception as e:
@ -136,13 +186,12 @@ async def to_code(config):
new_width_max, new_height_max = config[CONF_RESIZE] new_width_max, new_height_max = config[CONF_RESIZE]
ratio = min(new_width_max / width, new_height_max / height) ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio) width, height = int(width * ratio), int(height * ratio)
else: elif width > 500 or height > 500:
if width > 500 or height > 500: _LOGGER.warning(
_LOGGER.warning( 'The image "%s" you requested is very big. Please consider'
'The image "%s" you requested is very big. Please consider' " using the resize parameter.",
" using the resize parameter.", path,
path, )
)
transparent = config[CONF_USE_TRANSPARENCY] transparent = config[CONF_USE_TRANSPARENCY]
@ -157,7 +206,7 @@ async def to_code(config):
pixels = list(frame.getdata()) pixels = list(frame.getdata())
if len(pixels) != height * width: if len(pixels) != height * width:
raise core.EsphomeError( raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
) )
for pix, a in pixels: for pix, a in pixels:
if transparent: if transparent:
@ -180,7 +229,7 @@ async def to_code(config):
pixels = list(frame.getdata()) pixels = list(frame.getdata())
if len(pixels) != height * width: if len(pixels) != height * width:
raise core.EsphomeError( raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
) )
for pix in pixels: for pix in pixels:
data[pos] = pix[0] data[pos] = pix[0]
@ -203,7 +252,7 @@ async def to_code(config):
pixels = list(frame.getdata()) pixels = list(frame.getdata())
if len(pixels) != height * width: if len(pixels) != height * width:
raise core.EsphomeError( raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
) )
for r, g, b, a in pixels: for r, g, b, a in pixels:
if transparent: if transparent:
@ -232,7 +281,7 @@ async def to_code(config):
pixels = list(frame.getdata()) pixels = list(frame.getdata())
if len(pixels) != height * width: if len(pixels) != height * width:
raise core.EsphomeError( raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
) )
for r, g, b, a in pixels: for r, g, b, a in pixels:
R = r >> 3 R = r >> 3
@ -259,6 +308,8 @@ async def to_code(config):
if transparent: if transparent:
alpha = image.split()[-1] alpha = image.split()[-1]
has_alpha = alpha.getextrema()[0] < 0xFF has_alpha = alpha.getextrema()[0] < 0xFF
else:
has_alpha = False
frame = image.convert("1", dither=Image.Dither.NONE) frame = image.convert("1", dither=Image.Dither.NONE)
if CONF_RESIZE in config: if CONF_RESIZE in config:
frame = frame.resize([width, height]) frame = frame.resize([width, height])

View file

@ -0,0 +1,4 @@
# Based on this datasheet:
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
CODEOWNERS = ["@aodrenah"]

View file

@ -0,0 +1,151 @@
// Based on this datasheet:
// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
#include "apds9306.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace apds9306 {
static const char *const TAG = "apds9306";
enum { // APDS9306 registers
APDS9306_MAIN_CTRL = 0x00,
APDS9306_ALS_MEAS_RATE = 0x04,
APDS9306_ALS_GAIN = 0x05,
APDS9306_PART_ID = 0x06,
APDS9306_MAIN_STATUS = 0x07,
APDS9306_CLEAR_DATA_0 = 0x0A, // LSB
APDS9306_CLEAR_DATA_1 = 0x0B,
APDS9306_CLEAR_DATA_2 = 0x0C, // MSB
APDS9306_ALS_DATA_0 = 0x0D, // LSB
APDS9306_ALS_DATA_1 = 0x0E,
APDS9306_ALS_DATA_2 = 0x0F, // MSB
APDS9306_INT_CFG = 0x19,
APDS9306_INT_PERSISTENCE = 0x1A,
APDS9306_ALS_THRES_UP_0 = 0x21, // LSB
APDS9306_ALS_THRES_UP_1 = 0x22,
APDS9306_ALS_THRES_UP_2 = 0x23, // MSB
APDS9306_ALS_THRES_LOW_0 = 0x24, // LSB
APDS9306_ALS_THRES_LOW_1 = 0x25,
APDS9306_ALS_THRES_LOW_2 = 0x26, // MSB
APDS9306_ALS_THRES_VAR = 0x27
};
#define APDS9306_ERROR_CHECK(func, error) \
if (!(func)) { \
ESP_LOGE(TAG, error); \
this->mark_failed(); \
return; \
}
#define APDS9306_WARNING_CHECK(func, warning) \
if (!(func)) { \
ESP_LOGW(TAG, warning); \
this->status_set_warning(); \
return; \
}
#define APDS9306_WRITE_BYTE(reg, value) \
ESP_LOGV(TAG, "Writing 0x%02x to 0x%02x", value, reg); \
if (!this->write_byte(reg, value)) { \
ESP_LOGE(TAG, "Failed writing 0x%02x to 0x%02x", value, reg); \
this->mark_failed(); \
return; \
}
void APDS9306::setup() {
ESP_LOGCONFIG(TAG, "Setting up APDS9306...");
uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (id != 0xB1 && id != 0xB3) { // 0xB1 for APDS9306 0xB3 for APDS9306-065
this->error_code_ = WRONG_ID;
this->mark_failed();
return;
}
// ALS resolution and measurement, see datasheet or init.py for options
uint8_t als_meas_rate = ((this->bit_width_ & 0x07) << 4) | (this->measurement_rate_ & 0x07);
APDS9306_WRITE_BYTE(APDS9306_ALS_MEAS_RATE, als_meas_rate);
// ALS gain, see datasheet or init.py for options
uint8_t als_gain = (this->gain_ & 0x07);
APDS9306_WRITE_BYTE(APDS9306_ALS_GAIN, als_gain);
// Set to standby mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00);
// Check for data, clear main status
uint8_t status;
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
// Set to active mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
ESP_LOGCONFIG(TAG, "APDS9306 setup complete");
}
void APDS9306::dump_config() {
LOG_SENSOR("", "APDS9306", this);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with APDS9306 failed!");
break;
case WRONG_ID:
ESP_LOGE(TAG, "APDS9306 has invalid id!");
break;
default:
ESP_LOGE(TAG, "Setting up APDS9306 registers failed!");
break;
}
}
ESP_LOGCONFIG(TAG, " Gain: %u", AMBIENT_LIGHT_GAIN_VALUES[this->gain_]);
ESP_LOGCONFIG(TAG, " Measurement rate: %u", MEASUREMENT_RATE_VALUES[this->measurement_rate_]);
ESP_LOGCONFIG(TAG, " Measurement Resolution/Bit width: %d", MEASUREMENT_BIT_WIDTH_VALUES[this->bit_width_]);
LOG_UPDATE_INTERVAL(this);
}
void APDS9306::update() {
// Check for new data
uint8_t status;
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
this->status_clear_warning();
if (!(status &= 0b00001000)) { // No new data
return;
}
// Set to standby mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00);
// Clear MAIN STATUS
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
uint8_t als_data[3];
APDS9306_WARNING_CHECK(this->read_bytes(APDS9306_ALS_DATA_0, als_data, 3), "Reading ALS data has failed.");
// Set to active mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
uint32_t light_level = 0x00 | encode_uint24(als_data[2], als_data[1], als_data[0]);
float lux = ((float) light_level / AMBIENT_LIGHT_GAIN_VALUES[this->gain_]) *
(100.0f / MEASUREMENT_RATE_VALUES[this->measurement_rate_]);
ESP_LOGD(TAG, "Got illuminance=%.1flx from", lux);
this->publish_state(lux);
}
} // namespace apds9306
} // namespace esphome

View file

@ -0,0 +1,66 @@
// Based on this datasheet:
// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace apds9306 {
enum MeasurementBitWidth : uint8_t {
MEASUREMENT_BIT_WIDTH_20 = 0,
MEASUREMENT_BIT_WIDTH_19 = 1,
MEASUREMENT_BIT_WIDTH_18 = 2,
MEASUREMENT_BIT_WIDTH_17 = 3,
MEASUREMENT_BIT_WIDTH_16 = 4,
MEASUREMENT_BIT_WIDTH_13 = 5,
};
static const uint8_t MEASUREMENT_BIT_WIDTH_VALUES[] = {20, 19, 18, 17, 16, 13};
enum MeasurementRate : uint8_t {
MEASUREMENT_RATE_25 = 0,
MEASUREMENT_RATE_50 = 1,
MEASUREMENT_RATE_100 = 2,
MEASUREMENT_RATE_200 = 3,
MEASUREMENT_RATE_500 = 4,
MEASUREMENT_RATE_1000 = 5,
MEASUREMENT_RATE_2000 = 6,
};
static const uint16_t MEASUREMENT_RATE_VALUES[] = {25, 50, 100, 200, 500, 1000, 2000};
enum AmbientLightGain : uint8_t {
AMBIENT_LIGHT_GAIN_1 = 0,
AMBIENT_LIGHT_GAIN_3 = 1,
AMBIENT_LIGHT_GAIN_6 = 2,
AMBIENT_LIGHT_GAIN_9 = 3,
AMBIENT_LIGHT_GAIN_18 = 4,
};
static const uint8_t AMBIENT_LIGHT_GAIN_VALUES[] = {1, 3, 6, 9, 18};
class APDS9306 : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void dump_config() override;
void update() override;
void set_bit_width(MeasurementBitWidth bit_width) { this->bit_width_ = bit_width; }
void set_measurement_rate(MeasurementRate measurement_rate) { this->measurement_rate_ = measurement_rate; }
void set_ambient_light_gain(AmbientLightGain gain) { this->gain_ = gain; }
protected:
enum ErrorCode {
NONE = 0,
COMMUNICATION_FAILED,
WRONG_ID,
} error_code_{NONE};
MeasurementBitWidth bit_width_;
MeasurementRate measurement_rate_;
AmbientLightGain gain_;
};
} // namespace apds9306
} // namespace esphome

View file

@ -0,0 +1,95 @@
# Based on this datasheet:
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_GAIN,
DEVICE_CLASS_ILLUMINANCE,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
)
DEPENDENCIES = ["i2c"]
CONF_APDS9306_ID = "apds9306_id"
CONF_BIT_WIDTH = "bit_width"
CONF_MEASUREMENT_RATE = "measurement_rate"
apds9306_ns = cg.esphome_ns.namespace("apds9306")
APDS9306 = apds9306_ns.class_(
"APDS9306", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
MeasurementBitWidth = apds9306_ns.enum("MeasurementBitWidth")
MeasurementRate = apds9306_ns.enum("MeasurementRate")
AmbientLightGain = apds9306_ns.enum("AmbientLightGain")
MEASUREMENT_BIT_WIDTHS = {
20: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_20,
19: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_19,
18: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_18,
17: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_17,
16: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_16,
13: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_13,
}
MEASUREMENT_RATES = {
25: MeasurementRate.MEASUREMENT_RATE_25,
50: MeasurementRate.MEASUREMENT_RATE_50,
100: MeasurementRate.MEASUREMENT_RATE_100,
200: MeasurementRate.MEASUREMENT_RATE_200,
500: MeasurementRate.MEASUREMENT_RATE_500,
1000: MeasurementRate.MEASUREMENT_RATE_1000,
2000: MeasurementRate.MEASUREMENT_RATE_2000,
}
AMBIENT_LIGHT_GAINS = {
1: AmbientLightGain.AMBIENT_LIGHT_GAIN_1,
3: AmbientLightGain.AMBIENT_LIGHT_GAIN_3,
6: AmbientLightGain.AMBIENT_LIGHT_GAIN_6,
9: AmbientLightGain.AMBIENT_LIGHT_GAIN_9,
18: AmbientLightGain.AMBIENT_LIGHT_GAIN_18,
}
def _validate_measurement_rate(value):
value = cv.positive_time_period_milliseconds(value)
return cv.enum(MEASUREMENT_RATES, int=True)(value.total_milliseconds)
CONFIG_SCHEMA = (
sensor.sensor_schema(
APDS9306,
unit_of_measurement=UNIT_LUX,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_LIGHTBULB,
)
.extend(
{
cv.Optional(CONF_GAIN, default="1"): cv.enum(AMBIENT_LIGHT_GAINS, int=True),
cv.Optional(CONF_BIT_WIDTH, default="18"): cv.enum(
MEASUREMENT_BIT_WIDTHS, int=True
),
cv.Optional(
CONF_MEASUREMENT_RATE, default="100ms"
): _validate_measurement_rate,
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x52))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_bit_width(config[CONF_BIT_WIDTH]))
cg.add(var.set_measurement_rate(config[CONF_MEASUREMENT_RATE]))
cg.add(var.set_ambient_light_gain(config[CONF_GAIN]))

View file

@ -1,25 +1,27 @@
import base64 import base64
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import Condition from esphome.automation import Condition
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ACTION,
CONF_ACTIONS,
CONF_DATA, CONF_DATA,
CONF_DATA_TEMPLATE, CONF_DATA_TEMPLATE,
CONF_EVENT,
CONF_ID, CONF_ID,
CONF_KEY, CONF_KEY,
CONF_ON_CLIENT_CONNECTED,
CONF_ON_CLIENT_DISCONNECTED,
CONF_PASSWORD, CONF_PASSWORD,
CONF_PORT, CONF_PORT,
CONF_REBOOT_TIMEOUT, CONF_REBOOT_TIMEOUT,
CONF_SERVICE, CONF_SERVICE,
CONF_VARIABLES,
CONF_SERVICES, CONF_SERVICES,
CONF_TRIGGER_ID,
CONF_EVENT,
CONF_TAG, CONF_TAG,
CONF_ON_CLIENT_CONNECTED, CONF_TRIGGER_ID,
CONF_ON_CLIENT_DISCONNECTED, CONF_VARIABLES,
) )
from esphome.core import coroutine_with_priority from esphome.core import coroutine_with_priority
@ -63,40 +65,51 @@ def validate_encryption_key(value):
return value return value
CONFIG_SCHEMA = cv.Schema( ACTIONS_SCHEMA = automation.validate_automation(
{ {
cv.GenerateID(): cv.declare_id(APIServer), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
cv.Optional(CONF_PORT, default=6053): cv.port, cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.valid_name,
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.valid_name,
cv.Optional( cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
CONF_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_SERVICES): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
cv.Required(CONF_SERVICE): cv.valid_name,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
{
cv.validate_id_name: cv.one_of(
*SERVICE_ARG_NATIVE_TYPES, lower=True
),
}
),
} }
), ),
cv.Optional(CONF_ENCRYPTION): cv.Schema( },
{ cv.All(
cv.Required(CONF_KEY): validate_encryption_key, cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
} cv.rename_key(CONF_SERVICE, CONF_ACTION),
), ),
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( )
single=True
), CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( cv.Schema(
single=True {
), cv.GenerateID(): cv.declare_id(APIServer),
} cv.Optional(CONF_PORT, default=6053): cv.port,
).extend(cv.COMPONENT_SCHEMA) cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
cv.Optional(
CONF_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds,
cv.Exclusive(
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
cv.Optional(CONF_ENCRYPTION): cv.Schema(
{
cv.Required(CONF_KEY): validate_encryption_key,
}
),
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
single=True
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
)
@coroutine_with_priority(40.0) @coroutine_with_priority(40.0)
@ -108,7 +121,7 @@ async def to_code(config):
cg.add(var.set_password(config[CONF_PASSWORD])) cg.add(var.set_password(config[CONF_PASSWORD]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
for conf in config.get(CONF_SERVICES, []): for conf in config.get(CONF_ACTIONS, []):
template_args = [] template_args = []
func_args = [] func_args = []
service_arg_names = [] service_arg_names = []
@ -119,7 +132,7 @@ async def to_code(config):
service_arg_names.append(name) service_arg_names.append(name)
templ = cg.TemplateArguments(*template_args) templ = cg.TemplateArguments(*template_args)
trigger = cg.new_Pvariable( trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
) )
cg.add(var.register_user_service(trigger)) cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf) await automation.build_automation(trigger, func_args, conf)
@ -142,7 +155,7 @@ async def to_code(config):
decoded = base64.b64decode(encryption_config[CONF_KEY]) decoded = base64.b64decode(encryption_config[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded))) cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE") cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.4") cg.add_library("esphome/noise-c", "0.1.6")
else: else:
cg.add_define("USE_API_PLAINTEXT") cg.add_define("USE_API_PLAINTEXT")
@ -152,28 +165,43 @@ async def to_code(config):
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)}) KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
{ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
cv.GenerateID(): cv.use_id(APIServer), cv.Schema(
cv.Required(CONF_SERVICE): cv.templatable(cv.string), {
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, cv.GenerateID(): cv.use_id(APIServer),
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.templatable(
cv.Optional(CONF_VARIABLES, default={}): cv.Schema( cv.string
{cv.string: cv.returning_lambda} ),
), cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.templatable(
} cv.string
),
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
{cv.string: cv.returning_lambda}
),
}
),
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
cv.rename_key(CONF_SERVICE, CONF_ACTION),
) )
@automation.register_action(
"homeassistant.action",
HomeAssistantServiceCallAction,
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
)
@automation.register_action( @automation.register_action(
"homeassistant.service", "homeassistant.service",
HomeAssistantServiceCallAction, HomeAssistantServiceCallAction,
HOMEASSISTANT_SERVICE_ACTION_SCHEMA, HOMEASSISTANT_ACTION_ACTION_SCHEMA,
) )
async def homeassistant_service_to_code(config, action_id, template_arg, args): async def homeassistant_service_to_code(config, action_id, template_arg, args):
serv = await cg.get_variable(config[CONF_ID]) serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, False) var = cg.new_Pvariable(action_id, template_arg, serv, False)
templ = await cg.templatable(config[CONF_SERVICE], args, None) templ = await cg.templatable(config[CONF_ACTION], args, None)
cg.add(var.set_service(templ)) cg.add(var.set_service(templ))
for key, value in config[CONF_DATA].items(): for key, value in config[CONF_DATA].items():
templ = await cg.templatable(value, args, None) templ = await cg.templatable(value, args, None)

View file

@ -47,6 +47,8 @@ service APIConnection {
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
rpc date_command (DateCommandRequest) returns (void) {} rpc date_command (DateCommandRequest) returns (void) {}
rpc time_command (TimeCommandRequest) returns (void) {} rpc time_command (TimeCommandRequest) returns (void) {}
rpc datetime_command (DateTimeCommandRequest) returns (void) {}
rpc update_command (UpdateCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
@ -60,6 +62,8 @@ service APIConnection {
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {}
rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {}
rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
} }
@ -684,6 +688,7 @@ message SubscribeHomeAssistantStateResponse {
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
string entity_id = 1; string entity_id = 1;
string attribute = 2; string attribute = 2;
bool once = 3;
} }
message HomeAssistantStateResponse { message HomeAssistantStateResponse {
@ -1104,6 +1109,19 @@ enum MediaPlayerCommand {
MEDIA_PLAYER_COMMAND_MUTE = 3; MEDIA_PLAYER_COMMAND_MUTE = 3;
MEDIA_PLAYER_COMMAND_UNMUTE = 4; MEDIA_PLAYER_COMMAND_UNMUTE = 4;
} }
enum MediaPlayerFormatPurpose {
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0;
MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1;
}
message MediaPlayerSupportedFormat {
option (ifdef) = "USE_MEDIA_PLAYER";
string format = 1;
uint32 sample_rate = 2;
uint32 num_channels = 3;
MediaPlayerFormatPurpose purpose = 4;
uint32 sample_bytes = 5;
}
message ListEntitiesMediaPlayerResponse { message ListEntitiesMediaPlayerResponse {
option (id) = 63; option (id) = 63;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
@ -1119,6 +1137,8 @@ message ListEntitiesMediaPlayerResponse {
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
bool supports_pause = 8; bool supports_pause = 8;
repeated MediaPlayerSupportedFormat supported_formats = 9;
} }
message MediaPlayerStateResponse { message MediaPlayerStateResponse {
option (id) = 64; option (id) = 64;
@ -1146,6 +1166,9 @@ message MediaPlayerCommandRequest {
bool has_media_url = 6; bool has_media_url = 6;
string media_url = 7; string media_url = 7;
bool has_announcement = 8;
bool announcement = 9;
} }
// ==================== BLUETOOTH ==================== // ==================== BLUETOOTH ====================
@ -1513,6 +1536,72 @@ message VoiceAssistantAudio {
bool end = 2; bool end = 2;
} }
enum VoiceAssistantTimerEvent {
VOICE_ASSISTANT_TIMER_STARTED = 0;
VOICE_ASSISTANT_TIMER_UPDATED = 1;
VOICE_ASSISTANT_TIMER_CANCELLED = 2;
VOICE_ASSISTANT_TIMER_FINISHED = 3;
}
message VoiceAssistantTimerEventResponse {
option (id) = 115;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
VoiceAssistantTimerEvent event_type = 1;
string timer_id = 2;
string name = 3;
uint32 total_seconds = 4;
uint32 seconds_left = 5;
bool is_active = 6;
}
message VoiceAssistantAnnounceRequest {
option (id) = 119;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
string media_id = 1;
string text = 2;
}
message VoiceAssistantAnnounceFinished {
option (id) = 120;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_VOICE_ASSISTANT";
bool success = 1;
}
message VoiceAssistantWakeWord {
string id = 1;
string wake_word = 2;
repeated string trained_languages = 3;
}
message VoiceAssistantConfigurationRequest {
option (id) = 121;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
}
message VoiceAssistantConfigurationResponse {
option (id) = 122;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_VOICE_ASSISTANT";
repeated VoiceAssistantWakeWord available_wake_words = 1;
repeated string active_wake_words = 2;
uint32 max_active_wake_words = 3;
}
message VoiceAssistantSetConfiguration {
option (id) = 123;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
repeated string active_wake_words = 1;
}
// ==================== ALARM CONTROL PANEL ==================== // ==================== ALARM CONTROL PANEL ====================
enum AlarmControlPanelState { enum AlarmControlPanelState {
@ -1702,6 +1791,32 @@ message TimeCommandRequest {
uint32 second = 4; uint32 second = 4;
} }
// ==================== EVENT ====================
message ListEntitiesEventResponse {
option (id) = 107;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_EVENT";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
repeated string event_types = 9;
}
message EventResponse {
option (id) = 108;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_EVENT";
fixed32 key = 1;
string event_type = 2;
}
// ==================== VALVE ==================== // ==================== VALVE ====================
message ListEntitiesValveResponse { message ListEntitiesValveResponse {
@ -1751,3 +1866,88 @@ message ValveCommandRequest {
float position = 3; float position = 3;
bool stop = 4; bool stop = 4;
} }
// ==================== DATETIME DATETIME ====================
message ListEntitiesDateTimeResponse {
option (id) = 112;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_DATETIME";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
}
message DateTimeStateResponse {
option (id) = 113;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_DATETIME";
option (no_delay) = true;
fixed32 key = 1;
// If the datetime does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
bool missing_state = 2;
fixed32 epoch_seconds = 3;
}
message DateTimeCommandRequest {
option (id) = 114;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_DATETIME_DATETIME";
option (no_delay) = true;
fixed32 key = 1;
fixed32 epoch_seconds = 2;
}
// ==================== UPDATE ====================
message ListEntitiesUpdateResponse {
option (id) = 116;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_UPDATE";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
}
message UpdateStateResponse {
option (id) = 117;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_UPDATE";
option (no_delay) = true;
fixed32 key = 1;
bool missing_state = 2;
bool in_progress = 3;
bool has_progress = 4;
float progress = 5;
string current_version = 6;
string latest_version = 7;
string title = 8;
string release_summary = 9;
string release_url = 10;
}
enum UpdateCommand {
UPDATE_COMMAND_NONE = 0;
UPDATE_COMMAND_UPDATE = 1;
UPDATE_COMMAND_CHECK = 2;
}
message UpdateCommandRequest {
option (id) = 118;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_UPDATE";
option (no_delay) = true;
fixed32 key = 1;
UpdateCommand command = 2;
}

View file

@ -179,6 +179,7 @@ void APIConnection::loop() {
SubscribeHomeAssistantStateResponse resp; SubscribeHomeAssistantStateResponse resp;
resp.entity_id = it.entity_id; resp.entity_id = it.entity_id;
resp.attribute = it.attribute.value(); resp.attribute = it.attribute.value();
resp.once = it.once;
if (this->send_subscribe_home_assistant_state_response(resp)) { if (this->send_subscribe_home_assistant_state_response(resp)) {
state_subs_at_++; state_subs_at_++;
} }
@ -772,6 +773,44 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
} }
#endif #endif
#ifdef USE_DATETIME_DATETIME
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
if (!this->state_subscription_)
return false;
DateTimeStateResponse resp{};
resp.key = datetime->get_object_id_hash();
resp.missing_state = !datetime->has_state();
if (datetime->has_state()) {
ESPTime state = datetime->state_as_esptime();
resp.epoch_seconds = state.timestamp;
}
return this->send_date_time_state_response(resp);
}
bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) {
ListEntitiesDateTimeResponse msg;
msg.key = datetime->get_object_id_hash();
msg.object_id = datetime->get_object_id();
if (datetime->has_own_name())
msg.name = datetime->get_name();
msg.unique_id = get_default_unique_id("datetime", datetime);
msg.icon = datetime->get_icon();
msg.disabled_by_default = datetime->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(datetime->get_entity_category());
return this->send_list_entities_date_time_response(msg);
}
void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key);
if (datetime == nullptr)
return;
auto call = datetime->make_call();
call.set_datetime(msg.epoch_seconds);
call.perform();
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool APIConnection::send_text_state(text::Text *text, std::string state) { bool APIConnection::send_text_state(text::Text *text, std::string state) {
if (!this->state_subscription_) if (!this->state_subscription_)
@ -964,7 +1003,11 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla
MediaPlayerStateResponse resp{}; MediaPlayerStateResponse resp{};
resp.key = media_player->get_object_id_hash(); resp.key = media_player->get_object_id_hash();
resp.state = static_cast<enums::MediaPlayerState>(media_player->state);
media_player::MediaPlayerState report_state = media_player->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING
? media_player::MEDIA_PLAYER_STATE_PLAYING
: media_player->state;
resp.state = static_cast<enums::MediaPlayerState>(report_state);
resp.volume = media_player->volume; resp.volume = media_player->volume;
resp.muted = media_player->is_muted(); resp.muted = media_player->is_muted();
return this->send_media_player_state_response(resp); return this->send_media_player_state_response(resp);
@ -983,6 +1026,16 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
auto traits = media_player->get_traits(); auto traits = media_player->get_traits();
msg.supports_pause = traits.get_supports_pause(); msg.supports_pause = traits.get_supports_pause();
for (auto &supported_format : traits.get_supported_formats()) {
MediaPlayerSupportedFormat media_format;
media_format.format = supported_format.format;
media_format.sample_rate = supported_format.sample_rate;
media_format.num_channels = supported_format.num_channels;
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
media_format.sample_bytes = supported_format.sample_bytes;
msg.supported_formats.push_back(media_format);
}
return this->send_list_entities_media_player_response(msg); return this->send_list_entities_media_player_response(msg);
} }
void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
@ -1000,6 +1053,9 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
if (msg.has_media_url) { if (msg.has_media_url) {
call.set_media_url(msg.media_url); call.set_media_url(msg.media_url);
} }
if (msg.has_announcement) {
call.set_announcement(msg.announcement);
}
call.perform(); call.perform();
} }
#endif #endif
@ -1148,6 +1204,61 @@ void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
voice_assistant::global_voice_assistant->on_audio(msg); voice_assistant::global_voice_assistant->on_audio(msg);
} }
}; };
void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_timer_event(msg);
}
};
void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_announce(msg);
}
}
VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) {
VoiceAssistantConfigurationResponse resp;
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return resp;
}
auto &config = voice_assistant::global_voice_assistant->get_configuration();
for (auto &wake_word : config.available_wake_words) {
VoiceAssistantWakeWord resp_wake_word;
resp_wake_word.id = wake_word.id;
resp_wake_word.wake_word = wake_word.wake_word;
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
resp.available_wake_words.push_back(std::move(resp_wake_word));
}
for (auto &wake_word_id : config.active_wake_words) {
resp.active_wake_words.push_back(wake_word_id);
}
resp.max_active_wake_words = config.max_active_wake_words;
}
return resp;
}
void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
}
}
#endif #endif
@ -1209,6 +1320,88 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
} }
#endif #endif
#ifdef USE_EVENT
bool APIConnection::send_event(event::Event *event, std::string event_type) {
EventResponse resp{};
resp.key = event->get_object_id_hash();
resp.event_type = std::move(event_type);
return this->send_event_response(resp);
}
bool APIConnection::send_event_info(event::Event *event) {
ListEntitiesEventResponse msg;
msg.key = event->get_object_id_hash();
msg.object_id = event->get_object_id();
if (event->has_own_name())
msg.name = event->get_name();
msg.unique_id = get_default_unique_id("event", event);
msg.icon = event->get_icon();
msg.disabled_by_default = event->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(event->get_entity_category());
msg.device_class = event->get_device_class();
for (const auto &event_type : event->get_event_types())
msg.event_types.push_back(event_type);
return this->send_list_entities_event_response(msg);
}
#endif
#ifdef USE_UPDATE
bool APIConnection::send_update_state(update::UpdateEntity *update) {
if (!this->state_subscription_)
return false;
UpdateStateResponse resp{};
resp.key = update->get_object_id_hash();
resp.missing_state = !update->has_state();
if (update->has_state()) {
resp.in_progress = update->state == update::UpdateState::UPDATE_STATE_INSTALLING;
if (update->update_info.has_progress) {
resp.has_progress = true;
resp.progress = update->update_info.progress;
}
resp.current_version = update->update_info.current_version;
resp.latest_version = update->update_info.latest_version;
resp.title = update->update_info.title;
resp.release_summary = update->update_info.summary;
resp.release_url = update->update_info.release_url;
}
return this->send_update_state_response(resp);
}
bool APIConnection::send_update_info(update::UpdateEntity *update) {
ListEntitiesUpdateResponse msg;
msg.key = update->get_object_id_hash();
msg.object_id = update->get_object_id();
if (update->has_own_name())
msg.name = update->get_name();
msg.unique_id = get_default_unique_id("update", update);
msg.icon = update->get_icon();
msg.disabled_by_default = update->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category());
msg.device_class = update->get_device_class();
return this->send_list_entities_update_response(msg);
}
void APIConnection::update_command(const UpdateCommandRequest &msg) {
update::UpdateEntity *update = App.get_update_by_key(msg.key);
if (update == nullptr)
return;
switch (msg.command) {
case enums::UPDATE_COMMAND_UPDATE:
update->perform();
break;
case enums::UPDATE_COMMAND_CHECK:
update->check();
break;
case enums::UPDATE_COMMAND_NONE:
ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled. Check client is sending the correct command");
break;
default:
ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
break;
}
}
#endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) { bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level) if (this->log_subscription_ < level)
return false; return false;

View file

@ -82,6 +82,11 @@ class APIConnection : public APIServerConnection {
bool send_time_info(datetime::TimeEntity *time); bool send_time_info(datetime::TimeEntity *time);
void time_command(const TimeCommandRequest &msg) override; void time_command(const TimeCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_DATETIME
bool send_datetime_state(datetime::DateTimeEntity *datetime);
bool send_datetime_info(datetime::DateTimeEntity *datetime);
void datetime_command(const DateTimeCommandRequest &msg) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool send_text_state(text::Text *text, std::string state); bool send_text_state(text::Text *text, std::string state);
bool send_text_info(text::Text *text); bool send_text_info(text::Text *text);
@ -145,6 +150,11 @@ class APIConnection : public APIServerConnection {
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
VoiceAssistantConfigurationResponse voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) override;
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
@ -153,6 +163,17 @@ class APIConnection : public APIServerConnection {
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif #endif
#ifdef USE_EVENT
bool send_event(event::Event *event, std::string event_type);
bool send_event_info(event::Event *event);
#endif
#ifdef USE_UPDATE
bool send_update_state(update::UpdateEntity *update);
bool send_update_info(update::UpdateEntity *update);
void update_command(const UpdateCommandRequest &msg) override;
#endif
void on_disconnect_response(const DisconnectResponse &value) override; void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override { void on_ping_response(const PingResponse &value) override {
// we initiated ping // we initiated ping

File diff suppressed because it is too large Load diff

View file

@ -156,6 +156,10 @@ enum MediaPlayerCommand : uint32_t {
MEDIA_PLAYER_COMMAND_MUTE = 3, MEDIA_PLAYER_COMMAND_MUTE = 3,
MEDIA_PLAYER_COMMAND_UNMUTE = 4, MEDIA_PLAYER_COMMAND_UNMUTE = 4,
}; };
enum MediaPlayerFormatPurpose : uint32_t {
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0,
MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1,
};
enum BluetoothDeviceRequestType : uint32_t { enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
@ -191,6 +195,12 @@ enum VoiceAssistantEvent : uint32_t {
VOICE_ASSISTANT_TTS_STREAM_START = 98, VOICE_ASSISTANT_TTS_STREAM_START = 98,
VOICE_ASSISTANT_TTS_STREAM_END = 99, VOICE_ASSISTANT_TTS_STREAM_END = 99,
}; };
enum VoiceAssistantTimerEvent : uint32_t {
VOICE_ASSISTANT_TIMER_STARTED = 0,
VOICE_ASSISTANT_TIMER_UPDATED = 1,
VOICE_ASSISTANT_TIMER_CANCELLED = 2,
VOICE_ASSISTANT_TIMER_FINISHED = 3,
};
enum AlarmControlPanelState : uint32_t { enum AlarmControlPanelState : uint32_t {
ALARM_STATE_DISARMED = 0, ALARM_STATE_DISARMED = 0,
ALARM_STATE_ARMED_HOME = 1, ALARM_STATE_ARMED_HOME = 1,
@ -221,6 +231,11 @@ enum ValveOperation : uint32_t {
VALVE_OPERATION_IS_OPENING = 1, VALVE_OPERATION_IS_OPENING = 1,
VALVE_OPERATION_IS_CLOSING = 2, VALVE_OPERATION_IS_CLOSING = 2,
}; };
enum UpdateCommand : uint32_t {
UPDATE_COMMAND_NONE = 0,
UPDATE_COMMAND_UPDATE = 1,
UPDATE_COMMAND_CHECK = 2,
};
} // namespace enums } // namespace enums
@ -825,6 +840,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
public: public:
std::string entity_id{}; std::string entity_id{};
std::string attribute{}; std::string attribute{};
bool once{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -832,6 +848,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
protected: protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class HomeAssistantStateResponse : public ProtoMessage { class HomeAssistantStateResponse : public ProtoMessage {
public: public:
@ -1254,6 +1271,22 @@ class ButtonCommandRequest : public ProtoMessage {
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
}; };
class MediaPlayerSupportedFormat : public ProtoMessage {
public:
std::string format{};
uint32_t sample_rate{0};
uint32_t num_channels{0};
enums::MediaPlayerFormatPurpose purpose{};
uint32_t sample_bytes{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesMediaPlayerResponse : public ProtoMessage { class ListEntitiesMediaPlayerResponse : public ProtoMessage {
public: public:
std::string object_id{}; std::string object_id{};
@ -1264,6 +1297,7 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage {
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{}; enums::EntityCategory entity_category{};
bool supports_pause{false}; bool supports_pause{false};
std::vector<MediaPlayerSupportedFormat> supported_formats{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -1298,6 +1332,8 @@ class MediaPlayerCommandRequest : public ProtoMessage {
float volume{0.0f}; float volume{0.0f};
bool has_media_url{false}; bool has_media_url{false};
std::string media_url{}; std::string media_url{};
bool has_announcement{false};
bool announcement{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -1773,6 +1809,93 @@ class VoiceAssistantAudio : public ProtoMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class VoiceAssistantTimerEventResponse : public ProtoMessage {
public:
enums::VoiceAssistantTimerEvent event_type{};
std::string timer_id{};
std::string name{};
uint32_t total_seconds{0};
uint32_t seconds_left{0};
bool is_active{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class VoiceAssistantAnnounceRequest : public ProtoMessage {
public:
std::string media_id{};
std::string text{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class VoiceAssistantAnnounceFinished : public ProtoMessage {
public:
bool success{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class VoiceAssistantWakeWord : public ProtoMessage {
public:
std::string id{};
std::string wake_word{};
std::vector<std::string> trained_languages{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class VoiceAssistantConfigurationRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class VoiceAssistantConfigurationResponse : public ProtoMessage {
public:
std::vector<VoiceAssistantWakeWord> available_wake_words{};
std::vector<std::string> active_wake_words{};
uint32_t max_active_wake_words{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class VoiceAssistantSetConfiguration : public ProtoMessage {
public:
std::vector<std::string> active_wake_words{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
public: public:
std::string object_id{}; std::string object_id{};
@ -1974,6 +2097,40 @@ class TimeCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class ListEntitiesEventResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
std::vector<std::string> event_types{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class EventResponse : public ProtoMessage {
public:
uint32_t key{0};
std::string event_type{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesValveResponse : public ProtoMessage { class ListEntitiesValveResponse : public ProtoMessage {
public: public:
std::string object_id{}; std::string object_id{};
@ -2026,6 +2183,106 @@ class ValveCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class ListEntitiesDateTimeResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class DateTimeStateResponse : public ProtoMessage {
public:
uint32_t key{0};
bool missing_state{false};
uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class DateTimeCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
class ListEntitiesUpdateResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class UpdateStateResponse : public ProtoMessage {
public:
uint32_t key{0};
bool missing_state{false};
bool in_progress{false};
bool has_progress{false};
float progress{0.0f};
std::string current_version{};
std::string latest_version{};
std::string title{};
std::string release_summary{};
std::string release_url{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class UpdateCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
enums::UpdateCommand command{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -484,6 +484,31 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud
return this->send_message_<VoiceAssistantAudio>(msg, 106); return this->send_message_<VoiceAssistantAudio>(msg, 106);
} }
#endif #endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_VOICE_ASSISTANT
bool APIServerConnectionBase::send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_voice_assistant_announce_finished: %s", msg.dump().c_str());
#endif
return this->send_message_<VoiceAssistantAnnounceFinished>(msg, 120);
}
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_VOICE_ASSISTANT
bool APIServerConnectionBase::send_voice_assistant_configuration_response(
const VoiceAssistantConfigurationResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_voice_assistant_configuration_response: %s", msg.dump().c_str());
#endif
return this->send_message_<VoiceAssistantConfigurationResponse>(msg, 122);
}
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
const ListEntitiesAlarmControlPanelResponse &msg) { const ListEntitiesAlarmControlPanelResponse &msg) {
@ -557,6 +582,22 @@ bool APIServerConnectionBase::send_time_state_response(const TimeStateResponse &
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
#endif #endif
#ifdef USE_EVENT
bool APIServerConnectionBase::send_list_entities_event_response(const ListEntitiesEventResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_event_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesEventResponse>(msg, 107);
}
#endif
#ifdef USE_EVENT
bool APIServerConnectionBase::send_event_response(const EventResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_event_response: %s", msg.dump().c_str());
#endif
return this->send_message_<EventResponse>(msg, 108);
}
#endif
#ifdef USE_VALVE #ifdef USE_VALVE
bool APIServerConnectionBase::send_list_entities_valve_response(const ListEntitiesValveResponse &msg) { bool APIServerConnectionBase::send_list_entities_valve_response(const ListEntitiesValveResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -575,6 +616,42 @@ bool APIServerConnectionBase::send_valve_state_response(const ValveStateResponse
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
#endif #endif
#ifdef USE_DATETIME_DATETIME
bool APIServerConnectionBase::send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_date_time_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesDateTimeResponse>(msg, 112);
}
#endif
#ifdef USE_DATETIME_DATETIME
bool APIServerConnectionBase::send_date_time_state_response(const DateTimeStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_date_time_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<DateTimeStateResponse>(msg, 113);
}
#endif
#ifdef USE_DATETIME_DATETIME
#endif
#ifdef USE_UPDATE
bool APIServerConnectionBase::send_list_entities_update_response(const ListEntitiesUpdateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_update_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesUpdateResponse>(msg, 116);
}
#endif
#ifdef USE_UPDATE
bool APIServerConnectionBase::send_update_state_response(const UpdateStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_update_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<UpdateStateResponse>(msg, 117);
}
#endif
#ifdef USE_UPDATE
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) { switch (msg_type) {
case 1: { case 1: {
@ -1048,6 +1125,72 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str());
#endif #endif
this->on_valve_command_request(msg); this->on_valve_command_request(msg);
#endif
break;
}
case 114: {
#ifdef USE_DATETIME_DATETIME
DateTimeCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str());
#endif
this->on_date_time_command_request(msg);
#endif
break;
}
case 115: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantTimerEventResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_timer_event_response(msg);
#endif
break;
}
case 118: {
#ifdef USE_UPDATE
UpdateCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str());
#endif
this->on_update_command_request(msg);
#endif
break;
}
case 119: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantAnnounceRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_announce_request(msg);
#endif
break;
}
case 121: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantConfigurationRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_configuration_request(msg);
#endif
break;
}
case 123: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantSetConfiguration msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_set_configuration(msg);
#endif #endif
break; break;
} }
@ -1363,6 +1506,32 @@ void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg)
this->time_command(msg); this->time_command(msg);
} }
#endif #endif
#ifdef USE_DATETIME_DATETIME
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->datetime_command(msg);
}
#endif
#ifdef USE_UPDATE
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->update_command(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) { const SubscribeBluetoothLEAdvertisementsRequest &msg) {
@ -1512,6 +1681,35 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
this->subscribe_voice_assistant(msg); this->subscribe_voice_assistant(msg);
} }
#endif #endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg);
if (!this->send_voice_assistant_configuration_response(ret)) {
this->on_fatal_error();
}
}
#endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->voice_assistant_set_configuration(msg);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
if (!this->is_connection_setup()) { if (!this->is_connection_setup()) {

View file

@ -244,6 +244,24 @@ class APIServerConnectionBase : public ProtoService {
bool send_voice_assistant_audio(const VoiceAssistantAudio &msg); bool send_voice_assistant_audio(const VoiceAssistantAudio &msg);
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){}; virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
#endif #endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg);
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg);
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
#endif #endif
@ -280,6 +298,12 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
virtual void on_time_command_request(const TimeCommandRequest &value){}; virtual void on_time_command_request(const TimeCommandRequest &value){};
#endif #endif
#ifdef USE_EVENT
bool send_list_entities_event_response(const ListEntitiesEventResponse &msg);
#endif
#ifdef USE_EVENT
bool send_event_response(const EventResponse &msg);
#endif
#ifdef USE_VALVE #ifdef USE_VALVE
bool send_list_entities_valve_response(const ListEntitiesValveResponse &msg); bool send_list_entities_valve_response(const ListEntitiesValveResponse &msg);
#endif #endif
@ -288,6 +312,24 @@ class APIServerConnectionBase : public ProtoService {
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
virtual void on_valve_command_request(const ValveCommandRequest &value){}; virtual void on_valve_command_request(const ValveCommandRequest &value){};
#endif
#ifdef USE_DATETIME_DATETIME
bool send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg);
#endif
#ifdef USE_DATETIME_DATETIME
bool send_date_time_state_response(const DateTimeStateResponse &msg);
#endif
#ifdef USE_DATETIME_DATETIME
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
#endif
#ifdef USE_UPDATE
bool send_list_entities_update_response(const ListEntitiesUpdateResponse &msg);
#endif
#ifdef USE_UPDATE
bool send_update_state_response(const UpdateStateResponse &msg);
#endif
#ifdef USE_UPDATE
virtual void on_update_command_request(const UpdateCommandRequest &value){};
#endif #endif
protected: protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -352,6 +394,12 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
virtual void time_command(const TimeCommandRequest &msg) = 0; virtual void time_command(const TimeCommandRequest &msg) = 0;
#endif #endif
#ifdef USE_DATETIME_DATETIME
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
#endif
#ifdef USE_UPDATE
virtual void update_command(const UpdateCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif #endif
@ -386,6 +434,13 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif #endif
#ifdef USE_VOICE_ASSISTANT
virtual VoiceAssistantConfigurationResponse voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) = 0;
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0;
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
#endif #endif
@ -447,6 +502,12 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
void on_time_command_request(const TimeCommandRequest &msg) override; void on_time_command_request(const TimeCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_DATETIME
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
#endif
#ifdef USE_UPDATE
void on_update_command_request(const UpdateCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif #endif
@ -481,6 +542,12 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
#endif #endif
#ifdef USE_VOICE_ASSISTANT
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override;
#endif
#ifdef USE_VOICE_ASSISTANT
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
#endif #endif

View file

@ -273,6 +273,15 @@ void APIServer::on_time_update(datetime::TimeEntity *obj) {
} }
#endif #endif
#ifdef USE_DATETIME_DATETIME
void APIServer::on_datetime_update(datetime::DateTimeEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_datetime_state(obj);
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
void APIServer::on_text_update(text::Text *obj, const std::string &state) { void APIServer::on_text_update(text::Text *obj, const std::string &state) {
if (obj->is_internal()) if (obj->is_internal())
@ -318,6 +327,20 @@ void APIServer::on_media_player_update(media_player::MediaPlayer *obj) {
} }
#endif #endif
#ifdef USE_EVENT
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
for (auto &c : this->clients_)
c->send_event(obj, event_type);
}
#endif
#ifdef USE_UPDATE
void APIServer::on_update(update::UpdateEntity *obj) {
for (auto &c : this->clients_)
c->send_update_state(obj);
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; } void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@ -336,8 +359,18 @@ void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<s
.entity_id = std::move(entity_id), .entity_id = std::move(entity_id),
.attribute = std::move(attribute), .attribute = std::move(attribute),
.callback = std::move(f), .callback = std::move(f),
.once = false,
}); });
} }
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
.once = true,
});
};
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const { const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
return this->state_subs_; return this->state_subs_;
} }

View file

@ -72,6 +72,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
void on_time_update(datetime::TimeEntity *obj) override; void on_time_update(datetime::TimeEntity *obj) override;
#endif #endif
#ifdef USE_DATETIME_DATETIME
void on_datetime_update(datetime::DateTimeEntity *obj) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
void on_text_update(text::Text *obj, const std::string &state) override; void on_text_update(text::Text *obj, const std::string &state) override;
#endif #endif
@ -96,6 +99,12 @@ class APIServer : public Component, public Controller {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override; void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
#endif #endif
#ifdef USE_EVENT
void on_event(event::Event *obj, const std::string &event_type) override;
#endif
#ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override;
#endif
bool is_connected() const; bool is_connected() const;
@ -103,10 +112,13 @@ class APIServer : public Component, public Controller {
std::string entity_id; std::string entity_id;
optional<std::string> attribute; optional<std::string> attribute;
std::function<void(std::string)> callback; std::function<void(std::string)> callback;
bool once;
}; };
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute, void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f); std::function<void(std::string)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const; const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; } const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }

View file

@ -105,7 +105,7 @@ class CustomAPIDevice {
/** Subscribe to the state (or attribute state) of an entity from Home Assistant. /** Subscribe to the state (or attribute state) of an entity from Home Assistant.
* *
* Usage: * Usage:
*å *
* ```cpp * ```cpp
* void setup() override { * void setup() override {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast"); * subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");

View file

@ -71,6 +71,12 @@ bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->cl
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); } bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); }
#endif #endif
#ifdef USE_DATETIME_DATETIME
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) {
return this->client_->send_datetime_info(datetime);
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); } bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
#endif #endif
@ -89,6 +95,12 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
return this->client_->send_alarm_control_panel_info(a_alarm_control_panel); return this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
} }
#endif #endif
#ifdef USE_EVENT
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
#endif
#ifdef USE_UPDATE
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); }
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -52,6 +52,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool on_time(datetime::TimeEntity *time) override; bool on_time(datetime::TimeEntity *time) override;
#endif #endif
#ifdef USE_DATETIME_DATETIME
bool on_datetime(datetime::DateTimeEntity *datetime) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool on_text(text::Text *text) override; bool on_text(text::Text *text) override;
#endif #endif
@ -69,6 +72,12 @@ class ListEntitiesIterator : public ComponentIterator {
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override;
#endif
#ifdef USE_UPDATE
bool on_update(update::UpdateEntity *update) override;
#endif #endif
bool on_end() override; bool on_end() override;

View file

@ -48,6 +48,11 @@ bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->cl
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); } bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); }
#endif #endif
#ifdef USE_DATETIME_DATETIME
bool InitialStateIterator::on_datetime(datetime::DateTimeEntity *datetime) {
return this->client_->send_datetime_state(datetime);
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); } bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
#endif #endif
@ -72,6 +77,9 @@ bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
return this->client_->send_alarm_control_panel_state(a_alarm_control_panel); return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
} }
#endif #endif
#ifdef USE_UPDATE
bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); }
#endif
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
} // namespace api } // namespace api

View file

@ -49,6 +49,9 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool on_time(datetime::TimeEntity *time) override; bool on_time(datetime::TimeEntity *time) override;
#endif #endif
#ifdef USE_DATETIME_DATETIME
bool on_datetime(datetime::DateTimeEntity *datetime) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool on_text(text::Text *text) override; bool on_text(text::Text *text) override;
#endif #endif
@ -66,6 +69,12 @@ class InitialStateIterator : public ComponentIterator {
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override { return true; };
#endif
#ifdef USE_UPDATE
bool on_update(update::UpdateEntity *update) override;
#endif #endif
protected: protected:
APIConnection *client_; APIConnection *client_;

View file

@ -54,7 +54,6 @@ FAST_FILTER = {
"LSB10": 7, "LSB10": 7,
} }
CONF_ANGLE = "angle"
CONF_RAW_ANGLE = "raw_angle" CONF_RAW_ANGLE = "raw_angle"
CONF_RAW_POSITION = "raw_position" CONF_RAW_POSITION = "raw_position"
CONF_WATCHDOG = "watchdog" CONF_WATCHDOG = "watchdog"

View file

@ -11,6 +11,7 @@ from esphome.const import (
CONF_MAGNITUDE, CONF_MAGNITUDE,
CONF_STATUS, CONF_STATUS,
CONF_POSITION, CONF_POSITION,
CONF_ANGLE,
) )
from .. import as5600_ns, AS5600Component from .. import as5600_ns, AS5600Component
@ -19,7 +20,6 @@ DEPENDENCIES = ["as5600"]
AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingComponent) AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingComponent)
CONF_ANGLE = "angle"
CONF_RAW_ANGLE = "raw_angle" CONF_RAW_ANGLE = "raw_angle"
CONF_RAW_POSITION = "raw_position" CONF_RAW_POSITION = "raw_position"
CONF_WATCHDOG = "watchdog" CONF_WATCHDOG = "watchdog"

View file

@ -1,13 +1,13 @@
# Dummy integration to allow relying on AsyncTCP # Dummy integration to allow relying on AsyncTCP
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.core import CORE, coroutine_with_priority
from esphome.const import ( from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ["@OttoWinter"] CODEOWNERS = ["@OttoWinter"]
@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny: if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/esphome/AsyncTCP/blob/master/library.json # https://github.com/esphome/AsyncTCP/blob/master/library.json
cg.add_library("esphome/AsyncTCP-esphome", "2.1.3") cg.add_library("esphome/AsyncTCP-esphome", "2.1.4")
elif CORE.is_esp8266: elif CORE.is_esp8266:
# https://github.com/esphome/ESPAsyncTCP # https://github.com/esphome/ESPAsyncTCP
cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0")

View file

@ -22,7 +22,6 @@ CONF_AT581X_ID = "at581x_id"
CONF_SENSING_DISTANCE = "sensing_distance" CONF_SENSING_DISTANCE = "sensing_distance"
CONF_SENSITIVITY = "sensitivity"
CONF_POWERON_SELFCHECK_TIME = "poweron_selfcheck_time" CONF_POWERON_SELFCHECK_TIME = "poweron_selfcheck_time"
CONF_PROTECT_TIME = "protect_time" CONF_PROTECT_TIME = "protect_time"
CONF_TRIGGER_BASE = "trigger_base" CONF_TRIGGER_BASE = "trigger_base"

View file

@ -1,34 +1,34 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi from esphome.components import sensor, spi
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT, CONF_CURRENT,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_POWER, CONF_POWER,
CONF_POWER_FACTOR, CONF_POWER_FACTOR,
CONF_FREQUENCY, CONF_REACTIVE_POWER,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY, CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ICON_LIGHTBULB,
ICON_CURRENT_AC, ICON_CURRENT_AC,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_HERTZ, UNIT_HERTZ,
UNIT_VOLT, UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_VOLT_AMPS_REACTIVE, UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT,
UNIT_WATT_HOURS, UNIT_WATT_HOURS,
) )
CONF_LINE_FREQUENCY = "line_frequency"
CONF_METER_CONSTANT = "meter_constant" CONF_METER_CONSTANT = "meter_constant"
CONF_PL_CONST = "pl_const" CONF_PL_CONST = "pl_const"
CONF_GAIN_PGA = "gain_pga" CONF_GAIN_PGA = "gain_pga"

View file

@ -0,0 +1,7 @@
import esphome.codegen as cg
CODEOWNERS = ["@circuitsetup", "@descipher"]
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
CONF_ATM90E32_ID = "atm90e32_id"

View file

@ -132,10 +132,77 @@ void ATM90E32Component::update() {
this->status_clear_warning(); this->status_clear_warning();
} }
void ATM90E32Component::restore_calibrations_() {
if (enable_offset_calibration_) {
this->pref_.load(&this->offset_phase_);
}
};
void ATM90E32Component::run_offset_calibrations() {
// Run the calibrations and
// Setup voltage and current calibration offsets for PHASE A
this->offset_phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset
// Setup voltage and current calibration offsets for PHASE B
this->offset_phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset
// Setup voltage and current calibration offsets for PHASE C
this->offset_phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
this->pref_.save(&this->offset_phase_);
ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
}
void ATM90E32Component::clear_offset_calibrations() {
// Clear the calibrations and
this->offset_phase_[PHASEA].voltage_offset_ = 0;
this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEA].current_offset_ = 0;
this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset
this->offset_phase_[PHASEB].voltage_offset_ = 0;
this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEB].current_offset_ = 0;
this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset
this->offset_phase_[PHASEC].voltage_offset_ = 0;
this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEC].current_offset_ = 0;
this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
this->pref_.save(&this->offset_phase_);
ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
}
void ATM90E32Component::setup() { void ATM90E32Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component..."); ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
this->spi_setup(); this->spi_setup();
if (this->enable_offset_calibration_) {
uint32_t hash = fnv1_hash(App.get_friendly_name());
this->pref_ = global_preferences->make_preference<Calibration[3]>(hash, true);
this->restore_calibrations_();
}
uint16_t mmode0 = 0x87; // 3P4W 50Hz uint16_t mmode0 = 0x87; // 3P4W 50Hz
if (line_freq_ == 60) { if (line_freq_ == 60) {
mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz
@ -167,27 +234,12 @@ void ATM90E32Component::setup() {
this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50% this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750 this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10% this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
// Setup voltage and current calibration offsets for PHASE A
this->phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // A Voltage offset
this->phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // A Current offset
// Setup voltage and current gain for PHASE A // Setup voltage and current gain for PHASE A
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_); // A Voltage rms gain this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_); // A Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_); // A line current gain this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_); // A line current gain
// Setup voltage and current calibration offsets for PHASE B
this->phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // B Voltage offset
this->phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // B Current offset
// Setup voltage and current gain for PHASE B // Setup voltage and current gain for PHASE B
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_); // B Voltage rms gain this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_); // B Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_); // B line current gain this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_); // B line current gain
// Setup voltage and current calibration offsets for PHASE C
this->phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
// Setup voltage and current gain for PHASE C // Setup voltage and current gain for PHASE C
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_); // C Voltage rms gain this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_); // C Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain

View file

@ -1,9 +1,12 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "atm90e32_reg.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "atm90e32_reg.h" #include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
namespace esphome { namespace esphome {
namespace atm90e32 { namespace atm90e32 {
@ -20,7 +23,6 @@ class ATM90E32Component : public PollingComponent,
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void update() override; void update() override;
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; } void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; }
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; } void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; } void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
@ -48,9 +50,11 @@ class ATM90E32Component : public PollingComponent,
void set_line_freq(int freq) { line_freq_ = freq; } void set_line_freq(int freq) { line_freq_ = freq; }
void set_current_phases(int phases) { current_phases_ = phases; } void set_current_phases(int phases) { current_phases_ = phases; }
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; } void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
void run_offset_calibrations();
void clear_offset_calibrations();
void set_enable_offset_calibration(bool flag) { enable_offset_calibration_ = flag; }
uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/); uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/);
uint16_t calibrate_current_offset_phase(uint8_t /*phase*/); uint16_t calibrate_current_offset_phase(uint8_t /*phase*/);
int32_t last_periodic_millis = millis(); int32_t last_periodic_millis = millis();
protected: protected:
@ -83,10 +87,11 @@ class ATM90E32Component : public PollingComponent,
float get_chip_temperature_(); float get_chip_temperature_();
bool get_publish_interval_flag_() { return publish_interval_flag_; }; bool get_publish_interval_flag_() { return publish_interval_flag_; };
void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; }; void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
void restore_calibrations_();
struct ATM90E32Phase { struct ATM90E32Phase {
uint16_t voltage_gain_{7305}; uint16_t voltage_gain_{0};
uint16_t ct_gain_{27961}; uint16_t ct_gain_{0};
uint16_t voltage_offset_{0}; uint16_t voltage_offset_{0};
uint16_t current_offset_{0}; uint16_t current_offset_{0};
float voltage_{0}; float voltage_{0};
@ -114,13 +119,21 @@ class ATM90E32Component : public PollingComponent,
uint32_t cumulative_reverse_active_energy_{0}; uint32_t cumulative_reverse_active_energy_{0};
} phase_[3]; } phase_[3];
struct Calibration {
uint16_t voltage_offset_{0};
uint16_t current_offset_{0};
} offset_phase_[3];
ESPPreferenceObject pref_;
sensor::Sensor *freq_sensor_{nullptr}; sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *chip_temperature_sensor_{nullptr}; sensor::Sensor *chip_temperature_sensor_{nullptr};
uint16_t pga_gain_{0x15}; uint16_t pga_gain_{0x15};
int line_freq_{60}; int line_freq_{60};
int current_phases_{3}; int current_phases_{3};
bool publish_interval_flag_{true}; bool publish_interval_flag_{false};
bool peak_current_signed_{false}; bool peak_current_signed_{false};
bool enable_offset_calibration_{false};
}; };
} // namespace atm90e32 } // namespace atm90e32

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <cinttypes>
namespace esphome { namespace esphome {
namespace atm90e32 { namespace atm90e32 {

View file

@ -0,0 +1,43 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_CHIP, ICON_SCALE
from .. import atm90e32_ns
from ..sensor import ATM90E32Component
CONF_RUN_OFFSET_CALIBRATION = "run_offset_calibration"
CONF_CLEAR_OFFSET_CALIBRATION = "clear_offset_calibration"
ATM90E32CalibrationButton = atm90e32_ns.class_(
"ATM90E32CalibrationButton",
button.Button,
)
ATM90E32ClearCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearCalibrationButton",
button.Button,
)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
cv.Optional(CONF_RUN_OFFSET_CALIBRATION): button.button_schema(
ATM90E32CalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): button.button_schema(
ATM90E32ClearCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_CHIP,
),
}
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if run_offset := config.get(CONF_RUN_OFFSET_CALIBRATION):
b = await button.new_button(run_offset)
await cg.register_parented(b, parent)
if clear_offset := config.get(CONF_CLEAR_OFFSET_CALIBRATION):
b = await button.new_button(clear_offset)
await cg.register_parented(b, parent)

View file

@ -0,0 +1,20 @@
#include "atm90e32_button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace atm90e32 {
static const char *const TAG = "atm90e32.button";
void ATM90E32CalibrationButton::press_action() {
ESP_LOGI(TAG, "Running offset calibrations, Note: CTs and ACVs must be 0 during this process...");
this->parent_->run_offset_calibrations();
}
void ATM90E32ClearCalibrationButton::press_action() {
ESP_LOGI(TAG, "Offset calibrations cleared.");
this->parent_->clear_offset_calibrations();
}
} // namespace atm90e32
} // namespace esphome

View file

@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/atm90e32/atm90e32.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace atm90e32 {
class ATM90E32CalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32CalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32ClearCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32ClearCalibrationButton() = default;
protected:
void press_action() override;
};
} // namespace atm90e32
} // namespace esphome

View file

@ -1,21 +1,22 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi from esphome.components import sensor, spi
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_APPARENT_POWER,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT, CONF_CURRENT,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_PHASE_A, CONF_PHASE_A,
CONF_PHASE_ANGLE,
CONF_PHASE_B, CONF_PHASE_B,
CONF_PHASE_C, CONF_PHASE_C,
CONF_PHASE_ANGLE,
CONF_POWER, CONF_POWER,
CONF_POWER_FACTOR, CONF_POWER_FACTOR,
CONF_APPARENT_POWER, CONF_REACTIVE_POWER,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY, CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
@ -23,13 +24,13 @@ from esphome.const import (
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC,
ICON_LIGHTBULB,
ICON_CURRENT_AC, ICON_CURRENT_AC,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE, UNIT_AMPERE,
UNIT_DEGREES,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_DEGREES,
UNIT_HERTZ, UNIT_HERTZ,
UNIT_VOLT, UNIT_VOLT,
UNIT_VOLT_AMPS_REACTIVE, UNIT_VOLT_AMPS_REACTIVE,
@ -37,7 +38,8 @@ from esphome.const import (
UNIT_WATT_HOURS, UNIT_WATT_HOURS,
) )
CONF_LINE_FREQUENCY = "line_frequency" from . import atm90e32_ns
CONF_CHIP_TEMPERATURE = "chip_temperature" CONF_CHIP_TEMPERATURE = "chip_temperature"
CONF_GAIN_PGA = "gain_pga" CONF_GAIN_PGA = "gain_pga"
CONF_CURRENT_PHASES = "current_phases" CONF_CURRENT_PHASES = "current_phases"
@ -46,6 +48,7 @@ CONF_GAIN_CT = "gain_ct"
CONF_HARMONIC_POWER = "harmonic_power" CONF_HARMONIC_POWER = "harmonic_power"
CONF_PEAK_CURRENT = "peak_current" CONF_PEAK_CURRENT = "peak_current"
CONF_PEAK_CURRENT_SIGNED = "peak_current_signed" CONF_PEAK_CURRENT_SIGNED = "peak_current_signed"
CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration"
UNIT_DEG = "degrees" UNIT_DEG = "degrees"
LINE_FREQS = { LINE_FREQS = {
"50HZ": 50, "50HZ": 50,
@ -61,7 +64,6 @@ PGA_GAINS = {
"4X": 0x2A, "4X": 0x2A,
} }
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
ATM90E32Component = atm90e32_ns.class_( ATM90E32Component = atm90e32_ns.class_(
"ATM90E32Component", cg.PollingComponent, spi.SPIDevice "ATM90E32Component", cg.PollingComponent, spi.SPIDevice
) )
@ -164,6 +166,7 @@ CONFIG_SCHEMA = (
), ),
cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True), cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean, cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean,
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@ -227,3 +230,4 @@ async def to_code(config):
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES])) cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED])) cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION]))

View file

@ -31,7 +31,7 @@ CONFIG_SCHEMA = (
BEDJET_CLIENT_SCHEMA = cv.Schema( BEDJET_CLIENT_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_BEDJET_ID): cv.use_id(BedJetHub), cv.GenerateID(CONF_BEDJET_ID): cv.use_id(BedJetHub),
} }
) )

View file

@ -157,5 +157,11 @@ bool BedjetCodec::compare(const uint8_t *data, uint16_t length) {
return explicit_fields_changed; return explicit_fields_changed;
} }
/// Converts a BedJet temp step into degrees Celsius.
float bedjet_temp_to_c(uint8_t temp) {
// BedJet temp is "C*2"; to get C, divide by 2.
return temp / 2.0f;
}
} // namespace bedjet } // namespace bedjet
} // namespace esphome } // namespace esphome

View file

@ -90,7 +90,7 @@ struct BedjetStatusPacket {
int unused_6 : 1; // 0x4 int unused_6 : 1; // 0x4
bool is_dual_zone : 1; /// Is part of a Dual Zone configuration bool is_dual_zone : 1; /// Is part of a Dual Zone configuration
int unused_7 : 1; // 0x1 int unused_7 : 1; // 0x1
} dual_zone_flags; } dual_zone_flags; // NOLINT(clang-diagnostic-unaligned-access)
uint8_t unused_4 : 8; // Unknown 23-24 = 0x1310 uint8_t unused_4 : 8; // Unknown 23-24 = 0x1310
uint8_t unused_5 : 8; // Unknown 23-24 = 0x1310 uint8_t unused_5 : 8; // Unknown 23-24 = 0x1310
@ -187,5 +187,8 @@ class BedjetCodec {
BedjetStatusPacket buf_; BedjetStatusPacket buf_;
}; };
/// Converts a BedJet temp step into degrees Celsius.
float bedjet_temp_to_c(uint8_t temp);
} // namespace bedjet } // namespace bedjet
} // namespace esphome } // namespace esphome

View file

@ -40,6 +40,14 @@ enum BedjetHeatMode {
HEAT_MODE_EXTENDED, HEAT_MODE_EXTENDED,
}; };
// Which temperature to use as the climate entity's current temperature reading
enum BedjetTemperatureSource {
// Use the temperature of the air the BedJet is putting out
TEMPERATURE_SOURCE_OUTLET,
// Use the ambient temperature of the room the BedJet is in
TEMPERATURE_SOURCE_AMBIENT
};
enum BedjetButton : uint8_t { enum BedjetButton : uint8_t {
/// Turn BedJet off /// Turn BedJet off
BTN_OFF = 0x1, BTN_OFF = 0x1,

View file

@ -7,6 +7,7 @@ from esphome.const import (
CONF_HEAT_MODE, CONF_HEAT_MODE,
CONF_ID, CONF_ID,
CONF_RECEIVE_TIMEOUT, CONF_RECEIVE_TIMEOUT,
CONF_TEMPERATURE_SOURCE,
CONF_TIME_ID, CONF_TIME_ID,
) )
from .. import ( from .. import (
@ -21,10 +22,15 @@ DEPENDENCIES = ["bedjet"]
BedJetClimate = bedjet_ns.class_("BedJetClimate", climate.Climate, cg.PollingComponent) BedJetClimate = bedjet_ns.class_("BedJetClimate", climate.Climate, cg.PollingComponent)
BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode") BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode")
BedjetTemperatureSource = bedjet_ns.enum("BedjetTemperatureSource")
BEDJET_HEAT_MODES = { BEDJET_HEAT_MODES = {
"heat": BedjetHeatMode.HEAT_MODE_HEAT, "heat": BedjetHeatMode.HEAT_MODE_HEAT,
"extended": BedjetHeatMode.HEAT_MODE_EXTENDED, "extended": BedjetHeatMode.HEAT_MODE_EXTENDED,
} }
BEDJET_TEMPERATURE_SOURCES = {
"outlet": BedjetTemperatureSource.TEMPERATURE_SOURCE_OUTLET,
"ambient": BedjetTemperatureSource.TEMPERATURE_SOURCE_AMBIENT,
}
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
climate.CLIMATE_SCHEMA.extend( climate.CLIMATE_SCHEMA.extend(
@ -33,6 +39,9 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum( cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum(
BEDJET_HEAT_MODES, lower=True BEDJET_HEAT_MODES, lower=True
), ),
cv.Optional(CONF_TEMPERATURE_SOURCE, default="ambient"): cv.enum(
BEDJET_TEMPERATURE_SOURCES, lower=True
),
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@ -63,3 +72,4 @@ async def to_code(config):
await register_bedjet_child(var, config) await register_bedjet_child(var, config)
cg.add(var.set_heating_mode(config[CONF_HEAT_MODE])) cg.add(var.set_heating_mode(config[CONF_HEAT_MODE]))
cg.add(var.set_temperature_source(config[CONF_TEMPERATURE_SOURCE]))

View file

@ -8,12 +8,6 @@ namespace bedjet {
using namespace esphome::climate; using namespace esphome::climate;
/// Converts a BedJet temp step into degrees Celsius.
float bedjet_temp_to_c(const uint8_t temp) {
// BedJet temp is "C*2"; to get C, divide by 2.
return temp / 2.0f;
}
static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) { static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) {
if (fan_step < BEDJET_FAN_SPEED_COUNT) if (fan_step < BEDJET_FAN_SPEED_COUNT)
return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step]; return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step];
@ -236,9 +230,14 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) {
if (converted_temp > 0) if (converted_temp > 0)
this->target_temperature = converted_temp; this->target_temperature = converted_temp;
converted_temp = bedjet_temp_to_c(data->ambient_temp_step); if (this->temperature_source_ == TEMPERATURE_SOURCE_OUTLET) {
if (converted_temp > 0) converted_temp = bedjet_temp_to_c(data->actual_temp_step);
} else {
converted_temp = bedjet_temp_to_c(data->ambient_temp_step);
}
if (converted_temp > 0) {
this->current_temperature = converted_temp; this->current_temperature = converted_temp;
}
const auto *fan_mode_name = bedjet_fan_step_to_fan_mode(data->fan_step); const auto *fan_mode_name = bedjet_fan_step_to_fan_mode(data->fan_step);
if (fan_mode_name != nullptr) { if (fan_mode_name != nullptr) {

View file

@ -28,6 +28,8 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli
/** Sets the default strategy to use for climate::CLIMATE_MODE_HEAT. */ /** Sets the default strategy to use for climate::CLIMATE_MODE_HEAT. */
void set_heating_mode(BedjetHeatMode mode) { this->heating_mode_ = mode; } void set_heating_mode(BedjetHeatMode mode) { this->heating_mode_ = mode; }
/** Sets the temperature source to use for the climate entity's current temperature */
void set_temperature_source(BedjetTemperatureSource source) { this->temperature_source_ = source; }
climate::ClimateTraits traits() override { climate::ClimateTraits traits() override {
auto traits = climate::ClimateTraits(); auto traits = climate::ClimateTraits();
@ -74,6 +76,7 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli
void control(const climate::ClimateCall &call) override; void control(const climate::ClimateCall &call) override;
BedjetHeatMode heating_mode_ = HEAT_MODE_HEAT; BedjetHeatMode heating_mode_ = HEAT_MODE_HEAT;
BedjetTemperatureSource temperature_source_ = TEMPERATURE_SOURCE_AMBIENT;
void reset_state_(); void reset_state_();
bool update_status_(); bool update_status_();

View file

@ -0,0 +1,55 @@
import logging
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ID,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
from .. import (
BEDJET_CLIENT_SCHEMA,
bedjet_ns,
register_bedjet_child,
)
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@jhansche", "@javawizard"]
DEPENDENCIES = ["bedjet"]
CONF_OUTLET_TEMPERATURE = "outlet_temperature"
CONF_AMBIENT_TEMPERATURE = "ambient_temperature"
BedjetSensor = bedjet_ns.class_("BedjetSensor", cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(BedjetSensor),
cv.Optional(CONF_OUTLET_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_AMBIENT_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
).extend(BEDJET_CLIENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await register_bedjet_child(var, config)
if outlet_temperature_sensor := config.get(CONF_OUTLET_TEMPERATURE):
sensor_var = await sensor.new_sensor(outlet_temperature_sensor)
cg.add(var.set_outlet_temperature_sensor(sensor_var))
if ambient_temperature_sensor := config.get(CONF_AMBIENT_TEMPERATURE):
sensor_var = await sensor.new_sensor(ambient_temperature_sensor)
cg.add(var.set_ambient_temperature_sensor(sensor_var))

View file

@ -0,0 +1,34 @@
#include "bedjet_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bedjet {
std::string BedjetSensor::describe() { return "BedJet Sensor"; }
void BedjetSensor::dump_config() {
ESP_LOGCONFIG(TAG, "BedJet Sensor:");
LOG_SENSOR(" ", "Outlet Temperature", this->outlet_temperature_sensor_);
LOG_SENSOR(" ", "Ambient Temperature", this->ambient_temperature_sensor_);
}
void BedjetSensor::on_bedjet_state(bool is_ready) {}
void BedjetSensor::on_status(const BedjetStatusPacket *data) {
if (this->outlet_temperature_sensor_ != nullptr) {
float converted_temp = bedjet_temp_to_c(data->actual_temp_step);
if (converted_temp > 0) {
this->outlet_temperature_sensor_->publish_state(converted_temp);
}
}
if (this->ambient_temperature_sensor_ != nullptr) {
float converted_temp = bedjet_temp_to_c(data->ambient_temp_step);
if (converted_temp > 0) {
this->ambient_temperature_sensor_->publish_state(converted_temp);
}
}
}
} // namespace bedjet
} // namespace esphome

View file

@ -0,0 +1,32 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/bedjet/bedjet_child.h"
#include "esphome/components/bedjet/bedjet_codec.h"
namespace esphome {
namespace bedjet {
class BedjetSensor : public BedJetClient, public Component {
public:
void dump_config() override;
void on_status(const BedjetStatusPacket *data) override;
void on_bedjet_state(bool is_ready) override;
std::string describe() override;
void set_outlet_temperature_sensor(sensor::Sensor *outlet_temperature_sensor) {
this->outlet_temperature_sensor_ = outlet_temperature_sensor;
}
void set_ambient_temperature_sensor(sensor::Sensor *ambient_temperature_sensor) {
this->ambient_temperature_sensor_ = ambient_temperature_sensor;
}
protected:
sensor::Sensor *outlet_temperature_sensor_{nullptr};
sensor::Sensor *ambient_temperature_sensor_{nullptr};
};
} // namespace bedjet
} // namespace esphome

View file

@ -0,0 +1,384 @@
#include "led_strip.h"
#ifdef USE_BK72XX
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
extern "C" {
#include "rtos_pub.h"
#include "spi.h"
#include "arm_arch.h"
#include "general_dma_pub.h"
#include "gpio_pub.h"
#include "icu_pub.h"
#undef SPI_DAT
#undef SPI_BASE
};
static const uint32_t SPI_TX_DMA_CHANNEL = GDMA_CHANNEL_3;
// TODO: Check if SPI_PERI_CLK_DCO depends on the chip variant
static const uint32_t SPI_PERI_CLK_26M = 26000000;
static const uint32_t SPI_PERI_CLK_DCO = 120000000;
static const uint32_t SPI_BASE = 0x00802700;
static const uint32_t SPI_DAT = SPI_BASE + 3 * 4;
static const uint32_t SPI_CONFIG = SPI_BASE + 1 * 4;
static const uint32_t SPI_TX_EN = 1 << 0;
static const uint32_t CTRL_NSSMD_3 = 1 << 17;
static const uint32_t SPI_TX_FINISH_EN = 1 << 2;
static const uint32_t SPI_RX_FINISH_EN = 1 << 3;
namespace esphome {
namespace beken_spi_led_strip {
static const char *const TAG = "beken_spi_led_strip";
struct spi_data_t {
SemaphoreHandle_t dma_tx_semaphore;
volatile bool tx_in_progress;
bool first_run;
};
static spi_data_t *spi_data = nullptr;
static void set_spi_ctrl_register(unsigned long bit, bool val) {
uint32_t value = REG_READ(SPI_CTRL);
if (val == 0) {
value &= ~bit;
} else if (val == 1) {
value |= bit;
}
REG_WRITE(SPI_CTRL, value);
}
static void set_spi_config_register(unsigned long bit, bool val) {
uint32_t value = REG_READ(SPI_CONFIG);
if (val == 0) {
value &= ~bit;
} else if (val == 1) {
value |= bit;
}
REG_WRITE(SPI_CONFIG, value);
}
void spi_dma_tx_enable(bool enable) {
GDMA_CFG_ST en_cfg;
set_spi_config_register(SPI_TX_EN, enable ? 1 : 0);
en_cfg.channel = SPI_TX_DMA_CHANNEL;
en_cfg.param = enable ? 1 : 0;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_DMA_ENABLE, &en_cfg);
}
static void spi_set_clock(uint32_t max_hz) {
int source_clk = 0;
int spi_clk = 0;
int div = 0;
uint32_t param;
if (max_hz > 4333000) {
if (max_hz > 30000000) {
spi_clk = 30000000;
} else {
spi_clk = max_hz;
}
sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_DOWN, &param);
source_clk = SPI_PERI_CLK_DCO;
param = PCLK_POSI_SPI;
sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_DCO, &param);
param = PWD_SPI_CLK_BIT;
sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, &param);
} else {
spi_clk = max_hz;
#if CFG_XTAL_FREQUENCE
source_clk = CFG_XTAL_FREQUENCE;
#else
source_clk = SPI_PERI_CLK_26M;
#endif
param = PCLK_POSI_SPI;
sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_26M, &param);
}
div = ((source_clk >> 1) / spi_clk);
if (div < 2) {
div = 2;
} else if (div >= 255) {
div = 255;
}
param = REG_READ(SPI_CTRL);
param &= ~(SPI_CKR_MASK << SPI_CKR_POSI);
param |= (div << SPI_CKR_POSI);
REG_WRITE(SPI_CTRL, param);
ESP_LOGD(TAG, "target frequency: %d, actual frequency: %d", max_hz, source_clk / 2 / div);
}
void spi_dma_tx_finish_callback(unsigned int param) {
spi_data->tx_in_progress = false;
xSemaphoreGive(spi_data->dma_tx_semaphore);
spi_dma_tx_enable(0);
}
void BekenSPILEDStripLightOutput::setup() {
ESP_LOGCONFIG(TAG, "Setting up Beken SPI LED Strip...");
size_t buffer_size = this->get_buffer_size_();
size_t dma_buffer_size = (buffer_size * 8) + (2 * 64);
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->buf_ = allocator.allocate(buffer_size);
if (this->buf_ == nullptr) {
ESP_LOGE(TAG, "Cannot allocate LED buffer!");
this->mark_failed();
return;
}
this->effect_data_ = allocator.allocate(this->num_leds_);
if (this->effect_data_ == nullptr) {
ESP_LOGE(TAG, "Cannot allocate effect data!");
this->mark_failed();
return;
}
this->dma_buf_ = allocator.allocate(dma_buffer_size);
if (this->dma_buf_ == nullptr) {
ESP_LOGE(TAG, "Cannot allocate DMA buffer!");
this->mark_failed();
return;
}
memset(this->buf_, 0, buffer_size);
memset(this->effect_data_, 0, this->num_leds_);
memset(this->dma_buf_, 0, dma_buffer_size);
uint32_t value = PCLK_POSI_SPI;
sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_26M, &value);
value = PWD_SPI_CLK_BIT;
sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, &value);
if (spi_data != nullptr) {
ESP_LOGE(TAG, "SPI device already initialized!");
this->mark_failed();
return;
}
spi_data = (spi_data_t *) calloc(1, sizeof(spi_data_t));
if (spi_data == nullptr) {
ESP_LOGE(TAG, "Cannot allocate spi_data!");
this->mark_failed();
return;
}
spi_data->dma_tx_semaphore = xSemaphoreCreateBinary();
if (spi_data->dma_tx_semaphore == nullptr) {
ESP_LOGE(TAG, "TX Semaphore init faild!");
this->mark_failed();
return;
}
spi_data->first_run = true;
set_spi_ctrl_register(MSTEN, 0);
set_spi_ctrl_register(BIT_WDTH, 0);
spi_set_clock(this->spi_frequency_);
set_spi_ctrl_register(CKPOL, 0);
set_spi_ctrl_register(CKPHA, 0);
set_spi_ctrl_register(MSTEN, 1);
set_spi_ctrl_register(SPIEN, 1);
set_spi_ctrl_register(TXINT_EN, 0);
set_spi_ctrl_register(RXINT_EN, 0);
set_spi_config_register(SPI_TX_FINISH_EN, 1);
set_spi_config_register(SPI_RX_FINISH_EN, 1);
set_spi_ctrl_register(RXOVR_EN, 0);
set_spi_ctrl_register(TXOVR_EN, 0);
value = REG_READ(SPI_CTRL);
value &= ~CTRL_NSSMD_3;
value |= (1 << 17);
REG_WRITE(SPI_CTRL, value);
value = GFUNC_MODE_SPI_DMA;
sddev_control(GPIO_DEV_NAME, CMD_GPIO_ENABLE_SECOND, &value);
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, 0);
GDMA_CFG_ST en_cfg;
GDMACFG_TPYES_ST init_cfg;
memset(&init_cfg, 0, sizeof(GDMACFG_TPYES_ST));
init_cfg.dstdat_width = 8;
init_cfg.srcdat_width = 32;
init_cfg.dstptr_incr = 0;
init_cfg.srcptr_incr = 1;
init_cfg.src_start_addr = this->dma_buf_;
init_cfg.dst_start_addr = (void *) SPI_DAT; // SPI_DMA_REG4_TXFIFO
init_cfg.channel = SPI_TX_DMA_CHANNEL;
init_cfg.prio = 0; // 10
init_cfg.u.type4.src_loop_start_addr = this->dma_buf_;
init_cfg.u.type4.src_loop_end_addr = this->dma_buf_ + dma_buffer_size;
init_cfg.half_fin_handler = nullptr;
init_cfg.fin_handler = spi_dma_tx_finish_callback;
init_cfg.src_module = GDMA_X_SRC_DTCM_RD_REQ;
init_cfg.dst_module = GDMA_X_DST_GSPI_TX_REQ; // GDMA_X_DST_HSSPI_TX_REQ
sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_TYPE4, (void *) &init_cfg);
en_cfg.channel = SPI_TX_DMA_CHANNEL;
en_cfg.param = dma_buffer_size;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_TRANS_LENGTH, (void *) &en_cfg);
en_cfg.channel = SPI_TX_DMA_CHANNEL;
en_cfg.param = 0;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_WORK_MODE, (void *) &en_cfg);
en_cfg.channel = SPI_TX_DMA_CHANNEL;
en_cfg.param = 0;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_SRCADDR_LOOP, &en_cfg);
spi_dma_tx_enable(0);
value = REG_READ(SPI_CONFIG);
value &= ~(0xFFF << 8);
value |= ((dma_buffer_size & 0xFFF) << 8);
REG_WRITE(SPI_CONFIG, value);
}
void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency) {
this->bit0_ = bit0;
this->bit1_ = bit1;
this->spi_frequency_ = spi_frequency;
}
void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
// protect from refreshing too often
uint32_t now = micros();
if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
// try again next loop iteration, so that this change won't get lost
this->schedule_show();
return;
}
this->last_refresh_ = now;
this->mark_shown_();
ESP_LOGVV(TAG, "Writing RGB values to bus...");
if (spi_data == nullptr) {
ESP_LOGE(TAG, "SPI not initialized");
this->status_set_warning();
return;
}
if (!spi_data->first_run && !xSemaphoreTake(spi_data->dma_tx_semaphore, 10 / portTICK_PERIOD_MS)) {
ESP_LOGE(TAG, "Timed out waiting for semaphore");
return;
}
if (spi_data->tx_in_progress) {
ESP_LOGE(TAG, "tx_in_progress is set");
this->status_set_warning();
return;
}
spi_data->tx_in_progress = true;
size_t buffer_size = this->get_buffer_size_();
size_t size = 0;
uint8_t *psrc = this->buf_;
uint8_t *pdest = this->dma_buf_ + 64;
// The 64 byte padding is a workaround for a SPI DMA bug where the
// output doesn't exactly start at the beginning of dma_buf_
while (size < buffer_size) {
uint8_t b = *psrc;
for (int i = 0; i < 8; i++) {
*pdest++ = b & (1 << (7 - i)) ? this->bit1_ : this->bit0_;
}
size++;
psrc++;
}
spi_data->first_run = false;
spi_dma_tx_enable(1);
this->status_clear_warning();
}
light::ESPColorView BekenSPILEDStripLightOutput::get_view_internal(int32_t index) const {
int32_t r = 0, g = 0, b = 0;
switch (this->rgb_order_) {
case ORDER_RGB:
r = 0;
g = 1;
b = 2;
break;
case ORDER_RBG:
r = 0;
g = 2;
b = 1;
break;
case ORDER_GRB:
r = 1;
g = 0;
b = 2;
break;
case ORDER_GBR:
r = 2;
g = 0;
b = 1;
break;
case ORDER_BGR:
r = 2;
g = 1;
b = 0;
break;
case ORDER_BRG:
r = 1;
g = 2;
b = 0;
break;
}
uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3;
uint8_t white = this->is_wrgb_ ? 0 : 3;
return {this->buf_ + (index * multiplier) + r + this->is_wrgb_,
this->buf_ + (index * multiplier) + g + this->is_wrgb_,
this->buf_ + (index * multiplier) + b + this->is_wrgb_,
this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr,
&this->effect_data_[index],
&this->correction_};
}
void BekenSPILEDStripLightOutput::dump_config() {
ESP_LOGCONFIG(TAG, "Beken SPI LED Strip:");
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
const char *rgb_order;
switch (this->rgb_order_) {
case ORDER_RGB:
rgb_order = "RGB";
break;
case ORDER_RBG:
rgb_order = "RBG";
break;
case ORDER_GRB:
rgb_order = "GRB";
break;
case ORDER_GBR:
rgb_order = "GBR";
break;
case ORDER_BGR:
rgb_order = "BGR";
break;
case ORDER_BRG:
rgb_order = "BRG";
break;
default:
rgb_order = "UNKNOWN";
break;
}
ESP_LOGCONFIG(TAG, " RGB Order: %s", rgb_order);
ESP_LOGCONFIG(TAG, " Max refresh rate: %" PRIu32, *this->max_refresh_rate_);
ESP_LOGCONFIG(TAG, " Number of LEDs: %u", this->num_leds_);
}
float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
} // namespace beken_spi_led_strip
} // namespace esphome
#endif // USE_BK72XX

View file

@ -0,0 +1,85 @@
#pragma once
#ifdef USE_BK72XX
#include "esphome/components/light/addressable_light.h"
#include "esphome/components/light/light_output.h"
#include "esphome/core/color.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace beken_spi_led_strip {
enum RGBOrder : uint8_t {
ORDER_RGB,
ORDER_RBG,
ORDER_GRB,
ORDER_GBR,
ORDER_BGR,
ORDER_BRG,
};
class BekenSPILEDStripLightOutput : public light::AddressableLight {
public:
void setup() override;
void write_state(light::LightState *state) override;
float get_setup_priority() const override;
int32_t size() const override { return this->num_leds_; }
light::LightTraits get_traits() override {
auto traits = light::LightTraits();
if (this->is_rgbw_ || this->is_wrgb_) {
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE});
} else {
traits.set_supported_color_modes({light::ColorMode::RGB});
}
return traits;
}
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
void set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency);
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
void clear_effect_data() override {
for (int i = 0; i < this->size(); i++)
this->effect_data_[i] = 0;
}
void dump_config() override;
protected:
light::ESPColorView get_view_internal(int32_t index) const override;
size_t get_buffer_size_() const { return this->num_leds_ * (this->is_rgbw_ || this->is_wrgb_ ? 4 : 3); }
uint8_t *buf_{nullptr};
uint8_t *effect_data_{nullptr};
uint8_t *dma_buf_{nullptr};
uint8_t pin_;
uint16_t num_leds_;
bool is_rgbw_;
bool is_wrgb_;
uint32_t spi_frequency_{6666666};
uint8_t bit0_{0xE0};
uint8_t bit1_{0xFC};
RGBOrder rgb_order_;
uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{};
};
} // namespace beken_spi_led_strip
} // namespace esphome
#endif // USE_BK72XX

View file

@ -0,0 +1,134 @@
from dataclasses import dataclass
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import libretiny, light
from esphome.const import (
CONF_CHIPSET,
CONF_IS_RGBW,
CONF_MAX_REFRESH_RATE,
CONF_NUM_LEDS,
CONF_OUTPUT_ID,
CONF_PIN,
CONF_RGB_ORDER,
)
CODEOWNERS = ["@Mat931"]
DEPENDENCIES = ["libretiny"]
beken_spi_led_strip_ns = cg.esphome_ns.namespace("beken_spi_led_strip")
BekenSPILEDStripLightOutput = beken_spi_led_strip_ns.class_(
"BekenSPILEDStripLightOutput", light.AddressableLight
)
RGBOrder = beken_spi_led_strip_ns.enum("RGBOrder")
RGB_ORDERS = {
"RGB": RGBOrder.ORDER_RGB,
"RBG": RGBOrder.ORDER_RBG,
"GRB": RGBOrder.ORDER_GRB,
"GBR": RGBOrder.ORDER_GBR,
"BGR": RGBOrder.ORDER_BGR,
"BRG": RGBOrder.ORDER_BRG,
}
@dataclass
class LEDStripTimings:
bit0: int
bit1: int
spi_frequency: int
CHIPSETS = {
"WS2812": LEDStripTimings(
0b11100000, 0b11111100, 6666666
), # Clock divider: 9, Bit time: 1350ns
"SK6812": LEDStripTimings(
0b11000000, 0b11111000, 7500000
), # Clock divider: 8, Bit time: 1200ns
"APA106": LEDStripTimings(
0b11000000, 0b11111110, 5454545
), # Clock divider: 11, Bit time: 1650ns
"SM16703": LEDStripTimings(
0b11000000, 0b11111110, 7500000
), # Clock divider: 8, Bit time: 1200ns
}
CONF_IS_WRGB = "is_wrgb"
SUPPORTED_PINS = {
libretiny.const.FAMILY_BK7231N: [16],
libretiny.const.FAMILY_BK7231T: [16],
libretiny.const.FAMILY_BK7251: [16],
}
def _validate_pin(value):
family = libretiny.get_libretiny_family()
if family not in SUPPORTED_PINS:
raise cv.Invalid(f"Chip family {family} is not supported.")
if value not in SUPPORTED_PINS[family]:
supported_pin_info = ", ".join(f"{x}" for x in SUPPORTED_PINS[family])
raise cv.Invalid(
f"Pin {value} is not supported on the {family}. Supported pins: {supported_pin_info}"
)
return value
def _validate_num_leds(value):
max_num_leds = 165 # 170
if value[CONF_IS_RGBW] or value[CONF_IS_WRGB]:
max_num_leds = 123 # 127
if value[CONF_NUM_LEDS] > max_num_leds:
raise cv.Invalid(
f"The maximum number of LEDs for this configuration is {max_num_leds}.",
path=CONF_NUM_LEDS,
)
return value
CONFIG_SCHEMA = cv.All(
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BekenSPILEDStripLightOutput),
cv.Required(CONF_PIN): cv.All(
pins.internal_gpio_output_pin_number, _validate_pin
),
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
}
),
_validate_num_leds,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
await light.register_light(var, config)
await cg.register_component(var, config)
cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
cg.add(var.set_pin(config[CONF_PIN]))
if CONF_MAX_REFRESH_RATE in config:
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
chipset = CHIPSETS[config[CONF_CHIPSET]]
cg.add(
var.set_led_params(
chipset.bit0,
chipset.bit1,
chipset.spi_frequency,
)
)
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))

View file

@ -18,10 +18,11 @@ class BinaryLightOutput : public light::LightOutput {
void write_state(light::LightState *state) override { void write_state(light::LightState *state) override {
bool binary; bool binary;
state->current_values_as_binary(&binary); state->current_values_as_binary(&binary);
if (binary) if (binary) {
this->output_->turn_on(); this->output_->turn_on();
else } else {
this->output_->turn_off(); this->output_->turn_off();
}
} }
protected: protected:

View file

@ -1,10 +1,8 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome import automation, core from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DELAY, CONF_DELAY,
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
@ -16,6 +14,7 @@ from esphome.const import (
CONF_INVERTED, CONF_INVERTED,
CONF_MAX_LENGTH, CONF_MAX_LENGTH,
CONF_MIN_LENGTH, CONF_MIN_LENGTH,
CONF_MQTT_ID,
CONF_ON_CLICK, CONF_ON_CLICK,
CONF_ON_DOUBLE_CLICK, CONF_ON_DOUBLE_CLICK,
CONF_ON_MULTI_CLICK, CONF_ON_MULTI_CLICK,
@ -26,7 +25,7 @@ from esphome.const import (
CONF_STATE, CONF_STATE,
CONF_TIMING, CONF_TIMING,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_MQTT_ID, CONF_WEB_SERVER_ID,
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING, DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CARBON_MONOXIDE,
@ -58,6 +57,8 @@ from esphome.const import (
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome.util import Registry from esphome.util import Registry
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@ -385,70 +386,76 @@ def validate_click_timing(value):
return value return value
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( BINARY_SENSOR_SCHEMA = (
{ cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
cv.GenerateID(): cv.declare_id(BinarySensor), .extend(cv.MQTT_COMPONENT_SCHEMA)
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( .extend(
mqtt.MQTTBinarySensorComponent {
), cv.GenerateID(): cv.declare_id(BinarySensor),
cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean, cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
cv.Optional(CONF_DEVICE_CLASS): validate_device_class, mqtt.MQTTBinarySensorComponent
cv.Optional(CONF_FILTERS): validate_filters, ),
cv.Optional(CONF_ON_PRESS): automation.validate_automation( cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
{ cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), cv.Optional(CONF_FILTERS): validate_filters,
} cv.Optional(CONF_ON_PRESS): automation.validate_automation(
),
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
}
),
cv.Optional(CONF_ON_CLICK): cv.All(
automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
} }
), ),
validate_click_timing, cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
),
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
} }
), ),
validate_click_timing, cv.Optional(CONF_ON_CLICK): cv.All(
), automation.validate_automation(
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), cv.Optional(
cv.Required(CONF_TIMING): cv.All( CONF_MIN_LENGTH, default="50ms"
[parse_multi_click_timing_str], validate_multi_click_timing ): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
), ),
cv.Optional( validate_click_timing,
CONF_INVALID_COOLDOWN, default="1s" ),
): cv.positive_time_period_milliseconds, cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
} automation.validate_automation(
), {
cv.Optional(CONF_ON_STATE): automation.validate_automation( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
{ DoubleClickTrigger
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), ),
} cv.Optional(
), CONF_MIN_LENGTH, default="50ms"
} ): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
cv.Required(CONF_TIMING): cv.All(
[parse_multi_click_timing_str], validate_multi_click_timing
),
cv.Optional(
CONF_INVALID_COOLDOWN, default="1s"
): cv.positive_time_period_milliseconds,
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
)
) )
_UNDEF = object() _UNDEF = object()
@ -536,6 +543,10 @@ async def setup_binary_sensor_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_binary_sensor(var, config): async def register_binary_sensor(var, config):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):

View file

@ -51,15 +51,15 @@ void binary_sensor::MultiClickTrigger::on_state_(bool state) {
MultiClickTriggerEvent evt = this->timing_[*this->at_index_]; MultiClickTriggerEvent evt = this->timing_[*this->at_index_];
if (evt.max_length != 4294967294UL) { if (evt.max_length != 4294967294UL) {
ESP_LOGV(TAG, "A i=%u min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT ESP_LOGV(TAG, "A i=%zu min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT
this->schedule_is_valid_(evt.min_length); this->schedule_is_valid_(evt.min_length);
this->schedule_is_not_valid_(evt.max_length); this->schedule_is_not_valid_(evt.max_length);
} else if (*this->at_index_ + 1 != this->timing_.size()) { } else if (*this->at_index_ + 1 != this->timing_.size()) {
ESP_LOGV(TAG, "B i=%u min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
this->cancel_timeout("is_not_valid"); this->cancel_timeout("is_not_valid");
this->schedule_is_valid_(evt.min_length); this->schedule_is_valid_(evt.min_length);
} else { } else {
ESP_LOGV(TAG, "C i=%u min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
this->is_valid_ = false; this->is_valid_ = false;
this->cancel_timeout("is_not_valid"); this->cancel_timeout("is_not_valid");
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); }); this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
@ -98,6 +98,11 @@ void binary_sensor::MultiClickTrigger::schedule_is_not_valid_(uint32_t max_lengt
this->schedule_cooldown_(); this->schedule_cooldown_();
}); });
} }
void binary_sensor::MultiClickTrigger::cancel() {
ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled.");
this->is_valid_ = false;
this->schedule_cooldown_();
}
void binary_sensor::MultiClickTrigger::trigger_() { void binary_sensor::MultiClickTrigger::trigger_() {
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!"); ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
this->at_index_.reset(); this->at_index_.reset();

View file

@ -105,6 +105,8 @@ class MultiClickTrigger : public Trigger<>, public Component {
void set_invalid_cooldown(uint32_t invalid_cooldown) { this->invalid_cooldown_ = invalid_cooldown; } void set_invalid_cooldown(uint32_t invalid_cooldown) { this->invalid_cooldown_ = invalid_cooldown; }
void cancel();
protected: protected:
void on_state_(bool state); void on_state_(bool state);
void schedule_cooldown_(); void schedule_cooldown_();

View file

@ -0,0 +1 @@
CODEOWNERS = ["@athom-tech", "@tarontop", "@jesserockz"]

View file

@ -0,0 +1,238 @@
#include "bl0906.h"
#include "constants.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bl0906 {
static const char *const TAG = "bl0906";
constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
constexpr int32_t to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
// The SUM byte is (Addr+Data_L+Data_M+Data_H)&0xFF negated;
constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data) {
return (address + data->l + data->m + data->h) ^ 0xFF;
}
void BL0906::loop() {
if (this->current_channel_ == UINT8_MAX) {
return;
}
while (this->available())
this->flush();
if (this->current_channel_ == 0) {
// Temperature
this->read_data_(BL0906_TEMPERATURE, BL0906_TREF, this->temperature_sensor_);
} else if (this->current_channel_ == 1) {
this->read_data_(BL0906_I_1_RMS, BL0906_IREF, this->current_1_sensor_);
this->read_data_(BL0906_WATT_1, BL0906_PREF, this->power_1_sensor_);
this->read_data_(BL0906_CF_1_CNT, BL0906_EREF, this->energy_1_sensor_);
} else if (this->current_channel_ == 2) {
this->read_data_(BL0906_I_2_RMS, BL0906_IREF, this->current_2_sensor_);
this->read_data_(BL0906_WATT_2, BL0906_PREF, this->power_2_sensor_);
this->read_data_(BL0906_CF_2_CNT, BL0906_EREF, this->energy_2_sensor_);
} else if (this->current_channel_ == 3) {
this->read_data_(BL0906_I_3_RMS, BL0906_IREF, this->current_3_sensor_);
this->read_data_(BL0906_WATT_3, BL0906_PREF, this->power_3_sensor_);
this->read_data_(BL0906_CF_3_CNT, BL0906_EREF, this->energy_3_sensor_);
} else if (this->current_channel_ == 4) {
this->read_data_(BL0906_I_4_RMS, BL0906_IREF, this->current_4_sensor_);
this->read_data_(BL0906_WATT_4, BL0906_PREF, this->power_4_sensor_);
this->read_data_(BL0906_CF_4_CNT, BL0906_EREF, this->energy_4_sensor_);
} else if (this->current_channel_ == 5) {
this->read_data_(BL0906_I_5_RMS, BL0906_IREF, this->current_5_sensor_);
this->read_data_(BL0906_WATT_5, BL0906_PREF, this->power_5_sensor_);
this->read_data_(BL0906_CF_5_CNT, BL0906_EREF, this->energy_5_sensor_);
} else if (this->current_channel_ == 6) {
this->read_data_(BL0906_I_6_RMS, BL0906_IREF, this->current_6_sensor_);
this->read_data_(BL0906_WATT_6, BL0906_PREF, this->power_6_sensor_);
this->read_data_(BL0906_CF_6_CNT, BL0906_EREF, this->energy_6_sensor_);
} else if (this->current_channel_ == UINT8_MAX - 2) {
// Frequency
this->read_data_(BL0906_FREQUENCY, BL0906_FREF, frequency_sensor_);
// Voltage
this->read_data_(BL0906_V_RMS, BL0906_UREF, voltage_sensor_);
} else if (this->current_channel_ == UINT8_MAX - 1) {
// Total power
this->read_data_(BL0906_WATT_SUM, BL0906_WATT, this->total_power_sensor_);
// Total Energy
this->read_data_(BL0906_CF_SUM_CNT, BL0906_CF, this->total_energy_sensor_);
} else {
this->current_channel_ = UINT8_MAX - 2; // Go to frequency and voltage
return;
}
this->current_channel_++;
this->handle_actions_();
}
void BL0906::setup() {
while (this->available())
this->flush();
this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE));
// Calibration (1: register address; 2: value before calibration; 3: value after calibration)
this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0); // Calibration current_1
this->bias_correction_(BL0906_RMSOS_2, 0.01500, 0);
this->bias_correction_(BL0906_RMSOS_3, 0.01400, 0);
this->bias_correction_(BL0906_RMSOS_4, 0.01300, 0);
this->bias_correction_(BL0906_RMSOS_5, 0.01200, 0);
this->bias_correction_(BL0906_RMSOS_6, 0.01200, 0); // Calibration current_6
this->write_array(USR_WRPROT_ONLYREAD, sizeof(USR_WRPROT_ONLYREAD));
}
void BL0906::update() { this->current_channel_ = 0; }
size_t BL0906::enqueue_action_(ActionCallbackFuncPtr function) {
this->action_queue_.push_back(function);
return this->action_queue_.size();
}
void BL0906::handle_actions_() {
if (this->action_queue_.empty()) {
return;
}
ActionCallbackFuncPtr ptr_func = nullptr;
for (int i = 0; i < this->action_queue_.size(); i++) {
ptr_func = this->action_queue_[i];
if (ptr_func) {
ESP_LOGI(TAG, "HandleActionCallback[%d]...", i);
(this->*ptr_func)();
}
}
while (this->available()) {
this->read();
}
this->action_queue_.clear();
}
// Reset energy
void BL0906::reset_energy_() {
this->write_array(BL0906_INIT[0], 6);
delay(1);
this->flush();
ESP_LOGW(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_INIT[0][0], BL0906_INIT[0][1], BL0906_INIT[0][2],
BL0906_INIT[0][3], BL0906_INIT[0][4], BL0906_INIT[0][5]);
}
// Read data
void BL0906::read_data_(const uint8_t address, const float reference, sensor::Sensor *sensor) {
if (sensor == nullptr) {
return;
}
DataPacket buffer;
ube24_t data_u24;
sbe24_t data_s24;
float value = 0;
bool signed_result = reference == BL0906_TREF || reference == BL0906_WATT || reference == BL0906_PREF;
this->write_byte(BL0906_READ_COMMAND);
this->write_byte(address);
if (this->read_array((uint8_t *) &buffer, sizeof(buffer) - 1)) {
if (bl0906_checksum(address, &buffer) == buffer.checksum) {
if (signed_result) {
data_s24.l = buffer.l;
data_s24.m = buffer.m;
data_s24.h = buffer.h;
} else {
data_u24.l = buffer.l;
data_u24.m = buffer.m;
data_u24.h = buffer.h;
}
} else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
while (read() >= 0)
;
return;
}
}
// Power
if (reference == BL0906_PREF) {
value = (float) to_int32_t(data_s24) * reference;
}
// Total power
if (reference == BL0906_WATT) {
value = (float) to_int32_t(data_s24) * reference;
}
// Voltage, current, power, total power
if (reference == BL0906_UREF || reference == BL0906_IREF || reference == BL0906_EREF || reference == BL0906_CF) {
value = (float) to_uint32_t(data_u24) * reference;
}
// Frequency
if (reference == BL0906_FREF) {
value = reference / (float) to_uint32_t(data_u24);
}
// Chip temperature
if (reference == BL0906_TREF) {
value = (float) to_int32_t(data_s24);
value = (value - 64) * 12.5 / 59 - 40;
}
sensor->publish_state(value);
}
// RMS offset correction
void BL0906::bias_correction_(uint8_t address, float measurements, float correction) {
DataPacket data;
float ki = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097; // Current coefficient
float i_rms0 = measurements * ki;
float i_rms = correction * ki;
int32_t value = (i_rms * i_rms - i_rms0 * i_rms0) / 256;
data.l = value << 24 >> 24;
data.m = value << 16 >> 24;
if (value < 0) {
data.h = (value << 8 >> 24) | 0b10000000;
}
data.address = bl0906_checksum(address, &data);
ESP_LOGV(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_WRITE_COMMAND, address, data.l, data.m, data.h, data.address);
this->write_byte(BL0906_WRITE_COMMAND);
this->write_byte(address);
this->write_byte(data.l);
this->write_byte(data.m);
this->write_byte(data.h);
this->write_byte(data.address);
}
void BL0906::dump_config() {
ESP_LOGCONFIG(TAG, "BL0906:");
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current1", this->current_1_sensor_);
LOG_SENSOR(" ", "Current2", this->current_2_sensor_);
LOG_SENSOR(" ", "Current3", this->current_3_sensor_);
LOG_SENSOR(" ", "Current4", this->current_4_sensor_);
LOG_SENSOR(" ", "Current5", this->current_5_sensor_);
LOG_SENSOR(" ", "Current6", this->current_6_sensor_);
LOG_SENSOR(" ", "Power1", this->power_1_sensor_);
LOG_SENSOR(" ", "Power2", this->power_2_sensor_);
LOG_SENSOR(" ", "Power3", this->power_3_sensor_);
LOG_SENSOR(" ", "Power4", this->power_4_sensor_);
LOG_SENSOR(" ", "Power5", this->power_5_sensor_);
LOG_SENSOR(" ", "Power6", this->power_6_sensor_);
LOG_SENSOR(" ", "Energy1", this->energy_1_sensor_);
LOG_SENSOR(" ", "Energy2", this->energy_2_sensor_);
LOG_SENSOR(" ", "Energy3", this->energy_3_sensor_);
LOG_SENSOR(" ", "Energy4", this->energy_4_sensor_);
LOG_SENSOR(" ", "Energy5", this->energy_5_sensor_);
LOG_SENSOR(" ", "Energy6", this->energy_6_sensor_);
LOG_SENSOR(" ", "Total Power", this->total_power_sensor_);
LOG_SENSOR(" ", "Total Energy", this->total_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->frequency_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
}
} // namespace bl0906
} // namespace esphome

View file

@ -0,0 +1,96 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/datatypes.h"
// https://www.belling.com.cn/media/file_object/bel_product/BL0906/datasheet/BL0906_V1.02_cn.pdf
// https://www.belling.com.cn/media/file_object/bel_product/BL0906/guide/BL0906%20APP%20Note_V1.02.pdf
namespace esphome {
namespace bl0906 {
struct DataPacket { // NOLINT(altera-struct-pack-align)
uint8_t l{0};
uint8_t m{0};
uint8_t h{0};
uint8_t checksum; // checksum
uint8_t address;
} __attribute__((packed));
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l{0};
uint8_t m{0};
uint8_t h{0};
} __attribute__((packed));
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l{0};
uint8_t m{0};
int8_t h{0};
} __attribute__((packed));
template<typename... Ts> class ResetEnergyAction;
class BL0906;
using ActionCallbackFuncPtr = void (BL0906::*)();
class BL0906 : public PollingComponent, public uart::UARTDevice {
SUB_SENSOR(voltage)
SUB_SENSOR(current_1)
SUB_SENSOR(current_2)
SUB_SENSOR(current_3)
SUB_SENSOR(current_4)
SUB_SENSOR(current_5)
SUB_SENSOR(current_6)
SUB_SENSOR(power_1)
SUB_SENSOR(power_2)
SUB_SENSOR(power_3)
SUB_SENSOR(power_4)
SUB_SENSOR(power_5)
SUB_SENSOR(power_6)
SUB_SENSOR(total_power)
SUB_SENSOR(energy_1)
SUB_SENSOR(energy_2)
SUB_SENSOR(energy_3)
SUB_SENSOR(energy_4)
SUB_SENSOR(energy_5)
SUB_SENSOR(energy_6)
SUB_SENSOR(total_energy)
SUB_SENSOR(frequency)
SUB_SENSOR(temperature)
public:
void loop() override;
void update() override;
void setup() override;
void dump_config() override;
protected:
template<typename... Ts> friend class ResetEnergyAction;
void reset_energy_();
void read_data_(uint8_t address, float reference, sensor::Sensor *sensor);
void bias_correction_(uint8_t address, float measurements, float correction);
uint8_t current_channel_{0};
size_t enqueue_action_(ActionCallbackFuncPtr function);
void handle_actions_();
private:
std::vector<ActionCallbackFuncPtr> action_queue_{};
};
template<typename... Ts> class ResetEnergyAction : public Action<Ts...>, public Parented<BL0906> {
public:
void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); }
};
} // namespace bl0906
} // namespace esphome

View file

@ -0,0 +1,4 @@
# const.py
ICON_ENERGY = "mdi:lightning-bolt"
ICON_FREQUENCY = "mdi:cosine-wave"
ICON_VOLTAGE = "mdi:sine-wave"

View file

@ -0,0 +1,122 @@
#pragma once
#include <cstdint>
namespace esphome {
namespace bl0906 {
// Total power conversion
static const float BL0906_WATT = 16 * 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) /
(40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000);
// Total Energy conversion
static const float BL0906_CF = 16 * 4194304 * 0.032768 * 16 /
(3600000 * 16 *
(40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 /
(1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000))));
// Frequency conversion
static const float BL0906_FREF = 10000000;
// Temperature conversion
static const float BL0906_TREF = 12.5 / 59 - 40;
// Current conversion
static const float BL0906_IREF = 1.097 / (12875 * 1 * (5.1 + 5.1) * 1000 / 2000);
// Voltage conversion
static const float BL0906_UREF = 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / (13162 * 1 * 100 * 1000);
// Power conversion
static const float BL0906_PREF = 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) /
(40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000);
// Energy conversion
static const float BL0906_EREF = 4194304 * 0.032768 * 16 /
(3600000 * 16 *
(40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 /
(1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000))));
// Current coefficient
static const float BL0906_KI = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097;
// Power coefficient
static const float BL0906_KP = 40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / 1.097 / 1.097 /
(20000 + 20000 + 20000 + 20000 + 20000);
static const uint8_t USR_WRPROT_WITABLE[6] = {0xCA, 0x9E, 0x55, 0x55, 0x00, 0xB7};
static const uint8_t USR_WRPROT_ONLYREAD[6] = {0xCA, 0x9E, 0x00, 0x00, 0x00, 0x61};
static const uint8_t BL0906_READ_COMMAND = 0x35;
static const uint8_t BL0906_WRITE_COMMAND = 0xCA;
// Register address
// Voltage
static const uint8_t BL0906_V_RMS = 0x16;
// Total power
static const uint8_t BL0906_WATT_SUM = 0X2C;
// Current1~6
static const uint8_t BL0906_I_1_RMS = 0x0D; // current_1
static const uint8_t BL0906_I_2_RMS = 0x0E;
static const uint8_t BL0906_I_3_RMS = 0x0F;
static const uint8_t BL0906_I_4_RMS = 0x10;
static const uint8_t BL0906_I_5_RMS = 0x13;
static const uint8_t BL0906_I_6_RMS = 0x14; // current_6
// Power1~6
static const uint8_t BL0906_WATT_1 = 0X23; // power_1
static const uint8_t BL0906_WATT_2 = 0X24;
static const uint8_t BL0906_WATT_3 = 0X25;
static const uint8_t BL0906_WATT_4 = 0X26;
static const uint8_t BL0906_WATT_5 = 0X29;
static const uint8_t BL0906_WATT_6 = 0X2A; // power_6
// Active pulse count, unsigned
static const uint8_t BL0906_CF_1_CNT = 0X30; // Channel_1
static const uint8_t BL0906_CF_2_CNT = 0X31;
static const uint8_t BL0906_CF_3_CNT = 0X32;
static const uint8_t BL0906_CF_4_CNT = 0X33;
static const uint8_t BL0906_CF_5_CNT = 0X36;
static const uint8_t BL0906_CF_6_CNT = 0X37; // Channel_6
// Total active pulse count, unsigned
static const uint8_t BL0906_CF_SUM_CNT = 0X39;
// Voltage frequency cycle
static const uint8_t BL0906_FREQUENCY = 0X4E;
// Internal temperature
static const uint8_t BL0906_TEMPERATURE = 0X5E;
// Calibration register
// RMS gain adjustment register
static const uint8_t BL0906_RMSGN_1 = 0x6D; // Channel_1
static const uint8_t BL0906_RMSGN_2 = 0x6E;
static const uint8_t BL0906_RMSGN_3 = 0x6F;
static const uint8_t BL0906_RMSGN_4 = 0x70;
static const uint8_t BL0906_RMSGN_5 = 0x73;
static const uint8_t BL0906_RMSGN_6 = 0x74; // Channel_6
// RMS offset correction register
static const uint8_t BL0906_RMSOS_1 = 0x78; // Channel_1
static const uint8_t BL0906_RMSOS_2 = 0x79;
static const uint8_t BL0906_RMSOS_3 = 0x7A;
static const uint8_t BL0906_RMSOS_4 = 0x7B;
static const uint8_t BL0906_RMSOS_5 = 0x7E;
static const uint8_t BL0906_RMSOS_6 = 0x7F; // Channel_6
// Active power gain adjustment register
static const uint8_t BL0906_WATTGN_1 = 0xB7; // Channel_1
static const uint8_t BL0906_WATTGN_2 = 0xB8;
static const uint8_t BL0906_WATTGN_3 = 0xB9;
static const uint8_t BL0906_WATTGN_4 = 0xBA;
static const uint8_t BL0906_WATTGN_5 = 0xBD;
static const uint8_t BL0906_WATTGN_6 = 0xBE; // Channel_6
// User write protection setting register,
// You must first write 0x5555 to the write protection setting register before writing to other registers.
static const uint8_t BL0906_USR_WRPROT = 0x9E;
// Reset Register
static const uint8_t BL0906_SOFT_RESET = 0x9F;
const uint8_t BL0906_INIT[2][6] = {
// Reset to default
{BL0906_WRITE_COMMAND, BL0906_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x52},
// Enable User Operation Write
{BL0906_WRITE_COMMAND, BL0906_USR_WRPROT, 0x55, 0x55, 0x00, 0xB7}};
} // namespace bl0906
} // namespace esphome

View file

@ -0,0 +1,185 @@
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_CHANNEL,
CONF_CURRENT,
CONF_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_NAME,
CONF_POWER,
CONF_TEMPERATURE,
CONF_TOTAL_POWER,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC,
ICON_POWER,
ICON_THERMOMETER,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_HERTZ,
UNIT_KILOWATT_HOURS,
UNIT_VOLT,
UNIT_WATT,
)
# Import ICONS not included in esphome's const.py, from the local components const.py
from .const import ICON_ENERGY, ICON_FREQUENCY, ICON_VOLTAGE
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["bl0906"]
CONF_TOTAL_ENERGY = "total_energy"
bl0906_ns = cg.esphome_ns.namespace("bl0906")
BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice)
ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BL0906),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
icon=ICON_FREQUENCY,
accuracy_decimals=0,
device_class=DEVICE_CLASS_FREQUENCY,
unit_of_measurement=UNIT_HERTZ,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
icon=ICON_THERMOMETER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
unit_of_measurement=UNIT_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
icon=ICON_VOLTAGE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
unit_of_measurement=UNIT_VOLT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema(
icon=ICON_POWER,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
unit_of_measurement=UNIT_WATT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TOTAL_ENERGY): sensor.sensor_schema(
icon=ICON_ENERGY,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
unit_of_measurement=UNIT_KILOWATT_HOURS,
),
}
)
.extend(
cv.Schema(
{
cv.Optional(f"{CONF_CHANNEL}_{i + 1}"): cv.Schema(
{
cv.Optional(CONF_CURRENT): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_CURRENT_AC,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
unit_of_measurement=UNIT_AMPERE,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_POWER): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_POWER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
unit_of_measurement=UNIT_WATT,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_ENERGY): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_ENERGY,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
unit_of_measurement=UNIT_KILOWATT_HOURS,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
key=CONF_NAME,
),
}
)
for i in range(6)
}
)
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.polling_component_schema("60s"))
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"bl0906", baud_rate=19200, require_tx=True, require_rx=True
)
@automation.register_action(
"bl0906.reset_energy",
ResetEnergyAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(BL0906),
}
),
)
async def reset_energy_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_frequency_sensor(sens))
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))
for i in range(6):
if channel_config := config.get(f"{CONF_CHANNEL}_{i + 1}"):
if current_config := channel_config.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(getattr(var, f"set_current_{i + 1}_sensor")(sens))
if power_config := channel_config.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(getattr(var, f"set_power_{i + 1}_sensor")(sens))
if energy_config := channel_config.get(CONF_ENERGY):
sens = await sensor.new_sensor(energy_config)
cg.add(getattr(var, f"set_energy_{i + 1}_sensor")(sens))
if total_power_config := config.get(CONF_TOTAL_POWER):
sens = await sensor.new_sensor(total_power_config)
cg.add(var.set_total_power_sensor(sens))
if total_energy_config := config.get(CONF_TOTAL_ENERGY):
sens = await sensor.new_sensor(total_energy_config)
cg.add(var.set_total_energy_sensor(sens))

View file

@ -6,6 +6,7 @@ from esphome.const import (
CONF_ENERGY, CONF_ENERGY,
CONF_EXTERNAL_TEMPERATURE, CONF_EXTERNAL_TEMPERATURE,
CONF_ID, CONF_ID,
CONF_INTERNAL_TEMPERATURE,
CONF_POWER, CONF_POWER,
CONF_VOLTAGE, CONF_VOLTAGE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
@ -24,7 +25,6 @@ from esphome.const import (
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
bl0940_ns = cg.esphome_ns.namespace("bl0940") bl0940_ns = cg.esphome_ns.namespace("bl0940")
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice) BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)

View file

@ -1 +1 @@
CODEOWNERS = ["@dbuezas"] CODEOWNERS = ["@dbuezas", "@dwmw2"]

View file

@ -2,6 +2,8 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes> #include <cinttypes>
// Datasheet: https://www.belling.com.cn/media/file_object/bel_product/BL0942/datasheet/BL0942_V1.06_en.pdf
namespace esphome { namespace esphome {
namespace bl0942 { namespace bl0942 {
@ -12,43 +14,64 @@ static const uint8_t BL0942_FULL_PACKET = 0xAA;
static const uint8_t BL0942_PACKET_HEADER = 0x55; static const uint8_t BL0942_PACKET_HEADER = 0x55;
static const uint8_t BL0942_WRITE_COMMAND = 0xA8; static const uint8_t BL0942_WRITE_COMMAND = 0xA8;
static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10;
static const uint8_t BL0942_REG_MODE = 0x18; static const uint8_t BL0942_REG_I_RMSOS = 0x12;
static const uint8_t BL0942_REG_SOFT_RESET = 0x19; static const uint8_t BL0942_REG_WA_CREEP = 0x14;
static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; static const uint8_t BL0942_REG_I_FAST_RMS_TH = 0x15;
static const uint8_t BL0942_REG_I_FAST_RMS_CYC = 0x16;
static const uint8_t BL0942_REG_FREQ_CYC = 0x17;
static const uint8_t BL0942_REG_OT_FUNX = 0x18;
static const uint8_t BL0942_REG_MODE = 0x19;
static const uint8_t BL0942_REG_SOFT_RESET = 0x1C;
static const uint8_t BL0942_REG_USR_WRPROT = 0x1D;
static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; static const uint8_t BL0942_REG_TPS_CTRL = 0x1B;
// TODO: Confirm insialisation works as intended static const uint32_t BL0942_REG_MODE_RESV = 0x03;
const uint8_t BL0942_INIT[5][6] = { static const uint32_t BL0942_REG_MODE_CF_EN = 0x04;
// Reset to default static const uint32_t BL0942_REG_MODE_RMS_UPDATE_SEL = 0x08;
{BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, static const uint32_t BL0942_REG_MODE_FAST_RMS_SEL = 0x10;
// Enable User Operation Write static const uint32_t BL0942_REG_MODE_AC_FREQ_SEL = 0x20;
{BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, static const uint32_t BL0942_REG_MODE_CF_CNT_CLR_SEL = 0x40;
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS static const uint32_t BL0942_REG_MODE_CF_CNT_ADD_SEL = 0x80;
{BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, static const uint32_t BL0942_REG_MODE_UART_RATE_19200 = 0x200;
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS static const uint32_t BL0942_REG_MODE_UART_RATE_38400 = 0x300;
{BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, static const uint32_t BL0942_REG_MODE_DEFAULT =
// 0x181C = Half cycle, Fast RMS threshold 6172 BL0942_REG_MODE_RESV | BL0942_REG_MODE_CF_EN | BL0942_REG_MODE_CF_CNT_ADD_SEL;
{BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
static const uint32_t BL0942_REG_SOFT_RESET_MAGIC = 0x5a5a5a;
static const uint32_t BL0942_REG_USR_WRPROT_MAGIC = 0x55;
// 23-byte packet, 11 bits per byte, 2400 baud: about 105ms
static const uint32_t PKT_TIMEOUT_MS = 200;
void BL0942::loop() { void BL0942::loop() {
DataPacket buffer; DataPacket buffer;
if (!this->available()) { int avail = this->available();
if (!avail) {
return; return;
} }
if (read_array((uint8_t *) &buffer, sizeof(buffer))) { if (avail < sizeof(buffer)) {
if (validate_checksum(&buffer)) { if (!this->rx_start_) {
received_package_(&buffer); this->rx_start_ = millis();
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail);
this->read_array((uint8_t *) &buffer, avail);
this->rx_start_ = 0;
} }
} else { return;
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
while (read() >= 0)
;
} }
if (this->read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (this->validate_checksum_(&buffer)) {
this->received_package_(&buffer);
}
}
this->rx_start_ = 0;
} }
bool BL0942::validate_checksum(DataPacket *data) { bool BL0942::validate_checksum_(DataPacket *data) {
uint8_t checksum = BL0942_READ_COMMAND; uint8_t checksum = BL0942_READ_COMMAND | this->address_;
// Whole package but checksum // Whole package but checksum
uint8_t *raw = (uint8_t *) data; uint8_t *raw = (uint8_t *) data;
for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
@ -61,17 +84,73 @@ bool BL0942::validate_checksum(DataPacket *data) {
return checksum == data->checksum; return checksum == data->checksum;
} }
void BL0942::update() { void BL0942::write_reg_(uint8_t reg, uint32_t val) {
uint8_t pkt[6];
this->flush(); this->flush();
this->write_byte(BL0942_READ_COMMAND); pkt[0] = BL0942_WRITE_COMMAND | this->address_;
pkt[1] = reg;
pkt[2] = (val & 0xff);
pkt[3] = (val >> 8) & 0xff;
pkt[4] = (val >> 16) & 0xff;
pkt[5] = (pkt[0] + pkt[1] + pkt[2] + pkt[3] + pkt[4]) ^ 0xff;
this->write_array(pkt, 6);
delay(1);
}
int BL0942::read_reg_(uint8_t reg) {
union {
uint8_t b[4];
uint32_le_t le32;
} resp;
this->write_byte(BL0942_READ_COMMAND | this->address_);
this->write_byte(reg);
this->flush();
if (this->read_array(resp.b, 4) &&
resp.b[3] ==
(uint8_t) ((BL0942_READ_COMMAND + this->address_ + reg + resp.b[0] + resp.b[1] + resp.b[2]) ^ 0xff)) {
resp.b[3] = 0;
return resp.le32;
}
return -1;
}
void BL0942::update() {
this->write_byte(BL0942_READ_COMMAND | this->address_);
this->write_byte(BL0942_FULL_PACKET); this->write_byte(BL0942_FULL_PACKET);
} }
void BL0942::setup() { void BL0942::setup() {
for (auto *i : BL0942_INIT) { // If either current or voltage references are set explicitly by the user,
this->write_array(i, 6); // calculate the power reference from it unless that is also explicitly set.
delay(1); if ((this->current_reference_set_ || this->voltage_reference_set_) && !this->power_reference_set_) {
this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0 / 305978.0) / 73989.0;
this->power_reference_set_ = true;
} }
// Similarly for energy reference, if the power reference was set by the user
// either implicitly or explicitly.
if (this->power_reference_set_ && !this->energy_reference_set_) {
this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4;
this->energy_reference_set_ = true;
}
this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC);
if (this->reset_)
this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC);
uint32_t mode = BL0942_REG_MODE_DEFAULT;
mode |= BL0942_REG_MODE_RMS_UPDATE_SEL; /* 800ms refresh time */
if (this->line_freq_ == LINE_FREQUENCY_60HZ)
mode |= BL0942_REG_MODE_AC_FREQ_SEL;
this->write_reg_(BL0942_REG_MODE, mode);
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
if (this->read_reg_(BL0942_REG_MODE) != mode)
this->status_set_warning("BL0942 setup failed!");
this->flush(); this->flush();
} }
@ -82,10 +161,17 @@ void BL0942::received_package_(DataPacket *data) {
return; return;
} }
// cf_cnt is only 24 bits, so track overflows
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
if (cf_cnt < this->prev_cf_cnt_) {
cf_cnt += 0x1000000;
}
this->prev_cf_cnt_ = cf_cnt;
float v_rms = (uint24_t) data->v_rms / voltage_reference_; float v_rms = (uint24_t) data->v_rms / voltage_reference_;
float i_rms = (uint24_t) data->i_rms / current_reference_; float i_rms = (uint24_t) data->i_rms / current_reference_;
float watt = (int24_t) data->watt / power_reference_; float watt = (int24_t) data->watt / power_reference_;
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
float total_energy_consumption = cf_cnt / energy_reference_; float total_energy_consumption = cf_cnt / energy_reference_;
float frequency = 1000000.0f / data->frequency; float frequency = 1000000.0f / data->frequency;
@ -104,18 +190,25 @@ void BL0942::received_package_(DataPacket *data) {
if (frequency_sensor_ != nullptr) { if (frequency_sensor_ != nullptr) {
frequency_sensor_->publish_state(frequency); frequency_sensor_->publish_state(frequency);
} }
this->status_clear_warning();
ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms, ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms,
watt, cf_cnt, total_energy_consumption, frequency, data->status); watt, cf_cnt, total_energy_consumption, frequency, data->status);
} }
void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity) void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity)
ESP_LOGCONFIG(TAG, "BL0942:"); ESP_LOGCONFIG(TAG, "BL0942:");
ESP_LOGCONFIG(TAG, " Reset: %s", TRUEFALSE(this->reset_));
ESP_LOGCONFIG(TAG, " Address: %d", this->address_);
ESP_LOGCONFIG(TAG, " Nominal line frequency: %d Hz", this->line_freq_);
ESP_LOGCONFIG(TAG, " Current reference: %f", this->current_reference_);
ESP_LOGCONFIG(TAG, " Energy reference: %f", this->energy_reference_);
ESP_LOGCONFIG(TAG, " Power reference: %f", this->power_reference_);
ESP_LOGCONFIG(TAG, " Voltage reference: %f", this->voltage_reference_);
LOG_SENSOR("", "Voltage", this->voltage_sensor_); LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_); LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_); LOG_SENSOR("", "Power", this->power_sensor_);
LOG_SENSOR("", "Energy", this->energy_sensor_); LOG_SENSOR("", "Energy", this->energy_sensor_);
LOG_SENSOR("", "frequency", this->frequency_sensor_); LOG_SENSOR("", "Frequency", this->frequency_sensor_);
} }
} // namespace bl0942 } // namespace bl0942

View file

@ -8,6 +8,57 @@
namespace esphome { namespace esphome {
namespace bl0942 { namespace bl0942 {
// The BL0942 IC is "calibration-free", which means that it doesn't care
// at all about calibration, and that's left to software. It measures a
// voltage differential on its IP/IN pins which linearly proportional to
// the current flow, and another on its VP pin which is proportional to
// the line voltage. It never knows the actual calibration; the values
// it reports are solely in terms of those inputs.
//
// The datasheet refers to the input voltages as I(A) and V(V), both
// in millivolts. It measures them against a reference voltage Vref,
// which is typically 1.218V (but that absolute value is meaningless
// without the actual calibration anyway).
//
// The reported I_RMS value is 305978 I(A)/Vref, and the reported V_RMS
// value is 73989 V(V)/Vref. So we can calibrate those by applying a
// simple meter with a resistive load.
//
// The chip also measures the phase difference between voltage and
// current, and uses it to calculate the power factor (cos φ). It
// reports the WATT value of 3537 * I_RMS * V_RMS * cos φ).
//
// It also integrates total energy based on the WATT value. The time for
// one CF_CNT pulse is 1638.4*256 / WATT.
//
// So... how do we calibrate that?
//
// Using a simple resistive load and an external meter, we can measure
// the true voltage and current for a given V_RMS and I_RMS reading,
// to calculate BL0942_UREF and BL0942_IREF. Those are in units of
// "305978 counts per amp" or "73989 counts per volt" respectively.
//
// We can derive BL0942_PREF from those. Let's eliminate the weird
// factors and express the calibration in plain counts per volt/amp:
// UREF1 = UREF/73989, IREF1 = IREF/305978.
//
// Next... the true power in Watts is V * I * cos φ, so that's equal
// to WATT/3537 * IREF1 * UREF1. Which means
// BL0942_PREF = BL0942_UREF * BL0942_IREF * 3537 / 305978 / 73989.
//
// Finally the accumulated energy. The period of a CF_CNT count is
// 1638.4*256 / WATT seconds, or 419230.4 / WATT seconds. Which means
// the energy represented by a CN_CNT pulse is 419230.4 WATT-seconds.
// Factoring in the calibration, that's 419230.4 / BL0942_PREF actual
// Watt-seconds (or Joules, as the physicists like to call them).
//
// But we're not being physicists today; we we're being engineers, so
// we want to convert to kWh instead. Which we do by dividing by 1000
// and then by 3600, so the energy in kWh is
// CF_CNT * 419230.4 / BL0942_PREF / 3600000
//
// Which makes BL0952_EREF = BL0942_PREF * 3600000 / 419430.4
static const float BL0942_PREF = 596; // taken from tasmota static const float BL0942_PREF = 596; // taken from tasmota
static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218 static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218
static const float BL0942_IREF = 251213.46469622; // 305978/1.218 static const float BL0942_IREF = 251213.46469622; // 305978/1.218
@ -28,6 +79,11 @@ struct DataPacket {
uint8_t checksum; uint8_t checksum;
} __attribute__((packed)); } __attribute__((packed));
enum LineFrequency : uint8_t {
LINE_FREQUENCY_50HZ = 50,
LINE_FREQUENCY_60HZ = 60,
};
class BL0942 : public PollingComponent, public uart::UARTDevice { class BL0942 : public PollingComponent, public uart::UARTDevice {
public: public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
@ -35,9 +91,27 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; }
void set_address(uint8_t address) { this->address_ = address; }
void set_reset(bool reset) { this->reset_ = reset; }
void set_current_reference(float current_ref) {
this->current_reference_ = current_ref;
this->current_reference_set_ = true;
}
void set_energy_reference(float energy_ref) {
this->energy_reference_ = energy_ref;
this->energy_reference_set_ = true;
}
void set_power_reference(float power_ref) {
this->power_reference_ = power_ref;
this->power_reference_set_ = true;
}
void set_voltage_reference(float voltage_ref) {
this->voltage_reference_ = voltage_ref;
this->voltage_reference_set_ = true;
}
void loop() override; void loop() override;
void update() override; void update() override;
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
@ -53,15 +127,25 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
// Divide by this to turn into Watt // Divide by this to turn into Watt
float power_reference_ = BL0942_PREF; float power_reference_ = BL0942_PREF;
bool power_reference_set_ = false;
// Divide by this to turn into Volt // Divide by this to turn into Volt
float voltage_reference_ = BL0942_UREF; float voltage_reference_ = BL0942_UREF;
bool voltage_reference_set_ = false;
// Divide by this to turn into Ampere // Divide by this to turn into Ampere
float current_reference_ = BL0942_IREF; float current_reference_ = BL0942_IREF;
bool current_reference_set_ = false;
// Divide by this to turn into kWh // Divide by this to turn into kWh
float energy_reference_ = BL0942_EREF; float energy_reference_ = BL0942_EREF;
bool energy_reference_set_ = false;
uint8_t address_ = 0;
bool reset_ = false;
LineFrequency line_freq_ = LINE_FREQUENCY_50HZ;
uint32_t rx_start_ = 0;
uint32_t prev_cf_cnt_ = 0;
static bool validate_checksum(DataPacket *data); bool validate_checksum_(DataPacket *data);
int read_reg_(uint8_t reg);
void write_reg_(uint8_t reg, uint32_t val);
void received_package_(DataPacket *data); void received_package_(DataPacket *data);
}; };
} // namespace bl0942 } // namespace bl0942

Some files were not shown because too many files have changed in this diff Show more