mirror of
https://github.com/esphome/esphome.git
synced 2026-01-13 05:27:53 -07:00
Compare commits
120 Commits
filter-pla
...
scheduler_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d91cad1636 | ||
|
|
54d0328002 | ||
|
|
865312ff60 | ||
|
|
637cb3f04a | ||
|
|
80e881655f | ||
|
|
78b2ae8a35 | ||
|
|
8caaf53ef0 | ||
|
|
4db7748815 | ||
|
|
0da157ab98 | ||
|
|
cafa275579 | ||
|
|
a31fb223f3 | ||
|
|
37019231de | ||
|
|
2af66bd6fc | ||
|
|
951c5377c5 | ||
|
|
22803ef54b | ||
|
|
20f82a3820 | ||
|
|
fb331e1c5a | ||
|
|
a8518d3cea | ||
|
|
03aaa66f8e | ||
|
|
a24ba26068 | ||
|
|
623cdac689 | ||
|
|
1fbd91dc71 | ||
|
|
cfd88376b9 | ||
|
|
b3812b5811 | ||
|
|
577a6b2941 | ||
|
|
de68b56c4a | ||
|
|
ccd23e692b | ||
|
|
1f5a44be3d | ||
|
|
1d1e47c757 | ||
| 3fbed1fa79 | |||
|
|
5c71520635 | ||
|
|
9d6c81ec23 | ||
|
|
73fa9230e6 | ||
|
|
48caff13c9 | ||
|
|
71bb94524e | ||
|
|
a3199792c6 | ||
|
|
87ac4baf3a | ||
|
|
669bcad458 | ||
|
|
6f91c75f86 | ||
|
|
ab60ae092d | ||
|
|
708496c101 | ||
|
|
2f75962b19 | ||
|
|
a6a6f482e6 | ||
|
|
638c59e162 | ||
|
|
8f97f3b81f | ||
|
|
6ce2a45691 | ||
|
|
77477bd330 | ||
|
|
3f08cacf71 | ||
|
|
d1583456e9 | ||
|
|
101103c666 | ||
|
|
5142ff372b | ||
|
|
f9ad832e7b | ||
|
|
deda7a1bf3 | ||
|
|
29be1423f5 | ||
|
|
10ddebc737 | ||
|
|
9a0731437a | ||
|
|
82a06c697e | ||
|
|
c45cd44bb8 | ||
|
|
2903a4aa92 | ||
|
|
6943803176 | ||
|
|
6dafc5137e | ||
|
|
df58e832e5 | ||
|
|
e42cf9a4f4 | ||
|
|
96f28f0ab4 | ||
|
|
d332edfaca | ||
|
|
d4bd282bb4 | ||
|
|
78df884bb5 | ||
|
|
52fe3de78f | ||
|
|
6a79ce8eff | ||
|
|
2b7695ba3f | ||
|
|
6d336676a2 | ||
|
|
b322622ef1 | ||
|
|
065c1bfc6a | ||
|
|
664881bc13 | ||
|
|
dbc16ce468 | ||
|
|
161a18b326 | ||
|
|
4335fcdb72 | ||
| bf4ef36c3a | |||
|
|
2ca118f371 | ||
|
|
82e1238330 | ||
|
|
8308bc2911 | ||
|
|
47c767fa5e | ||
|
|
e95ceafc17 | ||
|
|
7317bf4a5d | ||
|
|
042a08887f | ||
|
|
77f5f2326f | ||
| d82a92b406 | |||
|
|
ec88bf0cb1 | ||
|
|
46567c4716 | ||
|
|
1f47797007 | ||
|
|
cf444fc3b8 | ||
|
|
c40e8e7f5c | ||
|
|
b71d8010d2 | ||
|
|
2174795b27 | ||
|
|
5fa4ff754c | ||
|
|
bc50be6053 | ||
|
|
ca599b25c2 | ||
|
|
2e55296640 | ||
|
|
d6ca01775e | ||
|
|
e15f3a08ae | ||
|
|
fb82362e9c | ||
|
|
26e979d3d5 | ||
|
|
60ffa0e52e | ||
|
|
e1ec6146c0 | ||
|
|
450065fdae | ||
|
|
71dc402a30 | ||
|
|
9bd148dfd1 | ||
|
|
50c1720c16 | ||
|
|
4c549798bc | ||
|
|
4115dd7222 | ||
|
|
d5e2543751 | ||
|
|
b4b34aee13 | ||
|
|
6645994700 | ||
|
|
ae140f52e3 | ||
|
|
46ae6d35a2 | ||
|
|
278f12fb99 | ||
|
|
acdcd56395 | ||
|
|
9289fc36f7 | ||
|
|
1fadd1227d | ||
|
|
91df0548ef |
@@ -402,35 +402,45 @@ This document provides essential context for AI models interacting with this pro
|
||||
_use_feature = True
|
||||
```
|
||||
|
||||
**Good Pattern (CORE.data with Helpers):**
|
||||
**Bad Pattern (Flat Keys):**
|
||||
```python
|
||||
# Don't do this - keys should be namespaced under component domain
|
||||
MY_FEATURE_KEY = "my_component_feature"
|
||||
CORE.data[MY_FEATURE_KEY] = True
|
||||
```
|
||||
|
||||
**Good Pattern (dataclass):**
|
||||
```python
|
||||
from dataclasses import dataclass, field
|
||||
from esphome.core import CORE
|
||||
|
||||
# Keys for CORE.data storage
|
||||
COMPONENT_STATE_KEY = "my_component_state"
|
||||
USE_FEATURE_KEY = "my_component_use_feature"
|
||||
DOMAIN = "my_component"
|
||||
|
||||
def _get_component_state() -> list:
|
||||
"""Get component state from CORE.data."""
|
||||
return CORE.data.setdefault(COMPONENT_STATE_KEY, [])
|
||||
@dataclass
|
||||
class MyComponentData:
|
||||
feature_enabled: bool = False
|
||||
item_count: int = 0
|
||||
items: list[str] = field(default_factory=list)
|
||||
|
||||
def _get_use_feature() -> bool | None:
|
||||
"""Get feature flag from CORE.data."""
|
||||
return CORE.data.get(USE_FEATURE_KEY)
|
||||
def _get_data() -> MyComponentData:
|
||||
if DOMAIN not in CORE.data:
|
||||
CORE.data[DOMAIN] = MyComponentData()
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
def _set_use_feature(value: bool) -> None:
|
||||
"""Set feature flag in CORE.data."""
|
||||
CORE.data[USE_FEATURE_KEY] = value
|
||||
def request_feature() -> None:
|
||||
_get_data().feature_enabled = True
|
||||
|
||||
def enable_feature():
|
||||
_set_use_feature(True)
|
||||
def add_item(item: str) -> None:
|
||||
_get_data().items.append(item)
|
||||
```
|
||||
|
||||
If you need a real-world example, search for components that use `@dataclass` with `CORE.data` in the codebase. Note: Some components may use `TypedDict` for dictionary-based storage; both patterns are acceptable depending on your needs.
|
||||
|
||||
**Why this matters:**
|
||||
- Module-level globals persist between compilation runs if the dashboard doesn't fork/exec
|
||||
- `CORE.data` automatically clears between runs
|
||||
- Typed helper functions provide better IDE support and maintainability
|
||||
- Encapsulation makes state management explicit and testable
|
||||
- Namespacing under `DOMAIN` prevents key collisions between components
|
||||
- `@dataclass` provides type safety and cleaner attribute access
|
||||
|
||||
* **Security:** Be mindful of security when making changes to the API, web server, or any other network-related code. Do not hardcode secrets or keys.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
3d46b63015d761c85ca9cb77ab79a389509e5776701fb22aed16e7b79d432c0c
|
||||
29270eecb86ffa07b2b1d2a4ca56dd7f84762ddc89c6248dbf3f012eca8780b6
|
||||
|
||||
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'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
|
||||
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
|
||||
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
- "docker"
|
||||
# - "lint"
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
with:
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Check out code from base repository
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# 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
|
||||
|
||||
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Restore Python
|
||||
id: restore-python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# Fetch enough history to find the merge base
|
||||
fetch-depth: 2
|
||||
@@ -237,7 +237,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
@@ -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 != '[]')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -321,7 +321,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -489,7 +489,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -577,7 +577,7 @@ jobs:
|
||||
version: 1.0
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -662,13 +662,13 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- uses: esphome/action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache
|
||||
- uses: esphome/pre-commit-action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache
|
||||
env:
|
||||
SKIP: pylint,clang-tidy-hash
|
||||
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
|
||||
@@ -688,7 +688,7 @@ jobs:
|
||||
skip: ${{ steps.check-script.outputs.skip }}
|
||||
steps:
|
||||
- name: Check out target branch
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
@@ -840,7 +840,7 @@ jobs:
|
||||
flash_usage: ${{ steps.extract.outputs.flash_usage }}
|
||||
steps:
|
||||
- name: Check out PR branch
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -908,7 +908,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
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
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
61
.github/workflows/release.yml
vendored
61
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
with:
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
os: "ubuntu-24.04-arm"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
with:
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
- ghcr
|
||||
- dockerhub
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
@@ -219,10 +219,19 @@ jobs:
|
||||
- init
|
||||
- deploy-manifest
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
owner: esphome
|
||||
repositories: home-assistant-addon
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
let description = "ESPHome";
|
||||
if (context.eventName == "release") {
|
||||
@@ -245,10 +254,19 @@ jobs:
|
||||
needs: [init]
|
||||
environment: ${{ needs.init.outputs.deploy_env }}
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
owner: esphome
|
||||
repositories: esphome-schema
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
@@ -259,3 +277,34 @@ jobs:
|
||||
version: "${{ needs.init.outputs.tag }}",
|
||||
}
|
||||
})
|
||||
|
||||
version-notifier:
|
||||
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- init
|
||||
- deploy-manifest
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
owner: esphome
|
||||
repositories: version-notifier
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
repo: "version-notifier",
|
||||
workflow_id: "notify.yml",
|
||||
ref: "main",
|
||||
inputs: {
|
||||
version: "${{ needs.init.outputs.tag }}",
|
||||
}
|
||||
})
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Stale
|
||||
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||
with:
|
||||
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
|
||||
remove-stale-when-updated: true
|
||||
|
||||
4
.github/workflows/sync-device-classes.yml
vendored
4
.github/workflows/sync-device-classes.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.5
|
||||
rev: v0.14.8
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -21,6 +21,7 @@ esphome/components/adc128s102/* @DeerMaximum
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/ade7880/* @kpfleming
|
||||
esphome/components/ade7953/* @angelnu
|
||||
esphome/components/ade7953_base/* @angelnu
|
||||
esphome/components/ade7953_i2c/* @angelnu
|
||||
esphome/components/ade7953_spi/* @angelnu
|
||||
esphome/components/ads1118/* @solomondg1
|
||||
@@ -96,6 +97,7 @@ esphome/components/camera_encoder/* @DT-art1
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @mreditor97
|
||||
esphome/components/captive_portal/* @esphome/core
|
||||
esphome/components/cc1101/* @gabest11 @lygris
|
||||
esphome/components/ccs811/* @habbie
|
||||
esphome/components/cd74hc4067/* @asoehlke
|
||||
esphome/components/ch422g/* @clydebarrow @jesterret
|
||||
@@ -189,6 +191,7 @@ esphome/components/gps/* @coogle @ximex
|
||||
esphome/components/graph/* @synco
|
||||
esphome/components/graphical_display_menu/* @MrMDavidson
|
||||
esphome/components/gree/* @orestismers
|
||||
esphome/components/gree/switch/* @nagyrobi
|
||||
esphome/components/grove_gas_mc_v2/* @YorkshireIoT
|
||||
esphome/components/grove_tb6612fng/* @max246
|
||||
esphome/components/growatt_solar/* @leeuwte
|
||||
|
||||
@@ -944,6 +944,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
"""
|
||||
from esphome import platformio_api
|
||||
from esphome.analyze_memory.cli import MemoryAnalyzerCLI
|
||||
from esphome.analyze_memory.ram_strings import RamStringsAnalyzer
|
||||
|
||||
# Always compile to ensure fresh data (fast if no changes - just relinks)
|
||||
exit_code = write_cpp(config)
|
||||
@@ -966,7 +967,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
external_components = detect_external_components(config)
|
||||
_LOGGER.debug("Detected external components: %s", external_components)
|
||||
|
||||
# Perform memory analysis
|
||||
# Perform component memory analysis
|
||||
_LOGGER.info("Analyzing memory usage...")
|
||||
analyzer = MemoryAnalyzerCLI(
|
||||
str(firmware_elf),
|
||||
@@ -976,11 +977,28 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
)
|
||||
analyzer.analyze()
|
||||
|
||||
# Generate and display report
|
||||
# Generate and display component report
|
||||
report = analyzer.generate_report()
|
||||
print()
|
||||
print(report)
|
||||
|
||||
# Perform RAM strings analysis
|
||||
_LOGGER.info("Analyzing RAM strings...")
|
||||
try:
|
||||
ram_analyzer = RamStringsAnalyzer(
|
||||
str(firmware_elf),
|
||||
objdump_path=idedata.objdump_path,
|
||||
platform=CORE.target_platform,
|
||||
)
|
||||
ram_analyzer.analyze()
|
||||
|
||||
# Generate and display RAM strings report
|
||||
ram_report = ram_analyzer.generate_report()
|
||||
print()
|
||||
print(ram_report)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
_LOGGER.warning("RAM strings analysis failed: %s", e)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from .const import (
|
||||
SECTION_TO_ATTR,
|
||||
SYMBOL_PATTERNS,
|
||||
)
|
||||
from .demangle import batch_demangle
|
||||
from .helpers import (
|
||||
get_component_class_patterns,
|
||||
get_esphome_components,
|
||||
@@ -27,15 +28,6 @@ if TYPE_CHECKING:
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# GCC global constructor/destructor prefix annotations
|
||||
_GCC_PREFIX_ANNOTATIONS = {
|
||||
"_GLOBAL__sub_I_": "global constructor for",
|
||||
"_GLOBAL__sub_D_": "global destructor for",
|
||||
}
|
||||
|
||||
# GCC optimization suffix pattern (e.g., $isra$0, $part$1, $constprop$2)
|
||||
_GCC_OPTIMIZATION_SUFFIX_PATTERN = re.compile(r"(\$(?:isra|part|constprop)\$\d+)")
|
||||
|
||||
# C++ runtime patterns for categorization
|
||||
_CPP_RUNTIME_PATTERNS = frozenset(["vtable", "typeinfo", "thunk"])
|
||||
|
||||
@@ -312,168 +304,9 @@ class MemoryAnalyzer:
|
||||
if not symbols:
|
||||
return
|
||||
|
||||
# Try to find the appropriate c++filt for the platform
|
||||
cppfilt_cmd = "c++filt"
|
||||
|
||||
_LOGGER.info("Demangling %d symbols", len(symbols))
|
||||
_LOGGER.debug("objdump_path = %s", self.objdump_path)
|
||||
|
||||
# Check if we have a toolchain-specific c++filt
|
||||
if self.objdump_path and self.objdump_path != "objdump":
|
||||
# Replace objdump with c++filt in the path
|
||||
potential_cppfilt = self.objdump_path.replace("objdump", "c++filt")
|
||||
_LOGGER.info("Checking for toolchain c++filt at: %s", potential_cppfilt)
|
||||
if Path(potential_cppfilt).exists():
|
||||
cppfilt_cmd = potential_cppfilt
|
||||
_LOGGER.info("✓ Using toolchain c++filt: %s", cppfilt_cmd)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"✗ Toolchain c++filt not found at %s, using system c++filt",
|
||||
potential_cppfilt,
|
||||
)
|
||||
else:
|
||||
_LOGGER.info("✗ Using system c++filt (objdump_path=%s)", self.objdump_path)
|
||||
|
||||
# Strip GCC optimization suffixes and prefixes before demangling
|
||||
# Suffixes like $isra$0, $part$0, $constprop$0 confuse c++filt
|
||||
# Prefixes like _GLOBAL__sub_I_ need to be removed and tracked
|
||||
symbols_stripped: list[str] = []
|
||||
symbols_prefixes: list[str] = [] # Track removed prefixes
|
||||
for symbol in symbols:
|
||||
# Remove GCC optimization markers
|
||||
stripped = _GCC_OPTIMIZATION_SUFFIX_PATTERN.sub("", symbol)
|
||||
|
||||
# Handle GCC global constructor/initializer prefixes
|
||||
# _GLOBAL__sub_I_<mangled> -> extract <mangled> for demangling
|
||||
prefix = ""
|
||||
for gcc_prefix in _GCC_PREFIX_ANNOTATIONS:
|
||||
if stripped.startswith(gcc_prefix):
|
||||
prefix = gcc_prefix
|
||||
stripped = stripped[len(prefix) :]
|
||||
break
|
||||
|
||||
symbols_stripped.append(stripped)
|
||||
symbols_prefixes.append(prefix)
|
||||
|
||||
try:
|
||||
# Send all symbols to c++filt at once
|
||||
result = subprocess.run(
|
||||
[cppfilt_cmd],
|
||||
input="\n".join(symbols_stripped),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
except (subprocess.SubprocessError, OSError, UnicodeDecodeError) as e:
|
||||
# On error, cache originals
|
||||
_LOGGER.warning("Failed to batch demangle symbols: %s", e)
|
||||
for symbol in symbols:
|
||||
self._demangle_cache[symbol] = symbol
|
||||
return
|
||||
|
||||
if result.returncode != 0:
|
||||
_LOGGER.warning(
|
||||
"c++filt exited with code %d: %s",
|
||||
result.returncode,
|
||||
result.stderr[:200] if result.stderr else "(no error output)",
|
||||
)
|
||||
# Cache originals on failure
|
||||
for symbol in symbols:
|
||||
self._demangle_cache[symbol] = symbol
|
||||
return
|
||||
|
||||
# Process demangled output
|
||||
self._process_demangled_output(
|
||||
symbols, symbols_stripped, symbols_prefixes, result.stdout, cppfilt_cmd
|
||||
)
|
||||
|
||||
def _process_demangled_output(
|
||||
self,
|
||||
symbols: list[str],
|
||||
symbols_stripped: list[str],
|
||||
symbols_prefixes: list[str],
|
||||
demangled_output: str,
|
||||
cppfilt_cmd: str,
|
||||
) -> None:
|
||||
"""Process demangled symbol output and populate cache.
|
||||
|
||||
Args:
|
||||
symbols: Original symbol names
|
||||
symbols_stripped: Stripped symbol names sent to c++filt
|
||||
symbols_prefixes: Removed prefixes to restore
|
||||
demangled_output: Output from c++filt
|
||||
cppfilt_cmd: Path to c++filt command (for logging)
|
||||
"""
|
||||
demangled_lines = demangled_output.strip().split("\n")
|
||||
failed_count = 0
|
||||
|
||||
for original, stripped, prefix, demangled in zip(
|
||||
symbols, symbols_stripped, symbols_prefixes, demangled_lines
|
||||
):
|
||||
# Add back any prefix that was removed
|
||||
demangled = self._restore_symbol_prefix(prefix, stripped, demangled)
|
||||
|
||||
# If we stripped a suffix, add it back to the demangled name for clarity
|
||||
if original != stripped and not prefix:
|
||||
demangled = self._restore_symbol_suffix(original, demangled)
|
||||
|
||||
self._demangle_cache[original] = demangled
|
||||
|
||||
# Log symbols that failed to demangle (stayed the same as stripped version)
|
||||
if stripped == demangled and stripped.startswith("_Z"):
|
||||
failed_count += 1
|
||||
if failed_count <= 5: # Only log first 5 failures
|
||||
_LOGGER.warning("Failed to demangle: %s", original)
|
||||
|
||||
if failed_count == 0:
|
||||
_LOGGER.info("Successfully demangled all %d symbols", len(symbols))
|
||||
return
|
||||
|
||||
_LOGGER.warning(
|
||||
"Failed to demangle %d/%d symbols using %s",
|
||||
failed_count,
|
||||
len(symbols),
|
||||
cppfilt_cmd,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _restore_symbol_prefix(prefix: str, stripped: str, demangled: str) -> str:
|
||||
"""Restore prefix that was removed before demangling.
|
||||
|
||||
Args:
|
||||
prefix: Prefix that was removed (e.g., "_GLOBAL__sub_I_")
|
||||
stripped: Stripped symbol name
|
||||
demangled: Demangled symbol name
|
||||
|
||||
Returns:
|
||||
Demangled name with prefix restored/annotated
|
||||
"""
|
||||
if not prefix:
|
||||
return demangled
|
||||
|
||||
# Successfully demangled - add descriptive prefix
|
||||
if demangled != stripped and (
|
||||
annotation := _GCC_PREFIX_ANNOTATIONS.get(prefix)
|
||||
):
|
||||
return f"[{annotation}: {demangled}]"
|
||||
|
||||
# Failed to demangle - restore original prefix
|
||||
return prefix + demangled
|
||||
|
||||
@staticmethod
|
||||
def _restore_symbol_suffix(original: str, demangled: str) -> str:
|
||||
"""Restore GCC optimization suffix that was removed before demangling.
|
||||
|
||||
Args:
|
||||
original: Original symbol name with suffix
|
||||
demangled: Demangled symbol name without suffix
|
||||
|
||||
Returns:
|
||||
Demangled name with suffix annotation
|
||||
"""
|
||||
if suffix_match := _GCC_OPTIMIZATION_SUFFIX_PATTERN.search(original):
|
||||
return f"{demangled} [{suffix_match.group(1)}]"
|
||||
return demangled
|
||||
self._demangle_cache = batch_demangle(symbols, objdump_path=self.objdump_path)
|
||||
_LOGGER.info("Successfully demangled %d symbols", len(self._demangle_cache))
|
||||
|
||||
def _demangle_symbol(self, symbol: str) -> str:
|
||||
"""Get demangled C++ symbol name from cache."""
|
||||
|
||||
182
esphome/analyze_memory/demangle.py
Normal file
182
esphome/analyze_memory/demangle.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""Symbol demangling utilities for memory analysis.
|
||||
|
||||
This module provides functions for demangling C++ symbol names using c++filt.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from .toolchain import find_tool
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# GCC global constructor/destructor prefix annotations
|
||||
GCC_PREFIX_ANNOTATIONS = {
|
||||
"_GLOBAL__sub_I_": "global constructor for",
|
||||
"_GLOBAL__sub_D_": "global destructor for",
|
||||
}
|
||||
|
||||
# GCC optimization suffix pattern (e.g., $isra$0, $part$1, $constprop$2)
|
||||
GCC_OPTIMIZATION_SUFFIX_PATTERN = re.compile(r"(\$(?:isra|part|constprop)\$\d+)")
|
||||
|
||||
|
||||
def _strip_gcc_annotations(symbol: str) -> tuple[str, str]:
|
||||
"""Strip GCC optimization suffixes and prefixes from a symbol.
|
||||
|
||||
Args:
|
||||
symbol: The mangled symbol name
|
||||
|
||||
Returns:
|
||||
Tuple of (stripped_symbol, removed_prefix)
|
||||
"""
|
||||
# Remove GCC optimization markers
|
||||
stripped = GCC_OPTIMIZATION_SUFFIX_PATTERN.sub("", symbol)
|
||||
|
||||
# Handle GCC global constructor/initializer prefixes
|
||||
prefix = ""
|
||||
for gcc_prefix in GCC_PREFIX_ANNOTATIONS:
|
||||
if stripped.startswith(gcc_prefix):
|
||||
prefix = gcc_prefix
|
||||
stripped = stripped[len(prefix) :]
|
||||
break
|
||||
|
||||
return stripped, prefix
|
||||
|
||||
|
||||
def _restore_symbol_prefix(prefix: str, stripped: str, demangled: str) -> str:
|
||||
"""Restore prefix that was removed before demangling.
|
||||
|
||||
Args:
|
||||
prefix: Prefix that was removed (e.g., "_GLOBAL__sub_I_")
|
||||
stripped: Stripped symbol name
|
||||
demangled: Demangled symbol name
|
||||
|
||||
Returns:
|
||||
Demangled name with prefix restored/annotated
|
||||
"""
|
||||
if not prefix:
|
||||
return demangled
|
||||
|
||||
# Successfully demangled - add descriptive prefix
|
||||
if demangled != stripped and (annotation := GCC_PREFIX_ANNOTATIONS.get(prefix)):
|
||||
return f"[{annotation}: {demangled}]"
|
||||
|
||||
# Failed to demangle - restore original prefix
|
||||
return prefix + demangled
|
||||
|
||||
|
||||
def _restore_symbol_suffix(original: str, demangled: str) -> str:
|
||||
"""Restore GCC optimization suffix that was removed before demangling.
|
||||
|
||||
Args:
|
||||
original: Original symbol name with suffix
|
||||
demangled: Demangled symbol name without suffix
|
||||
|
||||
Returns:
|
||||
Demangled name with suffix annotation
|
||||
"""
|
||||
if suffix_match := GCC_OPTIMIZATION_SUFFIX_PATTERN.search(original):
|
||||
return f"{demangled} [{suffix_match.group(1)}]"
|
||||
return demangled
|
||||
|
||||
|
||||
def batch_demangle(
|
||||
symbols: list[str],
|
||||
cppfilt_path: str | None = None,
|
||||
objdump_path: str | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""Batch demangle C++ symbol names.
|
||||
|
||||
Args:
|
||||
symbols: List of symbol names to demangle
|
||||
cppfilt_path: Path to c++filt binary (auto-detected if not provided)
|
||||
objdump_path: Path to objdump binary to derive c++filt path from
|
||||
|
||||
Returns:
|
||||
Dictionary mapping original symbol names to demangled names
|
||||
"""
|
||||
cache: dict[str, str] = {}
|
||||
|
||||
if not symbols:
|
||||
return cache
|
||||
|
||||
# Find c++filt tool
|
||||
cppfilt_cmd = cppfilt_path or find_tool("c++filt", objdump_path)
|
||||
if not cppfilt_cmd:
|
||||
_LOGGER.warning("Could not find c++filt, symbols will not be demangled")
|
||||
return {s: s for s in symbols}
|
||||
|
||||
_LOGGER.debug("Demangling %d symbols using %s", len(symbols), cppfilt_cmd)
|
||||
|
||||
# Strip GCC optimization suffixes and prefixes before demangling
|
||||
symbols_stripped: list[str] = []
|
||||
symbols_prefixes: list[str] = []
|
||||
for symbol in symbols:
|
||||
stripped, prefix = _strip_gcc_annotations(symbol)
|
||||
symbols_stripped.append(stripped)
|
||||
symbols_prefixes.append(prefix)
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[cppfilt_cmd],
|
||||
input="\n".join(symbols_stripped),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
except (subprocess.SubprocessError, OSError, UnicodeDecodeError) as e:
|
||||
_LOGGER.warning("Failed to batch demangle symbols: %s", e)
|
||||
return {s: s for s in symbols}
|
||||
|
||||
if result.returncode != 0:
|
||||
_LOGGER.warning(
|
||||
"c++filt exited with code %d: %s",
|
||||
result.returncode,
|
||||
result.stderr[:200] if result.stderr else "(no error output)",
|
||||
)
|
||||
return {s: s for s in symbols}
|
||||
|
||||
# Process demangled output
|
||||
demangled_lines = result.stdout.strip().split("\n")
|
||||
|
||||
# Check for output length mismatch
|
||||
if len(demangled_lines) != len(symbols):
|
||||
_LOGGER.warning(
|
||||
"c++filt output mismatch: expected %d lines, got %d",
|
||||
len(symbols),
|
||||
len(demangled_lines),
|
||||
)
|
||||
return {s: s for s in symbols}
|
||||
|
||||
failed_count = 0
|
||||
|
||||
for original, stripped, prefix, demangled in zip(
|
||||
symbols, symbols_stripped, symbols_prefixes, demangled_lines
|
||||
):
|
||||
# Add back any prefix that was removed
|
||||
demangled = _restore_symbol_prefix(prefix, stripped, demangled)
|
||||
|
||||
# If we stripped a suffix, add it back to the demangled name for clarity
|
||||
if original != stripped and not prefix:
|
||||
demangled = _restore_symbol_suffix(original, demangled)
|
||||
|
||||
cache[original] = demangled
|
||||
|
||||
# Count symbols that failed to demangle
|
||||
if stripped == demangled and stripped.startswith("_Z"):
|
||||
failed_count += 1
|
||||
if failed_count <= 5:
|
||||
_LOGGER.debug("Failed to demangle: %s", original)
|
||||
|
||||
if failed_count > 0:
|
||||
_LOGGER.debug(
|
||||
"Failed to demangle %d/%d symbols using %s",
|
||||
failed_count,
|
||||
len(symbols),
|
||||
cppfilt_cmd,
|
||||
)
|
||||
|
||||
return cache
|
||||
493
esphome/analyze_memory/ram_strings.py
Normal file
493
esphome/analyze_memory/ram_strings.py
Normal file
@@ -0,0 +1,493 @@
|
||||
"""Analyzer for RAM-stored strings in ESP8266/ESP32 firmware ELF files.
|
||||
|
||||
This module identifies strings that are stored in RAM sections (.data, .bss, .rodata)
|
||||
rather than in flash sections (.irom0.text, .irom.text), which is important for
|
||||
memory-constrained platforms like ESP8266.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from .demangle import batch_demangle
|
||||
from .toolchain import find_tool
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# ESP8266: .rodata is in RAM (DRAM), not flash
|
||||
# ESP32: .rodata is in flash, mapped to data bus
|
||||
ESP8266_RAM_SECTIONS = frozenset([".data", ".rodata", ".bss"])
|
||||
ESP8266_FLASH_SECTIONS = frozenset([".irom0.text", ".irom.text", ".text"])
|
||||
|
||||
# ESP32: .rodata is memory-mapped from flash
|
||||
ESP32_RAM_SECTIONS = frozenset([".data", ".bss", ".dram0.data", ".dram0.bss"])
|
||||
ESP32_FLASH_SECTIONS = frozenset([".text", ".rodata", ".flash.text", ".flash.rodata"])
|
||||
|
||||
# nm symbol types for data symbols (D=global data, d=local data, R=rodata, B=bss)
|
||||
DATA_SYMBOL_TYPES = frozenset(["D", "d", "R", "r", "B", "b"])
|
||||
|
||||
|
||||
@dataclass
|
||||
class SectionInfo:
|
||||
"""Information about an ELF section."""
|
||||
|
||||
name: str
|
||||
address: int
|
||||
size: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class RamString:
|
||||
"""A string found in RAM."""
|
||||
|
||||
section: str
|
||||
address: int
|
||||
content: str
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
"""Size in bytes including null terminator."""
|
||||
return len(self.content) + 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class RamSymbol:
|
||||
"""A symbol found in RAM."""
|
||||
|
||||
name: str
|
||||
sym_type: str
|
||||
address: int
|
||||
size: int
|
||||
section: str
|
||||
demangled: str = "" # Demangled name, set after batch demangling
|
||||
|
||||
|
||||
class RamStringsAnalyzer:
|
||||
"""Analyzes ELF files to find strings stored in RAM."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
elf_path: str,
|
||||
objdump_path: str | None = None,
|
||||
min_length: int = 8,
|
||||
platform: str = "esp32",
|
||||
) -> None:
|
||||
"""Initialize the RAM strings analyzer.
|
||||
|
||||
Args:
|
||||
elf_path: Path to the ELF file to analyze
|
||||
objdump_path: Path to objdump binary (used to find other tools)
|
||||
min_length: Minimum string length to report (default: 8)
|
||||
platform: Platform name ("esp8266", "esp32", etc.) for section mapping
|
||||
"""
|
||||
self.elf_path = Path(elf_path)
|
||||
if not self.elf_path.exists():
|
||||
raise FileNotFoundError(f"ELF file not found: {elf_path}")
|
||||
|
||||
self.objdump_path = objdump_path
|
||||
self.min_length = min_length
|
||||
self.platform = platform
|
||||
|
||||
# Set RAM/flash sections based on platform
|
||||
if self.platform == "esp8266":
|
||||
self.ram_sections = ESP8266_RAM_SECTIONS
|
||||
self.flash_sections = ESP8266_FLASH_SECTIONS
|
||||
else:
|
||||
# ESP32 and other platforms
|
||||
self.ram_sections = ESP32_RAM_SECTIONS
|
||||
self.flash_sections = ESP32_FLASH_SECTIONS
|
||||
|
||||
self.sections: dict[str, SectionInfo] = {}
|
||||
self.ram_strings: list[RamString] = []
|
||||
self.ram_symbols: list[RamSymbol] = []
|
||||
|
||||
def _run_command(self, cmd: list[str]) -> str:
|
||||
"""Run a command and return its output."""
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
return result.stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
_LOGGER.debug("Command failed: %s - %s", " ".join(cmd), e.stderr)
|
||||
raise
|
||||
except FileNotFoundError:
|
||||
_LOGGER.warning("Command not found: %s", cmd[0])
|
||||
raise
|
||||
|
||||
def analyze(self) -> None:
|
||||
"""Perform the full RAM analysis."""
|
||||
self._parse_sections()
|
||||
self._extract_strings()
|
||||
self._analyze_symbols()
|
||||
self._demangle_symbols()
|
||||
|
||||
def _parse_sections(self) -> None:
|
||||
"""Parse section headers from ELF file."""
|
||||
objdump = find_tool("objdump", self.objdump_path)
|
||||
if not objdump:
|
||||
_LOGGER.error("Could not find objdump command")
|
||||
return
|
||||
|
||||
try:
|
||||
output = self._run_command([objdump, "-h", str(self.elf_path)])
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
return
|
||||
|
||||
# Parse section headers
|
||||
# Format: Idx Name Size VMA LMA File off Algn
|
||||
section_pattern = re.compile(
|
||||
r"^\s*\d+\s+(\S+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)"
|
||||
)
|
||||
|
||||
for line in output.split("\n"):
|
||||
if match := section_pattern.match(line):
|
||||
name = match.group(1)
|
||||
size = int(match.group(2), 16)
|
||||
vma = int(match.group(3), 16)
|
||||
self.sections[name] = SectionInfo(name, vma, size)
|
||||
|
||||
def _extract_strings(self) -> None:
|
||||
"""Extract strings from RAM sections."""
|
||||
objdump = find_tool("objdump", self.objdump_path)
|
||||
if not objdump:
|
||||
return
|
||||
|
||||
for section_name in self.ram_sections:
|
||||
if section_name not in self.sections:
|
||||
continue
|
||||
|
||||
try:
|
||||
output = self._run_command(
|
||||
[objdump, "-s", "-j", section_name, str(self.elf_path)]
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
# Section may exist but have no content (e.g., .bss)
|
||||
continue
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
strings = self._parse_hex_dump(output, section_name)
|
||||
self.ram_strings.extend(strings)
|
||||
|
||||
def _parse_hex_dump(self, output: str, section_name: str) -> list[RamString]:
|
||||
"""Parse hex dump output to extract strings.
|
||||
|
||||
Args:
|
||||
output: Output from objdump -s
|
||||
section_name: Name of the section being parsed
|
||||
|
||||
Returns:
|
||||
List of RamString objects
|
||||
"""
|
||||
strings: list[RamString] = []
|
||||
current_string = bytearray()
|
||||
string_start_addr = 0
|
||||
|
||||
for line in output.split("\n"):
|
||||
# Lines look like: " 3ffef8a0 00000000 00000000 00000000 00000000 ................"
|
||||
match = re.match(r"^\s+([0-9a-fA-F]+)\s+((?:[0-9a-fA-F]{2,8}\s*)+)", line)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
addr = int(match.group(1), 16)
|
||||
hex_data = match.group(2).strip()
|
||||
|
||||
# Convert hex to bytes
|
||||
hex_bytes = hex_data.split()
|
||||
byte_offset = 0
|
||||
for hex_chunk in hex_bytes:
|
||||
# Handle both byte-by-byte and word formats
|
||||
for i in range(0, len(hex_chunk), 2):
|
||||
byte_val = int(hex_chunk[i : i + 2], 16)
|
||||
if 0x20 <= byte_val <= 0x7E: # Printable ASCII
|
||||
if not current_string:
|
||||
string_start_addr = addr + byte_offset
|
||||
current_string.append(byte_val)
|
||||
else:
|
||||
if byte_val == 0 and len(current_string) >= self.min_length:
|
||||
# Found null terminator
|
||||
strings.append(
|
||||
RamString(
|
||||
section=section_name,
|
||||
address=string_start_addr,
|
||||
content=current_string.decode(
|
||||
"ascii", errors="ignore"
|
||||
),
|
||||
)
|
||||
)
|
||||
current_string = bytearray()
|
||||
byte_offset += 1
|
||||
|
||||
return strings
|
||||
|
||||
def _analyze_symbols(self) -> None:
|
||||
"""Analyze symbols in RAM sections."""
|
||||
nm = find_tool("nm", self.objdump_path)
|
||||
if not nm:
|
||||
return
|
||||
|
||||
try:
|
||||
output = self._run_command([nm, "-S", "--size-sort", str(self.elf_path)])
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
return
|
||||
|
||||
for line in output.split("\n"):
|
||||
parts = line.split()
|
||||
if len(parts) < 4:
|
||||
continue
|
||||
|
||||
try:
|
||||
addr = int(parts[0], 16)
|
||||
size = int(parts[1], 16) if parts[1] != "?" else 0
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
sym_type = parts[2]
|
||||
name = " ".join(parts[3:])
|
||||
|
||||
# Filter for data symbols
|
||||
if sym_type not in DATA_SYMBOL_TYPES:
|
||||
continue
|
||||
|
||||
# Check if symbol is in a RAM section
|
||||
for section_name in self.ram_sections:
|
||||
if section_name not in self.sections:
|
||||
continue
|
||||
|
||||
section = self.sections[section_name]
|
||||
if section.address <= addr < section.address + section.size:
|
||||
self.ram_symbols.append(
|
||||
RamSymbol(
|
||||
name=name,
|
||||
sym_type=sym_type,
|
||||
address=addr,
|
||||
size=size,
|
||||
section=section_name,
|
||||
)
|
||||
)
|
||||
break
|
||||
|
||||
def _demangle_symbols(self) -> None:
|
||||
"""Batch demangle all RAM symbol names."""
|
||||
if not self.ram_symbols:
|
||||
return
|
||||
|
||||
# Collect all symbol names and demangle them
|
||||
symbol_names = [s.name for s in self.ram_symbols]
|
||||
demangle_cache = batch_demangle(symbol_names, objdump_path=self.objdump_path)
|
||||
|
||||
# Assign demangled names to symbols
|
||||
for symbol in self.ram_symbols:
|
||||
symbol.demangled = demangle_cache.get(symbol.name, symbol.name)
|
||||
|
||||
def _get_sections_size(self, section_names: frozenset[str]) -> int:
|
||||
"""Get total size of specified sections."""
|
||||
return sum(
|
||||
section.size
|
||||
for name, section in self.sections.items()
|
||||
if name in section_names
|
||||
)
|
||||
|
||||
def get_total_ram_usage(self) -> int:
|
||||
"""Get total RAM usage from RAM sections."""
|
||||
return self._get_sections_size(self.ram_sections)
|
||||
|
||||
def get_total_flash_usage(self) -> int:
|
||||
"""Get total flash usage from flash sections."""
|
||||
return self._get_sections_size(self.flash_sections)
|
||||
|
||||
def get_total_string_bytes(self) -> int:
|
||||
"""Get total bytes used by strings in RAM."""
|
||||
return sum(s.size for s in self.ram_strings)
|
||||
|
||||
def get_repeated_strings(self) -> list[tuple[str, int]]:
|
||||
"""Find strings that appear multiple times.
|
||||
|
||||
Returns:
|
||||
List of (string, count) tuples sorted by potential savings
|
||||
"""
|
||||
string_counts: dict[str, int] = defaultdict(int)
|
||||
for ram_string in self.ram_strings:
|
||||
string_counts[ram_string.content] += 1
|
||||
|
||||
return sorted(
|
||||
[(s, c) for s, c in string_counts.items() if c > 1],
|
||||
key=lambda x: x[1] * (len(x[0]) + 1),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
def get_long_strings(self, min_len: int = 20) -> list[RamString]:
|
||||
"""Get strings longer than the specified length.
|
||||
|
||||
Args:
|
||||
min_len: Minimum string length
|
||||
|
||||
Returns:
|
||||
List of RamString objects sorted by length
|
||||
"""
|
||||
return sorted(
|
||||
[s for s in self.ram_strings if len(s.content) >= min_len],
|
||||
key=lambda x: len(x.content),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
def get_largest_symbols(self, min_size: int = 100) -> list[RamSymbol]:
|
||||
"""Get RAM symbols larger than the specified size.
|
||||
|
||||
Args:
|
||||
min_size: Minimum symbol size in bytes
|
||||
|
||||
Returns:
|
||||
List of RamSymbol objects sorted by size
|
||||
"""
|
||||
return sorted(
|
||||
[s for s in self.ram_symbols if s.size >= min_size],
|
||||
key=lambda x: x.size,
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
def generate_report(self, show_all_sections: bool = False) -> str:
|
||||
"""Generate a formatted RAM strings analysis report.
|
||||
|
||||
Args:
|
||||
show_all_sections: If True, show all sections, not just RAM
|
||||
|
||||
Returns:
|
||||
Formatted report string
|
||||
"""
|
||||
lines: list[str] = []
|
||||
table_width = 80
|
||||
|
||||
lines.append("=" * table_width)
|
||||
lines.append(
|
||||
f"RAM Strings Analysis ({self.platform.upper()})".center(table_width)
|
||||
)
|
||||
lines.append("=" * table_width)
|
||||
lines.append("")
|
||||
|
||||
# Section Analysis
|
||||
lines.append("SECTION ANALYSIS")
|
||||
lines.append("-" * table_width)
|
||||
lines.append(f"{'Section':<20} {'Address':<12} {'Size':<12} {'Location'}")
|
||||
lines.append("-" * table_width)
|
||||
|
||||
total_ram_usage = 0
|
||||
total_flash_usage = 0
|
||||
|
||||
for name, section in sorted(self.sections.items(), key=lambda x: x[1].address):
|
||||
if name in self.ram_sections:
|
||||
location = "RAM"
|
||||
total_ram_usage += section.size
|
||||
elif name in self.flash_sections:
|
||||
location = "FLASH"
|
||||
total_flash_usage += section.size
|
||||
else:
|
||||
location = "OTHER"
|
||||
|
||||
if show_all_sections or name in self.ram_sections:
|
||||
lines.append(
|
||||
f"{name:<20} 0x{section.address:08x} {section.size:>8} B {location}"
|
||||
)
|
||||
|
||||
lines.append("-" * table_width)
|
||||
lines.append(f"Total RAM sections size: {total_ram_usage:,} bytes")
|
||||
lines.append(f"Total Flash sections size: {total_flash_usage:,} bytes")
|
||||
|
||||
# Strings in RAM
|
||||
lines.append("")
|
||||
lines.append("=" * table_width)
|
||||
lines.append("STRINGS IN RAM SECTIONS")
|
||||
lines.append("=" * table_width)
|
||||
lines.append(
|
||||
"Note: .bss sections contain uninitialized data (no strings to extract)"
|
||||
)
|
||||
|
||||
# Group strings by section
|
||||
strings_by_section: dict[str, list[RamString]] = defaultdict(list)
|
||||
for ram_string in self.ram_strings:
|
||||
strings_by_section[ram_string.section].append(ram_string)
|
||||
|
||||
for section_name in sorted(strings_by_section.keys()):
|
||||
section_strings = strings_by_section[section_name]
|
||||
lines.append(f"\nSection: {section_name}")
|
||||
lines.append("-" * 40)
|
||||
for ram_string in sorted(section_strings, key=lambda x: x.address):
|
||||
clean_string = ram_string.content[:100] + (
|
||||
"..." if len(ram_string.content) > 100 else ""
|
||||
)
|
||||
lines.append(
|
||||
f' 0x{ram_string.address:08x}: "{clean_string}" (len={len(ram_string.content)})'
|
||||
)
|
||||
|
||||
# Large RAM symbols
|
||||
lines.append("")
|
||||
lines.append("=" * table_width)
|
||||
lines.append("LARGE DATA SYMBOLS IN RAM (>= 50 bytes)")
|
||||
lines.append("=" * table_width)
|
||||
|
||||
largest_symbols = self.get_largest_symbols(50)
|
||||
lines.append(f"\n{'Symbol':<50} {'Type':<6} {'Size':<10} {'Section'}")
|
||||
lines.append("-" * table_width)
|
||||
|
||||
for symbol in largest_symbols:
|
||||
# Use demangled name if available, otherwise raw name
|
||||
display_name = symbol.demangled or symbol.name
|
||||
name_display = display_name[:49] if len(display_name) > 49 else display_name
|
||||
lines.append(
|
||||
f"{name_display:<50} {symbol.sym_type:<6} {symbol.size:>8} B {symbol.section}"
|
||||
)
|
||||
|
||||
# Summary
|
||||
lines.append("")
|
||||
lines.append("=" * table_width)
|
||||
lines.append("SUMMARY")
|
||||
lines.append("=" * table_width)
|
||||
lines.append(f"Total strings found in RAM: {len(self.ram_strings)}")
|
||||
total_string_bytes = self.get_total_string_bytes()
|
||||
lines.append(f"Total bytes used by strings: {total_string_bytes:,}")
|
||||
|
||||
# Optimization targets
|
||||
lines.append("")
|
||||
lines.append("=" * table_width)
|
||||
lines.append("POTENTIAL OPTIMIZATION TARGETS")
|
||||
lines.append("=" * table_width)
|
||||
|
||||
# Repeated strings
|
||||
repeated = self.get_repeated_strings()[:10]
|
||||
if repeated:
|
||||
lines.append("\nRepeated strings (could be deduplicated):")
|
||||
for string, count in repeated:
|
||||
savings = (count - 1) * (len(string) + 1)
|
||||
clean_string = string[:50] + ("..." if len(string) > 50 else "")
|
||||
lines.append(
|
||||
f' "{clean_string}" - appears {count} times (potential savings: {savings} bytes)'
|
||||
)
|
||||
|
||||
# Long strings - platform-specific advice
|
||||
long_strings = self.get_long_strings(20)[:10]
|
||||
if long_strings:
|
||||
if self.platform == "esp8266":
|
||||
lines.append(
|
||||
"\nLong strings that could be moved to PROGMEM (>= 20 chars):"
|
||||
)
|
||||
else:
|
||||
# ESP32: strings in DRAM are typically there for a reason
|
||||
# (interrupt handlers, pre-flash-init code, etc.)
|
||||
lines.append("\nLong strings in DRAM (>= 20 chars):")
|
||||
lines.append(
|
||||
"Note: ESP32 DRAM strings may be required for interrupt/early-boot contexts"
|
||||
)
|
||||
for ram_string in long_strings:
|
||||
clean_string = ram_string.content[:60] + (
|
||||
"..." if len(ram_string.content) > 60 else ""
|
||||
)
|
||||
lines.append(
|
||||
f' {ram_string.section} @ 0x{ram_string.address:08x}: "{clean_string}" ({len(ram_string.content)} bytes)'
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
57
esphome/analyze_memory/toolchain.py
Normal file
57
esphome/analyze_memory/toolchain.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""Toolchain utilities for memory analysis."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Platform-specific toolchain prefixes
|
||||
TOOLCHAIN_PREFIXES = [
|
||||
"xtensa-lx106-elf-", # ESP8266
|
||||
"xtensa-esp32-elf-", # ESP32
|
||||
"xtensa-esp-elf-", # ESP32 (newer IDF)
|
||||
"", # System default (no prefix)
|
||||
]
|
||||
|
||||
|
||||
def find_tool(
|
||||
tool_name: str,
|
||||
objdump_path: str | None = None,
|
||||
) -> str | None:
|
||||
"""Find a toolchain tool by name.
|
||||
|
||||
First tries to derive the tool path from objdump_path (if provided),
|
||||
then falls back to searching for platform-specific tools.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool (e.g., "objdump", "nm", "c++filt")
|
||||
objdump_path: Path to objdump binary to derive other tool paths from
|
||||
|
||||
Returns:
|
||||
Path to the tool or None if not found
|
||||
"""
|
||||
# Try to derive from objdump path first (most reliable)
|
||||
if objdump_path and objdump_path != "objdump":
|
||||
objdump_file = Path(objdump_path)
|
||||
# Replace just the filename portion, preserving any prefix (e.g., xtensa-esp32-elf-)
|
||||
new_name = objdump_file.name.replace("objdump", tool_name)
|
||||
potential_path = str(objdump_file.with_name(new_name))
|
||||
if Path(potential_path).exists():
|
||||
_LOGGER.debug("Found %s at: %s", tool_name, potential_path)
|
||||
return potential_path
|
||||
|
||||
# Try platform-specific tools
|
||||
for prefix in TOOLCHAIN_PREFIXES:
|
||||
cmd = f"{prefix}{tool_name}"
|
||||
try:
|
||||
subprocess.run([cmd, "--version"], capture_output=True, check=True)
|
||||
_LOGGER.debug("Found %s: %s", tool_name, cmd)
|
||||
return cmd
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
continue
|
||||
|
||||
_LOGGER.warning("Could not find %s tool", tool_name)
|
||||
return None
|
||||
@@ -107,6 +107,17 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
4: adc_channel_t.ADC_CHANNEL_3,
|
||||
5: adc_channel_t.ADC_CHANNEL_4,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32p4/include/soc/adc_channel.h
|
||||
VARIANT_ESP32P4: {
|
||||
16: adc_channel_t.ADC_CHANNEL_0,
|
||||
17: adc_channel_t.ADC_CHANNEL_1,
|
||||
18: adc_channel_t.ADC_CHANNEL_2,
|
||||
19: adc_channel_t.ADC_CHANNEL_3,
|
||||
20: adc_channel_t.ADC_CHANNEL_4,
|
||||
21: adc_channel_t.ADC_CHANNEL_5,
|
||||
22: adc_channel_t.ADC_CHANNEL_6,
|
||||
23: adc_channel_t.ADC_CHANNEL_7,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
|
||||
VARIANT_ESP32S2: {
|
||||
1: adc_channel_t.ADC_CHANNEL_0,
|
||||
@@ -133,16 +144,6 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
9: adc_channel_t.ADC_CHANNEL_8,
|
||||
10: adc_channel_t.ADC_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32P4: {
|
||||
16: adc_channel_t.ADC_CHANNEL_0,
|
||||
17: adc_channel_t.ADC_CHANNEL_1,
|
||||
18: adc_channel_t.ADC_CHANNEL_2,
|
||||
19: adc_channel_t.ADC_CHANNEL_3,
|
||||
20: adc_channel_t.ADC_CHANNEL_4,
|
||||
21: adc_channel_t.ADC_CHANNEL_5,
|
||||
22: adc_channel_t.ADC_CHANNEL_6,
|
||||
23: adc_channel_t.ADC_CHANNEL_7,
|
||||
},
|
||||
}
|
||||
|
||||
# pin to adc2 channel mapping
|
||||
@@ -175,6 +176,15 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
|
||||
VARIANT_ESP32C6: {}, # no ADC2
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
|
||||
VARIANT_ESP32H2: {}, # no ADC2
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32p4/include/soc/adc_channel.h
|
||||
VARIANT_ESP32P4: {
|
||||
49: adc_channel_t.ADC_CHANNEL_0,
|
||||
50: adc_channel_t.ADC_CHANNEL_1,
|
||||
51: adc_channel_t.ADC_CHANNEL_2,
|
||||
52: adc_channel_t.ADC_CHANNEL_3,
|
||||
53: adc_channel_t.ADC_CHANNEL_4,
|
||||
54: adc_channel_t.ADC_CHANNEL_5,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
|
||||
VARIANT_ESP32S2: {
|
||||
11: adc_channel_t.ADC_CHANNEL_0,
|
||||
@@ -201,14 +211,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
|
||||
19: adc_channel_t.ADC_CHANNEL_8,
|
||||
20: adc_channel_t.ADC_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32P4: {
|
||||
49: adc_channel_t.ADC_CHANNEL_0,
|
||||
50: adc_channel_t.ADC_CHANNEL_1,
|
||||
51: adc_channel_t.ADC_CHANNEL_2,
|
||||
52: adc_channel_t.ADC_CHANNEL_3,
|
||||
53: adc_channel_t.ADC_CHANNEL_4,
|
||||
54: adc_channel_t.ADC_CHANNEL_5,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ void ADCSensor::setup() {
|
||||
adc_cali_handle_t handle = nullptr;
|
||||
|
||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
||||
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
|
||||
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
|
||||
// RISC-V variants and S3 use curve fitting calibration
|
||||
adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
@@ -111,7 +111,7 @@ void ADCSensor::setup() {
|
||||
ESP_LOGW(TAG, "Line fitting calibration failed with error %d, will use uncalibrated readings", err);
|
||||
this->setup_flags_.calibration_complete = false;
|
||||
}
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32S3 || ESP32H2
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3
|
||||
}
|
||||
|
||||
this->setup_flags_.init_complete = true;
|
||||
@@ -186,11 +186,11 @@ float ADCSensor::sample_fixed_attenuation_() {
|
||||
ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err);
|
||||
if (this->calibration_handle_ != nullptr) {
|
||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
||||
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
|
||||
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
|
||||
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
|
||||
#else // Other ESP32 variants use line fitting calibration
|
||||
adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32S3 || ESP32H2
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3
|
||||
this->calibration_handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -219,7 +219,7 @@ float ADCSensor::sample_autorange_() {
|
||||
if (this->calibration_handle_ != nullptr) {
|
||||
// Delete old calibration handle
|
||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
||||
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
|
||||
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
|
||||
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
|
||||
#else
|
||||
adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
|
||||
@@ -231,7 +231,7 @@ float ADCSensor::sample_autorange_() {
|
||||
adc_cali_handle_t handle = nullptr;
|
||||
|
||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
||||
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
|
||||
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
|
||||
adc_cali_curve_fitting_config_t cali_config = {};
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
cali_config.chan = this->channel_;
|
||||
@@ -266,7 +266,7 @@ float ADCSensor::sample_autorange_() {
|
||||
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
|
||||
if (handle != nullptr) {
|
||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
||||
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
|
||||
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
|
||||
adc_cali_delete_scheme_curve_fitting(handle);
|
||||
#else
|
||||
adc_cali_delete_scheme_line_fitting(handle);
|
||||
@@ -288,7 +288,7 @@ float ADCSensor::sample_autorange_() {
|
||||
}
|
||||
// Clean up calibration handle
|
||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
||||
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
|
||||
USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
|
||||
adc_cali_delete_scheme_curve_fitting(handle);
|
||||
#else
|
||||
adc_cali_delete_scheme_line_fitting(handle);
|
||||
|
||||
@@ -24,6 +24,8 @@ from esphome.const import (
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@angelnu"]
|
||||
|
||||
CONF_CURRENT_A = "current_a"
|
||||
CONF_CURRENT_B = "current_b"
|
||||
CONF_ACTIVE_POWER_A = "active_power_a"
|
||||
|
||||
@@ -25,7 +25,8 @@ void ADE7953::setup() {
|
||||
this->ade_write_8(PGA_V_8, pga_v_);
|
||||
this->ade_write_8(PGA_IA_8, pga_ia_);
|
||||
this->ade_write_8(PGA_IB_8, pga_ib_);
|
||||
this->ade_write_32(AVGAIN_32, vgain_);
|
||||
this->ade_write_32(AVGAIN_32, avgain_);
|
||||
this->ade_write_32(BVGAIN_32, bvgain_);
|
||||
this->ade_write_32(AIGAIN_32, aigain_);
|
||||
this->ade_write_32(BIGAIN_32, bigain_);
|
||||
this->ade_write_32(AWGAIN_32, awgain_);
|
||||
@@ -34,7 +35,8 @@ void ADE7953::setup() {
|
||||
this->ade_read_8(PGA_V_8, &pga_v_);
|
||||
this->ade_read_8(PGA_IA_8, &pga_ia_);
|
||||
this->ade_read_8(PGA_IB_8, &pga_ib_);
|
||||
this->ade_read_32(AVGAIN_32, &vgain_);
|
||||
this->ade_read_32(AVGAIN_32, &avgain_);
|
||||
this->ade_read_32(BVGAIN_32, &bvgain_);
|
||||
this->ade_read_32(AIGAIN_32, &aigain_);
|
||||
this->ade_read_32(BIGAIN_32, &bigain_);
|
||||
this->ade_read_32(AWGAIN_32, &awgain_);
|
||||
@@ -63,13 +65,14 @@ void ADE7953::dump_config() {
|
||||
" PGA_V_8: 0x%X\n"
|
||||
" PGA_IA_8: 0x%X\n"
|
||||
" PGA_IB_8: 0x%X\n"
|
||||
" VGAIN_32: 0x%08jX\n"
|
||||
" AVGAIN_32: 0x%08jX\n"
|
||||
" BVGAIN_32: 0x%08jX\n"
|
||||
" AIGAIN_32: 0x%08jX\n"
|
||||
" BIGAIN_32: 0x%08jX\n"
|
||||
" AWGAIN_32: 0x%08jX\n"
|
||||
" BWGAIN_32: 0x%08jX",
|
||||
this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) vgain_, (uintmax_t) aigain_,
|
||||
(uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_);
|
||||
this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) avgain_, (uintmax_t) bvgain_,
|
||||
(uintmax_t) aigain_, (uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_);
|
||||
}
|
||||
|
||||
#define ADE_PUBLISH_(name, val, factor) \
|
||||
|
||||
@@ -46,7 +46,12 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
|
||||
void set_pga_ib(uint8_t pga_ib) { pga_ib_ = pga_ib; }
|
||||
|
||||
// Set input gains
|
||||
void set_vgain(uint32_t vgain) { vgain_ = vgain; }
|
||||
void set_vgain(uint32_t vgain) {
|
||||
// Datasheet says: "to avoid discrepancies in other registers,
|
||||
// if AVGAIN is set then BVGAIN should be set to the same value."
|
||||
avgain_ = vgain;
|
||||
bvgain_ = vgain;
|
||||
}
|
||||
void set_aigain(uint32_t aigain) { aigain_ = aigain; }
|
||||
void set_bigain(uint32_t bigain) { bigain_ = bigain; }
|
||||
void set_awgain(uint32_t awgain) { awgain_ = awgain; }
|
||||
@@ -100,7 +105,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
|
||||
uint8_t pga_v_;
|
||||
uint8_t pga_ia_;
|
||||
uint8_t pga_ib_;
|
||||
uint32_t vgain_;
|
||||
uint32_t avgain_;
|
||||
uint32_t bvgain_;
|
||||
uint32_t aigain_;
|
||||
uint32_t bigain_;
|
||||
uint32_t awgain_;
|
||||
|
||||
@@ -12,10 +12,11 @@ void AnalogThresholdBinarySensor::setup() {
|
||||
// TRUE state is defined to be when sensor is >= threshold
|
||||
// so when undefined sensor value initialize to FALSE
|
||||
if (std::isnan(sensor_value)) {
|
||||
this->raw_state_ = false;
|
||||
this->publish_initial_state(false);
|
||||
} else {
|
||||
this->publish_initial_state(sensor_value >=
|
||||
(this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
|
||||
this->raw_state_ = sensor_value >= (this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f;
|
||||
this->publish_initial_state(this->raw_state_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +26,10 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
|
||||
this->sensor_->add_on_state_callback([this](float sensor_value) {
|
||||
// if there is an invalid sensor reading, ignore the change and keep the current state
|
||||
if (!std::isnan(sensor_value)) {
|
||||
this->publish_state(sensor_value >=
|
||||
(this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
|
||||
// Use raw_state_ for hysteresis logic, not this->state which is post-filter
|
||||
this->raw_state_ =
|
||||
sensor_value >= (this->raw_state_ ? this->lower_threshold_.value() : this->upper_threshold_.value());
|
||||
this->publish_state(this->raw_state_);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
TemplatableValue<float> upper_threshold_{};
|
||||
TemplatableValue<float> lower_threshold_{};
|
||||
bool raw_state_{false}; // Pre-filter state for hysteresis logic
|
||||
};
|
||||
|
||||
} // namespace analog_threshold
|
||||
|
||||
@@ -589,6 +589,7 @@ enum SensorStateClass {
|
||||
STATE_CLASS_MEASUREMENT = 1;
|
||||
STATE_CLASS_TOTAL_INCREASING = 2;
|
||||
STATE_CLASS_TOTAL = 3;
|
||||
STATE_CLASS_MEASUREMENT_ANGLE = 4;
|
||||
}
|
||||
|
||||
// Deprecated in API version 1.5
|
||||
|
||||
@@ -169,8 +169,7 @@ void APIConnection::loop() {
|
||||
} else {
|
||||
this->last_traffic_ = now;
|
||||
// read a packet
|
||||
this->read_message(buffer.data_len, buffer.type,
|
||||
buffer.data_len > 0 ? &buffer.container[buffer.data_offset] : nullptr);
|
||||
this->read_message(buffer.data_len, buffer.type, buffer.data);
|
||||
if (this->flags_.remove)
|
||||
return;
|
||||
}
|
||||
@@ -195,6 +194,9 @@ void APIConnection::loop() {
|
||||
}
|
||||
// Now that everything is sent, enable immediate sending for future state changes
|
||||
this->flags_.should_try_send_immediately = true;
|
||||
// Release excess memory from buffers that grew during initial sync
|
||||
this->deferred_batch_.release_buffer();
|
||||
this->helper_->release_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -554,10 +554,8 @@ class APIConnection final : public APIServerConnection {
|
||||
std::vector<BatchItem> items;
|
||||
uint32_t batch_start_time{0};
|
||||
|
||||
DeferredBatch() {
|
||||
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
||||
items.reserve(8);
|
||||
}
|
||||
// No pre-allocation - log connections never use batching, and for
|
||||
// connections that do, buffers are released after initial sync anyway
|
||||
|
||||
// Add item to the batch
|
||||
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||
@@ -576,6 +574,15 @@ class APIConnection final : public APIServerConnection {
|
||||
bool empty() const { return items.empty(); }
|
||||
size_t size() const { return items.size(); }
|
||||
const BatchItem &operator[](size_t index) const { return items[index]; }
|
||||
// Release excess capacity - only releases if items already empty
|
||||
void release_buffer() {
|
||||
// Safe to call: batch is processed before release_buffer is called,
|
||||
// and if any items remain (partial processing), we must not clear them.
|
||||
// Use swap trick since shrink_to_fit() is non-binding and may be ignored.
|
||||
if (items.empty()) {
|
||||
std::vector<BatchItem>().swap(items);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// DeferredBatch here (16 bytes, 4-byte aligned)
|
||||
|
||||
@@ -35,10 +35,9 @@ struct ClientInfo;
|
||||
class ProtoWriteBuffer;
|
||||
|
||||
struct ReadPacketBuffer {
|
||||
std::vector<uint8_t> container;
|
||||
uint16_t type;
|
||||
uint16_t data_offset;
|
||||
const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call)
|
||||
uint16_t data_len;
|
||||
uint16_t type;
|
||||
};
|
||||
|
||||
// Packed packet info structure to minimize memory usage
|
||||
@@ -119,6 +118,22 @@ class APIFrameHelper {
|
||||
uint8_t frame_footer_size() const { return frame_footer_size_; }
|
||||
// Check if socket has data ready to read
|
||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||
// Release excess memory from internal buffers after initial sync
|
||||
void release_buffers() {
|
||||
// rx_buf_: Safe to clear only if no partial read in progress.
|
||||
// rx_buf_len_ tracks bytes read so far; if non-zero, we're mid-frame
|
||||
// and clearing would lose partially received data.
|
||||
if (this->rx_buf_len_ == 0) {
|
||||
// Use swap trick since shrink_to_fit() is non-binding and may be ignored
|
||||
std::vector<uint8_t>().swap(this->rx_buf_);
|
||||
}
|
||||
// reusable_iovs_: Safe to release unconditionally.
|
||||
// Only used within write_protobuf_packets() calls - cleared at start,
|
||||
// populated with pointers, used for writev(), then function returns.
|
||||
// The iovecs contain stale pointers after the call (data was either sent
|
||||
// or copied to tx_buf_), and are cleared on next write_protobuf_packets().
|
||||
std::vector<struct iovec>().swap(this->reusable_iovs_);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Buffer containing data to be sent
|
||||
|
||||
@@ -407,8 +407,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
buffer->container = std::move(this->rx_buf_);
|
||||
buffer->data_offset = 4;
|
||||
buffer->data = msg_data + 4; // Skip 4-byte header (type + length)
|
||||
buffer->data_len = data_len;
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
|
||||
@@ -210,8 +210,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return aerr;
|
||||
}
|
||||
|
||||
buffer->container = std::move(this->rx_buf_);
|
||||
buffer->data_offset = 0;
|
||||
buffer->data = this->rx_buf_.data();
|
||||
buffer->data_len = this->rx_header_parsed_len_;
|
||||
buffer->type = this->rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
|
||||
@@ -51,6 +51,7 @@ enum SensorStateClass : uint32_t {
|
||||
STATE_CLASS_MEASUREMENT = 1,
|
||||
STATE_CLASS_TOTAL_INCREASING = 2,
|
||||
STATE_CLASS_TOTAL = 3,
|
||||
STATE_CLASS_MEASUREMENT_ANGLE = 4,
|
||||
};
|
||||
#endif
|
||||
enum LogLevel : uint32_t {
|
||||
|
||||
@@ -179,6 +179,8 @@ template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::Sens
|
||||
return "STATE_CLASS_TOTAL_INCREASING";
|
||||
case enums::STATE_CLASS_TOTAL:
|
||||
return "STATE_CLASS_TOTAL";
|
||||
case enums::STATE_CLASS_MEASUREMENT_ANGLE:
|
||||
return "STATE_CLASS_MEASUREMENT_ANGLE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ void APIServerConnectionBase::log_send_message_(const char *name, const std::str
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case HelloRequest::MESSAGE_TYPE: {
|
||||
HelloRequest msg;
|
||||
@@ -827,7 +827,7 @@ void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { th
|
||||
void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); }
|
||||
#endif
|
||||
|
||||
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
// Check authentication/connection requirements for messages
|
||||
switch (msg_type) {
|
||||
case HelloRequest::MESSAGE_TYPE: // No setup required
|
||||
|
||||
@@ -218,7 +218,7 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
class APIServerConnection : public APIServerConnectionBase {
|
||||
@@ -480,7 +480,7 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
|
||||
#endif
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -52,11 +52,6 @@ void APIServer::setup() {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Schedule reboot if no clients connect within timeout
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
this->schedule_reboot_timeout_();
|
||||
}
|
||||
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
if (this->socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket");
|
||||
@@ -101,42 +96,22 @@ void APIServer::setup() {
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_on_log_callback(
|
||||
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||
if (this->shutting_down_) {
|
||||
// Don't try to send logs during shutdown
|
||||
// as it could result in a recursion and
|
||||
// we would be filling a buffer we are trying to clear
|
||||
return;
|
||||
}
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->flags_.remove && c->get_log_subscription_level() >= level)
|
||||
c->try_send_log_message(level, tag, message, message_len);
|
||||
}
|
||||
});
|
||||
logger::global_logger->add_log_listener(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
if (camera::Camera::instance() != nullptr && !camera::Camera::instance()->is_internal()) {
|
||||
camera::Camera::instance()->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->flags_.remove)
|
||||
c->set_camera_state(image);
|
||||
}
|
||||
});
|
||||
camera::Camera::instance()->add_listener(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIServer::schedule_reboot_timeout_() {
|
||||
this->status_set_warning();
|
||||
this->set_timeout("api_reboot", this->reboot_timeout_, []() {
|
||||
if (!global_api_server->is_connected()) {
|
||||
ESP_LOGE(TAG, "No clients; rebooting");
|
||||
App.reboot();
|
||||
}
|
||||
});
|
||||
// Initialize last_connected_ for reboot timeout tracking
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
// Set warning status if reboot timeout is enabled
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
this->status_set_warning();
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::loop() {
|
||||
@@ -164,15 +139,24 @@ void APIServer::loop() {
|
||||
this->clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
|
||||
// Clear warning status and cancel reboot when first client connects
|
||||
// First client connected - clear warning and update timestamp
|
||||
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
|
||||
this->status_clear_warning();
|
||||
this->cancel_timeout("api_reboot");
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->clients_.empty()) {
|
||||
// Check reboot timeout - done in loop to avoid scheduler heap churn
|
||||
// (cancelled scheduler items sit in heap memory until their scheduled time)
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (now - this->last_connected_ > this->reboot_timeout_) {
|
||||
ESP_LOGE(TAG, "No clients; rebooting");
|
||||
App.reboot();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -211,9 +195,10 @@ void APIServer::loop() {
|
||||
}
|
||||
this->clients_.pop_back();
|
||||
|
||||
// Schedule reboot when last client disconnects
|
||||
// Last client disconnected - set warning and start tracking for reboot timeout
|
||||
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
||||
this->schedule_reboot_timeout_();
|
||||
this->status_set_warning();
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
}
|
||||
// Don't increment client_index since we need to process the swapped element
|
||||
}
|
||||
@@ -541,6 +526,30 @@ bool APIServer::is_connected(bool state_subscription_only) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
void APIServer::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
if (this->shutting_down_) {
|
||||
// Don't try to send logs during shutdown
|
||||
// as it could result in a recursion and
|
||||
// we would be filling a buffer we are trying to clear
|
||||
return;
|
||||
}
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->flags_.remove && c->get_log_subscription_level() >= level)
|
||||
c->try_send_log_message(level, tag, message, message_len);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
void APIServer::on_camera_image(const std::shared_ptr<camera::CameraImage> &image) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->flags_.remove)
|
||||
c->set_camera_state(image);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServer::on_shutdown() {
|
||||
this->shutting_down_ = true;
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
#include "user_services.h"
|
||||
#endif
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
#include "esphome/components/camera/camera.h"
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
@@ -27,7 +33,17 @@ struct SavedNoisePsk {
|
||||
} PACKED; // NOLINT
|
||||
#endif
|
||||
|
||||
class APIServer : public Component, public Controller {
|
||||
class APIServer : public Component,
|
||||
public Controller
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
,
|
||||
public camera::CameraListener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
APIServer();
|
||||
void setup() override;
|
||||
@@ -37,6 +53,12 @@ class APIServer : public Component, public Controller {
|
||||
void dump_config() override;
|
||||
void on_shutdown() override;
|
||||
bool teardown() override;
|
||||
#ifdef USE_LOGGER
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override;
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
|
||||
#endif
|
||||
#ifdef USE_API_PASSWORD
|
||||
bool check_password(const uint8_t *password_data, size_t password_len) const;
|
||||
void set_password(const std::string &password);
|
||||
@@ -180,7 +202,6 @@ class APIServer : public Component, public Controller {
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void schedule_reboot_timeout_();
|
||||
#ifdef USE_API_NOISE
|
||||
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
||||
const psk_t &active_psk, bool make_active);
|
||||
@@ -196,6 +217,7 @@ class APIServer : public Component, public Controller {
|
||||
|
||||
// 4-byte aligned types
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t last_connected_{0};
|
||||
|
||||
// Vectors and strings (12 bytes each on 32-bit)
|
||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||
|
||||
@@ -846,7 +846,7 @@ class ProtoService {
|
||||
*/
|
||||
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
|
||||
virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||
virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0;
|
||||
|
||||
// Optimized method that pre-allocates buffer based on message size
|
||||
bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
#include "automation.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
const char *const Automation::TAG = "ble_client.automation";
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
// placeholder class for static TAG .
|
||||
class Automation {
|
||||
@@ -391,7 +390,6 @@ template<typename... Ts> class BLEClientDisconnectAction : public Action<Ts...>,
|
||||
BLEClient *ble_client_;
|
||||
std::tuple<Ts...> var_{};
|
||||
};
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
static const char *const TAG = "ble_client";
|
||||
|
||||
@@ -82,7 +81,6 @@ bool BLEClient::all_nodes_established_() {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
|
||||
#endif
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -75,7 +74,6 @@ class BLEClient : public BLEClientBase {
|
||||
std::vector<BLEClientNode *> nodes_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
static const char *const TAG = "ble_binary_output";
|
||||
|
||||
@@ -75,6 +74,5 @@ void BLEBinaryOutput::write_state(bool state) {
|
||||
ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err);
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
#endif
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -36,7 +35,6 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi
|
||||
esp_gatt_write_type_t write_type_{};
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
|
||||
public:
|
||||
@@ -35,7 +34,6 @@ class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
|
||||
BLESensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
static const char *const TAG = "ble_rssi_sensor";
|
||||
|
||||
@@ -78,6 +77,5 @@ void BLEClientRSSISensor::get_rssi_() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
#endif
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -29,6 +28,5 @@ class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, publ
|
||||
bool should_update_{false};
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
#endif
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
static const char *const TAG = "ble_sensor";
|
||||
|
||||
@@ -147,6 +146,5 @@ void BLESensor::update() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
#endif
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -48,6 +47,5 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie
|
||||
espbt::ESPBTUUID descr_uuid_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
#endif
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
static const char *const TAG = "ble_switch";
|
||||
|
||||
@@ -31,6 +30,5 @@ void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
||||
|
||||
void BLEClientSwitch::dump_config() { LOG_SWITCH("", "BLE Client Switch", this); }
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
#endif
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -24,6 +23,5 @@ class BLEClientSwitch : public switch_::Switch, public Component, public BLEClie
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
#endif
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSensor {
|
||||
public:
|
||||
@@ -33,7 +32,6 @@ class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSe
|
||||
BLETextSensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
static const char *const TAG = "ble_text_sensor";
|
||||
|
||||
@@ -138,6 +137,5 @@ void BLETextSensor::update() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
#endif
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
namespace esphome::ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -40,6 +39,5 @@ class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, p
|
||||
espbt::ESPBTUUID descr_uuid_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ble_client
|
||||
#endif
|
||||
|
||||
@@ -87,17 +87,21 @@ void BLENUS::setup() {
|
||||
global_ble_nus = this;
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr && this->expose_log_) {
|
||||
logger::global_logger->add_on_log_callback(
|
||||
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(message), message_len);
|
||||
const char c = '\n';
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(&c), 1);
|
||||
});
|
||||
logger::global_logger->add_log_listener(this);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
(void) level;
|
||||
(void) tag;
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(message), message_len);
|
||||
const char c = '\n';
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(&c), 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
void BLENUS::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ble nus:");
|
||||
ESP_LOGCONFIG(TAG, " log: %s", YESNO(this->expose_log_));
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
#ifdef USE_ZEPHYR
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
#include <shell/shell_bt_nus.h>
|
||||
#include <atomic>
|
||||
|
||||
namespace esphome::ble_nus {
|
||||
|
||||
class BLENUS : public Component {
|
||||
class BLENUS : public Component
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
enum TxStatus {
|
||||
TX_DISABLED,
|
||||
TX_ENABLED,
|
||||
@@ -20,6 +28,9 @@ class BLENUS : public Component {
|
||||
void loop() override;
|
||||
size_t write_array(const uint8_t *data, size_t len);
|
||||
void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; }
|
||||
#ifdef USE_LOGGER
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
static void send_enabled_callback(bt_nus_send_status status);
|
||||
|
||||
@@ -27,11 +27,13 @@ void BluetoothProxy::setup() {
|
||||
// Capture the configured scan mode from YAML before any API changes
|
||||
this->configured_scan_active_ = this->parent_->get_scan_active();
|
||||
|
||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
this->send_bluetooth_scanner_state_(state);
|
||||
}
|
||||
});
|
||||
this->parent_->add_scanner_state_listener(this);
|
||||
}
|
||||
|
||||
void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
this->send_bluetooth_scanner_state_(state);
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
|
||||
|
||||
@@ -52,7 +52,9 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
||||
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
|
||||
};
|
||||
|
||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
public esp32_ble_tracker::BLEScannerStateListener,
|
||||
public Component {
|
||||
friend class BluetoothConnection; // Allow connection to update connections_free_response_
|
||||
public:
|
||||
BluetoothProxy();
|
||||
@@ -108,6 +110,9 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
|
||||
void set_active(bool active) { this->active_ = active; }
|
||||
bool has_active() { return this->active_; }
|
||||
|
||||
/// BLEScannerStateListener interface
|
||||
void on_scanner_state(esp32_ble_tracker::ScannerState state) override;
|
||||
|
||||
uint32_t get_legacy_version() const {
|
||||
if (this->active_) {
|
||||
return LEGACY_ACTIVE_CONNECTIONS_VERSION;
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace button {
|
||||
namespace esphome::button {
|
||||
|
||||
template<typename... Ts> class PressAction : public Action<Ts...> {
|
||||
public:
|
||||
@@ -24,5 +23,4 @@ class ButtonPressTrigger : public Trigger<> {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace button
|
||||
} // namespace esphome
|
||||
} // namespace esphome::button
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#include "button.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace button {
|
||||
namespace esphome::button {
|
||||
|
||||
static const char *const TAG = "button";
|
||||
|
||||
@@ -26,5 +25,4 @@ void Button::press() {
|
||||
}
|
||||
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
|
||||
|
||||
} // namespace button
|
||||
} // namespace esphome
|
||||
} // namespace esphome::button
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace button {
|
||||
namespace esphome::button {
|
||||
|
||||
class Button;
|
||||
void log_button(const char *tag, const char *prefix, const char *type, Button *obj);
|
||||
@@ -45,5 +44,4 @@ class Button : public EntityBase, public EntityBase_DeviceClass {
|
||||
CallbackManager<void()> press_callback_{};
|
||||
};
|
||||
|
||||
} // namespace button
|
||||
} // namespace esphome
|
||||
} // namespace esphome::button
|
||||
|
||||
@@ -35,6 +35,21 @@ inline const char *to_string(PixelFormat format) {
|
||||
return "PIXEL_FORMAT_UNKNOWN";
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
class CameraImage;
|
||||
|
||||
/** Listener interface for camera events.
|
||||
*
|
||||
* Components can implement this interface to receive camera notifications
|
||||
* (new images, stream start/stop) without the overhead of std::function callbacks.
|
||||
*/
|
||||
class CameraListener {
|
||||
public:
|
||||
virtual void on_camera_image(const std::shared_ptr<CameraImage> &image) {}
|
||||
virtual void on_stream_start() {}
|
||||
virtual void on_stream_stop() {}
|
||||
};
|
||||
|
||||
/** Abstract camera image base class.
|
||||
* Encapsulates the JPEG encoded data and it is shared among
|
||||
* all connected clients.
|
||||
@@ -87,12 +102,12 @@ struct CameraImageSpec {
|
||||
};
|
||||
|
||||
/** Abstract camera base class. Collaborates with API.
|
||||
* 1) API server starts and installs callback (add_image_callback)
|
||||
* which is called by the camera when a new image is available.
|
||||
* 1) API server starts and registers as a listener (add_listener)
|
||||
* to receive new images from the camera.
|
||||
* 2) New API client connects and creates a new image reader (create_image_reader).
|
||||
* 3) API connection receives protobuf CameraImageRequest and calls request_image.
|
||||
* 3.a) API connection receives protobuf CameraImageRequest and calls start_stream.
|
||||
* 4) Camera implementation provides JPEG data in the CameraImage and calls callback.
|
||||
* 4) Camera implementation provides JPEG data in the CameraImage and notifies listeners.
|
||||
* 5) API connection sets the image in the image reader.
|
||||
* 6) API connection consumes data from the image reader and returns the image when finished.
|
||||
* 7.a) Camera captures a new image and continues with 4) until start_stream is called.
|
||||
@@ -100,8 +115,8 @@ struct CameraImageSpec {
|
||||
class Camera : public EntityBase, public Component {
|
||||
public:
|
||||
Camera();
|
||||
// Camera implementation invokes callback to publish a new image.
|
||||
virtual void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) = 0;
|
||||
/// Add a listener to receive camera events
|
||||
virtual void add_listener(CameraListener *listener) = 0;
|
||||
/// Returns a new camera image reader that keeps track of the JPEG data in the camera image.
|
||||
virtual CameraImageReader *create_image_reader() = 0;
|
||||
// Connection, camera or web server requests one new JPEG image.
|
||||
|
||||
220
esphome/components/cc1101/__init__.py
Normal file
220
esphome/components/cc1101/__init__.py
Normal file
@@ -0,0 +1,220 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import spi
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_WAIT_TIME
|
||||
|
||||
CODEOWNERS = ["@lygris", "@gabest11"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
MULTI_CONF = True
|
||||
|
||||
ns = cg.esphome_ns.namespace("cc1101")
|
||||
CC1101Component = ns.class_("CC1101Component", cg.Component, spi.SPIDevice)
|
||||
|
||||
# Config keys
|
||||
CONF_OUTPUT_POWER = "output_power"
|
||||
CONF_RX_ATTENUATION = "rx_attenuation"
|
||||
CONF_DC_BLOCKING_FILTER = "dc_blocking_filter"
|
||||
CONF_IF_FREQUENCY = "if_frequency"
|
||||
CONF_FILTER_BANDWIDTH = "filter_bandwidth"
|
||||
CONF_CHANNEL_SPACING = "channel_spacing"
|
||||
CONF_FSK_DEVIATION = "fsk_deviation"
|
||||
CONF_MSK_DEVIATION = "msk_deviation"
|
||||
CONF_SYMBOL_RATE = "symbol_rate"
|
||||
CONF_SYNC_MODE = "sync_mode"
|
||||
CONF_CARRIER_SENSE_ABOVE_THRESHOLD = "carrier_sense_above_threshold"
|
||||
CONF_MODULATION_TYPE = "modulation_type"
|
||||
CONF_MANCHESTER = "manchester"
|
||||
CONF_NUM_PREAMBLE = "num_preamble"
|
||||
CONF_SYNC1 = "sync1"
|
||||
CONF_SYNC0 = "sync0"
|
||||
CONF_PKTLEN = "pktlen"
|
||||
CONF_MAGN_TARGET = "magn_target"
|
||||
CONF_MAX_LNA_GAIN = "max_lna_gain"
|
||||
CONF_MAX_DVGA_GAIN = "max_dvga_gain"
|
||||
CONF_CARRIER_SENSE_ABS_THR = "carrier_sense_abs_thr"
|
||||
CONF_CARRIER_SENSE_REL_THR = "carrier_sense_rel_thr"
|
||||
CONF_LNA_PRIORITY = "lna_priority"
|
||||
CONF_FILTER_LENGTH_FSK_MSK = "filter_length_fsk_msk"
|
||||
CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook"
|
||||
CONF_FREEZE = "freeze"
|
||||
CONF_HYST_LEVEL = "hyst_level"
|
||||
|
||||
# Enums
|
||||
SyncMode = ns.enum("SyncMode", True)
|
||||
SYNC_MODE = {
|
||||
"None": SyncMode.SYNC_MODE_NONE,
|
||||
"15/16": SyncMode.SYNC_MODE_15_16,
|
||||
"16/16": SyncMode.SYNC_MODE_16_16,
|
||||
"30/32": SyncMode.SYNC_MODE_30_32,
|
||||
}
|
||||
|
||||
Modulation = ns.enum("Modulation", True)
|
||||
MODULATION = {
|
||||
"2-FSK": Modulation.MODULATION_2_FSK,
|
||||
"GFSK": Modulation.MODULATION_GFSK,
|
||||
"ASK/OOK": Modulation.MODULATION_ASK_OOK,
|
||||
"4-FSK": Modulation.MODULATION_4_FSK,
|
||||
"MSK": Modulation.MODULATION_MSK,
|
||||
}
|
||||
|
||||
RxAttenuation = ns.enum("RxAttenuation", True)
|
||||
RX_ATTENUATION = {
|
||||
"0dB": RxAttenuation.RX_ATTENUATION_0DB,
|
||||
"6dB": RxAttenuation.RX_ATTENUATION_6DB,
|
||||
"12dB": RxAttenuation.RX_ATTENUATION_12DB,
|
||||
"18dB": RxAttenuation.RX_ATTENUATION_18DB,
|
||||
}
|
||||
|
||||
MagnTarget = ns.enum("MagnTarget", True)
|
||||
MAGN_TARGET = {
|
||||
"24dB": MagnTarget.MAGN_TARGET_24DB,
|
||||
"27dB": MagnTarget.MAGN_TARGET_27DB,
|
||||
"30dB": MagnTarget.MAGN_TARGET_30DB,
|
||||
"33dB": MagnTarget.MAGN_TARGET_33DB,
|
||||
"36dB": MagnTarget.MAGN_TARGET_36DB,
|
||||
"38dB": MagnTarget.MAGN_TARGET_38DB,
|
||||
"40dB": MagnTarget.MAGN_TARGET_40DB,
|
||||
"42dB": MagnTarget.MAGN_TARGET_42DB,
|
||||
}
|
||||
|
||||
MaxLnaGain = ns.enum("MaxLnaGain", True)
|
||||
MAX_LNA_GAIN = {
|
||||
"Default": MaxLnaGain.MAX_LNA_GAIN_DEFAULT,
|
||||
"2.6dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_2P6DB,
|
||||
"6.1dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_6P1DB,
|
||||
"7.4dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_7P4DB,
|
||||
"9.2dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_9P2DB,
|
||||
"11.5dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_11P5DB,
|
||||
"14.6dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_14P6DB,
|
||||
"17.1dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_17P1DB,
|
||||
}
|
||||
|
||||
MaxDvgaGain = ns.enum("MaxDvgaGain", True)
|
||||
MAX_DVGA_GAIN = {
|
||||
"Default": MaxDvgaGain.MAX_DVGA_GAIN_DEFAULT,
|
||||
"-1": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_1,
|
||||
"-2": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_2,
|
||||
"-3": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_3,
|
||||
}
|
||||
|
||||
CarrierSenseRelThr = ns.enum("CarrierSenseRelThr", True)
|
||||
CARRIER_SENSE_REL_THR = {
|
||||
"Default": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_DEFAULT,
|
||||
"+6dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_6DB,
|
||||
"+10dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_10DB,
|
||||
"+14dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_14DB,
|
||||
}
|
||||
|
||||
FilterLengthFskMsk = ns.enum("FilterLengthFskMsk", True)
|
||||
FILTER_LENGTH_FSK_MSK = {
|
||||
"8": FilterLengthFskMsk.FILTER_LENGTH_8DB,
|
||||
"16": FilterLengthFskMsk.FILTER_LENGTH_16DB,
|
||||
"32": FilterLengthFskMsk.FILTER_LENGTH_32DB,
|
||||
"64": FilterLengthFskMsk.FILTER_LENGTH_64DB,
|
||||
}
|
||||
|
||||
FilterLengthAskOok = ns.enum("FilterLengthAskOok", True)
|
||||
FILTER_LENGTH_ASK_OOK = {
|
||||
"4dB": FilterLengthAskOok.FILTER_LENGTH_4DB,
|
||||
"8dB": FilterLengthAskOok.FILTER_LENGTH_8DB,
|
||||
"12dB": FilterLengthAskOok.FILTER_LENGTH_12DB,
|
||||
"16dB": FilterLengthAskOok.FILTER_LENGTH_16DB,
|
||||
}
|
||||
|
||||
Freeze = ns.enum("Freeze", True)
|
||||
FREEZE = {
|
||||
"Default": Freeze.FREEZE_DEFAULT,
|
||||
"On Sync": Freeze.FREEZE_ON_SYNC,
|
||||
"Analog Only": Freeze.FREEZE_ANALOG_ONLY,
|
||||
"Analog And Digital": Freeze.FREEZE_ANALOG_AND_DIGITAL,
|
||||
}
|
||||
|
||||
WaitTime = ns.enum("WaitTime", True)
|
||||
WAIT_TIME = {
|
||||
"8": WaitTime.WAIT_TIME_8_SAMPLES,
|
||||
"16": WaitTime.WAIT_TIME_16_SAMPLES,
|
||||
"24": WaitTime.WAIT_TIME_24_SAMPLES,
|
||||
"32": WaitTime.WAIT_TIME_32_SAMPLES,
|
||||
}
|
||||
|
||||
HystLevel = ns.enum("HystLevel", True)
|
||||
HYST_LEVEL = {
|
||||
"None": HystLevel.HYST_LEVEL_NONE,
|
||||
"Low": HystLevel.HYST_LEVEL_LOW,
|
||||
"Medium": HystLevel.HYST_LEVEL_MEDIUM,
|
||||
"High": HystLevel.HYST_LEVEL_HIGH,
|
||||
}
|
||||
|
||||
# Config key -> Validator mapping
|
||||
CONFIG_MAP = {
|
||||
CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0),
|
||||
CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False),
|
||||
CONF_DC_BLOCKING_FILTER: cv.boolean,
|
||||
CONF_FREQUENCY: cv.float_range(min=300000.0, max=928000.0),
|
||||
CONF_IF_FREQUENCY: cv.float_range(min=25, max=788),
|
||||
CONF_FILTER_BANDWIDTH: cv.float_range(min=58.0, max=812.0),
|
||||
CONF_CHANNEL: cv.uint8_t,
|
||||
CONF_CHANNEL_SPACING: cv.float_range(min=25, max=405),
|
||||
CONF_FSK_DEVIATION: cv.float_range(min=1.5, max=381),
|
||||
CONF_MSK_DEVIATION: cv.int_range(min=1, max=8),
|
||||
CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000),
|
||||
CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False),
|
||||
CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean,
|
||||
CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False),
|
||||
CONF_MANCHESTER: cv.boolean,
|
||||
CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7),
|
||||
CONF_SYNC1: cv.hex_uint8_t,
|
||||
CONF_SYNC0: cv.hex_uint8_t,
|
||||
CONF_PKTLEN: cv.uint8_t,
|
||||
CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False),
|
||||
CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False),
|
||||
CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False),
|
||||
CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7),
|
||||
CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False),
|
||||
CONF_LNA_PRIORITY: cv.boolean,
|
||||
CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False),
|
||||
CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False),
|
||||
CONF_FREEZE: cv.enum(FREEZE, upper=False),
|
||||
CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False),
|
||||
CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema({cv.GenerateID(): cv.declare_id(CC1101Component)})
|
||||
.extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()})
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(spi.spi_device_schema(cs_pin_required=True))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
for key in CONFIG_MAP:
|
||||
if key in config:
|
||||
cg.add(getattr(var, f"set_{key}")(config[key]))
|
||||
|
||||
|
||||
# Actions
|
||||
BeginTxAction = ns.class_("BeginTxAction", automation.Action)
|
||||
BeginRxAction = ns.class_("BeginRxAction", automation.Action)
|
||||
ResetAction = ns.class_("ResetAction", automation.Action)
|
||||
SetIdleAction = ns.class_("SetIdleAction", automation.Action)
|
||||
|
||||
CC1101_ACTION_SCHEMA = cv.Schema(
|
||||
maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)})
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action("cc1101.begin_tx", BeginTxAction, CC1101_ACTION_SCHEMA)
|
||||
@automation.register_action("cc1101.begin_rx", BeginRxAction, CC1101_ACTION_SCHEMA)
|
||||
@automation.register_action("cc1101.reset", ResetAction, CC1101_ACTION_SCHEMA)
|
||||
@automation.register_action("cc1101.set_idle", SetIdleAction, CC1101_ACTION_SCHEMA)
|
||||
async def cc1101_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
550
esphome/components/cc1101/cc1101.cpp
Normal file
550
esphome/components/cc1101/cc1101.cpp
Normal file
@@ -0,0 +1,550 @@
|
||||
#include "cc1101.h"
|
||||
#include "cc1101pa.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome::cc1101 {
|
||||
|
||||
static const char *const TAG = "cc1101";
|
||||
|
||||
static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) {
|
||||
int e_tmp;
|
||||
float m_tmp = std::frexp(value, &e_tmp);
|
||||
if (e_tmp <= mbits) {
|
||||
e = 0;
|
||||
m = 0;
|
||||
return;
|
||||
}
|
||||
e = static_cast<uint8_t>(e_tmp - mbits - 1);
|
||||
m = static_cast<uint32_t>(((m_tmp * 2 - 1) * (1 << (mbits + 1))) + 1) >> 1;
|
||||
if (m == (1UL << mbits)) {
|
||||
e = e + 1;
|
||||
m = 0;
|
||||
}
|
||||
}
|
||||
|
||||
CC1101Component::CC1101Component() {
|
||||
// Datasheet defaults
|
||||
memset(&this->state_, 0, sizeof(this->state_));
|
||||
this->state_.GDO2_CFG = 0x0D; // Serial Data (for RX on GDO2)
|
||||
this->state_.GDO1_CFG = 0x2E;
|
||||
this->state_.GDO0_CFG = 0x0D; // Serial Data (for RX on GDO0 / TX Input)
|
||||
this->state_.FIFO_THR = 7;
|
||||
this->state_.SYNC1 = 0xD3;
|
||||
this->state_.SYNC0 = 0x91;
|
||||
this->state_.PKTLEN = 0xFF;
|
||||
this->state_.APPEND_STATUS = 1;
|
||||
this->state_.LENGTH_CONFIG = 1;
|
||||
this->state_.CRC_EN = 1;
|
||||
this->state_.WHITE_DATA = 1;
|
||||
this->state_.FREQ_IF = 0x0F;
|
||||
this->state_.FREQ2 = 0x1E;
|
||||
this->state_.FREQ1 = 0xC4;
|
||||
this->state_.FREQ0 = 0xEC;
|
||||
this->state_.DRATE_E = 0x0C;
|
||||
this->state_.CHANBW_E = 0x02;
|
||||
this->state_.DRATE_M = 0x22;
|
||||
this->state_.SYNC_MODE = 2;
|
||||
this->state_.CHANSPC_E = 2;
|
||||
this->state_.NUM_PREAMBLE = 2;
|
||||
this->state_.CHANSPC_M = 0xF8;
|
||||
this->state_.DEVIATION_M = 7;
|
||||
this->state_.DEVIATION_E = 4;
|
||||
this->state_.RX_TIME = 7;
|
||||
this->state_.CCA_MODE = 3;
|
||||
this->state_.PO_TIMEOUT = 1;
|
||||
this->state_.FOC_LIMIT = 2;
|
||||
this->state_.FOC_POST_K = 1;
|
||||
this->state_.FOC_PRE_K = 2;
|
||||
this->state_.FOC_BS_CS_GATE = 1;
|
||||
this->state_.BS_POST_KP = 1;
|
||||
this->state_.BS_POST_KI = 1;
|
||||
this->state_.BS_PRE_KP = 2;
|
||||
this->state_.BS_PRE_KI = 1;
|
||||
this->state_.MAGN_TARGET = 3;
|
||||
this->state_.AGC_LNA_PRIORITY = 1;
|
||||
this->state_.FILTER_LENGTH = 1;
|
||||
this->state_.WAIT_TIME = 1;
|
||||
this->state_.HYST_LEVEL = 2;
|
||||
this->state_.WOREVT1 = 0x87;
|
||||
this->state_.WOREVT0 = 0x6B;
|
||||
this->state_.RC_CAL = 1;
|
||||
this->state_.EVENT1 = 7;
|
||||
this->state_.RC_PD = 1;
|
||||
this->state_.MIX_CURRENT = 2;
|
||||
this->state_.LODIV_BUF_CURRENT_RX = 1;
|
||||
this->state_.LNA2MIX_CURRENT = 1;
|
||||
this->state_.LNA_CURRENT = 1;
|
||||
this->state_.LODIV_BUF_CURRENT_TX = 1;
|
||||
this->state_.FSCAL3_LO = 9;
|
||||
this->state_.CHP_CURR_CAL_EN = 2;
|
||||
this->state_.FSCAL3_HI = 2;
|
||||
this->state_.FSCAL2 = 0x0A;
|
||||
this->state_.FSCAL1 = 0x20;
|
||||
this->state_.FSCAL0 = 0x0D;
|
||||
this->state_.RCCTRL1 = 0x41;
|
||||
this->state_.FSTEST = 0x59;
|
||||
this->state_.PTEST = 0x7F;
|
||||
this->state_.AGCTEST = 0x3F;
|
||||
this->state_.TEST2 = 0x88;
|
||||
this->state_.TEST1 = 0x31;
|
||||
this->state_.TEST0_LO = 1;
|
||||
this->state_.VCO_SEL_CAL_EN = 1;
|
||||
this->state_.TEST0_HI = 2;
|
||||
|
||||
// PKTCTRL0
|
||||
this->state_.PKT_FORMAT = 3;
|
||||
this->state_.LENGTH_CONFIG = 2;
|
||||
this->state_.FS_AUTOCAL = 1;
|
||||
|
||||
// Default Settings
|
||||
this->set_frequency(433920);
|
||||
this->set_if_frequency(153);
|
||||
this->set_filter_bandwidth(203);
|
||||
this->set_channel(0);
|
||||
this->set_channel_spacing(200);
|
||||
this->set_symbol_rate(5000);
|
||||
this->set_sync_mode(SyncMode::SYNC_MODE_NONE);
|
||||
this->set_carrier_sense_above_threshold(true);
|
||||
this->set_modulation_type(Modulation::MODULATION_ASK_OOK);
|
||||
this->set_magn_target(MagnTarget::MAGN_TARGET_42DB);
|
||||
this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT);
|
||||
this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3);
|
||||
this->set_lna_priority(false);
|
||||
this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES);
|
||||
|
||||
// CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence)
|
||||
memset(this->pa_table_, 0, sizeof(this->pa_table_));
|
||||
this->set_output_power(10.0f);
|
||||
}
|
||||
|
||||
void CC1101Component::setup() {
|
||||
this->spi_setup();
|
||||
this->cs_->digital_write(true);
|
||||
delayMicroseconds(1);
|
||||
this->cs_->digital_write(false);
|
||||
delayMicroseconds(1);
|
||||
this->cs_->digital_write(true);
|
||||
delayMicroseconds(41);
|
||||
this->cs_->digital_write(false);
|
||||
delay(5);
|
||||
|
||||
this->strobe_(Command::RES);
|
||||
delay(5);
|
||||
|
||||
this->read_(Register::PARTNUM);
|
||||
this->read_(Register::VERSION);
|
||||
this->chip_id_ = encode_uint16(this->state_.PARTNUM, this->state_.VERSION);
|
||||
ESP_LOGD(TAG, "CC1101 found! Chip ID: 0x%04X", this->chip_id_);
|
||||
if (this->state_.VERSION == 0 || this->state_.PARTNUM == 0xFF) {
|
||||
ESP_LOGE(TAG, "Failed to verify CC1101.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->initialized_ = true;
|
||||
|
||||
for (uint8_t i = 0; i <= static_cast<uint8_t>(Register::TEST0); i++) {
|
||||
if (i == static_cast<uint8_t>(Register::FSTEST) || i == static_cast<uint8_t>(Register::AGCTEST)) {
|
||||
continue;
|
||||
}
|
||||
this->write_(static_cast<Register>(i));
|
||||
}
|
||||
this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_));
|
||||
this->strobe_(Command::RX);
|
||||
}
|
||||
|
||||
void CC1101Component::dump_config() {
|
||||
static const char *const MODULATION_NAMES[] = {"2-FSK", "GFSK", "UNUSED", "ASK/OOK",
|
||||
"4-FSK", "UNUSED", "UNUSED", "MSK"};
|
||||
int32_t freq = static_cast<int32_t>(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) *
|
||||
XTAL_FREQUENCY / (1 << 16);
|
||||
float symbol_rate =
|
||||
(((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY * 1000.0f;
|
||||
float bw = XTAL_FREQUENCY / (8.0f * (4 + this->state_.CHANBW_M) * (1 << this->state_.CHANBW_E));
|
||||
ESP_LOGCONFIG(TAG, "CC1101:");
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Chip ID: 0x%04X\n"
|
||||
" Frequency: %" PRId32 " kHz\n"
|
||||
" Channel: %u\n"
|
||||
" Modulation: %s\n"
|
||||
" Symbol Rate: %.0f baud\n"
|
||||
" Filter Bandwidth: %.1f kHz\n"
|
||||
" Output Power: %.1f dBm",
|
||||
this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07],
|
||||
symbol_rate, bw, this->output_power_effective_);
|
||||
}
|
||||
|
||||
void CC1101Component::begin_tx() {
|
||||
// Ensure Packet Format is 3 (Async Serial), use GDO0 as input during TX
|
||||
this->write_(Register::PKTCTRL0, 0x32);
|
||||
ESP_LOGV(TAG, "Beginning TX sequence");
|
||||
this->strobe_(Command::TX);
|
||||
if (!this->wait_for_state_(State::TX, 50)) {
|
||||
ESP_LOGW(TAG, "Timed out waiting for TX state!");
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::begin_rx() {
|
||||
ESP_LOGV(TAG, "Beginning RX sequence");
|
||||
this->strobe_(Command::RX);
|
||||
}
|
||||
|
||||
void CC1101Component::reset() {
|
||||
this->strobe_(Command::RES);
|
||||
this->setup();
|
||||
}
|
||||
|
||||
void CC1101Component::set_idle() {
|
||||
ESP_LOGV(TAG, "Setting IDLE state");
|
||||
this->enter_idle_();
|
||||
}
|
||||
|
||||
void CC1101Component::set_gdo0_config(uint8_t value) {
|
||||
this->state_.GDO0_CFG = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::IOCFG0);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_gdo2_config(uint8_t value) {
|
||||
this->state_.GDO2_CFG = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::IOCFG2);
|
||||
}
|
||||
}
|
||||
|
||||
bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) {
|
||||
uint32_t start = millis();
|
||||
while (millis() - start < timeout_ms) {
|
||||
this->read_(Register::MARCSTATE);
|
||||
State s = static_cast<State>(this->state_.MARC_STATE);
|
||||
if (s == target_state) {
|
||||
return true;
|
||||
}
|
||||
delayMicroseconds(100);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CC1101Component::enter_idle_() {
|
||||
this->strobe_(Command::IDLE);
|
||||
this->wait_for_state_(State::IDLE);
|
||||
}
|
||||
|
||||
uint8_t CC1101Component::strobe_(Command cmd) {
|
||||
uint8_t index = static_cast<uint8_t>(cmd);
|
||||
if (cmd < Command::RES || cmd > Command::NOP) {
|
||||
return 0xFF;
|
||||
}
|
||||
this->enable();
|
||||
uint8_t status_byte = this->transfer_byte(index);
|
||||
this->disable();
|
||||
return status_byte;
|
||||
}
|
||||
|
||||
void CC1101Component::write_(Register reg) {
|
||||
uint8_t index = static_cast<uint8_t>(reg);
|
||||
this->enable();
|
||||
this->write_byte(index);
|
||||
this->write_array(&this->state_.regs()[index], 1);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void CC1101Component::write_(Register reg, uint8_t value) {
|
||||
uint8_t index = static_cast<uint8_t>(reg);
|
||||
this->state_.regs()[index] = value;
|
||||
this->write_(reg);
|
||||
}
|
||||
|
||||
void CC1101Component::write_(Register reg, const uint8_t *buffer, size_t length) {
|
||||
uint8_t index = static_cast<uint8_t>(reg);
|
||||
this->enable();
|
||||
this->write_byte(index | BUS_WRITE | BUS_BURST);
|
||||
this->write_array(buffer, length);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void CC1101Component::read_(Register reg) {
|
||||
uint8_t index = static_cast<uint8_t>(reg);
|
||||
this->enable();
|
||||
this->write_byte(index | BUS_READ | BUS_BURST);
|
||||
this->state_.regs()[index] = this->transfer_byte(0);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) {
|
||||
uint8_t index = static_cast<uint8_t>(reg);
|
||||
this->enable();
|
||||
this->write_byte(index | BUS_READ | BUS_BURST);
|
||||
this->read_array(buffer, length);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
// Setters
|
||||
void CC1101Component::set_output_power(float value) {
|
||||
this->output_power_requested_ = value;
|
||||
int32_t freq = static_cast<int32_t>(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) *
|
||||
XTAL_FREQUENCY / (1 << 16);
|
||||
uint8_t a = 0xC0;
|
||||
if (freq >= 300000 && freq <= 348000) {
|
||||
a = PowerTableItem::find(PA_TABLE_315, sizeof(PA_TABLE_315) / sizeof(PA_TABLE_315[0]), value);
|
||||
} else if (freq >= 378000 && freq <= 464000) {
|
||||
a = PowerTableItem::find(PA_TABLE_433, sizeof(PA_TABLE_433) / sizeof(PA_TABLE_433[0]), value);
|
||||
} else if (freq >= 779000 && freq < 900000) {
|
||||
a = PowerTableItem::find(PA_TABLE_868, sizeof(PA_TABLE_868) / sizeof(PA_TABLE_868[0]), value);
|
||||
} else if (freq >= 900000 && freq <= 928000) {
|
||||
a = PowerTableItem::find(PA_TABLE_915, sizeof(PA_TABLE_915) / sizeof(PA_TABLE_915[0]), value);
|
||||
}
|
||||
|
||||
if (static_cast<Modulation>(this->state_.MOD_FORMAT) == Modulation::MODULATION_ASK_OOK) {
|
||||
this->pa_table_[0] = 0;
|
||||
this->pa_table_[1] = a;
|
||||
} else {
|
||||
this->pa_table_[0] = a;
|
||||
this->pa_table_[1] = 0;
|
||||
}
|
||||
this->output_power_effective_ = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_));
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_rx_attenuation(RxAttenuation value) {
|
||||
this->state_.CLOSE_IN_RX = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::FIFOTHR);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_dc_blocking_filter(bool value) {
|
||||
this->state_.DEM_DCFILT_OFF = value ? 0 : 1;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::MDMCFG2);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_frequency(float value) {
|
||||
int32_t freq = static_cast<int32_t>(value * (1 << 16) / XTAL_FREQUENCY);
|
||||
this->state_.FREQ2 = static_cast<uint8_t>(freq >> 16);
|
||||
this->state_.FREQ1 = static_cast<uint8_t>(freq >> 8);
|
||||
this->state_.FREQ0 = static_cast<uint8_t>(freq);
|
||||
if (this->initialized_) {
|
||||
this->enter_idle_();
|
||||
this->write_(Register::FREQ2);
|
||||
this->write_(Register::FREQ1);
|
||||
this->write_(Register::FREQ0);
|
||||
this->strobe_(Command::RX);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_if_frequency(float value) {
|
||||
this->state_.FREQ_IF = value * (1 << 10) / XTAL_FREQUENCY;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::FSCTRL1);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_filter_bandwidth(float value) {
|
||||
uint8_t e;
|
||||
uint32_t m;
|
||||
split_float(XTAL_FREQUENCY / (value * 8), 2, e, m);
|
||||
this->state_.CHANBW_E = e;
|
||||
this->state_.CHANBW_M = static_cast<uint8_t>(m);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::MDMCFG4);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_channel(uint8_t value) {
|
||||
this->state_.CHANNR = value;
|
||||
if (this->initialized_) {
|
||||
this->enter_idle_();
|
||||
this->write_(Register::CHANNR);
|
||||
this->strobe_(Command::RX);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_channel_spacing(float value) {
|
||||
uint8_t e;
|
||||
uint32_t m;
|
||||
split_float(value * (1 << 18) / XTAL_FREQUENCY, 8, e, m);
|
||||
this->state_.CHANSPC_E = e;
|
||||
this->state_.CHANSPC_M = static_cast<uint8_t>(m);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::MDMCFG1);
|
||||
this->write_(Register::MDMCFG0);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_fsk_deviation(float value) {
|
||||
uint8_t e;
|
||||
uint32_t m;
|
||||
split_float(value * (1 << 17) / XTAL_FREQUENCY, 3, e, m);
|
||||
this->state_.DEVIATION_E = e;
|
||||
this->state_.DEVIATION_M = static_cast<uint8_t>(m);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::DEVIATN);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_msk_deviation(uint8_t value) {
|
||||
this->state_.DEVIATION_E = 0;
|
||||
this->state_.DEVIATION_M = value - 1;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::DEVIATN);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_symbol_rate(float value) {
|
||||
uint8_t e;
|
||||
uint32_t m;
|
||||
split_float(value * (1 << 28) / (XTAL_FREQUENCY * 1000), 8, e, m);
|
||||
this->state_.DRATE_E = e;
|
||||
this->state_.DRATE_M = static_cast<uint8_t>(m);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::MDMCFG4);
|
||||
this->write_(Register::MDMCFG3);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_sync_mode(SyncMode value) {
|
||||
this->state_.SYNC_MODE = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::MDMCFG2);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_carrier_sense_above_threshold(bool value) {
|
||||
this->state_.CARRIER_SENSE_ABOVE_THRESHOLD = value ? 1 : 0;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::MDMCFG2);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_modulation_type(Modulation value) {
|
||||
this->state_.MOD_FORMAT = static_cast<uint8_t>(value);
|
||||
this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0;
|
||||
if (this->initialized_) {
|
||||
this->enter_idle_();
|
||||
this->write_(Register::MDMCFG2);
|
||||
this->write_(Register::FREND0);
|
||||
this->strobe_(Command::RX);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_manchester(bool value) {
|
||||
this->state_.MANCHESTER_EN = value ? 1 : 0;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::MDMCFG2);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_num_preamble(uint8_t value) {
|
||||
this->state_.NUM_PREAMBLE = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::MDMCFG1);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_sync1(uint8_t value) {
|
||||
this->state_.SYNC1 = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::SYNC1);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_sync0(uint8_t value) {
|
||||
this->state_.SYNC0 = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::SYNC0);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_pktlen(uint8_t value) {
|
||||
this->state_.PKTLEN = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PKTLEN);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_magn_target(MagnTarget value) {
|
||||
this->state_.MAGN_TARGET = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL2);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_max_lna_gain(MaxLnaGain value) {
|
||||
this->state_.MAX_LNA_GAIN = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL2);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_max_dvga_gain(MaxDvgaGain value) {
|
||||
this->state_.MAX_DVGA_GAIN = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL2);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_carrier_sense_abs_thr(int8_t value) {
|
||||
this->state_.CARRIER_SENSE_ABS_THR = static_cast<uint8_t>(value & 0b1111);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL1);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_carrier_sense_rel_thr(CarrierSenseRelThr value) {
|
||||
this->state_.CARRIER_SENSE_REL_THR = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL1);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_lna_priority(bool value) {
|
||||
this->state_.AGC_LNA_PRIORITY = value ? 1 : 0;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL1);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_filter_length_fsk_msk(FilterLengthFskMsk value) {
|
||||
this->state_.FILTER_LENGTH = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL0);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_filter_length_ask_ook(FilterLengthAskOok value) {
|
||||
this->state_.FILTER_LENGTH = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL0);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_freeze(Freeze value) {
|
||||
this->state_.AGC_FREEZE = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL0);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_wait_time(WaitTime value) {
|
||||
this->state_.WAIT_TIME = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL0);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_hyst_level(HystLevel value) {
|
||||
this->state_.HYST_LEVEL = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::AGCCTRL0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::cc1101
|
||||
110
esphome/components/cc1101/cc1101.h
Normal file
110
esphome/components/cc1101/cc1101.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "cc1101defs.h"
|
||||
|
||||
namespace esphome::cc1101 {
|
||||
|
||||
class CC1101Component : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
CC1101Component();
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
// Actions
|
||||
void begin_tx();
|
||||
void begin_rx();
|
||||
void reset();
|
||||
void set_idle();
|
||||
|
||||
// GDO Pin Configuration
|
||||
void set_gdo0_config(uint8_t value);
|
||||
void set_gdo2_config(uint8_t value);
|
||||
|
||||
// Configuration Setters
|
||||
void set_output_power(float value);
|
||||
void set_rx_attenuation(RxAttenuation value);
|
||||
void set_dc_blocking_filter(bool value);
|
||||
|
||||
// Tuner settings
|
||||
void set_frequency(float value);
|
||||
void set_if_frequency(float value);
|
||||
void set_filter_bandwidth(float value);
|
||||
void set_channel(uint8_t value);
|
||||
void set_channel_spacing(float value);
|
||||
void set_fsk_deviation(float value);
|
||||
void set_msk_deviation(uint8_t value);
|
||||
void set_symbol_rate(float value);
|
||||
void set_sync_mode(SyncMode value);
|
||||
void set_carrier_sense_above_threshold(bool value);
|
||||
void set_modulation_type(Modulation value);
|
||||
void set_manchester(bool value);
|
||||
void set_num_preamble(uint8_t value);
|
||||
void set_sync1(uint8_t value);
|
||||
void set_sync0(uint8_t value);
|
||||
void set_pktlen(uint8_t value);
|
||||
|
||||
// AGC settings
|
||||
void set_magn_target(MagnTarget value);
|
||||
void set_max_lna_gain(MaxLnaGain value);
|
||||
void set_max_dvga_gain(MaxDvgaGain value);
|
||||
void set_carrier_sense_abs_thr(int8_t value);
|
||||
void set_carrier_sense_rel_thr(CarrierSenseRelThr value);
|
||||
void set_lna_priority(bool value);
|
||||
void set_filter_length_fsk_msk(FilterLengthFskMsk value);
|
||||
void set_filter_length_ask_ook(FilterLengthAskOok value);
|
||||
void set_freeze(Freeze value);
|
||||
void set_wait_time(WaitTime value);
|
||||
void set_hyst_level(HystLevel value);
|
||||
|
||||
protected:
|
||||
uint16_t chip_id_{0};
|
||||
bool initialized_{false};
|
||||
|
||||
float output_power_requested_{10.0f};
|
||||
float output_power_effective_{10.0f};
|
||||
uint8_t pa_table_[PA_TABLE_SIZE]{};
|
||||
|
||||
CC1101State state_;
|
||||
|
||||
// Low-level Helpers
|
||||
uint8_t strobe_(Command cmd);
|
||||
void write_(Register reg);
|
||||
void write_(Register reg, uint8_t value);
|
||||
void write_(Register reg, const uint8_t *buffer, size_t length);
|
||||
void read_(Register reg);
|
||||
void read_(Register reg, uint8_t *buffer, size_t length);
|
||||
|
||||
// State Management
|
||||
bool wait_for_state_(State target_state, uint32_t timeout_ms = 100);
|
||||
void enter_idle_();
|
||||
};
|
||||
|
||||
// Action Wrappers
|
||||
template<typename... Ts> class BeginTxAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->begin_tx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class BeginRxAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->begin_rx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->reset(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetIdleAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_idle(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::cc1101
|
||||
644
esphome/components/cc1101/cc1101defs.h
Normal file
644
esphome/components/cc1101/cc1101defs.h
Normal file
@@ -0,0 +1,644 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome::cc1101 {
|
||||
|
||||
static constexpr float XTAL_FREQUENCY = 26000;
|
||||
|
||||
static constexpr uint8_t BUS_BURST = 0x40;
|
||||
static constexpr uint8_t BUS_READ = 0x80;
|
||||
static constexpr uint8_t BUS_WRITE = 0x00;
|
||||
static constexpr uint8_t BYTES_IN_RXFIFO = 0x7F; // byte number in RXfifo
|
||||
static constexpr size_t PA_TABLE_SIZE = 8;
|
||||
|
||||
enum class Register : uint8_t {
|
||||
IOCFG2 = 0x00, // GDO2 output pin configuration
|
||||
IOCFG1 = 0x01, // GDO1 output pin configuration
|
||||
IOCFG0 = 0x02, // GDO0 output pin configuration
|
||||
FIFOTHR = 0x03, // RX FIFO and TX FIFO thresholds
|
||||
SYNC1 = 0x04, // Sync word, high INT8U
|
||||
SYNC0 = 0x05, // Sync word, low INT8U
|
||||
PKTLEN = 0x06, // Packet length
|
||||
PKTCTRL1 = 0x07, // Packet automation control
|
||||
PKTCTRL0 = 0x08, // Packet automation control
|
||||
ADDR = 0x09, // Device address
|
||||
CHANNR = 0x0A, // Channel number
|
||||
FSCTRL1 = 0x0B, // Frequency synthesizer control
|
||||
FSCTRL0 = 0x0C, // Frequency synthesizer control
|
||||
FREQ2 = 0x0D, // Frequency control word, high INT8U
|
||||
FREQ1 = 0x0E, // Frequency control word, middle INT8U
|
||||
FREQ0 = 0x0F, // Frequency control word, low INT8U
|
||||
MDMCFG4 = 0x10, // Modem configuration
|
||||
MDMCFG3 = 0x11, // Modem configuration
|
||||
MDMCFG2 = 0x12, // Modem configuration
|
||||
MDMCFG1 = 0x13, // Modem configuration
|
||||
MDMCFG0 = 0x14, // Modem configuration
|
||||
DEVIATN = 0x15, // Modem deviation setting
|
||||
MCSM2 = 0x16, // Main Radio Control State Machine configuration
|
||||
MCSM1 = 0x17, // Main Radio Control State Machine configuration
|
||||
MCSM0 = 0x18, // Main Radio Control State Machine configuration
|
||||
FOCCFG = 0x19, // Frequency Offset Compensation configuration
|
||||
BSCFG = 0x1A, // Bit Synchronization configuration
|
||||
AGCCTRL2 = 0x1B, // AGC control
|
||||
AGCCTRL1 = 0x1C, // AGC control
|
||||
AGCCTRL0 = 0x1D, // AGC control
|
||||
WOREVT1 = 0x1E, // High INT8U Event 0 timeout
|
||||
WOREVT0 = 0x1F, // Low INT8U Event 0 timeout
|
||||
WORCTRL = 0x20, // Wake On Radio control
|
||||
FREND1 = 0x21, // Front end RX configuration
|
||||
FREND0 = 0x22, // Front end TX configuration
|
||||
FSCAL3 = 0x23, // Frequency synthesizer calibration
|
||||
FSCAL2 = 0x24, // Frequency synthesizer calibration
|
||||
FSCAL1 = 0x25, // Frequency synthesizer calibration
|
||||
FSCAL0 = 0x26, // Frequency synthesizer calibration
|
||||
RCCTRL1 = 0x27, // RC oscillator configuration
|
||||
RCCTRL0 = 0x28, // RC oscillator configuration
|
||||
FSTEST = 0x29, // Frequency synthesizer calibration control
|
||||
PTEST = 0x2A, // Production test
|
||||
AGCTEST = 0x2B, // AGC test
|
||||
TEST2 = 0x2C, // Various test settings
|
||||
TEST1 = 0x2D, // Various test settings
|
||||
TEST0 = 0x2E, // Various test settings
|
||||
UNUSED = 0x2F,
|
||||
PARTNUM = 0x30,
|
||||
VERSION = 0x31,
|
||||
FREQEST = 0x32,
|
||||
LQI = 0x33,
|
||||
RSSI = 0x34,
|
||||
MARCSTATE = 0x35,
|
||||
WORTIME1 = 0x36,
|
||||
WORTIME0 = 0x37,
|
||||
PKTSTATUS = 0x38,
|
||||
VCO_VC_DAC = 0x39,
|
||||
TXBYTES = 0x3A,
|
||||
RXBYTES = 0x3B,
|
||||
RCCTRL1_STATUS = 0x3C,
|
||||
RCCTRL0_STATUS = 0x3D,
|
||||
PATABLE = 0x3E,
|
||||
FIFO = 0x3F,
|
||||
};
|
||||
|
||||
enum class Command : uint8_t {
|
||||
RES = 0x30, // Reset chip.
|
||||
FSTXON = 0x31, // Enable and calibrate frequency synthesizer
|
||||
XOFF = 0x32, // Turn off crystal oscillator.
|
||||
CAL = 0x33, // Calibrate frequency synthesizer and turn it off
|
||||
RX = 0x34, // Enable RX.
|
||||
TX = 0x35, // Enable TX.
|
||||
IDLE = 0x36, // Exit RX / TX
|
||||
// 0x37 is RESERVED / UNDEFINED in CC1101 Datasheet
|
||||
WOR = 0x38, // Start automatic RX polling sequence (Wake-on-Radio)
|
||||
PWD = 0x39, // Enter power down mode when CSn goes high.
|
||||
FRX = 0x3A, // Flush the RX FIFO buffer.
|
||||
FTX = 0x3B, // Flush the TX FIFO buffer.
|
||||
WORRST = 0x3C, // Reset real time clock.
|
||||
NOP = 0x3D, // No operation.
|
||||
};
|
||||
|
||||
enum class State : uint8_t {
|
||||
SLEEP,
|
||||
IDLE,
|
||||
XOFF,
|
||||
VCOON_MC,
|
||||
REGON_MC,
|
||||
MANCAL,
|
||||
VCOON,
|
||||
REGON,
|
||||
STARTCAL,
|
||||
BWBOOST,
|
||||
FS_LOCK,
|
||||
IFADCON,
|
||||
ENDCAL,
|
||||
RX,
|
||||
RX_END,
|
||||
RX_RST,
|
||||
TXRX_SWITCH,
|
||||
RXFIFO_OVERFLOW,
|
||||
FSTXON,
|
||||
TX,
|
||||
TX_END,
|
||||
RXTX_SWITCH,
|
||||
TXFIFO_UNDERFLOW,
|
||||
};
|
||||
|
||||
enum class RxAttenuation : uint8_t {
|
||||
RX_ATTENUATION_0DB,
|
||||
RX_ATTENUATION_6DB,
|
||||
RX_ATTENUATION_12DB,
|
||||
RX_ATTENUATION_18DB,
|
||||
};
|
||||
|
||||
enum class SyncMode : uint8_t {
|
||||
SYNC_MODE_NONE,
|
||||
SYNC_MODE_15_16,
|
||||
SYNC_MODE_16_16,
|
||||
SYNC_MODE_30_32,
|
||||
};
|
||||
|
||||
enum class Modulation : uint8_t {
|
||||
MODULATION_2_FSK,
|
||||
MODULATION_GFSK,
|
||||
MODULATION_UNUSED_2,
|
||||
MODULATION_ASK_OOK,
|
||||
MODULATION_4_FSK,
|
||||
MODULATION_UNUSED_5,
|
||||
MODULATION_UNUSED_6,
|
||||
MODULATION_MSK,
|
||||
};
|
||||
|
||||
enum class MagnTarget : uint8_t {
|
||||
MAGN_TARGET_24DB,
|
||||
MAGN_TARGET_27DB,
|
||||
MAGN_TARGET_30DB,
|
||||
MAGN_TARGET_33DB,
|
||||
MAGN_TARGET_36DB,
|
||||
MAGN_TARGET_38DB,
|
||||
MAGN_TARGET_40DB,
|
||||
MAGN_TARGET_42DB,
|
||||
};
|
||||
|
||||
enum class MaxLnaGain : uint8_t {
|
||||
MAX_LNA_GAIN_DEFAULT,
|
||||
MAX_LNA_GAIN_MINUS_2P6DB,
|
||||
MAX_LNA_GAIN_MINUS_6P1DB,
|
||||
MAX_LNA_GAIN_MINUS_7P4DB,
|
||||
MAX_LNA_GAIN_MINUS_9P2DB,
|
||||
MAX_LNA_GAIN_MINUS_11P5DB,
|
||||
MAX_LNA_GAIN_MINUS_14P6DB,
|
||||
MAX_LNA_GAIN_MINUS_17P1DB,
|
||||
};
|
||||
|
||||
enum class MaxDvgaGain : uint8_t {
|
||||
MAX_DVGA_GAIN_DEFAULT,
|
||||
MAX_DVGA_GAIN_MINUS_1,
|
||||
MAX_DVGA_GAIN_MINUS_2,
|
||||
MAX_DVGA_GAIN_MINUS_3,
|
||||
};
|
||||
|
||||
enum class CarrierSenseRelThr : uint8_t {
|
||||
CARRIER_SENSE_REL_THR_DEFAULT,
|
||||
CARRIER_SENSE_REL_THR_PLUS_6DB,
|
||||
CARRIER_SENSE_REL_THR_PLUS_10DB,
|
||||
CARRIER_SENSE_REL_THR_PLUS_14DB,
|
||||
};
|
||||
|
||||
enum class FilterLengthFskMsk : uint8_t {
|
||||
FILTER_LENGTH_8DB,
|
||||
FILTER_LENGTH_16DB,
|
||||
FILTER_LENGTH_32DB,
|
||||
FILTER_LENGTH_64DB,
|
||||
};
|
||||
|
||||
enum class FilterLengthAskOok : uint8_t {
|
||||
FILTER_LENGTH_4DB,
|
||||
FILTER_LENGTH_8DB,
|
||||
FILTER_LENGTH_12DB,
|
||||
FILTER_LENGTH_16DB,
|
||||
};
|
||||
|
||||
enum class Freeze : uint8_t {
|
||||
FREEZE_DEFAULT,
|
||||
FREEZE_ON_SYNC,
|
||||
FREEZE_ANALOG_ONLY,
|
||||
FREEZE_ANALOG_AND_DIGITAL,
|
||||
};
|
||||
|
||||
enum class WaitTime : uint8_t {
|
||||
WAIT_TIME_8_SAMPLES,
|
||||
WAIT_TIME_16_SAMPLES,
|
||||
WAIT_TIME_24_SAMPLES,
|
||||
WAIT_TIME_32_SAMPLES,
|
||||
};
|
||||
|
||||
enum class HystLevel : uint8_t {
|
||||
HYST_LEVEL_NONE,
|
||||
HYST_LEVEL_LOW,
|
||||
HYST_LEVEL_MEDIUM,
|
||||
HYST_LEVEL_HIGH,
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) CC1101State {
|
||||
// Byte array accessors for bulk SPI transfers
|
||||
uint8_t *regs() { return reinterpret_cast<uint8_t *>(this); }
|
||||
const uint8_t *regs() const { return reinterpret_cast<const uint8_t *>(this); }
|
||||
|
||||
// 0x00
|
||||
union {
|
||||
uint8_t IOCFG2;
|
||||
struct {
|
||||
uint8_t GDO2_CFG : 6;
|
||||
uint8_t GDO2_INV : 1;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x01
|
||||
union {
|
||||
uint8_t IOCFG1;
|
||||
struct {
|
||||
uint8_t GDO1_CFG : 6;
|
||||
uint8_t GDO1_INV : 1;
|
||||
uint8_t GDO_DS : 1; // GDO, not GD0
|
||||
};
|
||||
};
|
||||
// 0x02
|
||||
union {
|
||||
uint8_t IOCFG0;
|
||||
struct {
|
||||
uint8_t GDO0_CFG : 6;
|
||||
uint8_t GDO0_INV : 1;
|
||||
uint8_t TEMP_SENSOR_ENABLE : 1;
|
||||
};
|
||||
};
|
||||
// 0x03
|
||||
union {
|
||||
uint8_t FIFOTHR;
|
||||
struct {
|
||||
uint8_t FIFO_THR : 4;
|
||||
uint8_t CLOSE_IN_RX : 2; // RxAttenuation
|
||||
uint8_t ADC_RETENTION : 1;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x04
|
||||
uint8_t SYNC1;
|
||||
// 0x05
|
||||
uint8_t SYNC0;
|
||||
// 0x06
|
||||
uint8_t PKTLEN;
|
||||
// 0x07
|
||||
union {
|
||||
uint8_t PKTCTRL1;
|
||||
struct {
|
||||
uint8_t ADR_CHK : 2;
|
||||
uint8_t APPEND_STATUS : 1;
|
||||
uint8_t CRC_AUTOFLUSH : 1;
|
||||
uint8_t : 1;
|
||||
uint8_t PQT : 3;
|
||||
};
|
||||
};
|
||||
// 0x08
|
||||
union {
|
||||
uint8_t PKTCTRL0;
|
||||
struct {
|
||||
uint8_t LENGTH_CONFIG : 2;
|
||||
uint8_t CRC_EN : 1;
|
||||
uint8_t : 1;
|
||||
uint8_t PKT_FORMAT : 2;
|
||||
uint8_t WHITE_DATA : 1;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x09
|
||||
uint8_t ADDR;
|
||||
// 0x0A
|
||||
uint8_t CHANNR;
|
||||
// 0x0B
|
||||
union {
|
||||
uint8_t FSCTRL1;
|
||||
struct {
|
||||
uint8_t FREQ_IF : 5;
|
||||
uint8_t RESERVED : 1; // hm?
|
||||
uint8_t : 2;
|
||||
};
|
||||
};
|
||||
// 0x0C
|
||||
uint8_t FSCTRL0;
|
||||
// 0x0D
|
||||
uint8_t FREQ2; // [7:6] always zero
|
||||
// 0x0E
|
||||
uint8_t FREQ1;
|
||||
// 0x0F
|
||||
uint8_t FREQ0;
|
||||
// 0x10
|
||||
union {
|
||||
uint8_t MDMCFG4;
|
||||
struct {
|
||||
uint8_t DRATE_E : 4;
|
||||
uint8_t CHANBW_M : 2;
|
||||
uint8_t CHANBW_E : 2;
|
||||
};
|
||||
};
|
||||
// 0x11
|
||||
union {
|
||||
uint8_t MDMCFG3;
|
||||
struct {
|
||||
uint8_t DRATE_M : 8;
|
||||
};
|
||||
};
|
||||
// 0x12
|
||||
union {
|
||||
uint8_t MDMCFG2;
|
||||
struct {
|
||||
uint8_t SYNC_MODE : 2;
|
||||
uint8_t CARRIER_SENSE_ABOVE_THRESHOLD : 1;
|
||||
uint8_t MANCHESTER_EN : 1;
|
||||
uint8_t MOD_FORMAT : 3; // Modulation
|
||||
uint8_t DEM_DCFILT_OFF : 1;
|
||||
};
|
||||
};
|
||||
// 0x13
|
||||
union {
|
||||
uint8_t MDMCFG1;
|
||||
struct {
|
||||
uint8_t CHANSPC_E : 2;
|
||||
uint8_t : 2;
|
||||
uint8_t NUM_PREAMBLE : 3;
|
||||
uint8_t FEC_EN : 1;
|
||||
};
|
||||
};
|
||||
// 0x14
|
||||
union {
|
||||
uint8_t MDMCFG0;
|
||||
struct {
|
||||
uint8_t CHANSPC_M : 8;
|
||||
};
|
||||
};
|
||||
// 0x15
|
||||
union {
|
||||
uint8_t DEVIATN;
|
||||
struct {
|
||||
uint8_t DEVIATION_M : 3;
|
||||
uint8_t : 1;
|
||||
uint8_t DEVIATION_E : 3;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x16
|
||||
union {
|
||||
uint8_t MCSM2;
|
||||
struct {
|
||||
uint8_t RX_TIME : 3;
|
||||
uint8_t RX_TIME_QUAL : 1;
|
||||
uint8_t RX_TIME_RSSI : 1;
|
||||
uint8_t : 3;
|
||||
};
|
||||
};
|
||||
// 0x17
|
||||
union {
|
||||
uint8_t MCSM1;
|
||||
struct {
|
||||
uint8_t TXOFF_MODE : 2;
|
||||
uint8_t RXOFF_MODE : 2;
|
||||
uint8_t CCA_MODE : 2;
|
||||
uint8_t : 2;
|
||||
};
|
||||
};
|
||||
// 0x18
|
||||
union {
|
||||
uint8_t MCSM0;
|
||||
struct {
|
||||
uint8_t XOSC_FORCE_ON : 1;
|
||||
uint8_t PIN_CTRL_EN : 1;
|
||||
uint8_t PO_TIMEOUT : 2;
|
||||
uint8_t FS_AUTOCAL : 2;
|
||||
uint8_t : 2;
|
||||
};
|
||||
};
|
||||
// 0x19
|
||||
union {
|
||||
uint8_t FOCCFG;
|
||||
struct {
|
||||
uint8_t FOC_LIMIT : 2;
|
||||
uint8_t FOC_POST_K : 1;
|
||||
uint8_t FOC_PRE_K : 2;
|
||||
uint8_t FOC_BS_CS_GATE : 1;
|
||||
uint8_t : 2;
|
||||
};
|
||||
};
|
||||
// 0x1A
|
||||
union {
|
||||
uint8_t BSCFG;
|
||||
struct {
|
||||
uint8_t BS_LIMIT : 2;
|
||||
uint8_t BS_POST_KP : 1;
|
||||
uint8_t BS_POST_KI : 1;
|
||||
uint8_t BS_PRE_KP : 2;
|
||||
uint8_t BS_PRE_KI : 2;
|
||||
};
|
||||
};
|
||||
// 0x1B
|
||||
union {
|
||||
uint8_t AGCCTRL2;
|
||||
struct {
|
||||
uint8_t MAGN_TARGET : 3; // MagnTarget
|
||||
uint8_t MAX_LNA_GAIN : 3; // MaxLnaGain
|
||||
uint8_t MAX_DVGA_GAIN : 2; // MaxDvgaGain
|
||||
};
|
||||
};
|
||||
// 0x1C
|
||||
union {
|
||||
uint8_t AGCCTRL1;
|
||||
struct {
|
||||
uint8_t CARRIER_SENSE_ABS_THR : 4;
|
||||
uint8_t CARRIER_SENSE_REL_THR : 2; // CarrierSenseRelThr
|
||||
uint8_t AGC_LNA_PRIORITY : 1;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x1D
|
||||
union {
|
||||
uint8_t AGCCTRL0;
|
||||
struct {
|
||||
uint8_t FILTER_LENGTH : 2; // FilterLengthFskMsk or FilterLengthAskOok
|
||||
uint8_t AGC_FREEZE : 2; // Freeze
|
||||
uint8_t WAIT_TIME : 2; // WaitTime
|
||||
uint8_t HYST_LEVEL : 2; // HystLevel
|
||||
};
|
||||
};
|
||||
// 0x1E
|
||||
uint8_t WOREVT1;
|
||||
// 0x1F
|
||||
uint8_t WOREVT0;
|
||||
// 0x20
|
||||
union {
|
||||
uint8_t WORCTRL;
|
||||
struct {
|
||||
uint8_t WOR_RES : 2;
|
||||
uint8_t : 1;
|
||||
uint8_t RC_CAL : 1;
|
||||
uint8_t EVENT1 : 3;
|
||||
uint8_t RC_PD : 1;
|
||||
};
|
||||
};
|
||||
// 0x21
|
||||
union {
|
||||
uint8_t FREND1;
|
||||
struct {
|
||||
uint8_t MIX_CURRENT : 2;
|
||||
uint8_t LODIV_BUF_CURRENT_RX : 2;
|
||||
uint8_t LNA2MIX_CURRENT : 2;
|
||||
uint8_t LNA_CURRENT : 2;
|
||||
};
|
||||
};
|
||||
// 0x22
|
||||
union {
|
||||
uint8_t FREND0;
|
||||
struct {
|
||||
uint8_t PA_POWER : 3;
|
||||
uint8_t : 1;
|
||||
uint8_t LODIV_BUF_CURRENT_TX : 2;
|
||||
uint8_t : 2;
|
||||
};
|
||||
};
|
||||
// 0x23
|
||||
union {
|
||||
uint8_t FSCAL3;
|
||||
struct {
|
||||
uint8_t FSCAL3_LO : 4;
|
||||
uint8_t CHP_CURR_CAL_EN : 2; // Disable charge pump calibration stage when 0.
|
||||
uint8_t FSCAL3_HI : 2;
|
||||
};
|
||||
};
|
||||
// 0x24
|
||||
union {
|
||||
// uint8_t FSCAL2;
|
||||
struct {
|
||||
uint8_t FSCAL2 : 5;
|
||||
uint8_t VCO_CORE_H_EN : 1;
|
||||
uint8_t : 2;
|
||||
};
|
||||
};
|
||||
// 0x25
|
||||
union {
|
||||
// uint8_t FSCAL1;
|
||||
struct {
|
||||
uint8_t FSCAL1 : 6;
|
||||
uint8_t : 2;
|
||||
};
|
||||
};
|
||||
// 0x26
|
||||
union {
|
||||
// uint8_t FSCAL0;
|
||||
struct {
|
||||
uint8_t FSCAL0 : 7;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x27
|
||||
union {
|
||||
// uint8_t RCCTRL1;
|
||||
struct {
|
||||
uint8_t RCCTRL1 : 7;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x28
|
||||
union {
|
||||
// uint8_t RCCTRL0;
|
||||
struct {
|
||||
uint8_t RCCTRL0 : 7;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x29
|
||||
uint8_t FSTEST;
|
||||
// 0x2A
|
||||
uint8_t PTEST;
|
||||
// 0x2B
|
||||
uint8_t AGCTEST;
|
||||
// 0x2C
|
||||
uint8_t TEST2;
|
||||
// 0x2D
|
||||
uint8_t TEST1;
|
||||
// 0x2E
|
||||
union {
|
||||
uint8_t TEST0;
|
||||
struct {
|
||||
uint8_t TEST0_LO : 1;
|
||||
uint8_t VCO_SEL_CAL_EN : 1; // Enable VCO selection calibration stage when 1
|
||||
uint8_t TEST0_HI : 6;
|
||||
};
|
||||
};
|
||||
// 0x2F
|
||||
uint8_t REG_2F;
|
||||
// 0x30
|
||||
uint8_t PARTNUM;
|
||||
// 0x31
|
||||
uint8_t VERSION;
|
||||
// 0x32
|
||||
union {
|
||||
uint8_t FREQEST;
|
||||
struct {
|
||||
int8_t FREQOFF_EST : 8;
|
||||
};
|
||||
};
|
||||
// 0x33
|
||||
union {
|
||||
uint8_t LQI;
|
||||
struct {
|
||||
uint8_t LQI_EST : 7;
|
||||
uint8_t LQI_CRC_OK : 1;
|
||||
};
|
||||
};
|
||||
// 0x34
|
||||
int8_t RSSI;
|
||||
// 0x35
|
||||
union {
|
||||
// uint8_t MARCSTATE;
|
||||
struct {
|
||||
uint8_t MARC_STATE : 5; // State
|
||||
uint8_t : 3;
|
||||
};
|
||||
};
|
||||
// 0x36
|
||||
uint8_t WORTIME1;
|
||||
// 0x37
|
||||
uint8_t WORTIME0;
|
||||
// 0x38
|
||||
union {
|
||||
uint8_t PKTSTATUS;
|
||||
struct {
|
||||
uint8_t GDO0 : 1;
|
||||
uint8_t : 1;
|
||||
uint8_t GDO2 : 1;
|
||||
uint8_t SFD : 1;
|
||||
uint8_t CCA : 1;
|
||||
uint8_t PQT_REACHED : 1;
|
||||
uint8_t CS : 1;
|
||||
uint8_t CRC_OK : 1; // same as LQI_CRC_OK?
|
||||
};
|
||||
};
|
||||
// 0x39
|
||||
uint8_t VCO_VC_DAC;
|
||||
// 0x3A
|
||||
union {
|
||||
uint8_t TXBYTES;
|
||||
struct {
|
||||
uint8_t NUM_TXBYTES : 7;
|
||||
uint8_t TXFIFO_UNDERFLOW : 1;
|
||||
};
|
||||
};
|
||||
// 0x3B
|
||||
union {
|
||||
uint8_t RXBYTES;
|
||||
struct {
|
||||
uint8_t NUM_RXBYTES : 7;
|
||||
uint8_t RXFIFO_OVERFLOW : 1;
|
||||
};
|
||||
};
|
||||
// 0x3C
|
||||
union {
|
||||
// uint8_t RCCTRL1_STATUS;
|
||||
struct {
|
||||
uint8_t RCCTRL1_STATUS : 7;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x3D
|
||||
union {
|
||||
// uint8_t RCCTRL0_STATUS;
|
||||
struct {
|
||||
uint8_t RCCTRL0_STATUS : 7;
|
||||
uint8_t : 1;
|
||||
};
|
||||
};
|
||||
// 0x3E
|
||||
uint8_t REG_3E;
|
||||
// 0x3F
|
||||
uint8_t REG_3F;
|
||||
};
|
||||
|
||||
static_assert(sizeof(CC1101State) == 0x40, "CC1101State size mismatch");
|
||||
|
||||
} // namespace esphome::cc1101
|
||||
174
esphome/components/cc1101/cc1101pa.h
Normal file
174
esphome/components/cc1101/cc1101pa.h
Normal file
@@ -0,0 +1,174 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome::cc1101 {
|
||||
|
||||
// CC1101 Design Note DN013
|
||||
|
||||
struct PowerTableItem {
|
||||
uint8_t value;
|
||||
uint8_t dbm_diff; // starts from 12.0, diff to previous entry, scaled by 10
|
||||
|
||||
static uint8_t find(const PowerTableItem *items, size_t count, float &dbm_target) {
|
||||
int32_t dbmi = 120;
|
||||
int32_t dbmi_target = static_cast<int32_t>(std::lround(dbm_target * 10));
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
dbmi -= items[i].dbm_diff;
|
||||
if (dbmi_target >= dbmi) {
|
||||
// Skip invalid PA settings (magic numbers derived from TI DN013/SmartRC logic)
|
||||
if (items[i].value >= 0x61 && items[i].value <= 0x6F) {
|
||||
continue;
|
||||
}
|
||||
dbm_target = static_cast<float>(dbmi) / 10.0f;
|
||||
return items[i].value;
|
||||
}
|
||||
}
|
||||
dbm_target = -30.0f;
|
||||
return 0x03;
|
||||
}
|
||||
};
|
||||
|
||||
static const PowerTableItem PA_TABLE_315[] = {
|
||||
{0xC0, 14}, // C0 10.6 -35.3 -44.4 -57.8 -53.8 -58.3 -57.2 -57.8 -56.7 28.5
|
||||
{0xC3, 10}, // C3 9.6 -39.2 -45.3 -59.0 -54.2 -59.0 -57.5 -58.3 -57.2 26.2
|
||||
{0xC6, 11}, // C6 8.5 -43.2 -46.3 -59.2 -54.7 -59.1 -57.7 -58.3 -57.4 24.4
|
||||
{0xC9, 10}, // C9 7.5 -47.0 -47.3 -58.9 -55.0 -59.0 -57.9 -58.4 -57.5 23.0
|
||||
{0x81, 12}, // 81 6.3 -49.2 -45.7 -57.3 -53.6 -59.0 -56.0 -56.5 -57.5 19.5
|
||||
{0x85, 13}, // 85 5.0 -51.0 -47.2 -59.8 -54.2 -59.0 -56.9 -57.9 -58.0 18.3
|
||||
{0x88, 11}, // 88 3.9 -46.6 -48.1 -60.0 -55.0 -58.9 -57.5 -58.2 -58.2 17.4
|
||||
{0xCF, 11}, // CF 2.8 -49.8 -51.3 -57.6 -56.8 -59.1 -58.4 -58.1 -58.3 18.0
|
||||
{0x8D, 11}, // 8D 1.7 -43.8 -49.5 -58.9 -56.3 -58.8 -58.2 -58.4 -58.5 15.8
|
||||
{0x50, 10}, // 50 0.7 -59.2 -51.2 -59.0 -56.5 -59.0 -58.3 -58.3 -58.2 15.3
|
||||
{0x40, 10}, // 40 -0.3 -58.2 -52.1 -59.4 -56.9 -59.0 -58.4 -58.4 -58.3 14.7
|
||||
{0x3D, 10}, // 3D -1.3 -54.4 -48.4 -59.8 -57.5 -58.9 -58.3 -58.5 -58.5 19.3
|
||||
{0x55, 10}, // 55 -2.3 -56.7 -53.6 -59.7 -57.5 -59.1 -58.7 -58.4 -58.4 13.7
|
||||
{0x39, 11}, // 39 -3.4 -50.9 -49.5 -59.8 -58.0 -59.0 -58.5 -58.4 -58.4 16.8
|
||||
{0x2B, 15}, // 2B -4.9 -51.2 -50.4 -59.9 -58.0 -58.9 -58.7 -58.3 -58.4 15.6
|
||||
{0x29, 16}, // 29 -6.5 -51.8 -51.6 -59.9 -58.4 -59.0 -58.8 -58.3 -58.3 14.7
|
||||
{0x28, 10}, // 28 -7.5 -52.2 -52.5 -60.0 -58.6 -59.0 -58.8 -58.2 -58.4 14.3
|
||||
{0x27, 11}, // 27 -8.6 -52.9 -53.1 -60.0 -58.8 -59.1 -58.8 -58.3 -58.5 13.9
|
||||
{0x26, 12}, // 26 -9.8 -53.6 -54.3 -60.1 -58.7 -59.0 -58.7 -58.4 -58.4 13.4
|
||||
{0x25, 13}, // 25 -11.1 -54.3 -55.5 -60.1 -58.8 -59.1 -58.8 -58.4 -58.4 13.0
|
||||
{0x33, 11}, // 33 -12.2 -55.0 -56.3 -60.0 -58.7 -59.0 -58.9 -58.4 -58.4 12.8
|
||||
{0x1F, 11}, // 1F -13.3 -55.6 -57.2 -60.0 -58.8 -58.9 -58.9 -58.3 -58.4 12.4
|
||||
{0x1D, 12}, // 1D -14.5 -56.0 -58.0 -60.0 -58.8 -59.1 -58.7 -58.4 -58.5 12.1
|
||||
{0x32, 11}, // 32 -15.6 -56.9 -58.8 -59.9 -58.8 -59.0 -58.8 -58.3 -58.5 12.2
|
||||
{0x1A, 10}, // 1A -16.6 -57.3 -59.5 -59.9 -58.8 -59.1 -58.8 -58.4 -58.4 11.8
|
||||
{0x18, 19}, // 18 -18.5 -57.8 -60.3 -60.0 -58.8 -59.0 -58.9 -58.2 -58.5 11.6
|
||||
{0x17, 11}, // 17 -19.6 -58.7 -60.9 -60.0 -58.7 -58.9 -58.9 -58.5 -58.4 11.4
|
||||
{0x0C, 11}, // C -20.7 -59.4 -61.1 -60.0 -58.8 -59.1 -58.9 -58.4 -58.3 11.3
|
||||
{0x0A, 15}, // A -22.2 -59.9 -61.9 -60.0 -58.9 -59.0 -58.9 -58.4 -58.5 11.2
|
||||
{0x08, 18}, // 8 -24.0 -60.5 -62.5 -60.0 -58.7 -59.1 -58.8 -58.3 -58.5 11.1
|
||||
{0x07, 11}, // 7 -25.1 -61.3 -62.9 -60.1 -58.8 -59.1 -58.8 -58.4 -58.4 11.0
|
||||
{0x06, 13}, // 6 -26.4 -61.6 -63.2 -60.1 -58.7 -59.0 -58.9 -58.5 -58.5 11.0
|
||||
{0x05, 13}, // 5 -27.7 -62.3 -63.4 -60.1 -58.7 -59.2 -58.8 -58.4 -58.5 10.9
|
||||
{0x04, 19}, // 4 -29.6 -62.7 -63.6 -59.9 -58.7 -59.0 -58.9 -58.4 -58.4 10.8
|
||||
};
|
||||
|
||||
static const PowerTableItem PA_TABLE_433[] = {
|
||||
{0xC0, 21}, // C0 9.9 -43.4 -45.0 -53.9 -55.2 -55.8 -52.3 -55.6 29.1
|
||||
{0xC3, 11}, // C3 8.8 -49.3 -45.9 -55.9 -55.4 -57.2 -52.6 -57.5 26.9
|
||||
{0xC6, 10}, // C6 7.8 -56.2 -46.9 -56.9 -55.6 -58.2 -53.2 -57.9 25.2
|
||||
{0xC9, 10}, // C9 6.8 -56.1 -47.9 -57.3 -55.9 -58.5 -54.0 -56.9 23.8
|
||||
{0xCC, 10}, // CC 5.8 -52.8 -48.9 -57.0 -56.1 -58.4 -54.6 -56.2 22.6
|
||||
{0x85, 10}, // 85 4.8 -54.2 -53.0 -58.3 -55.0 -57.8 -56.8 -58.0 19.1
|
||||
{0x88, 12}, // 88 3.6 -56.2 -53.8 -58.3 -55.7 -58.1 -57.2 -58.2 18.2
|
||||
{0x8B, 13}, // 8B 2.3 -57.7 -54.5 -58.0 -56.3 -58.1 -57.5 -58.2 17.3
|
||||
{0x8E, 19}, // 8E 0.4 -58.0 -55.5 -57.8 -57.4 -58.2 -58.1 -58.4 16.2
|
||||
{0x40, 12}, // 40 -0.8 -59.7 -56.1 -58.2 -57.7 -58.4 -58.3 -58.2 15.4
|
||||
{0x3C, 13}, // 3C -2.1 -60.6 -57.3 -58.2 -58.0 -58.5 -58.4 -58.5 19.3
|
||||
{0x3A, 10}, // 3A -3.1 -59.5 -57.5 -58.3 -58.3 -58.6 -58.1 -58.6 18.1
|
||||
{0x8F, 15}, // 8F -4.6 -52.2 -57.7 -58.1 -58.8 -58.4 -58.7 -58.3 14.4
|
||||
{0x37, 10}, // 37 -5.6 -56.8 -58.3 -58.3 -58.8 -58.4 -58.5 -58.4 16.2
|
||||
{0x36, 12}, // 36 -6.8 -56.8 -58.9 -58.3 -58.8 -58.3 -58.5 -58.5 15.6
|
||||
{0x28, 10}, // 28 -7.8 -56.6 -59.0 -58.2 -59.0 -58.4 -58.5 -58.4 15.1
|
||||
{0x26, 21}, // 26 -9.9 -57.0 -59.4 -58.3 -59.0 -58.4 -58.7 -58.4 14.3
|
||||
{0x25, 15}, // 25 -11.4 -57.3 -59.7 -58.4 -59.0 -58.3 -58.7 -58.5 13.9
|
||||
{0x24, 19}, // 24 -13.3 -57.9 -59.9 -58.2 -59.0 -58.6 -58.7 -58.5 13.5
|
||||
{0x1E, 10}, // 1E -14.3 -58.4 -59.8 -58.2 -59.0 -58.4 -58.6 -58.6 13.2
|
||||
{0x1C, 12}, // 1C -15.5 -58.6 -59.9 -58.4 -58.8 -58.6 -58.8 -58.5 12.9
|
||||
{0x1A, 15}, // 1A -17.0 -59.4 -59.9 -58.3 -59.1 -58.5 -58.7 -58.4 12.7
|
||||
{0x18, 18}, // 18 -18.8 -60.2 -59.9 -58.2 -59.0 -58.5 -58.7 -58.6 12.5
|
||||
{0x17, 10}, // 17 -19.8 -60.6 -59.9 -58.2 -58.9 -58.4 -58.7 -58.4 12.4
|
||||
{0x0C, 12}, // C -21.0 -61.1 -59.9 -58.4 -59.0 -58.5 -58.7 -58.6 12.3
|
||||
{0x15, 15}, // 15 -22.5 -61.7 -60.0 -58.2 -59.1 -58.3 -58.6 -58.7 12.2
|
||||
{0x08, 18}, // 8 -24.3 -62.3 -59.9 -58.3 -59.0 -58.4 -58.8 -58.5 12.1
|
||||
{0x07, 10}, // 7 -25.3 -62.6 -59.9 -58.2 -59.0 -58.6 -58.7 -58.5 12.0
|
||||
{0x06, 12}, // 6 -26.5 -63.2 -59.9 -58.3 -58.9 -58.5 -58.6 -58.6 12.0
|
||||
{0x05, 14}, // 5 -27.9 -63.5 -59.8 -58.3 -59.1 -58.5 -58.7 -58.4 11.9
|
||||
{0x04, 16}, // 4 -29.5 -63.7 -59.9 -58.3 -58.9 -58.5 -58.5 -58.5 11.9
|
||||
};
|
||||
|
||||
static const PowerTableItem PA_TABLE_868[] = {
|
||||
{0xC0, 13}, // C0 10.7 -35.1 -58.6 -58.6 -57.5 -50.0 34.2
|
||||
{0xC3, 11}, // C3 9.6 -41.5 -58.5 -58.3 -57.4 -54.4 31.6
|
||||
{0xC6, 11}, // C6 8.5 -47.7 -58.5 -58.3 -57.6 -55.0 29.5
|
||||
{0xC9, 10}, // C9 7.5 -44.4 -58.5 -58.5 -57.7 -53.6 27.8
|
||||
{0xCC, 10}, // CC 6.5 -40.6 -58.6 -58.4 -57.6 -52.5 26.3
|
||||
{0xCE, 10}, // CE 5.5 -38.5 -58.5 -58.4 -57.8 -52.2 25.0
|
||||
{0x84, 11}, // 84 4.4 -35.3 -58.7 -58.5 -57.8 -55.8 20.3
|
||||
{0x87, 10}, // 87 3.4 -39.4 -58.6 -58.6 -57.8 -55.7 19.5
|
||||
{0xCF, 10}, // CF 2.4 -36.6 -58.6 -58.4 -57.7 -53.6 22.0
|
||||
{0x8C, 13}, // 8C 1.1 -50.2 -58.6 -58.5 -57.7 -55.9 17.9
|
||||
{0x50, 14}, // 50 -0.3 -42.1 -58.5 -58.5 -57.6 -57.1 16.9
|
||||
{0x40, 12}, // 40 -1.5 -43.2 -58.5 -58.7 -57.7 -57.2 16.1
|
||||
{0x3F, 11}, // 3F -2.6 -53.7 -58.6 -58.5 -57.8 -57.5 21.4
|
||||
{0x55, 10}, // 55 -3.6 -44.9 -58.6 -58.4 -57.8 -57.5 15.0
|
||||
{0x57, 12}, // 57 -4.8 -46.0 -58.6 -58.5 -57.6 -57.4 14.5
|
||||
{0x8F, 12}, // 8F -6.0 -51.6 -58.5 -58.6 -57.7 -57.1 15.0
|
||||
{0x2A, 14}, // 2A -7.4 -49.3 -58.5 -58.6 -57.7 -57.4 16.2
|
||||
{0x28, 16}, // 28 -9.0 -49.0 -58.5 -58.6 -57.7 -57.4 15.4
|
||||
{0x26, 20}, // 26 -11.0 -49.2 -58.5 -58.5 -57.7 -57.4 14.6
|
||||
{0x25, 15}, // 25 -12.5 -49.5 -58.6 -58.6 -57.8 -57.3 14.1
|
||||
{0x24, 18}, // 24 -14.3 -50.2 -58.5 -58.4 -57.8 -57.4 13.7
|
||||
{0x1D, 14}, // 1D -15.7 -50.7 -58.6 -58.6 -57.8 -57.5 13.3
|
||||
{0x1B, 13}, // 1B -17.0 -51.3 -58.5 -58.4 -57.7 -57.5 13.1
|
||||
{0x19, 16}, // 19 -18.6 -52.0 -58.6 -58.5 -57.8 -57.5 12.9
|
||||
{0x22, 10}, // 22 -19.6 -52.5 -58.5 -58.6 -57.7 -57.4 12.9
|
||||
{0x0D, 15}, // D -21.1 -53.3 -58.6 -58.6 -57.8 -57.4 12.6
|
||||
{0x0B, 12}, // B -22.3 -53.9 -58.6 -58.5 -57.8 -57.4 12.5
|
||||
{0x09, 15}, // 9 -23.8 -54.7 -58.5 -58.5 -57.8 -57.5 12.4
|
||||
{0x21, 10}, // 21 -24.8 -55.1 -58.5 -58.5 -57.7 -57.5 12.5
|
||||
{0x13, 17}, // 13 -26.5 -55.9 -58.6 -58.5 -57.6 -57.6 12.3
|
||||
{0x05, 12}, // 5 -27.7 -56.4 -58.4 -58.4 -57.7 -57.5 12.2
|
||||
{0x12, 12}, // 12 -28.9 -57.1 -58.4 -58.5 -57.7 -57.3 12.2
|
||||
};
|
||||
|
||||
static const PowerTableItem PA_TABLE_915[] = {
|
||||
{0xC0, 26}, // C0 9.4 -33.5 -58.5 -58.4 -55.8 -32.6 31.8
|
||||
{0xC3, 11}, // C3 8.3 -41.5 -58.6 -58.4 -56.3 -38.0 29.3
|
||||
{0xC6, 11}, // C6 7.2 -42.5 -58.5 -58.4 -56.7 -40.5 27.4
|
||||
{0xC9, 10}, // C9 6.2 -37.6 -58.6 -58.4 -57.2 -38.8 25.9
|
||||
{0xCD, 12}, // CD 5.0 -34.2 -58.6 -58.5 -57.5 -37.3 24.3
|
||||
{0x84, 11}, // 84 3.9 -32.0 -58.6 -58.4 -57.7 -40.1 19.7
|
||||
{0x87, 10}, // 87 2.9 -36.5 -58.4 -58.5 -57.7 -39.6 18.9
|
||||
{0x8A, 11}, // 8A 1.8 -42.2 -58.5 -58.4 -57.7 -39.6 18.1
|
||||
{0x8D, 13}, // 8D 0.5 -46.8 -58.5 -58.5 -57.7 -40.4 17.3
|
||||
{0x8E, 11}, // 8E -0.6 -46.6 -58.5 -58.5 -57.8 -41.1 16.7
|
||||
{0x51, 10}, // 51 -1.6 -38.7 -58.4 -58.5 -57.7 -46.9 16.0
|
||||
{0x3E, 11}, // 3E -2.7 -50.0 -58.5 -58.4 -57.6 -55.3 20.7
|
||||
{0x3B, 11}, // 3B -3.8 -50.7 -58.6 -58.4 -57.6 -55.2 18.9
|
||||
{0x39, 13}, // 39 -5.1 -50.0 -58.5 -58.5 -57.6 -54.0 17.7
|
||||
{0x2B, 13}, // 2B -6.4 -47.6 -58.4 -58.4 -57.8 -52.1 16.5
|
||||
{0x36, 15}, // 36 -7.9 -46.9 -58.5 -58.4 -57.7 -51.2 15.8
|
||||
{0x35, 14}, // 35 -9.3 -46.7 -58.6 -58.4 -57.7 -50.7 15.2
|
||||
{0x26, 16}, // 26 -10.9 -47.0 -58.6 -58.4 -57.8 -50.9 14.5
|
||||
{0x25, 14}, // 25 -12.3 -47.2 -58.6 -58.3 -57.7 -51.0 14.1
|
||||
{0x24, 18}, // 24 -14.1 -48.1 -58.4 -58.4 -57.8 -51.4 13.7
|
||||
{0x1D, 14}, // 1D -15.5 -48.7 -58.4 -58.5 -57.7 -51.9 13.2
|
||||
{0x1B, 13}, // 1B -16.8 -49.3 -58.6 -58.4 -57.8 -52.3 13.0
|
||||
{0x19, 15}, // 19 -18.3 -50.2 -58.5 -58.5 -57.6 -52.8 12.8
|
||||
{0x18, 10}, // 18 -19.3 -50.6 -58.5 -58.5 -57.7 -53.1 12.7
|
||||
{0x17, 10}, // 17 -20.3 -51.2 -58.6 -58.5 -57.8 -53.1 12.6
|
||||
{0x0C, 11}, // C -21.4 -51.8 -58.4 -58.5 -57.7 -53.4 12.5
|
||||
{0x0A, 13}, // A -22.7 -52.6 -58.5 -58.4 -57.7 -53.6 12.4
|
||||
{0x08, 16}, // 8 -24.3 -53.6 -58.4 -58.4 -57.6 -54.1 12.3
|
||||
{0x13, 19}, // 13 -26.2 -54.6 -58.4 -58.5 -57.7 -54.3 12.2
|
||||
{0x05, 11}, // 5 -27.3 -55.3 -58.4 -58.4 -57.8 -54.5 12.1
|
||||
{0x12, 13}, // 12 -28.6 -55.9 -58.6 -58.5 -57.7 -54.7 12.1
|
||||
{0x03, 12}, // 3 -29.8 -56.9 -58.5 -58.4 -57.7 -54.7 12.0
|
||||
};
|
||||
} // namespace esphome::cc1101
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "climate.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
namespace esphome::climate {
|
||||
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
public:
|
||||
@@ -58,5 +57,4 @@ class StateTrigger : public Trigger<Climate &> {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
} // namespace esphome::climate
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/macros.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
namespace esphome::climate {
|
||||
|
||||
static const char *const TAG = "climate";
|
||||
|
||||
@@ -762,5 +761,4 @@ void Climate::dump_traits_(const char *tag) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
} // namespace esphome::climate
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#include "climate_mode.h"
|
||||
#include "climate_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
namespace esphome::climate {
|
||||
|
||||
#define LOG_CLIMATE(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
@@ -345,5 +344,4 @@ class Climate : public EntityBase {
|
||||
const char *custom_preset_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
} // namespace esphome::climate
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "climate_mode.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
namespace esphome::climate {
|
||||
|
||||
const LogString *climate_mode_to_string(ClimateMode mode) {
|
||||
switch (mode) {
|
||||
@@ -107,5 +106,4 @@ const LogString *climate_preset_to_string(ClimatePreset preset) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
} // namespace esphome::climate
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include <cstdint>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
namespace esphome::climate {
|
||||
|
||||
/// Enum for all modes a climate device can be in.
|
||||
/// NOTE: If adding values, update ClimateModeMask in climate_traits.h to use the new last value
|
||||
@@ -132,5 +131,4 @@ const LogString *climate_swing_mode_to_string(ClimateSwingMode mode);
|
||||
/// Convert the given PresetMode to a human-readable string.
|
||||
const LogString *climate_preset_to_string(ClimatePreset preset);
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
} // namespace esphome::climate
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "climate_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
namespace esphome::climate {
|
||||
|
||||
int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const {
|
||||
return step_to_accuracy_decimals(this->visual_target_temperature_step_);
|
||||
@@ -11,5 +10,4 @@ int8_t ClimateTraits::get_current_temperature_accuracy_decimals() const {
|
||||
return step_to_accuracy_decimals(this->visual_current_temperature_step_);
|
||||
}
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
} // namespace esphome::climate
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
#include "esphome/core/finite_set_mask.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
namespace esphome::climate {
|
||||
|
||||
// Type aliases for climate enum bitmasks
|
||||
// These replace std::set<EnumType> to eliminate red-black tree overhead
|
||||
@@ -292,5 +291,4 @@ class ClimateTraits {
|
||||
std::vector<const char *> supported_custom_presets_;
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
} // namespace esphome::climate
|
||||
|
||||
@@ -13,25 +13,25 @@ static const char *const TAG = "cover";
|
||||
const float COVER_OPEN = 1.0f;
|
||||
const float COVER_CLOSED = 0.0f;
|
||||
|
||||
const char *cover_command_to_str(float pos) {
|
||||
const LogString *cover_command_to_str(float pos) {
|
||||
if (pos == COVER_OPEN) {
|
||||
return "OPEN";
|
||||
return LOG_STR("OPEN");
|
||||
} else if (pos == COVER_CLOSED) {
|
||||
return "CLOSE";
|
||||
return LOG_STR("CLOSE");
|
||||
} else {
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
const char *cover_operation_to_str(CoverOperation op) {
|
||||
const LogString *cover_operation_to_str(CoverOperation op) {
|
||||
switch (op) {
|
||||
case COVER_OPERATION_IDLE:
|
||||
return "IDLE";
|
||||
return LOG_STR("IDLE");
|
||||
case COVER_OPERATION_OPENING:
|
||||
return "OPENING";
|
||||
return LOG_STR("OPENING");
|
||||
case COVER_OPERATION_CLOSING:
|
||||
return "CLOSING";
|
||||
return LOG_STR("CLOSING");
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ void CoverCall::perform() {
|
||||
if (traits.get_supports_position()) {
|
||||
ESP_LOGD(TAG, " Position: %.0f%%", *this->position_ * 100.0f);
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Command: %s", cover_command_to_str(*this->position_));
|
||||
ESP_LOGD(TAG, " Command: %s", LOG_STR_ARG(cover_command_to_str(*this->position_)));
|
||||
}
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
@@ -169,7 +169,7 @@ void Cover::publish_state(bool save) {
|
||||
if (traits.get_supports_tilt()) {
|
||||
ESP_LOGD(TAG, " Tilt: %.0f%%", this->tilt * 100.0f);
|
||||
}
|
||||
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
|
||||
ESP_LOGD(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation)));
|
||||
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
#include "cover_traits.h"
|
||||
@@ -86,7 +87,7 @@ enum CoverOperation : uint8_t {
|
||||
COVER_OPERATION_CLOSING,
|
||||
};
|
||||
|
||||
const char *cover_operation_to_str(CoverOperation op);
|
||||
const LogString *cover_operation_to_str(CoverOperation op);
|
||||
|
||||
/** Base class for all cover devices.
|
||||
*
|
||||
|
||||
@@ -9,27 +9,40 @@ void CST816Touchscreen::continue_setup_() {
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
if (this->read_byte(REG_CHIP_ID, &this->chip_id_)) {
|
||||
switch (this->chip_id_) {
|
||||
case CST820_CHIP_ID:
|
||||
case CST826_CHIP_ID:
|
||||
case CST716_CHIP_ID:
|
||||
case CST816S_CHIP_ID:
|
||||
case CST816D_CHIP_ID:
|
||||
case CST816T_CHIP_ID:
|
||||
break;
|
||||
default:
|
||||
|
||||
if (!this->read_byte(REG_CHIP_ID, &this->chip_id_) && !this->skip_probe_) {
|
||||
this->status_set_error(LOG_STR("Failed to read chip ID"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// CST826/CST836 return 0 for chip ID, need to read from factory ID register
|
||||
if (this->chip_id_ == 0) {
|
||||
if (!this->read_byte(REG_FACTORY_ID, &this->chip_id_) && !this->skip_probe_) {
|
||||
this->status_set_error(LOG_STR("Failed to read chip ID"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (this->chip_id_) {
|
||||
case CST716_CHIP_ID:
|
||||
case CST816S_CHIP_ID:
|
||||
case CST816D_CHIP_ID:
|
||||
case CST816T_CHIP_ID:
|
||||
case CST820_CHIP_ID:
|
||||
case CST826_CHIP_ID:
|
||||
case CST836_CHIP_ID:
|
||||
break;
|
||||
default:
|
||||
if (!this->skip_probe_) {
|
||||
ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_);
|
||||
this->status_set_error(LOG_STR("Unknown chip ID"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
||||
} else if (!this->skip_probe_) {
|
||||
this->status_set_error(LOG_STR("Failed to read chip id"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
@@ -80,11 +93,8 @@ void CST816Touchscreen::dump_config() {
|
||||
this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_);
|
||||
const char *name;
|
||||
switch (this->chip_id_) {
|
||||
case CST820_CHIP_ID:
|
||||
name = "CST820";
|
||||
break;
|
||||
case CST826_CHIP_ID:
|
||||
name = "CST826";
|
||||
case CST716_CHIP_ID:
|
||||
name = "CST716";
|
||||
break;
|
||||
case CST816S_CHIP_ID:
|
||||
name = "CST816S";
|
||||
@@ -92,12 +102,18 @@ void CST816Touchscreen::dump_config() {
|
||||
case CST816D_CHIP_ID:
|
||||
name = "CST816D";
|
||||
break;
|
||||
case CST716_CHIP_ID:
|
||||
name = "CST716";
|
||||
break;
|
||||
case CST816T_CHIP_ID:
|
||||
name = "CST816T";
|
||||
break;
|
||||
case CST820_CHIP_ID:
|
||||
name = "CST820";
|
||||
break;
|
||||
case CST826_CHIP_ID:
|
||||
name = "CST826";
|
||||
break;
|
||||
case CST836_CHIP_ID:
|
||||
name = "CST836";
|
||||
break;
|
||||
default:
|
||||
name = "Unknown";
|
||||
break;
|
||||
|
||||
@@ -19,12 +19,14 @@ static const uint8_t REG_YPOS_HIGH = 0x05;
|
||||
static const uint8_t REG_YPOS_LOW = 0x06;
|
||||
static const uint8_t REG_DIS_AUTOSLEEP = 0xFE;
|
||||
static const uint8_t REG_CHIP_ID = 0xA7;
|
||||
static const uint8_t REG_FACTORY_ID = 0xAA;
|
||||
static const uint8_t REG_FW_VERSION = 0xA9;
|
||||
static const uint8_t REG_SLEEP = 0xE5;
|
||||
static const uint8_t REG_IRQ_CTL = 0xFA;
|
||||
static const uint8_t IRQ_EN_MOTION = 0x70;
|
||||
|
||||
static const uint8_t CST826_CHIP_ID = 0x11;
|
||||
static const uint8_t CST836_CHIP_ID = 0x13;
|
||||
static const uint8_t CST820_CHIP_ID = 0xB7;
|
||||
static const uint8_t CST816S_CHIP_ID = 0xB4;
|
||||
static const uint8_t CST816D_CHIP_ID = 0xB6;
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
namespace esphome::datetime {
|
||||
|
||||
static const char *const TAG = "datetime.date_entity";
|
||||
|
||||
@@ -129,7 +128,6 @@ void DateEntityRestoreState::apply(DateEntity *date) {
|
||||
date->publish_state();
|
||||
}
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::datetime
|
||||
|
||||
#endif // USE_DATETIME_DATE
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
#include "datetime_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
namespace esphome::datetime {
|
||||
|
||||
#define LOG_DATETIME_DATE(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
@@ -111,7 +110,6 @@ template<typename... Ts> class DateSetAction : public Action<Ts...>, public Pare
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::datetime
|
||||
|
||||
#endif // USE_DATETIME_DATE
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
namespace esphome::datetime {
|
||||
|
||||
class DateTimeBase : public EntityBase {
|
||||
public:
|
||||
@@ -37,5 +36,4 @@ class DateTimeStateTrigger : public Trigger<ESPTime> {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::datetime
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
namespace esphome::datetime {
|
||||
|
||||
static const char *const TAG = "datetime.datetime_entity";
|
||||
|
||||
@@ -250,7 +249,6 @@ bool OnDateTimeTrigger::matches_(const ESPTime &time) const {
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::datetime
|
||||
|
||||
#endif // USE_DATETIME_TIME
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
#include "datetime_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
namespace esphome::datetime {
|
||||
|
||||
#define LOG_DATETIME_DATETIME(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
@@ -146,7 +145,6 @@ class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<Da
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::datetime
|
||||
|
||||
#endif // USE_DATETIME_DATETIME
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
namespace esphome::datetime {
|
||||
|
||||
static const char *const TAG = "datetime.time_entity";
|
||||
|
||||
@@ -152,7 +151,6 @@ bool OnTimeTrigger::matches_(const ESPTime &time) const {
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::datetime
|
||||
|
||||
#endif // USE_DATETIME_TIME
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
#include "datetime_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
namespace esphome::datetime {
|
||||
|
||||
#define LOG_DATETIME_TIME(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
@@ -125,7 +124,6 @@ class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEn
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::datetime
|
||||
|
||||
#endif // USE_DATETIME_TIME
|
||||
|
||||
@@ -52,7 +52,10 @@ WAKEUP_PINS = {
|
||||
38,
|
||||
39,
|
||||
],
|
||||
VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5],
|
||||
VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5],
|
||||
VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||
VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14],
|
||||
VARIANT_ESP32S2: [
|
||||
0,
|
||||
1,
|
||||
@@ -101,9 +104,6 @@ WAKEUP_PINS = {
|
||||
20,
|
||||
21,
|
||||
],
|
||||
VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5],
|
||||
VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||
VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14],
|
||||
}
|
||||
|
||||
|
||||
@@ -122,10 +122,10 @@ def _validate_ex1_wakeup_mode(value):
|
||||
if value == "ANY_LOW":
|
||||
esp32.only_on_variant(
|
||||
supported=[
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
],
|
||||
msg_prefix="ANY_LOW",
|
||||
)(value)
|
||||
|
||||
@@ -33,7 +33,7 @@ class DemoAlarmControlPanel : public AlarmControlPanel, public Component {
|
||||
case ACP_STATE_ARMED_AWAY:
|
||||
if (this->get_requires_code_to_arm() && call.get_code().has_value()) {
|
||||
if (call.get_code().value() != "1234") {
|
||||
this->status_momentary_error("Invalid code", 5000);
|
||||
this->status_momentary_error("invalid_code", 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class DemoAlarmControlPanel : public AlarmControlPanel, public Component {
|
||||
case ACP_STATE_DISARMED:
|
||||
if (this->get_requires_code() && call.get_code().has_value()) {
|
||||
if (call.get_code().value() != "1234") {
|
||||
this->status_momentary_error("Invalid code", 5000);
|
||||
this->status_momentary_error("invalid_code", 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ ES8311_BITS_PER_SAMPLE_ENUM = {
|
||||
|
||||
es8311_mic_gain = es8311_ns.enum("ES8311MicGain")
|
||||
ES8311_MIC_GAIN_ENUM = {
|
||||
"MIN": es8311_mic_gain.ES8311_MIC_GAIN_MIN,
|
||||
"0DB": es8311_mic_gain.ES8311_MIC_GAIN_0DB,
|
||||
"6DB": es8311_mic_gain.ES8311_MIC_GAIN_6DB,
|
||||
"12DB": es8311_mic_gain.ES8311_MIC_GAIN_12DB,
|
||||
@@ -31,7 +30,6 @@ ES8311_MIC_GAIN_ENUM = {
|
||||
"30DB": es8311_mic_gain.ES8311_MIC_GAIN_30DB,
|
||||
"36DB": es8311_mic_gain.ES8311_MIC_GAIN_36DB,
|
||||
"42DB": es8311_mic_gain.ES8311_MIC_GAIN_42DB,
|
||||
"MAX": es8311_mic_gain.ES8311_MIC_GAIN_MAX,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ bool ES8388::set_dac_output(DacOutputLine line) {
|
||||
optional<DacOutputLine> ES8388::get_dac_power() {
|
||||
uint8_t dac_power;
|
||||
if (!this->read_byte(ES8388_DACPOWER, &dac_power)) {
|
||||
this->status_momentary_warning("Failed to read ES8388_DACPOWER");
|
||||
this->status_momentary_warning("dacpower_read");
|
||||
return {};
|
||||
}
|
||||
switch (dac_power) {
|
||||
@@ -268,7 +268,7 @@ bool ES8388::set_adc_input_mic(AdcInputMicLine line) {
|
||||
optional<AdcInputMicLine> ES8388::get_mic_input() {
|
||||
uint8_t mic_input;
|
||||
if (!this->read_byte(ES8388_ADCCONTROL2, &mic_input)) {
|
||||
this->status_momentary_warning("Failed to read ES8388_ADCCONTROL2");
|
||||
this->status_momentary_warning("adccontrol2_read");
|
||||
return {};
|
||||
}
|
||||
switch (mic_input) {
|
||||
|
||||
@@ -122,14 +122,14 @@ def get_cpu_frequencies(*frequencies):
|
||||
|
||||
CPU_FREQUENCIES = {
|
||||
VARIANT_ESP32: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32S3: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32C2: get_cpu_frequencies(80, 120),
|
||||
VARIANT_ESP32C3: get_cpu_frequencies(80, 160),
|
||||
VARIANT_ESP32C5: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32C6: get_cpu_frequencies(80, 120, 160),
|
||||
VARIANT_ESP32H2: get_cpu_frequencies(16, 32, 48, 64, 96),
|
||||
VARIANT_ESP32P4: get_cpu_frequencies(40, 360, 400),
|
||||
VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32S3: get_cpu_frequencies(80, 160, 240),
|
||||
}
|
||||
|
||||
# Make sure not missed here if a new variant added.
|
||||
@@ -584,6 +584,8 @@ CONF_DISABLE_LIBC_LOCKS_IN_IRAM = "disable_libc_locks_in_iram"
|
||||
CONF_DISABLE_VFS_SUPPORT_TERMIOS = "disable_vfs_support_termios"
|
||||
CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select"
|
||||
CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir"
|
||||
CONF_FREERTOS_IN_IRAM = "freertos_in_iram"
|
||||
CONF_RINGBUF_IN_IRAM = "ringbuf_in_iram"
|
||||
CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
|
||||
|
||||
# VFS requirement tracking
|
||||
@@ -677,6 +679,8 @@ FRAMEWORK_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_SELECT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_FREERTOS_IN_IRAM, default=False): cv.boolean,
|
||||
cv.Optional(CONF_RINGBUF_IN_IRAM, default=False): cv.boolean,
|
||||
cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean,
|
||||
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
|
||||
min=8192, max=32768
|
||||
@@ -903,6 +907,7 @@ async def to_code(config):
|
||||
)
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_build_flag("-DUSE_ESP32")
|
||||
cg.add_build_flag("-Wl,-z,noexecstack")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
variant = config[CONF_VARIANT]
|
||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}")
|
||||
@@ -1003,6 +1008,33 @@ async def to_code(config):
|
||||
# Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
|
||||
add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
|
||||
|
||||
# Place non-ISR FreeRTOS functions into flash instead of IRAM
|
||||
# This saves up to 8KB of IRAM. ISR-safe functions (FromISR variants) stay in IRAM.
|
||||
# In ESP-IDF 6.0 this becomes the default and CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH
|
||||
# is removed (replaced by CONFIG_FREERTOS_IN_IRAM to restore old behavior).
|
||||
# We enable this now to match IDF 6.0 behavior and catch any issues early.
|
||||
# Users can set freertos_in_iram: true as an escape hatch if they encounter problems
|
||||
# with code that incorrectly calls FreeRTOS functions from ISRs with cache disabled.
|
||||
if conf[CONF_ADVANCED][CONF_FREERTOS_IN_IRAM]:
|
||||
# IDF 5.x: don't set the flash option (keeps functions in IRAM)
|
||||
# IDF 6.0+: will need CONFIG_FREERTOS_IN_IRAM=y to restore IRAM placement
|
||||
add_idf_sdkconfig_option("CONFIG_FREERTOS_IN_IRAM", True)
|
||||
else:
|
||||
# IDF 5.x: explicitly place functions in flash
|
||||
# IDF 6.0+: this is the default, option no longer exists
|
||||
add_idf_sdkconfig_option("CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH", True)
|
||||
|
||||
# Place ring buffer functions into flash instead of IRAM by default
|
||||
# This saves IRAM. In ESP-IDF 6.0 flash placement becomes the default.
|
||||
# Users can set ringbuf_in_iram: true as an escape hatch if they encounter issues.
|
||||
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM]:
|
||||
# User requests ring buffer in IRAM
|
||||
# IDF 6.0+: will need CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=n
|
||||
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH", False)
|
||||
else:
|
||||
# Place in flash to save IRAM (default)
|
||||
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True)
|
||||
|
||||
# Setup watchdog
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True)
|
||||
|
||||
@@ -13,36 +13,36 @@ KEY_SUBMODULES = "submodules"
|
||||
KEY_EXTRA_BUILD_FILES = "extra_build_files"
|
||||
|
||||
VARIANT_ESP32 = "ESP32"
|
||||
VARIANT_ESP32S2 = "ESP32S2"
|
||||
VARIANT_ESP32S3 = "ESP32S3"
|
||||
VARIANT_ESP32C2 = "ESP32C2"
|
||||
VARIANT_ESP32C3 = "ESP32C3"
|
||||
VARIANT_ESP32C5 = "ESP32C5"
|
||||
VARIANT_ESP32C6 = "ESP32C6"
|
||||
VARIANT_ESP32H2 = "ESP32H2"
|
||||
VARIANT_ESP32P4 = "ESP32P4"
|
||||
VARIANT_ESP32S2 = "ESP32S2"
|
||||
VARIANT_ESP32S3 = "ESP32S3"
|
||||
VARIANTS = [
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
]
|
||||
|
||||
VARIANT_FRIENDLY = {
|
||||
VARIANT_ESP32: "ESP32",
|
||||
VARIANT_ESP32S2: "ESP32-S2",
|
||||
VARIANT_ESP32S3: "ESP32-S3",
|
||||
VARIANT_ESP32C2: "ESP32-C2",
|
||||
VARIANT_ESP32C3: "ESP32-C3",
|
||||
VARIANT_ESP32C5: "ESP32-C5",
|
||||
VARIANT_ESP32C6: "ESP32-C6",
|
||||
VARIANT_ESP32H2: "ESP32-H2",
|
||||
VARIANT_ESP32P4: "ESP32-P4",
|
||||
VARIANT_ESP32S2: "ESP32-S2",
|
||||
VARIANT_ESP32S3: "ESP32-S3",
|
||||
}
|
||||
|
||||
esp32_ns = cg.esphome_ns.namespace("esp32")
|
||||
|
||||
@@ -373,7 +373,9 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
||||
|
||||
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
|
||||
this->scanner_state_ = state;
|
||||
this->scanner_state_callbacks_.call(state);
|
||||
for (auto *listener : this->scanner_state_listeners_) {
|
||||
listener->on_scanner_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
|
||||
@@ -180,6 +180,16 @@ enum class ScannerState {
|
||||
STOPPING,
|
||||
};
|
||||
|
||||
/** Listener interface for BLE scanner state changes.
|
||||
*
|
||||
* Components can implement this interface to receive scanner state updates
|
||||
* without the overhead of std::function callbacks.
|
||||
*/
|
||||
class BLEScannerStateListener {
|
||||
public:
|
||||
virtual void on_scanner_state(ScannerState state) = 0;
|
||||
};
|
||||
|
||||
// Helper function to convert ClientState to string
|
||||
const char *client_state_to_string(ClientState state);
|
||||
|
||||
@@ -264,8 +274,9 @@ class ESP32BLETracker : public Component,
|
||||
void gap_scan_event_handler(const BLEScanResult &scan_result) override;
|
||||
void ble_before_disabled_event_handler() override;
|
||||
|
||||
void add_scanner_state_callback(std::function<void(ScannerState)> &&callback) {
|
||||
this->scanner_state_callbacks_.add(std::move(callback));
|
||||
/// Add a listener for scanner state changes
|
||||
void add_scanner_state_listener(BLEScannerStateListener *listener) {
|
||||
this->scanner_state_listeners_.push_back(listener);
|
||||
}
|
||||
ScannerState get_scanner_state() const { return this->scanner_state_; }
|
||||
|
||||
@@ -322,14 +333,14 @@ class ESP32BLETracker : public Component,
|
||||
return counts;
|
||||
}
|
||||
|
||||
// Group 1: Large objects (12+ bytes) - vectors and callback manager
|
||||
// Group 1: Large objects (12+ bytes) - vectors
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
StaticVector<ESPBTDeviceListener *, ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT> listeners_;
|
||||
#endif
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
StaticVector<ESPBTClient *, ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT> clients_;
|
||||
#endif
|
||||
CallbackManager<void(ScannerState)> scanner_state_callbacks_;
|
||||
std::vector<BLEScannerStateListener *> scanner_state_listeners_;
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
/// Vector of addresses that have already been printed in print_bt_device_info
|
||||
std::vector<uint64_t> already_discovered_;
|
||||
|
||||
@@ -205,7 +205,9 @@ void ESP32Camera::loop() {
|
||||
this->current_image_ = std::make_shared<ESP32CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
|
||||
|
||||
ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
|
||||
this->new_image_callback_.call(this->current_image_);
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->on_camera_image(this->current_image_);
|
||||
}
|
||||
this->last_update_ = now;
|
||||
this->single_requesters_ = 0;
|
||||
}
|
||||
@@ -357,21 +359,16 @@ void ESP32Camera::set_frame_buffer_location(camera_fb_location_t fb_location) {
|
||||
}
|
||||
|
||||
/* ---------------- public API (specific) ---------------- */
|
||||
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<camera::CameraImage>)> &&callback) {
|
||||
this->new_image_callback_.add(std::move(callback));
|
||||
}
|
||||
void ESP32Camera::add_stream_start_callback(std::function<void()> &&callback) {
|
||||
this->stream_start_callback_.add(std::move(callback));
|
||||
}
|
||||
void ESP32Camera::add_stream_stop_callback(std::function<void()> &&callback) {
|
||||
this->stream_stop_callback_.add(std::move(callback));
|
||||
}
|
||||
void ESP32Camera::start_stream(camera::CameraRequester requester) {
|
||||
this->stream_start_callback_.call();
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->on_stream_start();
|
||||
}
|
||||
this->stream_requesters_ |= (1U << requester);
|
||||
}
|
||||
void ESP32Camera::stop_stream(camera::CameraRequester requester) {
|
||||
this->stream_stop_callback_.call();
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->on_stream_stop();
|
||||
}
|
||||
this->stream_requesters_ &= ~(1U << requester);
|
||||
}
|
||||
void ESP32Camera::request_image(camera::CameraRequester requester) { this->single_requesters_ |= (1U << requester); }
|
||||
|
||||
@@ -165,9 +165,8 @@ class ESP32Camera : public camera::Camera {
|
||||
void request_image(camera::CameraRequester requester) override;
|
||||
void update_camera_parameters();
|
||||
|
||||
void add_image_callback(std::function<void(std::shared_ptr<camera::CameraImage>)> &&callback) override;
|
||||
void add_stream_start_callback(std::function<void()> &&callback);
|
||||
void add_stream_stop_callback(std::function<void()> &&callback);
|
||||
/// Add a listener to receive camera events
|
||||
void add_listener(camera::CameraListener *listener) override { this->listeners_.push_back(listener); }
|
||||
camera::CameraImageReader *create_image_reader() override;
|
||||
|
||||
protected:
|
||||
@@ -210,9 +209,7 @@ class ESP32Camera : public camera::Camera {
|
||||
uint8_t stream_requesters_{0};
|
||||
QueueHandle_t framebuffer_get_queue_;
|
||||
QueueHandle_t framebuffer_return_queue_;
|
||||
CallbackManager<void(std::shared_ptr<camera::CameraImage>)> new_image_callback_{};
|
||||
CallbackManager<void()> stream_start_callback_{};
|
||||
CallbackManager<void()> stream_stop_callback_{};
|
||||
std::vector<camera::CameraListener *> listeners_;
|
||||
|
||||
uint32_t last_idle_request_{0};
|
||||
uint32_t last_update_{0};
|
||||
@@ -221,33 +218,27 @@ class ESP32Camera : public camera::Camera {
|
||||
#endif // USE_I2C
|
||||
};
|
||||
|
||||
class ESP32CameraImageTrigger : public Trigger<CameraImageData> {
|
||||
class ESP32CameraImageTrigger : public Trigger<CameraImageData>, public camera::CameraListener {
|
||||
public:
|
||||
explicit ESP32CameraImageTrigger(ESP32Camera *parent) {
|
||||
parent->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) {
|
||||
CameraImageData camera_image_data{};
|
||||
camera_image_data.length = image->get_data_length();
|
||||
camera_image_data.data = image->get_data_buffer();
|
||||
this->trigger(camera_image_data);
|
||||
});
|
||||
explicit ESP32CameraImageTrigger(ESP32Camera *parent) { parent->add_listener(this); }
|
||||
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override {
|
||||
CameraImageData camera_image_data{};
|
||||
camera_image_data.length = image->get_data_length();
|
||||
camera_image_data.data = image->get_data_buffer();
|
||||
this->trigger(camera_image_data);
|
||||
}
|
||||
};
|
||||
|
||||
class ESP32CameraStreamStartTrigger : public Trigger<> {
|
||||
class ESP32CameraStreamStartTrigger : public Trigger<>, public camera::CameraListener {
|
||||
public:
|
||||
explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) {
|
||||
parent->add_stream_start_callback([this]() { this->trigger(); });
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) { parent->add_listener(this); }
|
||||
void on_stream_start() override { this->trigger(); }
|
||||
};
|
||||
class ESP32CameraStreamStopTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ESP32CameraStreamStopTrigger(ESP32Camera *parent) {
|
||||
parent->add_stream_stop_callback([this]() { this->trigger(); });
|
||||
}
|
||||
|
||||
protected:
|
||||
class ESP32CameraStreamStopTrigger : public Trigger<>, public camera::CameraListener {
|
||||
public:
|
||||
explicit ESP32CameraStreamStopTrigger(ESP32Camera *parent) { parent->add_listener(this); }
|
||||
void on_stream_stop() override { this->trigger(); }
|
||||
};
|
||||
|
||||
} // namespace esp32_camera
|
||||
|
||||
@@ -67,12 +67,14 @@ void CameraWebServer::setup() {
|
||||
|
||||
httpd_register_uri_handler(this->httpd_, &uri);
|
||||
|
||||
camera::Camera::instance()->add_image_callback([this](std::shared_ptr<camera::CameraImage> image) {
|
||||
if (this->running_ && image->was_requested_by(camera::WEB_REQUESTER)) {
|
||||
this->image_ = std::move(image);
|
||||
xSemaphoreGive(this->semaphore_);
|
||||
}
|
||||
});
|
||||
camera::Camera::instance()->add_listener(this);
|
||||
}
|
||||
|
||||
void CameraWebServer::on_camera_image(const std::shared_ptr<camera::CameraImage> &image) {
|
||||
if (this->running_ && image->was_requested_by(camera::WEB_REQUESTER)) {
|
||||
this->image_ = image;
|
||||
xSemaphoreGive(this->semaphore_);
|
||||
}
|
||||
}
|
||||
|
||||
void CameraWebServer::on_shutdown() {
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace esp32_camera_web_server {
|
||||
|
||||
enum Mode { STREAM, SNAPSHOT };
|
||||
|
||||
class CameraWebServer : public Component {
|
||||
class CameraWebServer : public Component, public camera::CameraListener {
|
||||
public:
|
||||
CameraWebServer();
|
||||
~CameraWebServer();
|
||||
@@ -31,6 +31,9 @@ class CameraWebServer : public Component {
|
||||
void set_mode(Mode mode) { this->mode_ = mode; }
|
||||
void loop() override;
|
||||
|
||||
/// CameraListener interface
|
||||
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<camera::CameraImage> wait_for_image_();
|
||||
esp_err_t handler_(struct httpd_req *req);
|
||||
|
||||
@@ -10,6 +10,7 @@ from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
@@ -59,14 +60,16 @@ CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2}
|
||||
CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2}
|
||||
CAN_SPEEDS_ESP32_C6 = {**CAN_SPEEDS_ESP32_S2}
|
||||
CAN_SPEEDS_ESP32_H2 = {**CAN_SPEEDS_ESP32_S2}
|
||||
CAN_SPEEDS_ESP32_P4 = {**CAN_SPEEDS_ESP32_S2}
|
||||
|
||||
CAN_SPEEDS = {
|
||||
VARIANT_ESP32: CAN_SPEEDS_ESP32,
|
||||
VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2,
|
||||
VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3,
|
||||
VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3,
|
||||
VARIANT_ESP32C6: CAN_SPEEDS_ESP32_C6,
|
||||
VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2,
|
||||
VARIANT_ESP32P4: CAN_SPEEDS_ESP32_P4,
|
||||
VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2,
|
||||
VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ static const char *const TAG = "esp32_can";
|
||||
|
||||
static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) {
|
||||
switch (bitrate) {
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case canbus::CAN_1KBPS:
|
||||
*t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS();
|
||||
return true;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user