mirror of
https://github.com/esphome/esphome.git
synced 2026-01-22 02:49:10 -07:00
Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bbf9153ca | ||
|
|
a1c4d56268 | ||
|
|
a9ce3df04c | ||
|
|
99aa83564e | ||
|
|
aa5092bdc2 | ||
|
|
645832a070 | ||
|
|
19c1d3aee7 | ||
|
|
ce5ec7a78f | ||
|
|
ebf589560d | ||
|
|
8dd1aec606 | ||
|
|
9d967b01c8 | ||
|
|
11e0d536e4 | ||
|
|
673f46f761 | ||
|
|
4abae8d445 | ||
|
|
e62368e058 | ||
|
|
5345c96ff3 | ||
|
|
333ace25c9 | ||
|
|
6014bba3d1 | ||
|
|
5f2394ef80 | ||
|
|
29555c0ddc | ||
|
|
37eaf10f75 | ||
|
|
0b60fd0c8c | ||
|
|
fc16ad806a | ||
|
|
7e43abd86f | ||
|
|
7a2734fae9 | ||
|
|
346f3d38d5 | ||
|
|
fbde91358c | ||
|
|
54d6825323 | ||
|
|
307c3e1061 | ||
|
|
df74d307c8 | ||
|
|
acdc7bd892 | ||
|
|
1095bde2db | ||
|
|
258b73d7f6 | ||
|
|
31608543c2 | ||
|
|
41a060668c | ||
|
|
6bad697fc6 | ||
|
|
3ca5e5e4e4 | ||
|
|
cd4cb8b3ec | ||
|
|
1f3a0490a7 | ||
|
|
b08d871add | ||
|
|
3c0f43db9e | ||
|
|
6edecd3d45 | ||
|
|
79ccacd6d6 | ||
|
|
e2319ba651 | ||
|
|
ed4ebffa74 | ||
|
|
c213de4861 | ||
|
|
6cf320fd60 | ||
|
|
aeea340bc6 | ||
|
|
d0e50ed030 | ||
|
|
280d460025 | ||
|
|
ea70faf642 | ||
|
|
5d7b38b261 | ||
|
|
e88093ca60 | ||
|
|
b48d4ab785 | ||
|
|
8ade9dfc10 | ||
|
|
4e0e7796de | ||
|
|
62b6c9bf7c | ||
|
|
b5fe271d6b | ||
|
|
5d787e2512 | ||
|
|
8998ef0bc3 | ||
|
|
8ec31dd769 | ||
|
|
0193464f92 | ||
|
|
1996bc425f | ||
|
|
a0d3d54d69 | ||
|
|
ee264d0fd4 | ||
|
|
892e9b006f | ||
|
|
f8bd4ef57d | ||
|
|
bfcc0e26a3 | ||
|
|
86a1b4cf69 | ||
|
|
d8a28f6fba | ||
|
|
e80a940222 | ||
|
|
e99dbe05f7 | ||
|
|
f453a8d9a1 | ||
|
|
126190d26a | ||
|
|
e40201a98d | ||
|
|
8142f5db44 | ||
|
|
98ccab87a7 | ||
|
|
b9e72a8774 | ||
|
|
d9fc625c6a | ||
|
|
dfbf79d6d6 | ||
|
|
ea0fac96cb | ||
|
|
3182222d60 | ||
|
|
d8849b16f2 | ||
|
|
635983f163 | ||
|
|
6cbe672004 | ||
|
|
226867b05c | ||
|
|
67871a1683 | ||
|
|
f60c03e350 | ||
|
|
eb66429144 | ||
|
|
0f3bac5dd6 | ||
|
|
5b92d0b89e | ||
|
|
052b05df56 | ||
|
|
7b0db659d1 | ||
|
|
2f7270cf8f | ||
|
|
b44727aee6 | ||
|
|
1a55254258 | ||
|
|
baf2b0e3c9 | ||
|
|
680e92a226 | ||
|
|
db0b32bfc9 | ||
|
|
21794e28e5 | ||
|
|
728236270c | ||
|
|
01cdc4ed58 | ||
|
|
d6a0c8ffbb | ||
|
|
4cc0f874f7 | ||
|
|
ed58b9372f | ||
|
|
ee2a81923b | ||
|
|
0a1e7ee50b | ||
|
|
4d4283bcfa | ||
|
|
e4fb6988ff | ||
|
|
d31b733dce | ||
|
|
b25a2f8d8e | ||
|
|
3f892711c7 | ||
|
|
798d3bd956 | ||
|
|
d830787c71 | ||
|
|
1f4221abfa | ||
|
|
92808a09c7 | ||
|
|
e54d5ee898 | ||
|
|
bbe1155518 | ||
|
|
69d7b6e921 | ||
|
|
510c874061 | ||
|
|
f7ad324d81 | ||
|
|
58a9e30017 | ||
|
|
52ac9e1861 | ||
|
|
c5e4a60884 | ||
|
|
a680884138 | ||
|
|
6832efbacc | ||
|
|
3057a0484f | ||
|
|
bc78f80f77 | ||
|
|
916b028fb2 | ||
|
|
16adae7359 | ||
|
|
4906f87751 | ||
|
|
5b37d2fb27 | ||
|
|
68affe0b9c | ||
|
|
8263a8273f | ||
|
|
14b7539094 | ||
|
|
b37cb812a7 | ||
|
|
42491569c8 | ||
|
|
b1230ec6bb | ||
|
|
4eda9e965f | ||
|
|
d2528af649 | ||
|
|
2eabc1b96b | ||
|
|
535c3eb2a2 | ||
|
|
20f937692e | ||
|
|
00cc9e44b6 | ||
|
|
0427350101 | ||
|
|
41dceb76ec | ||
|
|
6380458d78 | ||
|
|
0dc5a7c9a4 | ||
|
|
9003844eda | ||
|
|
22a4ec69c2 | ||
|
|
9d42bfd161 | ||
|
|
49c881d067 | ||
|
|
78aee4f498 | ||
|
|
9da2c08f36 | ||
|
|
03f3deff41 | ||
|
|
f1e5d3a39a | ||
|
|
2f6863230d | ||
|
|
f44036310c |
@@ -1 +1 @@
|
|||||||
d272a88e8ca28ae9340a9a03295a566432a52cb696501908f57764475bf7ca65
|
15dc295268b2dcf75942f42759b3ddec64eba89f75525698eb39c95a7f4b14ce
|
||||||
|
|||||||
96
.claude/skills/pr-workflow/SKILL.md
Normal file
96
.claude/skills/pr-workflow/SKILL.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
name: pr-workflow
|
||||||
|
description: Create pull requests for esphome. Use when creating PRs, submitting changes, or preparing contributions.
|
||||||
|
allowed-tools: Read, Bash, Glob, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
# ESPHome PR Workflow
|
||||||
|
|
||||||
|
When creating a pull request for esphome, follow these steps:
|
||||||
|
|
||||||
|
## 1. Create Branch from Upstream
|
||||||
|
|
||||||
|
Always base your branch on **upstream** (not origin/fork) to ensure you have the latest code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch upstream
|
||||||
|
git checkout -b <branch-name> upstream/dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Read the PR Template
|
||||||
|
|
||||||
|
Before creating a PR, read `.github/PULL_REQUEST_TEMPLATE.md` to understand required fields.
|
||||||
|
|
||||||
|
## 3. Create the PR
|
||||||
|
|
||||||
|
Use `gh pr create` with the **full template** filled in. Never skip or abbreviate sections.
|
||||||
|
|
||||||
|
Required fields:
|
||||||
|
- **What does this implement/fix?**: Brief description of changes
|
||||||
|
- **Types of changes**: Check ONE appropriate box (Bugfix, New feature, Breaking change, etc.)
|
||||||
|
- **Related issue**: Use `fixes <link>` syntax if applicable
|
||||||
|
- **Pull request in esphome-docs**: Link if docs are needed
|
||||||
|
- **Test Environment**: Check platforms you tested on
|
||||||
|
- **Example config.yaml**: Include working example YAML
|
||||||
|
- **Checklist**: Verify code is tested and tests added
|
||||||
|
|
||||||
|
## 4. Example PR Body
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# What does this implement/fix?
|
||||||
|
|
||||||
|
<describe your changes here>
|
||||||
|
|
||||||
|
## Types of changes
|
||||||
|
|
||||||
|
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||||
|
- [x] New feature (non-breaking change which adds functionality)
|
||||||
|
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||||
|
- [ ] Developer breaking change (an API change that could break external components)
|
||||||
|
- [ ] Code quality improvements to existing code or addition of tests
|
||||||
|
- [ ] Other
|
||||||
|
|
||||||
|
**Related issue or feature (if applicable):**
|
||||||
|
|
||||||
|
- fixes https://github.com/esphome/esphome/issues/XXX
|
||||||
|
|
||||||
|
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):**
|
||||||
|
|
||||||
|
- esphome/esphome-docs#XXX
|
||||||
|
|
||||||
|
## Test Environment
|
||||||
|
|
||||||
|
- [x] ESP32
|
||||||
|
- [x] ESP32 IDF
|
||||||
|
- [ ] ESP8266
|
||||||
|
- [ ] RP2040
|
||||||
|
- [ ] BK72xx
|
||||||
|
- [ ] RTL87xx
|
||||||
|
- [ ] LN882x
|
||||||
|
- [ ] nRF52840
|
||||||
|
|
||||||
|
## Example entry for `config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Example config.yaml
|
||||||
|
component_name:
|
||||||
|
id: my_component
|
||||||
|
option: value
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checklist:
|
||||||
|
- [x] The code change is tested and works locally.
|
||||||
|
- [x] Tests have been added to verify that the new code works (under `tests/` folder).
|
||||||
|
|
||||||
|
If user exposed functionality or configuration variables are added/changed:
|
||||||
|
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Push and Create PR
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push -u origin <branch-name>
|
||||||
|
gh pr create --repo esphome/esphome --base dev --title "[component] Brief description"
|
||||||
|
```
|
||||||
|
|
||||||
|
Title should be prefixed with the component name in brackets, e.g. `[safe_mode] Add feature`.
|
||||||
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@@ -22,7 +22,7 @@ runs:
|
|||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
|||||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
@@ -157,7 +157,7 @@ jobs:
|
|||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Save Python virtual environment cache
|
- name: Save Python virtual environment cache
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -193,7 +193,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Restore components graph cache
|
- name: Restore components graph cache
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: .temp/components_graph.json
|
path: .temp/components_graph.json
|
||||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||||
@@ -223,7 +223,7 @@ jobs:
|
|||||||
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
||||||
- name: Save components graph cache
|
- name: Save components graph cache
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: .temp/components_graph.json
|
path: .temp/components_graph.json
|
||||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||||
@@ -245,7 +245,7 @@ jobs:
|
|||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -334,14 +334,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -413,14 +413,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -502,14 +502,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -735,7 +735,7 @@ jobs:
|
|||||||
- name: Restore cached memory analysis
|
- name: Restore cached memory analysis
|
||||||
id: cache-memory-analysis
|
id: cache-memory-analysis
|
||||||
if: steps.check-script.outputs.skip != 'true'
|
if: steps.check-script.outputs.skip != 'true'
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: memory-analysis-target.json
|
path: memory-analysis-target.json
|
||||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||||
@@ -759,7 +759,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -800,7 +800,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save memory analysis to cache
|
- name: Save memory analysis to cache
|
||||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||||
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: memory-analysis-target.json
|
path: memory-analysis-target.json
|
||||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||||
@@ -847,7 +847,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|||||||
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
python script/run-in-env.py pre-commit run --all-files
|
python script/run-in-env.py pre-commit run --all-files
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||||
with:
|
with:
|
||||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||||
committer: esphomebot <esphome@openhomefoundation.org>
|
committer: esphomebot <esphome@openhomefoundation.org>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ci:
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.14.11
|
rev: v0.14.13
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|||||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2026.1.0
|
PROJECT_NUMBER = 2026.2.0-dev
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from esphome.const import (
|
|||||||
CONF_SUBSTITUTIONS,
|
CONF_SUBSTITUTIONS,
|
||||||
CONF_TOPIC,
|
CONF_TOPIC,
|
||||||
ENV_NOGITIGNORE,
|
ENV_NOGITIGNORE,
|
||||||
|
KEY_NATIVE_IDF,
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
@@ -116,6 +117,7 @@ class ArgsProtocol(Protocol):
|
|||||||
configuration: str
|
configuration: str
|
||||||
name: str
|
name: str
|
||||||
upload_speed: str | None
|
upload_speed: str | None
|
||||||
|
native_idf: bool
|
||||||
|
|
||||||
|
|
||||||
def choose_prompt(options, purpose: str = None):
|
def choose_prompt(options, purpose: str = None):
|
||||||
@@ -223,8 +225,13 @@ def choose_upload_log_host(
|
|||||||
else:
|
else:
|
||||||
resolved.append(device)
|
resolved.append(device)
|
||||||
if not resolved:
|
if not resolved:
|
||||||
|
if CORE.dashboard:
|
||||||
|
hint = "If you know the IP, set 'use_address' in your network config."
|
||||||
|
else:
|
||||||
|
hint = "If you know the IP, try --device <IP>"
|
||||||
raise EsphomeError(
|
raise EsphomeError(
|
||||||
f"All specified devices {defaults} could not be resolved. Is the device connected to the network?"
|
f"All specified devices {defaults} could not be resolved. "
|
||||||
|
f"Is the device connected to the network? {hint}"
|
||||||
)
|
)
|
||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
@@ -495,12 +502,15 @@ def wrap_to_code(name, comp):
|
|||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
def write_cpp(config: ConfigType) -> int:
|
def write_cpp(config: ConfigType, native_idf: bool = False) -> int:
|
||||||
if not get_bool_env(ENV_NOGITIGNORE):
|
if not get_bool_env(ENV_NOGITIGNORE):
|
||||||
writer.write_gitignore()
|
writer.write_gitignore()
|
||||||
|
|
||||||
|
# Store native_idf flag so esp32 component can check it
|
||||||
|
CORE.data[KEY_NATIVE_IDF] = native_idf
|
||||||
|
|
||||||
generate_cpp_contents(config)
|
generate_cpp_contents(config)
|
||||||
return write_cpp_file()
|
return write_cpp_file(native_idf=native_idf)
|
||||||
|
|
||||||
|
|
||||||
def generate_cpp_contents(config: ConfigType) -> None:
|
def generate_cpp_contents(config: ConfigType) -> None:
|
||||||
@@ -514,32 +524,54 @@ def generate_cpp_contents(config: ConfigType) -> None:
|
|||||||
CORE.flush_tasks()
|
CORE.flush_tasks()
|
||||||
|
|
||||||
|
|
||||||
def write_cpp_file() -> int:
|
def write_cpp_file(native_idf: bool = False) -> int:
|
||||||
code_s = indent(CORE.cpp_main_section)
|
code_s = indent(CORE.cpp_main_section)
|
||||||
writer.write_cpp(code_s)
|
writer.write_cpp(code_s)
|
||||||
|
|
||||||
from esphome.build_gen import platformio
|
if native_idf and CORE.is_esp32 and CORE.target_framework == "esp-idf":
|
||||||
|
from esphome.build_gen import espidf
|
||||||
|
|
||||||
platformio.write_project()
|
espidf.write_project()
|
||||||
|
else:
|
||||||
|
from esphome.build_gen import platformio
|
||||||
|
|
||||||
|
platformio.write_project()
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
||||||
from esphome import platformio_api
|
native_idf = getattr(args, "native_idf", False)
|
||||||
|
|
||||||
# NOTE: "Build path:" format is parsed by script/ci_memory_impact_extract.py
|
# NOTE: "Build path:" format is parsed by script/ci_memory_impact_extract.py
|
||||||
# If you change this format, update the regex in that script as well
|
# If you change this format, update the regex in that script as well
|
||||||
_LOGGER.info("Compiling app... Build path: %s", CORE.build_path)
|
_LOGGER.info("Compiling app... Build path: %s", CORE.build_path)
|
||||||
rc = platformio_api.run_compile(config, CORE.verbose)
|
|
||||||
if rc != 0:
|
if native_idf and CORE.is_esp32 and CORE.target_framework == "esp-idf":
|
||||||
return rc
|
from esphome import espidf_api
|
||||||
|
|
||||||
|
rc = espidf_api.run_compile(config, CORE.verbose)
|
||||||
|
if rc != 0:
|
||||||
|
return rc
|
||||||
|
|
||||||
|
# Create factory.bin and ota.bin
|
||||||
|
espidf_api.create_factory_bin()
|
||||||
|
espidf_api.create_ota_bin()
|
||||||
|
else:
|
||||||
|
from esphome import platformio_api
|
||||||
|
|
||||||
|
rc = platformio_api.run_compile(config, CORE.verbose)
|
||||||
|
if rc != 0:
|
||||||
|
return rc
|
||||||
|
|
||||||
|
idedata = platformio_api.get_idedata(config)
|
||||||
|
if idedata is None:
|
||||||
|
return 1
|
||||||
|
|
||||||
# Check if firmware was rebuilt and emit build_info + create manifest
|
# Check if firmware was rebuilt and emit build_info + create manifest
|
||||||
_check_and_emit_build_info()
|
_check_and_emit_build_info()
|
||||||
|
|
||||||
idedata = platformio_api.get_idedata(config)
|
return 0
|
||||||
return 0 if idedata is not None else 1
|
|
||||||
|
|
||||||
|
|
||||||
def _check_and_emit_build_info() -> None:
|
def _check_and_emit_build_info() -> None:
|
||||||
@@ -796,7 +828,8 @@ def command_vscode(args: ArgsProtocol) -> int | None:
|
|||||||
|
|
||||||
|
|
||||||
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
exit_code = write_cpp(config)
|
native_idf = getattr(args, "native_idf", False)
|
||||||
|
exit_code = write_cpp(config, native_idf=native_idf)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
if args.only_generate:
|
if args.only_generate:
|
||||||
@@ -851,7 +884,8 @@ def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
|
|
||||||
|
|
||||||
def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
exit_code = write_cpp(config)
|
native_idf = getattr(args, "native_idf", False)
|
||||||
|
exit_code = write_cpp(config, native_idf=native_idf)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
exit_code = compile_program(args, config)
|
exit_code = compile_program(args, config)
|
||||||
@@ -1305,6 +1339,11 @@ def parse_args(argv):
|
|||||||
help="Only generate source code, do not compile.",
|
help="Only generate source code, do not compile.",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
parser_compile.add_argument(
|
||||||
|
"--native-idf",
|
||||||
|
help="Build with native ESP-IDF instead of PlatformIO (ESP32 esp-idf framework only).",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
parser_upload = subparsers.add_parser(
|
parser_upload = subparsers.add_parser(
|
||||||
"upload",
|
"upload",
|
||||||
@@ -1386,6 +1425,11 @@ def parse_args(argv):
|
|||||||
help="Reset the device before starting serial logs.",
|
help="Reset the device before starting serial logs.",
|
||||||
default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
|
default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
|
||||||
)
|
)
|
||||||
|
parser_run.add_argument(
|
||||||
|
"--native-idf",
|
||||||
|
help="Build with native ESP-IDF instead of PlatformIO (ESP32 esp-idf framework only).",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
parser_clean = subparsers.add_parser(
|
parser_clean = subparsers.add_parser(
|
||||||
"clean-mqtt",
|
"clean-mqtt",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from .helpers import (
|
|||||||
map_section_name,
|
map_section_name,
|
||||||
parse_symbol_line,
|
parse_symbol_line,
|
||||||
)
|
)
|
||||||
from .toolchain import find_tool, run_tool
|
from .toolchain import find_tool, resolve_tool_path, run_tool
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from esphome.platformio_api import IDEData
|
from esphome.platformio_api import IDEData
|
||||||
@@ -132,6 +132,12 @@ class MemoryAnalyzer:
|
|||||||
readelf_path = readelf_path or idedata.readelf_path
|
readelf_path = readelf_path or idedata.readelf_path
|
||||||
_LOGGER.debug("Using toolchain paths from PlatformIO idedata")
|
_LOGGER.debug("Using toolchain paths from PlatformIO idedata")
|
||||||
|
|
||||||
|
# Validate paths exist, fall back to find_tool if they don't
|
||||||
|
# This handles cases like Zephyr where cc_path doesn't include full path
|
||||||
|
# and the toolchain prefix may differ (e.g., arm-zephyr-eabi- vs arm-none-eabi-)
|
||||||
|
objdump_path = resolve_tool_path("objdump", objdump_path, objdump_path)
|
||||||
|
readelf_path = resolve_tool_path("readelf", readelf_path, objdump_path)
|
||||||
|
|
||||||
self.objdump_path = objdump_path or "objdump"
|
self.objdump_path = objdump_path or "objdump"
|
||||||
self.readelf_path = readelf_path or "readelf"
|
self.readelf_path = readelf_path or "readelf"
|
||||||
self.external_components = external_components or set()
|
self.external_components = external_components or set()
|
||||||
|
|||||||
@@ -9,11 +9,61 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::")
|
|||||||
# Maps standard section names to their various platform-specific variants
|
# Maps standard section names to their various platform-specific variants
|
||||||
# Note: Order matters! More specific patterns (.bss) must come before general ones (.dram)
|
# Note: Order matters! More specific patterns (.bss) must come before general ones (.dram)
|
||||||
# because ESP-IDF uses names like ".dram0.bss" which would match ".dram" otherwise
|
# because ESP-IDF uses names like ".dram0.bss" which would match ".dram" otherwise
|
||||||
|
#
|
||||||
|
# Platform-specific sections:
|
||||||
|
# - ESP8266/ESP32: .iram*, .dram*
|
||||||
|
# - LibreTiny RTL87xx: .xip.code_* (flash), .ram.code_* (RAM)
|
||||||
|
# - LibreTiny BK7231: .itcm.code (fast RAM), .vectors (interrupt vectors)
|
||||||
|
# - LibreTiny LN882X: .flash_text, .flash_copy* (flash code)
|
||||||
|
# - Zephyr/nRF52: text, rodata, datas, bss (no leading dots)
|
||||||
SECTION_MAPPING = {
|
SECTION_MAPPING = {
|
||||||
".text": frozenset([".text", ".iram"]),
|
".text": frozenset(
|
||||||
".rodata": frozenset([".rodata"]),
|
[
|
||||||
".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss"
|
".text",
|
||||||
".data": frozenset([".data", ".dram"]),
|
".iram",
|
||||||
|
# LibreTiny RTL87xx XIP (eXecute In Place) flash code
|
||||||
|
".xip.code",
|
||||||
|
# LibreTiny RTL87xx RAM code
|
||||||
|
".ram.code_text",
|
||||||
|
# LibreTiny BK7231 fast RAM code and vectors
|
||||||
|
".itcm.code",
|
||||||
|
".vectors",
|
||||||
|
# LibreTiny LN882X flash code
|
||||||
|
".flash_text",
|
||||||
|
".flash_copy",
|
||||||
|
# Zephyr/nRF52 sections (no leading dots)
|
||||||
|
"text",
|
||||||
|
"rom_start",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
".rodata": frozenset(
|
||||||
|
[
|
||||||
|
".rodata",
|
||||||
|
# LibreTiny RTL87xx read-only data in RAM
|
||||||
|
".ram.code_rodata",
|
||||||
|
# Zephyr/nRF52 sections (no leading dots)
|
||||||
|
"rodata",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
# .bss patterns - must be before .data to catch ".dram0.bss"
|
||||||
|
".bss": frozenset(
|
||||||
|
[
|
||||||
|
".bss",
|
||||||
|
# LibreTiny LN882X BSS
|
||||||
|
".bss_ram",
|
||||||
|
# Zephyr/nRF52 sections (no leading dots)
|
||||||
|
"bss",
|
||||||
|
"noinit",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
".data": frozenset(
|
||||||
|
[
|
||||||
|
".data",
|
||||||
|
".dram",
|
||||||
|
# Zephyr/nRF52 sections (no leading dots)
|
||||||
|
"datas",
|
||||||
|
]
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Section to ComponentMemory attribute mapping
|
# Section to ComponentMemory attribute mapping
|
||||||
|
|||||||
@@ -94,13 +94,13 @@ def parse_symbol_line(line: str) -> tuple[str, str, int, str] | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Find section, size, and name
|
# Find section, size, and name
|
||||||
|
# Try each part as a potential section name
|
||||||
for i, part in enumerate(parts):
|
for i, part in enumerate(parts):
|
||||||
if not part.startswith("."):
|
# Skip parts that are clearly flags, addresses, or other metadata
|
||||||
continue
|
# Sections start with '.' (standard ELF) or are known section names (Zephyr)
|
||||||
|
|
||||||
section = map_section_name(part)
|
section = map_section_name(part)
|
||||||
if not section:
|
if not section:
|
||||||
break
|
continue
|
||||||
|
|
||||||
# Need at least size field after section
|
# Need at least size field after section
|
||||||
if i + 1 >= len(parts):
|
if i + 1 >= len(parts):
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
@@ -17,10 +18,82 @@ TOOLCHAIN_PREFIXES = [
|
|||||||
"xtensa-lx106-elf-", # ESP8266
|
"xtensa-lx106-elf-", # ESP8266
|
||||||
"xtensa-esp32-elf-", # ESP32
|
"xtensa-esp32-elf-", # ESP32
|
||||||
"xtensa-esp-elf-", # ESP32 (newer IDF)
|
"xtensa-esp-elf-", # ESP32 (newer IDF)
|
||||||
|
"arm-zephyr-eabi-", # nRF52/Zephyr SDK
|
||||||
|
"arm-none-eabi-", # Generic ARM (RP2040, etc.)
|
||||||
"", # System default (no prefix)
|
"", # System default (no prefix)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _find_in_platformio_packages(tool_name: str) -> str | None:
|
||||||
|
"""Search for a tool in PlatformIO package directories.
|
||||||
|
|
||||||
|
This handles cases like Zephyr SDK where tools are installed in nested
|
||||||
|
directories that aren't in PATH.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name: Name of the tool (e.g., "readelf", "objdump")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Full path to the tool or None if not found
|
||||||
|
"""
|
||||||
|
# Get PlatformIO packages directory
|
||||||
|
platformio_home = Path(os.path.expanduser("~/.platformio/packages"))
|
||||||
|
if not platformio_home.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Search patterns for toolchains that might contain the tool
|
||||||
|
# Order matters - more specific patterns first
|
||||||
|
search_patterns = [
|
||||||
|
# Zephyr SDK deeply nested structure (4 levels)
|
||||||
|
# e.g., toolchain-gccarmnoneeabi/zephyr-sdk-0.17.4/arm-zephyr-eabi/bin/arm-zephyr-eabi-objdump
|
||||||
|
f"toolchain-*/*/*/bin/*-{tool_name}",
|
||||||
|
# Zephyr SDK nested structure (3 levels)
|
||||||
|
f"toolchain-*/*/bin/*-{tool_name}",
|
||||||
|
f"toolchain-*/bin/*-{tool_name}",
|
||||||
|
# Standard PlatformIO toolchain structure
|
||||||
|
f"toolchain-*/bin/*{tool_name}",
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in search_patterns:
|
||||||
|
matches = list(platformio_home.glob(pattern))
|
||||||
|
if matches:
|
||||||
|
# Sort to get consistent results, prefer arm-zephyr-eabi over arm-none-eabi
|
||||||
|
matches.sort(key=lambda p: ("zephyr" not in str(p), str(p)))
|
||||||
|
tool_path = str(matches[0])
|
||||||
|
_LOGGER.debug("Found %s in PlatformIO packages: %s", tool_name, tool_path)
|
||||||
|
return tool_path
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_tool_path(
|
||||||
|
tool_name: str,
|
||||||
|
derived_path: str | None,
|
||||||
|
objdump_path: str | None = None,
|
||||||
|
) -> str | None:
|
||||||
|
"""Resolve a tool path, falling back to find_tool if derived path doesn't exist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name: Name of the tool (e.g., "objdump", "readelf")
|
||||||
|
derived_path: Path derived from idedata (may not exist for some platforms)
|
||||||
|
objdump_path: Path to objdump binary to derive other tool paths from
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Resolved path to the tool, or the original derived_path if it exists
|
||||||
|
"""
|
||||||
|
if derived_path and not Path(derived_path).exists():
|
||||||
|
found = find_tool(tool_name, objdump_path)
|
||||||
|
if found:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Derived %s path %s not found, using %s",
|
||||||
|
tool_name,
|
||||||
|
derived_path,
|
||||||
|
found,
|
||||||
|
)
|
||||||
|
return found
|
||||||
|
return derived_path
|
||||||
|
|
||||||
|
|
||||||
def find_tool(
|
def find_tool(
|
||||||
tool_name: str,
|
tool_name: str,
|
||||||
objdump_path: str | None = None,
|
objdump_path: str | None = None,
|
||||||
@@ -28,7 +101,8 @@ def find_tool(
|
|||||||
"""Find a toolchain tool by name.
|
"""Find a toolchain tool by name.
|
||||||
|
|
||||||
First tries to derive the tool path from objdump_path (if provided),
|
First tries to derive the tool path from objdump_path (if provided),
|
||||||
then falls back to searching for platform-specific tools.
|
then searches PlatformIO package directories (for cross-compile toolchains),
|
||||||
|
and finally falls back to searching for platform-specific tools in PATH.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tool_name: Name of the tool (e.g., "objdump", "nm", "c++filt")
|
tool_name: Name of the tool (e.g., "objdump", "nm", "c++filt")
|
||||||
@@ -47,7 +121,13 @@ def find_tool(
|
|||||||
_LOGGER.debug("Found %s at: %s", tool_name, potential_path)
|
_LOGGER.debug("Found %s at: %s", tool_name, potential_path)
|
||||||
return potential_path
|
return potential_path
|
||||||
|
|
||||||
# Try platform-specific tools
|
# Search in PlatformIO packages directory first (handles Zephyr SDK, etc.)
|
||||||
|
# This must come before PATH search because system tools (e.g., /usr/bin/objdump)
|
||||||
|
# are for the host architecture, not the target (ARM, Xtensa, etc.)
|
||||||
|
if found := _find_in_platformio_packages(tool_name):
|
||||||
|
return found
|
||||||
|
|
||||||
|
# Try platform-specific tools in PATH (fallback for when tools are installed globally)
|
||||||
for prefix in TOOLCHAIN_PREFIXES:
|
for prefix in TOOLCHAIN_PREFIXES:
|
||||||
cmd = f"{prefix}{tool_name}"
|
cmd = f"{prefix}{tool_name}"
|
||||||
try:
|
try:
|
||||||
|
|||||||
139
esphome/build_gen/espidf.py
Normal file
139
esphome/build_gen/espidf.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
"""ESP-IDF direct build generator for ESPHome."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from esphome.components.esp32 import get_esp32_variant
|
||||||
|
from esphome.core import CORE
|
||||||
|
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_components() -> list[str] | None:
|
||||||
|
"""Get list of available ESP-IDF components from project_description.json.
|
||||||
|
|
||||||
|
Returns only internal ESP-IDF components, excluding external/managed
|
||||||
|
components (from idf_component.yml).
|
||||||
|
"""
|
||||||
|
project_desc = Path(CORE.build_path) / "build" / "project_description.json"
|
||||||
|
if not project_desc.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(project_desc, encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
component_info = data.get("build_component_info", {})
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for name, info in component_info.items():
|
||||||
|
# Exclude our own src component
|
||||||
|
if name == "src":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Exclude managed/external components
|
||||||
|
comp_dir = info.get("dir", "")
|
||||||
|
if "managed_components" in comp_dir:
|
||||||
|
continue
|
||||||
|
|
||||||
|
result.append(name)
|
||||||
|
|
||||||
|
return result
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def has_discovered_components() -> bool:
|
||||||
|
"""Check if we have discovered components from a previous configure."""
|
||||||
|
return get_available_components() is not None
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_cmakelists() -> str:
|
||||||
|
"""Generate the top-level CMakeLists.txt for ESP-IDF project."""
|
||||||
|
# Get IDF target from ESP32 variant (e.g., ESP32S3 -> esp32s3)
|
||||||
|
variant = get_esp32_variant()
|
||||||
|
idf_target = variant.lower().replace("-", "")
|
||||||
|
|
||||||
|
return f"""\
|
||||||
|
# Auto-generated by ESPHome
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
set(IDF_TARGET {idf_target})
|
||||||
|
set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)
|
||||||
|
|
||||||
|
include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
|
||||||
|
project({CORE.name})
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_component_cmakelists(minimal: bool = False) -> str:
|
||||||
|
"""Generate the main component CMakeLists.txt."""
|
||||||
|
idf_requires = [] if minimal else (get_available_components() or [])
|
||||||
|
requires_str = " ".join(idf_requires)
|
||||||
|
|
||||||
|
# Extract compile definitions from build flags (-DXXX -> XXX)
|
||||||
|
compile_defs = [flag[2:] for flag in CORE.build_flags if flag.startswith("-D")]
|
||||||
|
compile_defs_str = "\n ".join(compile_defs) if compile_defs else ""
|
||||||
|
|
||||||
|
# Extract compile options (-W flags, excluding linker flags)
|
||||||
|
compile_opts = [
|
||||||
|
flag
|
||||||
|
for flag in CORE.build_flags
|
||||||
|
if flag.startswith("-W") and not flag.startswith("-Wl,")
|
||||||
|
]
|
||||||
|
compile_opts_str = "\n ".join(compile_opts) if compile_opts else ""
|
||||||
|
|
||||||
|
# Extract linker options (-Wl, flags)
|
||||||
|
link_opts = [flag for flag in CORE.build_flags if flag.startswith("-Wl,")]
|
||||||
|
link_opts_str = "\n ".join(link_opts) if link_opts else ""
|
||||||
|
|
||||||
|
return f"""\
|
||||||
|
# Auto-generated by ESPHome
|
||||||
|
file(GLOB_RECURSE app_sources
|
||||||
|
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cpp"
|
||||||
|
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c"
|
||||||
|
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cpp"
|
||||||
|
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c"
|
||||||
|
)
|
||||||
|
|
||||||
|
idf_component_register(
|
||||||
|
SRCS ${{app_sources}}
|
||||||
|
INCLUDE_DIRS "." "esphome"
|
||||||
|
REQUIRES {requires_str}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply C++ standard
|
||||||
|
target_compile_features(${{COMPONENT_LIB}} PUBLIC cxx_std_20)
|
||||||
|
|
||||||
|
# ESPHome compile definitions
|
||||||
|
target_compile_definitions(${{COMPONENT_LIB}} PUBLIC
|
||||||
|
{compile_defs_str}
|
||||||
|
)
|
||||||
|
|
||||||
|
# ESPHome compile options
|
||||||
|
target_compile_options(${{COMPONENT_LIB}} PUBLIC
|
||||||
|
{compile_opts_str}
|
||||||
|
)
|
||||||
|
|
||||||
|
# ESPHome linker options
|
||||||
|
target_link_options(${{COMPONENT_LIB}} PUBLIC
|
||||||
|
{link_opts_str}
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def write_project(minimal: bool = False) -> None:
|
||||||
|
"""Write ESP-IDF project files."""
|
||||||
|
mkdir_p(CORE.build_path)
|
||||||
|
mkdir_p(CORE.relative_src_path())
|
||||||
|
|
||||||
|
# Write top-level CMakeLists.txt
|
||||||
|
write_file_if_changed(
|
||||||
|
CORE.relative_build_path("CMakeLists.txt"),
|
||||||
|
get_project_cmakelists(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write component CMakeLists.txt in src/
|
||||||
|
write_file_if_changed(
|
||||||
|
CORE.relative_src_path("CMakeLists.txt"),
|
||||||
|
get_component_cmakelists(minimal=minimal),
|
||||||
|
)
|
||||||
@@ -69,6 +69,7 @@ from esphome.cpp_types import ( # noqa: F401
|
|||||||
JsonObjectConst,
|
JsonObjectConst,
|
||||||
Parented,
|
Parented,
|
||||||
PollingComponent,
|
PollingComponent,
|
||||||
|
StringRef,
|
||||||
arduino_json_ns,
|
arduino_json_ns,
|
||||||
bool_,
|
bool_,
|
||||||
const_char_ptr,
|
const_char_ptr,
|
||||||
|
|||||||
@@ -160,21 +160,21 @@ async def to_code(config):
|
|||||||
zephyr_add_user("io-channels", f"<&adc {channel_id}>")
|
zephyr_add_user("io-channels", f"<&adc {channel_id}>")
|
||||||
zephyr_add_overlay(
|
zephyr_add_overlay(
|
||||||
f"""
|
f"""
|
||||||
&adc {{
|
&adc {{
|
||||||
#address-cells = <1>;
|
#address-cells = <1>;
|
||||||
#size-cells = <0>;
|
#size-cells = <0>;
|
||||||
|
|
||||||
channel@{channel_id} {{
|
channel@{channel_id} {{
|
||||||
reg = <{channel_id}>;
|
reg = <{channel_id}>;
|
||||||
zephyr,gain = "{gain}";
|
zephyr,gain = "{gain}";
|
||||||
zephyr,reference = "ADC_REF_INTERNAL";
|
zephyr,reference = "ADC_REF_INTERNAL";
|
||||||
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
|
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
|
||||||
zephyr,input-positive = <NRF_SAADC_{pin_number}>;
|
zephyr,input-positive = <NRF_SAADC_{pin_number}>;
|
||||||
zephyr,resolution = <14>;
|
zephyr,resolution = <14>;
|
||||||
zephyr,oversampling = <8>;
|
zephyr,oversampling = <8>;
|
||||||
}};
|
}};
|
||||||
}};
|
}};
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -67,52 +67,29 @@ void AlarmControlPanel::add_on_ready_callback(std::function<void()> &&callback)
|
|||||||
this->ready_callback_.add(std::move(callback));
|
this->ready_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlarmControlPanel::arm_away(optional<std::string> code) {
|
void AlarmControlPanel::arm_with_code_(AlarmControlPanelCall &(AlarmControlPanelCall::*arm_method)(),
|
||||||
|
const char *code) {
|
||||||
auto call = this->make_call();
|
auto call = this->make_call();
|
||||||
call.arm_away();
|
(call.*arm_method)();
|
||||||
if (code.has_value())
|
if (code != nullptr)
|
||||||
call.set_code(code.value());
|
call.set_code(code);
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlarmControlPanel::arm_home(optional<std::string> code) {
|
void AlarmControlPanel::arm_away(const char *code) { this->arm_with_code_(&AlarmControlPanelCall::arm_away, code); }
|
||||||
auto call = this->make_call();
|
|
||||||
call.arm_home();
|
void AlarmControlPanel::arm_home(const char *code) { this->arm_with_code_(&AlarmControlPanelCall::arm_home, code); }
|
||||||
if (code.has_value())
|
|
||||||
call.set_code(code.value());
|
void AlarmControlPanel::arm_night(const char *code) { this->arm_with_code_(&AlarmControlPanelCall::arm_night, code); }
|
||||||
call.perform();
|
|
||||||
|
void AlarmControlPanel::arm_vacation(const char *code) {
|
||||||
|
this->arm_with_code_(&AlarmControlPanelCall::arm_vacation, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlarmControlPanel::arm_night(optional<std::string> code) {
|
void AlarmControlPanel::arm_custom_bypass(const char *code) {
|
||||||
auto call = this->make_call();
|
this->arm_with_code_(&AlarmControlPanelCall::arm_custom_bypass, code);
|
||||||
call.arm_night();
|
|
||||||
if (code.has_value())
|
|
||||||
call.set_code(code.value());
|
|
||||||
call.perform();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlarmControlPanel::arm_vacation(optional<std::string> code) {
|
void AlarmControlPanel::disarm(const char *code) { this->arm_with_code_(&AlarmControlPanelCall::disarm, code); }
|
||||||
auto call = this->make_call();
|
|
||||||
call.arm_vacation();
|
|
||||||
if (code.has_value())
|
|
||||||
call.set_code(code.value());
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AlarmControlPanel::arm_custom_bypass(optional<std::string> code) {
|
|
||||||
auto call = this->make_call();
|
|
||||||
call.arm_custom_bypass();
|
|
||||||
if (code.has_value())
|
|
||||||
call.set_code(code.value());
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AlarmControlPanel::disarm(optional<std::string> code) {
|
|
||||||
auto call = this->make_call();
|
|
||||||
call.disarm();
|
|
||||||
if (code.has_value())
|
|
||||||
call.set_code(code.value());
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::alarm_control_panel
|
} // namespace esphome::alarm_control_panel
|
||||||
|
|||||||
@@ -76,37 +76,53 @@ class AlarmControlPanel : public EntityBase {
|
|||||||
*
|
*
|
||||||
* @param code The code
|
* @param code The code
|
||||||
*/
|
*/
|
||||||
void arm_away(optional<std::string> code = nullopt);
|
void arm_away(const char *code = nullptr);
|
||||||
|
void arm_away(const optional<std::string> &code) {
|
||||||
|
this->arm_away(code.has_value() ? code.value().c_str() : nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
/** arm the alarm in home mode
|
/** arm the alarm in home mode
|
||||||
*
|
*
|
||||||
* @param code The code
|
* @param code The code
|
||||||
*/
|
*/
|
||||||
void arm_home(optional<std::string> code = nullopt);
|
void arm_home(const char *code = nullptr);
|
||||||
|
void arm_home(const optional<std::string> &code) {
|
||||||
|
this->arm_home(code.has_value() ? code.value().c_str() : nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
/** arm the alarm in night mode
|
/** arm the alarm in night mode
|
||||||
*
|
*
|
||||||
* @param code The code
|
* @param code The code
|
||||||
*/
|
*/
|
||||||
void arm_night(optional<std::string> code = nullopt);
|
void arm_night(const char *code = nullptr);
|
||||||
|
void arm_night(const optional<std::string> &code) {
|
||||||
|
this->arm_night(code.has_value() ? code.value().c_str() : nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
/** arm the alarm in vacation mode
|
/** arm the alarm in vacation mode
|
||||||
*
|
*
|
||||||
* @param code The code
|
* @param code The code
|
||||||
*/
|
*/
|
||||||
void arm_vacation(optional<std::string> code = nullopt);
|
void arm_vacation(const char *code = nullptr);
|
||||||
|
void arm_vacation(const optional<std::string> &code) {
|
||||||
|
this->arm_vacation(code.has_value() ? code.value().c_str() : nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
/** arm the alarm in custom bypass mode
|
/** arm the alarm in custom bypass mode
|
||||||
*
|
*
|
||||||
* @param code The code
|
* @param code The code
|
||||||
*/
|
*/
|
||||||
void arm_custom_bypass(optional<std::string> code = nullopt);
|
void arm_custom_bypass(const char *code = nullptr);
|
||||||
|
void arm_custom_bypass(const optional<std::string> &code) {
|
||||||
|
this->arm_custom_bypass(code.has_value() ? code.value().c_str() : nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
/** disarm the alarm
|
/** disarm the alarm
|
||||||
*
|
*
|
||||||
* @param code The code
|
* @param code The code
|
||||||
*/
|
*/
|
||||||
void disarm(optional<std::string> code = nullopt);
|
void disarm(const char *code = nullptr);
|
||||||
|
void disarm(const optional<std::string> &code) { this->disarm(code.has_value() ? code.value().c_str() : nullptr); }
|
||||||
|
|
||||||
/** Get the state
|
/** Get the state
|
||||||
*
|
*
|
||||||
@@ -118,6 +134,8 @@ class AlarmControlPanel : public EntityBase {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend AlarmControlPanelCall;
|
friend AlarmControlPanelCall;
|
||||||
|
// Helper to reduce code duplication for arm/disarm methods
|
||||||
|
void arm_with_code_(AlarmControlPanelCall &(AlarmControlPanelCall::*arm_method)(), const char *code);
|
||||||
// in order to store last panel state in flash
|
// in order to store last panel state in flash
|
||||||
ESPPreferenceObject pref_;
|
ESPPreferenceObject pref_;
|
||||||
// current state
|
// current state
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ static const char *const TAG = "alarm_control_panel";
|
|||||||
|
|
||||||
AlarmControlPanelCall::AlarmControlPanelCall(AlarmControlPanel *parent) : parent_(parent) {}
|
AlarmControlPanelCall::AlarmControlPanelCall(AlarmControlPanel *parent) : parent_(parent) {}
|
||||||
|
|
||||||
AlarmControlPanelCall &AlarmControlPanelCall::set_code(const std::string &code) {
|
AlarmControlPanelCall &AlarmControlPanelCall::set_code(const char *code) {
|
||||||
this->code_ = code;
|
if (code != nullptr) {
|
||||||
|
this->code_ = std::string(code);
|
||||||
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ class AlarmControlPanelCall {
|
|||||||
public:
|
public:
|
||||||
AlarmControlPanelCall(AlarmControlPanel *parent);
|
AlarmControlPanelCall(AlarmControlPanel *parent);
|
||||||
|
|
||||||
AlarmControlPanelCall &set_code(const std::string &code);
|
AlarmControlPanelCall &set_code(const char *code);
|
||||||
|
AlarmControlPanelCall &set_code(const std::string &code) { return this->set_code(code.c_str()); }
|
||||||
AlarmControlPanelCall &arm_away();
|
AlarmControlPanelCall &arm_away();
|
||||||
AlarmControlPanelCall &arm_home();
|
AlarmControlPanelCall &arm_home();
|
||||||
AlarmControlPanelCall &arm_night();
|
AlarmControlPanelCall &arm_night();
|
||||||
|
|||||||
@@ -66,15 +66,7 @@ template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(std::string, code)
|
TEMPLATABLE_VALUE(std::string, code)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(const Ts &...x) override { this->alarm_control_panel_->arm_away(this->code_.optional_value(x...)); }
|
||||||
auto call = this->alarm_control_panel_->make_call();
|
|
||||||
auto code = this->code_.optional_value(x...);
|
|
||||||
if (code.has_value()) {
|
|
||||||
call.set_code(code.value());
|
|
||||||
}
|
|
||||||
call.arm_away();
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AlarmControlPanel *alarm_control_panel_;
|
AlarmControlPanel *alarm_control_panel_;
|
||||||
@@ -86,15 +78,7 @@ template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(std::string, code)
|
TEMPLATABLE_VALUE(std::string, code)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(const Ts &...x) override { this->alarm_control_panel_->arm_home(this->code_.optional_value(x...)); }
|
||||||
auto call = this->alarm_control_panel_->make_call();
|
|
||||||
auto code = this->code_.optional_value(x...);
|
|
||||||
if (code.has_value()) {
|
|
||||||
call.set_code(code.value());
|
|
||||||
}
|
|
||||||
call.arm_home();
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AlarmControlPanel *alarm_control_panel_;
|
AlarmControlPanel *alarm_control_panel_;
|
||||||
@@ -106,15 +90,7 @@ template<typename... Ts> class ArmNightAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(std::string, code)
|
TEMPLATABLE_VALUE(std::string, code)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(const Ts &...x) override { this->alarm_control_panel_->arm_night(this->code_.optional_value(x...)); }
|
||||||
auto call = this->alarm_control_panel_->make_call();
|
|
||||||
auto code = this->code_.optional_value(x...);
|
|
||||||
if (code.has_value()) {
|
|
||||||
call.set_code(code.value());
|
|
||||||
}
|
|
||||||
call.arm_night();
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AlarmControlPanel *alarm_control_panel_;
|
AlarmControlPanel *alarm_control_panel_;
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
#include "am43_base.h"
|
#include "am43_base.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace am43 {
|
namespace am43 {
|
||||||
|
|
||||||
const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
|
const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
|
||||||
|
|
||||||
std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
|
|
||||||
char buf[64];
|
|
||||||
memset(buf, 0, 64);
|
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
sprintf(&buf[i * 2], "%02x", data[i]);
|
|
||||||
std::string ret = buf;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
Am43Packet *Am43Encoder::get_battery_level_request() {
|
Am43Packet *Am43Encoder::get_battery_level_request() {
|
||||||
uint8_t data = 0x1;
|
uint8_t data = 0x1;
|
||||||
return this->encode_(0xA2, &data, 1);
|
return this->encode_(0xA2, &data, 1);
|
||||||
@@ -73,7 +64,9 @@ Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length)
|
|||||||
memcpy(&this->packet_.data[7], data, length);
|
memcpy(&this->packet_.data[7], data, length);
|
||||||
this->packet_.length = length + 7;
|
this->packet_.length = length + 7;
|
||||||
this->checksum_();
|
this->checksum_();
|
||||||
ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str());
|
char hex_buf[format_hex_size(sizeof(this->packet_.data))];
|
||||||
|
ESP_LOGV("am43", "ENC(%d): 0x%s", this->packet_.length,
|
||||||
|
format_hex_to(hex_buf, this->packet_.data, this->packet_.length));
|
||||||
return &this->packet_;
|
return &this->packet_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +81,8 @@ void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
|
|||||||
this->has_set_state_response_ = false;
|
this->has_set_state_response_ = false;
|
||||||
this->has_position_ = false;
|
this->has_position_ = false;
|
||||||
this->has_pin_response_ = false;
|
this->has_pin_response_ = false;
|
||||||
ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str());
|
char hex_buf[format_hex_size(24)]; // Max expected packet size
|
||||||
|
ESP_LOGV("am43", "DEC(%d): 0x%s", length, format_hex_to(hex_buf, data, length));
|
||||||
|
|
||||||
if (length < 2 || data[0] != 0x9a)
|
if (length < 2 || data[0] != 0x9a)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -18,31 +18,31 @@ AnovaPacket *AnovaCodec::clean_packet_() {
|
|||||||
|
|
||||||
AnovaPacket *AnovaCodec::get_read_device_status_request() {
|
AnovaPacket *AnovaCodec::get_read_device_status_request() {
|
||||||
this->current_query_ = READ_DEVICE_STATUS;
|
this->current_query_ = READ_DEVICE_STATUS;
|
||||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_DEVICE_STATUS);
|
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DEVICE_STATUS);
|
||||||
return this->clean_packet_();
|
return this->clean_packet_();
|
||||||
}
|
}
|
||||||
|
|
||||||
AnovaPacket *AnovaCodec::get_read_target_temp_request() {
|
AnovaPacket *AnovaCodec::get_read_target_temp_request() {
|
||||||
this->current_query_ = READ_TARGET_TEMPERATURE;
|
this->current_query_ = READ_TARGET_TEMPERATURE;
|
||||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_TARGET_TEMP);
|
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_TARGET_TEMP);
|
||||||
return this->clean_packet_();
|
return this->clean_packet_();
|
||||||
}
|
}
|
||||||
|
|
||||||
AnovaPacket *AnovaCodec::get_read_current_temp_request() {
|
AnovaPacket *AnovaCodec::get_read_current_temp_request() {
|
||||||
this->current_query_ = READ_CURRENT_TEMPERATURE;
|
this->current_query_ = READ_CURRENT_TEMPERATURE;
|
||||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP);
|
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_CURRENT_TEMP);
|
||||||
return this->clean_packet_();
|
return this->clean_packet_();
|
||||||
}
|
}
|
||||||
|
|
||||||
AnovaPacket *AnovaCodec::get_read_unit_request() {
|
AnovaPacket *AnovaCodec::get_read_unit_request() {
|
||||||
this->current_query_ = READ_UNIT;
|
this->current_query_ = READ_UNIT;
|
||||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT);
|
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_UNIT);
|
||||||
return this->clean_packet_();
|
return this->clean_packet_();
|
||||||
}
|
}
|
||||||
|
|
||||||
AnovaPacket *AnovaCodec::get_read_data_request() {
|
AnovaPacket *AnovaCodec::get_read_data_request() {
|
||||||
this->current_query_ = READ_DATA;
|
this->current_query_ = READ_DATA;
|
||||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_DATA);
|
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DATA);
|
||||||
return this->clean_packet_();
|
return this->clean_packet_();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,25 +50,25 @@ AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) {
|
|||||||
this->current_query_ = SET_TARGET_TEMPERATURE;
|
this->current_query_ = SET_TARGET_TEMPERATURE;
|
||||||
if (this->fahrenheit_)
|
if (this->fahrenheit_)
|
||||||
temperature = ctof(temperature);
|
temperature = ctof(temperature);
|
||||||
sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature);
|
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TARGET_TEMP, temperature);
|
||||||
return this->clean_packet_();
|
return this->clean_packet_();
|
||||||
}
|
}
|
||||||
|
|
||||||
AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
|
AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
|
||||||
this->current_query_ = SET_UNIT;
|
this->current_query_ = SET_UNIT;
|
||||||
sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit);
|
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TEMP_UNIT, unit);
|
||||||
return this->clean_packet_();
|
return this->clean_packet_();
|
||||||
}
|
}
|
||||||
|
|
||||||
AnovaPacket *AnovaCodec::get_start_request() {
|
AnovaPacket *AnovaCodec::get_start_request() {
|
||||||
this->current_query_ = START;
|
this->current_query_ = START;
|
||||||
sprintf((char *) this->packet_.data, CMD_START);
|
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_START);
|
||||||
return this->clean_packet_();
|
return this->clean_packet_();
|
||||||
}
|
}
|
||||||
|
|
||||||
AnovaPacket *AnovaCodec::get_stop_request() {
|
AnovaPacket *AnovaCodec::get_stop_request() {
|
||||||
this->current_query_ = STOP;
|
this->current_query_ = STOP;
|
||||||
sprintf((char *) this->packet_.data, CMD_STOP);
|
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_STOP);
|
||||||
return this->clean_packet_();
|
return this->clean_packet_();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1715,7 +1715,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
|
|||||||
// HA state max length is 255 characters, but attributes can be much longer
|
// HA state max length is 255 characters, but attributes can be much longer
|
||||||
// Use stack buffer for common case (states), heap fallback for large attributes
|
// Use stack buffer for common case (states), heap fallback for large attributes
|
||||||
size_t state_len = msg.state.size();
|
size_t state_len = msg.state.size();
|
||||||
SmallBufferWithHeapFallback<256> state_buf_alloc(state_len + 1);
|
SmallBufferWithHeapFallback<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
|
||||||
char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
|
char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
|
||||||
if (state_len > 0) {
|
if (state_len > 0) {
|
||||||
memcpy(state_buf, msg.state.c_str(), state_len);
|
memcpy(state_buf, msg.state.c_str(), state_len);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
#include "api_connection.h" // For ClientInfo struct
|
#include "api_connection.h" // For ClientInfo struct
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/entity_base.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -256,28 +257,30 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
if (state_ == State::SERVER_HELLO) {
|
if (state_ == State::SERVER_HELLO) {
|
||||||
// send server hello
|
// send server hello
|
||||||
constexpr size_t mac_len = 13; // 12 hex chars + null terminator
|
|
||||||
const std::string &name = App.get_name();
|
const std::string &name = App.get_name();
|
||||||
char mac[mac_len];
|
char mac[MAC_ADDRESS_BUFFER_SIZE];
|
||||||
get_mac_address_into_buffer(mac);
|
get_mac_address_into_buffer(mac);
|
||||||
|
|
||||||
// Calculate positions and sizes
|
// Calculate positions and sizes
|
||||||
size_t name_len = name.size() + 1; // including null terminator
|
size_t name_len = name.size() + 1; // including null terminator
|
||||||
size_t name_offset = 1;
|
size_t name_offset = 1;
|
||||||
size_t mac_offset = name_offset + name_len;
|
size_t mac_offset = name_offset + name_len;
|
||||||
size_t total_size = 1 + name_len + mac_len;
|
size_t total_size = 1 + name_len + MAC_ADDRESS_BUFFER_SIZE;
|
||||||
|
|
||||||
auto msg = std::make_unique<uint8_t[]>(total_size);
|
// 1 (proto) + name (max ESPHOME_DEVICE_NAME_MAX_LEN) + 1 (name null)
|
||||||
|
// + mac (MAC_ADDRESS_BUFFER_SIZE - 1) + 1 (mac null)
|
||||||
|
constexpr size_t max_msg_size = 1 + ESPHOME_DEVICE_NAME_MAX_LEN + 1 + MAC_ADDRESS_BUFFER_SIZE;
|
||||||
|
uint8_t msg[max_msg_size];
|
||||||
|
|
||||||
// chosen proto
|
// chosen proto
|
||||||
msg[0] = 0x01;
|
msg[0] = 0x01;
|
||||||
|
|
||||||
// node name, terminated by null byte
|
// node name, terminated by null byte
|
||||||
std::memcpy(msg.get() + name_offset, name.c_str(), name_len);
|
std::memcpy(msg + name_offset, name.c_str(), name_len);
|
||||||
// node mac, terminated by null byte
|
// node mac, terminated by null byte
|
||||||
std::memcpy(msg.get() + mac_offset, mac, mac_len);
|
std::memcpy(msg + mac_offset, mac, MAC_ADDRESS_BUFFER_SIZE);
|
||||||
|
|
||||||
aerr = write_frame_(msg.get(), total_size);
|
aerr = write_frame_(msg, total_size);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
@@ -353,35 +356,32 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
|
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
|
||||||
|
// Max reject message: "Bad handshake packet len" (24) + 1 (failure byte) = 25 bytes
|
||||||
|
uint8_t data[32];
|
||||||
|
data[0] = 0x01; // failure
|
||||||
|
|
||||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||||
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
|
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
|
||||||
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
|
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
|
||||||
size_t data_size = reason_len + 1;
|
|
||||||
auto data = std::make_unique<uint8_t[]>(data_size);
|
|
||||||
data[0] = 0x01; // failure
|
|
||||||
|
|
||||||
// Copy error message from PROGMEM
|
|
||||||
if (reason_len > 0) {
|
if (reason_len > 0) {
|
||||||
memcpy_P(data.get() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
memcpy_P(data + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Normal memory access
|
// Normal memory access
|
||||||
const char *reason_str = LOG_STR_ARG(reason);
|
const char *reason_str = LOG_STR_ARG(reason);
|
||||||
size_t reason_len = strlen(reason_str);
|
size_t reason_len = strlen(reason_str);
|
||||||
size_t data_size = reason_len + 1;
|
|
||||||
auto data = std::make_unique<uint8_t[]>(data_size);
|
|
||||||
data[0] = 0x01; // failure
|
|
||||||
|
|
||||||
// Copy error message in bulk
|
|
||||||
if (reason_len > 0) {
|
if (reason_len > 0) {
|
||||||
std::memcpy(data.get() + 1, reason_str, reason_len);
|
// NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary protocol, not a C string
|
||||||
|
std::memcpy(data + 1, reason_str, reason_len);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
size_t data_size = reason_len + 1;
|
||||||
|
|
||||||
// temporarily remove failed state
|
// temporarily remove failed state
|
||||||
auto orig_state = state_;
|
auto orig_state = state_;
|
||||||
state_ = State::EXPLICIT_REJECT;
|
state_ = State::EXPLICIT_REJECT;
|
||||||
write_frame_(data.get(), data_size);
|
write_frame_(data, data_size);
|
||||||
state_ = orig_state;
|
state_ = orig_state;
|
||||||
}
|
}
|
||||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||||
|
|||||||
@@ -158,12 +158,14 @@ void ATM90E32Component::setup() {
|
|||||||
|
|
||||||
if (this->enable_offset_calibration_) {
|
if (this->enable_offset_calibration_) {
|
||||||
// Initialize flash storage for offset calibrations
|
// Initialize flash storage for offset calibrations
|
||||||
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_);
|
uint32_t o_hash = fnv1_hash("_offset_calibration_");
|
||||||
|
o_hash = fnv1_hash_extend(o_hash, this->cs_summary_);
|
||||||
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
||||||
this->restore_offset_calibrations_();
|
this->restore_offset_calibrations_();
|
||||||
|
|
||||||
// Initialize flash storage for power offset calibrations
|
// Initialize flash storage for power offset calibrations
|
||||||
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_);
|
uint32_t po_hash = fnv1_hash("_power_offset_calibration_");
|
||||||
|
po_hash = fnv1_hash_extend(po_hash, this->cs_summary_);
|
||||||
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
||||||
this->restore_power_offset_calibrations_();
|
this->restore_power_offset_calibrations_();
|
||||||
} else {
|
} else {
|
||||||
@@ -183,7 +185,8 @@ void ATM90E32Component::setup() {
|
|||||||
|
|
||||||
if (this->enable_gain_calibration_) {
|
if (this->enable_gain_calibration_) {
|
||||||
// Initialize flash storage for gain calibration
|
// Initialize flash storage for gain calibration
|
||||||
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_);
|
uint32_t g_hash = fnv1_hash("_gain_calibration_");
|
||||||
|
g_hash = fnv1_hash_extend(g_hash, this->cs_summary_);
|
||||||
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
||||||
this->restore_gain_calibrations_();
|
this->restore_gain_calibrations_();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components.esp32 import add_idf_component
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_NUM_CHANNELS, CONF_SAMPLE_RATE
|
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_NUM_CHANNELS, CONF_SAMPLE_RATE
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
@@ -165,4 +166,7 @@ def final_validate_audio_schema(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_library("esphome/esp-audio-libs", "2.0.1")
|
add_idf_component(
|
||||||
|
name="esphome/esp-audio-libs",
|
||||||
|
ref="2.0.3",
|
||||||
|
)
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ FileDecoderState AudioDecoder::decode_mp3_() {
|
|||||||
|
|
||||||
// Advance read pointer to match the offset for the syncword
|
// Advance read pointer to match the offset for the syncword
|
||||||
this->input_transfer_buffer_->decrease_buffer_length(offset);
|
this->input_transfer_buffer_->decrease_buffer_length(offset);
|
||||||
uint8_t *buffer_start = this->input_transfer_buffer_->get_buffer_start();
|
const uint8_t *buffer_start = this->input_transfer_buffer_->get_buffer_start();
|
||||||
|
|
||||||
buffer_length = (int) this->input_transfer_buffer_->available();
|
buffer_length = (int) this->input_transfer_buffer_->available();
|
||||||
int err = esp_audio_libs::helix_decoder::MP3Decode(this->mp3_decoder_, &buffer_start, &buffer_length,
|
int err = esp_audio_libs::helix_decoder::MP3Decode(this->mp3_decoder_, &buffer_start, &buffer_length,
|
||||||
|
|||||||
@@ -185,18 +185,16 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string url_string = str_lower_case(url);
|
if (str_endswith_ignore_case(url, ".wav")) {
|
||||||
|
|
||||||
if (str_endswith(url_string, ".wav")) {
|
|
||||||
file_type = AudioFileType::WAV;
|
file_type = AudioFileType::WAV;
|
||||||
}
|
}
|
||||||
#ifdef USE_AUDIO_MP3_SUPPORT
|
#ifdef USE_AUDIO_MP3_SUPPORT
|
||||||
else if (str_endswith(url_string, ".mp3")) {
|
else if (str_endswith_ignore_case(url, ".mp3")) {
|
||||||
file_type = AudioFileType::MP3;
|
file_type = AudioFileType::MP3;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_AUDIO_FLAC_SUPPORT
|
#ifdef USE_AUDIO_FLAC_SUPPORT
|
||||||
else if (str_endswith(url_string, ".flac")) {
|
else if (str_endswith_ignore_case(url, ".flac")) {
|
||||||
file_type = AudioFileType::FLAC;
|
file_type = AudioFileType::FLAC;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -135,8 +135,8 @@ void BluetoothConnection::loop() {
|
|||||||
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
|
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
|
||||||
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
|
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
|
||||||
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
|
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
|
||||||
if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
if (this->state() != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||||
this->send_service_ == DONE_SENDING_SERVICES)) {
|
this->send_service_ == DONE_SENDING_SERVICES)) {
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,13 @@ void CC1101Component::setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CC1101Component::call_listeners_(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi) {
|
||||||
|
for (auto &listener : this->listeners_) {
|
||||||
|
listener->on_packet(packet, freq_offset, rssi, lqi);
|
||||||
|
}
|
||||||
|
this->packet_trigger_->trigger(packet, freq_offset, rssi, lqi);
|
||||||
|
}
|
||||||
|
|
||||||
void CC1101Component::loop() {
|
void CC1101Component::loop() {
|
||||||
if (this->state_.PKT_FORMAT != static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO) || this->gdo0_pin_ == nullptr ||
|
if (this->state_.PKT_FORMAT != static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO) || this->gdo0_pin_ == nullptr ||
|
||||||
!this->gdo0_pin_->digital_read()) {
|
!this->gdo0_pin_->digital_read()) {
|
||||||
@@ -198,7 +205,7 @@ void CC1101Component::loop() {
|
|||||||
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
|
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
|
||||||
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
|
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
|
||||||
if (this->state_.CRC_EN == 0 || crc_ok) {
|
if (this->state_.CRC_EN == 0 || crc_ok) {
|
||||||
this->packet_trigger_->trigger(this->packet_, freq_offset, rssi, lqi);
|
this->call_listeners_(this->packet_, freq_offset, rssi, lqi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return to rx
|
// Return to rx
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ namespace esphome::cc1101 {
|
|||||||
|
|
||||||
enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW, PLL_LOCK };
|
enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW, PLL_LOCK };
|
||||||
|
|
||||||
|
class CC1101Listener {
|
||||||
|
public:
|
||||||
|
virtual void on_packet(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class CC1101Component : public Component,
|
class CC1101Component : public Component,
|
||||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
|
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
|
||||||
@@ -73,6 +78,7 @@ class CC1101Component : public Component,
|
|||||||
|
|
||||||
// Packet mode operations
|
// Packet mode operations
|
||||||
CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
|
CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
|
||||||
|
void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); }
|
||||||
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
|
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -89,9 +95,11 @@ class CC1101Component : public Component,
|
|||||||
InternalGPIOPin *gdo0_pin_{nullptr};
|
InternalGPIOPin *gdo0_pin_{nullptr};
|
||||||
|
|
||||||
// Packet handling
|
// Packet handling
|
||||||
|
void call_listeners_(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi);
|
||||||
Trigger<std::vector<uint8_t>, float, float, uint8_t> *packet_trigger_{
|
Trigger<std::vector<uint8_t>, float, float, uint8_t> *packet_trigger_{
|
||||||
new Trigger<std::vector<uint8_t>, float, float, uint8_t>()};
|
new Trigger<std::vector<uint8_t>, float, float, uint8_t>()};
|
||||||
std::vector<uint8_t> packet_;
|
std::vector<uint8_t> packet_;
|
||||||
|
std::vector<CC1101Listener *> listeners_;
|
||||||
|
|
||||||
// Low-level Helpers
|
// Low-level Helpers
|
||||||
uint8_t strobe_(Command cmd);
|
uint8_t strobe_(Command cmd);
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ void CCS811Component::setup() {
|
|||||||
bootloader_version, application_version);
|
bootloader_version, application_version);
|
||||||
if (this->version_ != nullptr) {
|
if (this->version_ != nullptr) {
|
||||||
char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room
|
char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room
|
||||||
sprintf(version, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15), (application_version >> 8 & 15),
|
buf_append_printf(version, sizeof(version), 0, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15),
|
||||||
(application_version >> 4 & 15), application_version);
|
(application_version >> 8 & 15), (application_version >> 4 & 15), application_version);
|
||||||
ESP_LOGD(TAG, "publishing version state: %s", version);
|
ESP_LOGD(TAG, "publishing version state: %s", version);
|
||||||
this->version_->publish_state(version);
|
this->version_->publish_state(version);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pi
|
|||||||
|
|
||||||
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); }
|
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); }
|
||||||
size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const {
|
size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const {
|
||||||
return snprintf(buffer, len, "EXIO%u via CH422G", this->pin_);
|
return buf_append_printf(buffer, len, 0, "EXIO%u via CH422G", this->pin_);
|
||||||
}
|
}
|
||||||
void CH422GGPIOPin::set_flags(gpio::Flags flags) {
|
void CH422GGPIOPin::set_flags(gpio::Flags flags) {
|
||||||
flags_ = flags;
|
flags_ = flags;
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ class CS5460AComponent : public Component,
|
|||||||
void restart() { restart_(); }
|
void restart() { restart_(); }
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void loop() override {}
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -207,20 +207,24 @@ void CSE7766Component::parse_data_() {
|
|||||||
|
|
||||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||||
{
|
{
|
||||||
std::string buf = "Parsed:";
|
// Buffer: 7 + 15 + 33 + 15 + 25 = 95 chars max + null, rounded to 128 for safety margin.
|
||||||
|
// Float sizes with %.4f can be up to 11 chars for large values (e.g., 999999.9999).
|
||||||
|
char buf[128];
|
||||||
|
size_t pos = buf_append_printf(buf, sizeof(buf), 0, "Parsed:");
|
||||||
if (have_voltage) {
|
if (have_voltage) {
|
||||||
buf += str_sprintf(" V=%fV", voltage);
|
pos = buf_append_printf(buf, sizeof(buf), pos, " V=%.4fV", voltage);
|
||||||
}
|
}
|
||||||
if (have_current) {
|
if (have_current) {
|
||||||
buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f);
|
pos = buf_append_printf(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f,
|
||||||
|
calculated_current * 1000.0f);
|
||||||
}
|
}
|
||||||
if (have_power) {
|
if (have_power) {
|
||||||
buf += str_sprintf(" P=%fW", power);
|
pos = buf_append_printf(buf, sizeof(buf), pos, " P=%.4fW", power);
|
||||||
}
|
}
|
||||||
if (energy != 0.0f) {
|
if (energy != 0.0f) {
|
||||||
buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses);
|
buf_append_printf(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses);
|
||||||
}
|
}
|
||||||
ESP_LOGVV(TAG, "%s", buf.c_str());
|
ESP_LOGVV(TAG, "%s", buf);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,8 +258,9 @@ bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char buf[DAIKIN_STATE_FRAME_SIZE * 3 + 1] = {0};
|
char buf[DAIKIN_STATE_FRAME_SIZE * 3 + 1] = {0};
|
||||||
|
size_t pos = 0;
|
||||||
for (size_t i = 0; i < DAIKIN_STATE_FRAME_SIZE; i++) {
|
for (size_t i = 0; i < DAIKIN_STATE_FRAME_SIZE; i++) {
|
||||||
sprintf(buf, "%s%02x ", buf, frame[i]);
|
pos = buf_append_printf(buf, sizeof(buf), pos, "%02x ", frame[i]);
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "FRAME %s", buf);
|
ESP_LOGD(TAG, "FRAME %s", buf);
|
||||||
|
|
||||||
@@ -349,8 +350,9 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
|||||||
if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
|
if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
|
||||||
valid_daikin_frame = true;
|
valid_daikin_frame = true;
|
||||||
size_t bytes_count = data.size() / 2 / 8;
|
size_t bytes_count = data.size() / 2 / 8;
|
||||||
std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]);
|
size_t buf_size = bytes_count * 3 + 1;
|
||||||
buf[0] = '\0';
|
std::unique_ptr<char[]> buf(new char[buf_size]()); // value-initialize (zero-fill)
|
||||||
|
size_t buf_pos = 0;
|
||||||
for (size_t i = 0; i < bytes_count; i++) {
|
for (size_t i = 0; i < bytes_count; i++) {
|
||||||
uint8_t byte = 0;
|
uint8_t byte = 0;
|
||||||
for (int8_t bit = 0; bit < 8; bit++) {
|
for (int8_t bit = 0; bit < 8; bit++) {
|
||||||
@@ -361,19 +363,19 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sprintf(buf.get(), "%s%02x ", buf.get(), byte);
|
buf_pos = buf_append_printf(buf.get(), buf_size, buf_pos, "%02x ", byte);
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "WHOLE FRAME %s size: %d", buf.get(), data.size());
|
ESP_LOGD(TAG, "WHOLE FRAME %s size: %d", buf.get(), data.size());
|
||||||
}
|
}
|
||||||
if (!valid_daikin_frame) {
|
if (!valid_daikin_frame) {
|
||||||
char sbuf[16 * 10 + 1];
|
char sbuf[16 * 10 + 1] = {0};
|
||||||
sbuf[0] = '\0';
|
size_t sbuf_pos = 0;
|
||||||
for (size_t j = 0; j < static_cast<size_t>(data.size()); j++) {
|
for (size_t j = 0; j < static_cast<size_t>(data.size()); j++) {
|
||||||
if ((j - 2) % 16 == 0) {
|
if ((j - 2) % 16 == 0) {
|
||||||
if (j > 0) {
|
if (j > 0) {
|
||||||
ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf);
|
ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf);
|
||||||
}
|
}
|
||||||
sbuf[0] = '\0';
|
sbuf_pos = 0;
|
||||||
}
|
}
|
||||||
char type_ch = ' ';
|
char type_ch = ' ';
|
||||||
// debug_tolerance = 25%
|
// debug_tolerance = 25%
|
||||||
@@ -401,9 +403,10 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
|||||||
type_ch = '0';
|
type_ch = '0';
|
||||||
|
|
||||||
if (abs(data[j]) > 100000) {
|
if (abs(data[j]) > 100000) {
|
||||||
sprintf(sbuf, "%s%-5d[%c] ", sbuf, data[j] > 0 ? 99999 : -99999, type_ch);
|
sbuf_pos = buf_append_printf(sbuf, sizeof(sbuf), sbuf_pos, "%-5d[%c] ", data[j] > 0 ? 99999 : -99999, type_ch);
|
||||||
} else {
|
} else {
|
||||||
sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch);
|
sbuf_pos =
|
||||||
|
buf_append_printf(sbuf, sizeof(sbuf), sbuf_pos, "%-5d[%c] ", (int) (round(data[j] / 10.) * 10), type_ch);
|
||||||
}
|
}
|
||||||
if (j + 1 == static_cast<size_t>(data.size())) {
|
if (j + 1 == static_cast<size_t>(data.size())) {
|
||||||
ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf);
|
ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf);
|
||||||
|
|||||||
@@ -106,9 +106,9 @@ DateCall &DateCall::set_date(uint16_t year, uint8_t month, uint8_t day) {
|
|||||||
|
|
||||||
DateCall &DateCall::set_date(ESPTime time) { return this->set_date(time.year, time.month, time.day_of_month); };
|
DateCall &DateCall::set_date(ESPTime time) { return this->set_date(time.year, time.month, time.day_of_month); };
|
||||||
|
|
||||||
DateCall &DateCall::set_date(const std::string &date) {
|
DateCall &DateCall::set_date(const char *date, size_t len) {
|
||||||
ESPTime val{};
|
ESPTime val{};
|
||||||
if (!ESPTime::strptime(date, val)) {
|
if (!ESPTime::strptime(date, len, val)) {
|
||||||
ESP_LOGE(TAG, "Could not convert the date string to an ESPTime object");
|
ESP_LOGE(TAG, "Could not convert the date string to an ESPTime object");
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ class DateCall {
|
|||||||
void perform();
|
void perform();
|
||||||
DateCall &set_date(uint16_t year, uint8_t month, uint8_t day);
|
DateCall &set_date(uint16_t year, uint8_t month, uint8_t day);
|
||||||
DateCall &set_date(ESPTime time);
|
DateCall &set_date(ESPTime time);
|
||||||
DateCall &set_date(const std::string &date);
|
DateCall &set_date(const char *date, size_t len);
|
||||||
|
DateCall &set_date(const char *date) { return this->set_date(date, strlen(date)); }
|
||||||
|
DateCall &set_date(const std::string &date) { return this->set_date(date.c_str(), date.size()); }
|
||||||
|
|
||||||
DateCall &set_year(uint16_t year) {
|
DateCall &set_year(uint16_t year) {
|
||||||
this->year_ = year;
|
this->year_ = year;
|
||||||
|
|||||||
@@ -163,9 +163,9 @@ DateTimeCall &DateTimeCall::set_datetime(ESPTime datetime) {
|
|||||||
datetime.second);
|
datetime.second);
|
||||||
};
|
};
|
||||||
|
|
||||||
DateTimeCall &DateTimeCall::set_datetime(const std::string &datetime) {
|
DateTimeCall &DateTimeCall::set_datetime(const char *datetime, size_t len) {
|
||||||
ESPTime val{};
|
ESPTime val{};
|
||||||
if (!ESPTime::strptime(datetime, val)) {
|
if (!ESPTime::strptime(datetime, len, val)) {
|
||||||
ESP_LOGE(TAG, "Could not convert the time string to an ESPTime object");
|
ESP_LOGE(TAG, "Could not convert the time string to an ESPTime object");
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,11 @@ class DateTimeCall {
|
|||||||
void perform();
|
void perform();
|
||||||
DateTimeCall &set_datetime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
|
DateTimeCall &set_datetime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
|
||||||
DateTimeCall &set_datetime(ESPTime datetime);
|
DateTimeCall &set_datetime(ESPTime datetime);
|
||||||
DateTimeCall &set_datetime(const std::string &datetime);
|
DateTimeCall &set_datetime(const char *datetime, size_t len);
|
||||||
|
DateTimeCall &set_datetime(const char *datetime) { return this->set_datetime(datetime, strlen(datetime)); }
|
||||||
|
DateTimeCall &set_datetime(const std::string &datetime) {
|
||||||
|
return this->set_datetime(datetime.c_str(), datetime.size());
|
||||||
|
}
|
||||||
DateTimeCall &set_datetime(time_t epoch_seconds);
|
DateTimeCall &set_datetime(time_t epoch_seconds);
|
||||||
|
|
||||||
DateTimeCall &set_year(uint16_t year) {
|
DateTimeCall &set_year(uint16_t year) {
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ TimeCall &TimeCall::set_time(uint8_t hour, uint8_t minute, uint8_t second) {
|
|||||||
|
|
||||||
TimeCall &TimeCall::set_time(ESPTime time) { return this->set_time(time.hour, time.minute, time.second); };
|
TimeCall &TimeCall::set_time(ESPTime time) { return this->set_time(time.hour, time.minute, time.second); };
|
||||||
|
|
||||||
TimeCall &TimeCall::set_time(const std::string &time) {
|
TimeCall &TimeCall::set_time(const char *time, size_t len) {
|
||||||
ESPTime val{};
|
ESPTime val{};
|
||||||
if (!ESPTime::strptime(time, val)) {
|
if (!ESPTime::strptime(time, len, val)) {
|
||||||
ESP_LOGE(TAG, "Could not convert the time string to an ESPTime object");
|
ESP_LOGE(TAG, "Could not convert the time string to an ESPTime object");
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,9 @@ class TimeCall {
|
|||||||
void perform();
|
void perform();
|
||||||
TimeCall &set_time(uint8_t hour, uint8_t minute, uint8_t second);
|
TimeCall &set_time(uint8_t hour, uint8_t minute, uint8_t second);
|
||||||
TimeCall &set_time(ESPTime time);
|
TimeCall &set_time(ESPTime time);
|
||||||
TimeCall &set_time(const std::string &time);
|
TimeCall &set_time(const char *time, size_t len);
|
||||||
|
TimeCall &set_time(const char *time) { return this->set_time(time, strlen(time)); }
|
||||||
|
TimeCall &set_time(const std::string &time) { return this->set_time(time.c_str(), time.size()); }
|
||||||
|
|
||||||
TimeCall &set_hour(uint8_t hour) {
|
TimeCall &set_hour(uint8_t hour) {
|
||||||
this->hour_ = hour;
|
this->hour_ = hour;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ void DebugComponent::dump_config() {
|
|||||||
|
|
||||||
char device_info_buffer[DEVICE_INFO_BUFFER_SIZE];
|
char device_info_buffer[DEVICE_INFO_BUFFER_SIZE];
|
||||||
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
|
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
|
||||||
size_t pos = buf_append(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION);
|
size_t pos = buf_append_printf(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION);
|
||||||
|
|
||||||
this->free_heap_ = get_free_heap_();
|
this->free_heap_ = get_free_heap_();
|
||||||
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
|
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
|
||||||
|
|||||||
@@ -5,12 +5,6 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/macros.h"
|
#include "esphome/core/macros.h"
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <cstdarg>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <algorithm>
|
|
||||||
#ifdef USE_ESP8266
|
|
||||||
#include <pgmspace.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
@@ -25,40 +19,7 @@ namespace debug {
|
|||||||
static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256;
|
static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256;
|
||||||
static constexpr size_t RESET_REASON_BUFFER_SIZE = 128;
|
static constexpr size_t RESET_REASON_BUFFER_SIZE = 128;
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
// buf_append_printf is now provided by esphome/core/helpers.h
|
||||||
// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM)
|
|
||||||
// Format strings must be wrapped with PSTR() macro
|
|
||||||
inline size_t buf_append_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) {
|
|
||||||
if (pos >= size) {
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
int written = vsnprintf_P(buf + pos, size - pos, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
if (written < 0) {
|
|
||||||
return pos; // encoding error
|
|
||||||
}
|
|
||||||
return std::min(pos + static_cast<size_t>(written), size);
|
|
||||||
}
|
|
||||||
#define buf_append(buf, size, pos, fmt, ...) buf_append_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
/// Safely append formatted string to buffer, returning new position (capped at size)
|
|
||||||
__attribute__((format(printf, 4, 5))) inline size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt,
|
|
||||||
...) {
|
|
||||||
if (pos >= size) {
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
int written = vsnprintf(buf + pos, size - pos, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
if (written < 0) {
|
|
||||||
return pos; // encoding error
|
|
||||||
}
|
|
||||||
return std::min(pos + static_cast<size_t>(written), size);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class DebugComponent : public PollingComponent {
|
class DebugComponent : public PollingComponent {
|
||||||
public:
|
public:
|
||||||
@@ -74,8 +35,11 @@ class DebugComponent : public PollingComponent {
|
|||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
|
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
|
||||||
void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; }
|
void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; }
|
||||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
#if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
|
||||||
void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; }
|
void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; }
|
||||||
|
#endif
|
||||||
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
void set_min_free_sensor(sensor::Sensor *min_free_sensor) { min_free_sensor_ = min_free_sensor; }
|
||||||
#endif
|
#endif
|
||||||
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
|
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
@@ -97,8 +61,11 @@ class DebugComponent : public PollingComponent {
|
|||||||
|
|
||||||
sensor::Sensor *free_sensor_{nullptr};
|
sensor::Sensor *free_sensor_{nullptr};
|
||||||
sensor::Sensor *block_sensor_{nullptr};
|
sensor::Sensor *block_sensor_{nullptr};
|
||||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
#if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
|
||||||
sensor::Sensor *fragmentation_sensor_{nullptr};
|
sensor::Sensor *fragmentation_sensor_{nullptr};
|
||||||
|
#endif
|
||||||
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
sensor::Sensor *min_free_sensor_{nullptr};
|
||||||
#endif
|
#endif
|
||||||
sensor::Sensor *loop_time_sensor_{nullptr};
|
sensor::Sensor *loop_time_sensor_{nullptr};
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|||||||
@@ -173,8 +173,8 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
|
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
|
||||||
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
|
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
|
||||||
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
|
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
|
||||||
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
||||||
flash_mode);
|
flash_mode);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
esp_chip_info_t info;
|
esp_chip_info_t info;
|
||||||
@@ -182,60 +182,71 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
const char *model = ESPHOME_VARIANT;
|
const char *model = ESPHOME_VARIANT;
|
||||||
|
|
||||||
// Build features string
|
// Build features string
|
||||||
pos = buf_append(buf, size, pos, "|Chip: %s Features:", model);
|
pos = buf_append_printf(buf, size, pos, "|Chip: %s Features:", model);
|
||||||
bool first_feature = true;
|
bool first_feature = true;
|
||||||
for (const auto &feature : CHIP_FEATURES) {
|
for (const auto &feature : CHIP_FEATURES) {
|
||||||
if (info.features & feature.bit) {
|
if (info.features & feature.bit) {
|
||||||
pos = buf_append(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name);
|
pos = buf_append_printf(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name);
|
||||||
first_feature = false;
|
first_feature = false;
|
||||||
info.features &= ~feature.bit;
|
info.features &= ~feature.bit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (info.features != 0) {
|
if (info.features != 0) {
|
||||||
pos = buf_append(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features);
|
pos = buf_append_printf(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features);
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision);
|
ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision);
|
||||||
pos = buf_append(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision);
|
pos = buf_append_printf(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision);
|
||||||
|
|
||||||
uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000;
|
uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000;
|
||||||
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
|
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
|
||||||
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
|
pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
|
||||||
|
|
||||||
// Framework detection
|
// Framework detection
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ARDUINO
|
||||||
ESP_LOGD(TAG, "Framework: Arduino");
|
ESP_LOGD(TAG, "Framework: Arduino");
|
||||||
pos = buf_append(buf, size, pos, "|Framework: Arduino");
|
pos = buf_append_printf(buf, size, pos, "|Framework: Arduino");
|
||||||
#elif defined(USE_ESP32)
|
#elif defined(USE_ESP32)
|
||||||
ESP_LOGD(TAG, "Framework: ESP-IDF");
|
ESP_LOGD(TAG, "Framework: ESP-IDF");
|
||||||
pos = buf_append(buf, size, pos, "|Framework: ESP-IDF");
|
pos = buf_append_printf(buf, size, pos, "|Framework: ESP-IDF");
|
||||||
#else
|
#else
|
||||||
ESP_LOGW(TAG, "Framework: UNKNOWN");
|
ESP_LOGW(TAG, "Framework: UNKNOWN");
|
||||||
pos = buf_append(buf, size, pos, "|Framework: UNKNOWN");
|
pos = buf_append_printf(buf, size, pos, "|Framework: UNKNOWN");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
|
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
|
||||||
pos = buf_append(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version());
|
pos = buf_append_printf(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version());
|
||||||
|
|
||||||
uint8_t mac[6];
|
uint8_t mac[6];
|
||||||
get_mac_address_raw(mac);
|
get_mac_address_raw(mac);
|
||||||
ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
pos = buf_append(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4],
|
pos = buf_append_printf(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3],
|
||||||
mac[5]);
|
mac[4], mac[5]);
|
||||||
|
|
||||||
char reason_buffer[RESET_REASON_BUFFER_SIZE];
|
char reason_buffer[RESET_REASON_BUFFER_SIZE];
|
||||||
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||||
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason);
|
pos = buf_append_printf(buf, size, pos, "|Reset: %s", reset_reason);
|
||||||
|
|
||||||
const char *wakeup_cause = get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
const char *wakeup_cause = get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||||
pos = buf_append(buf, size, pos, "|Wakeup: %s", wakeup_cause);
|
pos = buf_append_printf(buf, size, pos, "|Wakeup: %s", wakeup_cause);
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugComponent::update_platform_() {
|
void DebugComponent::update_platform_() {
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
|
uint32_t max_alloc = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
|
||||||
if (this->block_sensor_ != nullptr) {
|
if (this->block_sensor_ != nullptr) {
|
||||||
this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL));
|
this->block_sensor_->publish_state(max_alloc);
|
||||||
|
}
|
||||||
|
if (this->min_free_sensor_ != nullptr) {
|
||||||
|
this->min_free_sensor_->publish_state(heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
|
||||||
|
}
|
||||||
|
if (this->fragmentation_sensor_ != nullptr) {
|
||||||
|
uint32_t free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||||
|
if (free_heap > 0) {
|
||||||
|
float fragmentation = 100.0f - (100.0f * max_alloc / free_heap);
|
||||||
|
this->fragmentation_sensor_->publish_state(fragmentation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this->psram_sensor_ != nullptr) {
|
if (this->psram_sensor_ != nullptr) {
|
||||||
this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
||||||
|
|||||||
@@ -3,21 +3,80 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <Esp.h>
|
#include <Esp.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <user_interface.h>
|
||||||
|
|
||||||
|
// Global reset info struct populated by SDK at boot
|
||||||
|
extern struct rst_info resetInfo;
|
||||||
|
|
||||||
|
// Core version - either a string pointer or a version number to format as hex
|
||||||
|
extern uint32_t core_version;
|
||||||
|
extern const char *core_release;
|
||||||
|
}
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
|
|
||||||
static const char *const TAG = "debug";
|
static const char *const TAG = "debug";
|
||||||
|
|
||||||
|
// Get reset reason string from reason code (no heap allocation)
|
||||||
|
// Returns LogString* pointing to flash (PROGMEM) on ESP8266
|
||||||
|
static const LogString *get_reset_reason_str(uint32_t reason) {
|
||||||
|
switch (reason) {
|
||||||
|
case REASON_DEFAULT_RST:
|
||||||
|
return LOG_STR("Power On");
|
||||||
|
case REASON_WDT_RST:
|
||||||
|
return LOG_STR("Hardware Watchdog");
|
||||||
|
case REASON_EXCEPTION_RST:
|
||||||
|
return LOG_STR("Exception");
|
||||||
|
case REASON_SOFT_WDT_RST:
|
||||||
|
return LOG_STR("Software Watchdog");
|
||||||
|
case REASON_SOFT_RESTART:
|
||||||
|
return LOG_STR("Software/System restart");
|
||||||
|
case REASON_DEEP_SLEEP_AWAKE:
|
||||||
|
return LOG_STR("Deep-Sleep Wake");
|
||||||
|
case REASON_EXT_SYS_RST:
|
||||||
|
return LOG_STR("External System");
|
||||||
|
default:
|
||||||
|
return LOG_STR("Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size for core version hex buffer
|
||||||
|
static constexpr size_t CORE_VERSION_BUFFER_SIZE = 12;
|
||||||
|
|
||||||
|
// Get core version string (no heap allocation)
|
||||||
|
// Returns either core_release directly or formats core_version as hex into provided buffer
|
||||||
|
static const char *get_core_version_str(std::span<char, CORE_VERSION_BUFFER_SIZE> buffer) {
|
||||||
|
if (core_release != nullptr) {
|
||||||
|
return core_release;
|
||||||
|
}
|
||||||
|
snprintf_P(buffer.data(), CORE_VERSION_BUFFER_SIZE, PSTR("%08x"), core_version);
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size for reset info buffer
|
||||||
|
static constexpr size_t RESET_INFO_BUFFER_SIZE = 200;
|
||||||
|
|
||||||
|
// Get detailed reset info string (no heap allocation)
|
||||||
|
// For watchdog/exception resets, includes detailed exception info
|
||||||
|
static const char *get_reset_info_str(std::span<char, RESET_INFO_BUFFER_SIZE> buffer, uint32_t reason) {
|
||||||
|
if (reason >= REASON_WDT_RST && reason <= REASON_SOFT_WDT_RST) {
|
||||||
|
snprintf_P(buffer.data(), RESET_INFO_BUFFER_SIZE,
|
||||||
|
PSTR("Fatal exception:%d flag:%d (%s) epc1:0x%08x epc2:0x%08x epc3:0x%08x excvaddr:0x%08x depc:0x%08x"),
|
||||||
|
static_cast<int>(resetInfo.exccause), static_cast<int>(reason),
|
||||||
|
LOG_STR_ARG(get_reset_reason_str(reason)), resetInfo.epc1, resetInfo.epc2, resetInfo.epc3,
|
||||||
|
resetInfo.excvaddr, resetInfo.depc);
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
return LOG_STR_ARG(get_reset_reason_str(reason));
|
||||||
|
}
|
||||||
|
|
||||||
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||||
char *buf = buffer.data();
|
// Copy from flash to provided buffer
|
||||||
#if !defined(CLANG_TIDY)
|
strncpy_P(buffer.data(), (PGM_P) get_reset_reason_str(resetInfo.reason), RESET_REASON_BUFFER_SIZE - 1);
|
||||||
String reason = ESP.getResetReason(); // NOLINT
|
buffer[RESET_REASON_BUFFER_SIZE - 1] = '\0';
|
||||||
snprintf_P(buf, RESET_REASON_BUFFER_SIZE, PSTR("%s"), reason.c_str());
|
return buffer.data();
|
||||||
return buf;
|
|
||||||
#else
|
|
||||||
buf[0] = '\0';
|
|
||||||
return buf;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||||
@@ -33,37 +92,42 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
||||||
char *buf = buffer.data();
|
char *buf = buffer.data();
|
||||||
|
|
||||||
const char *flash_mode;
|
const LogString *flash_mode;
|
||||||
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
|
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
|
||||||
case FM_QIO:
|
case FM_QIO:
|
||||||
flash_mode = "QIO";
|
flash_mode = LOG_STR("QIO");
|
||||||
break;
|
break;
|
||||||
case FM_QOUT:
|
case FM_QOUT:
|
||||||
flash_mode = "QOUT";
|
flash_mode = LOG_STR("QOUT");
|
||||||
break;
|
break;
|
||||||
case FM_DIO:
|
case FM_DIO:
|
||||||
flash_mode = "DIO";
|
flash_mode = LOG_STR("DIO");
|
||||||
break;
|
break;
|
||||||
case FM_DOUT:
|
case FM_DOUT:
|
||||||
flash_mode = "DOUT";
|
flash_mode = LOG_STR("DOUT");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
flash_mode = "UNKNOWN";
|
flash_mode = LOG_STR("UNKNOWN");
|
||||||
}
|
}
|
||||||
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
|
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT(readability-static-accessed-through-instance)
|
||||||
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
|
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT(readability-static-accessed-through-instance)
|
||||||
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
|
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed,
|
||||||
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
LOG_STR_ARG(flash_mode));
|
||||||
flash_mode);
|
pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
||||||
|
LOG_STR_ARG(flash_mode));
|
||||||
|
|
||||||
#if !defined(CLANG_TIDY)
|
|
||||||
char reason_buffer[RESET_REASON_BUFFER_SIZE];
|
char reason_buffer[RESET_REASON_BUFFER_SIZE];
|
||||||
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
const char *reset_reason = get_reset_reason_(reason_buffer);
|
||||||
|
char core_version_buffer[CORE_VERSION_BUFFER_SIZE];
|
||||||
|
char reset_info_buffer[RESET_INFO_BUFFER_SIZE];
|
||||||
|
// NOLINTBEGIN(readability-static-accessed-through-instance)
|
||||||
uint32_t chip_id = ESP.getChipId();
|
uint32_t chip_id = ESP.getChipId();
|
||||||
uint8_t boot_version = ESP.getBootVersion();
|
uint8_t boot_version = ESP.getBootVersion();
|
||||||
uint8_t boot_mode = ESP.getBootMode();
|
uint8_t boot_mode = ESP.getBootMode();
|
||||||
uint8_t cpu_freq = ESP.getCpuFreqMHz();
|
uint8_t cpu_freq = ESP.getCpuFreqMHz();
|
||||||
uint32_t flash_chip_id = ESP.getFlashChipId();
|
uint32_t flash_chip_id = ESP.getFlashChipId();
|
||||||
|
const char *sdk_version = ESP.getSdkVersion();
|
||||||
|
// NOLINTEND(readability-static-accessed-through-instance)
|
||||||
|
|
||||||
ESP_LOGD(TAG,
|
ESP_LOGD(TAG,
|
||||||
"Chip ID: 0x%08" PRIX32 "\n"
|
"Chip ID: 0x%08" PRIX32 "\n"
|
||||||
@@ -74,19 +138,18 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
"Flash Chip ID=0x%08" PRIX32 "\n"
|
"Flash Chip ID=0x%08" PRIX32 "\n"
|
||||||
"Reset Reason: %s\n"
|
"Reset Reason: %s\n"
|
||||||
"Reset Info: %s",
|
"Reset Info: %s",
|
||||||
chip_id, ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), boot_version, boot_mode, cpu_freq, flash_chip_id,
|
chip_id, sdk_version, get_core_version_str(core_version_buffer), boot_version, boot_mode, cpu_freq,
|
||||||
reset_reason, ESP.getResetInfo().c_str());
|
flash_chip_id, reset_reason, get_reset_info_str(reset_info_buffer, resetInfo.reason));
|
||||||
|
|
||||||
pos = buf_append(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id);
|
pos = buf_append_printf(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id);
|
||||||
pos = buf_append(buf, size, pos, "|SDK: %s", ESP.getSdkVersion());
|
pos = buf_append_printf(buf, size, pos, "|SDK: %s", sdk_version);
|
||||||
pos = buf_append(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str());
|
pos = buf_append_printf(buf, size, pos, "|Core: %s", get_core_version_str(core_version_buffer));
|
||||||
pos = buf_append(buf, size, pos, "|Boot: %u", boot_version);
|
pos = buf_append_printf(buf, size, pos, "|Boot: %u", boot_version);
|
||||||
pos = buf_append(buf, size, pos, "|Mode: %u", boot_mode);
|
pos = buf_append_printf(buf, size, pos, "|Mode: %u", boot_mode);
|
||||||
pos = buf_append(buf, size, pos, "|CPU: %u", cpu_freq);
|
pos = buf_append_printf(buf, size, pos, "|CPU: %u", cpu_freq);
|
||||||
pos = buf_append(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id);
|
pos = buf_append_printf(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id);
|
||||||
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason);
|
pos = buf_append_printf(buf, size, pos, "|Reset: %s", reset_reason);
|
||||||
pos = buf_append(buf, size, pos, "|%s", ESP.getResetInfo().c_str());
|
pos = buf_append_printf(buf, size, pos, "|%s", get_reset_info_str(reset_info_buffer, resetInfo.reason));
|
||||||
#endif
|
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id,
|
lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id,
|
||||||
lt_get_board_code(), flash_kib, ram_kib, reset_reason);
|
lt_get_board_code(), flash_kib, ram_kib, reset_reason);
|
||||||
|
|
||||||
pos = buf_append(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10);
|
pos = buf_append_printf(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10);
|
||||||
pos = buf_append(buf, size, pos, "|Reset Reason: %s", reset_reason);
|
pos = buf_append_printf(buf, size, pos, "|Reset Reason: %s", reset_reason);
|
||||||
pos = buf_append(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name());
|
pos = buf_append_printf(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name());
|
||||||
pos = buf_append(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id);
|
pos = buf_append_printf(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id);
|
||||||
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib);
|
pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib);
|
||||||
pos = buf_append(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib);
|
pos = buf_append_printf(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib);
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,9 @@ void DebugComponent::update_platform_() {
|
|||||||
if (this->block_sensor_ != nullptr) {
|
if (this->block_sensor_ != nullptr) {
|
||||||
this->block_sensor_->publish_state(lt_heap_get_max_alloc());
|
this->block_sensor_->publish_state(lt_heap_get_max_alloc());
|
||||||
}
|
}
|
||||||
|
if (this->min_free_sensor_ != nullptr) {
|
||||||
|
this->min_free_sensor_->publish_state(lt_heap_get_min_free());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
|
|
||||||
uint32_t cpu_freq = rp2040.f_cpu();
|
uint32_t cpu_freq = rp2040.f_cpu();
|
||||||
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq);
|
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq);
|
||||||
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
|
pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ static size_t append_reset_reason(char *buf, size_t size, size_t pos, bool set,
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
if (pos > 0) {
|
if (pos > 0) {
|
||||||
pos = buf_append(buf, size, pos, ", ");
|
pos = buf_append_printf(buf, size, pos, ", ");
|
||||||
}
|
}
|
||||||
return buf_append(buf, size, pos, "%s", reason);
|
return buf_append_printf(buf, size, pos, "%s", reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint32_t read_mem_u32(uintptr_t addr) {
|
static inline uint32_t read_mem_u32(uintptr_t addr) {
|
||||||
@@ -132,6 +132,26 @@ void DebugComponent::log_partition_info_() {
|
|||||||
flash_area_foreach(fa_cb, nullptr);
|
flash_area_foreach(fa_cb, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *regout0_to_str(uint32_t value) {
|
||||||
|
switch (value) {
|
||||||
|
case (UICR_REGOUT0_VOUT_DEFAULT):
|
||||||
|
return "1.8V (default)";
|
||||||
|
case (UICR_REGOUT0_VOUT_1V8):
|
||||||
|
return "1.8V";
|
||||||
|
case (UICR_REGOUT0_VOUT_2V1):
|
||||||
|
return "2.1V";
|
||||||
|
case (UICR_REGOUT0_VOUT_2V4):
|
||||||
|
return "2.4V";
|
||||||
|
case (UICR_REGOUT0_VOUT_2V7):
|
||||||
|
return "2.7V";
|
||||||
|
case (UICR_REGOUT0_VOUT_3V0):
|
||||||
|
return "3.0V";
|
||||||
|
case (UICR_REGOUT0_VOUT_3V3):
|
||||||
|
return "3.3V";
|
||||||
|
}
|
||||||
|
return "???V";
|
||||||
|
}
|
||||||
|
|
||||||
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
|
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
|
||||||
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
||||||
char *buf = buffer.data();
|
char *buf = buffer.data();
|
||||||
@@ -140,48 +160,28 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
const char *supply_status =
|
const char *supply_status =
|
||||||
(nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage.";
|
(nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage.";
|
||||||
ESP_LOGD(TAG, "Main supply status: %s", supply_status);
|
ESP_LOGD(TAG, "Main supply status: %s", supply_status);
|
||||||
pos = buf_append(buf, size, pos, "|Main supply status: %s", supply_status);
|
pos = buf_append_printf(buf, size, pos, "|Main supply status: %s", supply_status);
|
||||||
|
|
||||||
// Regulator stage 0
|
// Regulator stage 0
|
||||||
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) {
|
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) {
|
||||||
const char *reg0_type = nrf_power_dcdcen_vddh_get(NRF_POWER) ? "DC/DC" : "LDO";
|
const char *reg0_type = nrf_power_dcdcen_vddh_get(NRF_POWER) ? "DC/DC" : "LDO";
|
||||||
const char *reg0_voltage;
|
const char *reg0_voltage = regout0_to_str((NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) >> UICR_REGOUT0_VOUT_Pos);
|
||||||
switch (NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) {
|
|
||||||
case (UICR_REGOUT0_VOUT_DEFAULT << UICR_REGOUT0_VOUT_Pos):
|
|
||||||
reg0_voltage = "1.8V (default)";
|
|
||||||
break;
|
|
||||||
case (UICR_REGOUT0_VOUT_1V8 << UICR_REGOUT0_VOUT_Pos):
|
|
||||||
reg0_voltage = "1.8V";
|
|
||||||
break;
|
|
||||||
case (UICR_REGOUT0_VOUT_2V1 << UICR_REGOUT0_VOUT_Pos):
|
|
||||||
reg0_voltage = "2.1V";
|
|
||||||
break;
|
|
||||||
case (UICR_REGOUT0_VOUT_2V4 << UICR_REGOUT0_VOUT_Pos):
|
|
||||||
reg0_voltage = "2.4V";
|
|
||||||
break;
|
|
||||||
case (UICR_REGOUT0_VOUT_2V7 << UICR_REGOUT0_VOUT_Pos):
|
|
||||||
reg0_voltage = "2.7V";
|
|
||||||
break;
|
|
||||||
case (UICR_REGOUT0_VOUT_3V0 << UICR_REGOUT0_VOUT_Pos):
|
|
||||||
reg0_voltage = "3.0V";
|
|
||||||
break;
|
|
||||||
case (UICR_REGOUT0_VOUT_3V3 << UICR_REGOUT0_VOUT_Pos):
|
|
||||||
reg0_voltage = "3.3V";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
reg0_voltage = "???V";
|
|
||||||
}
|
|
||||||
ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
||||||
pos = buf_append(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
||||||
|
#ifdef USE_NRF52_REG0_VOUT
|
||||||
|
if ((NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) >> UICR_REGOUT0_VOUT_Pos != USE_NRF52_REG0_VOUT) {
|
||||||
|
ESP_LOGE(TAG, "Regulator stage 0: expected %s", regout0_to_str(USE_NRF52_REG0_VOUT));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "Regulator stage 0: disabled");
|
ESP_LOGD(TAG, "Regulator stage 0: disabled");
|
||||||
pos = buf_append(buf, size, pos, "|Regulator stage 0: disabled");
|
pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regulator stage 1
|
// Regulator stage 1
|
||||||
const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO";
|
const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO";
|
||||||
ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type);
|
ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type);
|
||||||
pos = buf_append(buf, size, pos, "|Regulator stage 1: %s", reg1_type);
|
pos = buf_append_printf(buf, size, pos, "|Regulator stage 1: %s", reg1_type);
|
||||||
|
|
||||||
// USB power state
|
// USB power state
|
||||||
const char *usb_state;
|
const char *usb_state;
|
||||||
@@ -195,7 +195,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
usb_state = "disconnected";
|
usb_state = "disconnected";
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "USB power state: %s", usb_state);
|
ESP_LOGD(TAG, "USB power state: %s", usb_state);
|
||||||
pos = buf_append(buf, size, pos, "|USB power state: %s", usb_state);
|
pos = buf_append_printf(buf, size, pos, "|USB power state: %s", usb_state);
|
||||||
|
|
||||||
// Power-fail comparator
|
// Power-fail comparator
|
||||||
bool enabled;
|
bool enabled;
|
||||||
@@ -300,14 +300,14 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
||||||
pos = buf_append(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage);
|
ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage);
|
||||||
pos = buf_append(buf, size, pos, "|Power-fail comparator: %s", pof_voltage);
|
pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s", pof_voltage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "Power-fail comparator: disabled");
|
ESP_LOGD(TAG, "Power-fail comparator: disabled");
|
||||||
pos = buf_append(buf, size, pos, "|Power-fail comparator: disabled");
|
pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto package = [](uint32_t value) {
|
auto package = [](uint32_t value) {
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ from esphome.const import (
|
|||||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
ICON_COUNTER,
|
ICON_COUNTER,
|
||||||
ICON_TIMER,
|
ICON_TIMER,
|
||||||
|
PLATFORM_BK72XX,
|
||||||
|
PLATFORM_LN882X,
|
||||||
|
PLATFORM_RTL87XX,
|
||||||
UNIT_BYTES,
|
UNIT_BYTES,
|
||||||
UNIT_HERTZ,
|
UNIT_HERTZ,
|
||||||
UNIT_MILLISECOND,
|
UNIT_MILLISECOND,
|
||||||
@@ -25,6 +28,7 @@ from . import ( # noqa: F401 pylint: disable=unused-import
|
|||||||
|
|
||||||
DEPENDENCIES = ["debug"]
|
DEPENDENCIES = ["debug"]
|
||||||
|
|
||||||
|
CONF_MIN_FREE = "min_free"
|
||||||
CONF_PSRAM = "psram"
|
CONF_PSRAM = "psram"
|
||||||
|
|
||||||
CONFIG_SCHEMA = {
|
CONFIG_SCHEMA = {
|
||||||
@@ -42,8 +46,14 @@ CONFIG_SCHEMA = {
|
|||||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_FRAGMENTATION): cv.All(
|
cv.Optional(CONF_FRAGMENTATION): cv.All(
|
||||||
cv.only_on_esp8266,
|
cv.Any(
|
||||||
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
|
cv.All(
|
||||||
|
cv.only_on_esp8266,
|
||||||
|
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
|
||||||
|
),
|
||||||
|
cv.only_on_esp32,
|
||||||
|
msg="This feature is only available on ESP8266 (Arduino 2.5.2+) and ESP32",
|
||||||
|
),
|
||||||
sensor.sensor_schema(
|
sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_PERCENT,
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
icon=ICON_COUNTER,
|
icon=ICON_COUNTER,
|
||||||
@@ -51,6 +61,19 @@ CONFIG_SCHEMA = {
|
|||||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_MIN_FREE): cv.All(
|
||||||
|
cv.Any(
|
||||||
|
cv.only_on_esp32,
|
||||||
|
cv.only_on([PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX]),
|
||||||
|
msg="This feature is only available on ESP32 and LibreTiny (BK72xx, LN882x, RTL87xx)",
|
||||||
|
),
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_BYTES,
|
||||||
|
icon=ICON_COUNTER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
),
|
||||||
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(
|
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_MILLISECOND,
|
unit_of_measurement=UNIT_MILLISECOND,
|
||||||
icon=ICON_TIMER,
|
icon=ICON_TIMER,
|
||||||
@@ -93,6 +116,10 @@ async def to_code(config):
|
|||||||
sens = await sensor.new_sensor(fragmentation_conf)
|
sens = await sensor.new_sensor(fragmentation_conf)
|
||||||
cg.add(debug_component.set_fragmentation_sensor(sens))
|
cg.add(debug_component.set_fragmentation_sensor(sens))
|
||||||
|
|
||||||
|
if min_free_conf := config.get(CONF_MIN_FREE):
|
||||||
|
sens = await sensor.new_sensor(min_free_conf)
|
||||||
|
cg.add(debug_component.set_min_free_sensor(sens))
|
||||||
|
|
||||||
if loop_time_conf := config.get(CONF_LOOP_TIME):
|
if loop_time_conf := config.get(CONF_LOOP_TIME):
|
||||||
sens = await sensor.new_sensor(loop_time_conf)
|
sens = await sensor.new_sensor(loop_time_conf)
|
||||||
cg.add(debug_component.set_loop_time_sensor(sens))
|
cg.add(debug_component.set_loop_time_sensor(sens))
|
||||||
|
|||||||
@@ -127,7 +127,9 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
|||||||
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
|
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
|
||||||
this->max4_ = max4 = -1;
|
this->max4_ = max4 = -1;
|
||||||
|
|
||||||
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
|
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||||
|
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
|
||||||
|
this->cmd_ = buf;
|
||||||
} else if (min3 < 0 || max3 < 0) {
|
} else if (min3 < 0 || max3 < 0) {
|
||||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||||
@@ -135,7 +137,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
|||||||
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
||||||
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
|
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||||
|
|
||||||
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15);
|
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||||
|
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
|
||||||
|
max2 / 0.15);
|
||||||
|
this->cmd_ = buf;
|
||||||
} else if (min4 < 0 || max4 < 0) {
|
} else if (min4 < 0 || max4 < 0) {
|
||||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||||
@@ -145,9 +150,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
|||||||
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
|
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
|
||||||
this->min4_ = min4 = this->max4_ = max4 = -1;
|
this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||||
|
|
||||||
this->cmd_ = str_sprintf("detRangeCfg -1 "
|
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||||
"%.0f %.0f %.0f %.0f %.0f %.0f",
|
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
|
||||||
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15);
|
max2 / 0.15, min3 / 0.15, max3 / 0.15);
|
||||||
|
this->cmd_ = buf;
|
||||||
} else {
|
} else {
|
||||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||||
@@ -158,10 +164,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
|||||||
this->min4_ = min4 = round(min4 / 0.15) * 0.15;
|
this->min4_ = min4 = round(min4 / 0.15) * 0.15;
|
||||||
this->max4_ = max4 = round(max4 / 0.15) * 0.15;
|
this->max4_ = max4 = round(max4 / 0.15) * 0.15;
|
||||||
|
|
||||||
this->cmd_ = str_sprintf("detRangeCfg -1 "
|
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||||
"%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f",
|
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15,
|
||||||
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15,
|
min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15, max4 / 0.15);
|
||||||
max4 / 0.15);
|
this->cmd_ = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->min1_ = min1;
|
this->min1_ = min1;
|
||||||
@@ -203,7 +209,10 @@ SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_af
|
|||||||
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
|
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
|
||||||
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
|
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
|
||||||
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
|
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
|
||||||
this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
|
// max 32: "setLatency "(11) + float(8) + " "(1) + float(8) + null, rounded to 32
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
|
||||||
|
this->cmd_ = buf;
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t SetLatencyCommand::on_message(std::string &message) {
|
uint8_t SetLatencyCommand::on_message(std::string &message) {
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ class SetLatencyCommand : public Command {
|
|||||||
class SensorCfgStartCommand : public Command {
|
class SensorCfgStartCommand : public Command {
|
||||||
public:
|
public:
|
||||||
SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) {
|
SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) {
|
||||||
char tmp_cmd[20] = {0};
|
char tmp_cmd[20]; // "sensorCfgStart " (15) + "0/1" (1) + null = 17
|
||||||
sprintf(tmp_cmd, "sensorCfgStart %d", startup_mode);
|
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "sensorCfgStart %d", startup_mode);
|
||||||
cmd_ = std::string(tmp_cmd);
|
cmd_ = std::string(tmp_cmd);
|
||||||
}
|
}
|
||||||
uint8_t on_message(std::string &message) override;
|
uint8_t on_message(std::string &message) override;
|
||||||
@@ -142,8 +142,8 @@ class SensitivityCommand : public Command {
|
|||||||
SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) {
|
SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) {
|
||||||
if (sensitivity > 9)
|
if (sensitivity > 9)
|
||||||
sensitivity_ = sensitivity = 9;
|
sensitivity_ = sensitivity = 9;
|
||||||
char tmp_cmd[20] = {0};
|
char tmp_cmd[20]; // "setSensitivity " (15) + "0-9" (1) + null = 17
|
||||||
sprintf(tmp_cmd, "setSensitivity %d", sensitivity);
|
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "setSensitivity %d", sensitivity);
|
||||||
cmd_ = std::string(tmp_cmd);
|
cmd_ = std::string(tmp_cmd);
|
||||||
};
|
};
|
||||||
uint8_t on_message(std::string &message) override;
|
uint8_t on_message(std::string &message) override;
|
||||||
|
|||||||
@@ -25,29 +25,13 @@ dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
|
|||||||
Dsmr = dsmr_ns.class_("Dsmr", cg.Component, uart.UARTDevice)
|
Dsmr = dsmr_ns.class_("Dsmr", cg.Component, uart.UARTDevice)
|
||||||
|
|
||||||
|
|
||||||
def _validate_key(value):
|
|
||||||
value = cv.string_strict(value)
|
|
||||||
parts = [value[i : i + 2] for i in range(0, len(value), 2)]
|
|
||||||
if len(parts) != 16:
|
|
||||||
raise cv.Invalid("Decryption key must consist of 16 hexadecimal numbers")
|
|
||||||
parts_int = []
|
|
||||||
if any(len(part) != 2 for part in parts):
|
|
||||||
raise cv.Invalid("Decryption key must be format XX")
|
|
||||||
for part in parts:
|
|
||||||
try:
|
|
||||||
parts_int.append(int(part, 16))
|
|
||||||
except ValueError:
|
|
||||||
# pylint: disable=raise-missing-from
|
|
||||||
raise cv.Invalid("Decryption key must be hex values from 00 to FF")
|
|
||||||
|
|
||||||
return "".join(f"{part:02X}" for part in parts_int)
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(Dsmr),
|
cv.GenerateID(): cv.declare_id(Dsmr),
|
||||||
cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
|
cv.Optional(CONF_DECRYPTION_KEY): lambda value: cv.bind_key(
|
||||||
|
value, name="Decryption key"
|
||||||
|
),
|
||||||
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
|
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
|
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
|
||||||
cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_,
|
cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "dsmr.h"
|
#include "dsmr.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#include <AES.h>
|
#include <AES.h>
|
||||||
@@ -294,8 +295,8 @@ void Dsmr::dump_config() {
|
|||||||
DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, )
|
DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, )
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dsmr::set_decryption_key(const std::string &decryption_key) {
|
void Dsmr::set_decryption_key(const char *decryption_key) {
|
||||||
if (decryption_key.empty()) {
|
if (decryption_key == nullptr || decryption_key[0] == '\0') {
|
||||||
ESP_LOGI(TAG, "Disabling decryption");
|
ESP_LOGI(TAG, "Disabling decryption");
|
||||||
this->decryption_key_.clear();
|
this->decryption_key_.clear();
|
||||||
if (this->crypt_telegram_ != nullptr) {
|
if (this->crypt_telegram_ != nullptr) {
|
||||||
@@ -305,21 +306,15 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decryption_key.length() != 32) {
|
if (!parse_hex(decryption_key, this->decryption_key_, 16)) {
|
||||||
ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
|
ESP_LOGE(TAG, "Error, decryption key must be 32 hex characters");
|
||||||
|
this->decryption_key_.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->decryption_key_.clear();
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Decryption key is set");
|
ESP_LOGI(TAG, "Decryption key is set");
|
||||||
// Verbose level prints decryption key
|
// Verbose level prints decryption key
|
||||||
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());
|
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key);
|
||||||
|
|
||||||
char temp[3] = {0};
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
|
|
||||||
this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->crypt_telegram_ == nullptr) {
|
if (this->crypt_telegram_ == nullptr) {
|
||||||
this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
|
this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class Dsmr : public Component, public uart::UARTDevice {
|
|||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
void set_decryption_key(const std::string &decryption_key);
|
void set_decryption_key(const char *decryption_key);
|
||||||
void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
|
void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
|
||||||
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
|
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
|
||||||
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
|
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ async def to_code(config):
|
|||||||
# Rotation is handled by setting the transform
|
# Rotation is handled by setting the transform
|
||||||
display_config = {k: v for k, v in config.items() if k != CONF_ROTATION}
|
display_config = {k: v for k, v in config.items() if k != CONF_ROTATION}
|
||||||
await display.register_display(var, display_config)
|
await display.register_display(var, display_config)
|
||||||
await spi.register_spi_device(var, config)
|
await spi.register_spi_device(var, config, write_only=True)
|
||||||
|
|
||||||
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||||
cg.add(var.set_dc_pin(dc))
|
cg.add(var.set_dc_pin(dc))
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from esphome.const import (
|
|||||||
KEY_CORE,
|
KEY_CORE,
|
||||||
KEY_FRAMEWORK_VERSION,
|
KEY_FRAMEWORK_VERSION,
|
||||||
KEY_NAME,
|
KEY_NAME,
|
||||||
|
KEY_NATIVE_IDF,
|
||||||
KEY_TARGET_FRAMEWORK,
|
KEY_TARGET_FRAMEWORK,
|
||||||
KEY_TARGET_PLATFORM,
|
KEY_TARGET_PLATFORM,
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
@@ -53,6 +54,7 @@ from .const import ( # noqa
|
|||||||
KEY_COMPONENTS,
|
KEY_COMPONENTS,
|
||||||
KEY_ESP32,
|
KEY_ESP32,
|
||||||
KEY_EXTRA_BUILD_FILES,
|
KEY_EXTRA_BUILD_FILES,
|
||||||
|
KEY_FLASH_SIZE,
|
||||||
KEY_PATH,
|
KEY_PATH,
|
||||||
KEY_REF,
|
KEY_REF,
|
||||||
KEY_REPO,
|
KEY_REPO,
|
||||||
@@ -180,6 +182,12 @@ def set_core_data(config):
|
|||||||
path=[CONF_CPU_FREQUENCY],
|
path=[CONF_CPU_FREQUENCY],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if variant == VARIANT_ESP32P4 and cpu_frequency == "400MHZ":
|
||||||
|
_LOGGER.warning(
|
||||||
|
"400MHz on ESP32-P4 is experimental and may not boot. "
|
||||||
|
"Consider using 360MHz instead. See https://github.com/esphome/esphome/issues/13425"
|
||||||
|
)
|
||||||
|
|
||||||
CORE.data[KEY_ESP32] = {}
|
CORE.data[KEY_ESP32] = {}
|
||||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_ESP32
|
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_ESP32
|
||||||
conf = config[CONF_FRAMEWORK]
|
conf = config[CONF_FRAMEWORK]
|
||||||
@@ -199,6 +207,7 @@ def set_core_data(config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
|
CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
|
||||||
|
CORE.data[KEY_ESP32][KEY_FLASH_SIZE] = config[CONF_FLASH_SIZE]
|
||||||
CORE.data[KEY_ESP32][KEY_VARIANT] = variant
|
CORE.data[KEY_ESP32][KEY_VARIANT] = variant
|
||||||
CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {}
|
CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {}
|
||||||
|
|
||||||
@@ -339,7 +348,12 @@ def add_extra_build_file(filename: str, path: Path) -> bool:
|
|||||||
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||||
# format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
|
# format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
|
||||||
# a PIO pioarduino/framework-arduinoespressif32 value
|
# a PIO pioarduino/framework-arduinoespressif32 value
|
||||||
return f"pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/{str(ver)}/esp32-{str(ver)}.zip"
|
# 3.3.6+ changed filename from esp32-{ver}.zip to esp32-core-{ver}.tar.xz
|
||||||
|
if ver >= cv.Version(3, 3, 6):
|
||||||
|
filename = f"esp32-core-{ver}.tar.xz"
|
||||||
|
else:
|
||||||
|
filename = f"esp32-{ver}.zip"
|
||||||
|
return f"pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/{ver}/{filename}"
|
||||||
|
|
||||||
|
|
||||||
def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
|
def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
|
||||||
@@ -374,11 +388,12 @@ def _is_framework_url(source: str) -> bool:
|
|||||||
# The default/recommended arduino framework version
|
# The default/recommended arduino framework version
|
||||||
# - https://github.com/espressif/arduino-esp32/releases
|
# - https://github.com/espressif/arduino-esp32/releases
|
||||||
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
|
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
|
||||||
"recommended": cv.Version(3, 3, 5),
|
"recommended": cv.Version(3, 3, 6),
|
||||||
"latest": cv.Version(3, 3, 5),
|
"latest": cv.Version(3, 3, 6),
|
||||||
"dev": cv.Version(3, 3, 5),
|
"dev": cv.Version(3, 3, 6),
|
||||||
}
|
}
|
||||||
ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
||||||
|
cv.Version(3, 3, 6): cv.Version(55, 3, 36),
|
||||||
cv.Version(3, 3, 5): cv.Version(55, 3, 35),
|
cv.Version(3, 3, 5): cv.Version(55, 3, 35),
|
||||||
cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"),
|
cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"),
|
||||||
cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"),
|
cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"),
|
||||||
@@ -396,6 +411,7 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
|||||||
# These versions correspond to pioarduino/esp-idf releases
|
# These versions correspond to pioarduino/esp-idf releases
|
||||||
# See: https://github.com/pioarduino/esp-idf/releases
|
# See: https://github.com/pioarduino/esp-idf/releases
|
||||||
ARDUINO_IDF_VERSION_LOOKUP = {
|
ARDUINO_IDF_VERSION_LOOKUP = {
|
||||||
|
cv.Version(3, 3, 6): cv.Version(5, 5, 2),
|
||||||
cv.Version(3, 3, 5): cv.Version(5, 5, 2),
|
cv.Version(3, 3, 5): cv.Version(5, 5, 2),
|
||||||
cv.Version(3, 3, 4): cv.Version(5, 5, 1),
|
cv.Version(3, 3, 4): cv.Version(5, 5, 1),
|
||||||
cv.Version(3, 3, 3): cv.Version(5, 5, 1),
|
cv.Version(3, 3, 3): cv.Version(5, 5, 1),
|
||||||
@@ -418,7 +434,7 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
|||||||
"dev": cv.Version(5, 5, 2),
|
"dev": cv.Version(5, 5, 2),
|
||||||
}
|
}
|
||||||
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||||
cv.Version(5, 5, 2): cv.Version(55, 3, 35),
|
cv.Version(5, 5, 2): cv.Version(55, 3, 36),
|
||||||
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"),
|
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"),
|
||||||
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"),
|
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"),
|
||||||
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
|
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
|
||||||
@@ -435,9 +451,9 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
|||||||
# The platform-espressif32 version
|
# The platform-espressif32 version
|
||||||
# - https://github.com/pioarduino/platform-espressif32/releases
|
# - https://github.com/pioarduino/platform-espressif32/releases
|
||||||
PLATFORM_VERSION_LOOKUP = {
|
PLATFORM_VERSION_LOOKUP = {
|
||||||
"recommended": cv.Version(55, 3, 35),
|
"recommended": cv.Version(55, 3, 36),
|
||||||
"latest": cv.Version(55, 3, 35),
|
"latest": cv.Version(55, 3, 36),
|
||||||
"dev": cv.Version(55, 3, 35),
|
"dev": cv.Version(55, 3, 36),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -962,12 +978,54 @@ async def _add_yaml_idf_components(components: list[ConfigType]):
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||||
cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE])
|
conf = config[CONF_FRAMEWORK]
|
||||||
cg.add_platformio_option(
|
|
||||||
"board_upload.maximum_size",
|
# Check if using native ESP-IDF build (--native-idf)
|
||||||
int(config[CONF_FLASH_SIZE].removesuffix("MB")) * 1024 * 1024,
|
use_platformio = not CORE.data.get(KEY_NATIVE_IDF, False)
|
||||||
)
|
if use_platformio:
|
||||||
|
# Clear IDF environment variables to avoid conflicts with PlatformIO's ESP-IDF
|
||||||
|
# but keep them when using --native-idf for native ESP-IDF builds
|
||||||
|
for clean_var in ("IDF_PATH", "IDF_TOOLS_PATH"):
|
||||||
|
os.environ.pop(clean_var, None)
|
||||||
|
|
||||||
|
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||||
|
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||||
|
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||||
|
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||||
|
cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE])
|
||||||
|
cg.add_platformio_option(
|
||||||
|
"board_upload.maximum_size",
|
||||||
|
int(config[CONF_FLASH_SIZE].removesuffix("MB")) * 1024 * 1024,
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_SOURCE in conf:
|
||||||
|
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
|
||||||
|
|
||||||
|
add_extra_script(
|
||||||
|
"pre",
|
||||||
|
"pre_build.py",
|
||||||
|
Path(__file__).parent / "pre_build.py.script",
|
||||||
|
)
|
||||||
|
|
||||||
|
add_extra_script(
|
||||||
|
"post",
|
||||||
|
"post_build.py",
|
||||||
|
Path(__file__).parent / "post_build.py.script",
|
||||||
|
)
|
||||||
|
|
||||||
|
# In testing mode, add IRAM fix script to allow linking grouped component tests
|
||||||
|
# Similar to ESP8266's approach but for ESP-IDF
|
||||||
|
if CORE.testing_mode:
|
||||||
|
cg.add_build_flag("-DESPHOME_TESTING_MODE")
|
||||||
|
add_extra_script(
|
||||||
|
"pre",
|
||||||
|
"iram_fix.py",
|
||||||
|
Path(__file__).parent / "iram_fix.py.script",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cg.add_build_flag("-Wno-error=format")
|
||||||
|
|
||||||
cg.set_cpp_standard("gnu++20")
|
cg.set_cpp_standard("gnu++20")
|
||||||
cg.add_build_flag("-DUSE_ESP32")
|
cg.add_build_flag("-DUSE_ESP32")
|
||||||
cg.add_build_flag("-Wl,-z,noexecstack")
|
cg.add_build_flag("-Wl,-z,noexecstack")
|
||||||
@@ -977,79 +1035,49 @@ async def to_code(config):
|
|||||||
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[variant])
|
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[variant])
|
||||||
cg.add_define(ThreadModel.MULTI_ATOMICS)
|
cg.add_define(ThreadModel.MULTI_ATOMICS)
|
||||||
|
|
||||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
|
||||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
|
||||||
|
|
||||||
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
|
||||||
|
|
||||||
conf = config[CONF_FRAMEWORK]
|
|
||||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
|
||||||
if CONF_SOURCE in conf:
|
|
||||||
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
|
|
||||||
|
|
||||||
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
|
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
|
||||||
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
|
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
|
||||||
|
|
||||||
for clean_var in ("IDF_PATH", "IDF_TOOLS_PATH"):
|
|
||||||
os.environ.pop(clean_var, None)
|
|
||||||
|
|
||||||
# Set the location of the IDF component manager cache
|
# Set the location of the IDF component manager cache
|
||||||
os.environ["IDF_COMPONENT_CACHE_PATH"] = str(
|
os.environ["IDF_COMPONENT_CACHE_PATH"] = str(
|
||||||
CORE.relative_internal_path(".espressif")
|
CORE.relative_internal_path(".espressif")
|
||||||
)
|
)
|
||||||
|
|
||||||
add_extra_script(
|
|
||||||
"pre",
|
|
||||||
"pre_build.py",
|
|
||||||
Path(__file__).parent / "pre_build.py.script",
|
|
||||||
)
|
|
||||||
|
|
||||||
add_extra_script(
|
|
||||||
"post",
|
|
||||||
"post_build.py",
|
|
||||||
Path(__file__).parent / "post_build.py.script",
|
|
||||||
)
|
|
||||||
|
|
||||||
# In testing mode, add IRAM fix script to allow linking grouped component tests
|
|
||||||
# Similar to ESP8266's approach but for ESP-IDF
|
|
||||||
if CORE.testing_mode:
|
|
||||||
cg.add_build_flag("-DESPHOME_TESTING_MODE")
|
|
||||||
add_extra_script(
|
|
||||||
"pre",
|
|
||||||
"iram_fix.py",
|
|
||||||
Path(__file__).parent / "iram_fix.py.script",
|
|
||||||
)
|
|
||||||
|
|
||||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||||
cg.add_platformio_option("framework", "espidf")
|
|
||||||
cg.add_build_flag("-DUSE_ESP_IDF")
|
cg.add_build_flag("-DUSE_ESP_IDF")
|
||||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
||||||
|
if use_platformio:
|
||||||
|
cg.add_platformio_option("framework", "espidf")
|
||||||
else:
|
else:
|
||||||
cg.add_platformio_option("framework", "arduino, espidf")
|
|
||||||
cg.add_build_flag("-DUSE_ARDUINO")
|
cg.add_build_flag("-DUSE_ARDUINO")
|
||||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
|
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
|
||||||
|
if use_platformio:
|
||||||
|
cg.add_platformio_option("framework", "arduino, espidf")
|
||||||
|
|
||||||
|
# Add IDF framework source for Arduino builds to ensure it uses the same version as
|
||||||
|
# the ESP-IDF framework
|
||||||
|
if (idf_ver := ARDUINO_IDF_VERSION_LOOKUP.get(framework_ver)) is not None:
|
||||||
|
cg.add_platformio_option(
|
||||||
|
"platform_packages",
|
||||||
|
[_format_framework_espidf_version(idf_ver, None)],
|
||||||
|
)
|
||||||
|
|
||||||
|
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
|
||||||
|
if get_esp32_variant() == VARIANT_ESP32S2:
|
||||||
|
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1")
|
||||||
|
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=0")
|
||||||
|
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=0")
|
||||||
|
|
||||||
cg.add_define(
|
cg.add_define(
|
||||||
"USE_ARDUINO_VERSION_CODE",
|
"USE_ARDUINO_VERSION_CODE",
|
||||||
cg.RawExpression(
|
cg.RawExpression(
|
||||||
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
|
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
|
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
|
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
|
||||||
|
|
||||||
# Add IDF framework source for Arduino builds to ensure it uses the same version as
|
|
||||||
# the ESP-IDF framework
|
|
||||||
if (idf_ver := ARDUINO_IDF_VERSION_LOOKUP.get(framework_ver)) is not None:
|
|
||||||
cg.add_platformio_option(
|
|
||||||
"platform_packages", [_format_framework_espidf_version(idf_ver, None)]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
|
|
||||||
if get_esp32_variant() == VARIANT_ESP32S2:
|
|
||||||
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1")
|
|
||||||
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=0")
|
|
||||||
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=0")
|
|
||||||
|
|
||||||
cg.add_build_flag("-Wno-nonnull-compare")
|
cg.add_build_flag("-Wno-nonnull-compare")
|
||||||
|
|
||||||
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
|
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
|
||||||
@@ -1196,7 +1224,8 @@ async def to_code(config):
|
|||||||
"CONFIG_VFS_SUPPORT_DIR", not advanced[CONF_DISABLE_VFS_SUPPORT_DIR]
|
"CONFIG_VFS_SUPPORT_DIR", not advanced[CONF_DISABLE_VFS_SUPPORT_DIR]
|
||||||
)
|
)
|
||||||
|
|
||||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
if use_platformio:
|
||||||
|
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||||
if CONF_PARTITIONS in config:
|
if CONF_PARTITIONS in config:
|
||||||
add_extra_build_file(
|
add_extra_build_file(
|
||||||
"partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS])
|
"partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS])
|
||||||
@@ -1361,19 +1390,16 @@ def copy_files():
|
|||||||
_write_idf_component_yml()
|
_write_idf_component_yml()
|
||||||
|
|
||||||
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
|
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
|
||||||
|
flash_size = CORE.data[KEY_ESP32][KEY_FLASH_SIZE]
|
||||||
if CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
write_file_if_changed(
|
write_file_if_changed(
|
||||||
CORE.relative_build_path("partitions.csv"),
|
CORE.relative_build_path("partitions.csv"),
|
||||||
get_arduino_partition_csv(
|
get_arduino_partition_csv(flash_size),
|
||||||
CORE.platformio_options.get("board_upload.flash_size")
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
write_file_if_changed(
|
write_file_if_changed(
|
||||||
CORE.relative_build_path("partitions.csv"),
|
CORE.relative_build_path("partitions.csv"),
|
||||||
get_idf_partition_csv(
|
get_idf_partition_csv(flash_size),
|
||||||
CORE.platformio_options.get("board_upload.flash_size")
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
# IDF build scripts look for version string to put in the build.
|
# IDF build scripts look for version string to put in the build.
|
||||||
# However, if the build path does not have an initialized git repo,
|
# However, if the build path does not have an initialized git repo,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import esphome.codegen as cg
|
|||||||
|
|
||||||
KEY_ESP32 = "esp32"
|
KEY_ESP32 = "esp32"
|
||||||
KEY_BOARD = "board"
|
KEY_BOARD = "board"
|
||||||
|
KEY_FLASH_SIZE = "flash_size"
|
||||||
KEY_VARIANT = "variant"
|
KEY_VARIANT = "variant"
|
||||||
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
||||||
KEY_COMPONENTS = "components"
|
KEY_COMPONENTS = "components"
|
||||||
|
|||||||
@@ -181,7 +181,8 @@ class ESP32Preferences : public ESPPreferences {
|
|||||||
if (actual_len != to_save.len) {
|
if (actual_len != to_save.len) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
auto stored_data = std::make_unique<uint8_t[]>(actual_len);
|
// Most preferences are small, use stack buffer with heap fallback for large ones
|
||||||
|
SmallBufferWithHeapFallback<256> stored_data(actual_len);
|
||||||
err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
|
err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
|
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ class ESPBTUUID {
|
|||||||
|
|
||||||
esp_bt_uuid_t get_uuid() const;
|
esp_bt_uuid_t get_uuid() const;
|
||||||
|
|
||||||
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
||||||
std::string to_string() const;
|
std::string to_string() const;
|
||||||
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
|
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ void BLEClientBase::loop() {
|
|||||||
this->set_state(espbt::ClientState::INIT);
|
this->set_state(espbt::ClientState::INIT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->state_ == espbt::ClientState::INIT) {
|
if (this->state() == espbt::ClientState::INIT) {
|
||||||
auto ret = esp_ble_gattc_app_register(this->app_id);
|
auto ret = esp_ble_gattc_app_register(this->app_id);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
|
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
|
||||||
@@ -60,7 +60,7 @@ void BLEClientBase::loop() {
|
|||||||
}
|
}
|
||||||
// If idle, we can disable the loop as connect()
|
// If idle, we can disable the loop as connect()
|
||||||
// will enable it again when a connection is needed.
|
// will enable it again when a connection is needed.
|
||||||
else if (this->state_ == espbt::ClientState::IDLE) {
|
else if (this->state() == espbt::ClientState::IDLE) {
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
|||||||
return false;
|
return false;
|
||||||
if (this->address_ == 0 || device.address_uint64() != this->address_)
|
if (this->address_ == 0 || device.address_uint64() != this->address_)
|
||||||
return false;
|
return false;
|
||||||
if (this->state_ != espbt::ClientState::IDLE)
|
if (this->state() != espbt::ClientState::IDLE)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this->log_event_("Found device");
|
this->log_event_("Found device");
|
||||||
@@ -102,10 +102,10 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
|||||||
|
|
||||||
void BLEClientBase::connect() {
|
void BLEClientBase::connect() {
|
||||||
// Prevent duplicate connection attempts
|
// Prevent duplicate connection attempts
|
||||||
if (this->state_ == espbt::ClientState::CONNECTING || this->state_ == espbt::ClientState::CONNECTED ||
|
if (this->state() == espbt::ClientState::CONNECTING || this->state() == espbt::ClientState::CONNECTED ||
|
||||||
this->state_ == espbt::ClientState::ESTABLISHED) {
|
this->state() == espbt::ClientState::ESTABLISHED) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Connection already in progress, state=%s", this->connection_index_, this->address_str_,
|
ESP_LOGW(TAG, "[%d] [%s] Connection already in progress, state=%s", this->connection_index_, this->address_str_,
|
||||||
espbt::client_state_to_string(this->state_));
|
espbt::client_state_to_string(this->state()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_, this->remote_addr_type_);
|
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_, this->remote_addr_type_);
|
||||||
@@ -133,12 +133,12 @@ void BLEClientBase::connect() {
|
|||||||
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
|
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
|
||||||
|
|
||||||
void BLEClientBase::disconnect() {
|
void BLEClientBase::disconnect() {
|
||||||
if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) {
|
if (this->state() == espbt::ClientState::IDLE || this->state() == espbt::ClientState::DISCONNECTING) {
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already %s", this->connection_index_, this->address_str_,
|
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already %s", this->connection_index_, this->address_str_,
|
||||||
espbt::client_state_to_string(this->state_));
|
espbt::client_state_to_string(this->state()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
|
if (this->state() == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
|
||||||
ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_,
|
ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_,
|
||||||
this->address_str_);
|
this->address_str_);
|
||||||
this->want_disconnect_ = true;
|
this->want_disconnect_ = true;
|
||||||
@@ -150,7 +150,7 @@ void BLEClientBase::disconnect() {
|
|||||||
void BLEClientBase::unconditional_disconnect() {
|
void BLEClientBase::unconditional_disconnect() {
|
||||||
// Disconnect without checking the state.
|
// Disconnect without checking the state.
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_, this->conn_id_);
|
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_, this->conn_id_);
|
||||||
if (this->state_ == espbt::ClientState::DISCONNECTING) {
|
if (this->state() == espbt::ClientState::DISCONNECTING) {
|
||||||
this->log_error_("Already disconnecting");
|
this->log_error_("Already disconnecting");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ void BLEClientBase::unconditional_disconnect() {
|
|||||||
this->log_gattc_warning_("esp_ble_gattc_close", err);
|
this->log_gattc_warning_("esp_ble_gattc_close", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->state_ == espbt::ClientState::DISCOVERED) {
|
if (this->state() == espbt::ClientState::DISCOVERED) {
|
||||||
this->set_address(0);
|
this->set_address(0);
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
} else {
|
} else {
|
||||||
@@ -295,18 +295,18 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
// ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an
|
// ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an
|
||||||
// error, if the error occurred at the BTA/GATT layer. This can result in the event
|
// error, if the error occurred at the BTA/GATT layer. This can result in the event
|
||||||
// arriving after we've already transitioned to IDLE state.
|
// arriving after we've already transitioned to IDLE state.
|
||||||
if (this->state_ == espbt::ClientState::IDLE) {
|
if (this->state() == espbt::ClientState::IDLE) {
|
||||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_,
|
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_,
|
||||||
this->address_str_, param->open.status);
|
this->address_str_, param->open.status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->state_ != espbt::ClientState::CONNECTING) {
|
if (this->state() != espbt::ClientState::CONNECTING) {
|
||||||
// This should not happen but lets log it in case it does
|
// This should not happen but lets log it in case it does
|
||||||
// because it means we have a bad assumption about how the
|
// because it means we have a bad assumption about how the
|
||||||
// ESP BT stack works.
|
// ESP BT stack works.
|
||||||
ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_,
|
ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_,
|
||||||
this->address_str_, espbt::client_state_to_string(this->state_), param->open.status);
|
this->address_str_, espbt::client_state_to_string(this->state()), param->open.status);
|
||||||
}
|
}
|
||||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||||
this->log_gattc_warning_("Connection open", param->open.status);
|
this->log_gattc_warning_("Connection open", param->open.status);
|
||||||
@@ -327,7 +327,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||||
// Cached connections already connected with medium parameters, no update needed
|
// Cached connections already connected with medium parameters, no update needed
|
||||||
// only set our state, subclients might have more stuff to do yet.
|
// only set our state, subclients might have more stuff to do yet.
|
||||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
this->set_state_internal_(espbt::ClientState::ESTABLISHED);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// For V3_WITHOUT_CACHE, we already set fast params before connecting
|
// For V3_WITHOUT_CACHE, we already set fast params before connecting
|
||||||
@@ -356,7 +356,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
return false;
|
return false;
|
||||||
// Check if we were disconnected while waiting for service discovery
|
// Check if we were disconnected while waiting for service discovery
|
||||||
if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
|
if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
|
||||||
this->state_ == espbt::ClientState::CONNECTED) {
|
this->state() == espbt::ClientState::CONNECTED) {
|
||||||
this->log_warning_("Remote closed during discovery");
|
this->log_warning_("Remote closed during discovery");
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, this->address_str_,
|
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, this->address_str_,
|
||||||
@@ -433,7 +433,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_);
|
ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_);
|
||||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
this->set_state_internal_(espbt::ClientState::ESTABLISHED);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_READ_DESCR_EVT: {
|
case ESP_GATTC_READ_DESCR_EVT: {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
void unconditional_disconnect();
|
void unconditional_disconnect();
|
||||||
void release_services();
|
void release_services();
|
||||||
|
|
||||||
bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; }
|
bool connected() { return this->state() == espbt::ClientState::ESTABLISHED; }
|
||||||
|
|
||||||
void set_auto_connect(bool auto_connect) { this->auto_connect_ = auto_connect; }
|
void set_auto_connect(bool auto_connect) { this->auto_connect_ = auto_connect; }
|
||||||
|
|
||||||
|
|||||||
@@ -105,15 +105,13 @@ void ESP32BLETracker::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for scan timeout - moved here from scheduler to avoid false reboots
|
// Check for scan timeout - moved here from scheduler to avoid false reboots
|
||||||
// when the loop is blocked
|
// when the loop is blocked. This must run every iteration for safety.
|
||||||
if (this->scanner_state_ == ScannerState::RUNNING) {
|
if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||||
switch (this->scan_timeout_state_) {
|
switch (this->scan_timeout_state_) {
|
||||||
case ScanTimeoutState::MONITORING: {
|
case ScanTimeoutState::MONITORING: {
|
||||||
uint32_t now = App.get_loop_component_start_time();
|
|
||||||
uint32_t timeout_ms = this->scan_duration_ * 2000;
|
|
||||||
// Robust time comparison that handles rollover correctly
|
// Robust time comparison that handles rollover correctly
|
||||||
// This works because unsigned arithmetic wraps around predictably
|
// This works because unsigned arithmetic wraps around predictably
|
||||||
if ((now - this->scan_start_time_) > timeout_ms) {
|
if ((App.get_loop_component_start_time() - this->scan_start_time_) > this->scan_timeout_ms_) {
|
||||||
// First time we've seen the timeout exceeded - wait one more loop iteration
|
// First time we've seen the timeout exceeded - wait one more loop iteration
|
||||||
// This ensures all components have had a chance to process pending events
|
// This ensures all components have had a chance to process pending events
|
||||||
// This is because esp32_ble may not have run yet and called
|
// This is because esp32_ble may not have run yet and called
|
||||||
@@ -128,13 +126,31 @@ void ESP32BLETracker::loop() {
|
|||||||
ESP_LOGE(TAG, "Scan never terminated, rebooting");
|
ESP_LOGE(TAG, "Scan never terminated, rebooting");
|
||||||
App.reboot();
|
App.reboot();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ScanTimeoutState::INACTIVE:
|
case ScanTimeoutState::INACTIVE:
|
||||||
// This case should be unreachable - scanner and timeout states are always synchronized
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast path: skip expensive client state counting and processing
|
||||||
|
// if no state has changed since last loop iteration.
|
||||||
|
//
|
||||||
|
// How state changes ensure we reach the code below:
|
||||||
|
// - handle_scanner_failure_(): scanner_state_ becomes FAILED via set_scanner_state_(), or
|
||||||
|
// scan_set_param_failed_ requires scanner_state_==RUNNING which can only be reached via
|
||||||
|
// set_scanner_state_(RUNNING) in gap_scan_start_complete_() (scan params are set during
|
||||||
|
// STARTING, not RUNNING, so version is always incremented before this condition is true)
|
||||||
|
// - start_scan_(): scanner_state_ becomes IDLE via set_scanner_state_() in cleanup_scan_state_()
|
||||||
|
// - try_promote_discovered_clients_(): client enters DISCOVERED via set_state(), or
|
||||||
|
// connecting client finishes (state change), or scanner reaches RUNNING/IDLE
|
||||||
|
//
|
||||||
|
// All conditions that affect the logic below are tied to state changes that increment
|
||||||
|
// state_version_, so the fast path is safe.
|
||||||
|
if (this->state_version_ == this->last_processed_version_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->last_processed_version_ = this->state_version_;
|
||||||
|
|
||||||
|
// State changed - do full processing
|
||||||
ClientStateCounts counts = this->count_client_states_();
|
ClientStateCounts counts = this->count_client_states_();
|
||||||
if (counts != this->client_state_counts_) {
|
if (counts != this->client_state_counts_) {
|
||||||
this->client_state_counts_ = counts;
|
this->client_state_counts_ = counts;
|
||||||
@@ -142,6 +158,7 @@ void ESP32BLETracker::loop() {
|
|||||||
this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
|
this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scanner failure: reached when set_scanner_state_(FAILED) or scan_set_param_failed_ set
|
||||||
if (this->scanner_state_ == ScannerState::FAILED ||
|
if (this->scanner_state_ == ScannerState::FAILED ||
|
||||||
(this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
|
(this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
|
||||||
this->handle_scanner_failure_();
|
this->handle_scanner_failure_();
|
||||||
@@ -160,6 +177,8 @@ void ESP32BLETracker::loop() {
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Start scan: reached when scanner_state_ becomes IDLE (via set_scanner_state_()) and
|
||||||
|
// all clients are idle (their state changes increment version when they finish)
|
||||||
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && !counts.discovered) {
|
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && !counts.discovered) {
|
||||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
||||||
this->update_coex_preference_(false);
|
this->update_coex_preference_(false);
|
||||||
@@ -168,8 +187,9 @@ void ESP32BLETracker::loop() {
|
|||||||
this->start_scan_(false); // first = false
|
this->start_scan_(false); // first = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If there is a discovered client and no connecting
|
// Promote discovered clients: reached when a client's state becomes DISCOVERED (via set_state()),
|
||||||
// clients, then promote the discovered client to ready to connect.
|
// or when a blocking condition clears (connecting client finishes, scanner reaches RUNNING/IDLE).
|
||||||
|
// All these trigger state_version_ increment, so we'll process and check promotion eligibility.
|
||||||
// We check both RUNNING and IDLE states because:
|
// We check both RUNNING and IDLE states because:
|
||||||
// - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
|
// - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
|
||||||
// - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
|
// - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
|
||||||
@@ -236,6 +256,7 @@ void ESP32BLETracker::start_scan_(bool first) {
|
|||||||
// Start timeout monitoring in loop() instead of using scheduler
|
// Start timeout monitoring in loop() instead of using scheduler
|
||||||
// This prevents false reboots when the loop is blocked
|
// This prevents false reboots when the loop is blocked
|
||||||
this->scan_start_time_ = App.get_loop_component_start_time();
|
this->scan_start_time_ = App.get_loop_component_start_time();
|
||||||
|
this->scan_timeout_ms_ = this->scan_duration_ * 2000;
|
||||||
this->scan_timeout_state_ = ScanTimeoutState::MONITORING;
|
this->scan_timeout_state_ = ScanTimeoutState::MONITORING;
|
||||||
|
|
||||||
esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
|
esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
|
||||||
@@ -253,6 +274,10 @@ void ESP32BLETracker::start_scan_(bool first) {
|
|||||||
void ESP32BLETracker::register_client(ESPBTClient *client) {
|
void ESP32BLETracker::register_client(ESPBTClient *client) {
|
||||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||||
client->app_id = ++this->app_id_;
|
client->app_id = ++this->app_id_;
|
||||||
|
// Give client a pointer to our state_version_ so it can notify us of state changes.
|
||||||
|
// This enables loop() fast-path optimization - we skip expensive work when no state changed.
|
||||||
|
// Safe because ESP32BLETracker (singleton) outlives all registered clients.
|
||||||
|
client->set_tracker_state_version(&this->state_version_);
|
||||||
this->clients_.push_back(client);
|
this->clients_.push_back(client);
|
||||||
this->recalculate_advertisement_parser_types();
|
this->recalculate_advertisement_parser_types();
|
||||||
#endif
|
#endif
|
||||||
@@ -382,6 +407,7 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
|||||||
|
|
||||||
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
|
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
|
||||||
this->scanner_state_ = state;
|
this->scanner_state_ = state;
|
||||||
|
this->state_version_++;
|
||||||
for (auto *listener : this->scanner_state_listeners_) {
|
for (auto *listener : this->scanner_state_listeners_) {
|
||||||
listener->on_scanner_state(state);
|
listener->on_scanner_state(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,6 +216,19 @@ enum class ConnectionType : uint8_t {
|
|||||||
V3_WITHOUT_CACHE
|
V3_WITHOUT_CACHE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Base class for BLE GATT clients that connect to remote devices.
|
||||||
|
///
|
||||||
|
/// State Change Tracking Design:
|
||||||
|
/// -----------------------------
|
||||||
|
/// ESP32BLETracker::loop() needs to know when client states change to avoid
|
||||||
|
/// expensive polling. Rather than checking all clients every iteration (~7000/min),
|
||||||
|
/// we use a version counter owned by ESP32BLETracker that clients increment on
|
||||||
|
/// state changes. The tracker compares versions to skip work when nothing changed.
|
||||||
|
///
|
||||||
|
/// Ownership: ESP32BLETracker owns state_version_. Clients hold a non-owning
|
||||||
|
/// pointer (tracker_state_version_) set during register_client(). Clients
|
||||||
|
/// increment the counter through this pointer when their state changes.
|
||||||
|
/// The pointer may be null if the client is not registered with a tracker.
|
||||||
class ESPBTClient : public ESPBTDeviceListener {
|
class ESPBTClient : public ESPBTDeviceListener {
|
||||||
public:
|
public:
|
||||||
virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
@@ -225,26 +238,49 @@ class ESPBTClient : public ESPBTDeviceListener {
|
|||||||
virtual void disconnect() = 0;
|
virtual void disconnect() = 0;
|
||||||
bool disconnect_pending() const { return this->want_disconnect_; }
|
bool disconnect_pending() const { return this->want_disconnect_; }
|
||||||
void cancel_pending_disconnect() { this->want_disconnect_ = false; }
|
void cancel_pending_disconnect() { this->want_disconnect_ = false; }
|
||||||
|
|
||||||
|
/// Set the client state with IDLE handling (clears want_disconnect_).
|
||||||
|
/// Notifies the tracker of state change for loop optimization.
|
||||||
virtual void set_state(ClientState st) {
|
virtual void set_state(ClientState st) {
|
||||||
this->state_ = st;
|
this->set_state_internal_(st);
|
||||||
if (st == ClientState::IDLE) {
|
if (st == ClientState::IDLE) {
|
||||||
this->want_disconnect_ = false;
|
this->want_disconnect_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientState state() const { return state_; }
|
ClientState state() const { return this->state_; }
|
||||||
|
|
||||||
|
/// Called by ESP32BLETracker::register_client() to enable state change notifications.
|
||||||
|
/// The pointer must remain valid for the lifetime of the client (guaranteed since
|
||||||
|
/// ESP32BLETracker is a singleton that outlives all clients).
|
||||||
|
void set_tracker_state_version(uint8_t *version) { this->tracker_state_version_ = version; }
|
||||||
|
|
||||||
// Memory optimized layout
|
// Memory optimized layout
|
||||||
uint8_t app_id; // App IDs are small integers assigned sequentially
|
uint8_t app_id; // App IDs are small integers assigned sequentially
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Group 1: 1-byte types
|
/// Set state without IDLE handling - use for direct state transitions.
|
||||||
ClientState state_{ClientState::INIT};
|
/// Increments the tracker's state version counter to signal that loop()
|
||||||
|
/// should do full processing on the next iteration.
|
||||||
|
void set_state_internal_(ClientState st) {
|
||||||
|
this->state_ = st;
|
||||||
|
// Notify tracker that state changed (tracker_state_version_ is owned by ESP32BLETracker)
|
||||||
|
if (this->tracker_state_version_ != nullptr) {
|
||||||
|
(*this->tracker_state_version_)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// want_disconnect_ is set to true when a disconnect is requested
|
// want_disconnect_ is set to true when a disconnect is requested
|
||||||
// while the client is connecting. This is used to disconnect the
|
// while the client is connecting. This is used to disconnect the
|
||||||
// client as soon as we get the connection id (conn_id_) from the
|
// client as soon as we get the connection id (conn_id_) from the
|
||||||
// ESP_GATTC_OPEN_EVT event.
|
// ESP_GATTC_OPEN_EVT event.
|
||||||
bool want_disconnect_{false};
|
bool want_disconnect_{false};
|
||||||
// 2 bytes used, 2 bytes padding
|
|
||||||
|
private:
|
||||||
|
ClientState state_{ClientState::INIT};
|
||||||
|
/// Non-owning pointer to ESP32BLETracker::state_version_. When this client's
|
||||||
|
/// state changes, we increment the tracker's counter to signal that loop()
|
||||||
|
/// should perform full processing. Null if client not registered with tracker.
|
||||||
|
uint8_t *tracker_state_version_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ESP32BLETracker : public Component,
|
class ESP32BLETracker : public Component,
|
||||||
@@ -380,6 +416,16 @@ class ESP32BLETracker : public Component,
|
|||||||
// Group 4: 1-byte types (enums, uint8_t, bool)
|
// Group 4: 1-byte types (enums, uint8_t, bool)
|
||||||
uint8_t app_id_{0};
|
uint8_t app_id_{0};
|
||||||
uint8_t scan_start_fail_count_{0};
|
uint8_t scan_start_fail_count_{0};
|
||||||
|
/// Version counter for loop() fast-path optimization. Incremented when:
|
||||||
|
/// - Scanner state changes (via set_scanner_state_())
|
||||||
|
/// - Any registered client's state changes (clients hold pointer to this counter)
|
||||||
|
/// Owned by this class; clients receive non-owning pointer via register_client().
|
||||||
|
/// When loop() sees state_version_ == last_processed_version_, it skips expensive
|
||||||
|
/// client state counting and takes the fast path (just timeout check + return).
|
||||||
|
uint8_t state_version_{0};
|
||||||
|
/// Last state_version_ value when loop() did full processing. Compared against
|
||||||
|
/// state_version_ to detect if any state changed since last iteration.
|
||||||
|
uint8_t last_processed_version_{0};
|
||||||
ScannerState scanner_state_{ScannerState::IDLE};
|
ScannerState scanner_state_{ScannerState::IDLE};
|
||||||
bool scan_continuous_;
|
bool scan_continuous_;
|
||||||
bool scan_active_;
|
bool scan_active_;
|
||||||
@@ -396,6 +442,8 @@ class ESP32BLETracker : public Component,
|
|||||||
EXCEEDED_WAIT, // Timeout exceeded, waiting one loop before reboot
|
EXCEEDED_WAIT, // Timeout exceeded, waiting one loop before reboot
|
||||||
};
|
};
|
||||||
uint32_t scan_start_time_{0};
|
uint32_t scan_start_time_{0};
|
||||||
|
/// Precomputed timeout value: scan_duration_ * 2000
|
||||||
|
uint32_t scan_timeout_ms_{0};
|
||||||
ScanTimeoutState scan_timeout_state_{ScanTimeoutState::INACTIVE};
|
ScanTimeoutState scan_timeout_state_{ScanTimeoutState::INACTIVE};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <esp_ota_ops.h>
|
#include <esp_ota_ops.h>
|
||||||
|
|
||||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||||
|
#include "esphome/components/http_request/http_request.h"
|
||||||
#include "esphome/components/json/json_util.h"
|
#include "esphome/components/json/json_util.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -69,7 +70,10 @@ void Esp32HostedUpdate::setup() {
|
|||||||
// Get coprocessor version
|
// Get coprocessor version
|
||||||
esp_hosted_coprocessor_fwver_t ver_info;
|
esp_hosted_coprocessor_fwver_t ver_info;
|
||||||
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
|
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
|
||||||
this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
|
// 16 bytes: "255.255.255" (11 chars) + null + safety margin
|
||||||
|
char buf[16];
|
||||||
|
snprintf(buf, sizeof(buf), "%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
|
||||||
|
this->update_info_.current_version = buf;
|
||||||
} else {
|
} else {
|
||||||
this->update_info_.current_version = "unknown";
|
this->update_info_.current_version = "unknown";
|
||||||
}
|
}
|
||||||
@@ -181,15 +185,23 @@ bool Esp32HostedUpdate::fetch_manifest_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read manifest JSON into string (manifest is small, ~1KB max)
|
// Read manifest JSON into string (manifest is small, ~1KB max)
|
||||||
|
// NOTE: HttpContainer::read() has non-BSD socket semantics - see http_request.h
|
||||||
|
// Use http_read_loop_result() helper instead of checking return values directly
|
||||||
std::string json_str;
|
std::string json_str;
|
||||||
json_str.reserve(container->content_length);
|
json_str.reserve(container->content_length);
|
||||||
uint8_t buf[256];
|
uint8_t buf[256];
|
||||||
|
uint32_t last_data_time = millis();
|
||||||
|
const uint32_t read_timeout = this->http_request_parent_->get_timeout();
|
||||||
while (container->get_bytes_read() < container->content_length) {
|
while (container->get_bytes_read() < container->content_length) {
|
||||||
int read = container->read(buf, sizeof(buf));
|
int read_or_error = container->read(buf, sizeof(buf));
|
||||||
if (read > 0) {
|
App.feed_wdt();
|
||||||
json_str.append(reinterpret_cast<char *>(buf), read);
|
|
||||||
}
|
|
||||||
yield();
|
yield();
|
||||||
|
auto result = http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout);
|
||||||
|
if (result == http_request::HttpReadLoopResult::RETRY)
|
||||||
|
continue;
|
||||||
|
if (result != http_request::HttpReadLoopResult::DATA)
|
||||||
|
break; // ERROR or TIMEOUT
|
||||||
|
json_str.append(reinterpret_cast<char *>(buf), read_or_error);
|
||||||
}
|
}
|
||||||
container->end();
|
container->end();
|
||||||
|
|
||||||
@@ -294,32 +306,38 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stream firmware to coprocessor while computing SHA256
|
// Stream firmware to coprocessor while computing SHA256
|
||||||
|
// NOTE: HttpContainer::read() has non-BSD socket semantics - see http_request.h
|
||||||
|
// Use http_read_loop_result() helper instead of checking return values directly
|
||||||
sha256::SHA256 hasher;
|
sha256::SHA256 hasher;
|
||||||
hasher.init();
|
hasher.init();
|
||||||
|
|
||||||
uint8_t buffer[CHUNK_SIZE];
|
uint8_t buffer[CHUNK_SIZE];
|
||||||
|
uint32_t last_data_time = millis();
|
||||||
|
const uint32_t read_timeout = this->http_request_parent_->get_timeout();
|
||||||
while (container->get_bytes_read() < total_size) {
|
while (container->get_bytes_read() < total_size) {
|
||||||
int read = container->read(buffer, sizeof(buffer));
|
int read_or_error = container->read(buffer, sizeof(buffer));
|
||||||
|
|
||||||
// Feed watchdog and give other tasks a chance to run
|
// Feed watchdog and give other tasks a chance to run
|
||||||
App.feed_wdt();
|
App.feed_wdt();
|
||||||
yield();
|
yield();
|
||||||
|
|
||||||
// Exit loop if no data available (stream closed or end of data)
|
auto result = http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout);
|
||||||
if (read <= 0) {
|
if (result == http_request::HttpReadLoopResult::RETRY)
|
||||||
if (read < 0) {
|
continue;
|
||||||
ESP_LOGE(TAG, "Stream closed with error");
|
if (result != http_request::HttpReadLoopResult::DATA) {
|
||||||
esp_hosted_slave_ota_end(); // NOLINT
|
if (result == http_request::HttpReadLoopResult::TIMEOUT) {
|
||||||
container->end();
|
ESP_LOGE(TAG, "Timeout reading firmware data");
|
||||||
this->status_set_error(LOG_STR("Download failed"));
|
} else {
|
||||||
return false;
|
ESP_LOGE(TAG, "Error reading firmware data: %d", read_or_error);
|
||||||
}
|
}
|
||||||
// read == 0: no more data available, exit loop
|
esp_hosted_slave_ota_end(); // NOLINT
|
||||||
break;
|
container->end();
|
||||||
|
this->status_set_error(LOG_STR("Download failed"));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasher.add(buffer, read);
|
hasher.add(buffer, read_or_error);
|
||||||
err = esp_hosted_slave_ota_write(buffer, read); // NOLINT
|
err = esp_hosted_slave_ota_write(buffer, read_or_error); // NOLINT
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
|
||||||
esp_hosted_slave_ota_end(); // NOLINT
|
esp_hosted_slave_ota_end(); // NOLINT
|
||||||
|
|||||||
@@ -6,7 +6,11 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Esp.h>
|
#include <core_esp8266_features.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <user_interface.h>
|
||||||
|
}
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
@@ -16,23 +20,19 @@ void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
|
|||||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||||
void arch_restart() {
|
void arch_restart() {
|
||||||
ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
|
system_restart();
|
||||||
// restart() doesn't always end execution
|
// restart() doesn't always end execution
|
||||||
while (true) { // NOLINT(clang-diagnostic-unreachable-code)
|
while (true) { // NOLINT(clang-diagnostic-unreachable-code)
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void arch_init() {}
|
void arch_init() {}
|
||||||
void IRAM_ATTR HOT arch_feed_wdt() {
|
void IRAM_ATTR HOT arch_feed_wdt() { system_soft_wdt_feed(); }
|
||||||
ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t progmem_read_byte(const uint8_t *addr) {
|
uint8_t progmem_read_byte(const uint8_t *addr) {
|
||||||
return pgm_read_byte(addr); // NOLINT
|
return pgm_read_byte(addr); // NOLINT
|
||||||
}
|
}
|
||||||
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() {
|
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return esp_get_cycle_count(); }
|
||||||
return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance)
|
|
||||||
}
|
|
||||||
uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
|
uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
|
||||||
|
|
||||||
void force_link_symbols() {
|
void force_link_symbols() {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const {
|
size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const {
|
||||||
return snprintf(buffer, len, "GPIO%u", this->pin_);
|
return buf_append_printf(buffer, len, 0, "GPIO%u", this->pin_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ESP8266GPIOPin::digital_read() {
|
bool ESP8266GPIOPin::digital_read() {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ extern "C" {
|
|||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace esphome::esp8266 {
|
namespace esphome::esp8266 {
|
||||||
|
|
||||||
@@ -143,16 +142,8 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
|
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
|
||||||
uint32_t stack_buffer[PREF_BUFFER_WORDS];
|
SmallBufferWithHeapFallback<PREF_BUFFER_WORDS, uint32_t> buffer_alloc(buffer_size);
|
||||||
std::unique_ptr<uint32_t[]> heap_buffer;
|
uint32_t *buffer = buffer_alloc.get();
|
||||||
uint32_t *buffer;
|
|
||||||
|
|
||||||
if (buffer_size <= PREF_BUFFER_WORDS) {
|
|
||||||
buffer = stack_buffer;
|
|
||||||
} else {
|
|
||||||
heap_buffer = make_unique<uint32_t[]>(buffer_size);
|
|
||||||
buffer = heap_buffer.get();
|
|
||||||
}
|
|
||||||
memset(buffer, 0, buffer_size * sizeof(uint32_t));
|
memset(buffer, 0, buffer_size * sizeof(uint32_t));
|
||||||
|
|
||||||
memcpy(buffer, data, len);
|
memcpy(buffer, data, len);
|
||||||
@@ -167,16 +158,8 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
|
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
|
||||||
uint32_t stack_buffer[PREF_BUFFER_WORDS];
|
SmallBufferWithHeapFallback<PREF_BUFFER_WORDS, uint32_t> buffer_alloc(buffer_size);
|
||||||
std::unique_ptr<uint32_t[]> heap_buffer;
|
uint32_t *buffer = buffer_alloc.get();
|
||||||
uint32_t *buffer;
|
|
||||||
|
|
||||||
if (buffer_size <= PREF_BUFFER_WORDS) {
|
|
||||||
buffer = stack_buffer;
|
|
||||||
} else {
|
|
||||||
heap_buffer = make_unique<uint32_t[]>(buffer_size);
|
|
||||||
buffer = heap_buffer.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size)
|
bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size)
|
||||||
: load_from_rtc(this->offset, buffer, buffer_size);
|
: load_from_rtc(this->offset, buffer, buffer_size);
|
||||||
|
|||||||
@@ -90,9 +90,7 @@ async def setup_event_core_(var, config, *, event_types: list[str]):
|
|||||||
|
|
||||||
for conf in config.get(CONF_ON_EVENT, []):
|
for conf in config.get(CONF_ON_EVENT, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(
|
await automation.build_automation(trigger, [(cg.StringRef, "event_type")], conf)
|
||||||
trigger, [(cg.std_string, "event_type")], conf
|
|
||||||
)
|
|
||||||
|
|
||||||
cg.add(var.set_event_types(event_types))
|
cg.add(var.set_event_types(event_types))
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ template<typename... Ts> class TriggerEventAction : public Action<Ts...>, public
|
|||||||
void play(const Ts &...x) override { this->parent_->trigger(this->event_type_.value(x...)); }
|
void play(const Ts &...x) override { this->parent_->trigger(this->event_type_.value(x...)); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class EventTrigger : public Trigger<std::string> {
|
class EventTrigger : public Trigger<StringRef> {
|
||||||
public:
|
public:
|
||||||
EventTrigger(Event *event) {
|
EventTrigger(Event *event) {
|
||||||
event->add_on_event_callback([this](const std::string &event_type) { this->trigger(event_type); });
|
event->add_on_event_callback([this](StringRef event_type) { this->trigger(event_type); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ void Event::trigger(const std::string &event_type) {
|
|||||||
}
|
}
|
||||||
this->last_event_type_ = found;
|
this->last_event_type_ = found;
|
||||||
ESP_LOGD(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_);
|
ESP_LOGD(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_);
|
||||||
this->event_callback_.call(event_type);
|
this->event_callback_.call(StringRef(found));
|
||||||
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_event(this);
|
ControllerRegistry::notify_event(this);
|
||||||
#endif
|
#endif
|
||||||
@@ -45,7 +45,7 @@ void Event::set_event_types(const std::vector<const char *> &event_types) {
|
|||||||
this->last_event_type_ = nullptr; // Reset when types change
|
this->last_event_type_ = nullptr; // Reset when types change
|
||||||
}
|
}
|
||||||
|
|
||||||
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {
|
void Event::add_on_event_callback(std::function<void(StringRef event_type)> &&callback) {
|
||||||
this->event_callback_.add(std::move(callback));
|
this->event_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
|||||||
/// Check if an event has been triggered.
|
/// Check if an event has been triggered.
|
||||||
bool has_event() const { return this->last_event_type_ != nullptr; }
|
bool has_event() const { return this->last_event_type_ != nullptr; }
|
||||||
|
|
||||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
void add_on_event_callback(std::function<void(StringRef event_type)> &&callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
LazyCallbackManager<void(const std::string &event_type)> event_callback_;
|
LazyCallbackManager<void(StringRef event_type)> event_callback_;
|
||||||
FixedVector<const char *> types_;
|
FixedVector<const char *> types_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ void EZOSensor::loop() {
|
|||||||
this->commands_.pop_front();
|
this->commands_.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms) {
|
void EZOSensor::add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms) {
|
||||||
std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
|
std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
|
||||||
ezo_command->command = command;
|
ezo_command->command = command;
|
||||||
ezo_command->command_type = command_type;
|
ezo_command->command_type = command_type;
|
||||||
@@ -169,13 +169,17 @@ void EZOSensor::add_command_(const std::string &command, EzoCommandType command_
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
|
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
|
||||||
std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
|
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
|
||||||
|
char payload[24];
|
||||||
|
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
|
||||||
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::set_address(uint8_t address) {
|
void EZOSensor::set_address(uint8_t address) {
|
||||||
if (address > 0 && address < 128) {
|
if (address > 0 && address < 128) {
|
||||||
std::string payload = str_sprintf("I2C,%u", address);
|
// max 8: "I2C,"(4) + uint8(3) + null
|
||||||
|
char payload[8];
|
||||||
|
snprintf(payload, sizeof(payload), "I2C,%u", address);
|
||||||
this->new_address_ = address;
|
this->new_address_ = address;
|
||||||
this->add_command_(payload, EzoCommandType::EZO_I2C);
|
this->add_command_(payload, EzoCommandType::EZO_I2C);
|
||||||
} else {
|
} else {
|
||||||
@@ -194,7 +198,9 @@ void EZOSensor::get_slope() { this->add_command_("Slope,?", EzoCommandType::EZO_
|
|||||||
void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
|
void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
|
||||||
|
|
||||||
void EZOSensor::set_t(float value) {
|
void EZOSensor::set_t(float value) {
|
||||||
std::string payload = str_sprintf("T,%0.2f", value);
|
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
|
||||||
|
char payload[16];
|
||||||
|
snprintf(payload, sizeof(payload), "T,%0.2f", value);
|
||||||
this->add_command_(payload, EzoCommandType::EZO_T);
|
this->add_command_(payload, EzoCommandType::EZO_T);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +221,9 @@ void EZOSensor::set_calibration_point_high(float value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::set_calibration_generic(float value) {
|
void EZOSensor::set_calibration_generic(float value) {
|
||||||
std::string payload = str_sprintf("Cal,%0.2f", value);
|
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
|
||||||
|
char payload[16];
|
||||||
|
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
|
||||||
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,13 +231,11 @@ void EZOSensor::clear_calibration() { this->add_command_("Cal,clear", EzoCommand
|
|||||||
|
|
||||||
void EZOSensor::get_led_state() { this->add_command_("L,?", EzoCommandType::EZO_LED); }
|
void EZOSensor::get_led_state() { this->add_command_("L,?", EzoCommandType::EZO_LED); }
|
||||||
|
|
||||||
void EZOSensor::set_led_state(bool on) {
|
void EZOSensor::set_led_state(bool on) { this->add_command_(on ? "L,1" : "L,0", EzoCommandType::EZO_LED); }
|
||||||
std::string to_send = "L,";
|
|
||||||
to_send += on ? "1" : "0";
|
|
||||||
this->add_command_(to_send, EzoCommandType::EZO_LED);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EZOSensor::send_custom(const std::string &to_send) { this->add_command_(to_send, EzoCommandType::EZO_CUSTOM); }
|
void EZOSensor::send_custom(const std::string &to_send) {
|
||||||
|
this->add_command_(to_send.c_str(), EzoCommandType::EZO_CUSTOM);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ezo
|
} // namespace ezo
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
|
|||||||
std::deque<std::unique_ptr<EzoCommand>> commands_;
|
std::deque<std::unique_ptr<EzoCommand>> commands_;
|
||||||
int new_address_;
|
int new_address_;
|
||||||
|
|
||||||
void add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms = 300);
|
void add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms = 300);
|
||||||
|
|
||||||
void set_calibration_point_(EzoCalibrationType type, float value);
|
void set_calibration_point_(EzoCalibrationType type, float value);
|
||||||
|
|
||||||
|
|||||||
@@ -318,90 +318,93 @@ void EzoPMP::send_next_command_() {
|
|||||||
switch (this->next_command_) {
|
switch (this->next_command_) {
|
||||||
// Read Commands
|
// Read Commands
|
||||||
case EZO_PMP_COMMAND_READ_DOSING: // Page 54
|
case EZO_PMP_COMMAND_READ_DOSING: // Page 54
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "D,?");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,?");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
|
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "R");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "R");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE:
|
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE:
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "DC,?");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "DC,?");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_READ_PAUSE_STATUS:
|
case EZO_PMP_COMMAND_READ_PAUSE_STATUS:
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "P,?");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "P,?");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED:
|
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED:
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "TV,?");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "TV,?");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED:
|
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED:
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "ATV,?");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "ATV,?");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS:
|
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS:
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "Cal,?");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,?");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE:
|
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE:
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "PV,?");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "PV,?");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Non-Read Commands
|
// Non-Read Commands
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_FIND: // Find (page 52)
|
case EZO_PMP_COMMAND_FIND: // Find (page 52)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "Find");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Find");
|
||||||
wait_time_for_command = 60000; // This command will block all updates for a minute
|
wait_time_for_command = 60000; // This command will block all updates for a minute
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
|
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "D,*");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,*");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
|
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "Clear");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Clear");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
|
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "Cal,clear");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,clear");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
|
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "P");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "P");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
|
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "X");
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "X");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Non-Read commands with parameters
|
// Non-Read commands with parameters
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
|
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "D,%0.1f", this->next_command_volume_);
|
command_buffer_length =
|
||||||
|
snprintf((char *) command_buffer, sizeof(command_buffer), "D,%0.1f", this->next_command_volume_);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
|
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
|
||||||
command_buffer_length =
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,%0.1f,%i",
|
||||||
sprintf((char *) command_buffer, "D,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
|
this->next_command_volume_, this->next_command_duration_);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
|
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
|
||||||
command_buffer_length =
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "DC,%0.1f,%i",
|
||||||
sprintf((char *) command_buffer, "DC,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
|
this->next_command_volume_, this->next_command_duration_);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
|
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "Cal,%0.2f", this->next_command_volume_);
|
command_buffer_length =
|
||||||
|
snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,%0.2f", this->next_command_volume_);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73)
|
case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73)
|
||||||
command_buffer_length = sprintf((char *) command_buffer, "I2C,%i", this->next_command_duration_);
|
command_buffer_length =
|
||||||
|
snprintf((char *) command_buffer, sizeof(command_buffer), "I2C,%i", this->next_command_duration_);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command
|
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command
|
||||||
command_buffer_length = sprintf((char *) command_buffer, this->arbitrary_command_, this->next_command_duration_);
|
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "%s", this->arbitrary_command_);
|
||||||
ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
|
ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ FanSpeedSetTrigger = fan_ns.class_(
|
|||||||
"FanSpeedSetTrigger", automation.Trigger.template(cg.int_)
|
"FanSpeedSetTrigger", automation.Trigger.template(cg.int_)
|
||||||
)
|
)
|
||||||
FanPresetSetTrigger = fan_ns.class_(
|
FanPresetSetTrigger = fan_ns.class_(
|
||||||
"FanPresetSetTrigger", automation.Trigger.template(cg.std_string)
|
"FanPresetSetTrigger", automation.Trigger.template(cg.StringRef)
|
||||||
)
|
)
|
||||||
|
|
||||||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
|
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
|
||||||
@@ -287,7 +287,7 @@ async def setup_fan_core_(var, config):
|
|||||||
await automation.build_automation(trigger, [(cg.int_, "x")], conf)
|
await automation.build_automation(trigger, [(cg.int_, "x")], conf)
|
||||||
for conf in config.get(CONF_ON_PRESET_SET, []):
|
for conf in config.get(CONF_ON_PRESET_SET, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
await automation.build_automation(trigger, [(cg.StringRef, "x")], conf)
|
||||||
|
|
||||||
|
|
||||||
async def register_fan(var, config):
|
async def register_fan(var, config):
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ class FanSpeedSetTrigger : public Trigger<int> {
|
|||||||
int last_speed_;
|
int last_speed_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FanPresetSetTrigger : public Trigger<std::string> {
|
class FanPresetSetTrigger : public Trigger<StringRef> {
|
||||||
public:
|
public:
|
||||||
FanPresetSetTrigger(Fan *state) {
|
FanPresetSetTrigger(Fan *state) {
|
||||||
state->add_on_state_callback([this, state]() {
|
state->add_on_state_callback([this, state]() {
|
||||||
@@ -216,7 +216,7 @@ class FanPresetSetTrigger : public Trigger<std::string> {
|
|||||||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||||
this->last_preset_mode_ = preset_mode;
|
this->last_preset_mode_ = preset_mode;
|
||||||
if (should_trigger) {
|
if (should_trigger) {
|
||||||
this->trigger(std::string(preset_mode));
|
this->trigger(preset_mode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this->last_preset_mode_ = state->get_preset_mode();
|
this->last_preset_mode_ = state->get_preset_mode();
|
||||||
|
|||||||
@@ -163,9 +163,10 @@ bool GDK101Component::read_fw_version_(uint8_t *data) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
|
// max 8: "255.255" (7 chars) + null
|
||||||
|
char buf[8];
|
||||||
this->fw_version_text_sensor_->publish_state(fw_version_str);
|
snprintf(buf, sizeof(buf), "%d.%d", data[0], data[1]);
|
||||||
|
this->fw_version_text_sensor_->publish_state(buf);
|
||||||
}
|
}
|
||||||
#endif // USE_TEXT_SENSOR
|
#endif // USE_TEXT_SENSOR
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from esphome.const import (
|
|||||||
CONF_BASELINE,
|
CONF_BASELINE,
|
||||||
CONF_CO2,
|
CONF_CO2,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
|
CONF_WARMUP_TIME,
|
||||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
ICON_MOLECULE_CO2,
|
ICON_MOLECULE_CO2,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
@@ -14,8 +15,6 @@ from esphome.const import (
|
|||||||
|
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
|
|
||||||
CONF_WARMUP_TIME = "warmup_time"
|
|
||||||
|
|
||||||
hc8_ns = cg.esphome_ns.namespace("hc8")
|
hc8_ns = cg.esphome_ns.namespace("hc8")
|
||||||
HC8Component = hc8_ns.class_("HC8Component", cg.PollingComponent, uart.UARTDevice)
|
HC8Component = hc8_ns.class_("HC8Component", cg.PollingComponent, uart.UARTDevice)
|
||||||
HC8CalibrateAction = hc8_ns.class_("HC8CalibrateAction", automation.Action)
|
HC8CalibrateAction = hc8_ns.class_("HC8CalibrateAction", automation.Action)
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Required(CONF_MAX_TEMPERATURE): cv.temperature,
|
cv.Required(CONF_MAX_TEMPERATURE): cv.temperature,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.only_with_arduino,
|
cv.Any(cv.only_with_arduino, cv.only_on_esp32),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -126,6 +126,6 @@ async def to_code(config):
|
|||||||
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||||
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||||
|
|
||||||
cg.add_library("tonia/HeatpumpIR", "1.0.37")
|
cg.add_library("tonia/HeatpumpIR", "1.0.40")
|
||||||
if CORE.is_libretiny or CORE.is_esp32:
|
if CORE.is_libretiny or CORE.is_esp32:
|
||||||
CORE.add_platformio_option("lib_ignore", ["IRremoteESP8266"])
|
CORE.add_platformio_option("lib_ignore", ["IRremoteESP8266"])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "heatpumpir.h"
|
#include "heatpumpir.h"
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "ir_sender_esphome.h"
|
#include "ir_sender_esphome.h"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
||||||
|
|
||||||
#include "esphome/components/climate_ir/climate_ir.h"
|
#include "esphome/components/climate_ir/climate_ir.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "ir_sender_esphome.h"
|
#include "ir_sender_esphome.h"
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace heatpumpir {
|
namespace heatpumpir {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
||||||
|
|
||||||
#include "esphome/components/remote_base/remote_base.h"
|
#include "esphome/components/remote_base/remote_base.h"
|
||||||
#include <IRSender.h> // arduino-heatpump library
|
#include <IRSender.h> // arduino-heatpump library
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ void HomeassistantNumber::control(float value) {
|
|||||||
entity_value.key = VALUE_KEY;
|
entity_value.key = VALUE_KEY;
|
||||||
// Stack buffer - no heap allocation; %g produces shortest representation
|
// Stack buffer - no heap allocation; %g produces shortest representation
|
||||||
char value_buf[16];
|
char value_buf[16];
|
||||||
snprintf(value_buf, sizeof(value_buf), "%g", value);
|
buf_append_printf(value_buf, sizeof(value_buf), 0, "%g", value);
|
||||||
entity_value.value = StringRef(value_buf);
|
entity_value.value = StringRef(value_buf);
|
||||||
|
|
||||||
api::global_api_server->send_homeassistant_action(resp);
|
api::global_api_server->send_homeassistant_action(resp);
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ async def to_code(config):
|
|||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
|
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
|
||||||
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
|
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
|
||||||
|
cg.add(var.set_verify_ssl(config[CONF_VERIFY_SSL]))
|
||||||
|
|
||||||
if config.get(CONF_VERIFY_SSL):
|
if config.get(CONF_VERIFY_SSL):
|
||||||
esp32.add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
|
esp32.add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
|
||||||
|
|||||||
@@ -79,6 +79,81 @@ inline bool is_redirect(int const status) {
|
|||||||
*/
|
*/
|
||||||
inline bool is_success(int const status) { return status >= HTTP_STATUS_OK && status < HTTP_STATUS_MULTIPLE_CHOICES; }
|
inline bool is_success(int const status) { return status >= HTTP_STATUS_OK && status < HTTP_STATUS_MULTIPLE_CHOICES; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HTTP Container Read Semantics
|
||||||
|
* =============================
|
||||||
|
*
|
||||||
|
* IMPORTANT: These semantics differ from standard BSD sockets!
|
||||||
|
*
|
||||||
|
* BSD socket read() returns:
|
||||||
|
* > 0: bytes read
|
||||||
|
* == 0: connection closed (EOF)
|
||||||
|
* < 0: error (check errno)
|
||||||
|
*
|
||||||
|
* HttpContainer::read() returns:
|
||||||
|
* > 0: bytes read successfully
|
||||||
|
* == 0: no data available yet OR all content read
|
||||||
|
* (caller should check bytes_read vs content_length)
|
||||||
|
* < 0: error or connection closed (caller should EXIT)
|
||||||
|
* HTTP_ERROR_CONNECTION_CLOSED (-1) = connection closed prematurely
|
||||||
|
* other negative values = platform-specific errors
|
||||||
|
*
|
||||||
|
* Platform behaviors:
|
||||||
|
* - ESP-IDF: blocking reads, 0 only returned when all content read
|
||||||
|
* - Arduino: non-blocking, 0 means "no data yet" or "all content read"
|
||||||
|
*
|
||||||
|
* Use the helper functions below instead of checking return values directly:
|
||||||
|
* - http_read_loop_result(): for manual loops with per-chunk processing
|
||||||
|
* - http_read_fully(): for simple "read N bytes into buffer" operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Error code returned by HttpContainer::read() when connection closed prematurely
|
||||||
|
/// NOTE: Unlike BSD sockets where 0 means EOF, here 0 means "no data yet, retry"
|
||||||
|
static constexpr int HTTP_ERROR_CONNECTION_CLOSED = -1;
|
||||||
|
|
||||||
|
/// Status of a read operation
|
||||||
|
enum class HttpReadStatus : uint8_t {
|
||||||
|
OK, ///< Read completed successfully
|
||||||
|
ERROR, ///< Read error occurred
|
||||||
|
TIMEOUT, ///< Timeout waiting for data
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Result of an HTTP read operation
|
||||||
|
struct HttpReadResult {
|
||||||
|
HttpReadStatus status; ///< Status of the read operation
|
||||||
|
int error_code; ///< Error code from read() on failure, 0 on success
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Result of processing a non-blocking read with timeout (for manual loops)
|
||||||
|
enum class HttpReadLoopResult : uint8_t {
|
||||||
|
DATA, ///< Data was read, process it
|
||||||
|
RETRY, ///< No data yet, already delayed, caller should continue loop
|
||||||
|
ERROR, ///< Read error, caller should exit loop
|
||||||
|
TIMEOUT, ///< Timeout waiting for data, caller should exit loop
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Process a read result with timeout tracking and delay handling
|
||||||
|
/// @param bytes_read_or_error Return value from read() - positive for bytes read, negative for error
|
||||||
|
/// @param last_data_time Time of last successful read, updated when data received
|
||||||
|
/// @param timeout_ms Maximum time to wait for data
|
||||||
|
/// @return DATA if data received, RETRY if should continue loop, ERROR/TIMEOUT if should exit
|
||||||
|
inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time,
|
||||||
|
uint32_t timeout_ms) {
|
||||||
|
if (bytes_read_or_error > 0) {
|
||||||
|
last_data_time = millis();
|
||||||
|
return HttpReadLoopResult::DATA;
|
||||||
|
}
|
||||||
|
if (bytes_read_or_error < 0) {
|
||||||
|
return HttpReadLoopResult::ERROR;
|
||||||
|
}
|
||||||
|
// bytes_read_or_error == 0: no data available yet
|
||||||
|
if (millis() - last_data_time >= timeout_ms) {
|
||||||
|
return HttpReadLoopResult::TIMEOUT;
|
||||||
|
}
|
||||||
|
delay(1); // Small delay to prevent tight spinning
|
||||||
|
return HttpReadLoopResult::RETRY;
|
||||||
|
}
|
||||||
|
|
||||||
class HttpRequestComponent;
|
class HttpRequestComponent;
|
||||||
|
|
||||||
class HttpContainer : public Parented<HttpRequestComponent> {
|
class HttpContainer : public Parented<HttpRequestComponent> {
|
||||||
@@ -88,6 +163,33 @@ class HttpContainer : public Parented<HttpRequestComponent> {
|
|||||||
int status_code;
|
int status_code;
|
||||||
uint32_t duration_ms;
|
uint32_t duration_ms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read data from the HTTP response body.
|
||||||
|
*
|
||||||
|
* WARNING: These semantics differ from BSD sockets!
|
||||||
|
* BSD sockets: 0 = EOF (connection closed)
|
||||||
|
* This method: 0 = no data yet OR all content read, negative = error/closed
|
||||||
|
*
|
||||||
|
* @param buf Buffer to read data into
|
||||||
|
* @param max_len Maximum number of bytes to read
|
||||||
|
* @return
|
||||||
|
* - > 0: Number of bytes read successfully
|
||||||
|
* - 0: No data available yet OR all content read
|
||||||
|
* (check get_bytes_read() >= content_length to distinguish)
|
||||||
|
* - HTTP_ERROR_CONNECTION_CLOSED (-1): Connection closed prematurely
|
||||||
|
* - < -1: Other error (platform-specific error code)
|
||||||
|
*
|
||||||
|
* Platform notes:
|
||||||
|
* - ESP-IDF: blocking read, 0 only when all content read
|
||||||
|
* - Arduino: non-blocking, 0 can mean "no data yet" or "all content read"
|
||||||
|
*
|
||||||
|
* Use get_bytes_read() and content_length to track progress.
|
||||||
|
* When get_bytes_read() >= content_length, all data has been received.
|
||||||
|
*
|
||||||
|
* IMPORTANT: Do not use raw return values directly. Use these helpers:
|
||||||
|
* - http_read_loop_result(): for loops with per-chunk processing
|
||||||
|
* - http_read_fully(): for simple "read N bytes" operations
|
||||||
|
*/
|
||||||
virtual int read(uint8_t *buf, size_t max_len) = 0;
|
virtual int read(uint8_t *buf, size_t max_len) = 0;
|
||||||
virtual void end() = 0;
|
virtual void end() = 0;
|
||||||
|
|
||||||
@@ -110,6 +212,38 @@ class HttpContainer : public Parented<HttpRequestComponent> {
|
|||||||
std::map<std::string, std::list<std::string>> response_headers_{};
|
std::map<std::string, std::list<std::string>> response_headers_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Read data from HTTP container into buffer with timeout handling
|
||||||
|
/// Handles feed_wdt, yield, and timeout checking internally
|
||||||
|
/// @param container The HTTP container to read from
|
||||||
|
/// @param buffer Buffer to read into
|
||||||
|
/// @param total_size Total bytes to read
|
||||||
|
/// @param chunk_size Maximum bytes per read call
|
||||||
|
/// @param timeout_ms Read timeout in milliseconds
|
||||||
|
/// @return HttpReadResult with status and error_code on failure
|
||||||
|
inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer, size_t total_size, size_t chunk_size,
|
||||||
|
uint32_t timeout_ms) {
|
||||||
|
size_t read_index = 0;
|
||||||
|
uint32_t last_data_time = millis();
|
||||||
|
|
||||||
|
while (read_index < total_size) {
|
||||||
|
int read_bytes_or_error = container->read(buffer + read_index, std::min(chunk_size, total_size - read_index));
|
||||||
|
|
||||||
|
App.feed_wdt();
|
||||||
|
yield();
|
||||||
|
|
||||||
|
auto result = http_read_loop_result(read_bytes_or_error, last_data_time, timeout_ms);
|
||||||
|
if (result == HttpReadLoopResult::RETRY)
|
||||||
|
continue;
|
||||||
|
if (result == HttpReadLoopResult::ERROR)
|
||||||
|
return {HttpReadStatus::ERROR, read_bytes_or_error};
|
||||||
|
if (result == HttpReadLoopResult::TIMEOUT)
|
||||||
|
return {HttpReadStatus::TIMEOUT, 0};
|
||||||
|
|
||||||
|
read_index += read_bytes_or_error;
|
||||||
|
}
|
||||||
|
return {HttpReadStatus::OK, 0};
|
||||||
|
}
|
||||||
|
|
||||||
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
|
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
|
||||||
public:
|
public:
|
||||||
void process(const std::shared_ptr<HttpContainer> &container, std::string &response_body) {
|
void process(const std::shared_ptr<HttpContainer> &container, std::string &response_body) {
|
||||||
@@ -124,6 +258,7 @@ class HttpRequestComponent : public Component {
|
|||||||
|
|
||||||
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
|
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
|
||||||
void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
|
void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
|
||||||
|
uint32_t get_timeout() const { return this->timeout_; }
|
||||||
void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
|
void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
|
||||||
uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
|
uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
|
||||||
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
|
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
|
||||||
@@ -249,15 +384,21 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|||||||
RAMAllocator<uint8_t> allocator;
|
RAMAllocator<uint8_t> allocator;
|
||||||
uint8_t *buf = allocator.allocate(max_length);
|
uint8_t *buf = allocator.allocate(max_length);
|
||||||
if (buf != nullptr) {
|
if (buf != nullptr) {
|
||||||
|
// NOTE: HttpContainer::read() has non-BSD socket semantics - see top of this file
|
||||||
|
// Use http_read_loop_result() helper instead of checking return values directly
|
||||||
size_t read_index = 0;
|
size_t read_index = 0;
|
||||||
|
uint32_t last_data_time = millis();
|
||||||
|
const uint32_t read_timeout = this->parent_->get_timeout();
|
||||||
while (container->get_bytes_read() < max_length) {
|
while (container->get_bytes_read() < max_length) {
|
||||||
int read = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
|
int read_or_error = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
|
||||||
if (read <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
App.feed_wdt();
|
App.feed_wdt();
|
||||||
yield();
|
yield();
|
||||||
read_index += read;
|
auto result = http_read_loop_result(read_or_error, last_data_time, read_timeout);
|
||||||
|
if (result == HttpReadLoopResult::RETRY)
|
||||||
|
continue;
|
||||||
|
if (result != HttpReadLoopResult::DATA)
|
||||||
|
break; // ERROR or TIMEOUT
|
||||||
|
read_index += read_or_error;
|
||||||
}
|
}
|
||||||
response_body.reserve(read_index);
|
response_body.reserve(read_index);
|
||||||
response_body.assign((char *) buf, read_index);
|
response_body.assign((char *) buf, read_index);
|
||||||
|
|||||||
@@ -139,6 +139,23 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Arduino HTTP read implementation
|
||||||
|
//
|
||||||
|
// WARNING: Return values differ from BSD sockets! See http_request.h for full documentation.
|
||||||
|
//
|
||||||
|
// Arduino's WiFiClient is inherently non-blocking - available() returns 0 when
|
||||||
|
// no data is ready. We use connected() to distinguish "no data yet" from
|
||||||
|
// "connection closed".
|
||||||
|
//
|
||||||
|
// WiFiClient behavior:
|
||||||
|
// available() > 0: data ready to read
|
||||||
|
// available() == 0 && connected(): no data yet, still connected
|
||||||
|
// available() == 0 && !connected(): connection closed
|
||||||
|
//
|
||||||
|
// We normalize to HttpContainer::read() contract (NOT BSD socket semantics!):
|
||||||
|
// > 0: bytes read
|
||||||
|
// 0: no data yet, retry <-- NOTE: 0 means retry, NOT EOF!
|
||||||
|
// < 0: error/connection closed <-- connection closed returns -1, not 0
|
||||||
int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
|
int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
|
||||||
const uint32_t start = millis();
|
const uint32_t start = millis();
|
||||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||||
@@ -146,7 +163,7 @@ int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
|
|||||||
WiFiClient *stream_ptr = this->client_.getStreamPtr();
|
WiFiClient *stream_ptr = this->client_.getStreamPtr();
|
||||||
if (stream_ptr == nullptr) {
|
if (stream_ptr == nullptr) {
|
||||||
ESP_LOGE(TAG, "Stream pointer vanished!");
|
ESP_LOGE(TAG, "Stream pointer vanished!");
|
||||||
return -1;
|
return HTTP_ERROR_CONNECTION_CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
int available_data = stream_ptr->available();
|
int available_data = stream_ptr->available();
|
||||||
@@ -154,7 +171,15 @@ int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
|
|||||||
|
|
||||||
if (bufsize == 0) {
|
if (bufsize == 0) {
|
||||||
this->duration_ms += (millis() - start);
|
this->duration_ms += (millis() - start);
|
||||||
return 0;
|
// Check if we've read all expected content
|
||||||
|
if (this->bytes_read_ >= this->content_length) {
|
||||||
|
return 0; // All content read successfully
|
||||||
|
}
|
||||||
|
// No data available - check if connection is still open
|
||||||
|
if (!stream_ptr->connected()) {
|
||||||
|
return HTTP_ERROR_CONNECTION_CLOSED; // Connection closed prematurely
|
||||||
|
}
|
||||||
|
return 0; // No data yet, caller should retry
|
||||||
}
|
}
|
||||||
|
|
||||||
App.feed_wdt();
|
App.feed_wdt();
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
|||||||
config.max_redirection_count = this->redirect_limit_;
|
config.max_redirection_count = this->redirect_limit_;
|
||||||
config.auth_type = HTTP_AUTH_TYPE_BASIC;
|
config.auth_type = HTTP_AUTH_TYPE_BASIC;
|
||||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||||
if (secure) {
|
if (secure && this->verify_ssl_) {
|
||||||
config.crt_bundle_attach = esp_crt_bundle_attach;
|
config.crt_bundle_attach = esp_crt_bundle_attach;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -209,26 +209,57 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ESP-IDF HTTP read implementation (blocking mode)
|
||||||
|
//
|
||||||
|
// WARNING: Return values differ from BSD sockets! See http_request.h for full documentation.
|
||||||
|
//
|
||||||
|
// esp_http_client_read() in blocking mode returns:
|
||||||
|
// > 0: bytes read
|
||||||
|
// 0: connection closed (end of stream)
|
||||||
|
// < 0: error
|
||||||
|
//
|
||||||
|
// We normalize to HttpContainer::read() contract:
|
||||||
|
// > 0: bytes read
|
||||||
|
// 0: no data yet / all content read (caller should check bytes_read vs content_length)
|
||||||
|
// < 0: error/connection closed
|
||||||
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
||||||
const uint32_t start = millis();
|
const uint32_t start = millis();
|
||||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||||
|
|
||||||
this->feed_wdt();
|
// Check if we've already read all expected content
|
||||||
int read_len = esp_http_client_read(this->client_, (char *) buf, max_len);
|
if (this->bytes_read_ >= this->content_length) {
|
||||||
this->feed_wdt();
|
return 0; // All content read successfully
|
||||||
if (read_len > 0) {
|
|
||||||
this->bytes_read_ += read_len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->feed_wdt();
|
||||||
|
int read_len_or_error = esp_http_client_read(this->client_, (char *) buf, max_len);
|
||||||
|
this->feed_wdt();
|
||||||
|
|
||||||
this->duration_ms += (millis() - start);
|
this->duration_ms += (millis() - start);
|
||||||
|
|
||||||
return read_len;
|
if (read_len_or_error > 0) {
|
||||||
|
this->bytes_read_ += read_len_or_error;
|
||||||
|
return read_len_or_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection closed by server before all content received
|
||||||
|
if (read_len_or_error == 0) {
|
||||||
|
return HTTP_ERROR_CONNECTION_CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative value - error, return the actual error code for debugging
|
||||||
|
return read_len_or_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpContainerIDF::end() {
|
void HttpContainerIDF::end() {
|
||||||
|
if (this->client_ == nullptr) {
|
||||||
|
return; // Already cleaned up
|
||||||
|
}
|
||||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||||
|
|
||||||
esp_http_client_close(this->client_);
|
esp_http_client_close(this->client_);
|
||||||
esp_http_client_cleanup(this->client_);
|
esp_http_client_cleanup(this->client_);
|
||||||
|
this->client_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpContainerIDF::feed_wdt() {
|
void HttpContainerIDF::feed_wdt() {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class HttpRequestIDF : public HttpRequestComponent {
|
|||||||
|
|
||||||
void set_buffer_size_rx(uint16_t buffer_size_rx) { this->buffer_size_rx_ = buffer_size_rx; }
|
void set_buffer_size_rx(uint16_t buffer_size_rx) { this->buffer_size_rx_ = buffer_size_rx; }
|
||||||
void set_buffer_size_tx(uint16_t buffer_size_tx) { this->buffer_size_tx_ = buffer_size_tx; }
|
void set_buffer_size_tx(uint16_t buffer_size_tx) { this->buffer_size_tx_ = buffer_size_tx; }
|
||||||
|
void set_verify_ssl(bool verify_ssl) { this->verify_ssl_ = verify_ssl; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||||
@@ -42,6 +43,7 @@ class HttpRequestIDF : public HttpRequestComponent {
|
|||||||
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
|
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
|
||||||
uint16_t buffer_size_rx_{};
|
uint16_t buffer_size_rx_{};
|
||||||
uint16_t buffer_size_tx_{};
|
uint16_t buffer_size_tx_{};
|
||||||
|
bool verify_ssl_{true};
|
||||||
|
|
||||||
/// @brief Monitors the http client events to gather response headers
|
/// @brief Monitors the http client events to gather response headers
|
||||||
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
|
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
|
||||||
|
|||||||
@@ -115,39 +115,47 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
|||||||
return error_code;
|
return error_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: HttpContainer::read() has non-BSD socket semantics - see http_request.h
|
||||||
|
// Use http_read_loop_result() helper instead of checking return values directly
|
||||||
|
uint32_t last_data_time = millis();
|
||||||
|
const uint32_t read_timeout = this->parent_->get_timeout();
|
||||||
|
|
||||||
while (container->get_bytes_read() < container->content_length) {
|
while (container->get_bytes_read() < container->content_length) {
|
||||||
// read a maximum of chunk_size bytes into buf. (real read size returned)
|
// read a maximum of chunk_size bytes into buf. (real read size returned, or negative error code)
|
||||||
int bufsize = container->read(buf, OtaHttpRequestComponent::HTTP_RECV_BUFFER);
|
int bufsize_or_error = container->read(buf, OtaHttpRequestComponent::HTTP_RECV_BUFFER);
|
||||||
ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize = %i", container->get_bytes_read(),
|
ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize_or_error = %i", container->get_bytes_read(),
|
||||||
container->content_length, bufsize);
|
container->content_length, bufsize_or_error);
|
||||||
|
|
||||||
// feed watchdog and give other tasks a chance to run
|
// feed watchdog and give other tasks a chance to run
|
||||||
App.feed_wdt();
|
App.feed_wdt();
|
||||||
yield();
|
yield();
|
||||||
|
|
||||||
// Exit loop if no data available (stream closed or end of data)
|
auto result = http_read_loop_result(bufsize_or_error, last_data_time, read_timeout);
|
||||||
if (bufsize <= 0) {
|
if (result == HttpReadLoopResult::RETRY)
|
||||||
if (bufsize < 0) {
|
continue;
|
||||||
ESP_LOGE(TAG, "Stream closed with error");
|
if (result != HttpReadLoopResult::DATA) {
|
||||||
this->cleanup_(std::move(backend), container);
|
if (result == HttpReadLoopResult::TIMEOUT) {
|
||||||
return OTA_CONNECTION_ERROR;
|
ESP_LOGE(TAG, "Timeout reading data");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Error reading data: %d", bufsize_or_error);
|
||||||
}
|
}
|
||||||
// bufsize == 0: no more data available, exit loop
|
this->cleanup_(std::move(backend), container);
|
||||||
break;
|
return OTA_CONNECTION_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) {
|
// At this point bufsize_or_error > 0, so it's a valid size
|
||||||
|
if (bufsize_or_error <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) {
|
||||||
// add read bytes to MD5
|
// add read bytes to MD5
|
||||||
md5_receive.add(buf, bufsize);
|
md5_receive.add(buf, bufsize_or_error);
|
||||||
|
|
||||||
// write bytes to OTA backend
|
// write bytes to OTA backend
|
||||||
this->update_started_ = true;
|
this->update_started_ = true;
|
||||||
error_code = backend->write(buf, bufsize);
|
error_code = backend->write(buf, bufsize_or_error);
|
||||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
// error code explanation available at
|
// error code explanation available at
|
||||||
// https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h
|
// https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h
|
||||||
ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
|
ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
|
||||||
container->get_bytes_read() - bufsize, container->content_length);
|
container->get_bytes_read() - bufsize_or_error, container->content_length);
|
||||||
this->cleanup_(std::move(backend), container);
|
this->cleanup_(std::move(backend), container);
|
||||||
return error_code;
|
return error_code;
|
||||||
}
|
}
|
||||||
@@ -244,19 +252,19 @@ bool OtaHttpRequestComponent::http_get_md5_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->md5_expected_.resize(MD5_SIZE);
|
this->md5_expected_.resize(MD5_SIZE);
|
||||||
int read_len = 0;
|
auto result = http_read_fully(container.get(), (uint8_t *) this->md5_expected_.data(), MD5_SIZE, MD5_SIZE,
|
||||||
while (container->get_bytes_read() < MD5_SIZE) {
|
this->parent_->get_timeout());
|
||||||
read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
|
|
||||||
if (read_len <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
App.feed_wdt();
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
container->end();
|
container->end();
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Read len: %u, MD5 expected: %u", read_len, MD5_SIZE);
|
if (result.status != HttpReadStatus::OK) {
|
||||||
return read_len == MD5_SIZE;
|
if (result.status == HttpReadStatus::TIMEOUT) {
|
||||||
|
ESP_LOGE(TAG, "Timeout reading MD5");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Error reading MD5: %d", result.error_code);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OtaHttpRequestComponent::validate_url_(const std::string &url) {
|
bool OtaHttpRequestComponent::validate_url_(const std::string &url) {
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ namespace http_request {
|
|||||||
|
|
||||||
// The update function runs in a task only on ESP32s.
|
// The update function runs in a task only on ESP32s.
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#define UPDATE_RETURN vTaskDelete(nullptr) // Delete the current update task
|
// vTaskDelete doesn't return, but clang-tidy doesn't know that
|
||||||
|
#define UPDATE_RETURN \
|
||||||
|
do { \
|
||||||
|
vTaskDelete(nullptr); \
|
||||||
|
__builtin_unreachable(); \
|
||||||
|
} while (0)
|
||||||
#else
|
#else
|
||||||
#define UPDATE_RETURN return
|
#define UPDATE_RETURN return
|
||||||
#endif
|
#endif
|
||||||
@@ -70,19 +75,21 @@ void HttpRequestUpdate::update_task(void *params) {
|
|||||||
UPDATE_RETURN;
|
UPDATE_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t read_index = 0;
|
auto read_result = http_read_fully(container.get(), data, container->content_length, MAX_READ_SIZE,
|
||||||
while (container->get_bytes_read() < container->content_length) {
|
this_update->request_parent_->get_timeout());
|
||||||
int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
|
if (read_result.status != HttpReadStatus::OK) {
|
||||||
|
if (read_result.status == HttpReadStatus::TIMEOUT) {
|
||||||
yield();
|
ESP_LOGE(TAG, "Timeout reading manifest");
|
||||||
|
} else {
|
||||||
if (read_bytes <= 0) {
|
ESP_LOGE(TAG, "Error reading manifest: %d", read_result.error_code);
|
||||||
// Network error or connection closed - break to avoid infinite loop
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||||
read_index += read_bytes;
|
this_update->defer([this_update]() { this_update->status_set_error(LOG_STR("Failed to read manifest")); });
|
||||||
|
allocator.deallocate(data, container->content_length);
|
||||||
|
container->end();
|
||||||
|
UPDATE_RETURN;
|
||||||
}
|
}
|
||||||
|
size_t read_index = container->get_bytes_read();
|
||||||
|
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
{ // Ensures the response string falls out of scope and deallocates before the task ends
|
{ // Ensures the response string falls out of scope and deallocates before the task ends
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ async def to_code(config):
|
|||||||
var = cg.Pvariable(config[CONF_ID], rhs)
|
var = cg.Pvariable(config[CONF_ID], rhs)
|
||||||
|
|
||||||
await display.register_display(var, config)
|
await display.register_display(var, config)
|
||||||
await spi.register_spi_device(var, config)
|
await spi.register_spi_device(var, config, write_only=True)
|
||||||
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||||
cg.add(var.set_dc_pin(dc))
|
cg.add(var.set_dc_pin(dc))
|
||||||
if init_sequences := config.get(CONF_INIT_SEQUENCE):
|
if init_sequences := config.get(CONF_INIT_SEQUENCE):
|
||||||
|
|||||||
@@ -18,7 +18,15 @@ InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) {
|
|||||||
|
|
||||||
InfraredCall &InfraredCall::set_raw_timings(const std::vector<int32_t> &timings) {
|
InfraredCall &InfraredCall::set_raw_timings(const std::vector<int32_t> &timings) {
|
||||||
this->raw_timings_ = &timings;
|
this->raw_timings_ = &timings;
|
||||||
this->packed_data_ = nullptr; // Clear packed if vector is set
|
this->packed_data_ = nullptr;
|
||||||
|
this->base64url_ptr_ = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
InfraredCall &InfraredCall::set_raw_timings_base64url(const std::string &base64url) {
|
||||||
|
this->base64url_ptr_ = &base64url;
|
||||||
|
this->raw_timings_ = nullptr;
|
||||||
|
this->packed_data_ = nullptr;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +34,8 @@ InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t
|
|||||||
this->packed_data_ = data;
|
this->packed_data_ = data;
|
||||||
this->packed_length_ = length;
|
this->packed_length_ = length;
|
||||||
this->packed_count_ = count;
|
this->packed_count_ = count;
|
||||||
this->raw_timings_ = nullptr; // Clear vector if packed is set
|
this->raw_timings_ = nullptr;
|
||||||
|
this->base64url_ptr_ = nullptr;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +101,23 @@ void Infrared::control(const InfraredCall &call) {
|
|||||||
call.get_packed_count());
|
call.get_packed_count());
|
||||||
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(),
|
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(),
|
||||||
call.get_repeat_count());
|
call.get_repeat_count());
|
||||||
|
} else if (call.is_base64url()) {
|
||||||
|
// Decode base64url (URL-safe) into transmit buffer
|
||||||
|
if (!transmit_data->set_data_from_base64url(call.get_base64url_data())) {
|
||||||
|
ESP_LOGE(TAG, "Invalid base64url data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Sanity check: validate timing values are within reasonable bounds
|
||||||
|
constexpr int32_t max_timing_us = 500000; // 500ms absolute max
|
||||||
|
for (int32_t timing : transmit_data->get_data()) {
|
||||||
|
int32_t abs_timing = timing < 0 ? -timing : timing;
|
||||||
|
if (abs_timing > max_timing_us) {
|
||||||
|
ESP_LOGE(TAG, "Invalid timing value: %d µs (max %d)", timing, max_timing_us);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(),
|
||||||
|
call.get_repeat_count());
|
||||||
} else {
|
} else {
|
||||||
// From vector (lambdas/automations)
|
// From vector (lambdas/automations)
|
||||||
transmit_data->set_data(call.get_raw_timings());
|
transmit_data->set_data(call.get_raw_timings());
|
||||||
|
|||||||
@@ -28,12 +28,29 @@ class InfraredCall {
|
|||||||
|
|
||||||
/// Set the carrier frequency in Hz
|
/// Set the carrier frequency in Hz
|
||||||
InfraredCall &set_carrier_frequency(uint32_t frequency);
|
InfraredCall &set_carrier_frequency(uint32_t frequency);
|
||||||
/// Set the raw timings (positive = mark, negative = space)
|
|
||||||
/// Note: The timings vector must outlive the InfraredCall (zero-copy reference)
|
// ===== Raw Timings Methods =====
|
||||||
|
// All set_raw_timings_* methods store pointers/references to external data.
|
||||||
|
// The referenced data must remain valid until perform() completes.
|
||||||
|
// Safe pattern: call.set_raw_timings_xxx(data); call.perform(); // synchronous
|
||||||
|
// Unsafe pattern: call.set_raw_timings_xxx(data); defer([call]() { call.perform(); }); // data may be gone!
|
||||||
|
|
||||||
|
/// Set the raw timings from a vector (positive = mark, negative = space)
|
||||||
|
/// @note Lifetime: Stores a pointer to the vector. The vector must outlive perform().
|
||||||
|
/// @note Usage: Primarily for lambdas/automations where the vector is in scope.
|
||||||
InfraredCall &set_raw_timings(const std::vector<int32_t> &timings);
|
InfraredCall &set_raw_timings(const std::vector<int32_t> &timings);
|
||||||
/// Set the raw timings from packed protobuf sint32 data (zero-copy from wire)
|
|
||||||
/// Note: The data must outlive the InfraredCall
|
/// Set the raw timings from base64url-encoded little-endian int32 data
|
||||||
|
/// @note Lifetime: Stores a pointer to the string. The string must outlive perform().
|
||||||
|
/// @note Usage: For web_server - base64url is fully URL-safe (uses '-' and '_').
|
||||||
|
/// @note Decoding happens at perform() time, directly into the transmit buffer.
|
||||||
|
InfraredCall &set_raw_timings_base64url(const std::string &base64url);
|
||||||
|
|
||||||
|
/// Set the raw timings from packed protobuf sint32 data (zigzag + varint encoded)
|
||||||
|
/// @note Lifetime: Stores a pointer to the buffer. The buffer must outlive perform().
|
||||||
|
/// @note Usage: For API component where data comes directly from the protobuf message.
|
||||||
InfraredCall &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count);
|
InfraredCall &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count);
|
||||||
|
|
||||||
/// Set the number of times to repeat transmission (1 = transmit once, 2 = transmit twice, etc.)
|
/// Set the number of times to repeat transmission (1 = transmit once, 2 = transmit twice, etc.)
|
||||||
InfraredCall &set_repeat_count(uint32_t count);
|
InfraredCall &set_repeat_count(uint32_t count);
|
||||||
|
|
||||||
@@ -42,12 +59,18 @@ class InfraredCall {
|
|||||||
|
|
||||||
/// Get the carrier frequency
|
/// Get the carrier frequency
|
||||||
const optional<uint32_t> &get_carrier_frequency() const { return this->carrier_frequency_; }
|
const optional<uint32_t> &get_carrier_frequency() const { return this->carrier_frequency_; }
|
||||||
/// Get the raw timings (only valid if set via set_raw_timings, not packed)
|
/// Get the raw timings (only valid if set via set_raw_timings)
|
||||||
const std::vector<int32_t> &get_raw_timings() const { return *this->raw_timings_; }
|
const std::vector<int32_t> &get_raw_timings() const { return *this->raw_timings_; }
|
||||||
/// Check if raw timings have been set (either vector or packed)
|
/// Check if raw timings have been set (any format)
|
||||||
bool has_raw_timings() const { return this->raw_timings_ != nullptr || this->packed_data_ != nullptr; }
|
bool has_raw_timings() const {
|
||||||
|
return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base64url_ptr_ != nullptr;
|
||||||
|
}
|
||||||
/// Check if using packed data format
|
/// Check if using packed data format
|
||||||
bool is_packed() const { return this->packed_data_ != nullptr; }
|
bool is_packed() const { return this->packed_data_ != nullptr; }
|
||||||
|
/// Check if using base64url data format
|
||||||
|
bool is_base64url() const { return this->base64url_ptr_ != nullptr; }
|
||||||
|
/// Get the base64url data string
|
||||||
|
const std::string &get_base64url_data() const { return *this->base64url_ptr_; }
|
||||||
/// Get packed data (only valid if set via set_raw_timings_packed)
|
/// Get packed data (only valid if set via set_raw_timings_packed)
|
||||||
const uint8_t *get_packed_data() const { return this->packed_data_; }
|
const uint8_t *get_packed_data() const { return this->packed_data_; }
|
||||||
uint16_t get_packed_length() const { return this->packed_length_; }
|
uint16_t get_packed_length() const { return this->packed_length_; }
|
||||||
@@ -59,9 +82,11 @@ class InfraredCall {
|
|||||||
uint32_t repeat_count_{1};
|
uint32_t repeat_count_{1};
|
||||||
Infrared *parent_;
|
Infrared *parent_;
|
||||||
optional<uint32_t> carrier_frequency_;
|
optional<uint32_t> carrier_frequency_;
|
||||||
// Vector-based timings (for lambdas/automations)
|
// Pointer to vector-based timings (caller-owned, must outlive perform())
|
||||||
const std::vector<int32_t> *raw_timings_{nullptr};
|
const std::vector<int32_t> *raw_timings_{nullptr};
|
||||||
// Packed protobuf timings (for API zero-copy)
|
// Pointer to base64url-encoded string (caller-owned, must outlive perform())
|
||||||
|
const std::string *base64url_ptr_{nullptr};
|
||||||
|
// Pointer to packed protobuf buffer (caller-owned, must outlive perform())
|
||||||
const uint8_t *packed_data_{nullptr};
|
const uint8_t *packed_data_{nullptr};
|
||||||
uint16_t packed_length_{0};
|
uint16_t packed_length_{0};
|
||||||
uint16_t packed_count_{0};
|
uint16_t packed_count_{0};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.core import CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
json_ns = cg.esphome_ns.namespace("json")
|
json_ns = cg.esphome_ns.namespace("json")
|
||||||
@@ -12,6 +12,11 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.BUS)
|
@coroutine_with_priority(CoroPriority.BUS)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_library("bblanchon/ArduinoJson", "7.4.2")
|
if CORE.is_esp32:
|
||||||
|
from esphome.components.esp32 import add_idf_component
|
||||||
|
|
||||||
|
add_idf_component(name="bblanchon/arduinojson", ref="7.4.2")
|
||||||
|
else:
|
||||||
|
cg.add_library("bblanchon/ArduinoJson", "7.4.2")
|
||||||
cg.add_define("USE_JSON")
|
cg.add_define("USE_JSON")
|
||||||
cg.add_global(json_ns.using)
|
cg.add_global(json_ns.using)
|
||||||
|
|||||||
@@ -382,4 +382,11 @@ async def component_to_code(config):
|
|||||||
"custom_options.sys_config#h", _BK7231N_SYS_CONFIG_OPTIONS
|
"custom_options.sys_config#h", _BK7231N_SYS_CONFIG_OPTIONS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Disable LWIP statistics to save RAM - not needed in production
|
||||||
|
# Must explicitly disable all sub-stats to avoid redefinition warnings
|
||||||
|
cg.add_platformio_option(
|
||||||
|
"custom_options.lwip",
|
||||||
|
["LWIP_STATS=0", "MEM_STATS=0", "MEMP_STATS=0"],
|
||||||
|
)
|
||||||
|
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|||||||
@@ -166,8 +166,8 @@ class LibreTinyPreferences : public ESPPreferences {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate buffer on heap to avoid stack allocation for large data
|
// Most preferences are small, use stack buffer with heap fallback for large ones
|
||||||
auto stored_data = std::make_unique<uint8_t[]>(kv.value_len);
|
SmallBufferWithHeapFallback<256> stored_data(kv.value_len);
|
||||||
fdb_blob_make(&this->blob, stored_data.get(), kv.value_len);
|
fdb_blob_make(&this->blob, stored_data.get(), kv.value_len);
|
||||||
size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob);
|
size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob);
|
||||||
if (actual_len != kv.value_len) {
|
if (actual_len != kv.value_len) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "light_json_schema.h"
|
#include "light_json_schema.h"
|
||||||
|
#include "color_mode.h"
|
||||||
#include "light_output.h"
|
#include "light_output.h"
|
||||||
#include "esphome/core/progmem.h"
|
#include "esphome/core/progmem.h"
|
||||||
|
|
||||||
@@ -8,29 +9,32 @@ namespace esphome::light {
|
|||||||
|
|
||||||
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
|
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
|
||||||
|
|
||||||
// Get JSON string for color mode using linear search (avoids large switch jump table)
|
// Get JSON string for color mode.
|
||||||
static const char *get_color_mode_json_str(ColorMode mode) {
|
// ColorMode enum values are sparse bitmasks (0, 1, 3, 7, 11, 19, 35, 39, 47, 51) which would
|
||||||
// Parallel arrays: mode values and their corresponding strings
|
// generate a large jump table. Converting to bit index (0-9) allows a compact switch.
|
||||||
// Uses less RAM than a switch jump table on sparse enum values
|
static ProgmemStr get_color_mode_json_str(ColorMode mode) {
|
||||||
static constexpr ColorMode MODES[] = {
|
switch (ColorModeBitPolicy::to_bit(mode)) {
|
||||||
ColorMode::ON_OFF,
|
case 1:
|
||||||
ColorMode::BRIGHTNESS,
|
return ESPHOME_F("onoff");
|
||||||
ColorMode::WHITE,
|
case 2:
|
||||||
ColorMode::COLOR_TEMPERATURE,
|
return ESPHOME_F("brightness");
|
||||||
ColorMode::COLD_WARM_WHITE,
|
case 3:
|
||||||
ColorMode::RGB,
|
return ESPHOME_F("white");
|
||||||
ColorMode::RGB_WHITE,
|
case 4:
|
||||||
ColorMode::RGB_COLOR_TEMPERATURE,
|
return ESPHOME_F("color_temp");
|
||||||
ColorMode::RGB_COLD_WARM_WHITE,
|
case 5:
|
||||||
};
|
return ESPHOME_F("cwww");
|
||||||
static constexpr const char *STRINGS[] = {
|
case 6:
|
||||||
"onoff", "brightness", "white", "color_temp", "cwww", "rgb", "rgbw", "rgbct", "rgbww",
|
return ESPHOME_F("rgb");
|
||||||
};
|
case 7:
|
||||||
for (size_t i = 0; i < sizeof(MODES) / sizeof(MODES[0]); i++) {
|
return ESPHOME_F("rgbw");
|
||||||
if (MODES[i] == mode)
|
case 8:
|
||||||
return STRINGS[i];
|
return ESPHOME_F("rgbct");
|
||||||
|
case 9:
|
||||||
|
return ESPHOME_F("rgbww");
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||||
@@ -44,7 +48,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
|||||||
auto values = state.remote_values;
|
auto values = state.remote_values;
|
||||||
|
|
||||||
const auto color_mode = values.get_color_mode();
|
const auto color_mode = values.get_color_mode();
|
||||||
const char *mode_str = get_color_mode_json_str(color_mode);
|
const auto *mode_str = get_color_mode_json_str(color_mode);
|
||||||
if (mode_str != nullptr) {
|
if (mode_str != nullptr) {
|
||||||
root[ESPHOME_F("color_mode")] = mode_str;
|
root[ESPHOME_F("color_mode")] = mode_str;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
@@ -44,13 +45,16 @@ void LightWaveRF::send_rx(const std::vector<uint8_t> &msg, uint8_t repeats, bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LightWaveRF::print_msg_(uint8_t *msg, uint8_t len) {
|
void LightWaveRF::print_msg_(uint8_t *msg, uint8_t len) {
|
||||||
char buffer[65];
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||||
|
char buffer[65]; // max 10 entries * 6 chars + null
|
||||||
ESP_LOGD(TAG, " Received code (len:%i): ", len);
|
ESP_LOGD(TAG, " Received code (len:%i): ", len);
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
sprintf(&buffer[i * 6], "0x%02x, ", msg[i]);
|
pos = buf_append_printf(buffer, sizeof(buffer), pos, "0x%02x, ", msg[i]);
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "[%s]", buffer);
|
ESP_LOGD(TAG, "[%s]", buffer);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightWaveRF::dump_config() {
|
void LightWaveRF::dump_config() {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user