mirror of
https://github.com/esphome/esphome.git
synced 2026-01-18 17:16:25 -07:00
Compare commits
83 Commits
ezo_stack_
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
680e92a226 | ||
|
|
db0b32bfc9 | ||
|
|
21794e28e5 | ||
|
|
728236270c | ||
|
|
01cdc4ed58 | ||
|
|
d6a0c8ffbb | ||
|
|
4cc0f874f7 | ||
|
|
ed58b9372f | ||
|
|
ee2a81923b | ||
|
|
0a1e7ee50b | ||
|
|
4d4283bcfa | ||
|
|
e4fb6988ff | ||
|
|
d31b733dce | ||
|
|
b25a2f8d8e | ||
|
|
3f892711c7 | ||
|
|
798d3bd956 | ||
|
|
77df3933db | ||
|
|
19514ccdf4 | ||
|
|
2947642ca5 | ||
|
|
60e333db08 | ||
|
|
d8463f4813 | ||
|
|
e1800d2fe2 | ||
|
|
50aa4b1992 | ||
|
|
edb303e495 | ||
|
|
973fc4c5dc | ||
|
|
f88e8fc43b | ||
|
|
d830787c71 | ||
|
|
c4c31a2e8e | ||
|
|
e6790f0042 | ||
|
|
ec7f72e280 | ||
|
|
6f29dbd6f1 | ||
|
|
9caf78aa7e | ||
|
|
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 | ||
|
|
c2737ca3bb | ||
|
|
00cc9e44b6 | ||
|
|
c151b2da67 | ||
|
|
dacd185afb | ||
|
|
f88cf1b83a | ||
|
|
3f6412ba07 | ||
|
|
1ad0969099 | ||
|
|
737c2b8732 | ||
|
|
9030dc9d4e | ||
|
|
0b5a3506cc | ||
|
|
3c63ff5e36 | ||
|
|
0427350101 | ||
|
|
41dceb76ec | ||
|
|
6380458d78 | ||
|
|
0dc5a7c9a4 | ||
|
|
9003844eda | ||
|
|
22a4ec69c2 | ||
|
|
9d42bfd161 | ||
|
|
49c881d067 | ||
|
|
78aee4f498 | ||
|
|
9da2c08f36 | ||
|
|
03f3deff41 |
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') }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -222,8 +222,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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
|
|||||||
this->last_update_ = millis();
|
this->last_update_ = millis();
|
||||||
if (state != this->current_state_) {
|
if (state != this->current_state_) {
|
||||||
auto prev_state = this->current_state_;
|
auto prev_state = this->current_state_;
|
||||||
ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
|
ESP_LOGD(TAG, "'%s' >> %s (was %s)", this->get_name().c_str(),
|
||||||
|
LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
|
||||||
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
|
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
|
||||||
this->current_state_ = state;
|
this->current_state_ = state;
|
||||||
// Single state callback - triggers check get_state() for specific states
|
// Single state callback - triggers check get_state() for specific states
|
||||||
|
|||||||
@@ -241,8 +241,10 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
|
|||||||
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||||
if (obj->is_internal()) \
|
if (obj->is_internal()) \
|
||||||
return; \
|
return; \
|
||||||
for (auto &c : this->clients_) \
|
for (auto &c : this->clients_) { \
|
||||||
c->send_##entity_name##_state(obj); \
|
if (c->flags_.state_subscription) \
|
||||||
|
c->send_##entity_name##_state(obj); \
|
||||||
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
@@ -321,8 +323,10 @@ API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
|
|||||||
void APIServer::on_event(event::Event *obj) {
|
void APIServer::on_event(event::Event *obj) {
|
||||||
if (obj->is_internal())
|
if (obj->is_internal())
|
||||||
return;
|
return;
|
||||||
for (auto &c : this->clients_)
|
for (auto &c : this->clients_) {
|
||||||
c->send_event(obj);
|
if (c->flags_.state_subscription)
|
||||||
|
c->send_event(obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -331,8 +335,10 @@ void APIServer::on_event(event::Event *obj) {
|
|||||||
void APIServer::on_update(update::UpdateEntity *obj) {
|
void APIServer::on_update(update::UpdateEntity *obj) {
|
||||||
if (obj->is_internal())
|
if (obj->is_internal())
|
||||||
return;
|
return;
|
||||||
for (auto &c : this->clients_)
|
for (auto &c : this->clients_) {
|
||||||
c->send_update_state(obj);
|
if (c->flags_.state_subscription)
|
||||||
|
c->send_update_state(obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -552,8 +558,10 @@ bool APIServer::clear_noise_psk(bool make_active) {
|
|||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
void APIServer::request_time() {
|
void APIServer::request_time() {
|
||||||
for (auto &client : this->clients_) {
|
for (auto &client : this->clients_) {
|
||||||
if (!client->flags_.remove && client->is_authenticated())
|
if (!client->flags_.remove && client->is_authenticated()) {
|
||||||
client->send_time_request();
|
client->send_time_request();
|
||||||
|
return; // Only request from one client to avoid clock conflicts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size
|
|||||||
}
|
}
|
||||||
uint32_t field_length = res->as_uint32();
|
uint32_t field_length = res->as_uint32();
|
||||||
ptr += consumed;
|
ptr += consumed;
|
||||||
if (ptr + field_length > end) {
|
if (field_length > static_cast<size_t>(end - ptr)) {
|
||||||
return count; // Out of bounds
|
return count; // Out of bounds
|
||||||
}
|
}
|
||||||
ptr += field_length;
|
ptr += field_length;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WIRE_TYPE_FIXED32: { // 32-bit - skip 4 bytes
|
case WIRE_TYPE_FIXED32: { // 32-bit - skip 4 bytes
|
||||||
if (ptr + 4 > end) {
|
if (end - ptr < 4) {
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
ptr += 4;
|
ptr += 4;
|
||||||
@@ -110,7 +110,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
|||||||
}
|
}
|
||||||
uint32_t field_length = res->as_uint32();
|
uint32_t field_length = res->as_uint32();
|
||||||
ptr += consumed;
|
ptr += consumed;
|
||||||
if (ptr + field_length > end) {
|
if (field_length > static_cast<size_t>(end - ptr)) {
|
||||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WIRE_TYPE_FIXED32: { // 32-bit
|
case WIRE_TYPE_FIXED32: { // 32-bit
|
||||||
if (ptr + 4 > end) {
|
if (end - ptr < 4) {
|
||||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_();
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ bool BinarySensor::set_new_state(const optional<bool> &new_state) {
|
|||||||
#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_binary_sensor_update(this);
|
ControllerRegistry::notify_binary_sensor_update(this);
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGD(TAG, "'%s': %s", this->get_name().c_str(), ONOFFMAYBE(new_state));
|
ESP_LOGD(TAG, "'%s' >> %s", this->get_name().c_str(), ONOFFMAYBE(new_state));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
||||||
web_server_base.WebServerBase
|
web_server_base.WebServerBase
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"),
|
cv.Optional(CONF_COMPRESSION, default="gzip"): cv.one_of("gzip", "br"),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_on(
|
cv.only_on(
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ void Climate::save_state_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Climate::publish_state() {
|
void Climate::publish_state() {
|
||||||
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
|
ESP_LOGD(TAG, "'%s' >>", this->name_.c_str());
|
||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
|
|
||||||
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ void Cover::publish_state(bool save) {
|
|||||||
this->position = clamp(this->position, 0.0f, 1.0f);
|
this->position = clamp(this->position, 0.0f, 1.0f);
|
||||||
this->tilt = clamp(this->tilt, 0.0f, 1.0f);
|
this->tilt = clamp(this->tilt, 0.0f, 1.0f);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str());
|
ESP_LOGD(TAG, "'%s' >>", this->name_.c_str());
|
||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
if (traits.get_supports_position()) {
|
if (traits.get_supports_position()) {
|
||||||
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
|
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ void DallasTemperatureSensor::update() {
|
|||||||
|
|
||||||
this->send_command_(DALLAS_COMMAND_START_CONVERSION);
|
this->send_command_(DALLAS_COMMAND_START_CONVERSION);
|
||||||
|
|
||||||
this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] {
|
this->set_timeout(this->get_address_name().c_str(), this->millis_to_wait_for_conversion_(), [this] {
|
||||||
if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
|
if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ void DateEntity::publish_state() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->set_has_state(true);
|
this->set_has_state(true);
|
||||||
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
ESP_LOGD(TAG, "'%s' >> %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||||
this->state_callback_.call();
|
this->state_callback_.call();
|
||||||
#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_date_update(this);
|
ControllerRegistry::notify_date_update(this);
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ void DateTimeEntity::publish_state() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->set_has_state(true);
|
this->set_has_state(true);
|
||||||
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
|
ESP_LOGD(TAG, "'%s' >> %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, this->month_,
|
||||||
this->month_, this->day_, this->hour_, this->minute_, this->second_);
|
this->day_, this->hour_, this->minute_, this->second_);
|
||||||
this->state_callback_.call();
|
this->state_callback_.call();
|
||||||
#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_datetime_update(this);
|
ControllerRegistry::notify_datetime_update(this);
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ void TimeEntity::publish_state() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->set_has_state(true);
|
this->set_has_state(true);
|
||||||
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
|
ESP_LOGD(TAG, "'%s' >> %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, this->second_);
|
||||||
this->second_);
|
|
||||||
this->state_callback_.call();
|
this->state_callback_.call();
|
||||||
#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_time_update(this);
|
ControllerRegistry::notify_time_update(this);
|
||||||
|
|||||||
@@ -74,8 +74,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 +100,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
|
||||||
|
|||||||
@@ -234,8 +234,19 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
|
|
||||||
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));
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,16 +11,24 @@ 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,
|
||||||
UNIT_PERCENT,
|
UNIT_PERCENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import CONF_DEBUG_ID, DebugComponent
|
from . import ( # noqa: F401 pylint: disable=unused-import
|
||||||
|
CONF_DEBUG_ID,
|
||||||
|
FILTER_SOURCE_FILES,
|
||||||
|
DebugComponent,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["debug"]
|
DEPENDENCIES = ["debug"]
|
||||||
|
|
||||||
|
CONF_MIN_FREE = "min_free"
|
||||||
CONF_PSRAM = "psram"
|
CONF_PSRAM = "psram"
|
||||||
|
|
||||||
CONFIG_SCHEMA = {
|
CONFIG_SCHEMA = {
|
||||||
@@ -38,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,
|
||||||
@@ -47,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,
|
||||||
@@ -89,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))
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ from esphome.const import (
|
|||||||
ICON_RESTART,
|
ICON_RESTART,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import CONF_DEBUG_ID, DebugComponent
|
from . import ( # noqa: F401 pylint: disable=unused-import
|
||||||
|
CONF_DEBUG_ID,
|
||||||
|
FILTER_SOURCE_FILES,
|
||||||
|
DebugComponent,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["debug"]
|
DEPENDENCIES = ["debug"]
|
||||||
|
|
||||||
|
|||||||
@@ -193,10 +193,18 @@ void BLEClientBase::log_event_(const char *name) {
|
|||||||
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, name);
|
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLEClientBase::log_gattc_event_(const char *name) {
|
void BLEClientBase::log_gattc_lifecycle_event_(const char *name) {
|
||||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name);
|
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BLEClientBase::log_gattc_data_event_(const char *name) {
|
||||||
|
// Data transfer events are logged at VERBOSE level because logging to UART creates
|
||||||
|
// delays that cause timing issues during time-sensitive BLE operations. This is
|
||||||
|
// especially problematic during pairing or firmware updates which require rapid
|
||||||
|
// writes to many characteristics - the log spam can cause these operations to fail.
|
||||||
|
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name);
|
||||||
|
}
|
||||||
|
|
||||||
void BLEClientBase::log_gattc_warning_(const char *operation, esp_gatt_status_t status) {
|
void BLEClientBase::log_gattc_warning_(const char *operation, esp_gatt_status_t status) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, status);
|
ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, status);
|
||||||
}
|
}
|
||||||
@@ -280,7 +288,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
case ESP_GATTC_OPEN_EVT: {
|
case ESP_GATTC_OPEN_EVT: {
|
||||||
if (!this->check_addr(param->open.remote_bda))
|
if (!this->check_addr(param->open.remote_bda))
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("OPEN");
|
this->log_gattc_lifecycle_event_("OPEN");
|
||||||
// conn_id was already set in ESP_GATTC_CONNECT_EVT
|
// conn_id was already set in ESP_GATTC_CONNECT_EVT
|
||||||
this->service_count_ = 0;
|
this->service_count_ = 0;
|
||||||
|
|
||||||
@@ -331,7 +339,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
case ESP_GATTC_CONNECT_EVT: {
|
case ESP_GATTC_CONNECT_EVT: {
|
||||||
if (!this->check_addr(param->connect.remote_bda))
|
if (!this->check_addr(param->connect.remote_bda))
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("CONNECT");
|
this->log_gattc_lifecycle_event_("CONNECT");
|
||||||
this->conn_id_ = param->connect.conn_id;
|
this->conn_id_ = param->connect.conn_id;
|
||||||
// Start MTU negotiation immediately as recommended by ESP-IDF examples
|
// Start MTU negotiation immediately as recommended by ESP-IDF examples
|
||||||
// (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in
|
// (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in
|
||||||
@@ -376,7 +384,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
case ESP_GATTC_CLOSE_EVT: {
|
case ESP_GATTC_CLOSE_EVT: {
|
||||||
if (this->conn_id_ != param->close.conn_id)
|
if (this->conn_id_ != param->close.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("CLOSE");
|
this->log_gattc_lifecycle_event_("CLOSE");
|
||||||
this->release_services();
|
this->release_services();
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
this->conn_id_ = UNSET_CONN_ID;
|
this->conn_id_ = UNSET_CONN_ID;
|
||||||
@@ -404,7 +412,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||||
if (this->conn_id_ != param->search_cmpl.conn_id)
|
if (this->conn_id_ != param->search_cmpl.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("SEARCH_CMPL");
|
this->log_gattc_lifecycle_event_("SEARCH_CMPL");
|
||||||
// For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
|
// For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
|
||||||
// This balances performance with bandwidth usage after the critical discovery phase
|
// This balances performance with bandwidth usage after the critical discovery phase
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||||
@@ -431,35 +439,35 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
case ESP_GATTC_READ_DESCR_EVT: {
|
case ESP_GATTC_READ_DESCR_EVT: {
|
||||||
if (this->conn_id_ != param->write.conn_id)
|
if (this->conn_id_ != param->write.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("READ_DESCR");
|
this->log_gattc_data_event_("READ_DESCR");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||||
if (this->conn_id_ != param->write.conn_id)
|
if (this->conn_id_ != param->write.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("WRITE_DESCR");
|
this->log_gattc_data_event_("WRITE_DESCR");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||||
if (this->conn_id_ != param->write.conn_id)
|
if (this->conn_id_ != param->write.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("WRITE_CHAR");
|
this->log_gattc_data_event_("WRITE_CHAR");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_READ_CHAR_EVT: {
|
case ESP_GATTC_READ_CHAR_EVT: {
|
||||||
if (this->conn_id_ != param->read.conn_id)
|
if (this->conn_id_ != param->read.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("READ_CHAR");
|
this->log_gattc_data_event_("READ_CHAR");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_NOTIFY_EVT: {
|
case ESP_GATTC_NOTIFY_EVT: {
|
||||||
if (this->conn_id_ != param->notify.conn_id)
|
if (this->conn_id_ != param->notify.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("NOTIFY");
|
this->log_gattc_data_event_("NOTIFY");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||||
this->log_gattc_event_("REG_FOR_NOTIFY");
|
this->log_gattc_data_event_("REG_FOR_NOTIFY");
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||||
// Client is responsible for flipping the descriptor value
|
// Client is responsible for flipping the descriptor value
|
||||||
@@ -491,7 +499,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
esp_err_t status =
|
esp_err_t status =
|
||||||
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
|
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
|
||||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||||
ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
|
ESP_LOGV(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
|
||||||
if (status) {
|
if (status) {
|
||||||
this->log_gattc_warning_("esp_ble_gattc_write_char_descr", status);
|
this->log_gattc_warning_("esp_ble_gattc_write_char_descr", status);
|
||||||
}
|
}
|
||||||
@@ -499,13 +507,13 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||||
this->log_gattc_event_("UNREG_FOR_NOTIFY");
|
this->log_gattc_data_event_("UNREG_FOR_NOTIFY");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// ideally would check all other events for matching conn_id
|
// Unknown events logged at VERBOSE to avoid UART delays during time-sensitive operations
|
||||||
ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event);
|
ESP_LOGV(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -127,7 +127,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
// 6 bytes used, 2 bytes padding
|
// 6 bytes used, 2 bytes padding
|
||||||
|
|
||||||
void log_event_(const char *name);
|
void log_event_(const char *name);
|
||||||
void log_gattc_event_(const char *name);
|
void log_gattc_lifecycle_event_(const char *name);
|
||||||
|
void log_gattc_data_event_(const char *name);
|
||||||
void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
||||||
const char *param_type);
|
const char *param_type);
|
||||||
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
||||||
|
|||||||
@@ -294,8 +294,7 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stream firmware to coprocessor while computing SHA256
|
// Stream firmware to coprocessor while computing SHA256
|
||||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
sha256::SHA256 hasher;
|
||||||
alignas(32) sha256::SHA256 hasher;
|
|
||||||
hasher.init();
|
hasher.init();
|
||||||
|
|
||||||
uint8_t buffer[CHUNK_SIZE];
|
uint8_t buffer[CHUNK_SIZE];
|
||||||
@@ -352,8 +351,7 @@ bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify SHA256 before writing
|
// Verify SHA256 before writing
|
||||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
sha256::SHA256 hasher;
|
||||||
alignas(32) sha256::SHA256 hasher;
|
|
||||||
hasher.init();
|
hasher.init();
|
||||||
hasher.add(this->firmware_data_, this->firmware_size_);
|
hasher.add(this->firmware_data_, this->firmware_size_);
|
||||||
hasher.calculate();
|
hasher.calculate();
|
||||||
|
|||||||
@@ -563,11 +563,9 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
|
|||||||
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
|
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
|
||||||
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
|
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
|
||||||
|
|
||||||
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
// CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
||||||
// (no passing to other functions). All hash operations must happen in this function.
|
// (no passing to other functions). All hash operations must happen in this function.
|
||||||
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
|
sha256::SHA256 hasher;
|
||||||
// hardware SHA acceleration DMA operations.
|
|
||||||
alignas(32) sha256::SHA256 hasher;
|
|
||||||
|
|
||||||
const size_t hex_size = hasher.get_size() * 2;
|
const size_t hex_size = hasher.get_size() * 2;
|
||||||
const size_t nonce_len = hasher.get_size() / 4;
|
const size_t nonce_len = hasher.get_size() / 4;
|
||||||
@@ -639,11 +637,9 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
|
|||||||
const char *cnonce = nonce + hex_size;
|
const char *cnonce = nonce + hex_size;
|
||||||
const char *response = cnonce + hex_size;
|
const char *response = cnonce + hex_size;
|
||||||
|
|
||||||
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
// CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
||||||
// (no passing to other functions). All hash operations must happen in this function.
|
// (no passing to other functions). All hash operations must happen in this function.
|
||||||
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
|
sha256::SHA256 hasher;
|
||||||
// hardware SHA acceleration DMA operations.
|
|
||||||
alignas(32) sha256::SHA256 hasher;
|
|
||||||
|
|
||||||
hasher.init();
|
hasher.init();
|
||||||
hasher.add(this->password_.c_str(), this->password_.length());
|
hasher.add(this->password_.c_str(), this->password_.length());
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ void Event::trigger(const std::string &event_type) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->last_event_type_ = found;
|
this->last_event_type_ = found;
|
||||||
ESP_LOGD(TAG, "'%s' Triggered event '%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(event_type);
|
||||||
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_event(this);
|
ControllerRegistry::notify_event(this);
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ void EZOSensor::loop() {
|
|||||||
this->commands_.pop_front();
|
this->commands_.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms) {
|
void EZOSensor::add_command_(const std::string &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,17 +169,13 @@ void EZOSensor::add_command_(const char *command, EzoCommandType command_type, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
|
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
|
||||||
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
|
std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
|
||||||
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) {
|
||||||
// max 8: "I2C,"(4) + uint8(3) + null
|
std::string payload = str_sprintf("I2C,%u", address);
|
||||||
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 {
|
||||||
@@ -198,9 +194,7 @@ 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) {
|
||||||
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
|
std::string payload = str_sprintf("T,%0.2f", value);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,9 +215,7 @@ void EZOSensor::set_calibration_point_high(float value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::set_calibration_generic(float value) {
|
void EZOSensor::set_calibration_generic(float value) {
|
||||||
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
|
std::string payload = str_sprintf("Cal,%0.2f", value);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,11 +223,13 @@ 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) { this->add_command_(on ? "L,1" : "L,0", EzoCommandType::EZO_LED); }
|
void EZOSensor::set_led_state(bool on) {
|
||||||
|
std::string to_send = "L,";
|
||||||
void EZOSensor::send_custom(const std::string &to_send) {
|
to_send += on ? "1" : "0";
|
||||||
this->add_command_(to_send.c_str(), EzoCommandType::EZO_CUSTOM);
|
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); }
|
||||||
|
|
||||||
} // 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 char *command, EzoCommandType command_type, uint16_t delay_ms = 300);
|
void add_command_(const std::string &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);
|
||||||
|
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ void Fan::publish_state() {
|
|||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
|
|
||||||
ESP_LOGD(TAG,
|
ESP_LOGD(TAG,
|
||||||
"'%s' - Sending state:\n"
|
"'%s' >>\n"
|
||||||
" State: %s",
|
" State: %s",
|
||||||
this->name_.c_str(), ONOFF(this->state));
|
this->name_.c_str(), ONOFF(this->state));
|
||||||
if (traits.supports_speed()) {
|
if (traits.supports_speed()) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "hmac_sha256.h"
|
#include "hmac_sha256.h"
|
||||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
||||||
@@ -26,9 +25,7 @@ void HmacSHA256::calculate() { mbedtls_md_hmac_finish(&this->ctx_, this->digest_
|
|||||||
void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); }
|
void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); }
|
||||||
|
|
||||||
void HmacSHA256::get_hex(char *output) {
|
void HmacSHA256::get_hex(char *output) {
|
||||||
for (size_t i = 0; i < SHA256_DIGEST_SIZE; i++) {
|
format_hex_to(output, SHA256_DIGEST_SIZE * 2 + 1, this->digest_, SHA256_DIGEST_SIZE);
|
||||||
sprintf(output + (i * 2), "%02x", this->digest_[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HmacSHA256::equals_bytes(const uint8_t *expected) {
|
bool HmacSHA256::equals_bytes(const uint8_t *expected) {
|
||||||
|
|||||||
@@ -242,9 +242,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t content_length = container->content_length;
|
size_t max_length = this->max_response_buffer_size_;
|
||||||
size_t max_length = std::min(content_length, this->max_response_buffer_size_);
|
|
||||||
|
|
||||||
#ifdef USE_HTTP_REQUEST_RESPONSE
|
#ifdef USE_HTTP_REQUEST_RESPONSE
|
||||||
if (this->capture_response_.value(x...)) {
|
if (this->capture_response_.value(x...)) {
|
||||||
std::string response_body;
|
std::string response_body;
|
||||||
|
|||||||
@@ -213,18 +213,12 @@ 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());
|
||||||
|
|
||||||
int bufsize = std::min(max_len, this->content_length - this->bytes_read_);
|
this->feed_wdt();
|
||||||
|
int read_len = esp_http_client_read(this->client_, (char *) buf, max_len);
|
||||||
if (bufsize == 0) {
|
this->feed_wdt();
|
||||||
this->duration_ms += (millis() - start);
|
if (read_len > 0) {
|
||||||
return 0;
|
this->bytes_read_ += read_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->feed_wdt();
|
|
||||||
int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
|
|
||||||
this->feed_wdt();
|
|
||||||
this->bytes_read_ += read_len;
|
|
||||||
|
|
||||||
this->duration_ms += (millis() - start);
|
this->duration_ms += (millis() - start);
|
||||||
|
|
||||||
return read_len;
|
return read_len;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from esphome import automation, pins
|
from esphome import automation, pins
|
||||||
@@ -18,13 +19,16 @@ from esphome.const import (
|
|||||||
CONF_ROTATION,
|
CONF_ROTATION,
|
||||||
CONF_UPDATE_INTERVAL,
|
CONF_UPDATE_INTERVAL,
|
||||||
)
|
)
|
||||||
from esphome.core import ID
|
from esphome.core import ID, EnumValue
|
||||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
|
from esphome.helpers import add_class_to_obj
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
from . import boards, hub75_ns
|
from . import boards, hub75_ns
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
CODEOWNERS = ["@stuartparmenter"]
|
CODEOWNERS = ["@stuartparmenter"]
|
||||||
|
|
||||||
@@ -120,13 +124,51 @@ PANEL_LAYOUTS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Hub75ScanWiring = cg.global_ns.enum("Hub75ScanWiring", is_class=True)
|
Hub75ScanWiring = cg.global_ns.enum("Hub75ScanWiring", is_class=True)
|
||||||
SCAN_PATTERNS = {
|
SCAN_WIRINGS = {
|
||||||
"STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN,
|
"STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN,
|
||||||
"FOUR_SCAN_16PX_HIGH": Hub75ScanWiring.FOUR_SCAN_16PX_HIGH,
|
"SCAN_1_4_16PX_HIGH": Hub75ScanWiring.SCAN_1_4_16PX_HIGH,
|
||||||
"FOUR_SCAN_32PX_HIGH": Hub75ScanWiring.FOUR_SCAN_32PX_HIGH,
|
"SCAN_1_8_32PX_HIGH": Hub75ScanWiring.SCAN_1_8_32PX_HIGH,
|
||||||
"FOUR_SCAN_64PX_HIGH": Hub75ScanWiring.FOUR_SCAN_64PX_HIGH,
|
"SCAN_1_8_40PX_HIGH": Hub75ScanWiring.SCAN_1_8_40PX_HIGH,
|
||||||
|
"SCAN_1_8_64PX_HIGH": Hub75ScanWiring.SCAN_1_8_64PX_HIGH,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Deprecated scan wiring names - mapped to new names
|
||||||
|
DEPRECATED_SCAN_WIRINGS = {
|
||||||
|
"FOUR_SCAN_16PX_HIGH": "SCAN_1_4_16PX_HIGH",
|
||||||
|
"FOUR_SCAN_32PX_HIGH": "SCAN_1_8_32PX_HIGH",
|
||||||
|
"FOUR_SCAN_64PX_HIGH": "SCAN_1_8_64PX_HIGH",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_scan_wiring(value):
|
||||||
|
"""Validate scan_wiring with deprecation warnings for old names."""
|
||||||
|
value = cv.string(value).upper().replace(" ", "_")
|
||||||
|
|
||||||
|
# Check if using deprecated name
|
||||||
|
# Remove deprecated names in 2026.7.0
|
||||||
|
if value in DEPRECATED_SCAN_WIRINGS:
|
||||||
|
new_name = DEPRECATED_SCAN_WIRINGS[value]
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Scan wiring '%s' is deprecated and will be removed in ESPHome 2026.7.0. "
|
||||||
|
"Please use '%s' instead.",
|
||||||
|
value,
|
||||||
|
new_name,
|
||||||
|
)
|
||||||
|
value = new_name
|
||||||
|
|
||||||
|
# Validate against allowed values
|
||||||
|
if value not in SCAN_WIRINGS:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Unknown scan wiring '{value}'. "
|
||||||
|
f"Valid options are: {', '.join(sorted(SCAN_WIRINGS.keys()))}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return as EnumValue like cv.enum does
|
||||||
|
result = add_class_to_obj(value, EnumValue)
|
||||||
|
result.enum_value = SCAN_WIRINGS[value]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True)
|
Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True)
|
||||||
CLOCK_SPEEDS = {
|
CLOCK_SPEEDS = {
|
||||||
"8MHZ": Hub75ClockSpeed.HZ_8M,
|
"8MHZ": Hub75ClockSpeed.HZ_8M,
|
||||||
@@ -382,9 +424,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_LAYOUT_COLS): cv.positive_int,
|
cv.Optional(CONF_LAYOUT_COLS): cv.positive_int,
|
||||||
cv.Optional(CONF_LAYOUT): cv.enum(PANEL_LAYOUTS, upper=True, space="_"),
|
cv.Optional(CONF_LAYOUT): cv.enum(PANEL_LAYOUTS, upper=True, space="_"),
|
||||||
# Panel hardware configuration
|
# Panel hardware configuration
|
||||||
cv.Optional(CONF_SCAN_WIRING): cv.enum(
|
cv.Optional(CONF_SCAN_WIRING): _validate_scan_wiring,
|
||||||
SCAN_PATTERNS, upper=True, space="_"
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_SHIFT_DRIVER): cv.enum(SHIFT_DRIVERS, upper=True),
|
cv.Optional(CONF_SHIFT_DRIVER): cv.enum(SHIFT_DRIVERS, upper=True),
|
||||||
# Display configuration
|
# Display configuration
|
||||||
cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean,
|
cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean,
|
||||||
@@ -547,7 +587,7 @@ def _build_config_struct(
|
|||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
add_idf_component(
|
add_idf_component(
|
||||||
name="esphome/esp-hub75",
|
name="esphome/esp-hub75",
|
||||||
ref="0.2.2",
|
ref="0.3.0",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set compile-time configuration via build flags (so external library sees them)
|
# Set compile-time configuration via build flags (so external library sees them)
|
||||||
|
|||||||
@@ -665,15 +665,10 @@ async def write_image(config, all_frames=False):
|
|||||||
if is_svg_file(path):
|
if is_svg_file(path):
|
||||||
import resvg_py
|
import resvg_py
|
||||||
|
|
||||||
if resize:
|
resize = resize or (None, None)
|
||||||
width, height = resize
|
image_data = resvg_py.svg_to_bytes(
|
||||||
# resvg-py allows rendering by width/height directly
|
svg_path=str(path), width=resize[0], height=resize[1], dpi=100
|
||||||
image_data = resvg_py.svg_to_bytes(
|
)
|
||||||
svg_path=str(path), width=int(width), height=int(height)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Default size
|
|
||||||
image_data = resvg_py.svg_to_bytes(svg_path=str(path))
|
|
||||||
|
|
||||||
# Convert bytes to Pillow Image
|
# Convert bytes to Pillow Image
|
||||||
image = Image.open(io.BytesIO(image_data))
|
image = Image.open(io.BytesIO(image_data))
|
||||||
|
|||||||
@@ -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,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ void Lock::publish_state(LockState state) {
|
|||||||
|
|
||||||
this->state = state;
|
this->state = state;
|
||||||
this->rtc_.save(&this->state);
|
this->rtc_.save(&this->state);
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), LOG_STR_ARG(lock_state_to_string(state)));
|
ESP_LOGD(TAG, "'%s' >> %s", this->name_.c_str(), LOG_STR_ARG(lock_state_to_string(state)));
|
||||||
this->state_callback_.call();
|
this->state_callback_.call();
|
||||||
#if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_lock_update(this);
|
ControllerRegistry::notify_lock_update(this);
|
||||||
|
|||||||
@@ -413,6 +413,7 @@ class TextValidator(LValidator):
|
|||||||
str_args = [str(x) for x in value[CONF_ARGS]]
|
str_args = [str(x) for x in value[CONF_ARGS]]
|
||||||
arg_expr = cg.RawExpression(",".join(str_args))
|
arg_expr = cg.RawExpression(",".join(str_args))
|
||||||
format_str = cpp_string_escape(format_str)
|
format_str = cpp_string_escape(format_str)
|
||||||
|
# str_sprintf justified: user-defined format, can't optimize without permanent RAM cost
|
||||||
sprintf_str = f"str_sprintf({format_str}, {arg_expr}).c_str()"
|
sprintf_str = f"str_sprintf({format_str}, {arg_expr}).c_str()"
|
||||||
if nanval := value.get(CONF_IF_NAN):
|
if nanval := value.get(CONF_IF_NAN):
|
||||||
nanval = cpp_string_escape(nanval)
|
nanval = cpp_string_escape(nanval)
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ std::string lv_event_code_name_for(uint8_t event_code) {
|
|||||||
if (event_code < sizeof(EVENT_NAMES) / sizeof(EVENT_NAMES[0])) {
|
if (event_code < sizeof(EVENT_NAMES) / sizeof(EVENT_NAMES[0])) {
|
||||||
return EVENT_NAMES[event_code];
|
return EVENT_NAMES[event_code];
|
||||||
}
|
}
|
||||||
return str_sprintf("%2d", event_code);
|
// max 4 bytes: "%u" with uint8_t (max 255, 3 digits) + null
|
||||||
|
char buf[4];
|
||||||
|
snprintf(buf, sizeof(buf), "%u", event_code);
|
||||||
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
|
static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
|
||||||
|
|||||||
@@ -101,4 +101,225 @@ DriverChip(
|
|||||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x00),
|
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x00),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# jc8012P4A1 Driver Configuration (jd9365)
|
||||||
|
# Using parameters from esp_lcd_jd9365.h and the working full init sequence
|
||||||
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
|
# * Resolution: 800x1280
|
||||||
|
# * PCLK Frequency: 60 MHz
|
||||||
|
# * DSI Lane Bit Rate: 1 Gbps (using 2-Lane DSI configuration)
|
||||||
|
# * Horizontal Timing (hsync_pulse_width=20, hsync_back_porch=20, hsync_front_porch=40)
|
||||||
|
# * Vertical Timing (vsync_pulse_width=4, vsync_back_porch=8, vsync_front_porch=20)
|
||||||
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
|
DriverChip(
|
||||||
|
"JC8012P4A1",
|
||||||
|
width=800,
|
||||||
|
height=1280,
|
||||||
|
hsync_back_porch=20,
|
||||||
|
hsync_pulse_width=20,
|
||||||
|
hsync_front_porch=40,
|
||||||
|
vsync_back_porch=8,
|
||||||
|
vsync_pulse_width=4,
|
||||||
|
vsync_front_porch=20,
|
||||||
|
pclk_frequency="60MHz",
|
||||||
|
lane_bit_rate="1Gbps",
|
||||||
|
swap_xy=cv.UNDEFINED,
|
||||||
|
color_order="RGB",
|
||||||
|
reset_pin=27,
|
||||||
|
initsequence=[
|
||||||
|
(0xE0, 0x00),
|
||||||
|
(0xE1, 0x93),
|
||||||
|
(0xE2, 0x65),
|
||||||
|
(0xE3, 0xF8),
|
||||||
|
(0x80, 0x01),
|
||||||
|
(0xE0, 0x01),
|
||||||
|
(0x00, 0x00),
|
||||||
|
(0x01, 0x39),
|
||||||
|
(0x03, 0x10),
|
||||||
|
(0x04, 0x41),
|
||||||
|
(0x0C, 0x74),
|
||||||
|
(0x17, 0x00),
|
||||||
|
(0x18, 0xD7),
|
||||||
|
(0x19, 0x00),
|
||||||
|
(0x1A, 0x00),
|
||||||
|
(0x1B, 0xD7),
|
||||||
|
(0x1C, 0x00),
|
||||||
|
(0x24, 0xFE),
|
||||||
|
(0x35, 0x26),
|
||||||
|
(0x37, 0x69),
|
||||||
|
(0x38, 0x05),
|
||||||
|
(0x39, 0x06),
|
||||||
|
(0x3A, 0x08),
|
||||||
|
(0x3C, 0x78),
|
||||||
|
(0x3D, 0xFF),
|
||||||
|
(0x3E, 0xFF),
|
||||||
|
(0x3F, 0xFF),
|
||||||
|
(0x40, 0x06),
|
||||||
|
(0x41, 0xA0),
|
||||||
|
(0x43, 0x14),
|
||||||
|
(0x44, 0x0B),
|
||||||
|
(0x45, 0x30),
|
||||||
|
(0x4B, 0x04),
|
||||||
|
(0x55, 0x02),
|
||||||
|
(0x57, 0x89),
|
||||||
|
(0x59, 0x0A),
|
||||||
|
(0x5A, 0x28),
|
||||||
|
(0x5B, 0x15),
|
||||||
|
(0x5D, 0x50),
|
||||||
|
(0x5E, 0x37),
|
||||||
|
(0x5F, 0x29),
|
||||||
|
(0x60, 0x1E),
|
||||||
|
(0x61, 0x1D),
|
||||||
|
(0x62, 0x12),
|
||||||
|
(0x63, 0x1A),
|
||||||
|
(0x64, 0x08),
|
||||||
|
(0x65, 0x25),
|
||||||
|
(0x66, 0x26),
|
||||||
|
(0x67, 0x28),
|
||||||
|
(0x68, 0x49),
|
||||||
|
(0x69, 0x3A),
|
||||||
|
(0x6A, 0x43),
|
||||||
|
(0x6B, 0x3A),
|
||||||
|
(0x6C, 0x3B),
|
||||||
|
(0x6D, 0x32),
|
||||||
|
(0x6E, 0x1F),
|
||||||
|
(0x6F, 0x0E),
|
||||||
|
(0x70, 0x50),
|
||||||
|
(0x71, 0x37),
|
||||||
|
(0x72, 0x29),
|
||||||
|
(0x73, 0x1E),
|
||||||
|
(0x74, 0x1D),
|
||||||
|
(0x75, 0x12),
|
||||||
|
(0x76, 0x1A),
|
||||||
|
(0x77, 0x08),
|
||||||
|
(0x78, 0x25),
|
||||||
|
(0x79, 0x26),
|
||||||
|
(0x7A, 0x28),
|
||||||
|
(0x7B, 0x49),
|
||||||
|
(0x7C, 0x3A),
|
||||||
|
(0x7D, 0x43),
|
||||||
|
(0x7E, 0x3A),
|
||||||
|
(0x7F, 0x3B),
|
||||||
|
(0x80, 0x32),
|
||||||
|
(0x81, 0x1F),
|
||||||
|
(0x82, 0x0E),
|
||||||
|
(0xE0, 0x02),
|
||||||
|
(0x00, 0x1F),
|
||||||
|
(0x01, 0x1F),
|
||||||
|
(0x02, 0x52),
|
||||||
|
(0x03, 0x51),
|
||||||
|
(0x04, 0x50),
|
||||||
|
(0x05, 0x4B),
|
||||||
|
(0x06, 0x4A),
|
||||||
|
(0x07, 0x49),
|
||||||
|
(0x08, 0x48),
|
||||||
|
(0x09, 0x47),
|
||||||
|
(0x0A, 0x46),
|
||||||
|
(0x0B, 0x45),
|
||||||
|
(0x0C, 0x44),
|
||||||
|
(0x0D, 0x40),
|
||||||
|
(0x0E, 0x41),
|
||||||
|
(0x0F, 0x1F),
|
||||||
|
(0x10, 0x1F),
|
||||||
|
(0x11, 0x1F),
|
||||||
|
(0x12, 0x1F),
|
||||||
|
(0x13, 0x1F),
|
||||||
|
(0x14, 0x1F),
|
||||||
|
(0x15, 0x1F),
|
||||||
|
(0x16, 0x1F),
|
||||||
|
(0x17, 0x1F),
|
||||||
|
(0x18, 0x52),
|
||||||
|
(0x19, 0x51),
|
||||||
|
(0x1A, 0x50),
|
||||||
|
(0x1B, 0x4B),
|
||||||
|
(0x1C, 0x4A),
|
||||||
|
(0x1D, 0x49),
|
||||||
|
(0x1E, 0x48),
|
||||||
|
(0x1F, 0x47),
|
||||||
|
(0x20, 0x46),
|
||||||
|
(0x21, 0x45),
|
||||||
|
(0x22, 0x44),
|
||||||
|
(0x23, 0x40),
|
||||||
|
(0x24, 0x41),
|
||||||
|
(0x25, 0x1F),
|
||||||
|
(0x26, 0x1F),
|
||||||
|
(0x27, 0x1F),
|
||||||
|
(0x28, 0x1F),
|
||||||
|
(0x29, 0x1F),
|
||||||
|
(0x2A, 0x1F),
|
||||||
|
(0x2B, 0x1F),
|
||||||
|
(0x2C, 0x1F),
|
||||||
|
(0x2D, 0x1F),
|
||||||
|
(0x2E, 0x52),
|
||||||
|
(0x2F, 0x40),
|
||||||
|
(0x30, 0x41),
|
||||||
|
(0x31, 0x48),
|
||||||
|
(0x32, 0x49),
|
||||||
|
(0x33, 0x4A),
|
||||||
|
(0x34, 0x4B),
|
||||||
|
(0x35, 0x44),
|
||||||
|
(0x36, 0x45),
|
||||||
|
(0x37, 0x46),
|
||||||
|
(0x38, 0x47),
|
||||||
|
(0x39, 0x51),
|
||||||
|
(0x3A, 0x50),
|
||||||
|
(0x3B, 0x1F),
|
||||||
|
(0x3C, 0x1F),
|
||||||
|
(0x3D, 0x1F),
|
||||||
|
(0x3E, 0x1F),
|
||||||
|
(0x3F, 0x1F),
|
||||||
|
(0x40, 0x1F),
|
||||||
|
(0x41, 0x1F),
|
||||||
|
(0x42, 0x1F),
|
||||||
|
(0x43, 0x1F),
|
||||||
|
(0x44, 0x52),
|
||||||
|
(0x45, 0x40),
|
||||||
|
(0x46, 0x41),
|
||||||
|
(0x47, 0x48),
|
||||||
|
(0x48, 0x49),
|
||||||
|
(0x49, 0x4A),
|
||||||
|
(0x4A, 0x4B),
|
||||||
|
(0x4B, 0x44),
|
||||||
|
(0x4C, 0x45),
|
||||||
|
(0x4D, 0x46),
|
||||||
|
(0x4E, 0x47),
|
||||||
|
(0x4F, 0x51),
|
||||||
|
(0x50, 0x50),
|
||||||
|
(0x51, 0x1F),
|
||||||
|
(0x52, 0x1F),
|
||||||
|
(0x53, 0x1F),
|
||||||
|
(0x54, 0x1F),
|
||||||
|
(0x55, 0x1F),
|
||||||
|
(0x56, 0x1F),
|
||||||
|
(0x57, 0x1F),
|
||||||
|
(0x58, 0x40),
|
||||||
|
(0x59, 0x00),
|
||||||
|
(0x5A, 0x00),
|
||||||
|
(0x5B, 0x10),
|
||||||
|
(0x5C, 0x05),
|
||||||
|
(0x5D, 0x50),
|
||||||
|
(0x5E, 0x01),
|
||||||
|
(0x5F, 0x02),
|
||||||
|
(0x60, 0x50),
|
||||||
|
(0x61, 0x06),
|
||||||
|
(0x62, 0x04),
|
||||||
|
(0x63, 0x03),
|
||||||
|
(0x64, 0x64),
|
||||||
|
(0x65, 0x65),
|
||||||
|
(0x66, 0x0B),
|
||||||
|
(0x67, 0x73),
|
||||||
|
(0x68, 0x07),
|
||||||
|
(0x69, 0x06),
|
||||||
|
(0x6A, 0x64),
|
||||||
|
(0x6B, 0x08),
|
||||||
|
(0x6C, 0x00),
|
||||||
|
(0x6D, 0x32),
|
||||||
|
(0x6E, 0x08),
|
||||||
|
(0xE0, 0x04),
|
||||||
|
(0x2C, 0x6B),
|
||||||
|
(0x35, 0x08),
|
||||||
|
(0x37, 0x00),
|
||||||
|
(0xE0, 0x00),
|
||||||
|
]
|
||||||
|
)
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|||||||
@@ -271,24 +271,31 @@ class ServerRegister {
|
|||||||
|
|
||||||
// Formats a raw value into a string representation based on the value type for debugging
|
// Formats a raw value into a string representation based on the value type for debugging
|
||||||
std::string format_value(int64_t value) const {
|
std::string format_value(int64_t value) const {
|
||||||
|
// max 44: float with %.1f can be up to 42 chars (3.4e38 → 39 integer digits + sign + decimal + 1 digit)
|
||||||
|
// plus null terminator = 43, rounded to 44 for 4-byte alignment
|
||||||
|
char buf[44];
|
||||||
switch (this->value_type) {
|
switch (this->value_type) {
|
||||||
case SensorValueType::U_WORD:
|
case SensorValueType::U_WORD:
|
||||||
case SensorValueType::U_DWORD:
|
case SensorValueType::U_DWORD:
|
||||||
case SensorValueType::U_DWORD_R:
|
case SensorValueType::U_DWORD_R:
|
||||||
case SensorValueType::U_QWORD:
|
case SensorValueType::U_QWORD:
|
||||||
case SensorValueType::U_QWORD_R:
|
case SensorValueType::U_QWORD_R:
|
||||||
return std::to_string(static_cast<uint64_t>(value));
|
buf_append_printf(buf, sizeof(buf), 0, "%" PRIu64, static_cast<uint64_t>(value));
|
||||||
|
return buf;
|
||||||
case SensorValueType::S_WORD:
|
case SensorValueType::S_WORD:
|
||||||
case SensorValueType::S_DWORD:
|
case SensorValueType::S_DWORD:
|
||||||
case SensorValueType::S_DWORD_R:
|
case SensorValueType::S_DWORD_R:
|
||||||
case SensorValueType::S_QWORD:
|
case SensorValueType::S_QWORD:
|
||||||
case SensorValueType::S_QWORD_R:
|
case SensorValueType::S_QWORD_R:
|
||||||
return std::to_string(value);
|
buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, value);
|
||||||
|
return buf;
|
||||||
case SensorValueType::FP32_R:
|
case SensorValueType::FP32_R:
|
||||||
case SensorValueType::FP32:
|
case SensorValueType::FP32:
|
||||||
return str_sprintf("%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
|
buf_append_printf(buf, sizeof(buf), 0, "%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
|
||||||
|
return buf;
|
||||||
default:
|
default:
|
||||||
return std::to_string(value);
|
buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, value);
|
||||||
|
return buf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,20 @@ void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
|
|||||||
while ((items_left > 0) && index < data.size()) {
|
while ((items_left > 0) && index < data.size()) {
|
||||||
uint8_t b = data[index];
|
uint8_t b = data[index];
|
||||||
switch (this->encode_) {
|
switch (this->encode_) {
|
||||||
case RawEncoding::HEXBYTES:
|
case RawEncoding::HEXBYTES: {
|
||||||
output_str += str_snprintf("%02x", 2, b);
|
// max 3: 2 hex digits + null
|
||||||
|
char hex_buf[3];
|
||||||
|
snprintf(hex_buf, sizeof(hex_buf), "%02x", b);
|
||||||
|
output_str += hex_buf;
|
||||||
break;
|
break;
|
||||||
case RawEncoding::COMMA:
|
}
|
||||||
output_str += str_sprintf(index != this->offset ? ",%d" : "%d", b);
|
case RawEncoding::COMMA: {
|
||||||
|
// max 5: optional ','(1) + uint8(3) + null, for both ",%d" and "%d"
|
||||||
|
char dec_buf[5];
|
||||||
|
snprintf(dec_buf, sizeof(dec_buf), index != this->offset ? ",%d" : "%d", b);
|
||||||
|
output_str += dec_buf;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case RawEncoding::ANSI:
|
case RawEncoding::ANSI:
|
||||||
if (b < 0x20)
|
if (b < 0x20)
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -189,8 +189,7 @@ bool MQTTComponent::send_discovery_() {
|
|||||||
StringRef object_id = this->get_default_object_id_to_(object_id_buf);
|
StringRef object_id = this->get_default_object_id_to_(object_id_buf);
|
||||||
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
||||||
char friendly_name_hash[9];
|
char friendly_name_hash[9];
|
||||||
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name_()));
|
snprintf(friendly_name_hash, sizeof(friendly_name_hash), "%08" PRIx32, fnv1_hash(this->friendly_name_()));
|
||||||
friendly_name_hash[8] = 0; // ensure the hash-string ends with null
|
|
||||||
// Format: mac-component_type-hash (e.g. "aabbccddeeff-sensor-12345678")
|
// Format: mac-component_type-hash (e.g. "aabbccddeeff-sensor-12345678")
|
||||||
// MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43
|
// MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43
|
||||||
char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11];
|
char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11];
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ namespace network {
|
|||||||
/// Buffer size for IP address string (IPv6 max: 39 chars + null)
|
/// Buffer size for IP address string (IPv6 max: 39 chars + null)
|
||||||
static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40;
|
static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40;
|
||||||
|
|
||||||
|
/// Lowercase hex digits in IP address string (A-F -> a-f for IPv6 per RFC 5952)
|
||||||
|
inline void lowercase_ip_str(char *buf) {
|
||||||
|
for (char *p = buf; *p; ++p) {
|
||||||
|
if (*p >= 'A' && *p <= 'F')
|
||||||
|
*p += 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct IPAddress {
|
struct IPAddress {
|
||||||
public:
|
public:
|
||||||
#ifdef USE_HOST
|
#ifdef USE_HOST
|
||||||
@@ -52,10 +60,15 @@ struct IPAddress {
|
|||||||
}
|
}
|
||||||
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
|
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
|
||||||
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
|
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
|
||||||
std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); }
|
std::string str() const {
|
||||||
|
char buf[IP_ADDRESS_BUFFER_SIZE];
|
||||||
|
this->str_to(buf);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
||||||
char *str_to(char *buf) const {
|
char *str_to(char *buf) const {
|
||||||
return const_cast<char *>(inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE));
|
inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE);
|
||||||
|
return buf; // IPv4 only, no hex letters to lowercase
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
IPAddress() { ip_addr_set_zero(&ip_addr_); }
|
IPAddress() { ip_addr_set_zero(&ip_addr_); }
|
||||||
@@ -134,9 +147,18 @@ struct IPAddress {
|
|||||||
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
|
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
|
||||||
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
|
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
|
||||||
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
|
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
|
||||||
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
|
std::string str() const {
|
||||||
|
char buf[IP_ADDRESS_BUFFER_SIZE];
|
||||||
|
this->str_to(buf);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
||||||
char *str_to(char *buf) const { return ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE); }
|
/// Output is lowercased per RFC 5952 (IPv6 hex digits a-f).
|
||||||
|
char *str_to(char *buf) const {
|
||||||
|
ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE);
|
||||||
|
lowercase_ip_str(buf);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||||
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||||
IPAddress &operator+=(uint8_t increase) {
|
IPAddress &operator+=(uint8_t increase) {
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE, TimePeriod
|
from esphome.core import CORE, TimePeriod
|
||||||
|
|
||||||
from . import Nextion, nextion_ns, nextion_ref
|
from . import ( # noqa: F401 pylint: disable=unused-import
|
||||||
|
FILTER_SOURCE_FILES,
|
||||||
|
Nextion,
|
||||||
|
nextion_ns,
|
||||||
|
nextion_ref,
|
||||||
|
)
|
||||||
from .base_component import (
|
from .base_component import (
|
||||||
CONF_AUTO_WAKE_ON_TOUCH,
|
CONF_AUTO_WAKE_ON_TOUCH,
|
||||||
CONF_COMMAND_SPACING,
|
CONF_COMMAND_SPACING,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ void NTC::process_(float value) {
|
|||||||
double v = this->a_ + this->b_ * lr + this->c_ * lr * lr * lr;
|
double v = this->a_ + this->b_ * lr + this->c_ * lr * lr * lr;
|
||||||
auto temp = float(1.0 / v - 273.15);
|
auto temp = float(1.0 / v - 273.15);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "'%s' - Temperature: %.1f°C", this->name_.c_str(), temp);
|
ESP_LOGV(TAG, "'%s' - Temperature: %.1f°C", this->name_.c_str(), temp);
|
||||||
this->publish_state(temp);
|
this->publish_state(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ void log_number(const char *tag, const char *prefix, const char *type, Number *o
|
|||||||
void Number::publish_state(float state) {
|
void Number::publish_state(float state) {
|
||||||
this->set_has_state(true);
|
this->set_has_state(true);
|
||||||
this->state = state;
|
this->state = state;
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
|
ESP_LOGD(TAG, "'%s' >> %.2f", this->get_name().c_str(), state);
|
||||||
this->state_callback_.call(state);
|
this->state_callback_.call(state);
|
||||||
#if defined(USE_NUMBER) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_NUMBER) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_number_update(this);
|
ControllerRegistry::notify_number_update(this);
|
||||||
|
|||||||
@@ -561,8 +561,9 @@ const char *OpenTherm::message_id_to_str(MessageId id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OpenTherm::debug_data(OpenthermData &data) {
|
void OpenTherm::debug_data(OpenthermData &data) {
|
||||||
ESP_LOGD(TAG, "%s %s %s %s", format_bin(data.type).c_str(), format_bin(data.id).c_str(),
|
char type_buf[9], id_buf[9], hb_buf[9], lb_buf[9];
|
||||||
format_bin(data.valueHB).c_str(), format_bin(data.valueLB).c_str());
|
ESP_LOGD(TAG, "%s %s %s %s", format_bin_to(type_buf, data.type), format_bin_to(id_buf, data.id),
|
||||||
|
format_bin_to(hb_buf, data.valueHB), format_bin_to(lb_buf, data.valueLB));
|
||||||
ESP_LOGD(TAG, "type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f",
|
ESP_LOGD(TAG, "type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f",
|
||||||
this->message_type_to_str((MessageType) data.type), data.id, data.valueHB, data.valueLB, data.u16(),
|
this->message_type_to_str((MessageType) data.type), data.id, data.valueHB, data.valueLB, data.u16(),
|
||||||
data.f88());
|
data.f88());
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ static const char *const TAG = "pipsolar.output";
|
|||||||
|
|
||||||
void PipsolarOutput::write_state(float state) {
|
void PipsolarOutput::write_state(float state) {
|
||||||
char tmp[10];
|
char tmp[10];
|
||||||
sprintf(tmp, this->set_command_.c_str(), state);
|
snprintf(tmp, sizeof(tmp), this->set_command_, state);
|
||||||
|
|
||||||
if (std::find(this->possible_values_.begin(), this->possible_values_.end(), state) != this->possible_values_.end()) {
|
if (std::find(this->possible_values_.begin(), this->possible_values_.end(), state) != this->possible_values_.end()) {
|
||||||
ESP_LOGD(TAG, "Will write: %s out of value %f / %02.0f", tmp, state, state);
|
ESP_LOGD(TAG, "Will write: %s out of value %f / %02.0f", tmp, state, state);
|
||||||
|
|||||||
@@ -15,13 +15,15 @@ class PipsolarOutput : public output::FloatOutput {
|
|||||||
public:
|
public:
|
||||||
PipsolarOutput() {}
|
PipsolarOutput() {}
|
||||||
void set_parent(Pipsolar *parent) { this->parent_ = parent; }
|
void set_parent(Pipsolar *parent) { this->parent_ = parent; }
|
||||||
void set_set_command(const std::string &command) { this->set_command_ = command; };
|
void set_set_command(const char *command) { this->set_command_ = command; }
|
||||||
|
/// Prevent accidental use of std::string which would dangle
|
||||||
|
void set_set_command(const std::string &command) = delete;
|
||||||
void set_possible_values(std::vector<float> possible_values) { this->possible_values_ = std::move(possible_values); }
|
void set_possible_values(std::vector<float> possible_values) { this->possible_values_ = std::move(possible_values); }
|
||||||
void set_value(float value) { this->write_state(value); };
|
void set_value(float value) { this->write_state(value); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void write_state(float state) override;
|
void write_state(float state) override;
|
||||||
std::string set_command_;
|
const char *set_command_{nullptr};
|
||||||
Pipsolar *parent_;
|
Pipsolar *parent_;
|
||||||
std::vector<float> possible_values_;
|
std::vector<float> possible_values_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,14 +9,9 @@ static const char *const TAG = "pipsolar.switch";
|
|||||||
|
|
||||||
void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); }
|
void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); }
|
||||||
void PipsolarSwitch::write_state(bool state) {
|
void PipsolarSwitch::write_state(bool state) {
|
||||||
if (state) {
|
const char *command = state ? this->on_command_ : this->off_command_;
|
||||||
if (!this->on_command_.empty()) {
|
if (command != nullptr) {
|
||||||
this->parent_->queue_command(this->on_command_);
|
this->parent_->queue_command(command);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!this->off_command_.empty()) {
|
|
||||||
this->parent_->queue_command(this->off_command_);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,15 +9,18 @@ namespace pipsolar {
|
|||||||
class Pipsolar;
|
class Pipsolar;
|
||||||
class PipsolarSwitch : public switch_::Switch, public Component {
|
class PipsolarSwitch : public switch_::Switch, public Component {
|
||||||
public:
|
public:
|
||||||
void set_parent(Pipsolar *parent) { this->parent_ = parent; };
|
void set_parent(Pipsolar *parent) { this->parent_ = parent; }
|
||||||
void set_on_command(const std::string &command) { this->on_command_ = command; };
|
void set_on_command(const char *command) { this->on_command_ = command; }
|
||||||
void set_off_command(const std::string &command) { this->off_command_ = command; };
|
void set_off_command(const char *command) { this->off_command_ = command; }
|
||||||
|
/// Prevent accidental use of std::string which would dangle
|
||||||
|
void set_on_command(const std::string &command) = delete;
|
||||||
|
void set_off_command(const std::string &command) = delete;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void write_state(bool state) override;
|
void write_state(bool state) override;
|
||||||
std::string on_command_;
|
const char *on_command_{nullptr};
|
||||||
std::string off_command_;
|
const char *off_command_{nullptr};
|
||||||
Pipsolar *parent_;
|
Pipsolar *parent_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,16 @@ void QrCode::set_ecc(qrcodegen_Ecc ecc) {
|
|||||||
|
|
||||||
void QrCode::generate_qr_code() {
|
void QrCode::generate_qr_code() {
|
||||||
ESP_LOGV(TAG, "Generating QR code");
|
ESP_LOGV(TAG, "Generating QR code");
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
// ESP32 has 8KB stack, safe to allocate ~4KB buffer on stack
|
||||||
uint8_t tempbuffer[qrcodegen_BUFFER_LEN_MAX];
|
uint8_t tempbuffer[qrcodegen_BUFFER_LEN_MAX];
|
||||||
|
#else
|
||||||
|
// Other platforms (ESP8266: 4KB, RP2040: 2KB, LibreTiny: ~4KB) have smaller stacks
|
||||||
|
// Allocate buffer on heap to avoid stack overflow
|
||||||
|
auto tempbuffer_owner = std::make_unique<uint8_t[]>(qrcodegen_BUFFER_LEN_MAX);
|
||||||
|
uint8_t *tempbuffer = tempbuffer_owner.get();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!qrcodegen_encodeText(this->value_.c_str(), tempbuffer, this->qr_, this->ecc_, qrcodegen_VERSION_MIN,
|
if (!qrcodegen_encodeText(this->value_.c_str(), tempbuffer, this->qr_, this->ecc_, qrcodegen_VERSION_MIN,
|
||||||
qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true)) {
|
qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true)) {
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ optional<AEHAData> AEHAProtocol::decode(RemoteReceiveData src) {
|
|||||||
std::string AEHAProtocol::format_data_(const std::vector<uint8_t> &data) {
|
std::string AEHAProtocol::format_data_(const std::vector<uint8_t> &data) {
|
||||||
std::string out;
|
std::string out;
|
||||||
for (uint8_t byte : data) {
|
for (uint8_t byte : data) {
|
||||||
char buf[6];
|
char buf[8]; // "0x%02X," = 5 chars + null + margin
|
||||||
sprintf(buf, "0x%02X,", byte);
|
snprintf(buf, sizeof(buf), "0x%02X,", byte);
|
||||||
out += buf;
|
out += buf;
|
||||||
}
|
}
|
||||||
out.pop_back();
|
out.pop_back();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "raw_protocol.h"
|
#include "raw_protocol.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -8,36 +9,30 @@ static const char *const TAG = "remote.raw";
|
|||||||
|
|
||||||
bool RawDumper::dump(RemoteReceiveData src) {
|
bool RawDumper::dump(RemoteReceiveData src) {
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
uint32_t buffer_offset = 0;
|
size_t pos = buf_append_printf(buffer, sizeof(buffer), 0, "Received Raw: ");
|
||||||
buffer_offset += sprintf(buffer, "Received Raw: ");
|
|
||||||
|
|
||||||
for (int32_t i = 0; i < src.size() - 1; i++) {
|
for (int32_t i = 0; i < src.size() - 1; i++) {
|
||||||
const int32_t value = src[i];
|
const int32_t value = src[i];
|
||||||
const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
|
size_t prev_pos = pos;
|
||||||
int written;
|
|
||||||
|
|
||||||
if (i + 1 < src.size() - 1) {
|
if (i + 1 < src.size() - 1) {
|
||||||
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32 ", ", value);
|
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32 ", ", value);
|
||||||
} else {
|
} else {
|
||||||
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32, value);
|
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (written < 0 || written >= int(remaining_length)) {
|
if (pos >= sizeof(buffer) - 1) {
|
||||||
// write failed, flush...
|
// buffer full, flush and continue
|
||||||
buffer[buffer_offset] = '\0';
|
buffer[prev_pos] = '\0';
|
||||||
ESP_LOGI(TAG, "%s", buffer);
|
ESP_LOGI(TAG, "%s", buffer);
|
||||||
buffer_offset = 0;
|
|
||||||
written = sprintf(buffer, " ");
|
|
||||||
if (i + 1 < src.size() - 1) {
|
if (i + 1 < src.size() - 1) {
|
||||||
written += sprintf(buffer + written, "%" PRId32 ", ", value);
|
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32 ", ", value);
|
||||||
} else {
|
} else {
|
||||||
written += sprintf(buffer + written, "%" PRId32, value);
|
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_offset += written;
|
|
||||||
}
|
}
|
||||||
if (buffer_offset != 0) {
|
if (pos != 0) {
|
||||||
ESP_LOGI(TAG, "%s", buffer);
|
ESP_LOGI(TAG, "%s", buffer);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
#include "remote_base.h"
|
#include "remote_base.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#include <cinttypes>
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace remote_base {
|
namespace remote_base {
|
||||||
|
|
||||||
@@ -159,42 +158,41 @@ void RemoteTransmitData::set_data_from_packed_sint32(const uint8_t *data, size_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RemoteTransmitData::set_data_from_base64url(const std::string &base64url) {
|
||||||
|
return base64_decode_int32_vector(base64url, this->data_);
|
||||||
|
}
|
||||||
|
|
||||||
/* RemoteTransmitterBase */
|
/* RemoteTransmitterBase */
|
||||||
|
|
||||||
void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) {
|
void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) {
|
||||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||||
const auto &vec = this->temp_.get_data();
|
const auto &vec = this->temp_.get_data();
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
uint32_t buffer_offset = 0;
|
size_t pos = buf_append_printf(buffer, sizeof(buffer), 0,
|
||||||
buffer_offset += sprintf(buffer, "Sending times=%" PRIu32 " wait=%" PRIu32 "ms: ", send_times, send_wait);
|
"Sending times=%" PRIu32 " wait=%" PRIu32 "ms: ", send_times, send_wait);
|
||||||
|
|
||||||
for (size_t i = 0; i < vec.size(); i++) {
|
for (size_t i = 0; i < vec.size(); i++) {
|
||||||
const int32_t value = vec[i];
|
const int32_t value = vec[i];
|
||||||
const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
|
size_t prev_pos = pos;
|
||||||
int written;
|
|
||||||
|
|
||||||
if (i + 1 < vec.size()) {
|
if (i + 1 < vec.size()) {
|
||||||
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32 ", ", value);
|
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32 ", ", value);
|
||||||
} else {
|
} else {
|
||||||
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32, value);
|
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (written < 0 || written >= int(remaining_length)) {
|
if (pos >= sizeof(buffer) - 1) {
|
||||||
// write failed, flush...
|
// buffer full, flush and continue
|
||||||
buffer[buffer_offset] = '\0';
|
buffer[prev_pos] = '\0';
|
||||||
ESP_LOGVV(TAG, "%s", buffer);
|
ESP_LOGVV(TAG, "%s", buffer);
|
||||||
buffer_offset = 0;
|
|
||||||
written = sprintf(buffer, " ");
|
|
||||||
if (i + 1 < vec.size()) {
|
if (i + 1 < vec.size()) {
|
||||||
written += sprintf(buffer + written, "%" PRId32 ", ", value);
|
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32 ", ", value);
|
||||||
} else {
|
} else {
|
||||||
written += sprintf(buffer + written, "%" PRId32, value);
|
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_offset += written;
|
|
||||||
}
|
}
|
||||||
if (buffer_offset != 0) {
|
if (pos != 0) {
|
||||||
ESP_LOGVV(TAG, "%s", buffer);
|
ESP_LOGVV(TAG, "%s", buffer);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ class RemoteTransmitData {
|
|||||||
/// @param len Length of the buffer in bytes
|
/// @param len Length of the buffer in bytes
|
||||||
/// @param count Number of values (for reserve optimization)
|
/// @param count Number of values (for reserve optimization)
|
||||||
void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count);
|
void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count);
|
||||||
|
/// Set data from base64url-encoded little-endian int32 values
|
||||||
|
/// Base64url is URL-safe: uses '-' instead of '+', '_' instead of '/'
|
||||||
|
/// @param base64url Base64url-encoded string of little-endian int32 values
|
||||||
|
/// @return true if successful, false if decode failed or invalid size
|
||||||
|
bool set_data_from_base64url(const std::string &base64url);
|
||||||
void reset() {
|
void reset() {
|
||||||
this->data_.clear();
|
this->data_.clear();
|
||||||
this->carrier_frequency_ = 0;
|
this->carrier_frequency_ = 0;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from esphome.components import binary_sensor, remote_base
|
from esphome.components import binary_sensor, remote_base
|
||||||
|
|
||||||
|
from . import FILTER_SOURCE_FILES # noqa: F401 pylint: disable=unused-import
|
||||||
|
|
||||||
DEPENDENCIES = ["remote_receiver"]
|
DEPENDENCIES = ["remote_receiver"]
|
||||||
|
|
||||||
CONFIG_SCHEMA = remote_base.validate_binary_sensor
|
CONFIG_SCHEMA = remote_base.validate_binary_sensor
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ void ResistanceSensor::process_(float value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res *= this->resistor_;
|
res *= this->resistor_;
|
||||||
ESP_LOGD(TAG, "'%s' - Resistance %.1fΩ", this->name_.c_str(), res);
|
ESP_LOGV(TAG, "'%s' - Resistance %.1fΩ", this->name_.c_str(), res);
|
||||||
this->publish_state(res);
|
this->publish_state(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
#ifdef USE_OTA_ROLLBACK
|
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||||
#include <esp_ota_ops.h>
|
#include <esp_ota_ops.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -26,6 +26,17 @@ void SafeModeComponent::dump_config() {
|
|||||||
this->safe_mode_boot_is_good_after_ / 1000, // because milliseconds
|
this->safe_mode_boot_is_good_after_ / 1000, // because milliseconds
|
||||||
this->safe_mode_num_attempts_,
|
this->safe_mode_num_attempts_,
|
||||||
this->safe_mode_enable_time_ / 1000); // because milliseconds
|
this->safe_mode_enable_time_ / 1000); // because milliseconds
|
||||||
|
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||||
|
const char *state_str;
|
||||||
|
if (this->ota_state_ == ESP_OTA_IMG_NEW) {
|
||||||
|
state_str = "not supported";
|
||||||
|
} else if (this->ota_state_ == ESP_OTA_IMG_PENDING_VERIFY) {
|
||||||
|
state_str = "supported";
|
||||||
|
} else {
|
||||||
|
state_str = "support unknown";
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Bootloader rollback: %s", state_str);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (this->safe_mode_rtc_value_ > 1 && this->safe_mode_rtc_value_ != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
|
if (this->safe_mode_rtc_value_ > 1 && this->safe_mode_rtc_value_ != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||||
auto remaining_restarts = this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_;
|
auto remaining_restarts = this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_;
|
||||||
@@ -36,7 +47,7 @@ void SafeModeComponent::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_OTA_ROLLBACK
|
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||||
const esp_partition_t *last_invalid = esp_ota_get_last_invalid_partition();
|
const esp_partition_t *last_invalid = esp_ota_get_last_invalid_partition();
|
||||||
if (last_invalid != nullptr) {
|
if (last_invalid != nullptr) {
|
||||||
ESP_LOGW(TAG,
|
ESP_LOGW(TAG,
|
||||||
@@ -55,7 +66,7 @@ void SafeModeComponent::loop() {
|
|||||||
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
|
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
|
||||||
this->clean_rtc();
|
this->clean_rtc();
|
||||||
this->boot_successful_ = true;
|
this->boot_successful_ = true;
|
||||||
#ifdef USE_OTA_ROLLBACK
|
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||||
// Mark OTA partition as valid to prevent rollback
|
// Mark OTA partition as valid to prevent rollback
|
||||||
esp_ota_mark_app_valid_cancel_rollback();
|
esp_ota_mark_app_valid_cancel_rollback();
|
||||||
#endif
|
#endif
|
||||||
@@ -90,6 +101,12 @@ bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t en
|
|||||||
this->safe_mode_num_attempts_ = num_attempts;
|
this->safe_mode_num_attempts_ = num_attempts;
|
||||||
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
|
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
|
||||||
|
|
||||||
|
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||||
|
// Check partition state to detect if bootloader supports rollback
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
esp_ota_get_state_partition(running, &this->ota_state_);
|
||||||
|
#endif
|
||||||
|
|
||||||
uint32_t rtc_val = this->read_rtc_();
|
uint32_t rtc_val = this->read_rtc_();
|
||||||
this->safe_mode_rtc_value_ = rtc_val;
|
this->safe_mode_rtc_value_ = rtc_val;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/preferences.h"
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
|
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||||
|
#include <esp_ota_ops.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome::safe_mode {
|
namespace esphome::safe_mode {
|
||||||
|
|
||||||
/// SafeModeComponent provides a safe way to recover from repeated boot failures
|
/// SafeModeComponent provides a safe way to recover from repeated boot failures
|
||||||
@@ -42,6 +46,9 @@ class SafeModeComponent : public Component {
|
|||||||
// Group 1-byte members together to minimize padding
|
// Group 1-byte members together to minimize padding
|
||||||
bool boot_successful_{false}; ///< set to true after boot is considered successful
|
bool boot_successful_{false}; ///< set to true after boot is considered successful
|
||||||
uint8_t safe_mode_num_attempts_{0};
|
uint8_t safe_mode_num_attempts_{0};
|
||||||
|
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||||
|
esp_ota_img_states_t ota_state_{ESP_OTA_IMG_UNDEFINED};
|
||||||
|
#endif
|
||||||
// Larger objects at the end
|
// Larger objects at the end
|
||||||
ESPPreferenceObject rtc_;
|
ESPPreferenceObject rtc_;
|
||||||
#ifdef USE_SAFE_MODE_CALLBACK
|
#ifdef USE_SAFE_MODE_CALLBACK
|
||||||
|
|||||||
@@ -8,17 +8,20 @@ from esphome.const import (
|
|||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INDEX,
|
CONF_INDEX,
|
||||||
|
CONF_LAMBDA,
|
||||||
CONF_MODE,
|
CONF_MODE,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_ON_VALUE,
|
CONF_ON_VALUE,
|
||||||
CONF_OPERATION,
|
CONF_OPERATION,
|
||||||
CONF_OPTION,
|
CONF_OPTION,
|
||||||
|
CONF_OPTIONS,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass, TemplateArguments
|
||||||
|
from esphome.cpp_types import global_ns
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
@@ -38,6 +41,9 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action)
|
|||||||
SelectSetIndexAction = select_ns.class_("SelectSetIndexAction", automation.Action)
|
SelectSetIndexAction = select_ns.class_("SelectSetIndexAction", automation.Action)
|
||||||
SelectOperationAction = select_ns.class_("SelectOperationAction", automation.Action)
|
SelectOperationAction = select_ns.class_("SelectOperationAction", automation.Action)
|
||||||
|
|
||||||
|
# Conditions
|
||||||
|
SelectIsCondition = select_ns.class_("SelectIsCondition", automation.Condition)
|
||||||
|
|
||||||
# Enums
|
# Enums
|
||||||
SelectOperation = select_ns.enum("SelectOperation")
|
SelectOperation = select_ns.enum("SelectOperation")
|
||||||
SELECT_OPERATION_OPTIONS = {
|
SELECT_OPERATION_OPTIONS = {
|
||||||
@@ -165,6 +171,41 @@ async def select_set_index_to_code(config, action_id, template_arg, args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_condition(
|
||||||
|
"select.is",
|
||||||
|
SelectIsCondition,
|
||||||
|
OPERATION_BASE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OPTIONS): cv.All(
|
||||||
|
cv.ensure_list(cv.string_strict), cv.Length(min=1)
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||||
|
}
|
||||||
|
).add_extra(cv.has_exactly_one_key(CONF_OPTIONS, CONF_LAMBDA)),
|
||||||
|
)
|
||||||
|
async def select_is_to_code(config, condition_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
if options := config.get(CONF_OPTIONS):
|
||||||
|
# List of constant options
|
||||||
|
# Create a constexpr and pass that with a template length
|
||||||
|
arr_id = ID(
|
||||||
|
f"{condition_id}_data",
|
||||||
|
is_declaration=True,
|
||||||
|
type=global_ns.namespace("constexpr char * const"),
|
||||||
|
)
|
||||||
|
arg = cg.static_const_array(arr_id, cg.ArrayInitializer(*options))
|
||||||
|
template_arg = TemplateArguments(len(options), *template_arg)
|
||||||
|
else:
|
||||||
|
# Lambda
|
||||||
|
arg = await cg.process_lambda(
|
||||||
|
config[CONF_LAMBDA],
|
||||||
|
[(global_ns.namespace("StringRef &").operator("const"), "current")] + args,
|
||||||
|
return_type=cg.bool_,
|
||||||
|
)
|
||||||
|
template_arg = TemplateArguments(0, *template_arg)
|
||||||
|
return cg.new_Pvariable(condition_id, template_arg, paren, arg)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action(
|
@automation.register_action(
|
||||||
"select.operation",
|
"select.operation",
|
||||||
SelectOperationAction,
|
SelectOperationAction,
|
||||||
|
|||||||
@@ -66,4 +66,34 @@ template<typename... Ts> class SelectOperationAction : public Action<Ts...> {
|
|||||||
Select *select_;
|
Select *select_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<size_t N, typename... Ts> class SelectIsCondition : public Condition<Ts...> {
|
||||||
|
public:
|
||||||
|
SelectIsCondition(Select *parent, const char *const *option_list) : parent_(parent), option_list_(option_list) {}
|
||||||
|
|
||||||
|
bool check(const Ts &...x) override {
|
||||||
|
auto current = this->parent_->current_option();
|
||||||
|
for (size_t i = 0; i != N; i++) {
|
||||||
|
if (current == this->option_list_[i]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Select *parent_;
|
||||||
|
const char *const *option_list_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SelectIsCondition<0, Ts...> : public Condition<Ts...> {
|
||||||
|
public:
|
||||||
|
SelectIsCondition(Select *parent, std::function<bool(const StringRef &, const Ts &...)> &&f)
|
||||||
|
: parent_(parent), f_(f) {}
|
||||||
|
|
||||||
|
bool check(const Ts &...x) override { return this->f_(this->parent_->current_option(), x...); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Select *parent_;
|
||||||
|
std::function<bool(const StringRef &, const Ts &...)> f_;
|
||||||
|
};
|
||||||
} // namespace esphome::select
|
} // namespace esphome::select
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ void Select::publish_state(size_t index) {
|
|||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
this->state = option; // Update deprecated member for backward compatibility
|
this->state = option; // Update deprecated member for backward compatibility
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", this->get_name().c_str(), option, index);
|
ESP_LOGD(TAG, "'%s' >> %s (%zu)", this->get_name().c_str(), option, index);
|
||||||
this->state_callback_.call(index);
|
this->state_callback_.call(index);
|
||||||
#if defined(USE_SELECT) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_SELECT) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_select_update(this);
|
ControllerRegistry::notify_select_update(this);
|
||||||
|
|||||||
@@ -126,8 +126,8 @@ float Sensor::get_raw_state() const { return this->raw_state; }
|
|||||||
void Sensor::internal_send_state_to_frontend(float state) {
|
void Sensor::internal_send_state_to_frontend(float state) {
|
||||||
this->set_has_state(true);
|
this->set_has_state(true);
|
||||||
this->state = state;
|
this->state = state;
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state,
|
ESP_LOGD(TAG, "'%s' >> %.*f %s", this->get_name().c_str(), std::max(0, (int) this->get_accuracy_decimals()), state,
|
||||||
this->get_unit_of_measurement_ref().c_str(), this->get_accuracy_decimals());
|
this->get_unit_of_measurement_ref().c_str());
|
||||||
this->callback_.call(state);
|
this->callback_.call(state);
|
||||||
#if defined(USE_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_sensor_update(this);
|
ControllerRegistry::notify_sensor_update(this);
|
||||||
|
|||||||
@@ -10,26 +10,24 @@ namespace esphome::sha256 {
|
|||||||
|
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
|
||||||
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x):
|
// CRITICAL ESP32 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x):
|
||||||
//
|
//
|
||||||
// The ESP32-S3 uses hardware DMA for SHA acceleration. The mbedtls_sha256_context structure contains
|
// ESP32 variants (except original ESP32) use DMA-based hardware SHA acceleration that requires
|
||||||
// internal state that the DMA engine references. This imposes three critical constraints:
|
// 32-byte aligned digest buffers. This is handled automatically via HashBase::digest_ which has
|
||||||
|
// alignas(32) on these platforms. Two additional constraints apply:
|
||||||
//
|
//
|
||||||
// 1. ALIGNMENT: The SHA256 object MUST be declared with `alignas(32)` for proper DMA alignment.
|
// 1. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to
|
||||||
// Without this, the DMA engine may crash with an abort in sha_hal_read_digest().
|
|
||||||
//
|
|
||||||
// 2. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to
|
|
||||||
// write to incorrect memory locations. This results in null pointer dereferences and crashes.
|
// write to incorrect memory locations. This results in null pointer dereferences and crashes.
|
||||||
// ALWAYS use fixed-size arrays (e.g., char buf[65], not char buf[size+1]).
|
// ALWAYS use fixed-size arrays (e.g., char buf[65], not char buf[size+1]).
|
||||||
//
|
//
|
||||||
// 3. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same
|
// 2. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same
|
||||||
// function. NEVER pass the SHA256 object or HashBase pointer to another function. When the stack
|
// function. NEVER pass the SHA256 object or HashBase pointer to another function. When the stack
|
||||||
// frame changes (function call/return), the DMA references become invalid and will produce
|
// frame changes (function call/return), the DMA references become invalid and will produce
|
||||||
// truncated hash output (20 bytes instead of 32) or corrupt memory.
|
// truncated hash output (20 bytes instead of 32) or corrupt memory.
|
||||||
//
|
//
|
||||||
// CORRECT USAGE:
|
// CORRECT USAGE:
|
||||||
// void my_function() {
|
// void my_function() {
|
||||||
// alignas(32) sha256::SHA256 hasher; // Created locally with proper alignment
|
// sha256::SHA256 hasher;
|
||||||
// hasher.init();
|
// hasher.init();
|
||||||
// hasher.add(data, len); // Any size, no chunking needed
|
// hasher.add(data, len); // Any size, no chunking needed
|
||||||
// hasher.calculate();
|
// hasher.calculate();
|
||||||
@@ -37,9 +35,9 @@ namespace esphome::sha256 {
|
|||||||
// // hasher destroyed when function returns
|
// // hasher destroyed when function returns
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// INCORRECT USAGE (WILL FAIL ON ESP32-S3):
|
// INCORRECT USAGE (WILL FAIL):
|
||||||
// void my_function() {
|
// void my_function() {
|
||||||
// sha256::SHA256 hasher; // WRONG: Missing alignas(32)
|
// sha256::SHA256 hasher;
|
||||||
// helper(&hasher); // WRONG: Passed to different stack frame
|
// helper(&hasher); // WRONG: Passed to different stack frame
|
||||||
// }
|
// }
|
||||||
// void helper(HashBase *h) {
|
// void helper(HashBase *h) {
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ namespace esphome::sha256 {
|
|||||||
|
|
||||||
/// SHA256 hash implementation.
|
/// SHA256 hash implementation.
|
||||||
///
|
///
|
||||||
/// CRITICAL for ESP32-S3 with IDF 5.5.x hardware SHA acceleration:
|
/// CRITICAL for ESP32 variants (except original) with IDF 5.5.x hardware SHA acceleration:
|
||||||
/// 1. SHA256 objects MUST be declared with `alignas(32)` for proper DMA alignment
|
/// 1. The object MUST stay in the same stack frame (no passing to other functions)
|
||||||
/// 2. The object MUST stay in the same stack frame (no passing to other functions)
|
/// 2. NO Variable Length Arrays (VLAs) in the same function
|
||||||
/// 3. NO Variable Length Arrays (VLAs) in the same function
|
///
|
||||||
|
/// Note: Alignment is handled automatically via the HashBase::digest_ member.
|
||||||
///
|
///
|
||||||
/// Example usage:
|
/// Example usage:
|
||||||
/// alignas(32) sha256::SHA256 hasher;
|
/// sha256::SHA256 hasher;
|
||||||
/// hasher.init();
|
/// hasher.init();
|
||||||
/// hasher.add(data, len);
|
/// hasher.add(data, len);
|
||||||
/// hasher.calculate();
|
/// hasher.calculate();
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ Sprinkler::Sprinkler(const std::string &name) {
|
|||||||
// The `name` is needed to set timers up, hence non-default constructor
|
// The `name` is needed to set timers up, hence non-default constructor
|
||||||
// replaces `set_name()` method previously existed
|
// replaces `set_name()` method previously existed
|
||||||
this->name_ = name;
|
this->name_ = name;
|
||||||
|
this->timer_.init(2);
|
||||||
this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)});
|
this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)});
|
||||||
this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)});
|
this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)});
|
||||||
}
|
}
|
||||||
@@ -1574,7 +1575,8 @@ const LogString *Sprinkler::state_as_str_(SprinklerState state) {
|
|||||||
|
|
||||||
void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
||||||
if (this->timer_duration_(timer_index) > 0) {
|
if (this->timer_duration_(timer_index) > 0) {
|
||||||
this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
|
// FixedVector ensures timer_ can't be resized, so .c_str() pointers remain valid
|
||||||
|
this->set_timeout(this->timer_[timer_index].name.c_str(), this->timer_duration_(timer_index),
|
||||||
this->timer_cbf_(timer_index));
|
this->timer_cbf_(timer_index));
|
||||||
this->timer_[timer_index].start_time = millis();
|
this->timer_[timer_index].start_time = millis();
|
||||||
this->timer_[timer_index].active = true;
|
this->timer_[timer_index].active = true;
|
||||||
@@ -1585,7 +1587,7 @@ void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
|||||||
|
|
||||||
bool Sprinkler::cancel_timer_(const SprinklerTimerIndex timer_index) {
|
bool Sprinkler::cancel_timer_(const SprinklerTimerIndex timer_index) {
|
||||||
this->timer_[timer_index].active = false;
|
this->timer_[timer_index].active = false;
|
||||||
return this->cancel_timeout(this->timer_[timer_index].name);
|
return this->cancel_timeout(this->timer_[timer_index].name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
|
bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/components/number/number.h"
|
#include "esphome/components/number/number.h"
|
||||||
#include "esphome/components/switch/switch.h"
|
#include "esphome/components/switch/switch.h"
|
||||||
|
|
||||||
@@ -553,8 +554,8 @@ class Sprinkler : public Component {
|
|||||||
/// Sprinkler valve operator objects
|
/// Sprinkler valve operator objects
|
||||||
std::vector<SprinklerValveOperator> valve_op_{2};
|
std::vector<SprinklerValveOperator> valve_op_{2};
|
||||||
|
|
||||||
/// Valve control timers
|
/// Valve control timers - FixedVector enforces that this can never grow beyond init() size
|
||||||
std::vector<SprinklerTimer> timer_{};
|
FixedVector<SprinklerTimer> timer_;
|
||||||
|
|
||||||
/// Other Sprinkler instances we should be aware of (used to check if pumps are in use)
|
/// Other Sprinkler instances we should be aware of (used to check if pumps are in use)
|
||||||
std::vector<Sprinkler *> other_controllers_;
|
std::vector<Sprinkler *> other_controllers_;
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
|||||||
void set_parent(Sun *parent) { parent_ = parent; }
|
void set_parent(Sun *parent) { parent_ = parent; }
|
||||||
void set_elevation(double elevation) { elevation_ = elevation; }
|
void set_elevation(double elevation) { elevation_ = elevation; }
|
||||||
void set_sunrise(bool sunrise) { sunrise_ = sunrise; }
|
void set_sunrise(bool sunrise) { sunrise_ = sunrise; }
|
||||||
void set_format(const std::string &format) { format_ = format; }
|
void set_format(const char *format) { this->format_ = format; }
|
||||||
|
/// Prevent accidental use of std::string which would dangle
|
||||||
|
void set_format(const std::string &format) = delete;
|
||||||
|
|
||||||
void update() override {
|
void update() override {
|
||||||
optional<ESPTime> res;
|
optional<ESPTime> res;
|
||||||
@@ -29,14 +31,14 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char buf[ESPTime::STRFTIME_BUFFER_SIZE];
|
char buf[ESPTime::STRFTIME_BUFFER_SIZE];
|
||||||
size_t len = res->strftime_to(buf, this->format_.c_str());
|
size_t len = res->strftime_to(buf, this->format_);
|
||||||
this->publish_state(buf, len);
|
this->publish_state(buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string format_{};
|
const char *format_{nullptr};
|
||||||
Sun *parent_;
|
Sun *parent_;
|
||||||
double elevation_;
|
double elevation_;
|
||||||
bool sunrise_;
|
bool sunrise_;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ void Switch::publish_state(bool state) {
|
|||||||
if (restore_mode & RESTORE_MODE_PERSISTENT_MASK)
|
if (restore_mode & RESTORE_MODE_PERSISTENT_MASK)
|
||||||
this->rtc_.save(&this->state);
|
this->rtc_.save(&this->state);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), ONOFF(this->state));
|
ESP_LOGD(TAG, "'%s' >> %s", this->name_.c_str(), ONOFF(this->state));
|
||||||
this->state_callback_.call(this->state);
|
this->state_callback_.call(this->state);
|
||||||
#if defined(USE_SWITCH) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_SWITCH) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_switch_update(this);
|
ControllerRegistry::notify_switch_update(this);
|
||||||
|
|||||||
@@ -118,8 +118,7 @@ async def to_code(config):
|
|||||||
var = await alarm_control_panel.new_alarm_control_panel(config)
|
var = await alarm_control_panel.new_alarm_control_panel(config)
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
if CONF_CODES in config:
|
if CONF_CODES in config:
|
||||||
for acode in config[CONF_CODES]:
|
cg.add(var.set_codes(config[CONF_CODES]))
|
||||||
cg.add(var.add_code(acode))
|
|
||||||
if CONF_REQUIRES_CODE_TO_ARM in config:
|
if CONF_REQUIRES_CODE_TO_ARM in config:
|
||||||
cg.add(var.set_requires_code_to_arm(config[CONF_REQUIRES_CODE_TO_ARM]))
|
cg.add(var.set_requires_code_to_arm(config[CONF_REQUIRES_CODE_TO_ARM]))
|
||||||
|
|
||||||
|
|||||||
@@ -206,7 +206,13 @@ bool TemplateAlarmControlPanel::is_code_valid_(optional<std::string> code) {
|
|||||||
if (!this->codes_.empty()) {
|
if (!this->codes_.empty()) {
|
||||||
if (code.has_value()) {
|
if (code.has_value()) {
|
||||||
ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
|
ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
|
||||||
return (std::count(this->codes_.begin(), this->codes_.end(), code.value()) == 1);
|
// Use strcmp for const char* comparison
|
||||||
|
const char *code_cstr = code.value().c_str();
|
||||||
|
for (const char *stored_code : this->codes_) {
|
||||||
|
if (strcmp(stored_code, code_cstr) == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "No code provided");
|
ESP_LOGD(TAG, "No code provided");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
@@ -86,11 +87,14 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
|
|||||||
AlarmSensorType type = ALARM_SENSOR_TYPE_DELAYED);
|
AlarmSensorType type = ALARM_SENSOR_TYPE_DELAYED);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** add a code
|
/** Set the codes (from initializer list).
|
||||||
*
|
*
|
||||||
* @param code The code
|
* @param codes The list of valid codes
|
||||||
*/
|
*/
|
||||||
void add_code(const std::string &code) { this->codes_.push_back(code); }
|
void set_codes(std::initializer_list<const char *> codes) { this->codes_ = codes; }
|
||||||
|
|
||||||
|
// Deleted overload to catch incorrect std::string usage at compile time
|
||||||
|
void set_codes(std::initializer_list<std::string> codes) = delete;
|
||||||
|
|
||||||
/** set requires a code to arm
|
/** set requires a code to arm
|
||||||
*
|
*
|
||||||
@@ -155,8 +159,8 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
|
|||||||
uint32_t pending_time_;
|
uint32_t pending_time_;
|
||||||
// the time in trigger
|
// the time in trigger
|
||||||
uint32_t trigger_time_;
|
uint32_t trigger_time_;
|
||||||
// a list of codes
|
// a list of codes (const char* pointers to string literals in flash)
|
||||||
std::vector<std::string> codes_;
|
FixedVector<const char *> codes_;
|
||||||
// requires a code to arm
|
// requires a code to arm
|
||||||
bool requires_code_to_arm_ = false;
|
bool requires_code_to_arm_ = false;
|
||||||
bool supports_arm_home_ = false;
|
bool supports_arm_home_ = false;
|
||||||
|
|||||||
@@ -8,16 +8,23 @@ static const char *const TAG = "template.text";
|
|||||||
void TemplateText::setup() {
|
void TemplateText::setup() {
|
||||||
if (this->f_.has_value())
|
if (this->f_.has_value())
|
||||||
return;
|
return;
|
||||||
std::string value = this->initial_value_;
|
|
||||||
if (!this->pref_) {
|
if (this->pref_ == nullptr) {
|
||||||
ESP_LOGD(TAG, "State from initial: %s", value.c_str());
|
// No restore - use const char* directly, no heap allocation needed
|
||||||
} else {
|
if (this->initial_value_ != nullptr && this->initial_value_[0] != '\0') {
|
||||||
uint32_t key = this->get_preference_hash();
|
ESP_LOGD(TAG, "State from initial: %s", this->initial_value_);
|
||||||
key += this->traits.get_min_length() << 2;
|
this->publish_state(this->initial_value_);
|
||||||
key += this->traits.get_max_length() << 4;
|
}
|
||||||
key += fnv1_hash(this->traits.get_pattern_c_str()) << 6;
|
return;
|
||||||
this->pref_->setup(key, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Need std::string for pref_->setup() to fill from flash
|
||||||
|
std::string value{this->initial_value_ != nullptr ? this->initial_value_ : ""};
|
||||||
|
uint32_t key = this->get_preference_hash();
|
||||||
|
key += this->traits.get_min_length() << 2;
|
||||||
|
key += this->traits.get_max_length() << 4;
|
||||||
|
key += fnv1_hash(this->traits.get_pattern_c_str()) << 6;
|
||||||
|
this->pref_->setup(key, value);
|
||||||
if (!value.empty())
|
if (!value.empty())
|
||||||
this->publish_state(value);
|
this->publish_state(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,13 +70,15 @@ class TemplateText final : public text::Text, public PollingComponent {
|
|||||||
|
|
||||||
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
|
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
|
||||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||||
void set_initial_value(const std::string &initial_value) { this->initial_value_ = initial_value; }
|
void set_initial_value(const char *initial_value) { this->initial_value_ = initial_value; }
|
||||||
|
/// Prevent accidental use of std::string which would dangle
|
||||||
|
void set_initial_value(const std::string &initial_value) = delete;
|
||||||
void set_value_saver(TemplateTextSaverBase *restore_value_saver) { this->pref_ = restore_value_saver; }
|
void set_value_saver(TemplateTextSaverBase *restore_value_saver) { this->pref_ = restore_value_saver; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(const std::string &value) override;
|
||||||
bool optimistic_ = false;
|
bool optimistic_ = false;
|
||||||
std::string initial_value_;
|
const char *initial_value_{nullptr};
|
||||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||||
TemplateLambda<std::string> f_{};
|
TemplateLambda<std::string> f_{};
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ void Text::publish_state(const char *state, size_t len) {
|
|||||||
this->state.assign(state, len);
|
this->state.assign(state, len);
|
||||||
}
|
}
|
||||||
if (this->traits.get_mode() == TEXT_MODE_PASSWORD) {
|
if (this->traits.get_mode() == TEXT_MODE_PASSWORD) {
|
||||||
ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), this->state.c_str());
|
ESP_LOGD(TAG, "'%s' >> " LOG_SECRET("'%s'"), this->get_name().c_str(), this->state.c_str());
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), this->state.c_str());
|
ESP_LOGD(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->state.c_str());
|
||||||
}
|
}
|
||||||
this->state_callback_.call(this->state);
|
this->state_callback_.call(this->state);
|
||||||
#if defined(USE_TEXT) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_TEXT) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ void TextSensor::internal_send_state_to_frontend(const char *state, size_t len)
|
|||||||
|
|
||||||
void TextSensor::notify_frontend_() {
|
void TextSensor::notify_frontend_() {
|
||||||
this->set_has_state(true);
|
this->set_has_state(true);
|
||||||
ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), this->state.c_str());
|
ESP_LOGD(TAG, "'%s' >> '%s'", this->name_.c_str(), this->state.c_str());
|
||||||
this->callback_.call(this->state);
|
this->callback_.call(this->state);
|
||||||
#if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
#if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
ControllerRegistry::notify_text_sensor_update(this);
|
ControllerRegistry::notify_text_sensor_update(this);
|
||||||
|
|||||||
@@ -31,6 +31,18 @@ void RealTimeClock::dump_config() {
|
|||||||
|
|
||||||
void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
||||||
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
|
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
|
||||||
|
// Skip if time is already synchronized to avoid unnecessary writes, log spam,
|
||||||
|
// and prevent clock jumping backwards due to network latency
|
||||||
|
constexpr time_t min_valid_epoch = 1546300800; // January 1, 2019
|
||||||
|
time_t current_time = this->timestamp_now();
|
||||||
|
// Check if time is valid (year >= 2019) before comparing
|
||||||
|
if (current_time >= min_valid_epoch) {
|
||||||
|
// Unsigned subtraction handles wraparound correctly, then cast to signed
|
||||||
|
int32_t diff = static_cast<int32_t>(epoch - static_cast<uint32_t>(current_time));
|
||||||
|
if (diff >= -1 && diff <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Update UTC epoch time.
|
// Update UTC epoch time.
|
||||||
#ifdef USE_ZEPHYR
|
#ifdef USE_ZEPHYR
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
|
|||||||
@@ -108,8 +108,7 @@ async def to_code(config):
|
|||||||
cg.add(var.set_broadcast_port(conf_port[CONF_BROADCAST_PORT]))
|
cg.add(var.set_broadcast_port(conf_port[CONF_BROADCAST_PORT]))
|
||||||
if (listen_address := str(config[CONF_LISTEN_ADDRESS])) != "255.255.255.255":
|
if (listen_address := str(config[CONF_LISTEN_ADDRESS])) != "255.255.255.255":
|
||||||
cg.add(var.set_listen_address(listen_address))
|
cg.add(var.set_listen_address(listen_address))
|
||||||
for address in config[CONF_ADDRESSES]:
|
cg.add(var.set_addresses([str(addr) for addr in config[CONF_ADDRESSES]]))
|
||||||
cg.add(var.add_address(str(address)))
|
|
||||||
if on_receive := config.get(CONF_ON_RECEIVE):
|
if on_receive := config.get(CONF_ON_RECEIVE):
|
||||||
on_receive = on_receive[0]
|
on_receive = on_receive[0]
|
||||||
trigger = cg.new_Pvariable(on_receive[CONF_TRIGGER_ID])
|
trigger = cg.new_Pvariable(on_receive[CONF_TRIGGER_ID])
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
#include "udp_component.h"
|
#include "udp_component.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::udp {
|
||||||
namespace udp {
|
|
||||||
|
|
||||||
static const char *const TAG = "udp";
|
static const char *const TAG = "udp";
|
||||||
|
|
||||||
@@ -95,7 +94,7 @@ void UDPComponent::setup() {
|
|||||||
// 8266 and RP2040 `Duino
|
// 8266 and RP2040 `Duino
|
||||||
for (const auto &address : this->addresses_) {
|
for (const auto &address : this->addresses_) {
|
||||||
auto ipaddr = IPAddress();
|
auto ipaddr = IPAddress();
|
||||||
ipaddr.fromString(address.c_str());
|
ipaddr.fromString(address);
|
||||||
this->ipaddrs_.push_back(ipaddr);
|
this->ipaddrs_.push_back(ipaddr);
|
||||||
}
|
}
|
||||||
if (this->should_listen_)
|
if (this->should_listen_)
|
||||||
@@ -130,8 +129,8 @@ void UDPComponent::dump_config() {
|
|||||||
" Listen Port: %u\n"
|
" Listen Port: %u\n"
|
||||||
" Broadcast Port: %u",
|
" Broadcast Port: %u",
|
||||||
this->listen_port_, this->broadcast_port_);
|
this->listen_port_, this->broadcast_port_);
|
||||||
for (const auto &address : this->addresses_)
|
for (const char *address : this->addresses_)
|
||||||
ESP_LOGCONFIG(TAG, " Address: %s", address.c_str());
|
ESP_LOGCONFIG(TAG, " Address: %s", address);
|
||||||
if (this->listen_address_.has_value()) {
|
if (this->listen_address_.has_value()) {
|
||||||
char addr_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
char addr_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||||
ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str_to(addr_buf));
|
ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str_to(addr_buf));
|
||||||
@@ -162,7 +161,6 @@ void UDPComponent::send_packet(const uint8_t *data, size_t size) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} // namespace udp
|
} // namespace esphome::udp
|
||||||
} // namespace esphome
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#ifdef USE_NETWORK
|
#ifdef USE_NETWORK
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||||
#include "esphome/components/socket/socket.h"
|
#include "esphome/components/socket/socket.h"
|
||||||
@@ -9,15 +10,17 @@
|
|||||||
#ifdef USE_SOCKET_IMPL_LWIP_TCP
|
#ifdef USE_SOCKET_IMPL_LWIP_TCP
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include <initializer_list>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::udp {
|
||||||
namespace udp {
|
|
||||||
|
|
||||||
static const size_t MAX_PACKET_SIZE = 508;
|
static const size_t MAX_PACKET_SIZE = 508;
|
||||||
class UDPComponent : public Component {
|
class UDPComponent : public Component {
|
||||||
public:
|
public:
|
||||||
void add_address(const char *addr) { this->addresses_.emplace_back(addr); }
|
void set_addresses(std::initializer_list<const char *> addresses) { this->addresses_ = addresses; }
|
||||||
|
/// Prevent accidental use of std::string which would dangle
|
||||||
|
void set_addresses(std::initializer_list<std::string> addresses) = delete;
|
||||||
void set_listen_address(const char *listen_addr) { this->listen_address_ = network::IPAddress(listen_addr); }
|
void set_listen_address(const char *listen_addr) { this->listen_address_ = network::IPAddress(listen_addr); }
|
||||||
void set_listen_port(uint16_t port) { this->listen_port_ = port; }
|
void set_listen_port(uint16_t port) { this->listen_port_ = port; }
|
||||||
void set_broadcast_port(uint16_t port) { this->broadcast_port_ = port; }
|
void set_broadcast_port(uint16_t port) { this->broadcast_port_ = port; }
|
||||||
@@ -49,11 +52,10 @@ class UDPComponent : public Component {
|
|||||||
std::vector<IPAddress> ipaddrs_{};
|
std::vector<IPAddress> ipaddrs_{};
|
||||||
WiFiUDP udp_client_{};
|
WiFiUDP udp_client_{};
|
||||||
#endif
|
#endif
|
||||||
std::vector<std::string> addresses_{};
|
FixedVector<const char *> addresses_{};
|
||||||
|
|
||||||
optional<network::IPAddress> listen_address_{};
|
optional<network::IPAddress> listen_address_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace udp
|
} // namespace esphome::udp
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ static const char *const TAG = "update";
|
|||||||
|
|
||||||
void UpdateEntity::publish_state() {
|
void UpdateEntity::publish_state() {
|
||||||
ESP_LOGD(TAG,
|
ESP_LOGD(TAG,
|
||||||
"'%s' - Publishing:\n"
|
"'%s' >>\n"
|
||||||
" Current Version: %s",
|
" Current Version: %s",
|
||||||
this->name_.c_str(), this->update_info_.current_version.c_str());
|
this->name_.c_str(), this->update_info_.current_version.c_str());
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ void Valve::add_on_state_callback(std::function<void()> &&f) { this->state_callb
|
|||||||
void Valve::publish_state(bool save) {
|
void Valve::publish_state(bool save) {
|
||||||
this->position = clamp(this->position, 0.0f, 1.0f);
|
this->position = clamp(this->position, 0.0f, 1.0f);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str());
|
ESP_LOGD(TAG, "'%s' >>", this->name_.c_str());
|
||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
if (traits.get_supports_position()) {
|
if (traits.get_supports_position()) {
|
||||||
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
|
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ void WaterHeater::setup() {
|
|||||||
void WaterHeater::publish_state() {
|
void WaterHeater::publish_state() {
|
||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
ESP_LOGD(TAG,
|
ESP_LOGD(TAG,
|
||||||
"'%s' - Sending state:\n"
|
"'%s' >>\n"
|
||||||
" Mode: %s",
|
" Mode: %s",
|
||||||
this->name_.c_str(), LOG_STR_ARG(water_heater_mode_to_string(this->mode_)));
|
this->name_.c_str(), LOG_STR_ARG(water_heater_mode_to_string(this->mode_)));
|
||||||
if (!std::isnan(this->current_temperature_)) {
|
if (!std::isnan(this->current_temperature_)) {
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_OTA): cv.boolean,
|
cv.Optional(CONF_OTA): cv.boolean,
|
||||||
cv.Optional(CONF_LOG, default=True): cv.boolean,
|
cv.Optional(CONF_LOG, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_LOCAL): cv.boolean,
|
cv.Optional(CONF_LOCAL): cv.boolean,
|
||||||
cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"),
|
cv.Optional(CONF_COMPRESSION, default="gzip"): cv.one_of("gzip", "br"),
|
||||||
cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group),
|
cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ bool ListEntitiesIterator::on_water_heater(water_heater::WaterHeater *obj) {
|
|||||||
|
|
||||||
#ifdef USE_INFRARED
|
#ifdef USE_INFRARED
|
||||||
bool ListEntitiesIterator::on_infrared(infrared::Infrared *obj) {
|
bool ListEntitiesIterator::on_infrared(infrared::Infrared *obj) {
|
||||||
// Infrared web_server support not yet implemented - this stub acknowledges the entity
|
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::infrared_all_json_generator);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -33,6 +33,10 @@
|
|||||||
#include "esphome/components/water_heater/water_heater.h"
|
#include "esphome/components/water_heater/water_heater.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_INFRARED
|
||||||
|
#include "esphome/components/infrared/infrared.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_WEBSERVER_LOCAL
|
#ifdef USE_WEBSERVER_LOCAL
|
||||||
#if USE_WEBSERVER_VERSION == 2
|
#if USE_WEBSERVER_VERSION == 2
|
||||||
#include "server_index_v2.h"
|
#include "server_index_v2.h"
|
||||||
@@ -658,6 +662,24 @@ std::string WebServer::text_sensor_json_(text_sensor::TextSensor *obj, const std
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
|
enum SwitchAction : uint8_t { SWITCH_ACTION_NONE, SWITCH_ACTION_TOGGLE, SWITCH_ACTION_TURN_ON, SWITCH_ACTION_TURN_OFF };
|
||||||
|
|
||||||
|
static void execute_switch_action(switch_::Switch *obj, SwitchAction action) {
|
||||||
|
switch (action) {
|
||||||
|
case SWITCH_ACTION_TOGGLE:
|
||||||
|
obj->toggle();
|
||||||
|
break;
|
||||||
|
case SWITCH_ACTION_TURN_ON:
|
||||||
|
obj->turn_on();
|
||||||
|
break;
|
||||||
|
case SWITCH_ACTION_TURN_OFF:
|
||||||
|
obj->turn_off();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void WebServer::on_switch_update(switch_::Switch *obj) {
|
void WebServer::on_switch_update(switch_::Switch *obj) {
|
||||||
if (!this->include_internal_ && obj->is_internal())
|
if (!this->include_internal_ && obj->is_internal())
|
||||||
return;
|
return;
|
||||||
@@ -676,34 +698,22 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle action methods with single defer and response
|
SwitchAction action = SWITCH_ACTION_NONE;
|
||||||
enum SwitchAction { NONE, TOGGLE, TURN_ON, TURN_OFF };
|
|
||||||
SwitchAction action = NONE;
|
|
||||||
|
|
||||||
if (match.method_equals(ESPHOME_F("toggle"))) {
|
if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||||
action = TOGGLE;
|
action = SWITCH_ACTION_TOGGLE;
|
||||||
} else if (match.method_equals(ESPHOME_F("turn_on"))) {
|
} else if (match.method_equals(ESPHOME_F("turn_on"))) {
|
||||||
action = TURN_ON;
|
action = SWITCH_ACTION_TURN_ON;
|
||||||
} else if (match.method_equals(ESPHOME_F("turn_off"))) {
|
} else if (match.method_equals(ESPHOME_F("turn_off"))) {
|
||||||
action = TURN_OFF;
|
action = SWITCH_ACTION_TURN_OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action != NONE) {
|
if (action != SWITCH_ACTION_NONE) {
|
||||||
this->defer([obj, action]() {
|
#ifdef USE_ESP8266
|
||||||
switch (action) {
|
execute_switch_action(obj, action);
|
||||||
case TOGGLE:
|
#else
|
||||||
obj->toggle();
|
this->defer([obj, action]() { execute_switch_action(obj, action); });
|
||||||
break;
|
#endif
|
||||||
case TURN_ON:
|
|
||||||
obj->turn_on();
|
|
||||||
break;
|
|
||||||
case TURN_OFF:
|
|
||||||
obj->turn_off();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
request->send(200);
|
request->send(200);
|
||||||
} else {
|
} else {
|
||||||
request->send(404);
|
request->send(404);
|
||||||
@@ -743,7 +753,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
|
|||||||
std::string data = this->button_json_(obj, detail);
|
std::string data = this->button_json_(obj, detail);
|
||||||
request->send(200, "application/json", data.c_str());
|
request->send(200, "application/json", data.c_str());
|
||||||
} else if (match.method_equals(ESPHOME_F("press"))) {
|
} else if (match.method_equals(ESPHOME_F("press"))) {
|
||||||
this->defer([obj]() { obj->press(); });
|
DEFER_ACTION(obj, obj->press());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -753,9 +763,6 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
|
|||||||
}
|
}
|
||||||
request->send(404);
|
request->send(404);
|
||||||
}
|
}
|
||||||
std::string WebServer::button_state_json_generator(WebServer *web_server, void *source) {
|
|
||||||
return web_server->button_json_((button::Button *) (source), DETAIL_STATE);
|
|
||||||
}
|
|
||||||
std::string WebServer::button_all_json_generator(WebServer *web_server, void *source) {
|
std::string WebServer::button_all_json_generator(WebServer *web_server, void *source) {
|
||||||
return web_server->button_json_((button::Button *) (source), DETAIL_ALL);
|
return web_server->button_json_((button::Button *) (source), DETAIL_ALL);
|
||||||
}
|
}
|
||||||
@@ -831,7 +838,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
|||||||
std::string data = this->fan_json_(obj, detail);
|
std::string data = this->fan_json_(obj, detail);
|
||||||
request->send(200, "application/json", data.c_str());
|
request->send(200, "application/json", data.c_str());
|
||||||
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||||
this->defer([obj]() { obj->toggle().perform(); });
|
DEFER_ACTION(obj, obj->toggle().perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
} else {
|
} else {
|
||||||
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
||||||
@@ -862,7 +869,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -912,7 +919,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
|||||||
std::string data = this->light_json_(obj, detail);
|
std::string data = this->light_json_(obj, detail);
|
||||||
request->send(200, "application/json", data.c_str());
|
request->send(200, "application/json", data.c_str());
|
||||||
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||||
this->defer([obj]() { obj->toggle().perform(); });
|
DEFER_ACTION(obj, obj->toggle().perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
} else {
|
} else {
|
||||||
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
||||||
@@ -941,7 +948,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
|||||||
parse_string_param_(request, ESPHOME_F("effect"), call, &decltype(call)::set_effect);
|
parse_string_param_(request, ESPHOME_F("effect"), call, &decltype(call)::set_effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1030,7 +1037,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
|
|||||||
parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position);
|
parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position);
|
||||||
parse_float_param_(request, ESPHOME_F("tilt"), call, &decltype(call)::set_tilt);
|
parse_float_param_(request, ESPHOME_F("tilt"), call, &decltype(call)::set_tilt);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1089,7 +1096,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
|||||||
auto call = obj->make_call();
|
auto call = obj->make_call();
|
||||||
parse_float_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value);
|
parse_float_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1162,7 +1169,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat
|
|||||||
|
|
||||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_date);
|
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_date);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1226,7 +1233,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat
|
|||||||
|
|
||||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_time);
|
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_time);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1289,7 +1296,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur
|
|||||||
|
|
||||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_datetime);
|
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_datetime);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1349,7 +1356,7 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat
|
|||||||
auto call = obj->make_call();
|
auto call = obj->make_call();
|
||||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value);
|
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1407,7 +1414,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
|
|||||||
auto call = obj->make_call();
|
auto call = obj->make_call();
|
||||||
parse_string_param_(request, ESPHOME_F("option"), call, &decltype(call)::set_option);
|
parse_string_param_(request, ESPHOME_F("option"), call, &decltype(call)::set_option);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1476,7 +1483,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
|
|||||||
parse_float_param_(request, ESPHOME_F("target_temperature_low"), call, &decltype(call)::set_target_temperature_low);
|
parse_float_param_(request, ESPHOME_F("target_temperature_low"), call, &decltype(call)::set_target_temperature_low);
|
||||||
parse_float_param_(request, ESPHOME_F("target_temperature"), call, &decltype(call)::set_target_temperature);
|
parse_float_param_(request, ESPHOME_F("target_temperature"), call, &decltype(call)::set_target_temperature);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1592,6 +1599,24 @@ std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_con
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
|
enum LockAction : uint8_t { LOCK_ACTION_NONE, LOCK_ACTION_LOCK, LOCK_ACTION_UNLOCK, LOCK_ACTION_OPEN };
|
||||||
|
|
||||||
|
static void execute_lock_action(lock::Lock *obj, LockAction action) {
|
||||||
|
switch (action) {
|
||||||
|
case LOCK_ACTION_LOCK:
|
||||||
|
obj->lock();
|
||||||
|
break;
|
||||||
|
case LOCK_ACTION_UNLOCK:
|
||||||
|
obj->unlock();
|
||||||
|
break;
|
||||||
|
case LOCK_ACTION_OPEN:
|
||||||
|
obj->open();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void WebServer::on_lock_update(lock::Lock *obj) {
|
void WebServer::on_lock_update(lock::Lock *obj) {
|
||||||
if (!this->include_internal_ && obj->is_internal())
|
if (!this->include_internal_ && obj->is_internal())
|
||||||
return;
|
return;
|
||||||
@@ -1610,34 +1635,22 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle action methods with single defer and response
|
LockAction action = LOCK_ACTION_NONE;
|
||||||
enum LockAction { NONE, LOCK, UNLOCK, OPEN };
|
|
||||||
LockAction action = NONE;
|
|
||||||
|
|
||||||
if (match.method_equals(ESPHOME_F("lock"))) {
|
if (match.method_equals(ESPHOME_F("lock"))) {
|
||||||
action = LOCK;
|
action = LOCK_ACTION_LOCK;
|
||||||
} else if (match.method_equals(ESPHOME_F("unlock"))) {
|
} else if (match.method_equals(ESPHOME_F("unlock"))) {
|
||||||
action = UNLOCK;
|
action = LOCK_ACTION_UNLOCK;
|
||||||
} else if (match.method_equals(ESPHOME_F("open"))) {
|
} else if (match.method_equals(ESPHOME_F("open"))) {
|
||||||
action = OPEN;
|
action = LOCK_ACTION_OPEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action != NONE) {
|
if (action != LOCK_ACTION_NONE) {
|
||||||
this->defer([obj, action]() {
|
#ifdef USE_ESP8266
|
||||||
switch (action) {
|
execute_lock_action(obj, action);
|
||||||
case LOCK:
|
#else
|
||||||
obj->lock();
|
this->defer([obj, action]() { execute_lock_action(obj, action); });
|
||||||
break;
|
#endif
|
||||||
case UNLOCK:
|
|
||||||
obj->unlock();
|
|
||||||
break;
|
|
||||||
case OPEN:
|
|
||||||
obj->open();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
request->send(200);
|
request->send(200);
|
||||||
} else {
|
} else {
|
||||||
request->send(404);
|
request->send(404);
|
||||||
@@ -1720,7 +1733,7 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa
|
|||||||
|
|
||||||
parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position);
|
parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1799,7 +1812,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1875,7 +1888,7 @@ void WebServer::handle_water_heater_request(AsyncWebServerRequest *request, cons
|
|||||||
// Parse on/off parameter
|
// Parse on/off parameter
|
||||||
parse_bool_param_(request, ESPHOME_F("is_on"), base_call, &water_heater::WaterHeaterCall::set_on);
|
parse_bool_param_(request, ESPHOME_F("is_on"), base_call, &water_heater::WaterHeaterCall::set_on);
|
||||||
|
|
||||||
this->defer([call]() mutable { call.perform(); });
|
DEFER_ACTION(call, call.perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1943,6 +1956,110 @@ std::string WebServer::water_heater_json_(water_heater::WaterHeater *obj, JsonDe
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_INFRARED
|
||||||
|
void WebServer::handle_infrared_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||||
|
for (infrared::Infrared *obj : App.get_infrareds()) {
|
||||||
|
auto entity_match = match.match_entity(obj);
|
||||||
|
if (!entity_match.matched)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
|
||||||
|
auto detail = get_request_detail(request);
|
||||||
|
std::string data = this->infrared_json_(obj, detail);
|
||||||
|
request->send(200, ESPHOME_F("application/json"), data.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!match.method_equals(ESPHOME_F("transmit"))) {
|
||||||
|
request->send(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow transmit if the device supports it
|
||||||
|
if (!obj->has_transmitter()) {
|
||||||
|
request->send(400, ESPHOME_F("text/plain"), "Device does not support transmission");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parameters
|
||||||
|
auto call = obj->make_call();
|
||||||
|
|
||||||
|
// Parse carrier frequency (optional)
|
||||||
|
if (request->hasParam(ESPHOME_F("carrier_frequency"))) {
|
||||||
|
auto value = parse_number<uint32_t>(request->getParam(ESPHOME_F("carrier_frequency"))->value().c_str());
|
||||||
|
if (value.has_value()) {
|
||||||
|
call.set_carrier_frequency(*value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse repeat count (optional, defaults to 1)
|
||||||
|
if (request->hasParam(ESPHOME_F("repeat_count"))) {
|
||||||
|
auto value = parse_number<uint32_t>(request->getParam(ESPHOME_F("repeat_count"))->value().c_str());
|
||||||
|
if (value.has_value()) {
|
||||||
|
call.set_repeat_count(*value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse base64url-encoded raw timings (required)
|
||||||
|
// Base64url is URL-safe: uses A-Za-z0-9-_ (no special characters needing escaping)
|
||||||
|
if (!request->hasParam(ESPHOME_F("data"))) {
|
||||||
|
request->send(400, ESPHOME_F("text/plain"), "Missing 'data' parameter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .c_str() is required for Arduino framework where value() returns Arduino String instead of std::string
|
||||||
|
std::string encoded =
|
||||||
|
request->getParam(ESPHOME_F("data"))->value().c_str(); // NOLINT(readability-redundant-string-cstr)
|
||||||
|
|
||||||
|
// Validate base64url is not empty
|
||||||
|
if (encoded.empty()) {
|
||||||
|
request->send(400, ESPHOME_F("text/plain"), "Empty 'data' parameter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
// ESP8266 is single-threaded, call directly
|
||||||
|
call.set_raw_timings_base64url(encoded);
|
||||||
|
call.perform();
|
||||||
|
#else
|
||||||
|
// Defer to main loop for thread safety. Move encoded string into lambda to ensure
|
||||||
|
// it outlives the call - set_raw_timings_base64url stores a pointer, so the string
|
||||||
|
// must remain valid until perform() completes.
|
||||||
|
this->defer([call, encoded = std::move(encoded)]() mutable {
|
||||||
|
call.set_raw_timings_base64url(encoded);
|
||||||
|
call.perform();
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
request->send(200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
request->send(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WebServer::infrared_all_json_generator(WebServer *web_server, void *source) {
|
||||||
|
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
|
return web_server->infrared_json_(static_cast<infrared::Infrared *>(source), DETAIL_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WebServer::infrared_json_(infrared::Infrared *obj, JsonDetail start_config) {
|
||||||
|
json::JsonBuilder builder;
|
||||||
|
JsonObject root = builder.root();
|
||||||
|
|
||||||
|
set_json_icon_state_value(root, obj, "infrared", "", 0, start_config);
|
||||||
|
|
||||||
|
auto traits = obj->get_traits();
|
||||||
|
|
||||||
|
root[ESPHOME_F("supports_transmitter")] = traits.get_supports_transmitter();
|
||||||
|
root[ESPHOME_F("supports_receiver")] = traits.get_supports_receiver();
|
||||||
|
|
||||||
|
if (start_config == DETAIL_ALL) {
|
||||||
|
this->add_sorting_info_(root, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.serialize();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void WebServer::on_event(event::Event *obj) {
|
void WebServer::on_event(event::Event *obj) {
|
||||||
if (!this->include_internal_ && obj->is_internal())
|
if (!this->include_internal_ && obj->is_internal())
|
||||||
@@ -2035,7 +2152,7 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->defer([obj]() mutable { obj->perform(); });
|
DEFER_ACTION(obj, obj->perform());
|
||||||
request->send(200);
|
request->send(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2074,24 +2191,21 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
|
|||||||
const auto &url = request->url();
|
const auto &url = request->url();
|
||||||
const auto method = request->method();
|
const auto method = request->method();
|
||||||
|
|
||||||
// Static URL checks
|
// Static URL checks - use ESPHOME_F to keep strings in flash on ESP8266
|
||||||
static const char *const STATIC_URLS[] = {
|
if (url == ESPHOME_F("/"))
|
||||||
"/",
|
return true;
|
||||||
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
|
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
|
||||||
"/events",
|
if (url == ESPHOME_F("/events"))
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||||
"/0.css",
|
if (url == ESPHOME_F("/0.css"))
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||||
"/0.js",
|
if (url == ESPHOME_F("/0.js"))
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto &static_url : STATIC_URLS) {
|
|
||||||
if (url == static_url)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
|
#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
|
||||||
if (method == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network")))
|
if (method == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network")))
|
||||||
@@ -2111,90 +2225,100 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
|
|||||||
if (!is_get_or_post)
|
if (!is_get_or_post)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Use lookup tables for domain checks
|
// Check GET-only domains - use ESPHOME_F to keep strings in flash on ESP8266
|
||||||
static const char *const GET_ONLY_DOMAINS[] = {
|
if (is_get) {
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
"sensor",
|
if (match.domain_equals(ESPHOME_F("sensor")))
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
"binary_sensor",
|
if (match.domain_equals(ESPHOME_F("binary_sensor")))
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
"text_sensor",
|
if (match.domain_equals(ESPHOME_F("text_sensor")))
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
"event",
|
if (match.domain_equals(ESPHOME_F("event")))
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
};
|
|
||||||
|
|
||||||
static const char *const GET_POST_DOMAINS[] = {
|
|
||||||
#ifdef USE_SWITCH
|
|
||||||
"switch",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_BUTTON
|
|
||||||
"button",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_FAN
|
|
||||||
"fan",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_LIGHT
|
|
||||||
"light",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_COVER
|
|
||||||
"cover",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_NUMBER
|
|
||||||
"number",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_DATETIME_DATE
|
|
||||||
"date",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_DATETIME_TIME
|
|
||||||
"time",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_DATETIME_DATETIME
|
|
||||||
"datetime",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_TEXT
|
|
||||||
"text",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_SELECT
|
|
||||||
"select",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_CLIMATE
|
|
||||||
"climate",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_LOCK
|
|
||||||
"lock",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_VALVE
|
|
||||||
"valve",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
|
||||||
"alarm_control_panel",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_UPDATE
|
|
||||||
"update",
|
|
||||||
#endif
|
|
||||||
#ifdef USE_WATER_HEATER
|
|
||||||
"water_heater",
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check GET-only domains
|
|
||||||
if (is_get) {
|
|
||||||
for (const auto &domain : GET_ONLY_DOMAINS) {
|
|
||||||
if (match.domain_equals(domain))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check GET+POST domains
|
// Check GET+POST domains
|
||||||
if (is_get_or_post) {
|
if (is_get_or_post) {
|
||||||
for (const auto &domain : GET_POST_DOMAINS) {
|
#ifdef USE_SWITCH
|
||||||
if (match.domain_equals(domain))
|
if (match.domain_equals(ESPHOME_F("switch")))
|
||||||
return true;
|
return true;
|
||||||
}
|
#endif
|
||||||
|
#ifdef USE_BUTTON
|
||||||
|
if (match.domain_equals(ESPHOME_F("button")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
if (match.domain_equals(ESPHOME_F("fan")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
if (match.domain_equals(ESPHOME_F("light")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_COVER
|
||||||
|
if (match.domain_equals(ESPHOME_F("cover")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
if (match.domain_equals(ESPHOME_F("number")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATE
|
||||||
|
if (match.domain_equals(ESPHOME_F("date")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_TIME
|
||||||
|
if (match.domain_equals(ESPHOME_F("time")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
if (match.domain_equals(ESPHOME_F("datetime")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT
|
||||||
|
if (match.domain_equals(ESPHOME_F("text")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SELECT
|
||||||
|
if (match.domain_equals(ESPHOME_F("select")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
if (match.domain_equals(ESPHOME_F("climate")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LOCK
|
||||||
|
if (match.domain_equals(ESPHOME_F("lock")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_VALVE
|
||||||
|
if (match.domain_equals(ESPHOME_F("valve")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
|
if (match.domain_equals(ESPHOME_F("alarm_control_panel")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
if (match.domain_equals(ESPHOME_F("update")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_WATER_HEATER
|
||||||
|
if (match.domain_equals(ESPHOME_F("water_heater")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_INFRARED
|
||||||
|
if (match.domain_equals(ESPHOME_F("infrared")))
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -2343,6 +2467,11 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
|||||||
else if (match.domain_equals(ESPHOME_F("water_heater"))) {
|
else if (match.domain_equals(ESPHOME_F("water_heater"))) {
|
||||||
this->handle_water_heater_request(request, match);
|
this->handle_water_heater_request(request, match);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_INFRARED
|
||||||
|
else if (match.domain_equals(ESPHOME_F("infrared"))) {
|
||||||
|
this->handle_infrared_request(request, match);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
else {
|
else {
|
||||||
// No matching handler found - send 404
|
// No matching handler found - send 404
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ using ParamNameType = const __FlashStringHelper *;
|
|||||||
using ParamNameType = const char *;
|
using ParamNameType = const char *;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// ESP8266 is single-threaded, so actions can execute directly in request context.
|
||||||
|
// Multi-core platforms need to defer to main loop thread for thread safety.
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
#define DEFER_ACTION(capture, action) action
|
||||||
|
#else
|
||||||
|
#define DEFER_ACTION(capture, action) this->defer([capture]() mutable { action; })
|
||||||
|
#endif
|
||||||
|
|
||||||
/// Result of matching a URL against an entity
|
/// Result of matching a URL against an entity
|
||||||
struct EntityMatchResult {
|
struct EntityMatchResult {
|
||||||
bool matched; ///< True if entity matched the URL
|
bool matched; ///< True if entity matched the URL
|
||||||
@@ -295,7 +303,7 @@ class WebServer : public Controller,
|
|||||||
/// Handle a button request under '/button/<id>/press'.
|
/// Handle a button request under '/button/<id>/press'.
|
||||||
void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||||
|
|
||||||
static std::string button_state_json_generator(WebServer *web_server, void *source);
|
// Buttons are stateless, so there is no button_state_json_generator
|
||||||
static std::string button_all_json_generator(WebServer *web_server, void *source);
|
static std::string button_all_json_generator(WebServer *web_server, void *source);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -452,6 +460,13 @@ class WebServer : public Controller,
|
|||||||
static std::string water_heater_all_json_generator(WebServer *web_server, void *source);
|
static std::string water_heater_all_json_generator(WebServer *web_server, void *source);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_INFRARED
|
||||||
|
/// Handle an infrared request under '/infrared/<id>/transmit'.
|
||||||
|
void handle_infrared_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||||
|
|
||||||
|
static std::string infrared_all_json_generator(WebServer *web_server, void *source);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void on_event(event::Event *obj) override;
|
void on_event(event::Event *obj) override;
|
||||||
|
|
||||||
@@ -654,6 +669,9 @@ class WebServer : public Controller,
|
|||||||
#ifdef USE_WATER_HEATER
|
#ifdef USE_WATER_HEATER
|
||||||
std::string water_heater_json_(water_heater::WaterHeater *obj, JsonDetail start_config);
|
std::string water_heater_json_(water_heater::WaterHeater *obj, JsonDetail start_config);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_INFRARED
|
||||||
|
std::string infrared_json_(infrared::Infrared *obj, JsonDetail start_config);
|
||||||
|
#endif
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
std::string update_json_(update::UpdateEntity *obj, JsonDetail start_config);
|
std::string update_json_(update::UpdateEntity *obj, JsonDetail start_config);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,19 +4,13 @@
|
|||||||
/// @details The classes declared in this file can be used by the Weikai family
|
/// @details The classes declared in this file can be used by the Weikai family
|
||||||
|
|
||||||
#include "weikai.h"
|
#include "weikai.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace weikai {
|
namespace weikai {
|
||||||
|
|
||||||
static const char *const TAG = "weikai";
|
static const char *const TAG = "weikai";
|
||||||
|
|
||||||
/// @brief convert an int to binary representation as C++ std::string
|
|
||||||
/// @param val integer to convert
|
|
||||||
/// @return a std::string
|
|
||||||
inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); }
|
|
||||||
/// Convert std::string to C string
|
|
||||||
#define I2S2CS(val) (i2s(val).c_str())
|
|
||||||
|
|
||||||
/// @brief measure the time elapsed between two calls
|
/// @brief measure the time elapsed between two calls
|
||||||
/// @param last_time time of the previous call
|
/// @param last_time time of the previous call
|
||||||
/// @return the elapsed time in milliseconds
|
/// @return the elapsed time in milliseconds
|
||||||
@@ -170,17 +164,18 @@ void WeikaiComponent::test_gpio_input_() {
|
|||||||
static bool init_input{false};
|
static bool init_input{false};
|
||||||
static uint8_t state{0};
|
static uint8_t state{0};
|
||||||
uint8_t value;
|
uint8_t value;
|
||||||
|
char bin_buf[9]; // 8 binary digits + null
|
||||||
if (!init_input) {
|
if (!init_input) {
|
||||||
init_input = true;
|
init_input = true;
|
||||||
// set all pins in input mode
|
// set all pins in input mode
|
||||||
this->reg(WKREG_GPDIR, 0) = 0x00;
|
this->reg(WKREG_GPDIR, 0) = 0x00;
|
||||||
ESP_LOGI(TAG, "initializing all pins to input mode");
|
ESP_LOGI(TAG, "initializing all pins to input mode");
|
||||||
state = this->reg(WKREG_GPDAT, 0);
|
state = this->reg(WKREG_GPDAT, 0);
|
||||||
ESP_LOGI(TAG, "initial input data state = %02X (%s)", state, I2S2CS(state));
|
ESP_LOGI(TAG, "initial input data state = %02X (%s)", state, format_bin_to(bin_buf, state));
|
||||||
}
|
}
|
||||||
value = this->reg(WKREG_GPDAT, 0);
|
value = this->reg(WKREG_GPDAT, 0);
|
||||||
if (value != state) {
|
if (value != state) {
|
||||||
ESP_LOGI(TAG, "Input data changed from %02X to %02X (%s)", state, value, I2S2CS(value));
|
ESP_LOGI(TAG, "Input data changed from %02X to %02X (%s)", state, value, format_bin_to(bin_buf, value));
|
||||||
state = value;
|
state = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,6 +183,7 @@ void WeikaiComponent::test_gpio_input_() {
|
|||||||
void WeikaiComponent::test_gpio_output_() {
|
void WeikaiComponent::test_gpio_output_() {
|
||||||
static bool init_output{false};
|
static bool init_output{false};
|
||||||
static uint8_t state{0};
|
static uint8_t state{0};
|
||||||
|
char bin_buf[9]; // 8 binary digits + null
|
||||||
if (!init_output) {
|
if (!init_output) {
|
||||||
init_output = true;
|
init_output = true;
|
||||||
// set all pins in output mode
|
// set all pins in output mode
|
||||||
@@ -198,7 +194,7 @@ void WeikaiComponent::test_gpio_output_() {
|
|||||||
}
|
}
|
||||||
state = ~state;
|
state = ~state;
|
||||||
this->reg(WKREG_GPDAT, 0) = state;
|
this->reg(WKREG_GPDAT, 0) = state;
|
||||||
ESP_LOGI(TAG, "Flipping all outputs to %02X (%s)", state, I2S2CS(state));
|
ESP_LOGI(TAG, "Flipping all outputs to %02X (%s)", state, format_bin_to(bin_buf, state));
|
||||||
delay(100); // NOLINT
|
delay(100); // NOLINT
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -208,7 +204,9 @@ void WeikaiComponent::test_gpio_output_() {
|
|||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
bool WeikaiComponent::read_pin_val_(uint8_t pin) {
|
bool WeikaiComponent::read_pin_val_(uint8_t pin) {
|
||||||
this->input_state_ = this->reg(WKREG_GPDAT, 0);
|
this->input_state_ = this->reg(WKREG_GPDAT, 0);
|
||||||
ESP_LOGVV(TAG, "reading input pin %u = %u in_state %s", pin, this->input_state_ & (1 << pin), I2S2CS(input_state_));
|
char bin_buf[9];
|
||||||
|
ESP_LOGVV(TAG, "reading input pin %u = %u in_state %s", pin, this->input_state_ & (1 << pin),
|
||||||
|
format_bin_to(bin_buf, this->input_state_));
|
||||||
return this->input_state_ & (1 << pin);
|
return this->input_state_ & (1 << pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +216,9 @@ void WeikaiComponent::write_pin_val_(uint8_t pin, bool value) {
|
|||||||
} else {
|
} else {
|
||||||
this->output_state_ &= ~(1 << pin);
|
this->output_state_ &= ~(1 << pin);
|
||||||
}
|
}
|
||||||
ESP_LOGVV(TAG, "writing output pin %d with %d out_state %s", pin, uint8_t(value), I2S2CS(this->output_state_));
|
char bin_buf[9];
|
||||||
|
ESP_LOGVV(TAG, "writing output pin %d with %d out_state %s", pin, uint8_t(value),
|
||||||
|
format_bin_to(bin_buf, this->output_state_));
|
||||||
this->reg(WKREG_GPDAT, 0) = this->output_state_;
|
this->reg(WKREG_GPDAT, 0) = this->output_state_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +232,8 @@ void WeikaiComponent::set_pin_direction_(uint8_t pin, gpio::Flags flags) {
|
|||||||
ESP_LOGE(TAG, "pin %d direction invalid", pin);
|
ESP_LOGE(TAG, "pin %d direction invalid", pin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGVV(TAG, "setting pin %d direction to %d pin_config=%s", pin, flags, I2S2CS(this->pin_config_));
|
char bin_buf[9];
|
||||||
|
ESP_LOGVV(TAG, "setting pin %d direction to %d pin_config=%s", pin, flags, format_bin_to(bin_buf, this->pin_config_));
|
||||||
this->reg(WKREG_GPDIR, 0) = this->pin_config_; // TODO check ~
|
this->reg(WKREG_GPDIR, 0) = this->pin_config_; // TODO check ~
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +242,6 @@ void WeikaiGPIOPin::setup() {
|
|||||||
flags_ == gpio::FLAG_INPUT ? "Input"
|
flags_ == gpio::FLAG_INPUT ? "Input"
|
||||||
: this->flags_ == gpio::FLAG_OUTPUT ? "Output"
|
: this->flags_ == gpio::FLAG_OUTPUT ? "Output"
|
||||||
: "NOT SPECIFIED");
|
: "NOT SPECIFIED");
|
||||||
// ESP_LOGCONFIG(TAG, "Setting GPIO pins mode to '%s' %02X", I2S2CS(this->flags_), this->flags_);
|
|
||||||
this->pin_mode(this->flags_);
|
this->pin_mode(this->flags_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,8 +297,9 @@ void WeikaiChannel::set_line_param_() {
|
|||||||
break; // no parity 000x
|
break; // no parity 000x
|
||||||
}
|
}
|
||||||
this->reg(WKREG_LCR) = lcr; // write LCR
|
this->reg(WKREG_LCR) = lcr; // write LCR
|
||||||
|
char bin_buf[9];
|
||||||
ESP_LOGV(TAG, " line config: %d data_bits, %d stop_bits, parity %s register [%s]", this->data_bits_,
|
ESP_LOGV(TAG, " line config: %d data_bits, %d stop_bits, parity %s register [%s]", this->data_bits_,
|
||||||
this->stop_bits_, p2s(this->parity_), I2S2CS(lcr));
|
this->stop_bits_, p2s(this->parity_), format_bin_to(bin_buf, lcr));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeikaiChannel::set_baudrate_() {
|
void WeikaiChannel::set_baudrate_() {
|
||||||
@@ -334,7 +335,8 @@ size_t WeikaiChannel::tx_in_fifo_() {
|
|||||||
if (tfcnt == 0) {
|
if (tfcnt == 0) {
|
||||||
uint8_t const fsr = this->reg(WKREG_FSR);
|
uint8_t const fsr = this->reg(WKREG_FSR);
|
||||||
if (fsr & FSR_TFFULL) {
|
if (fsr & FSR_TFFULL) {
|
||||||
ESP_LOGVV(TAG, "tx FIFO full FSR=%s", I2S2CS(fsr));
|
char bin_buf[9];
|
||||||
|
ESP_LOGVV(TAG, "tx FIFO full FSR=%s", format_bin_to(bin_buf, fsr));
|
||||||
tfcnt = FIFO_SIZE;
|
tfcnt = FIFO_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,14 +348,15 @@ size_t WeikaiChannel::rx_in_fifo_() {
|
|||||||
size_t available = this->reg(WKREG_RFCNT);
|
size_t available = this->reg(WKREG_RFCNT);
|
||||||
uint8_t const fsr = this->reg(WKREG_FSR);
|
uint8_t const fsr = this->reg(WKREG_FSR);
|
||||||
if (fsr & (FSR_RFOE | FSR_RFLB | FSR_RFFE | FSR_RFPE)) {
|
if (fsr & (FSR_RFOE | FSR_RFLB | FSR_RFFE | FSR_RFPE)) {
|
||||||
|
char bin_buf[9];
|
||||||
if (fsr & FSR_RFOE)
|
if (fsr & FSR_RFOE)
|
||||||
ESP_LOGE(TAG, "Receive data overflow FSR=%s", I2S2CS(fsr));
|
ESP_LOGE(TAG, "Receive data overflow FSR=%s", format_bin_to(bin_buf, fsr));
|
||||||
if (fsr & FSR_RFLB)
|
if (fsr & FSR_RFLB)
|
||||||
ESP_LOGE(TAG, "Receive line break FSR=%s", I2S2CS(fsr));
|
ESP_LOGE(TAG, "Receive line break FSR=%s", format_bin_to(bin_buf, fsr));
|
||||||
if (fsr & FSR_RFFE)
|
if (fsr & FSR_RFFE)
|
||||||
ESP_LOGE(TAG, "Receive frame error FSR=%s", I2S2CS(fsr));
|
ESP_LOGE(TAG, "Receive frame error FSR=%s", format_bin_to(bin_buf, fsr));
|
||||||
if (fsr & FSR_RFPE)
|
if (fsr & FSR_RFPE)
|
||||||
ESP_LOGE(TAG, "Receive parity error FSR=%s", I2S2CS(fsr));
|
ESP_LOGE(TAG, "Receive parity error FSR=%s", format_bin_to(bin_buf, fsr));
|
||||||
}
|
}
|
||||||
if ((available == 0) && (fsr & FSR_RFDAT)) {
|
if ((available == 0) && (fsr & FSR_RFDAT)) {
|
||||||
// here we should be very careful because we can have something like this:
|
// here we should be very careful because we can have something like this:
|
||||||
@@ -362,11 +365,13 @@ size_t WeikaiChannel::rx_in_fifo_() {
|
|||||||
// - so to be sure we need to do another read of RFCNT and if it is still zero -> buffer full
|
// - so to be sure we need to do another read of RFCNT and if it is still zero -> buffer full
|
||||||
available = this->reg(WKREG_RFCNT);
|
available = this->reg(WKREG_RFCNT);
|
||||||
if (available == 0) { // still zero ?
|
if (available == 0) { // still zero ?
|
||||||
ESP_LOGV(TAG, "rx FIFO is full FSR=%s", I2S2CS(fsr));
|
char bin_buf[9];
|
||||||
|
ESP_LOGV(TAG, "rx FIFO is full FSR=%s", format_bin_to(bin_buf, fsr));
|
||||||
available = FIFO_SIZE;
|
available = FIFO_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGVV(TAG, "rx FIFO contain %d bytes - FSR status=%s", available, I2S2CS(fsr));
|
char bin_buf2[9];
|
||||||
|
ESP_LOGVV(TAG, "rx FIFO contain %d bytes - FSR status=%s", available, format_bin_to(bin_buf2, fsr));
|
||||||
return available;
|
return available;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
/// wk2132_i2c, wk2168_i2c, wk2204_i2c, wk2212_i2c
|
/// wk2132_i2c, wk2168_i2c, wk2204_i2c, wk2212_i2c
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <bitset>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
|||||||
@@ -10,13 +10,6 @@ namespace weikai_spi {
|
|||||||
using namespace weikai;
|
using namespace weikai;
|
||||||
static const char *const TAG = "weikai_spi";
|
static const char *const TAG = "weikai_spi";
|
||||||
|
|
||||||
/// @brief convert an int to binary representation as C++ std::string
|
|
||||||
/// @param val integer to convert
|
|
||||||
/// @return a std::string
|
|
||||||
inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); }
|
|
||||||
/// Convert std::string to C string
|
|
||||||
#define I2S2CS(val) (i2s(val).c_str())
|
|
||||||
|
|
||||||
/// @brief measure the time elapsed between two calls
|
/// @brief measure the time elapsed between two calls
|
||||||
/// @param last_time time of the previous call
|
/// @param last_time time of the previous call
|
||||||
/// @return the elapsed time in microseconds
|
/// @return the elapsed time in microseconds
|
||||||
@@ -107,7 +100,8 @@ uint8_t WeikaiRegisterSPI::read_reg() const {
|
|||||||
spi_comp->write_byte(cmd);
|
spi_comp->write_byte(cmd);
|
||||||
uint8_t val = spi_comp->read_byte();
|
uint8_t val = spi_comp->read_byte();
|
||||||
spi_comp->disable();
|
spi_comp->disable();
|
||||||
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(cmd), cmd,
|
char bin_buf[9];
|
||||||
|
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", format_bin_to(bin_buf, cmd), cmd,
|
||||||
reg_to_str(this->register_, this->comp_->page1()), this->channel_, val);
|
reg_to_str(this->register_, this->comp_->page1()), this->channel_, val);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
@@ -120,8 +114,9 @@ void WeikaiRegisterSPI::read_fifo(uint8_t *data, size_t length) const {
|
|||||||
spi_comp->read_array(data, length);
|
spi_comp->read_array(data, length);
|
||||||
spi_comp->disable();
|
spi_comp->disable();
|
||||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||||
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_,
|
char bin_buf[9];
|
||||||
length);
|
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_fifo() cmd=%s(%02X) ch=%d len=%d buffer", format_bin_to(bin_buf, cmd), cmd,
|
||||||
|
this->channel_, length);
|
||||||
print_buffer(data, length);
|
print_buffer(data, length);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -132,8 +127,9 @@ void WeikaiRegisterSPI::write_reg(uint8_t value) {
|
|||||||
spi_comp->enable();
|
spi_comp->enable();
|
||||||
spi_comp->write_array(buf, 2);
|
spi_comp->write_array(buf, 2);
|
||||||
spi_comp->disable();
|
spi_comp->disable();
|
||||||
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(buf[0]), buf[0],
|
char bin_buf[9];
|
||||||
reg_to_str(this->register_, this->comp_->page1()), this->channel_, buf[1]);
|
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", format_bin_to(bin_buf, buf[0]),
|
||||||
|
buf[0], reg_to_str(this->register_, this->comp_->page1()), this->channel_, buf[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeikaiRegisterSPI::write_fifo(uint8_t *data, size_t length) {
|
void WeikaiRegisterSPI::write_fifo(uint8_t *data, size_t length) {
|
||||||
@@ -145,8 +141,9 @@ void WeikaiRegisterSPI::write_fifo(uint8_t *data, size_t length) {
|
|||||||
spi_comp->disable();
|
spi_comp->disable();
|
||||||
|
|
||||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||||
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_,
|
char bin_buf[9];
|
||||||
length);
|
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_fifo() cmd=%s(%02X) ch=%d len=%d buffer", format_bin_to(bin_buf, cmd), cmd,
|
||||||
|
this->channel_, length);
|
||||||
print_buffer(data, length);
|
print_buffer(data, length);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
/// wk2124_spi, wk2132_spi, wk2168_spi, wk2204_spi, wk2212_spi,
|
/// wk2124_spi, wk2132_spi, wk2168_spi, wk2204_spi, wk2212_spi,
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <bitset>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user