mirror of
https://github.com/esphome/esphome.git
synced 2026-01-24 20:09:12 -07:00
Compare commits
3 Commits
dev
...
str_sprint
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c37f372885 | ||
|
|
06c619b2e0 | ||
|
|
71c922bb60 |
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@@ -17,7 +17,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Set up Python ${{ inputs.python-version }}
|
- name: Set up Python ${{ inputs.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
|
|||||||
2
.github/workflows/auto-label-pr.yml
vendored
2
.github/workflows/auto-label-pr.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
|
|||||||
4
.github/workflows/ci-api-proto.yml
vendored
4
.github/workflows/ci-api-proto.yml
vendored
@@ -21,9 +21,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/ci-clang-tidy-hash.yml
vendored
4
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -21,10 +21,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/ci-docker.yml
vendored
4
.github/workflows/ci-docker.yml
vendored
@@ -43,9 +43,9 @@ jobs:
|
|||||||
- "docker"
|
- "docker"
|
||||||
# - "lint"
|
# - "lint"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check out code from base repository
|
- name: Check out code from base repository
|
||||||
if: steps.pr.outputs.skip != 'true'
|
if: steps.pr.outputs.skip != 'true'
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
# Always check out from the base repository (esphome/esphome), never from forks
|
# Always check out from the base repository (esphome/esphome), never from forks
|
||||||
# Use the PR's target branch to ensure we run trusted code from the main repo
|
# Use the PR's target branch to ensure we run trusted code from the main repo
|
||||||
|
|||||||
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@@ -36,13 +36,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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Generate cache-key
|
- name: Generate cache-key
|
||||||
id: cache-key
|
id: cache-key
|
||||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
id: restore-python
|
id: restore-python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
@@ -183,7 +183,7 @@ jobs:
|
|||||||
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
|
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
# Fetch enough history to find the merge base
|
# Fetch enough history to find the merge base
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -237,10 +237,10 @@ jobs:
|
|||||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Python 3.13
|
- name: Set up Python 3.13
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
@@ -273,7 +273,7 @@ jobs:
|
|||||||
if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]')
|
if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]')
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
@@ -321,7 +321,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
# Need history for HEAD~1 to work for checking changed files
|
# Need history for HEAD~1 to work for checking changed files
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -400,7 +400,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
# Need history for HEAD~1 to work for checking changed files
|
# Need history for HEAD~1 to work for checking changed files
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -489,7 +489,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
# Need history for HEAD~1 to work for checking changed files
|
# Need history for HEAD~1 to work for checking changed files
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -577,7 +577,7 @@ jobs:
|
|||||||
version: 1.0
|
version: 1.0
|
||||||
|
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -662,7 +662,7 @@ jobs:
|
|||||||
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release')
|
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release')
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -688,7 +688,7 @@ jobs:
|
|||||||
skip: ${{ steps.check-script.outputs.skip }}
|
skip: ${{ steps.check-script.outputs.skip }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out target branch
|
- name: Check out target branch
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.base_ref }}
|
ref: ${{ github.base_ref }}
|
||||||
|
|
||||||
@@ -840,7 +840,7 @@ jobs:
|
|||||||
flash_usage: ${{ steps.extract.outputs.flash_usage }}
|
flash_usage: ${{ steps.extract.outputs.flash_usage }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out PR branch
|
- name: Check out PR branch
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -908,7 +908,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
|||||||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -54,11 +54,11 @@ jobs:
|
|||||||
# 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
|
# 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:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
|
uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
@@ -86,6 +86,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
|
uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Get tag
|
- name: Get tag
|
||||||
id: tag
|
id: tag
|
||||||
# yamllint disable rule:line-length
|
# yamllint disable rule:line-length
|
||||||
@@ -60,9 +60,9 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -92,9 +92,9 @@ jobs:
|
|||||||
os: "ubuntu-24.04-arm"
|
os: "ubuntu-24.04-arm"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ jobs:
|
|||||||
- ghcr
|
- ghcr
|
||||||
- dockerhub
|
- dockerhub
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||||
|
|||||||
6
.github/workflows/sync-device-classes.yml
vendored
6
.github/workflows/sync-device-classes.yml
vendored
@@ -13,16 +13,16 @@ jobs:
|
|||||||
if: github.repository == 'esphome/esphome'
|
if: github.repository == 'esphome/esphome'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Checkout Home Assistant
|
- name: Checkout Home Assistant
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.13
|
python-version: 3.13
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ci:
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.14.14
|
rev: v0.14.13
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|||||||
@@ -88,8 +88,7 @@ esphome/components/bmp3xx/* @latonita
|
|||||||
esphome/components/bmp3xx_base/* @latonita @martgras
|
esphome/components/bmp3xx_base/* @latonita @martgras
|
||||||
esphome/components/bmp3xx_i2c/* @latonita
|
esphome/components/bmp3xx_i2c/* @latonita
|
||||||
esphome/components/bmp3xx_spi/* @latonita
|
esphome/components/bmp3xx_spi/* @latonita
|
||||||
esphome/components/bmp581_base/* @danielkent-net @kahrendt
|
esphome/components/bmp581/* @kahrendt
|
||||||
esphome/components/bmp581_i2c/* @danielkent-net @kahrendt
|
|
||||||
esphome/components/bp1658cj/* @Cossid
|
esphome/components/bp1658cj/* @Cossid
|
||||||
esphome/components/bp5758d/* @Cossid
|
esphome/components/bp5758d/* @Cossid
|
||||||
esphome/components/bthome_mithermometer/* @nagyrobi
|
esphome/components/bthome_mithermometer/* @nagyrobi
|
||||||
@@ -482,7 +481,6 @@ esphome/components/switch/* @esphome/core
|
|||||||
esphome/components/switch/binary_sensor/* @ssieb
|
esphome/components/switch/binary_sensor/* @ssieb
|
||||||
esphome/components/sx126x/* @swoboda1337
|
esphome/components/sx126x/* @swoboda1337
|
||||||
esphome/components/sx127x/* @swoboda1337
|
esphome/components/sx127x/* @swoboda1337
|
||||||
esphome/components/sy6970/* @linkedupbits
|
|
||||||
esphome/components/syslog/* @clydebarrow
|
esphome/components/syslog/* @clydebarrow
|
||||||
esphome/components/t6615/* @tylermenezes
|
esphome/components/t6615/* @tylermenezes
|
||||||
esphome/components/tc74/* @sethgirvan
|
esphome/components/tc74/* @sethgirvan
|
||||||
|
|||||||
@@ -1844,8 +1844,23 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set TCP_NODELAY based on message type - see set_nodelay_for_message() for details
|
// Toggle Nagle's algorithm based on message type to prevent log messages from
|
||||||
this->helper_->set_nodelay_for_message(is_log_message);
|
// filling the TCP send buffer and crowding out important state updates.
|
||||||
|
//
|
||||||
|
// This honors the `no_delay` proto option - SubscribeLogsResponse is the only
|
||||||
|
// message with `option (no_delay) = false;` in api.proto, indicating it should
|
||||||
|
// allow Nagle coalescing. This option existed since 2019 but was never implemented.
|
||||||
|
//
|
||||||
|
// - Log messages: Enable Nagle (NODELAY=false) so small log packets coalesce
|
||||||
|
// into fewer, larger packets. They flush naturally via TCP delayed ACK timer
|
||||||
|
// (~200ms), buffer filling, or when a state update triggers a flush.
|
||||||
|
//
|
||||||
|
// - All other messages (state updates, responses): Disable Nagle (NODELAY=true)
|
||||||
|
// for immediate delivery. These are time-sensitive and should not be delayed.
|
||||||
|
//
|
||||||
|
// This must be done proactively BEFORE the buffer fills up - checking buffer
|
||||||
|
// state here would be too late since we'd already be in a degraded state.
|
||||||
|
this->helper_->set_nodelay(!is_log_message);
|
||||||
|
|
||||||
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
|
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
|
||||||
if (err == APIError::WOULD_BLOCK)
|
if (err == APIError::WOULD_BLOCK)
|
||||||
|
|||||||
@@ -120,39 +120,26 @@ class APIFrameHelper {
|
|||||||
}
|
}
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
// Manage TCP_NODELAY (Nagle's algorithm) based on message type.
|
/// Toggle TCP_NODELAY socket option to control Nagle's algorithm.
|
||||||
//
|
///
|
||||||
// For non-log messages (sensor data, state updates): Always disable Nagle
|
/// This is used to allow log messages to coalesce (Nagle enabled) while keeping
|
||||||
// (NODELAY on) for immediate delivery - these are time-sensitive.
|
/// state updates low-latency (NODELAY enabled). Without this, many small log
|
||||||
//
|
/// packets fill the TCP send buffer, crowding out important state updates.
|
||||||
// For log messages: Use Nagle to coalesce multiple small log packets into
|
///
|
||||||
// fewer larger packets, reducing WiFi overhead. However, we limit batching
|
/// State is tracked to minimize setsockopt() overhead - on lwip_raw (ESP8266/RP2040)
|
||||||
// to 3 messages to avoid excessive LWIP buffer pressure on memory-constrained
|
/// this is just a boolean assignment; on other platforms it's a lightweight syscall.
|
||||||
// devices like ESP8266. LWIP's TCP_OVERSIZE option coalesces the data into
|
///
|
||||||
// shared pbufs, but holding data too long waiting for Nagle's timer causes
|
/// @param enable true to enable NODELAY (disable Nagle), false to enable Nagle
|
||||||
// buffer exhaustion and dropped messages.
|
/// @return true if successful or already in desired state
|
||||||
//
|
bool set_nodelay(bool enable) {
|
||||||
// Flow: Log 1 (Nagle on) -> Log 2 (Nagle on) -> Log 3 (NODELAY, flush all)
|
if (this->nodelay_enabled_ == enable)
|
||||||
//
|
return true;
|
||||||
void set_nodelay_for_message(bool is_log_message) {
|
int val = enable ? 1 : 0;
|
||||||
if (!is_log_message) {
|
int err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||||
if (this->nodelay_state_ != NODELAY_ON) {
|
if (err == 0) {
|
||||||
this->set_nodelay_raw_(true);
|
this->nodelay_enabled_ = enable;
|
||||||
this->nodelay_state_ = NODELAY_ON;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log messages 1-3: state transitions -1 -> 1 -> 2 -> -1 (flush on 3rd)
|
|
||||||
if (this->nodelay_state_ == NODELAY_ON) {
|
|
||||||
this->set_nodelay_raw_(false);
|
|
||||||
this->nodelay_state_ = 1;
|
|
||||||
} else if (this->nodelay_state_ >= LOG_NAGLE_COUNT) {
|
|
||||||
this->set_nodelay_raw_(true);
|
|
||||||
this->nodelay_state_ = NODELAY_ON;
|
|
||||||
} else {
|
|
||||||
this->nodelay_state_++;
|
|
||||||
}
|
}
|
||||||
|
return err == 0;
|
||||||
}
|
}
|
||||||
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
|
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
|
||||||
// Write multiple protobuf messages in a single operation
|
// Write multiple protobuf messages in a single operation
|
||||||
@@ -242,18 +229,10 @@ class APIFrameHelper {
|
|||||||
uint8_t tx_buf_head_{0};
|
uint8_t tx_buf_head_{0};
|
||||||
uint8_t tx_buf_tail_{0};
|
uint8_t tx_buf_tail_{0};
|
||||||
uint8_t tx_buf_count_{0};
|
uint8_t tx_buf_count_{0};
|
||||||
// Nagle batching state for log messages. NODELAY_ON (-1) means NODELAY is enabled
|
// Tracks TCP_NODELAY state to minimize setsockopt() calls. Initialized to true
|
||||||
// (immediate send). Values 1-2 count log messages in the current Nagle batch.
|
// since init_common_() enables NODELAY. Used by set_nodelay() to allow log
|
||||||
// After LOG_NAGLE_COUNT logs, we switch to NODELAY to flush and reset.
|
// messages to coalesce while keeping state updates low-latency.
|
||||||
static constexpr int8_t NODELAY_ON = -1;
|
bool nodelay_enabled_{true};
|
||||||
static constexpr int8_t LOG_NAGLE_COUNT = 2;
|
|
||||||
int8_t nodelay_state_{NODELAY_ON};
|
|
||||||
|
|
||||||
// Internal helper to set TCP_NODELAY socket option
|
|
||||||
void set_nodelay_raw_(bool enable) {
|
|
||||||
int val = enable ? 1 : 0;
|
|
||||||
this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common initialization for both plaintext and noise protocols
|
// Common initialization for both plaintext and noise protocols
|
||||||
APIError init_common_();
|
APIError init_common_();
|
||||||
|
|||||||
@@ -13,11 +13,14 @@ from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns
|
|||||||
CODEOWNERS = ["@jasstrong"]
|
CODEOWNERS = ["@jasstrong"]
|
||||||
DEPENDENCIES = ["sensor"]
|
DEPENDENCIES = ["sensor"]
|
||||||
|
|
||||||
|
UNIT_INDEX = "index"
|
||||||
|
|
||||||
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
|
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
sensor.sensor_schema(
|
sensor.sensor_schema(
|
||||||
AQISensor,
|
AQISensor,
|
||||||
|
unit_of_measurement=UNIT_INDEX,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_AQI,
|
device_class=DEVICE_CLASS_AQI,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
|||||||
@@ -108,14 +108,10 @@ void ATM90E32Component::update() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::get_cs_summary_(std::span<char, GPIO_SUMMARY_MAX_LEN> buffer) {
|
|
||||||
this->cs_->dump_summary(buffer.data(), buffer.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ATM90E32Component::setup() {
|
void ATM90E32Component::setup() {
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
this->cs_summary_ = this->cs_->dump_summary();
|
||||||
this->get_cs_summary_(cs);
|
const char *cs = this->cs_summary_.c_str();
|
||||||
|
|
||||||
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||||
uint16_t high_thresh = 0;
|
uint16_t high_thresh = 0;
|
||||||
@@ -163,13 +159,13 @@ void ATM90E32Component::setup() {
|
|||||||
if (this->enable_offset_calibration_) {
|
if (this->enable_offset_calibration_) {
|
||||||
// Initialize flash storage for offset calibrations
|
// Initialize flash storage for offset calibrations
|
||||||
uint32_t o_hash = fnv1_hash("_offset_calibration_");
|
uint32_t o_hash = fnv1_hash("_offset_calibration_");
|
||||||
o_hash = fnv1_hash_extend(o_hash, cs);
|
o_hash = fnv1_hash_extend(o_hash, this->cs_summary_);
|
||||||
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
||||||
this->restore_offset_calibrations_();
|
this->restore_offset_calibrations_();
|
||||||
|
|
||||||
// Initialize flash storage for power offset calibrations
|
// Initialize flash storage for power offset calibrations
|
||||||
uint32_t po_hash = fnv1_hash("_power_offset_calibration_");
|
uint32_t po_hash = fnv1_hash("_power_offset_calibration_");
|
||||||
po_hash = fnv1_hash_extend(po_hash, cs);
|
po_hash = fnv1_hash_extend(po_hash, this->cs_summary_);
|
||||||
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
||||||
this->restore_power_offset_calibrations_();
|
this->restore_power_offset_calibrations_();
|
||||||
} else {
|
} else {
|
||||||
@@ -190,7 +186,7 @@ void ATM90E32Component::setup() {
|
|||||||
if (this->enable_gain_calibration_) {
|
if (this->enable_gain_calibration_) {
|
||||||
// Initialize flash storage for gain calibration
|
// Initialize flash storage for gain calibration
|
||||||
uint32_t g_hash = fnv1_hash("_gain_calibration_");
|
uint32_t g_hash = fnv1_hash("_gain_calibration_");
|
||||||
g_hash = fnv1_hash_extend(g_hash, cs);
|
g_hash = fnv1_hash_extend(g_hash, this->cs_summary_);
|
||||||
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
||||||
this->restore_gain_calibrations_();
|
this->restore_gain_calibrations_();
|
||||||
|
|
||||||
@@ -221,8 +217,7 @@ void ATM90E32Component::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::log_calibration_status_() {
|
void ATM90E32Component::log_calibration_status_() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
|
|
||||||
bool offset_mismatch = false;
|
bool offset_mismatch = false;
|
||||||
bool power_mismatch = false;
|
bool power_mismatch = false;
|
||||||
@@ -573,8 +568,7 @@ float ATM90E32Component::get_chip_temperature_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::run_gain_calibrations() {
|
void ATM90E32Component::run_gain_calibrations() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
if (!this->enable_gain_calibration_) {
|
if (!this->enable_gain_calibration_) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Gain calibration is disabled! Enable it first with enable_gain_calibration: true",
|
ESP_LOGW(TAG, "[CALIBRATION][%s] Gain calibration is disabled! Enable it first with enable_gain_calibration: true",
|
||||||
cs);
|
cs);
|
||||||
@@ -674,8 +668,7 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::save_gain_calibration_to_memory_() {
|
void ATM90E32Component::save_gain_calibration_to_memory_() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
||||||
global_preferences->sync();
|
global_preferences->sync();
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -688,8 +681,7 @@ void ATM90E32Component::save_gain_calibration_to_memory_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::save_offset_calibration_to_memory_() {
|
void ATM90E32Component::save_offset_calibration_to_memory_() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
bool success = this->offset_pref_.save(&this->offset_phase_);
|
bool success = this->offset_pref_.save(&this->offset_phase_);
|
||||||
global_preferences->sync();
|
global_preferences->sync();
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -705,8 +697,7 @@ void ATM90E32Component::save_offset_calibration_to_memory_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::save_power_offset_calibration_to_memory_() {
|
void ATM90E32Component::save_power_offset_calibration_to_memory_() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
bool success = this->power_offset_pref_.save(&this->power_offset_phase_);
|
bool success = this->power_offset_pref_.save(&this->power_offset_phase_);
|
||||||
global_preferences->sync();
|
global_preferences->sync();
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -722,8 +713,7 @@ void ATM90E32Component::save_power_offset_calibration_to_memory_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::run_offset_calibrations() {
|
void ATM90E32Component::run_offset_calibrations() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
if (!this->enable_offset_calibration_) {
|
if (!this->enable_offset_calibration_) {
|
||||||
ESP_LOGW(TAG,
|
ESP_LOGW(TAG,
|
||||||
"[CALIBRATION][%s] Offset calibration is disabled! Enable it first with enable_offset_calibration: true",
|
"[CALIBRATION][%s] Offset calibration is disabled! Enable it first with enable_offset_calibration: true",
|
||||||
@@ -753,8 +743,7 @@ void ATM90E32Component::run_offset_calibrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::run_power_offset_calibrations() {
|
void ATM90E32Component::run_power_offset_calibrations() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
if (!this->enable_offset_calibration_) {
|
if (!this->enable_offset_calibration_) {
|
||||||
ESP_LOGW(
|
ESP_LOGW(
|
||||||
TAG,
|
TAG,
|
||||||
@@ -827,8 +816,7 @@ void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::restore_gain_calibrations_() {
|
void ATM90E32Component::restore_gain_calibrations_() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
for (uint8_t i = 0; i < 3; ++i) {
|
for (uint8_t i = 0; i < 3; ++i) {
|
||||||
this->config_gain_phase_[i].voltage_gain = this->phase_[i].voltage_gain_;
|
this->config_gain_phase_[i].voltage_gain = this->phase_[i].voltage_gain_;
|
||||||
this->config_gain_phase_[i].current_gain = this->phase_[i].ct_gain_;
|
this->config_gain_phase_[i].current_gain = this->phase_[i].ct_gain_;
|
||||||
@@ -882,8 +870,7 @@ void ATM90E32Component::restore_gain_calibrations_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::restore_offset_calibrations_() {
|
void ATM90E32Component::restore_offset_calibrations_() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
for (uint8_t i = 0; i < 3; ++i)
|
for (uint8_t i = 0; i < 3; ++i)
|
||||||
this->config_offset_phase_[i] = this->offset_phase_[i];
|
this->config_offset_phase_[i] = this->offset_phase_[i];
|
||||||
|
|
||||||
@@ -925,8 +912,7 @@ void ATM90E32Component::restore_offset_calibrations_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::restore_power_offset_calibrations_() {
|
void ATM90E32Component::restore_power_offset_calibrations_() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
for (uint8_t i = 0; i < 3; ++i)
|
for (uint8_t i = 0; i < 3; ++i)
|
||||||
this->config_power_offset_phase_[i] = this->power_offset_phase_[i];
|
this->config_power_offset_phase_[i] = this->power_offset_phase_[i];
|
||||||
|
|
||||||
@@ -968,8 +954,7 @@ void ATM90E32Component::restore_power_offset_calibrations_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::clear_gain_calibrations() {
|
void ATM90E32Component::clear_gain_calibrations() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
if (!this->using_saved_calibrations_) {
|
if (!this->using_saved_calibrations_) {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||||
@@ -1018,8 +1003,7 @@ void ATM90E32Component::clear_gain_calibrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::clear_offset_calibrations() {
|
void ATM90E32Component::clear_offset_calibrations() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
if (!this->restored_offset_calibration_) {
|
if (!this->restored_offset_calibration_) {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored offset calibrations to clear. Current values:", cs);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored offset calibrations to clear. Current values:", cs);
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||||
@@ -1061,8 +1045,7 @@ void ATM90E32Component::clear_offset_calibrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::clear_power_offset_calibrations() {
|
void ATM90E32Component::clear_power_offset_calibrations() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
if (!this->restored_power_offset_calibration_) {
|
if (!this->restored_power_offset_calibration_) {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored power offsets to clear. Current values:", cs);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored power offsets to clear. Current values:", cs);
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
@@ -1137,8 +1120,7 @@ int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ATM90E32Component::verify_gain_writes_() {
|
bool ATM90E32Component::verify_gain_writes_() {
|
||||||
char cs[GPIO_SUMMARY_MAX_LEN];
|
const char *cs = this->cs_summary_.c_str();
|
||||||
this->get_cs_summary_(cs);
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
|
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <span>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "atm90e32_reg.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 "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/gpio.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/preferences.h"
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
@@ -184,7 +182,6 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
bool verify_gain_writes_();
|
bool verify_gain_writes_();
|
||||||
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
|
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
|
||||||
void log_calibration_status_();
|
void log_calibration_status_();
|
||||||
void get_cs_summary_(std::span<char, GPIO_SUMMARY_MAX_LEN> buffer);
|
|
||||||
|
|
||||||
struct ATM90E32Phase {
|
struct ATM90E32Phase {
|
||||||
uint16_t voltage_gain_{0};
|
uint16_t voltage_gain_{0};
|
||||||
@@ -250,6 +247,7 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
ESPPreferenceObject offset_pref_;
|
ESPPreferenceObject offset_pref_;
|
||||||
ESPPreferenceObject power_offset_pref_;
|
ESPPreferenceObject power_offset_pref_;
|
||||||
ESPPreferenceObject gain_calibration_pref_;
|
ESPPreferenceObject gain_calibration_pref_;
|
||||||
|
std::string cs_summary_;
|
||||||
|
|
||||||
sensor::Sensor *freq_sensor_{nullptr};
|
sensor::Sensor *freq_sensor_{nullptr};
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
|||||||
@@ -10,11 +10,12 @@
|
|||||||
* - All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
|
* - All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "bmp581_base.h"
|
#include "bmp581.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
namespace esphome::bmp581_base {
|
namespace esphome {
|
||||||
|
namespace bmp581 {
|
||||||
|
|
||||||
static const char *const TAG = "bmp581";
|
static const char *const TAG = "bmp581";
|
||||||
|
|
||||||
@@ -90,6 +91,7 @@ void BMP581Component::dump_config() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
|
ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
|
||||||
@@ -147,7 +149,7 @@ void BMP581Component::setup() {
|
|||||||
uint8_t chip_id;
|
uint8_t chip_id;
|
||||||
|
|
||||||
// read chip id from sensor
|
// read chip id from sensor
|
||||||
if (!this->bmp_read_byte(BMP581_CHIP_ID, &chip_id)) {
|
if (!this->read_byte(BMP581_CHIP_ID, &chip_id)) {
|
||||||
ESP_LOGE(TAG, "Read chip ID failed");
|
ESP_LOGE(TAG, "Read chip ID failed");
|
||||||
|
|
||||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||||
@@ -170,7 +172,7 @@ void BMP581Component::setup() {
|
|||||||
// 3) Verify sensor status (check if NVM is okay) //
|
// 3) Verify sensor status (check if NVM is okay) //
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
if (!this->bmp_read_byte(BMP581_STATUS, &this->status_.reg)) {
|
if (!this->read_byte(BMP581_STATUS, &this->status_.reg)) {
|
||||||
ESP_LOGE(TAG, "Failed to read status register");
|
ESP_LOGE(TAG, "Failed to read status register");
|
||||||
|
|
||||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||||
@@ -357,7 +359,7 @@ bool BMP581Component::check_data_readiness_() {
|
|||||||
|
|
||||||
uint8_t status;
|
uint8_t status;
|
||||||
|
|
||||||
if (!this->bmp_read_byte(BMP581_INT_STATUS, &status)) {
|
if (!this->read_byte(BMP581_INT_STATUS, &status)) {
|
||||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -398,7 +400,7 @@ bool BMP581Component::prime_iir_filter_() {
|
|||||||
|
|
||||||
// flush the IIR filter with forced measurements (we will only flush once)
|
// flush the IIR filter with forced measurements (we will only flush once)
|
||||||
this->dsp_config_.bit.iir_flush_forced_en = true;
|
this->dsp_config_.bit.iir_flush_forced_en = true;
|
||||||
if (!this->bmp_write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||||
ESP_LOGE(TAG, "Failed to write IIR source register");
|
ESP_LOGE(TAG, "Failed to write IIR source register");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -428,7 +430,7 @@ bool BMP581Component::prime_iir_filter_() {
|
|||||||
|
|
||||||
// disable IIR filter flushings on future forced measurements
|
// disable IIR filter flushings on future forced measurements
|
||||||
this->dsp_config_.bit.iir_flush_forced_en = false;
|
this->dsp_config_.bit.iir_flush_forced_en = false;
|
||||||
if (!this->bmp_write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||||
ESP_LOGE(TAG, "Failed to write IIR source register");
|
ESP_LOGE(TAG, "Failed to write IIR source register");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -452,7 +454,7 @@ bool BMP581Component::read_temperature_(float &temperature) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t data[3];
|
uint8_t data[3];
|
||||||
if (!this->bmp_read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
|
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
|
||||||
ESP_LOGW(TAG, "Failed to read measurement");
|
ESP_LOGW(TAG, "Failed to read measurement");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
|
|
||||||
@@ -481,7 +483,7 @@ bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t data[6];
|
uint8_t data[6];
|
||||||
if (!this->bmp_read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
|
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
|
||||||
ESP_LOGW(TAG, "Failed to read measurement");
|
ESP_LOGW(TAG, "Failed to read measurement");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
|
|
||||||
@@ -505,7 +507,7 @@ bool BMP581Component::reset_() {
|
|||||||
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
|
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
|
||||||
|
|
||||||
// writes reset command to BMP's command register
|
// writes reset command to BMP's command register
|
||||||
if (!this->bmp_write_byte(BMP581_COMMAND, RESET_COMMAND)) {
|
if (!this->write_byte(BMP581_COMMAND, RESET_COMMAND)) {
|
||||||
ESP_LOGE(TAG, "Failed to write reset command");
|
ESP_LOGE(TAG, "Failed to write reset command");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -516,7 +518,7 @@ bool BMP581Component::reset_() {
|
|||||||
delay(3);
|
delay(3);
|
||||||
|
|
||||||
// read interrupt status register
|
// read interrupt status register
|
||||||
if (!this->bmp_read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
|
if (!this->read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
|
||||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -560,7 +562,7 @@ bool BMP581Component::write_iir_settings_(IIRFilter temperature_iir, IIRFilter p
|
|||||||
// BMP581_DSP register and BMP581_DSP_IIR registers are successive
|
// BMP581_DSP register and BMP581_DSP_IIR registers are successive
|
||||||
// - allows us to write the IIR configuration with one command to both registers
|
// - allows us to write the IIR configuration with one command to both registers
|
||||||
uint8_t register_data[2] = {this->dsp_config_.reg, this->iir_config_.reg};
|
uint8_t register_data[2] = {this->dsp_config_.reg, this->iir_config_.reg};
|
||||||
return this->bmp_write_bytes(BMP581_DSP, register_data, sizeof(register_data));
|
return this->write_bytes(BMP581_DSP, register_data, sizeof(register_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BMP581Component::write_interrupt_source_settings_(bool data_ready_enable) {
|
bool BMP581Component::write_interrupt_source_settings_(bool data_ready_enable) {
|
||||||
@@ -570,7 +572,7 @@ bool BMP581Component::write_interrupt_source_settings_(bool data_ready_enable) {
|
|||||||
this->int_source_.bit.drdy_data_reg_en = data_ready_enable;
|
this->int_source_.bit.drdy_data_reg_en = data_ready_enable;
|
||||||
|
|
||||||
// write interrupt source register
|
// write interrupt source register
|
||||||
return this->bmp_write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
|
return this->write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BMP581Component::write_oversampling_settings_(Oversampling temperature_oversampling,
|
bool BMP581Component::write_oversampling_settings_(Oversampling temperature_oversampling,
|
||||||
@@ -581,7 +583,7 @@ bool BMP581Component::write_oversampling_settings_(Oversampling temperature_over
|
|||||||
this->osr_config_.bit.osr_t = temperature_oversampling;
|
this->osr_config_.bit.osr_t = temperature_oversampling;
|
||||||
this->osr_config_.bit.osr_p = pressure_oversampling;
|
this->osr_config_.bit.osr_p = pressure_oversampling;
|
||||||
|
|
||||||
return this->bmp_write_byte(BMP581_OSR, this->osr_config_.reg);
|
return this->write_byte(BMP581_OSR, this->osr_config_.reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BMP581Component::write_power_mode_(OperationMode mode) {
|
bool BMP581Component::write_power_mode_(OperationMode mode) {
|
||||||
@@ -591,7 +593,8 @@ bool BMP581Component::write_power_mode_(OperationMode mode) {
|
|||||||
this->odr_config_.bit.pwr_mode = mode;
|
this->odr_config_.bit.pwr_mode = mode;
|
||||||
|
|
||||||
// write odr register
|
// write odr register
|
||||||
return this->bmp_write_byte(BMP581_ODR, this->odr_config_.reg);
|
return this->write_byte(BMP581_ODR, this->odr_config_.reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::bmp581_base
|
} // namespace bmp581
|
||||||
|
} // namespace esphome
|
||||||
@@ -3,9 +3,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
namespace esphome::bmp581_base {
|
namespace esphome {
|
||||||
|
namespace bmp581 {
|
||||||
|
|
||||||
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
|
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
|
||||||
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
|
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
|
||||||
@@ -57,7 +59,7 @@ enum IIRFilter {
|
|||||||
IIR_FILTER_128 = 0x7
|
IIR_FILTER_128 = 0x7
|
||||||
};
|
};
|
||||||
|
|
||||||
class BMP581Component : public PollingComponent {
|
class BMP581Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
@@ -82,11 +84,6 @@ class BMP581Component : public PollingComponent {
|
|||||||
void set_conversion_time(uint8_t conversion_time) { this->conversion_time_ = conversion_time; }
|
void set_conversion_time(uint8_t conversion_time) { this->conversion_time_ = conversion_time; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data) = 0;
|
|
||||||
virtual bool bmp_write_byte(uint8_t a_register, uint8_t data) = 0;
|
|
||||||
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
|
||||||
virtual bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
|
||||||
|
|
||||||
sensor::Sensor *temperature_sensor_{nullptr};
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
sensor::Sensor *pressure_sensor_{nullptr};
|
sensor::Sensor *pressure_sensor_{nullptr};
|
||||||
|
|
||||||
@@ -219,4 +216,5 @@ class BMP581Component : public PollingComponent {
|
|||||||
} odr_config_ = {.reg = 0};
|
} odr_config_ = {.reg = 0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::bmp581_base
|
} // namespace bmp581
|
||||||
|
} // namespace esphome
|
||||||
@@ -1,5 +1,164 @@
|
|||||||
import esphome.config_validation as cv
|
import math
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.invalid(
|
import esphome.codegen as cg
|
||||||
"The bmp581 sensor component has been renamed to bmp581_i2c."
|
from esphome.components import i2c, sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_IIR_FILTER,
|
||||||
|
CONF_OVERSAMPLING,
|
||||||
|
CONF_PRESSURE,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_PASCAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@kahrendt"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
bmp581_ns = cg.esphome_ns.namespace("bmp581")
|
||||||
|
|
||||||
|
Oversampling = bmp581_ns.enum("Oversampling")
|
||||||
|
OVERSAMPLING_OPTIONS = {
|
||||||
|
"NONE": Oversampling.OVERSAMPLING_NONE,
|
||||||
|
"2X": Oversampling.OVERSAMPLING_X2,
|
||||||
|
"4X": Oversampling.OVERSAMPLING_X4,
|
||||||
|
"8X": Oversampling.OVERSAMPLING_X8,
|
||||||
|
"16X": Oversampling.OVERSAMPLING_X16,
|
||||||
|
"32X": Oversampling.OVERSAMPLING_X32,
|
||||||
|
"64X": Oversampling.OVERSAMPLING_X64,
|
||||||
|
"128X": Oversampling.OVERSAMPLING_X128,
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRFilter = bmp581_ns.enum("IIRFilter")
|
||||||
|
IIR_FILTER_OPTIONS = {
|
||||||
|
"OFF": IIRFilter.IIR_FILTER_OFF,
|
||||||
|
"2X": IIRFilter.IIR_FILTER_2,
|
||||||
|
"4X": IIRFilter.IIR_FILTER_4,
|
||||||
|
"8X": IIRFilter.IIR_FILTER_8,
|
||||||
|
"16X": IIRFilter.IIR_FILTER_16,
|
||||||
|
"32X": IIRFilter.IIR_FILTER_32,
|
||||||
|
"64X": IIRFilter.IIR_FILTER_64,
|
||||||
|
"128X": IIRFilter.IIR_FILTER_128,
|
||||||
|
}
|
||||||
|
|
||||||
|
BMP581Component = bmp581_ns.class_(
|
||||||
|
"BMP581Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_measurement_conversion_time(config):
|
||||||
|
# - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet
|
||||||
|
# - returns a rounded up time in ms
|
||||||
|
|
||||||
|
# Page 12 of datasheet
|
||||||
|
PRESSURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||||
|
"NONE": 1.0,
|
||||||
|
"2X": 1.7,
|
||||||
|
"4X": 2.9,
|
||||||
|
"8X": 5.4,
|
||||||
|
"16X": 10.4,
|
||||||
|
"32X": 20.4,
|
||||||
|
"64X": 40.4,
|
||||||
|
"128X": 80.4,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Page 12 of datasheet
|
||||||
|
TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||||
|
"NONE": 1.0,
|
||||||
|
"2X": 1.1,
|
||||||
|
"4X": 1.5,
|
||||||
|
"8X": 2.1,
|
||||||
|
"16X": 3.3,
|
||||||
|
"32X": 5.8,
|
||||||
|
"64X": 10.8,
|
||||||
|
"128X": 20.8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pressure_conversion_time = (
|
||||||
|
0.0 # No conversion time necessary without a pressure sensor
|
||||||
|
)
|
||||||
|
if pressure_config := config.get(CONF_PRESSURE):
|
||||||
|
pressure_conversion_time = PRESSURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||||
|
pressure_config.get(CONF_OVERSAMPLING)
|
||||||
|
]
|
||||||
|
|
||||||
|
temperature_conversion_time = (
|
||||||
|
1.0 # BMP581 always samples the temperature even if only reading pressure
|
||||||
|
)
|
||||||
|
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||||
|
temperature_conversion_time = TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||||
|
temperature_config.get(CONF_OVERSAMPLING)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Datasheet indicates a 5% possible error in each conversion time listed
|
||||||
|
return math.ceil(1.05 * (pressure_conversion_time + temperature_conversion_time))
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BMP581Component),
|
||||||
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OVERSAMPLING, default="NONE"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||||
|
IIR_FILTER_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PASCAL,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||||
|
IIR_FILTER_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x46))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await i2c.register_i2c_device(var, config)
|
||||||
|
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||||
|
sens = await sensor.new_sensor(temperature_config)
|
||||||
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
cg.add(
|
||||||
|
var.set_temperature_oversampling_config(
|
||||||
|
temperature_config[CONF_OVERSAMPLING]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cg.add(
|
||||||
|
var.set_temperature_iir_filter_config(temperature_config[CONF_IIR_FILTER])
|
||||||
|
)
|
||||||
|
|
||||||
|
if pressure_config := config.get(CONF_PRESSURE):
|
||||||
|
sens = await sensor.new_sensor(pressure_config)
|
||||||
|
cg.add(var.set_pressure_sensor(sens))
|
||||||
|
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
||||||
|
cg.add(var.set_pressure_iir_filter_config(pressure_config[CONF_IIR_FILTER]))
|
||||||
|
|
||||||
|
cg.add(var.set_conversion_time(compute_measurement_conversion_time(config)))
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
import math
|
|
||||||
|
|
||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components import sensor
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.const import (
|
|
||||||
CONF_ID,
|
|
||||||
CONF_IIR_FILTER,
|
|
||||||
CONF_OVERSAMPLING,
|
|
||||||
CONF_PRESSURE,
|
|
||||||
CONF_TEMPERATURE,
|
|
||||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
|
||||||
STATE_CLASS_MEASUREMENT,
|
|
||||||
UNIT_CELSIUS,
|
|
||||||
UNIT_PASCAL,
|
|
||||||
)
|
|
||||||
|
|
||||||
CODEOWNERS = ["@kahrendt", "@danielkent-net"]
|
|
||||||
|
|
||||||
bmp581_ns = cg.esphome_ns.namespace("bmp581_base")
|
|
||||||
|
|
||||||
Oversampling = bmp581_ns.enum("Oversampling")
|
|
||||||
OVERSAMPLING_OPTIONS = {
|
|
||||||
"NONE": Oversampling.OVERSAMPLING_NONE,
|
|
||||||
"2X": Oversampling.OVERSAMPLING_X2,
|
|
||||||
"4X": Oversampling.OVERSAMPLING_X4,
|
|
||||||
"8X": Oversampling.OVERSAMPLING_X8,
|
|
||||||
"16X": Oversampling.OVERSAMPLING_X16,
|
|
||||||
"32X": Oversampling.OVERSAMPLING_X32,
|
|
||||||
"64X": Oversampling.OVERSAMPLING_X64,
|
|
||||||
"128X": Oversampling.OVERSAMPLING_X128,
|
|
||||||
}
|
|
||||||
|
|
||||||
IIRFilter = bmp581_ns.enum("IIRFilter")
|
|
||||||
IIR_FILTER_OPTIONS = {
|
|
||||||
"OFF": IIRFilter.IIR_FILTER_OFF,
|
|
||||||
"2X": IIRFilter.IIR_FILTER_2,
|
|
||||||
"4X": IIRFilter.IIR_FILTER_4,
|
|
||||||
"8X": IIRFilter.IIR_FILTER_8,
|
|
||||||
"16X": IIRFilter.IIR_FILTER_16,
|
|
||||||
"32X": IIRFilter.IIR_FILTER_32,
|
|
||||||
"64X": IIRFilter.IIR_FILTER_64,
|
|
||||||
"128X": IIRFilter.IIR_FILTER_128,
|
|
||||||
}
|
|
||||||
|
|
||||||
BMP581Component = bmp581_ns.class_("BMP581Component", cg.PollingComponent)
|
|
||||||
|
|
||||||
|
|
||||||
def compute_measurement_conversion_time(config):
|
|
||||||
# - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet
|
|
||||||
# - returns a rounded up time in ms
|
|
||||||
|
|
||||||
# Page 12 of datasheet
|
|
||||||
PRESSURE_OVERSAMPLING_CONVERSION_TIMES = {
|
|
||||||
"NONE": 1.0,
|
|
||||||
"2X": 1.7,
|
|
||||||
"4X": 2.9,
|
|
||||||
"8X": 5.4,
|
|
||||||
"16X": 10.4,
|
|
||||||
"32X": 20.4,
|
|
||||||
"64X": 40.4,
|
|
||||||
"128X": 80.4,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Page 12 of datasheet
|
|
||||||
TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES = {
|
|
||||||
"NONE": 1.0,
|
|
||||||
"2X": 1.1,
|
|
||||||
"4X": 1.5,
|
|
||||||
"8X": 2.1,
|
|
||||||
"16X": 3.3,
|
|
||||||
"32X": 5.8,
|
|
||||||
"64X": 10.8,
|
|
||||||
"128X": 20.8,
|
|
||||||
}
|
|
||||||
|
|
||||||
pressure_conversion_time = (
|
|
||||||
0.0 # No conversion time necessary without a pressure sensor
|
|
||||||
)
|
|
||||||
if pressure_config := config.get(CONF_PRESSURE):
|
|
||||||
pressure_conversion_time = PRESSURE_OVERSAMPLING_CONVERSION_TIMES[
|
|
||||||
pressure_config.get(CONF_OVERSAMPLING)
|
|
||||||
]
|
|
||||||
|
|
||||||
temperature_conversion_time = (
|
|
||||||
1.0 # BMP581 always samples the temperature even if only reading pressure
|
|
||||||
)
|
|
||||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
|
||||||
temperature_conversion_time = TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES[
|
|
||||||
temperature_config.get(CONF_OVERSAMPLING)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Datasheet indicates a 5% possible error in each conversion time listed
|
|
||||||
return math.ceil(1.05 * (pressure_conversion_time + temperature_conversion_time))
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA_BASE = cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(): cv.declare_id(BMP581Component),
|
|
||||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
|
||||||
accuracy_decimals=1,
|
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
).extend(
|
|
||||||
{
|
|
||||||
cv.Optional(CONF_OVERSAMPLING, default="NONE"): cv.enum(
|
|
||||||
OVERSAMPLING_OPTIONS, upper=True
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
|
||||||
IIR_FILTER_OPTIONS, upper=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
|
||||||
unit_of_measurement=UNIT_PASCAL,
|
|
||||||
accuracy_decimals=0,
|
|
||||||
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
).extend(
|
|
||||||
{
|
|
||||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
|
||||||
OVERSAMPLING_OPTIONS, upper=True
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
|
||||||
IIR_FILTER_OPTIONS, upper=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
}
|
|
||||||
).extend(cv.polling_component_schema("60s"))
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code_base(config):
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
|
||||||
sens = await sensor.new_sensor(temperature_config)
|
|
||||||
cg.add(var.set_temperature_sensor(sens))
|
|
||||||
cg.add(
|
|
||||||
var.set_temperature_oversampling_config(
|
|
||||||
temperature_config[CONF_OVERSAMPLING]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
cg.add(
|
|
||||||
var.set_temperature_iir_filter_config(temperature_config[CONF_IIR_FILTER])
|
|
||||||
)
|
|
||||||
|
|
||||||
if pressure_config := config.get(CONF_PRESSURE):
|
|
||||||
sens = await sensor.new_sensor(pressure_config)
|
|
||||||
cg.add(var.set_pressure_sensor(sens))
|
|
||||||
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
|
||||||
cg.add(var.set_pressure_iir_filter_config(pressure_config[CONF_IIR_FILTER]))
|
|
||||||
|
|
||||||
cg.add(var.set_conversion_time(compute_measurement_conversion_time(config)))
|
|
||||||
return var
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#include "bmp581_i2c.h"
|
|
||||||
#include "esphome/core/hal.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
namespace esphome::bmp581_i2c {
|
|
||||||
|
|
||||||
void BMP581I2CComponent::dump_config() {
|
|
||||||
LOG_I2C_DEVICE(this);
|
|
||||||
BMP581Component::dump_config();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::bmp581_i2c
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/components/bmp581_base/bmp581_base.h"
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
|
||||||
|
|
||||||
namespace esphome::bmp581_i2c {
|
|
||||||
|
|
||||||
static const char *const TAG = "bmp581_i2c.sensor";
|
|
||||||
|
|
||||||
/// This class implements support for the BMP581 Temperature+Pressure i2c sensor.
|
|
||||||
class BMP581I2CComponent : public esphome::bmp581_base::BMP581Component, public i2c::I2CDevice {
|
|
||||||
public:
|
|
||||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
|
|
||||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
|
|
||||||
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
|
|
||||||
return read_bytes(a_register, data, len);
|
|
||||||
}
|
|
||||||
bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
|
|
||||||
return write_bytes(a_register, data, len);
|
|
||||||
}
|
|
||||||
void dump_config() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::bmp581_i2c
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components import i2c
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
|
|
||||||
from ..bmp581_base import CONFIG_SCHEMA_BASE, to_code_base
|
|
||||||
|
|
||||||
AUTO_LOAD = ["bmp581_base"]
|
|
||||||
CODEOWNERS = ["@kahrendt", "@danielkent-net"]
|
|
||||||
DEPENDENCIES = ["i2c"]
|
|
||||||
|
|
||||||
bmp581_ns = cg.esphome_ns.namespace("bmp581_i2c")
|
|
||||||
BMP581I2CComponent = bmp581_ns.class_(
|
|
||||||
"BMP581I2CComponent", cg.PollingComponent, i2c.I2CDevice
|
|
||||||
)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(
|
|
||||||
i2c.i2c_device_schema(default_address=0x46)
|
|
||||||
).extend({cv.GenerateID(): cv.declare_id(BMP581I2CComponent)})
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
var = await to_code_base(config)
|
|
||||||
await i2c.register_i2c_device(var, config)
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32_ble_tracker
|
from esphome.components import esp32_ble_tracker
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_BINDKEY, CONF_ID, CONF_MAC_ADDRESS
|
from esphome.const import CONF_ID, CONF_MAC_ADDRESS
|
||||||
from esphome.core import HexInt
|
|
||||||
|
|
||||||
CODEOWNERS = ["@nagyrobi"]
|
CODEOWNERS = ["@nagyrobi"]
|
||||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||||
@@ -23,7 +22,6 @@ def bthome_mithermometer_base_schema(extra_schema=None):
|
|||||||
{
|
{
|
||||||
cv.GenerateID(CONF_ID): cv.declare_id(BTHomeMiThermometer),
|
cv.GenerateID(CONF_ID): cv.declare_id(BTHomeMiThermometer),
|
||||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
cv.Optional(CONF_BINDKEY): cv.bind_key,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(BLE_DEVICE_SCHEMA)
|
.extend(BLE_DEVICE_SCHEMA)
|
||||||
@@ -36,9 +34,3 @@ async def setup_bthome_mithermometer(var, config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await esp32_ble_tracker.register_ble_device(var, config)
|
await esp32_ble_tracker.register_ble_device(var, config)
|
||||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||||
if bindkey := config.get(CONF_BINDKEY):
|
|
||||||
bindkey_bytes = [
|
|
||||||
HexInt(int(bindkey[index : index + 2], 16))
|
|
||||||
for index in range(0, len(bindkey), 2)
|
|
||||||
]
|
|
||||||
cg.add(var.set_bindkey(cg.ArrayInitializer(*bindkey_bytes)))
|
|
||||||
|
|||||||
@@ -3,23 +3,15 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstring>
|
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include "mbedtls/ccm.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bthome_mithermometer {
|
namespace bthome_mithermometer {
|
||||||
|
|
||||||
static const char *const TAG = "bthome_mithermometer";
|
static const char *const TAG = "bthome_mithermometer";
|
||||||
static constexpr size_t BTHOME_BINDKEY_SIZE = 16;
|
|
||||||
static constexpr size_t BTHOME_NONCE_SIZE = 13;
|
|
||||||
static constexpr size_t BTHOME_MIC_SIZE = 4;
|
|
||||||
static constexpr size_t BTHOME_COUNTER_SIZE = 4;
|
|
||||||
|
|
||||||
static const char *format_mac_address(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buffer, uint64_t address) {
|
static const char *format_mac_address(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buffer, uint64_t address) {
|
||||||
std::array<uint8_t, MAC_ADDRESS_SIZE> mac{};
|
std::array<uint8_t, MAC_ADDRESS_SIZE> mac{};
|
||||||
@@ -138,10 +130,6 @@ void BTHomeMiThermometer::dump_config() {
|
|||||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||||
ESP_LOGCONFIG(TAG, "BTHome MiThermometer");
|
ESP_LOGCONFIG(TAG, "BTHome MiThermometer");
|
||||||
ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(addr_buf, this->address_));
|
ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(addr_buf, this->address_));
|
||||||
if (this->has_bindkey_) {
|
|
||||||
char bindkey_hex[format_hex_pretty_size(BTHOME_BINDKEY_SIZE)];
|
|
||||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty_to(bindkey_hex, this->bindkey_, BTHOME_BINDKEY_SIZE, '.'));
|
|
||||||
}
|
|
||||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||||
@@ -162,60 +150,6 @@ bool BTHomeMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &dev
|
|||||||
return matched;
|
return matched;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BTHomeMiThermometer::set_bindkey(std::initializer_list<uint8_t> bindkey) {
|
|
||||||
if (bindkey.size() != sizeof(this->bindkey_)) {
|
|
||||||
ESP_LOGW(TAG, "BTHome bindkey size mismatch: %zu", bindkey.size());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::copy(bindkey.begin(), bindkey.end(), this->bindkey_);
|
|
||||||
this->has_bindkey_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BTHomeMiThermometer::decrypt_bthome_payload_(const std::vector<uint8_t> &data, uint64_t source_address,
|
|
||||||
std::vector<uint8_t> &payload) const {
|
|
||||||
if (data.size() <= 1 + BTHOME_COUNTER_SIZE + BTHOME_MIC_SIZE) {
|
|
||||||
ESP_LOGVV(TAG, "Encrypted BTHome payload too short: %zu", data.size());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t ciphertext_size = data.size() - 1 - BTHOME_COUNTER_SIZE - BTHOME_MIC_SIZE;
|
|
||||||
payload.resize(ciphertext_size);
|
|
||||||
|
|
||||||
std::array<uint8_t, MAC_ADDRESS_SIZE> mac{};
|
|
||||||
for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) {
|
|
||||||
mac[i] = (source_address >> ((MAC_ADDRESS_SIZE - 1 - i) * 8)) & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<uint8_t, BTHOME_NONCE_SIZE> nonce{};
|
|
||||||
memcpy(nonce.data(), mac.data(), mac.size());
|
|
||||||
nonce[6] = 0xD2;
|
|
||||||
nonce[7] = 0xFC;
|
|
||||||
nonce[8] = data[0];
|
|
||||||
memcpy(nonce.data() + 9, &data[data.size() - BTHOME_COUNTER_SIZE - BTHOME_MIC_SIZE], BTHOME_COUNTER_SIZE);
|
|
||||||
|
|
||||||
const uint8_t *ciphertext = data.data() + 1;
|
|
||||||
const uint8_t *mic = data.data() + data.size() - BTHOME_MIC_SIZE;
|
|
||||||
|
|
||||||
mbedtls_ccm_context ctx;
|
|
||||||
mbedtls_ccm_init(&ctx);
|
|
||||||
|
|
||||||
int ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, this->bindkey_, BTHOME_BINDKEY_SIZE * 8);
|
|
||||||
if (ret) {
|
|
||||||
ESP_LOGVV(TAG, "mbedtls_ccm_setkey() failed.");
|
|
||||||
mbedtls_ccm_free(&ctx);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = mbedtls_ccm_auth_decrypt(&ctx, ciphertext_size, nonce.data(), nonce.size(), nullptr, 0, ciphertext,
|
|
||||||
payload.data(), mic, BTHOME_MIC_SIZE);
|
|
||||||
mbedtls_ccm_free(&ctx);
|
|
||||||
if (ret) {
|
|
||||||
ESP_LOGVV(TAG, "BTHome decryption failed (ret=%d).", ret);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
|
bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
|
||||||
const esp32_ble_tracker::ESPBTDevice &device) {
|
const esp32_ble_tracker::ESPBTDevice &device) {
|
||||||
if (!service_data.uuid.contains(0xD2, 0xFC)) {
|
if (!service_data.uuid.contains(0xD2, 0xFC)) {
|
||||||
@@ -239,88 +173,51 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t source_address = device.address_uint64();
|
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||||
bool address_matches = source_address == this->address_;
|
|
||||||
if (!is_encrypted && mac_included && data.size() >= 7) {
|
|
||||||
uint64_t advertised_address = 0;
|
|
||||||
for (int i = 5; i >= 0; i--) {
|
|
||||||
advertised_address = (advertised_address << 8) | data[1 + i];
|
|
||||||
}
|
|
||||||
address_matches = address_matches || advertised_address == this->address_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_encrypted && !this->has_bindkey_) {
|
|
||||||
if (address_matches) {
|
|
||||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
|
||||||
ESP_LOGE(TAG, "Encrypted BTHome frame received but no bindkey configured for %s",
|
|
||||||
device.address_str_to(addr_buf));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_encrypted && this->has_bindkey_) {
|
|
||||||
if (address_matches) {
|
|
||||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
|
||||||
ESP_LOGE(TAG, "Unencrypted BTHome frame received with bindkey configured for %s",
|
|
||||||
device.address_str_to(addr_buf));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::vector<uint8_t> decrypted_payload;
|
|
||||||
const uint8_t *payload = nullptr;
|
|
||||||
size_t payload_size = 0;
|
|
||||||
|
|
||||||
if (is_encrypted) {
|
if (is_encrypted) {
|
||||||
if (!this->decrypt_bthome_payload_(data, source_address, decrypted_payload)) {
|
ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str_to(addr_buf));
|
||||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
return false;
|
||||||
ESP_LOGVV(TAG, "Failed to decrypt BTHome frame from %s", device.address_str_to(addr_buf));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
payload = decrypted_payload.data();
|
|
||||||
payload_size = decrypted_payload.size();
|
|
||||||
} else {
|
|
||||||
payload = data.data() + 1;
|
|
||||||
payload_size = data.size() - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t payload_index = 1;
|
||||||
|
uint64_t source_address = device.address_uint64();
|
||||||
|
|
||||||
if (mac_included) {
|
if (mac_included) {
|
||||||
if (payload_size < 6) {
|
if (data.size() < 7) {
|
||||||
ESP_LOGVV(TAG, "BTHome payload missing MAC address");
|
ESP_LOGVV(TAG, "BTHome payload missing MAC address");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
source_address = 0;
|
source_address = 0;
|
||||||
for (int i = 5; i >= 0; i--) {
|
for (int i = 5; i >= 0; i--) {
|
||||||
source_address = (source_address << 8) | payload[i];
|
source_address = (source_address << 8) | data[1 + i];
|
||||||
}
|
}
|
||||||
payload += 6;
|
payload_index = 7;
|
||||||
payload_size -= 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
|
||||||
if (source_address != this->address_) {
|
if (source_address != this->address_) {
|
||||||
ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(addr_buf, source_address));
|
ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(addr_buf, source_address));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload_size == 0) {
|
if (payload_index >= data.size()) {
|
||||||
ESP_LOGVV(TAG, "BTHome payload empty after header");
|
ESP_LOGVV(TAG, "BTHome payload empty after header");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool reported = false;
|
bool reported = false;
|
||||||
size_t offset = 0;
|
size_t offset = payload_index;
|
||||||
uint8_t last_type = 0;
|
uint8_t last_type = 0;
|
||||||
|
|
||||||
while (offset < payload_size) {
|
while (offset < data.size()) {
|
||||||
const uint8_t obj_type = payload[offset++];
|
const uint8_t obj_type = data[offset++];
|
||||||
size_t value_length = 0;
|
size_t value_length = 0;
|
||||||
bool has_length_byte = obj_type == 0x53; // text objects include explicit length
|
bool has_length_byte = obj_type == 0x53; // text objects include explicit length
|
||||||
|
|
||||||
if (has_length_byte) {
|
if (has_length_byte) {
|
||||||
if (offset >= payload_size) {
|
if (offset >= data.size()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
value_length = payload[offset++];
|
value_length = data[offset++];
|
||||||
} else {
|
} else {
|
||||||
if (!get_bthome_value_length(obj_type, value_length)) {
|
if (!get_bthome_value_length(obj_type, value_length)) {
|
||||||
ESP_LOGVV(TAG, "Unknown BTHome object 0x%02X", obj_type);
|
ESP_LOGVV(TAG, "Unknown BTHome object 0x%02X", obj_type);
|
||||||
@@ -332,12 +229,12 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset + value_length > payload_size) {
|
if (offset + value_length > data.size()) {
|
||||||
ESP_LOGVV(TAG, "BTHome object length exceeds payload");
|
ESP_LOGVV(TAG, "BTHome object length exceeds payload");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *value = &payload[offset];
|
const uint8_t *value = &data[offset];
|
||||||
offset += value_length;
|
offset += value_length;
|
||||||
|
|
||||||
if (obj_type < last_type) {
|
if (obj_type < last_type) {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <initializer_list>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
@@ -16,7 +14,6 @@ namespace bthome_mithermometer {
|
|||||||
class BTHomeMiThermometer : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
class BTHomeMiThermometer : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||||
public:
|
public:
|
||||||
void set_address(uint64_t address) { this->address_ = address; }
|
void set_address(uint64_t address) { this->address_ = address; }
|
||||||
void set_bindkey(std::initializer_list<uint8_t> bindkey);
|
|
||||||
|
|
||||||
void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; }
|
void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; }
|
||||||
void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; }
|
void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; }
|
||||||
@@ -30,13 +27,9 @@ class BTHomeMiThermometer : public esp32_ble_tracker::ESPBTDeviceListener, publi
|
|||||||
protected:
|
protected:
|
||||||
bool handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
|
bool handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
|
||||||
const esp32_ble_tracker::ESPBTDevice &device);
|
const esp32_ble_tracker::ESPBTDevice &device);
|
||||||
bool decrypt_bthome_payload_(const std::vector<uint8_t> &data, uint64_t source_address,
|
|
||||||
std::vector<uint8_t> &payload) const;
|
|
||||||
|
|
||||||
uint64_t address_{0};
|
uint64_t address_{0};
|
||||||
optional<uint8_t> last_packet_id_{};
|
optional<uint8_t> last_packet_id_{};
|
||||||
bool has_bindkey_{false};
|
|
||||||
uint8_t bindkey_[16];
|
|
||||||
|
|
||||||
sensor::Sensor *temperature_{nullptr};
|
sensor::Sensor *temperature_{nullptr};
|
||||||
sensor::Sensor *humidity_{nullptr};
|
sensor::Sensor *humidity_{nullptr};
|
||||||
|
|||||||
@@ -89,8 +89,10 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
|||||||
delayMicroseconds(500);
|
delayMicroseconds(500);
|
||||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||||
delayMicroseconds(2000);
|
delayMicroseconds(2000);
|
||||||
} else {
|
} else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {
|
||||||
delayMicroseconds(1000);
|
delayMicroseconds(1000);
|
||||||
|
} else {
|
||||||
|
delayMicroseconds(800);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include "fingerprint_grow.h"
|
#include "fingerprint_grow.h"
|
||||||
#include "esphome/core/gpio.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
@@ -533,21 +532,14 @@ void FingerprintGrowComponent::sensor_sleep_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FingerprintGrowComponent::dump_config() {
|
void FingerprintGrowComponent::dump_config() {
|
||||||
char sensing_pin_buf[GPIO_SUMMARY_MAX_LEN];
|
|
||||||
char power_pin_buf[GPIO_SUMMARY_MAX_LEN];
|
|
||||||
if (this->has_sensing_pin_) {
|
|
||||||
this->sensing_pin_->dump_summary(sensing_pin_buf, sizeof(sensing_pin_buf));
|
|
||||||
}
|
|
||||||
if (this->has_power_pin_) {
|
|
||||||
this->sensor_power_pin_->dump_summary(power_pin_buf, sizeof(power_pin_buf));
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"GROW_FINGERPRINT_READER:\n"
|
"GROW_FINGERPRINT_READER:\n"
|
||||||
" System Identifier Code: 0x%.4X\n"
|
" System Identifier Code: 0x%.4X\n"
|
||||||
" Touch Sensing Pin: %s\n"
|
" Touch Sensing Pin: %s\n"
|
||||||
" Sensor Power Pin: %s",
|
" Sensor Power Pin: %s",
|
||||||
this->system_identifier_code_, this->has_sensing_pin_ ? sensing_pin_buf : "None",
|
this->system_identifier_code_,
|
||||||
this->has_power_pin_ ? power_pin_buf : "None");
|
this->has_sensing_pin_ ? this->sensing_pin_->dump_summary().c_str() : "None",
|
||||||
|
this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None");
|
||||||
if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
|
if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
|
||||||
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %" PRIu32 " ms", this->idle_period_to_sleep_ms_);
|
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %" PRIu32 " ms", this->idle_period_to_sleep_ms_);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,44 +3,13 @@
|
|||||||
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <IRSender.h>
|
#include "ir_sender_esphome.h"
|
||||||
#include <HeatpumpIRFactory.h>
|
#include "HeatpumpIRFactory.h"
|
||||||
#include "esphome/components/remote_base/remote_base.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace heatpumpir {
|
namespace heatpumpir {
|
||||||
|
|
||||||
// IRSenderESPHome - bridge between ESPHome's remote_transmitter and HeatpumpIR library
|
|
||||||
// Defined here (not in a header) to isolate HeatpumpIR's headers from the rest of ESPHome,
|
|
||||||
// as they define conflicting symbols like millis() in the global namespace.
|
|
||||||
class IRSenderESPHome : public IRSender {
|
|
||||||
public:
|
|
||||||
IRSenderESPHome(remote_base::RemoteTransmitterBase *transmitter) : IRSender(0), transmit_(transmitter->transmit()) {}
|
|
||||||
|
|
||||||
void setFrequency(int frequency) override { // NOLINT(readability-identifier-naming)
|
|
||||||
auto *data = this->transmit_.get_data();
|
|
||||||
data->set_carrier_frequency(1000 * frequency);
|
|
||||||
}
|
|
||||||
|
|
||||||
void space(int space_length) override {
|
|
||||||
if (space_length) {
|
|
||||||
auto *data = this->transmit_.get_data();
|
|
||||||
data->space(space_length);
|
|
||||||
} else {
|
|
||||||
this->transmit_.perform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void mark(int mark_length) override {
|
|
||||||
auto *data = this->transmit_.get_data();
|
|
||||||
data->mark(mark_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
remote_base::RemoteTransmitterBase::TransmitCall transmit_;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char *const TAG = "heatpumpir.climate";
|
static const char *const TAG = "heatpumpir.climate";
|
||||||
|
|
||||||
const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP = {
|
const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP = {
|
||||||
|
|||||||
32
esphome/components/heatpumpir/ir_sender_esphome.cpp
Normal file
32
esphome/components/heatpumpir/ir_sender_esphome.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include "ir_sender_esphome.h"
|
||||||
|
|
||||||
|
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace heatpumpir {
|
||||||
|
|
||||||
|
void IRSenderESPHome::setFrequency(int frequency) { // NOLINT(readability-identifier-naming)
|
||||||
|
auto *data = transmit_.get_data();
|
||||||
|
data->set_carrier_frequency(1000 * frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an IR 'mark' symbol, i.e. transmitter ON
|
||||||
|
void IRSenderESPHome::mark(int mark_length) {
|
||||||
|
auto *data = transmit_.get_data();
|
||||||
|
data->mark(mark_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an IR 'space' symbol, i.e. transmitter OFF
|
||||||
|
void IRSenderESPHome::space(int space_length) {
|
||||||
|
if (space_length) {
|
||||||
|
auto *data = transmit_.get_data();
|
||||||
|
data->space(space_length);
|
||||||
|
} else {
|
||||||
|
transmit_.perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace heatpumpir
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
||||||
25
esphome/components/heatpumpir/ir_sender_esphome.h
Normal file
25
esphome/components/heatpumpir/ir_sender_esphome.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
||||||
|
|
||||||
|
#include "esphome/components/remote_base/remote_base.h"
|
||||||
|
#include <IRSender.h> // arduino-heatpump library
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace heatpumpir {
|
||||||
|
|
||||||
|
class IRSenderESPHome : public IRSender {
|
||||||
|
public:
|
||||||
|
IRSenderESPHome(remote_base::RemoteTransmitterBase *transmitter) : IRSender(0), transmit_(transmitter->transmit()){};
|
||||||
|
void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming)
|
||||||
|
void space(int space_length) override;
|
||||||
|
void mark(int mark_length) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
remote_base::RemoteTransmitterBase::TransmitCall transmit_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace heatpumpir
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -185,7 +185,7 @@ ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, s
|
|||||||
}
|
}
|
||||||
jobs[num_jobs++].command = I2C_MASTER_CMD_STOP;
|
jobs[num_jobs++].command = I2C_MASTER_CMD_STOP;
|
||||||
ESP_LOGV(TAG, "Sending %zu jobs", num_jobs);
|
ESP_LOGV(TAG, "Sending %zu jobs", num_jobs);
|
||||||
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num_jobs, 100);
|
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num_jobs, 20);
|
||||||
if (err == ESP_ERR_INVALID_STATE) {
|
if (err == ESP_ERR_INVALID_STATE) {
|
||||||
ESP_LOGV(TAG, "TX to %02X failed: not acked", address);
|
ESP_LOGV(TAG, "TX to %02X failed: not acked", address);
|
||||||
return ERROR_NOT_ACKNOWLEDGED;
|
return ERROR_NOT_ACKNOWLEDGED;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
// Once the API is considered stable, this warning will be removed.
|
// Once the API is considered stable, this warning will be removed.
|
||||||
|
|
||||||
#include "esphome/components/infrared/infrared.h"
|
#include "esphome/components/infrared/infrared.h"
|
||||||
|
#include "esphome/components/remote_transmitter/remote_transmitter.h"
|
||||||
|
#include "esphome/components/remote_receiver/remote_receiver.h"
|
||||||
|
|
||||||
namespace esphome::ir_rf_proxy {
|
namespace esphome::ir_rf_proxy {
|
||||||
|
|
||||||
|
|||||||
@@ -391,10 +391,7 @@ void LightCall::transform_parameters_() {
|
|||||||
min_mireds > 0.0f && max_mireds > 0.0f) {
|
min_mireds > 0.0f && max_mireds > 0.0f) {
|
||||||
ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
|
ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
|
||||||
this->parent_->get_name().c_str());
|
this->parent_->get_name().c_str());
|
||||||
// Only compute cold_white/warm_white from color_temperature if they're not already explicitly set.
|
if (this->has_color_temperature()) {
|
||||||
// This is important for state restoration, where both color_temperature and cold_white/warm_white
|
|
||||||
// are restored from flash - we want to preserve the saved cold_white/warm_white values.
|
|
||||||
if (this->has_color_temperature() && !this->has_cold_white() && !this->has_warm_white()) {
|
|
||||||
const float color_temp = clamp(this->color_temperature_, min_mireds, max_mireds);
|
const float color_temp = clamp(this->color_temperature_, min_mireds, max_mireds);
|
||||||
const float range = max_mireds - min_mireds;
|
const float range = max_mireds - min_mireds;
|
||||||
const float ww_fraction = (color_temp - min_mireds) / range;
|
const float ww_fraction = (color_temp - min_mireds) / range;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class LabelType(WidgetType):
|
|||||||
|
|
||||||
async def to_code(self, w: Widget, config):
|
async def to_code(self, w: Widget, config):
|
||||||
"""For a text object, create and set text"""
|
"""For a text object, create and set text"""
|
||||||
if (value := config.get(CONF_TEXT)) is not None:
|
if value := config.get(CONF_TEXT):
|
||||||
await w.set_property(CONF_TEXT, await lv_text.process(value))
|
await w.set_property(CONF_TEXT, await lv_text.process(value))
|
||||||
await w.set_property(CONF_LONG_MODE, config)
|
await w.set_property(CONF_LONG_MODE, config)
|
||||||
await w.set_property(CONF_RECOLOR, config)
|
await w.set_property(CONF_RECOLOR, config)
|
||||||
|
|||||||
@@ -55,44 +55,3 @@ DriverChip(
|
|||||||
(0x35,), (0xFE,),
|
(0x35,), (0xFE,),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
DriverChip(
|
|
||||||
"M5STACK-TAB5-V2",
|
|
||||||
height=1280,
|
|
||||||
width=720,
|
|
||||||
hsync_back_porch=40,
|
|
||||||
hsync_pulse_width=2,
|
|
||||||
hsync_front_porch=40,
|
|
||||||
vsync_back_porch=8,
|
|
||||||
vsync_pulse_width=2,
|
|
||||||
vsync_front_porch=220,
|
|
||||||
pclk_frequency="80MHz",
|
|
||||||
lane_bit_rate="960Mbps",
|
|
||||||
swap_xy=cv.UNDEFINED,
|
|
||||||
color_order="RGB",
|
|
||||||
initsequence=[
|
|
||||||
(0x60, 0x71, 0x23, 0xa2),
|
|
||||||
(0x60, 0x71, 0x23, 0xa3),
|
|
||||||
(0x60, 0x71, 0x23, 0xa4),
|
|
||||||
(0xA4, 0x31),
|
|
||||||
(0xD7, 0x10, 0x0A, 0x10, 0x2A, 0x80, 0x80),
|
|
||||||
(0x90, 0x71, 0x23, 0x5A, 0x20, 0x24, 0x09, 0x09),
|
|
||||||
(0xA3, 0x80, 0x01, 0x88, 0x30, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x4F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x6F, 0x58, 0x00, 0x00, 0x00, 0xFF),
|
|
||||||
(0xA6, 0x03, 0x00, 0x24, 0x55, 0x36, 0x00, 0x39, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24, 0x55, 0x38, 0x00, 0x37, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24, 0x11, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0xEC, 0x11, 0x00, 0x03, 0x00, 0x03, 0x6E, 0x6E, 0xFF, 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00),
|
|
||||||
(0xA7, 0x19, 0x19, 0x80, 0x64, 0x40, 0x07, 0x16, 0x40, 0x00, 0x44, 0x03, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x25, 0x34, 0x40, 0x00, 0x02, 0x01, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x6E, 0x6E, 0x84, 0xFF, 0x08, 0x80, 0x44),
|
|
||||||
(0xAC, 0x03, 0x19, 0x19, 0x18, 0x18, 0x06, 0x13, 0x13, 0x11, 0x11, 0x08, 0x08, 0x0A, 0x0A, 0x1C, 0x1C, 0x07, 0x07, 0x00, 0x00, 0x02, 0x02, 0x01, 0x19, 0x19, 0x18, 0x18, 0x06, 0x12, 0x12, 0x10, 0x10, 0x09, 0x09, 0x0B, 0x0B, 0x1C, 0x1C, 0x07, 0x07, 0x03, 0x03, 0x01, 0x01),
|
|
||||||
(0xAD, 0xF0, 0x00, 0x46, 0x00, 0x03, 0x50, 0x50, 0xFF, 0xFF, 0xF0, 0x40, 0x06, 0x01, 0x07, 0x42, 0x42, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF),
|
|
||||||
(0xAE, 0xFE, 0x3F, 0x3F, 0xFE, 0x3F, 0x3F, 0x00),
|
|
||||||
(0xB2, 0x15, 0x19, 0x05, 0x23, 0x49, 0xAF, 0x03, 0x2E, 0x5C, 0xD2, 0xFF, 0x10, 0x20, 0xFD, 0x20, 0xC0, 0x00),
|
|
||||||
(0xE8, 0x20, 0x6F, 0x04, 0x97, 0x97, 0x3E, 0x04, 0xDC, 0xDC, 0x3E, 0x06, 0xFA, 0x26, 0x3E),
|
|
||||||
(0x75, 0x03, 0x04),
|
|
||||||
(0xE7, 0x3B, 0x00, 0x00, 0x7C, 0xA1, 0x8C, 0x20, 0x1A, 0xF0, 0xB1, 0x50, 0x00, 0x50, 0xB1, 0x50, 0xB1, 0x50, 0xD8, 0x00, 0x55, 0x00, 0xB1, 0x00, 0x45, 0xC9, 0x6A, 0xFF, 0x5A, 0xD8, 0x18, 0x88, 0x15, 0xB1, 0x01, 0x01, 0x77),
|
|
||||||
(0xEA, 0x13, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2C),
|
|
||||||
(0xB0, 0x22, 0x43, 0x11, 0x61, 0x25, 0x43, 0x43),
|
|
||||||
(0xb7, 0x00, 0x00, 0x73, 0x73),
|
|
||||||
(0xBF, 0xA6, 0xAA),
|
|
||||||
(0xA9, 0x00, 0x00, 0x73, 0xFF, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03),
|
|
||||||
(0xC8, 0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06, 0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32, 0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF),
|
|
||||||
(0xC9, 0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06, 0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32, 0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||||
#include "mipi_rgb.h"
|
#include "mipi_rgb.h"
|
||||||
#include "esphome/core/gpio.h"
|
|
||||||
#include "esphome/core/hal.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
#include "esp_lcd_panel_rgb.h"
|
#include "esp_lcd_panel_rgb.h"
|
||||||
#include <span>
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace mipi_rgb {
|
namespace mipi_rgb {
|
||||||
@@ -345,27 +343,19 @@ int MipiRgb::get_height() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *get_pin_name(GPIOPin *pin, std::span<char, GPIO_SUMMARY_MAX_LEN> buffer) {
|
static std::string get_pin_name(GPIOPin *pin) {
|
||||||
if (pin == nullptr)
|
if (pin == nullptr)
|
||||||
return "None";
|
return "None";
|
||||||
pin->dump_summary(buffer.data(), buffer.size());
|
return pin->dump_summary();
|
||||||
return buffer.data();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MipiRgb::dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset) {
|
void MipiRgb::dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset) {
|
||||||
char pin_summary[GPIO_SUMMARY_MAX_LEN];
|
|
||||||
for (uint8_t i = start; i != end; i++) {
|
for (uint8_t i = start; i != end; i++) {
|
||||||
this->data_pins_[i]->dump_summary(pin_summary, sizeof(pin_summary));
|
ESP_LOGCONFIG(TAG, " %s pin %d: %s", name, offset++, this->data_pins_[i]->dump_summary().c_str());
|
||||||
ESP_LOGCONFIG(TAG, " %s pin %d: %s", name, offset++, pin_summary);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MipiRgb::dump_config() {
|
void MipiRgb::dump_config() {
|
||||||
char reset_buf[GPIO_SUMMARY_MAX_LEN];
|
|
||||||
char de_buf[GPIO_SUMMARY_MAX_LEN];
|
|
||||||
char pclk_buf[GPIO_SUMMARY_MAX_LEN];
|
|
||||||
char hsync_buf[GPIO_SUMMARY_MAX_LEN];
|
|
||||||
char vsync_buf[GPIO_SUMMARY_MAX_LEN];
|
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"MIPI_RGB LCD"
|
"MIPI_RGB LCD"
|
||||||
"\n Model: %s"
|
"\n Model: %s"
|
||||||
@@ -389,9 +379,9 @@ void MipiRgb::dump_config() {
|
|||||||
this->model_, this->width_, this->height_, this->rotation_, YESNO(this->pclk_inverted_),
|
this->model_, this->width_, this->height_, this->rotation_, YESNO(this->pclk_inverted_),
|
||||||
this->hsync_pulse_width_, this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_,
|
this->hsync_pulse_width_, this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_,
|
||||||
this->vsync_back_porch_, this->vsync_front_porch_, YESNO(this->invert_colors_),
|
this->vsync_back_porch_, this->vsync_front_porch_, YESNO(this->invert_colors_),
|
||||||
(unsigned) (this->pclk_frequency_ / 1000000), get_pin_name(this->reset_pin_, reset_buf),
|
(unsigned) (this->pclk_frequency_ / 1000000), get_pin_name(this->reset_pin_).c_str(),
|
||||||
get_pin_name(this->de_pin_, de_buf), get_pin_name(this->pclk_pin_, pclk_buf),
|
get_pin_name(this->de_pin_).c_str(), get_pin_name(this->pclk_pin_).c_str(),
|
||||||
get_pin_name(this->hsync_pin_, hsync_buf), get_pin_name(this->vsync_pin_, vsync_buf));
|
get_pin_name(this->hsync_pin_).c_str(), get_pin_name(this->vsync_pin_).c_str());
|
||||||
|
|
||||||
this->dump_pins_(8, 13, "Blue", 0);
|
this->dump_pins_(8, 13, "Blue", 0);
|
||||||
this->dump_pins_(13, 16, "Green", 0);
|
this->dump_pins_(13, 16, "Green", 0);
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ st7701s = ST7701S(
|
|||||||
pclk_frequency="16MHz",
|
pclk_frequency="16MHz",
|
||||||
pclk_inverted=True,
|
pclk_inverted=True,
|
||||||
initsequence=(
|
initsequence=(
|
||||||
(0x01,), # Software Reset
|
|
||||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), # Page 0
|
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), # Page 0
|
||||||
(0xC0, 0x3B, 0x00), (0xC1, 0x0D, 0x02), (0xC2, 0x31, 0x05),
|
(0xC0, 0x3B, 0x00), (0xC1, 0x0D, 0x02), (0xC2, 0x31, 0x05),
|
||||||
(0xB0, 0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18,),
|
(0xB0, 0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18,),
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ def modbus_calc_properties(config):
|
|||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = value.encode()
|
value = value.encode()
|
||||||
config[CONF_ADDRESS] = binascii.crc_hqx(value, 0)
|
config[CONF_ADDRESS] = binascii.crc_hqx(value, 0)
|
||||||
config[CONF_REGISTER_TYPE] = cv.enum(MODBUS_REGISTER_TYPE)("custom")
|
config[CONF_REGISTER_TYPE] = ModbusRegisterType.CUSTOM
|
||||||
config[CONF_FORCE_NEW_RANGE] = True
|
config[CONF_FORCE_NEW_RANGE] = True
|
||||||
return byte_offset, reg_count
|
return byte_offset, reg_count
|
||||||
|
|
||||||
|
|||||||
@@ -133,17 +133,14 @@ void RD03DComponent::process_frame_() {
|
|||||||
uint8_t offset = FRAME_HEADER_SIZE + (i * TARGET_DATA_SIZE);
|
uint8_t offset = FRAME_HEADER_SIZE + (i * TARGET_DATA_SIZE);
|
||||||
|
|
||||||
// Extract raw bytes for this target
|
// Extract raw bytes for this target
|
||||||
// Note: Despite datasheet Table 5-2 showing order as X, Y, Speed, Resolution,
|
|
||||||
// actual radar output has Resolution before Speed (verified empirically -
|
|
||||||
// stationary targets were showing non-zero speed with original field order)
|
|
||||||
uint8_t x_low = this->buffer_[offset + 0];
|
uint8_t x_low = this->buffer_[offset + 0];
|
||||||
uint8_t x_high = this->buffer_[offset + 1];
|
uint8_t x_high = this->buffer_[offset + 1];
|
||||||
uint8_t y_low = this->buffer_[offset + 2];
|
uint8_t y_low = this->buffer_[offset + 2];
|
||||||
uint8_t y_high = this->buffer_[offset + 3];
|
uint8_t y_high = this->buffer_[offset + 3];
|
||||||
uint8_t res_low = this->buffer_[offset + 4];
|
uint8_t speed_low = this->buffer_[offset + 4];
|
||||||
uint8_t res_high = this->buffer_[offset + 5];
|
uint8_t speed_high = this->buffer_[offset + 5];
|
||||||
uint8_t speed_low = this->buffer_[offset + 6];
|
uint8_t res_low = this->buffer_[offset + 6];
|
||||||
uint8_t speed_high = this->buffer_[offset + 7];
|
uint8_t res_high = this->buffer_[offset + 7];
|
||||||
|
|
||||||
// Decode values per RD-03D format
|
// Decode values per RD-03D format
|
||||||
int16_t x = decode_value(x_low, x_high);
|
int16_t x = decode_value(x_low, x_high);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||||
#include "rpi_dpi_rgb.h"
|
#include "rpi_dpi_rgb.h"
|
||||||
#include "esphome/core/gpio.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -135,11 +134,8 @@ void RpiDpiRgb::dump_config() {
|
|||||||
LOG_PIN(" Enable Pin: ", this->enable_pin_);
|
LOG_PIN(" Enable Pin: ", this->enable_pin_);
|
||||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||||
char pin_summary[GPIO_SUMMARY_MAX_LEN];
|
for (size_t i = 0; i != data_pin_count; i++)
|
||||||
for (size_t i = 0; i != data_pin_count; i++) {
|
ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, (this->data_pins_[i])->dump_summary().c_str());
|
||||||
this->data_pins_[i]->dump_summary(pin_summary, sizeof(pin_summary));
|
|
||||||
ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, pin_summary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpiDpiRgb::reset_display_() const {
|
void RpiDpiRgb::reset_display_() const {
|
||||||
|
|||||||
@@ -30,19 +30,6 @@ static const int8_t SEN5X_INDEX_SCALE_FACTOR = 10; //
|
|||||||
static const int8_t SEN5X_MIN_INDEX_VALUE = 1 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
static const int8_t SEN5X_MIN_INDEX_VALUE = 1 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
||||||
static const int16_t SEN5X_MAX_INDEX_VALUE = 500 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
static const int16_t SEN5X_MAX_INDEX_VALUE = 500 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
||||||
|
|
||||||
static const LogString *type_to_string(Sen5xType type) {
|
|
||||||
switch (type) {
|
|
||||||
case Sen5xType::SEN50:
|
|
||||||
return LOG_STR("SEN50");
|
|
||||||
case Sen5xType::SEN54:
|
|
||||||
return LOG_STR("SEN54");
|
|
||||||
case Sen5xType::SEN55:
|
|
||||||
return LOG_STR("SEN55");
|
|
||||||
default:
|
|
||||||
return LOG_STR("UNKNOWN");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const LogString *rht_accel_mode_to_string(RhtAccelerationMode mode) {
|
static const LogString *rht_accel_mode_to_string(RhtAccelerationMode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case LOW_ACCELERATION:
|
case LOW_ACCELERATION:
|
||||||
@@ -56,15 +43,6 @@ static const LogString *rht_accel_mode_to_string(RhtAccelerationMode mode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function performs an in-place conversion of the provided buffer
|
|
||||||
// from uint16_t values to big endianness
|
|
||||||
static inline const char *sensirion_convert_to_string_in_place(uint16_t *array, size_t length) {
|
|
||||||
for (size_t i = 0; i < length; i++) {
|
|
||||||
array[i] = convert_big_endian(array[i]);
|
|
||||||
}
|
|
||||||
return reinterpret_cast<const char *>(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SEN5XComponent::setup() {
|
void SEN5XComponent::setup() {
|
||||||
// the sensor needs 1000 ms to enter the idle state
|
// the sensor needs 1000 ms to enter the idle state
|
||||||
this->set_timeout(1000, [this]() {
|
this->set_timeout(1000, [this]() {
|
||||||
@@ -97,18 +75,18 @@ void SEN5XComponent::setup() {
|
|||||||
stop_measurement_delay = 200;
|
stop_measurement_delay = 200;
|
||||||
}
|
}
|
||||||
this->set_timeout(stop_measurement_delay, [this]() {
|
this->set_timeout(stop_measurement_delay, [this]() {
|
||||||
// note: serial number register is actually 32-bytes long but we grab only the first 16-bytes,
|
uint16_t raw_serial_number[3];
|
||||||
// this appears to be all that Sensirion uses for serial numbers, this could change
|
if (!this->get_register(SEN5X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 20)) {
|
||||||
uint16_t raw_serial_number[8];
|
|
||||||
if (!this->get_register(SEN5X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 8, 20)) {
|
|
||||||
ESP_LOGE(TAG, "Failed to read serial number");
|
ESP_LOGE(TAG, "Failed to read serial number");
|
||||||
this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED;
|
this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const char *serial_number = sensirion_convert_to_string_in_place(raw_serial_number, 8);
|
this->serial_number_[0] = static_cast<bool>(uint16_t(raw_serial_number[0]) & 0xFF);
|
||||||
snprintf(this->serial_number_, sizeof(this->serial_number_), "%s", serial_number);
|
this->serial_number_[1] = static_cast<uint16_t>(raw_serial_number[0] & 0xFF);
|
||||||
ESP_LOGV(TAG, "Serial number %s", this->serial_number_);
|
this->serial_number_[2] = static_cast<uint16_t>(raw_serial_number[1] >> 8);
|
||||||
|
ESP_LOGV(TAG, "Serial number %02d.%02d.%02d", this->serial_number_[0], this->serial_number_[1],
|
||||||
|
this->serial_number_[2]);
|
||||||
|
|
||||||
uint16_t raw_product_name[16];
|
uint16_t raw_product_name[16];
|
||||||
if (!this->get_register(SEN5X_CMD_GET_PRODUCT_NAME, raw_product_name, 16, 20)) {
|
if (!this->get_register(SEN5X_CMD_GET_PRODUCT_NAME, raw_product_name, 16, 20)) {
|
||||||
@@ -117,35 +95,50 @@ void SEN5XComponent::setup() {
|
|||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const char *product_name = sensirion_convert_to_string_in_place(raw_product_name, 16);
|
// 2 ASCII bytes are encoded in an int
|
||||||
if (strncmp(product_name, "SEN50", 5) == 0) {
|
const uint16_t *current_int = raw_product_name;
|
||||||
this->type_ = Sen5xType::SEN50;
|
char current_char;
|
||||||
} else if (strncmp(product_name, "SEN54", 5) == 0) {
|
uint8_t max = 16;
|
||||||
this->type_ = Sen5xType::SEN54;
|
do {
|
||||||
} else if (strncmp(product_name, "SEN55", 5) == 0) {
|
// first char
|
||||||
this->type_ = Sen5xType::SEN55;
|
current_char = *current_int >> 8;
|
||||||
} else {
|
if (current_char) {
|
||||||
this->type_ = Sen5xType::UNKNOWN;
|
this->product_name_.push_back(current_char);
|
||||||
ESP_LOGE(TAG, "Unknown product name: %.32s", product_name);
|
// second char
|
||||||
this->error_code_ = PRODUCT_NAME_FAILED;
|
current_char = *current_int & 0xFF;
|
||||||
this->mark_failed();
|
if (current_char) {
|
||||||
return;
|
this->product_name_.push_back(current_char);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
current_int++;
|
||||||
|
} while (current_char && --max);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Type: %s", LOG_STR_ARG(type_to_string(this->type_)));
|
Sen5xType sen5x_type = UNKNOWN;
|
||||||
if (this->humidity_sensor_ && this->type_ == Sen5xType::SEN50) {
|
if (this->product_name_ == "SEN50") {
|
||||||
|
sen5x_type = SEN50;
|
||||||
|
} else {
|
||||||
|
if (this->product_name_ == "SEN54") {
|
||||||
|
sen5x_type = SEN54;
|
||||||
|
} else {
|
||||||
|
if (this->product_name_ == "SEN55") {
|
||||||
|
sen5x_type = SEN55;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Product name: %s", this->product_name_.c_str());
|
||||||
|
}
|
||||||
|
if (this->humidity_sensor_ && sen5x_type == SEN50) {
|
||||||
ESP_LOGE(TAG, "Relative humidity requires a SEN54 or SEN55");
|
ESP_LOGE(TAG, "Relative humidity requires a SEN54 or SEN55");
|
||||||
this->humidity_sensor_ = nullptr; // mark as not used
|
this->humidity_sensor_ = nullptr; // mark as not used
|
||||||
}
|
}
|
||||||
if (this->temperature_sensor_ && this->type_ == Sen5xType::SEN50) {
|
if (this->temperature_sensor_ && sen5x_type == SEN50) {
|
||||||
ESP_LOGE(TAG, "Temperature requires a SEN54 or SEN55");
|
ESP_LOGE(TAG, "Temperature requires a SEN54 or SEN55");
|
||||||
this->temperature_sensor_ = nullptr; // mark as not used
|
this->temperature_sensor_ = nullptr; // mark as not used
|
||||||
}
|
}
|
||||||
if (this->voc_sensor_ && this->type_ == Sen5xType::SEN50) {
|
if (this->voc_sensor_ && sen5x_type == SEN50) {
|
||||||
ESP_LOGE(TAG, "VOC requires a SEN54 or SEN55");
|
ESP_LOGE(TAG, "VOC requires a SEN54 or SEN55");
|
||||||
this->voc_sensor_ = nullptr; // mark as not used
|
this->voc_sensor_ = nullptr; // mark as not used
|
||||||
}
|
}
|
||||||
if (this->nox_sensor_ && this->type_ != Sen5xType::SEN55) {
|
if (this->nox_sensor_ && sen5x_type != SEN55) {
|
||||||
ESP_LOGE(TAG, "NOx requires a SEN55");
|
ESP_LOGE(TAG, "NOx requires a SEN55");
|
||||||
this->nox_sensor_ = nullptr; // mark as not used
|
this->nox_sensor_ = nullptr; // mark as not used
|
||||||
}
|
}
|
||||||
@@ -160,25 +153,43 @@ void SEN5XComponent::setup() {
|
|||||||
ESP_LOGV(TAG, "Firmware version %d", this->firmware_version_);
|
ESP_LOGV(TAG, "Firmware version %d", this->firmware_version_);
|
||||||
|
|
||||||
if (this->voc_sensor_ && this->store_baseline_) {
|
if (this->voc_sensor_ && this->store_baseline_) {
|
||||||
// Hash with serial number, serial numbers are unique, so multiple sensors can be used without conflict
|
uint32_t combined_serial =
|
||||||
uint32_t hash = fnv1a_hash(this->serial_number_);
|
encode_uint24(this->serial_number_[0], this->serial_number_[1], this->serial_number_[2]);
|
||||||
this->pref_ = global_preferences->make_preference<uint16_t[4]>(hash, true);
|
// Hash with config hash, version, and serial number
|
||||||
this->voc_baseline_time_ = App.get_loop_component_start_time();
|
// This ensures the baseline storage is cleared after OTA
|
||||||
if (this->pref_.load(&this->voc_baseline_state_)) {
|
// Serial numbers are unique to each sensor, so multiple sensors can be used without conflict
|
||||||
if (!this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE, this->voc_baseline_state_, 4)) {
|
uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), combined_serial);
|
||||||
ESP_LOGE(TAG, "VOC Baseline State write to sensor failed");
|
this->pref_ = global_preferences->make_preference<Sen5xBaselines>(hash, true);
|
||||||
} else {
|
|
||||||
ESP_LOGV(TAG, "VOC Baseline State loaded");
|
if (this->pref_.load(&this->voc_baselines_storage_)) {
|
||||||
delay(20);
|
ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32,
|
||||||
|
this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize storage timestamp
|
||||||
|
this->seconds_since_last_store_ = 0;
|
||||||
|
|
||||||
|
if (this->voc_baselines_storage_.state0 > 0 && this->voc_baselines_storage_.state1 > 0) {
|
||||||
|
ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32,
|
||||||
|
this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1);
|
||||||
|
uint16_t states[4];
|
||||||
|
|
||||||
|
states[0] = this->voc_baselines_storage_.state0 >> 16;
|
||||||
|
states[1] = this->voc_baselines_storage_.state0 & 0xFFFF;
|
||||||
|
states[2] = this->voc_baselines_storage_.state1 >> 16;
|
||||||
|
states[3] = this->voc_baselines_storage_.state1 & 0xFFFF;
|
||||||
|
|
||||||
|
if (!this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE, states, 4)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to set VOC baseline from saved state");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool result;
|
bool result;
|
||||||
if (this->auto_cleaning_interval_.has_value()) {
|
if (this->auto_cleaning_interval_.has_value()) {
|
||||||
// override default value
|
// override default value
|
||||||
result = this->write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL, this->auto_cleaning_interval_.value());
|
result = write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL, this->auto_cleaning_interval_.value());
|
||||||
} else {
|
} else {
|
||||||
result = this->write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL);
|
result = write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL);
|
||||||
}
|
}
|
||||||
if (result) {
|
if (result) {
|
||||||
delay(20);
|
delay(20);
|
||||||
@@ -265,10 +276,11 @@ void SEN5XComponent::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
" Type: %s\n"
|
" Product name: %s\n"
|
||||||
" Firmware version: %d\n"
|
" Firmware version: %d\n"
|
||||||
" Serial number: %s",
|
" Serial number %02d.%02d.%02d",
|
||||||
LOG_STR_ARG(type_to_string(this->type_)), this->firmware_version_, this->serial_number_);
|
this->product_name_.c_str(), this->firmware_version_, this->serial_number_[0], this->serial_number_[1],
|
||||||
|
this->serial_number_[2]);
|
||||||
if (this->auto_cleaning_interval_.has_value()) {
|
if (this->auto_cleaning_interval_.has_value()) {
|
||||||
ESP_LOGCONFIG(TAG, " Auto cleaning interval: %" PRId32 "s", this->auto_cleaning_interval_.value());
|
ESP_LOGCONFIG(TAG, " Auto cleaning interval: %" PRId32 "s", this->auto_cleaning_interval_.value());
|
||||||
}
|
}
|
||||||
@@ -276,14 +288,6 @@ void SEN5XComponent::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG, " RH/T acceleration mode: %s",
|
ESP_LOGCONFIG(TAG, " RH/T acceleration mode: %s",
|
||||||
LOG_STR_ARG(rht_accel_mode_to_string(this->acceleration_mode_.value())));
|
LOG_STR_ARG(rht_accel_mode_to_string(this->acceleration_mode_.value())));
|
||||||
}
|
}
|
||||||
if (this->voc_sensor_) {
|
|
||||||
char hex_buf[5 * 4];
|
|
||||||
format_hex_pretty_to(hex_buf, this->voc_baseline_state_, 4, 0);
|
|
||||||
ESP_LOGCONFIG(TAG,
|
|
||||||
" Store Baseline: %s\n"
|
|
||||||
" State: %s\n",
|
|
||||||
TRUEFALSE(this->store_baseline_), hex_buf);
|
|
||||||
}
|
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
LOG_SENSOR(" ", "PM 1.0", this->pm_1_0_sensor_);
|
LOG_SENSOR(" ", "PM 1.0", this->pm_1_0_sensor_);
|
||||||
LOG_SENSOR(" ", "PM 2.5", this->pm_2_5_sensor_);
|
LOG_SENSOR(" ", "PM 2.5", this->pm_2_5_sensor_);
|
||||||
@@ -300,6 +304,36 @@ void SEN5XComponent::update() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store baselines after defined interval or if the difference between current and stored baseline becomes too
|
||||||
|
// much
|
||||||
|
if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) {
|
||||||
|
if (this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE)) {
|
||||||
|
// run it a bit later to avoid adding a delay here
|
||||||
|
this->set_timeout(550, [this]() {
|
||||||
|
uint16_t states[4];
|
||||||
|
if (this->read_data(states, 4)) {
|
||||||
|
uint32_t state0 = states[0] << 16 | states[1];
|
||||||
|
uint32_t state1 = states[2] << 16 | states[3];
|
||||||
|
if ((uint32_t) std::abs(static_cast<int32_t>(this->voc_baselines_storage_.state0 - state0)) >
|
||||||
|
MAXIMUM_STORAGE_DIFF ||
|
||||||
|
(uint32_t) std::abs(static_cast<int32_t>(this->voc_baselines_storage_.state1 - state1)) >
|
||||||
|
MAXIMUM_STORAGE_DIFF) {
|
||||||
|
this->seconds_since_last_store_ = 0;
|
||||||
|
this->voc_baselines_storage_.state0 = state0;
|
||||||
|
this->voc_baselines_storage_.state1 = state1;
|
||||||
|
|
||||||
|
if (this->pref_.save(&this->voc_baselines_storage_)) {
|
||||||
|
ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32,
|
||||||
|
this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Could not store VOC baselines");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this->write_command(SEN5X_CMD_READ_MEASUREMENT)) {
|
if (!this->write_command(SEN5X_CMD_READ_MEASUREMENT)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
ESP_LOGD(TAG, "Write error: read measurement (%d)", this->last_error_);
|
ESP_LOGD(TAG, "Write error: read measurement (%d)", this->last_error_);
|
||||||
@@ -368,29 +402,7 @@ void SEN5XComponent::update() {
|
|||||||
if (this->nox_sensor_ != nullptr) {
|
if (this->nox_sensor_ != nullptr) {
|
||||||
this->nox_sensor_->publish_state(nox);
|
this->nox_sensor_->publish_state(nox);
|
||||||
}
|
}
|
||||||
|
this->status_clear_warning();
|
||||||
if (!this->voc_sensor_ || !this->store_baseline_ ||
|
|
||||||
(App.get_loop_component_start_time() - this->voc_baseline_time_) < SHORTEST_BASELINE_STORE_INTERVAL) {
|
|
||||||
this->status_clear_warning();
|
|
||||||
} else {
|
|
||||||
this->voc_baseline_time_ = App.get_loop_component_start_time();
|
|
||||||
if (!this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE)) {
|
|
||||||
this->status_set_warning();
|
|
||||||
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
|
|
||||||
} else {
|
|
||||||
this->set_timeout(20, [this]() {
|
|
||||||
if (!this->read_data(this->voc_baseline_state_, 4)) {
|
|
||||||
this->status_set_warning();
|
|
||||||
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
|
|
||||||
} else {
|
|
||||||
if (this->pref_.save(&this->voc_baseline_state_)) {
|
|
||||||
ESP_LOGD(TAG, "VOC Baseline State saved");
|
|
||||||
}
|
|
||||||
this->status_clear_warning();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ enum RhtAccelerationMode : uint16_t {
|
|||||||
HIGH_ACCELERATION = 2,
|
HIGH_ACCELERATION = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Sen5xType : uint8_t { SEN50, SEN54, SEN55, UNKNOWN };
|
struct Sen5xBaselines {
|
||||||
|
int32_t state0;
|
||||||
|
int32_t state1;
|
||||||
|
} PACKED; // NOLINT
|
||||||
|
|
||||||
struct GasTuning {
|
struct GasTuning {
|
||||||
uint16_t index_offset;
|
uint16_t index_offset;
|
||||||
@@ -41,9 +44,11 @@ struct TemperatureCompensation {
|
|||||||
uint16_t time_constant;
|
uint16_t time_constant;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Shortest time interval of 2H (in milliseconds) for storing baseline values.
|
// Shortest time interval of 3H for storing baseline values.
|
||||||
// Prevents wear of the flash because of too many write operations
|
// Prevents wear of the flash because of too many write operations
|
||||||
static const uint32_t SHORTEST_BASELINE_STORE_INTERVAL = 2 * 60 * 60 * 1000;
|
static const uint32_t SHORTEST_BASELINE_STORE_INTERVAL = 10800;
|
||||||
|
// Store anyway if the baseline difference exceeds the max storage diff value
|
||||||
|
static const uint32_t MAXIMUM_STORAGE_DIFF = 50;
|
||||||
|
|
||||||
class SEN5XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
class SEN5XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
@@ -51,20 +56,20 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
|||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { this->pm_1_0_sensor_ = pm_1_0; }
|
enum Sen5xType { SEN50, SEN54, SEN55, UNKNOWN };
|
||||||
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { this->pm_2_5_sensor_ = pm_2_5; }
|
|
||||||
void set_pm_4_0_sensor(sensor::Sensor *pm_4_0) { this->pm_4_0_sensor_ = pm_4_0; }
|
|
||||||
void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { this->pm_10_0_sensor_ = pm_10_0; }
|
|
||||||
|
|
||||||
void set_voc_sensor(sensor::Sensor *voc_sensor) { this->voc_sensor_ = voc_sensor; }
|
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; }
|
||||||
void set_nox_sensor(sensor::Sensor *nox_sensor) { this->nox_sensor_ = nox_sensor; }
|
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; }
|
||||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
void set_pm_4_0_sensor(sensor::Sensor *pm_4_0) { pm_4_0_sensor_ = pm_4_0; }
|
||||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { pm_10_0_sensor_ = pm_10_0; }
|
||||||
void set_store_baseline(bool store_baseline) { this->store_baseline_ = store_baseline; }
|
|
||||||
void set_acceleration_mode(RhtAccelerationMode mode) { this->acceleration_mode_ = mode; }
|
void set_voc_sensor(sensor::Sensor *voc_sensor) { voc_sensor_ = voc_sensor; }
|
||||||
void set_auto_cleaning_interval(uint32_t auto_cleaning_interval) {
|
void set_nox_sensor(sensor::Sensor *nox_sensor) { nox_sensor_ = nox_sensor; }
|
||||||
this->auto_cleaning_interval_ = auto_cleaning_interval;
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||||
}
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||||
|
void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; }
|
||||||
|
void set_acceleration_mode(RhtAccelerationMode mode) { acceleration_mode_ = mode; }
|
||||||
|
void set_auto_cleaning_interval(uint32_t auto_cleaning_interval) { auto_cleaning_interval_ = auto_cleaning_interval; }
|
||||||
void set_voc_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours,
|
void set_voc_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours,
|
||||||
uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes,
|
uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes,
|
||||||
uint16_t std_initial, uint16_t gain_factor) {
|
uint16_t std_initial, uint16_t gain_factor) {
|
||||||
@@ -75,7 +80,7 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
|||||||
tuning_params.gating_max_duration_minutes = gating_max_duration_minutes;
|
tuning_params.gating_max_duration_minutes = gating_max_duration_minutes;
|
||||||
tuning_params.std_initial = std_initial;
|
tuning_params.std_initial = std_initial;
|
||||||
tuning_params.gain_factor = gain_factor;
|
tuning_params.gain_factor = gain_factor;
|
||||||
this->voc_tuning_params_ = tuning_params;
|
voc_tuning_params_ = tuning_params;
|
||||||
}
|
}
|
||||||
void set_nox_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours,
|
void set_nox_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours,
|
||||||
uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes,
|
uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes,
|
||||||
@@ -87,14 +92,14 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
|||||||
tuning_params.gating_max_duration_minutes = gating_max_duration_minutes;
|
tuning_params.gating_max_duration_minutes = gating_max_duration_minutes;
|
||||||
tuning_params.std_initial = 50;
|
tuning_params.std_initial = 50;
|
||||||
tuning_params.gain_factor = gain_factor;
|
tuning_params.gain_factor = gain_factor;
|
||||||
this->nox_tuning_params_ = tuning_params;
|
nox_tuning_params_ = tuning_params;
|
||||||
}
|
}
|
||||||
void set_temperature_compensation(float offset, float normalized_offset_slope, uint16_t time_constant) {
|
void set_temperature_compensation(float offset, float normalized_offset_slope, uint16_t time_constant) {
|
||||||
TemperatureCompensation temp_comp;
|
TemperatureCompensation temp_comp;
|
||||||
temp_comp.offset = offset * 200;
|
temp_comp.offset = offset * 200;
|
||||||
temp_comp.normalized_offset_slope = normalized_offset_slope * 10000;
|
temp_comp.normalized_offset_slope = normalized_offset_slope * 10000;
|
||||||
temp_comp.time_constant = time_constant;
|
temp_comp.time_constant = time_constant;
|
||||||
this->temperature_compensation_ = temp_comp;
|
temperature_compensation_ = temp_comp;
|
||||||
}
|
}
|
||||||
bool start_fan_cleaning();
|
bool start_fan_cleaning();
|
||||||
|
|
||||||
@@ -102,12 +107,10 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
|||||||
bool write_tuning_parameters_(uint16_t i2c_command, const GasTuning &tuning);
|
bool write_tuning_parameters_(uint16_t i2c_command, const GasTuning &tuning);
|
||||||
bool write_temperature_compensation_(const TemperatureCompensation &compensation);
|
bool write_temperature_compensation_(const TemperatureCompensation &compensation);
|
||||||
|
|
||||||
char serial_number_[17] = "UNKNOWN";
|
uint32_t seconds_since_last_store_;
|
||||||
uint16_t voc_baseline_state_[4]{0};
|
|
||||||
uint32_t voc_baseline_time_;
|
|
||||||
uint16_t firmware_version_;
|
uint16_t firmware_version_;
|
||||||
Sen5xType type_{Sen5xType::UNKNOWN};
|
|
||||||
ERRORCODE error_code_;
|
ERRORCODE error_code_;
|
||||||
|
uint8_t serial_number_[4];
|
||||||
bool initialized_{false};
|
bool initialized_{false};
|
||||||
bool store_baseline_;
|
bool store_baseline_;
|
||||||
|
|
||||||
@@ -128,6 +131,8 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
|||||||
optional<GasTuning> nox_tuning_params_;
|
optional<GasTuning> nox_tuning_params_;
|
||||||
optional<TemperatureCompensation> temperature_compensation_;
|
optional<TemperatureCompensation> temperature_compensation_;
|
||||||
ESPPreferenceObject pref_;
|
ESPPreferenceObject pref_;
|
||||||
|
std::string product_name_;
|
||||||
|
Sen5xBaselines voc_baselines_storage_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sen5x
|
} // namespace sen5x
|
||||||
|
|||||||
@@ -210,7 +210,6 @@ SENSOR_MAP = {
|
|||||||
SETTING_MAP = {
|
SETTING_MAP = {
|
||||||
CONF_AUTO_CLEANING_INTERVAL: "set_auto_cleaning_interval",
|
CONF_AUTO_CLEANING_INTERVAL: "set_auto_cleaning_interval",
|
||||||
CONF_ACCELERATION_MODE: "set_acceleration_mode",
|
CONF_ACCELERATION_MODE: "set_acceleration_mode",
|
||||||
CONF_STORE_BASELINE: "set_store_baseline",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,23 +39,42 @@ bool SensirionI2CDevice::read_data(uint16_t *data, const uint8_t len) {
|
|||||||
*/
|
*/
|
||||||
bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data,
|
bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data,
|
||||||
const uint8_t data_len) {
|
const uint8_t data_len) {
|
||||||
|
uint8_t temp_stack[BUFFER_STACK_SIZE];
|
||||||
|
std::unique_ptr<uint8_t[]> temp_heap;
|
||||||
|
uint8_t *temp;
|
||||||
size_t required_buffer_len = data_len * 3 + 2;
|
size_t required_buffer_len = data_len * 3 + 2;
|
||||||
SmallBufferWithHeapFallback<BUFFER_STACK_SIZE> buffer(required_buffer_len);
|
|
||||||
uint8_t *temp = buffer.get();
|
// Is a dynamic allocation required ?
|
||||||
|
if (required_buffer_len >= BUFFER_STACK_SIZE) {
|
||||||
|
temp_heap = std::unique_ptr<uint8_t[]>(new uint8_t[required_buffer_len]);
|
||||||
|
temp = temp_heap.get();
|
||||||
|
} else {
|
||||||
|
temp = temp_stack;
|
||||||
|
}
|
||||||
// First byte or word is the command
|
// First byte or word is the command
|
||||||
uint8_t raw_idx = 0;
|
uint8_t raw_idx = 0;
|
||||||
if (command_len == 1) {
|
if (command_len == 1) {
|
||||||
temp[raw_idx++] = command & 0xFF;
|
temp[raw_idx++] = command & 0xFF;
|
||||||
} else {
|
} else {
|
||||||
// command is 2 bytes
|
// command is 2 bytes
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
temp[raw_idx++] = command >> 8;
|
temp[raw_idx++] = command >> 8;
|
||||||
temp[raw_idx++] = command & 0xFF;
|
temp[raw_idx++] = command & 0xFF;
|
||||||
|
#else
|
||||||
|
temp[raw_idx++] = command & 0xFF;
|
||||||
|
temp[raw_idx++] = command >> 8;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// add parameters followed by crc
|
// add parameters followed by crc
|
||||||
// skipped if len == 0
|
// skipped if len == 0
|
||||||
for (size_t i = 0; i < data_len; i++) {
|
for (size_t i = 0; i < data_len; i++) {
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
temp[raw_idx++] = data[i] >> 8;
|
temp[raw_idx++] = data[i] >> 8;
|
||||||
temp[raw_idx++] = data[i] & 0xFF;
|
temp[raw_idx++] = data[i] & 0xFF;
|
||||||
|
#else
|
||||||
|
temp[raw_idx++] = data[i] & 0xFF;
|
||||||
|
temp[raw_idx++] = data[i] >> 8;
|
||||||
|
#endif
|
||||||
// Use MSB first since Sensirion devices use CRC-8 with MSB first
|
// Use MSB first since Sensirion devices use CRC-8 with MSB first
|
||||||
uint8_t crc = crc8(&temp[raw_idx - 2], 2, 0xFF, CRC_POLYNOMIAL, true);
|
uint8_t crc = crc8(&temp[raw_idx - 2], 2, 0xFF, CRC_POLYNOMIAL, true);
|
||||||
temp[raw_idx++] = crc;
|
temp[raw_idx++] = crc;
|
||||||
|
|||||||
@@ -445,18 +445,22 @@ optional<float> CalibratePolynomialFilter::new_value(float value) {
|
|||||||
ClampFilter::ClampFilter(float min, float max, bool ignore_out_of_range)
|
ClampFilter::ClampFilter(float min, float max, bool ignore_out_of_range)
|
||||||
: min_(min), max_(max), ignore_out_of_range_(ignore_out_of_range) {}
|
: min_(min), max_(max), ignore_out_of_range_(ignore_out_of_range) {}
|
||||||
optional<float> ClampFilter::new_value(float value) {
|
optional<float> ClampFilter::new_value(float value) {
|
||||||
if (std::isfinite(this->min_) && !(value >= this->min_)) {
|
if (std::isfinite(value)) {
|
||||||
if (this->ignore_out_of_range_) {
|
if (std::isfinite(this->min_) && value < this->min_) {
|
||||||
return {};
|
if (this->ignore_out_of_range_) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
return this->min_;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this->min_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::isfinite(this->max_) && !(value <= this->max_)) {
|
if (std::isfinite(this->max_) && value > this->max_) {
|
||||||
if (this->ignore_out_of_range_) {
|
if (this->ignore_out_of_range_) {
|
||||||
return {};
|
return {};
|
||||||
|
} else {
|
||||||
|
return this->max_;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this->max_;
|
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "slow_pwm_output.h"
|
#include "slow_pwm_output.h"
|
||||||
#include "esphome/core/application.h"
|
|
||||||
#include "esphome/core/gpio.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace slow_pwm {
|
namespace slow_pwm {
|
||||||
@@ -21,9 +20,7 @@ void SlowPWMOutput::set_output_state_(bool new_state) {
|
|||||||
}
|
}
|
||||||
if (new_state != current_state_) {
|
if (new_state != current_state_) {
|
||||||
if (this->pin_) {
|
if (this->pin_) {
|
||||||
char pin_summary[GPIO_SUMMARY_MAX_LEN];
|
ESP_LOGV(TAG, "Switching output pin %s to %s", this->pin_->dump_summary().c_str(), ONOFF(new_state));
|
||||||
this->pin_->dump_summary(pin_summary, sizeof(pin_summary));
|
|
||||||
ESP_LOGV(TAG, "Switching output pin %s to %s", pin_summary, ONOFF(new_state));
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Switching to %s", ONOFF(new_state));
|
ESP_LOGV(TAG, "Switching to %s", ONOFF(new_state));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||||
#include "st7701s.h"
|
#include "st7701s.h"
|
||||||
#include "esphome/core/gpio.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -184,11 +183,8 @@ void ST7701S::dump_config() {
|
|||||||
LOG_PIN(" DE Pin: ", this->de_pin_);
|
LOG_PIN(" DE Pin: ", this->de_pin_);
|
||||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||||
char pin_summary[GPIO_SUMMARY_MAX_LEN];
|
for (size_t i = 0; i != data_pin_count; i++)
|
||||||
for (size_t i = 0; i != data_pin_count; i++) {
|
ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, (this->data_pins_[i])->dump_summary().c_str());
|
||||||
this->data_pins_[i]->dump_summary(pin_summary, sizeof(pin_summary));
|
|
||||||
ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, pin_summary);
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
|
ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components import i2c
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.const import CONF_ID
|
|
||||||
|
|
||||||
CODEOWNERS = ["@linkedupbits"]
|
|
||||||
DEPENDENCIES = ["i2c"]
|
|
||||||
MULTI_CONF = True
|
|
||||||
|
|
||||||
CONF_SY6970_ID = "sy6970_id"
|
|
||||||
CONF_ENABLE_STATUS_LED = "enable_status_led"
|
|
||||||
CONF_INPUT_CURRENT_LIMIT = "input_current_limit"
|
|
||||||
CONF_CHARGE_VOLTAGE = "charge_voltage"
|
|
||||||
CONF_CHARGE_CURRENT = "charge_current"
|
|
||||||
CONF_PRECHARGE_CURRENT = "precharge_current"
|
|
||||||
CONF_CHARGE_ENABLED = "charge_enabled"
|
|
||||||
CONF_ENABLE_ADC = "enable_adc"
|
|
||||||
|
|
||||||
sy6970_ns = cg.esphome_ns.namespace("sy6970")
|
|
||||||
SY6970Component = sy6970_ns.class_(
|
|
||||||
"SY6970Component", cg.PollingComponent, i2c.I2CDevice
|
|
||||||
)
|
|
||||||
SY6970Listener = sy6970_ns.class_("SY6970Listener")
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
|
||||||
cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(): cv.declare_id(SY6970Component),
|
|
||||||
cv.Optional(CONF_ENABLE_STATUS_LED, default=True): cv.boolean,
|
|
||||||
cv.Optional(CONF_INPUT_CURRENT_LIMIT, default=500): cv.int_range(
|
|
||||||
min=100, max=3200
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_CHARGE_VOLTAGE, default=4208): cv.int_range(
|
|
||||||
min=3840, max=4608
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_CHARGE_CURRENT, default=2048): cv.int_range(
|
|
||||||
min=0, max=5056
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_PRECHARGE_CURRENT, default=128): cv.int_range(
|
|
||||||
min=64, max=1024
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_CHARGE_ENABLED, default=True): cv.boolean,
|
|
||||||
cv.Optional(CONF_ENABLE_ADC, default=True): cv.boolean,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.extend(cv.polling_component_schema("5s"))
|
|
||||||
.extend(i2c.i2c_device_schema(0x6A))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
var = cg.new_Pvariable(
|
|
||||||
config[CONF_ID],
|
|
||||||
config[CONF_ENABLE_STATUS_LED],
|
|
||||||
config[CONF_INPUT_CURRENT_LIMIT],
|
|
||||||
config[CONF_CHARGE_VOLTAGE],
|
|
||||||
config[CONF_CHARGE_CURRENT],
|
|
||||||
config[CONF_PRECHARGE_CURRENT],
|
|
||||||
config[CONF_CHARGE_ENABLED],
|
|
||||||
config[CONF_ENABLE_ADC],
|
|
||||||
)
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
await i2c.register_i2c_device(var, config)
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components import binary_sensor
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.const import DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_POWER
|
|
||||||
|
|
||||||
from .. import CONF_SY6970_ID, SY6970Component, sy6970_ns
|
|
||||||
|
|
||||||
DEPENDENCIES = ["sy6970"]
|
|
||||||
|
|
||||||
CONF_VBUS_CONNECTED = "vbus_connected"
|
|
||||||
CONF_CHARGING = "charging"
|
|
||||||
CONF_CHARGE_DONE = "charge_done"
|
|
||||||
|
|
||||||
SY6970VbusConnectedBinarySensor = sy6970_ns.class_(
|
|
||||||
"SY6970VbusConnectedBinarySensor", binary_sensor.BinarySensor
|
|
||||||
)
|
|
||||||
SY6970ChargingBinarySensor = sy6970_ns.class_(
|
|
||||||
"SY6970ChargingBinarySensor", binary_sensor.BinarySensor
|
|
||||||
)
|
|
||||||
SY6970ChargeDoneBinarySensor = sy6970_ns.class_(
|
|
||||||
"SY6970ChargeDoneBinarySensor", binary_sensor.BinarySensor
|
|
||||||
)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(CONF_SY6970_ID): cv.use_id(SY6970Component),
|
|
||||||
cv.Optional(CONF_VBUS_CONNECTED): binary_sensor.binary_sensor_schema(
|
|
||||||
SY6970VbusConnectedBinarySensor,
|
|
||||||
device_class=DEVICE_CLASS_CONNECTIVITY,
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_CHARGING): binary_sensor.binary_sensor_schema(
|
|
||||||
SY6970ChargingBinarySensor,
|
|
||||||
device_class=DEVICE_CLASS_POWER,
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_CHARGE_DONE): binary_sensor.binary_sensor_schema(
|
|
||||||
SY6970ChargeDoneBinarySensor,
|
|
||||||
device_class=DEVICE_CLASS_POWER,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
parent = await cg.get_variable(config[CONF_SY6970_ID])
|
|
||||||
|
|
||||||
if vbus_connected_config := config.get(CONF_VBUS_CONNECTED):
|
|
||||||
sens = await binary_sensor.new_binary_sensor(vbus_connected_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
|
|
||||||
if charging_config := config.get(CONF_CHARGING):
|
|
||||||
sens = await binary_sensor.new_binary_sensor(charging_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
|
|
||||||
if charge_done_config := config.get(CONF_CHARGE_DONE):
|
|
||||||
sens = await binary_sensor.new_binary_sensor(charge_done_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../sy6970.h"
|
|
||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
|
||||||
|
|
||||||
namespace esphome::sy6970 {
|
|
||||||
|
|
||||||
template<uint8_t REG, uint8_t SHIFT, uint8_t MASK, uint8_t TRUE_VALUE>
|
|
||||||
class StatusBinarySensor : public SY6970Listener, public binary_sensor::BinarySensor {
|
|
||||||
public:
|
|
||||||
void on_data(const SY6970Data &data) override {
|
|
||||||
uint8_t value = (data.registers[REG] >> SHIFT) & MASK;
|
|
||||||
this->publish_state(value == TRUE_VALUE);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<uint8_t REG, uint8_t SHIFT, uint8_t MASK, uint8_t FALSE_VALUE>
|
|
||||||
class InverseStatusBinarySensor : public SY6970Listener, public binary_sensor::BinarySensor {
|
|
||||||
public:
|
|
||||||
void on_data(const SY6970Data &data) override {
|
|
||||||
uint8_t value = (data.registers[REG] >> SHIFT) & MASK;
|
|
||||||
this->publish_state(value != FALSE_VALUE);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Custom binary sensor for charging (true when pre-charge or fast charge)
|
|
||||||
class SY6970ChargingBinarySensor : public SY6970Listener, public binary_sensor::BinarySensor {
|
|
||||||
public:
|
|
||||||
void on_data(const SY6970Data &data) override {
|
|
||||||
uint8_t chrg_stat = (data.registers[SY6970_REG_STATUS] >> 3) & 0x03;
|
|
||||||
bool charging = chrg_stat != CHARGE_STATUS_NOT_CHARGING && chrg_stat != CHARGE_STATUS_CHARGE_DONE;
|
|
||||||
this->publish_state(charging);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Specialized sensor types using templates
|
|
||||||
// VBUS connected: BUS_STATUS != NO_INPUT
|
|
||||||
using SY6970VbusConnectedBinarySensor = InverseStatusBinarySensor<SY6970_REG_STATUS, 5, 0x07, BUS_STATUS_NO_INPUT>;
|
|
||||||
|
|
||||||
// Charge done: CHARGE_STATUS == CHARGE_DONE
|
|
||||||
using SY6970ChargeDoneBinarySensor = StatusBinarySensor<SY6970_REG_STATUS, 3, 0x03, CHARGE_STATUS_CHARGE_DONE>;
|
|
||||||
|
|
||||||
} // namespace esphome::sy6970
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components import sensor
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.const import (
|
|
||||||
CONF_BATTERY_VOLTAGE,
|
|
||||||
DEVICE_CLASS_CURRENT,
|
|
||||||
DEVICE_CLASS_VOLTAGE,
|
|
||||||
STATE_CLASS_MEASUREMENT,
|
|
||||||
UNIT_MILLIAMP,
|
|
||||||
UNIT_VOLT,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .. import CONF_SY6970_ID, SY6970Component, sy6970_ns
|
|
||||||
|
|
||||||
DEPENDENCIES = ["sy6970"]
|
|
||||||
|
|
||||||
CONF_VBUS_VOLTAGE = "vbus_voltage"
|
|
||||||
CONF_SYSTEM_VOLTAGE = "system_voltage"
|
|
||||||
CONF_CHARGE_CURRENT = "charge_current"
|
|
||||||
CONF_PRECHARGE_CURRENT = "precharge_current"
|
|
||||||
|
|
||||||
SY6970VbusVoltageSensor = sy6970_ns.class_("SY6970VbusVoltageSensor", sensor.Sensor)
|
|
||||||
SY6970BatteryVoltageSensor = sy6970_ns.class_(
|
|
||||||
"SY6970BatteryVoltageSensor", sensor.Sensor
|
|
||||||
)
|
|
||||||
SY6970SystemVoltageSensor = sy6970_ns.class_("SY6970SystemVoltageSensor", sensor.Sensor)
|
|
||||||
SY6970ChargeCurrentSensor = sy6970_ns.class_("SY6970ChargeCurrentSensor", sensor.Sensor)
|
|
||||||
SY6970PrechargeCurrentSensor = sy6970_ns.class_(
|
|
||||||
"SY6970PrechargeCurrentSensor", sensor.Sensor
|
|
||||||
)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(CONF_SY6970_ID): cv.use_id(SY6970Component),
|
|
||||||
cv.Optional(CONF_VBUS_VOLTAGE): sensor.sensor_schema(
|
|
||||||
SY6970VbusVoltageSensor,
|
|
||||||
unit_of_measurement=UNIT_VOLT,
|
|
||||||
accuracy_decimals=2,
|
|
||||||
device_class=DEVICE_CLASS_VOLTAGE,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
|
||||||
SY6970BatteryVoltageSensor,
|
|
||||||
unit_of_measurement=UNIT_VOLT,
|
|
||||||
accuracy_decimals=2,
|
|
||||||
device_class=DEVICE_CLASS_VOLTAGE,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_SYSTEM_VOLTAGE): sensor.sensor_schema(
|
|
||||||
SY6970SystemVoltageSensor,
|
|
||||||
unit_of_measurement=UNIT_VOLT,
|
|
||||||
accuracy_decimals=2,
|
|
||||||
device_class=DEVICE_CLASS_VOLTAGE,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_CHARGE_CURRENT): sensor.sensor_schema(
|
|
||||||
SY6970ChargeCurrentSensor,
|
|
||||||
unit_of_measurement=UNIT_MILLIAMP,
|
|
||||||
accuracy_decimals=0,
|
|
||||||
device_class=DEVICE_CLASS_CURRENT,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_PRECHARGE_CURRENT): sensor.sensor_schema(
|
|
||||||
SY6970PrechargeCurrentSensor,
|
|
||||||
unit_of_measurement=UNIT_MILLIAMP,
|
|
||||||
accuracy_decimals=0,
|
|
||||||
device_class=DEVICE_CLASS_CURRENT,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
parent = await cg.get_variable(config[CONF_SY6970_ID])
|
|
||||||
|
|
||||||
if vbus_voltage_config := config.get(CONF_VBUS_VOLTAGE):
|
|
||||||
sens = await sensor.new_sensor(vbus_voltage_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
|
|
||||||
if battery_voltage_config := config.get(CONF_BATTERY_VOLTAGE):
|
|
||||||
sens = await sensor.new_sensor(battery_voltage_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
|
|
||||||
if system_voltage_config := config.get(CONF_SYSTEM_VOLTAGE):
|
|
||||||
sens = await sensor.new_sensor(system_voltage_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
|
|
||||||
if charge_current_config := config.get(CONF_CHARGE_CURRENT):
|
|
||||||
sens = await sensor.new_sensor(charge_current_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
|
|
||||||
if precharge_current_config := config.get(CONF_PRECHARGE_CURRENT):
|
|
||||||
sens = await sensor.new_sensor(precharge_current_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../sy6970.h"
|
|
||||||
#include "esphome/components/sensor/sensor.h"
|
|
||||||
|
|
||||||
namespace esphome::sy6970 {
|
|
||||||
|
|
||||||
// Template for voltage sensors (converts mV to V)
|
|
||||||
template<uint8_t REG, uint8_t MASK, uint16_t BASE, uint16_t STEP>
|
|
||||||
class VoltageSensor : public SY6970Listener, public sensor::Sensor {
|
|
||||||
public:
|
|
||||||
void on_data(const SY6970Data &data) override {
|
|
||||||
uint8_t val = data.registers[REG] & MASK;
|
|
||||||
uint16_t voltage_mv = BASE + (val * STEP);
|
|
||||||
this->publish_state(voltage_mv * 0.001f); // Convert mV to V
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Template for current sensors (returns mA)
|
|
||||||
template<uint8_t REG, uint8_t MASK, uint16_t BASE, uint16_t STEP>
|
|
||||||
class CurrentSensor : public SY6970Listener, public sensor::Sensor {
|
|
||||||
public:
|
|
||||||
void on_data(const SY6970Data &data) override {
|
|
||||||
uint8_t val = data.registers[REG] & MASK;
|
|
||||||
uint16_t current_ma = BASE + (val * STEP);
|
|
||||||
this->publish_state(current_ma);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Specialized sensor types using templates
|
|
||||||
using SY6970VbusVoltageSensor = VoltageSensor<SY6970_REG_VBUS_VOLTAGE, 0x7F, VBUS_BASE_MV, VBUS_STEP_MV>;
|
|
||||||
using SY6970BatteryVoltageSensor = VoltageSensor<SY6970_REG_BATV, 0x7F, VBAT_BASE_MV, VBAT_STEP_MV>;
|
|
||||||
using SY6970SystemVoltageSensor = VoltageSensor<SY6970_REG_VINDPM_STATUS, 0x7F, VSYS_BASE_MV, VSYS_STEP_MV>;
|
|
||||||
using SY6970ChargeCurrentSensor = CurrentSensor<SY6970_REG_CHARGE_CURRENT_MONITOR, 0x7F, 0, CHG_CURRENT_STEP_MA>;
|
|
||||||
|
|
||||||
// Precharge current sensor needs special handling (bit shift)
|
|
||||||
class SY6970PrechargeCurrentSensor : public SY6970Listener, public sensor::Sensor {
|
|
||||||
public:
|
|
||||||
void on_data(const SY6970Data &data) override {
|
|
||||||
uint8_t iprechg = (data.registers[SY6970_REG_PRECHARGE_CURRENT] >> 4) & 0x0F;
|
|
||||||
uint16_t iprechg_ma = PRE_CHG_BASE_MA + (iprechg * PRE_CHG_STEP_MA);
|
|
||||||
this->publish_state(iprechg_ma);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::sy6970
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
#include "sy6970.h"
|
|
||||||
#include "esphome/core/hal.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
namespace esphome::sy6970 {
|
|
||||||
|
|
||||||
static const char *const TAG = "sy6970";
|
|
||||||
|
|
||||||
bool SY6970Component::read_all_registers_() {
|
|
||||||
// Read all registers from 0x00 to 0x14 in one transaction (21 bytes)
|
|
||||||
// This includes unused registers 0x0F, 0x10 for performance
|
|
||||||
if (!this->read_bytes(SY6970_REG_INPUT_CURRENT_LIMIT, this->data_.registers, 21)) {
|
|
||||||
ESP_LOGW(TAG, "Failed to read registers 0x00-0x14");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SY6970Component::write_register_(uint8_t reg, uint8_t value) {
|
|
||||||
if (!this->write_byte(reg, value)) {
|
|
||||||
ESP_LOGW(TAG, "Failed to write register 0x%02X", reg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SY6970Component::update_register_(uint8_t reg, uint8_t mask, uint8_t value) {
|
|
||||||
uint8_t reg_value;
|
|
||||||
if (!this->read_byte(reg, ®_value)) {
|
|
||||||
ESP_LOGW(TAG, "Failed to read register 0x%02X for update", reg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
reg_value = (reg_value & ~mask) | (value & mask);
|
|
||||||
return this->write_register_(reg, reg_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::setup() {
|
|
||||||
ESP_LOGV(TAG, "Setting up SY6970...");
|
|
||||||
|
|
||||||
// Try to read chip ID
|
|
||||||
uint8_t reg_value;
|
|
||||||
if (!this->read_byte(SY6970_REG_DEVICE_ID, ®_value)) {
|
|
||||||
ESP_LOGE(TAG, "Failed to communicate with SY6970");
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t chip_id = reg_value & 0x03;
|
|
||||||
if (chip_id != 0x00) {
|
|
||||||
ESP_LOGW(TAG, "Unexpected chip ID: 0x%02X (expected 0x00)", chip_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply configuration options (all have defaults now)
|
|
||||||
ESP_LOGV(TAG, "Setting LED enabled to %s", ONOFF(this->led_enabled_));
|
|
||||||
this->set_led_enabled(this->led_enabled_);
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Setting input current limit to %u mA", this->input_current_limit_);
|
|
||||||
this->set_input_current_limit(this->input_current_limit_);
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Setting charge voltage to %u mV", this->charge_voltage_);
|
|
||||||
this->set_charge_target_voltage(this->charge_voltage_);
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Setting charge current to %u mA", this->charge_current_);
|
|
||||||
this->set_charge_current(this->charge_current_);
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Setting precharge current to %u mA", this->precharge_current_);
|
|
||||||
this->set_precharge_current(this->precharge_current_);
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Setting charge enabled to %s", ONOFF(this->charge_enabled_));
|
|
||||||
this->set_charge_enabled(this->charge_enabled_);
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Setting ADC measurements to %s", ONOFF(this->enable_adc_));
|
|
||||||
this->set_enable_adc_measure(this->enable_adc_);
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "SY6970 initialized successfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::dump_config() {
|
|
||||||
ESP_LOGCONFIG(TAG,
|
|
||||||
"SY6970:\n"
|
|
||||||
" LED Enabled: %s\n"
|
|
||||||
" Input Current Limit: %u mA\n"
|
|
||||||
" Charge Voltage: %u mV\n"
|
|
||||||
" Charge Current: %u mA\n"
|
|
||||||
" Precharge Current: %u mA\n"
|
|
||||||
" Charge Enabled: %s\n"
|
|
||||||
" ADC Enabled: %s",
|
|
||||||
ONOFF(this->led_enabled_), this->input_current_limit_, this->charge_voltage_, this->charge_current_,
|
|
||||||
this->precharge_current_, ONOFF(this->charge_enabled_), ONOFF(this->enable_adc_));
|
|
||||||
LOG_I2C_DEVICE(this);
|
|
||||||
LOG_UPDATE_INTERVAL(this);
|
|
||||||
if (this->is_failed()) {
|
|
||||||
ESP_LOGE(TAG, "Communication with SY6970 failed!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::update() {
|
|
||||||
if (this->is_failed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read all registers in one transaction
|
|
||||||
if (!this->read_all_registers_()) {
|
|
||||||
ESP_LOGW(TAG, "Failed to read registers during update");
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->status_clear_warning();
|
|
||||||
|
|
||||||
// Notify all listeners with the new data
|
|
||||||
for (auto *listener : this->listeners_) {
|
|
||||||
listener->on_data(this->data_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::set_input_current_limit(uint16_t milliamps) {
|
|
||||||
if (this->is_failed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (milliamps < INPUT_CURRENT_MIN) {
|
|
||||||
milliamps = INPUT_CURRENT_MIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t val = (milliamps - INPUT_CURRENT_MIN) / INPUT_CURRENT_STEP;
|
|
||||||
if (val > 0x3F) {
|
|
||||||
val = 0x3F;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->update_register_(SY6970_REG_INPUT_CURRENT_LIMIT, 0x3F, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::set_charge_target_voltage(uint16_t millivolts) {
|
|
||||||
if (this->is_failed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (millivolts < CHG_VOLTAGE_BASE) {
|
|
||||||
millivolts = CHG_VOLTAGE_BASE;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t val = (millivolts - CHG_VOLTAGE_BASE) / CHG_VOLTAGE_STEP;
|
|
||||||
if (val > 0x3F) {
|
|
||||||
val = 0x3F;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->update_register_(SY6970_REG_CHARGE_VOLTAGE, 0xFC, val << 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::set_precharge_current(uint16_t milliamps) {
|
|
||||||
if (this->is_failed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (milliamps < PRE_CHG_BASE_MA) {
|
|
||||||
milliamps = PRE_CHG_BASE_MA;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t val = (milliamps - PRE_CHG_BASE_MA) / PRE_CHG_STEP_MA;
|
|
||||||
if (val > 0x0F) {
|
|
||||||
val = 0x0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->update_register_(SY6970_REG_PRECHARGE_CURRENT, 0xF0, val << 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::set_charge_current(uint16_t milliamps) {
|
|
||||||
if (this->is_failed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
uint8_t val = milliamps / 64;
|
|
||||||
if (val > 0x7F) {
|
|
||||||
val = 0x7F;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->update_register_(SY6970_REG_CHARGE_CURRENT, 0x7F, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::set_charge_enabled(bool enabled) {
|
|
||||||
if (this->is_failed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->update_register_(SY6970_REG_SYS_CONTROL, 0x10, enabled ? 0x10 : 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::set_led_enabled(bool enabled) {
|
|
||||||
if (this->is_failed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Bit 6: 0 = LED enabled, 1 = LED disabled
|
|
||||||
this->update_register_(SY6970_REG_TIMER_CONTROL, 0x40, enabled ? 0x00 : 0x40);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SY6970Component::set_enable_adc_measure(bool enabled) {
|
|
||||||
if (this->is_failed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Set bits to enable ADC conversion
|
|
||||||
this->update_register_(SY6970_REG_ADC_CONTROL, 0xC0, enabled ? 0xC0 : 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::sy6970
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace esphome::sy6970 {
|
|
||||||
|
|
||||||
// SY6970 Register addresses with descriptive names
|
|
||||||
static const uint8_t SY6970_REG_INPUT_CURRENT_LIMIT = 0x00; // Input current limit control
|
|
||||||
static const uint8_t SY6970_REG_VINDPM = 0x01; // Input voltage limit
|
|
||||||
static const uint8_t SY6970_REG_ADC_CONTROL = 0x02; // ADC control and function disable
|
|
||||||
static const uint8_t SY6970_REG_SYS_CONTROL = 0x03; // Charge enable and system config
|
|
||||||
static const uint8_t SY6970_REG_CHARGE_CURRENT = 0x04; // Fast charge current limit
|
|
||||||
static const uint8_t SY6970_REG_PRECHARGE_CURRENT = 0x05; // Pre-charge/termination current
|
|
||||||
static const uint8_t SY6970_REG_CHARGE_VOLTAGE = 0x06; // Charge voltage limit
|
|
||||||
static const uint8_t SY6970_REG_TIMER_CONTROL = 0x07; // Charge timer and status LED control
|
|
||||||
static const uint8_t SY6970_REG_IR_COMP = 0x08; // IR compensation
|
|
||||||
static const uint8_t SY6970_REG_FORCE_DPDM = 0x09; // Force DPDM detection
|
|
||||||
static const uint8_t SY6970_REG_BOOST_CONTROL = 0x0A; // Boost mode voltage/current
|
|
||||||
static const uint8_t SY6970_REG_STATUS = 0x0B; // System status (bus, charge status)
|
|
||||||
static const uint8_t SY6970_REG_FAULT = 0x0C; // Fault status (NTC)
|
|
||||||
static const uint8_t SY6970_REG_VINDPM_STATUS = 0x0D; // Input voltage limit status (also sys voltage)
|
|
||||||
static const uint8_t SY6970_REG_BATV = 0x0E; // Battery voltage
|
|
||||||
static const uint8_t SY6970_REG_VBUS_VOLTAGE = 0x11; // VBUS voltage
|
|
||||||
static const uint8_t SY6970_REG_CHARGE_CURRENT_MONITOR = 0x12; // Charge current
|
|
||||||
static const uint8_t SY6970_REG_INPUT_VOLTAGE_LIMIT = 0x13; // Input voltage limit
|
|
||||||
static const uint8_t SY6970_REG_DEVICE_ID = 0x14; // Part information
|
|
||||||
|
|
||||||
// Constants for voltage and current calculations
|
|
||||||
static const uint16_t VBUS_BASE_MV = 2600; // mV
|
|
||||||
static const uint16_t VBUS_STEP_MV = 100; // mV
|
|
||||||
static const uint16_t VBAT_BASE_MV = 2304; // mV
|
|
||||||
static const uint16_t VBAT_STEP_MV = 20; // mV
|
|
||||||
static const uint16_t VSYS_BASE_MV = 2304; // mV
|
|
||||||
static const uint16_t VSYS_STEP_MV = 20; // mV
|
|
||||||
static const uint16_t CHG_CURRENT_STEP_MA = 50; // mA
|
|
||||||
static const uint16_t PRE_CHG_BASE_MA = 64; // mA
|
|
||||||
static const uint16_t PRE_CHG_STEP_MA = 64; // mA
|
|
||||||
static const uint16_t CHG_VOLTAGE_BASE = 3840; // mV
|
|
||||||
static const uint16_t CHG_VOLTAGE_STEP = 16; // mV
|
|
||||||
static const uint16_t INPUT_CURRENT_MIN = 100; // mA
|
|
||||||
static const uint16_t INPUT_CURRENT_STEP = 50; // mA
|
|
||||||
|
|
||||||
// Bus Status values (REG_0B[7:5])
|
|
||||||
enum BusStatus {
|
|
||||||
BUS_STATUS_NO_INPUT = 0,
|
|
||||||
BUS_STATUS_USB_SDP = 1,
|
|
||||||
BUS_STATUS_USB_CDP = 2,
|
|
||||||
BUS_STATUS_USB_DCP = 3,
|
|
||||||
BUS_STATUS_HVDCP = 4,
|
|
||||||
BUS_STATUS_ADAPTER = 5,
|
|
||||||
BUS_STATUS_NO_STD_ADAPTER = 6,
|
|
||||||
BUS_STATUS_OTG = 7,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Charge Status values (REG_0B[4:3])
|
|
||||||
enum ChargeStatus {
|
|
||||||
CHARGE_STATUS_NOT_CHARGING = 0,
|
|
||||||
CHARGE_STATUS_PRE_CHARGE = 1,
|
|
||||||
CHARGE_STATUS_FAST_CHARGE = 2,
|
|
||||||
CHARGE_STATUS_CHARGE_DONE = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Structure to hold all register data read in one transaction
|
|
||||||
struct SY6970Data {
|
|
||||||
uint8_t registers[21]; // Registers 0x00-0x14 (includes unused 0x0F, 0x10)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Listener interface for components that want to receive SY6970 data updates
|
|
||||||
class SY6970Listener {
|
|
||||||
public:
|
|
||||||
virtual void on_data(const SY6970Data &data) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SY6970Component : public PollingComponent, public i2c::I2CDevice {
|
|
||||||
public:
|
|
||||||
SY6970Component(bool led_enabled, uint16_t input_current_limit, uint16_t charge_voltage, uint16_t charge_current,
|
|
||||||
uint16_t precharge_current, bool charge_enabled, bool enable_adc)
|
|
||||||
: led_enabled_(led_enabled),
|
|
||||||
input_current_limit_(input_current_limit),
|
|
||||||
charge_voltage_(charge_voltage),
|
|
||||||
charge_current_(charge_current),
|
|
||||||
precharge_current_(precharge_current),
|
|
||||||
charge_enabled_(charge_enabled),
|
|
||||||
enable_adc_(enable_adc) {}
|
|
||||||
void setup() override;
|
|
||||||
void dump_config() override;
|
|
||||||
void update() override;
|
|
||||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
|
||||||
|
|
||||||
// Listener registration
|
|
||||||
void add_listener(SY6970Listener *listener) { this->listeners_.push_back(listener); }
|
|
||||||
|
|
||||||
// Configuration methods to be called from lambdas
|
|
||||||
void set_input_current_limit(uint16_t milliamps);
|
|
||||||
void set_charge_target_voltage(uint16_t millivolts);
|
|
||||||
void set_precharge_current(uint16_t milliamps);
|
|
||||||
void set_charge_current(uint16_t milliamps);
|
|
||||||
void set_charge_enabled(bool enabled);
|
|
||||||
void set_led_enabled(bool enabled);
|
|
||||||
void set_enable_adc_measure(bool enabled = true);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool read_all_registers_();
|
|
||||||
bool write_register_(uint8_t reg, uint8_t value);
|
|
||||||
bool update_register_(uint8_t reg, uint8_t mask, uint8_t value);
|
|
||||||
|
|
||||||
SY6970Data data_{};
|
|
||||||
std::vector<SY6970Listener *> listeners_;
|
|
||||||
|
|
||||||
// Configuration values to set during setup()
|
|
||||||
bool led_enabled_;
|
|
||||||
uint16_t input_current_limit_;
|
|
||||||
uint16_t charge_voltage_;
|
|
||||||
uint16_t charge_current_;
|
|
||||||
uint16_t precharge_current_;
|
|
||||||
bool charge_enabled_;
|
|
||||||
bool enable_adc_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::sy6970
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components import text_sensor
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
|
|
||||||
from .. import CONF_SY6970_ID, SY6970Component, sy6970_ns
|
|
||||||
|
|
||||||
DEPENDENCIES = ["sy6970"]
|
|
||||||
|
|
||||||
CONF_BUS_STATUS = "bus_status"
|
|
||||||
CONF_CHARGE_STATUS = "charge_status"
|
|
||||||
CONF_NTC_STATUS = "ntc_status"
|
|
||||||
|
|
||||||
SY6970BusStatusTextSensor = sy6970_ns.class_(
|
|
||||||
"SY6970BusStatusTextSensor", text_sensor.TextSensor
|
|
||||||
)
|
|
||||||
SY6970ChargeStatusTextSensor = sy6970_ns.class_(
|
|
||||||
"SY6970ChargeStatusTextSensor", text_sensor.TextSensor
|
|
||||||
)
|
|
||||||
SY6970NtcStatusTextSensor = sy6970_ns.class_(
|
|
||||||
"SY6970NtcStatusTextSensor", text_sensor.TextSensor
|
|
||||||
)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(CONF_SY6970_ID): cv.use_id(SY6970Component),
|
|
||||||
cv.Optional(CONF_BUS_STATUS): text_sensor.text_sensor_schema(
|
|
||||||
SY6970BusStatusTextSensor
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_CHARGE_STATUS): text_sensor.text_sensor_schema(
|
|
||||||
SY6970ChargeStatusTextSensor
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_NTC_STATUS): text_sensor.text_sensor_schema(
|
|
||||||
SY6970NtcStatusTextSensor
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
parent = await cg.get_variable(config[CONF_SY6970_ID])
|
|
||||||
|
|
||||||
if bus_status_config := config.get(CONF_BUS_STATUS):
|
|
||||||
sens = await text_sensor.new_text_sensor(bus_status_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
|
|
||||||
if charge_status_config := config.get(CONF_CHARGE_STATUS):
|
|
||||||
sens = await text_sensor.new_text_sensor(charge_status_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
|
|
||||||
if ntc_status_config := config.get(CONF_NTC_STATUS):
|
|
||||||
sens = await text_sensor.new_text_sensor(ntc_status_config)
|
|
||||||
cg.add(parent.add_listener(sens))
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../sy6970.h"
|
|
||||||
#include "esphome/components/text_sensor/text_sensor.h"
|
|
||||||
|
|
||||||
namespace esphome::sy6970 {
|
|
||||||
|
|
||||||
// Bus status text sensor
|
|
||||||
class SY6970BusStatusTextSensor : public SY6970Listener, public text_sensor::TextSensor {
|
|
||||||
public:
|
|
||||||
void on_data(const SY6970Data &data) override {
|
|
||||||
uint8_t status = (data.registers[SY6970_REG_STATUS] >> 5) & 0x07;
|
|
||||||
const char *status_str = this->get_bus_status_string_(status);
|
|
||||||
this->publish_state(status_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
const char *get_bus_status_string_(uint8_t status) {
|
|
||||||
switch (status) {
|
|
||||||
case BUS_STATUS_NO_INPUT:
|
|
||||||
return "No Input";
|
|
||||||
case BUS_STATUS_USB_SDP:
|
|
||||||
return "USB SDP";
|
|
||||||
case BUS_STATUS_USB_CDP:
|
|
||||||
return "USB CDP";
|
|
||||||
case BUS_STATUS_USB_DCP:
|
|
||||||
return "USB DCP";
|
|
||||||
case BUS_STATUS_HVDCP:
|
|
||||||
return "HVDCP";
|
|
||||||
case BUS_STATUS_ADAPTER:
|
|
||||||
return "Adapter";
|
|
||||||
case BUS_STATUS_NO_STD_ADAPTER:
|
|
||||||
return "Non-Standard Adapter";
|
|
||||||
case BUS_STATUS_OTG:
|
|
||||||
return "OTG";
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Charge status text sensor
|
|
||||||
class SY6970ChargeStatusTextSensor : public SY6970Listener, public text_sensor::TextSensor {
|
|
||||||
public:
|
|
||||||
void on_data(const SY6970Data &data) override {
|
|
||||||
uint8_t status = (data.registers[SY6970_REG_STATUS] >> 3) & 0x03;
|
|
||||||
const char *status_str = this->get_charge_status_string_(status);
|
|
||||||
this->publish_state(status_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
const char *get_charge_status_string_(uint8_t status) {
|
|
||||||
switch (status) {
|
|
||||||
case CHARGE_STATUS_NOT_CHARGING:
|
|
||||||
return "Not Charging";
|
|
||||||
case CHARGE_STATUS_PRE_CHARGE:
|
|
||||||
return "Pre-charge";
|
|
||||||
case CHARGE_STATUS_FAST_CHARGE:
|
|
||||||
return "Fast Charge";
|
|
||||||
case CHARGE_STATUS_CHARGE_DONE:
|
|
||||||
return "Charge Done";
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// NTC status text sensor
|
|
||||||
class SY6970NtcStatusTextSensor : public SY6970Listener, public text_sensor::TextSensor {
|
|
||||||
public:
|
|
||||||
void on_data(const SY6970Data &data) override {
|
|
||||||
uint8_t status = data.registers[SY6970_REG_FAULT] & 0x07;
|
|
||||||
const char *status_str = this->get_ntc_status_string_(status);
|
|
||||||
this->publish_state(status_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
const char *get_ntc_status_string_(uint8_t status) {
|
|
||||||
switch (status) {
|
|
||||||
case 0:
|
|
||||||
return "Normal";
|
|
||||||
case 2:
|
|
||||||
return "Warm";
|
|
||||||
case 3:
|
|
||||||
return "Cool";
|
|
||||||
case 5:
|
|
||||||
return "Cold";
|
|
||||||
case 6:
|
|
||||||
return "Hot";
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::sy6970
|
|
||||||
@@ -20,7 +20,7 @@ from .. import template_ns
|
|||||||
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
||||||
|
|
||||||
TemplateWaterHeater = template_ns.class_(
|
TemplateWaterHeater = template_ns.class_(
|
||||||
"TemplateWaterHeater", cg.Component, water_heater.WaterHeater
|
"TemplateWaterHeater", water_heater.WaterHeater
|
||||||
)
|
)
|
||||||
|
|
||||||
TemplateWaterHeaterPublishAction = template_ns.class_(
|
TemplateWaterHeaterPublishAction = template_ns.class_(
|
||||||
@@ -36,29 +36,24 @@ RESTORE_MODES = {
|
|||||||
"RESTORE_AND_CALL": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE_AND_CALL,
|
"RESTORE_AND_CALL": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE_AND_CALL,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = water_heater.water_heater_schema(TemplateWaterHeater).extend(
|
||||||
water_heater.water_heater_schema(TemplateWaterHeater)
|
{
|
||||||
.extend(
|
cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean,
|
||||||
{
|
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
||||||
cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean,
|
cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum(
|
||||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
RESTORE_MODES, upper=True
|
||||||
cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum(
|
),
|
||||||
RESTORE_MODES, upper=True
|
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.returning_lambda,
|
||||||
),
|
cv.Optional(CONF_MODE): cv.returning_lambda,
|
||||||
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.returning_lambda,
|
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
||||||
cv.Optional(CONF_MODE): cv.returning_lambda,
|
water_heater.validate_water_heater_mode
|
||||||
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
),
|
||||||
water_heater.validate_water_heater_mode
|
}
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
|
||||||
await water_heater.register_water_heater(var, config)
|
await water_heater.register_water_heater(var, config)
|
||||||
|
|
||||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {}
|
|||||||
void TemplateWaterHeater::setup() {
|
void TemplateWaterHeater::setup() {
|
||||||
if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE ||
|
if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE ||
|
||||||
this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE_AND_CALL) {
|
this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE_AND_CALL) {
|
||||||
auto restore = this->restore_state_();
|
auto restore = this->restore_state();
|
||||||
|
|
||||||
if (restore.has_value()) {
|
if (restore.has_value()) {
|
||||||
restore->perform();
|
restore->perform();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ enum TemplateWaterHeaterRestoreMode {
|
|||||||
WATER_HEATER_RESTORE_AND_CALL,
|
WATER_HEATER_RESTORE_AND_CALL,
|
||||||
};
|
};
|
||||||
|
|
||||||
class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
|
class TemplateWaterHeater : public water_heater::WaterHeater {
|
||||||
public:
|
public:
|
||||||
TemplateWaterHeater();
|
TemplateWaterHeater();
|
||||||
|
|
||||||
|
|||||||
@@ -1060,11 +1060,11 @@ bool ThermostatClimate::cooling_required_() {
|
|||||||
auto temperature = this->supports_two_points_ ? this->target_temperature_high : this->target_temperature;
|
auto temperature = this->supports_two_points_ ? this->target_temperature_high : this->target_temperature;
|
||||||
|
|
||||||
if (this->supports_cool_) {
|
if (this->supports_cool_) {
|
||||||
if (this->current_temperature >= temperature + this->cooling_deadband_) {
|
if (this->current_temperature > temperature + this->cooling_deadband_) {
|
||||||
// if the current temperature reaches or exceeds the target + deadband, cooling is required
|
// if the current temperature exceeds the target + deadband, cooling is required
|
||||||
return true;
|
return true;
|
||||||
} else if (this->current_temperature <= temperature - this->cooling_overrun_) {
|
} else if (this->current_temperature < temperature - this->cooling_overrun_) {
|
||||||
// if the current temperature is less than or equal to the target - overrun, cooling should stop
|
// if the current temperature is less than the target - overrun, cooling should stop
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// if we get here, the current temperature is between target + deadband and target - overrun,
|
// if we get here, the current temperature is between target + deadband and target - overrun,
|
||||||
@@ -1081,11 +1081,11 @@ bool ThermostatClimate::fanning_required_() {
|
|||||||
|
|
||||||
if (this->supports_fan_only_) {
|
if (this->supports_fan_only_) {
|
||||||
if (this->supports_fan_only_cooling_) {
|
if (this->supports_fan_only_cooling_) {
|
||||||
if (this->current_temperature >= temperature + this->cooling_deadband_) {
|
if (this->current_temperature > temperature + this->cooling_deadband_) {
|
||||||
// if the current temperature reaches or exceeds the target + deadband, fanning is required
|
// if the current temperature exceeds the target + deadband, fanning is required
|
||||||
return true;
|
return true;
|
||||||
} else if (this->current_temperature <= temperature - this->cooling_overrun_) {
|
} else if (this->current_temperature < temperature - this->cooling_overrun_) {
|
||||||
// if the current temperature is less than or equal to the target - overrun, fanning should stop
|
// if the current temperature is less than the target - overrun, fanning should stop
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// if we get here, the current temperature is between target + deadband and target - overrun,
|
// if we get here, the current temperature is between target + deadband and target - overrun,
|
||||||
@@ -1103,12 +1103,11 @@ bool ThermostatClimate::heating_required_() {
|
|||||||
auto temperature = this->supports_two_points_ ? this->target_temperature_low : this->target_temperature;
|
auto temperature = this->supports_two_points_ ? this->target_temperature_low : this->target_temperature;
|
||||||
|
|
||||||
if (this->supports_heat_) {
|
if (this->supports_heat_) {
|
||||||
if (this->current_temperature <= temperature - this->heating_deadband_) {
|
if (this->current_temperature < temperature - this->heating_deadband_) {
|
||||||
// if the current temperature is below or equal to the target - deadband, heating is required
|
// if the current temperature is below the target - deadband, heating is required
|
||||||
return true;
|
return true;
|
||||||
} else if (this->current_temperature >= temperature + this->heating_overrun_) {
|
} else if (this->current_temperature > temperature + this->heating_overrun_) {
|
||||||
// if the current temperature is above or equal to the target + overrun, heating should stop
|
// if the current temperature is above the target + overrun, heating should stop
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// if we get here, the current temperature is between target - deadband and target + overrun,
|
// if we get here, the current temperature is between target - deadband and target + overrun,
|
||||||
|
|||||||
@@ -40,9 +40,6 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
|||||||
// Unsigned subtraction handles wraparound correctly, then cast to signed
|
// Unsigned subtraction handles wraparound correctly, then cast to signed
|
||||||
int32_t diff = static_cast<int32_t>(epoch - static_cast<uint32_t>(current_time));
|
int32_t diff = static_cast<int32_t>(epoch - static_cast<uint32_t>(current_time));
|
||||||
if (diff >= -1 && diff <= 1) {
|
if (diff >= -1 && diff <= 1) {
|
||||||
// Time is already synchronized, but still call callbacks so components
|
|
||||||
// waiting for time sync (e.g., uptime timestamp sensor) can initialize
|
|
||||||
this->time_sync_callback_.call();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ CODEOWNERS = ["@dhoeben"]
|
|||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
water_heater_ns = cg.esphome_ns.namespace("water_heater")
|
water_heater_ns = cg.esphome_ns.namespace("water_heater")
|
||||||
WaterHeater = water_heater_ns.class_("WaterHeater", cg.EntityBase)
|
WaterHeater = water_heater_ns.class_("WaterHeater", cg.EntityBase, cg.Component)
|
||||||
WaterHeaterCall = water_heater_ns.class_("WaterHeaterCall")
|
WaterHeaterCall = water_heater_ns.class_("WaterHeaterCall")
|
||||||
WaterHeaterTraits = water_heater_ns.class_("WaterHeaterTraits")
|
WaterHeaterTraits = water_heater_ns.class_("WaterHeaterTraits")
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ _WATER_HEATER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
_WATER_HEATER_SCHEMA.add_extra(entity_duplicate_validator("water_heater"))
|
_WATER_HEATER_SCHEMA.add_extra(entity_duplicate_validator("water_heater"))
|
||||||
|
|
||||||
@@ -91,6 +91,8 @@ async def register_water_heater(var: cg.Pvariable, config: ConfigType) -> cg.Pva
|
|||||||
|
|
||||||
cg.add_define("USE_WATER_HEATER")
|
cg.add_define("USE_WATER_HEATER")
|
||||||
|
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
cg.add(cg.App.register_water_heater(var))
|
cg.add(cg.App.register_water_heater(var))
|
||||||
|
|
||||||
CORE.register_platform_component("water_heater", var)
|
CORE.register_platform_component("water_heater", var)
|
||||||
|
|||||||
@@ -146,6 +146,10 @@ void WaterHeaterCall::validate_() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WaterHeater::setup() {
|
||||||
|
this->pref_ = global_preferences->make_preference<SavedWaterHeaterState>(this->get_preference_hash());
|
||||||
|
}
|
||||||
|
|
||||||
void WaterHeater::publish_state() {
|
void WaterHeater::publish_state() {
|
||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
ESP_LOGD(TAG,
|
ESP_LOGD(TAG,
|
||||||
@@ -184,8 +188,7 @@ void WaterHeater::publish_state() {
|
|||||||
this->pref_.save(&saved);
|
this->pref_.save(&saved);
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<WaterHeaterCall> WaterHeater::restore_state_() {
|
optional<WaterHeaterCall> WaterHeater::restore_state() {
|
||||||
this->pref_ = global_preferences->make_preference<SavedWaterHeaterState>(this->get_preference_hash());
|
|
||||||
SavedWaterHeaterState recovered{};
|
SavedWaterHeaterState recovered{};
|
||||||
if (!this->pref_.load(&recovered))
|
if (!this->pref_.load(&recovered))
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ class WaterHeaterTraits {
|
|||||||
WaterHeaterModeMask supported_modes_;
|
WaterHeaterModeMask supported_modes_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WaterHeater : public EntityBase {
|
class WaterHeater : public EntityBase, public Component {
|
||||||
public:
|
public:
|
||||||
WaterHeaterMode get_mode() const { return this->mode_; }
|
WaterHeaterMode get_mode() const { return this->mode_; }
|
||||||
float get_current_temperature() const { return this->current_temperature_; }
|
float get_current_temperature() const { return this->current_temperature_; }
|
||||||
@@ -204,15 +204,16 @@ class WaterHeater : public EntityBase {
|
|||||||
#endif
|
#endif
|
||||||
virtual void control(const WaterHeaterCall &call) = 0;
|
virtual void control(const WaterHeaterCall &call) = 0;
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
optional<WaterHeaterCall> restore_state();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual WaterHeaterTraits traits() = 0;
|
virtual WaterHeaterTraits traits() = 0;
|
||||||
|
|
||||||
/// Log the traits of this water heater for dump_config().
|
/// Log the traits of this water heater for dump_config().
|
||||||
void dump_traits_(const char *tag);
|
void dump_traits_(const char *tag);
|
||||||
|
|
||||||
/// Restore the state of the water heater, call this from your setup() method.
|
|
||||||
optional<WaterHeaterCall> restore_state_();
|
|
||||||
|
|
||||||
/// Set the mode of the water heater. Should only be called from control().
|
/// Set the mode of the water heater. Should only be called from control().
|
||||||
void set_mode_(WaterHeaterMode mode) { this->mode_ = mode; }
|
void set_mode_(WaterHeaterMode mode) { this->mode_ = mode; }
|
||||||
/// Set the target temperature of the water heater. Should only be called from control().
|
/// Set the target temperature of the water heater. Should only be called from control().
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -565,11 +565,6 @@ void WiFiComponent::start() {
|
|||||||
void WiFiComponent::restart_adapter() {
|
void WiFiComponent::restart_adapter() {
|
||||||
ESP_LOGW(TAG, "Restarting adapter");
|
ESP_LOGW(TAG, "Restarting adapter");
|
||||||
this->wifi_mode_(false, {});
|
this->wifi_mode_(false, {});
|
||||||
// Clear error flag here because restart_adapter() enters COOLDOWN state,
|
|
||||||
// and check_connecting_finished() is called after cooldown without going
|
|
||||||
// through start_connecting() first. Without this clear, stale errors would
|
|
||||||
// trigger spurious "failed (callback)" logs. The canonical clear location
|
|
||||||
// is in start_connecting(); this is the only exception to that pattern.
|
|
||||||
this->error_from_callback_ = false;
|
this->error_from_callback_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,6 +618,8 @@ void WiFiComponent::loop() {
|
|||||||
if (!this->is_connected()) {
|
if (!this->is_connected()) {
|
||||||
ESP_LOGW(TAG, "Connection lost; reconnecting");
|
ESP_LOGW(TAG, "Connection lost; reconnecting");
|
||||||
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING;
|
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING;
|
||||||
|
// Clear error flag before reconnecting so first attempt is not seen as immediate failure
|
||||||
|
this->error_from_callback_ = false;
|
||||||
this->retry_connect();
|
this->retry_connect();
|
||||||
} else {
|
} else {
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
@@ -966,12 +963,6 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) {
|
|||||||
ESP_LOGV(TAG, " Hidden: %s", YESNO(ap.get_hidden()));
|
ESP_LOGV(TAG, " Hidden: %s", YESNO(ap.get_hidden()));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Clear any stale error from previous connection attempt.
|
|
||||||
// This is the canonical location for clearing the flag since all connection
|
|
||||||
// attempts go through start_connecting(). The only other clear is in
|
|
||||||
// restart_adapter() which enters COOLDOWN without calling start_connecting().
|
|
||||||
this->error_from_callback_ = false;
|
|
||||||
|
|
||||||
if (!this->wifi_sta_connect_(ap)) {
|
if (!this->wifi_sta_connect_(ap)) {
|
||||||
ESP_LOGE(TAG, "wifi_sta_connect_ failed");
|
ESP_LOGE(TAG, "wifi_sta_connect_ failed");
|
||||||
// Enter cooldown to allow WiFi hardware to stabilize
|
// Enter cooldown to allow WiFi hardware to stabilize
|
||||||
@@ -1077,6 +1068,7 @@ void WiFiComponent::enable() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Enabling");
|
ESP_LOGD(TAG, "Enabling");
|
||||||
|
this->error_from_callback_ = false;
|
||||||
this->state_ = WIFI_COMPONENT_STATE_OFF;
|
this->state_ = WIFI_COMPONENT_STATE_OFF;
|
||||||
this->start();
|
this->start();
|
||||||
}
|
}
|
||||||
@@ -1337,6 +1329,11 @@ void WiFiComponent::check_connecting_finished(uint32_t now) {
|
|||||||
// Reset to initial phase on successful connection (don't log transition, just reset state)
|
// Reset to initial phase on successful connection (don't log transition, just reset state)
|
||||||
this->retry_phase_ = WiFiRetryPhase::INITIAL_CONNECT;
|
this->retry_phase_ = WiFiRetryPhase::INITIAL_CONNECT;
|
||||||
this->num_retried_ = 0;
|
this->num_retried_ = 0;
|
||||||
|
// Ensure next connection attempt does not inherit error state
|
||||||
|
// so when WiFi disconnects later we start fresh and don't see
|
||||||
|
// the first connection as a failure.
|
||||||
|
this->error_from_callback_ = false;
|
||||||
|
|
||||||
if (this->has_ap()) {
|
if (this->has_ap()) {
|
||||||
#ifdef USE_CAPTIVE_PORTAL
|
#ifdef USE_CAPTIVE_PORTAL
|
||||||
if (this->is_captive_portal_active_()) {
|
if (this->is_captive_portal_active_()) {
|
||||||
@@ -1847,6 +1844,8 @@ void WiFiComponent::retry_connect() {
|
|||||||
this->advance_to_next_target_or_increment_retry_();
|
this->advance_to_next_target_or_increment_retry_();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->error_from_callback_ = false;
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
// Check if we have a valid target before building params
|
// Check if we have a valid target before building params
|
||||||
// After exhausting all networks in a phase, selected_sta_index_ may be -1
|
// After exhausting all networks in a phase, selected_sta_index_ may be -1
|
||||||
@@ -2172,6 +2171,7 @@ void WiFiComponent::process_roaming_scan_() {
|
|||||||
this->roaming_state_ = RoamingState::CONNECTING;
|
this->roaming_state_ = RoamingState::CONNECTING;
|
||||||
|
|
||||||
// Connect directly - wifi_sta_connect_ handles disconnect internally
|
// Connect directly - wifi_sta_connect_ handles disconnect internally
|
||||||
|
this->error_from_callback_ = false;
|
||||||
this->start_connecting(roam_params);
|
this->start_connecting(roam_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -655,9 +655,11 @@ inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator).
|
/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator).
|
||||||
|
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
|
||||||
std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...);
|
std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...);
|
||||||
|
|
||||||
/// sprintf-like function returning std::string.
|
/// sprintf-like function returning std::string.
|
||||||
|
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
|
||||||
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
|
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools==80.10.1", "wheel>=0.43,<0.47"]
|
requires = ["setuptools==80.10.1", "wheel>=0.43,<0.46"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pylint==4.0.4
|
pylint==4.0.4
|
||||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||||
ruff==0.14.14 # also change in .pre-commit-config.yaml when updating
|
ruff==0.14.13 # also change in .pre-commit-config.yaml when updating
|
||||||
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
|
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
|
||||||
pre-commit
|
pre-commit
|
||||||
|
|
||||||
|
|||||||
@@ -692,6 +692,8 @@ HEAP_ALLOCATING_HELPERS = {
|
|||||||
"str_truncate": "removal (function is unused)",
|
"str_truncate": "removal (function is unused)",
|
||||||
"str_upper_case": "removal (function is unused)",
|
"str_upper_case": "removal (function is unused)",
|
||||||
"str_snake_case": "removal (function is unused)",
|
"str_snake_case": "removal (function is unused)",
|
||||||
|
"str_sprintf": "snprintf() with a stack buffer",
|
||||||
|
"str_snprintf": "snprintf() with a stack buffer",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -710,7 +712,9 @@ HEAP_ALLOCATING_HELPERS = {
|
|||||||
r"str_sanitize(?!_)|"
|
r"str_sanitize(?!_)|"
|
||||||
r"str_truncate|"
|
r"str_truncate|"
|
||||||
r"str_upper_case|"
|
r"str_upper_case|"
|
||||||
r"str_snake_case"
|
r"str_snake_case|"
|
||||||
|
r"str_sprintf|"
|
||||||
|
r"str_snprintf"
|
||||||
r")\s*\(" + CPP_RE_EOL,
|
r")\s*\(" + CPP_RE_EOL,
|
||||||
include=cpp_include,
|
include=cpp_include,
|
||||||
exclude=[
|
exclude=[
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
sensor:
|
sensor:
|
||||||
- platform: bmp581_i2c
|
- platform: bmp581
|
||||||
i2c_id: i2c_bus
|
i2c_id: i2c_bus
|
||||||
temperature:
|
temperature:
|
||||||
name: BMP581 Temperature
|
name: BMP581 Temperature
|
||||||
@@ -3,7 +3,6 @@ esp32_ble_tracker:
|
|||||||
sensor:
|
sensor:
|
||||||
- platform: bthome_mithermometer
|
- platform: bthome_mithermometer
|
||||||
mac_address: A4:C1:38:4E:16:78
|
mac_address: A4:C1:38:4E:16:78
|
||||||
bindkey: eef418daf699a0c188f3bfd17e4565d9
|
|
||||||
temperature:
|
temperature:
|
||||||
name: "BTHome Temperature"
|
name: "BTHome Temperature"
|
||||||
humidity:
|
humidity:
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
remote_receiver:
|
|
||||||
id: ir_receiver
|
|
||||||
pin: ${rx_pin}
|
|
||||||
|
|
||||||
# Test various hardware types with transmitter/receiver using infrared platform
|
|
||||||
infrared:
|
|
||||||
# Infrared receiver
|
|
||||||
- platform: ir_rf_proxy
|
|
||||||
id: ir_rx
|
|
||||||
name: "IR Receiver"
|
|
||||||
remote_receiver_id: ir_receiver
|
|
||||||
|
|
||||||
# RF 900MHz receiver
|
|
||||||
- platform: ir_rf_proxy
|
|
||||||
id: rf_900_rx
|
|
||||||
name: "RF 900 Receiver"
|
|
||||||
frequency: 900 MHz
|
|
||||||
remote_receiver_id: ir_receiver
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
remote_transmitter:
|
|
||||||
id: ir_transmitter
|
|
||||||
pin: ${tx_pin}
|
|
||||||
carrier_duty_percent: 50%
|
|
||||||
|
|
||||||
# Test various hardware types with transmitter/receiver using infrared platform
|
|
||||||
infrared:
|
|
||||||
# Infrared transmitter
|
|
||||||
- platform: ir_rf_proxy
|
|
||||||
id: ir_tx
|
|
||||||
name: "IR Transmitter"
|
|
||||||
remote_transmitter_id: ir_transmitter
|
|
||||||
|
|
||||||
# RF 433MHz transmitter
|
|
||||||
- platform: ir_rf_proxy
|
|
||||||
id: rf_433_tx
|
|
||||||
name: "RF 433 Transmitter"
|
|
||||||
frequency: 433 MHz
|
|
||||||
remote_transmitter_id: ir_transmitter
|
|
||||||
@@ -1,7 +1,42 @@
|
|||||||
network:
|
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: MySSID
|
ssid: MySSID
|
||||||
password: password1
|
password: password1
|
||||||
|
|
||||||
api:
|
api:
|
||||||
|
|
||||||
|
remote_transmitter:
|
||||||
|
id: ir_transmitter
|
||||||
|
pin: ${tx_pin}
|
||||||
|
carrier_duty_percent: 50%
|
||||||
|
|
||||||
|
remote_receiver:
|
||||||
|
id: ir_receiver
|
||||||
|
pin: ${rx_pin}
|
||||||
|
|
||||||
|
# Test various hardware types with transmitter/receiver using infrared platform
|
||||||
|
infrared:
|
||||||
|
# Infrared transmitter
|
||||||
|
- platform: ir_rf_proxy
|
||||||
|
id: ir_tx
|
||||||
|
name: "IR Transmitter"
|
||||||
|
remote_transmitter_id: ir_transmitter
|
||||||
|
|
||||||
|
# Infrared receiver
|
||||||
|
- platform: ir_rf_proxy
|
||||||
|
id: ir_rx
|
||||||
|
name: "IR Receiver"
|
||||||
|
remote_receiver_id: ir_receiver
|
||||||
|
|
||||||
|
# RF 433MHz transmitter
|
||||||
|
- platform: ir_rf_proxy
|
||||||
|
id: rf_433_tx
|
||||||
|
name: "RF 433 Transmitter"
|
||||||
|
frequency: 433 MHz
|
||||||
|
remote_transmitter_id: ir_transmitter
|
||||||
|
|
||||||
|
# RF 900MHz receiver
|
||||||
|
- platform: ir_rf_proxy
|
||||||
|
id: rf_900_rx
|
||||||
|
name: "RF 900 Receiver"
|
||||||
|
frequency: 900 MHz
|
||||||
|
remote_receiver_id: ir_receiver
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
tx_pin: GPIO4
|
|
||||||
rx_pin: GPIO5
|
|
||||||
|
|
||||||
packages:
|
|
||||||
common: !include common.yaml
|
|
||||||
rx: !include common-rx.yaml
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
tx_pin: GPIO4
|
|
||||||
rx_pin: GPIO5
|
|
||||||
|
|
||||||
packages:
|
|
||||||
common: !include common.yaml
|
|
||||||
rx: !include common-rx.yaml
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
tx_pin: GPIO4
|
|
||||||
rx_pin: GPIO5
|
|
||||||
|
|
||||||
packages:
|
|
||||||
common: !include common.yaml
|
|
||||||
rx: !include common-rx.yaml
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
tx_pin: GPIO4
|
|
||||||
rx_pin: GPIO5
|
|
||||||
|
|
||||||
packages:
|
|
||||||
common: !include common.yaml
|
|
||||||
tx: !include common-tx.yaml
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
tx_pin: GPIO4
|
|
||||||
rx_pin: GPIO5
|
|
||||||
|
|
||||||
packages:
|
|
||||||
common: !include common.yaml
|
|
||||||
tx: !include common-tx.yaml
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
tx_pin: GPIO4
|
|
||||||
rx_pin: GPIO5
|
|
||||||
|
|
||||||
packages:
|
|
||||||
common: !include common.yaml
|
|
||||||
tx: !include common-tx.yaml
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
tx_pin: GPIO4
|
|
||||||
rx_pin: GPIO5
|
|
||||||
|
|
||||||
packages:
|
|
||||||
common: !include common.yaml
|
|
||||||
rx: !include common-rx.yaml
|
|
||||||
tx: !include common-tx.yaml
|
|
||||||
@@ -2,7 +2,4 @@ substitutions:
|
|||||||
tx_pin: GPIO4
|
tx_pin: GPIO4
|
||||||
rx_pin: GPIO5
|
rx_pin: GPIO5
|
||||||
|
|
||||||
packages:
|
<<: !include common.yaml
|
||||||
common: !include common.yaml
|
|
||||||
rx: !include common-rx.yaml
|
|
||||||
tx: !include common-tx.yaml
|
|
||||||
|
|||||||
@@ -2,7 +2,4 @@ substitutions:
|
|||||||
tx_pin: GPIO4
|
tx_pin: GPIO4
|
||||||
rx_pin: GPIO5
|
rx_pin: GPIO5
|
||||||
|
|
||||||
packages:
|
<<: !include common.yaml
|
||||||
common: !include common.yaml
|
|
||||||
rx: !include common-rx.yaml
|
|
||||||
tx: !include common-tx.yaml
|
|
||||||
|
|||||||
@@ -2,7 +2,4 @@ substitutions:
|
|||||||
tx_pin: GPIO4
|
tx_pin: GPIO4
|
||||||
rx_pin: GPIO5
|
rx_pin: GPIO5
|
||||||
|
|
||||||
packages:
|
<<: !include common.yaml
|
||||||
common: !include common.yaml
|
|
||||||
rx: !include common-rx.yaml
|
|
||||||
tx: !include common-tx.yaml
|
|
||||||
|
|||||||
@@ -197,9 +197,6 @@ lvgl:
|
|||||||
- lvgl.label.update:
|
- lvgl.label.update:
|
||||||
id: msgbox_label
|
id: msgbox_label
|
||||||
text: Unloaded
|
text: Unloaded
|
||||||
- lvgl.label.update:
|
|
||||||
id: msgbox_label
|
|
||||||
text: "" # Empty text
|
|
||||||
on_all_events:
|
on_all_events:
|
||||||
logger.log:
|
logger.log:
|
||||||
format: "Event %s"
|
format: "Event %s"
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
sy6970:
|
|
||||||
id: sy6970_component
|
|
||||||
i2c_id: i2c_bus
|
|
||||||
address: 0x6A
|
|
||||||
enable_status_led: true
|
|
||||||
input_current_limit: 1000
|
|
||||||
charge_voltage: 4200
|
|
||||||
charge_current: 500
|
|
||||||
precharge_current: 128
|
|
||||||
charge_enabled: true
|
|
||||||
enable_adc: true
|
|
||||||
update_interval: 5s
|
|
||||||
|
|
||||||
sensor:
|
|
||||||
- platform: sy6970
|
|
||||||
sy6970_id: sy6970_component
|
|
||||||
vbus_voltage:
|
|
||||||
name: "VBUS Voltage"
|
|
||||||
id: vbus_voltage_sensor
|
|
||||||
battery_voltage:
|
|
||||||
name: "Battery Voltage"
|
|
||||||
id: battery_voltage_sensor
|
|
||||||
system_voltage:
|
|
||||||
name: "System Voltage"
|
|
||||||
id: system_voltage_sensor
|
|
||||||
charge_current:
|
|
||||||
name: "Charge Current"
|
|
||||||
id: charge_current_sensor
|
|
||||||
precharge_current:
|
|
||||||
name: "Precharge Current"
|
|
||||||
id: precharge_current_sensor
|
|
||||||
|
|
||||||
binary_sensor:
|
|
||||||
- platform: sy6970
|
|
||||||
sy6970_id: sy6970_component
|
|
||||||
vbus_connected:
|
|
||||||
name: "VBUS Connected"
|
|
||||||
id: vbus_connected_binary
|
|
||||||
charging:
|
|
||||||
name: "Charging"
|
|
||||||
id: charging_binary
|
|
||||||
charge_done:
|
|
||||||
name: "Charge Done"
|
|
||||||
id: charge_done_binary
|
|
||||||
|
|
||||||
text_sensor:
|
|
||||||
- platform: sy6970
|
|
||||||
sy6970_id: sy6970_component
|
|
||||||
bus_status:
|
|
||||||
name: "Bus Status"
|
|
||||||
id: bus_status_text
|
|
||||||
charge_status:
|
|
||||||
name: "Charge Status"
|
|
||||||
id: charge_status_text
|
|
||||||
ntc_status:
|
|
||||||
name: "NTC Status"
|
|
||||||
id: ntc_status_text
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
packages:
|
|
||||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
|
||||||
|
|
||||||
<<: !include common.yaml
|
|
||||||
@@ -117,7 +117,6 @@ sensor:
|
|||||||
- 10.0 -> 12.1
|
- 10.0 -> 12.1
|
||||||
- 13.0 -> 14.0
|
- 13.0 -> 14.0
|
||||||
- clamp:
|
- clamp:
|
||||||
# Infinity and NaN will be clamped (NaN -> min_value, +Infinity -> max_value, -Infinity -> min_value)
|
|
||||||
max_value: 10.0
|
max_value: 10.0
|
||||||
min_value: -10.0
|
min_value: -10.0
|
||||||
- debounce: 0.1s
|
- debounce: 0.1s
|
||||||
|
|||||||
Reference in New Issue
Block a user