mirror of
https://github.com/esphome/esphome.git
synced 2026-01-20 01:49:11 -07:00
Compare commits
2 Commits
lazy_callb
...
str_sprint
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06c619b2e0 | ||
|
|
71c922bb60 |
@@ -1,96 +0,0 @@
|
||||
---
|
||||
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 }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: venv
|
||||
# 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 }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Save Python virtual environment cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: venv
|
||||
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 }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Restore components graph cache
|
||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
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
|
||||
- name: Save components graph cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||
@@ -245,7 +245,7 @@ jobs:
|
||||
python-version: "3.13"
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -334,14 +334,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -413,14 +413,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
@@ -502,14 +502,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
@@ -735,7 +735,7 @@ jobs:
|
||||
- name: Restore cached memory analysis
|
||||
id: cache-memory-analysis
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -759,7 +759,7 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
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
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -847,7 +847,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache platformio
|
||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.13
|
||||
rev: v0.14.11
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
import argparse
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import getpass
|
||||
@@ -223,13 +222,8 @@ def choose_upload_log_host(
|
||||
else:
|
||||
resolved.append(device)
|
||||
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(
|
||||
f"All specified devices {defaults} could not be resolved. "
|
||||
f"Is the device connected to the network? {hint}"
|
||||
f"All specified devices {defaults} could not be resolved. Is the device connected to the network?"
|
||||
)
|
||||
return resolved
|
||||
|
||||
@@ -937,21 +931,11 @@ def command_dashboard(args: ArgsProtocol) -> int | None:
|
||||
return dashboard.start_dashboard(args)
|
||||
|
||||
|
||||
def run_multiple_configs(
|
||||
files: list, command_builder: Callable[[str], list[str]]
|
||||
) -> int:
|
||||
"""Run a command for each configuration file in a subprocess.
|
||||
|
||||
Args:
|
||||
files: List of configuration files to process.
|
||||
command_builder: Callable that takes a file path and returns a command list.
|
||||
|
||||
Returns:
|
||||
Number of failed files.
|
||||
"""
|
||||
def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration)
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
@@ -961,19 +945,17 @@ def run_multiple_configs(
|
||||
safe_print(f"{half_line}{middle_text}{half_line}")
|
||||
|
||||
for f in files:
|
||||
f_path = Path(f) if not isinstance(f, Path) else f
|
||||
|
||||
if any(f_path.name == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", f_path)
|
||||
continue
|
||||
|
||||
safe_print(f"Processing {color(AnsiFore.CYAN, str(f))}")
|
||||
safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
|
||||
safe_print("-" * twidth)
|
||||
safe_print()
|
||||
|
||||
cmd = command_builder(f)
|
||||
rc = run_external_process(*cmd)
|
||||
|
||||
if CORE.dashboard:
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
else:
|
||||
rc = run_external_process(
|
||||
"esphome", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
if rc == 0:
|
||||
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
|
||||
success[f] = True
|
||||
@@ -988,8 +970,6 @@ def run_multiple_configs(
|
||||
print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]")
|
||||
failed = 0
|
||||
for f in files:
|
||||
if f not in success:
|
||||
continue # Skipped file
|
||||
if success[f]:
|
||||
safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
||||
else:
|
||||
@@ -998,17 +978,6 @@ def run_multiple_configs(
|
||||
return failed
|
||||
|
||||
|
||||
def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
files = list_yaml_files(args.configuration)
|
||||
|
||||
def build_command(f):
|
||||
if CORE.dashboard:
|
||||
return ["esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"]
|
||||
return ["esphome", "run", f, "--no-logs", "--device", "OTA"]
|
||||
|
||||
return run_multiple_configs(files, build_command)
|
||||
|
||||
|
||||
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
import json
|
||||
|
||||
@@ -1559,48 +1528,38 @@ def run_esphome(argv):
|
||||
|
||||
_LOGGER.info("ESPHome %s", const.__version__)
|
||||
|
||||
# Multiple configurations: use subprocesses to avoid state leakage
|
||||
# between compilations (e.g., LVGL touchscreen state in module globals)
|
||||
if len(args.configuration) > 1:
|
||||
# Build command by reusing argv, replacing all configs with single file
|
||||
# argv[0] is the program path, skip it since we prefix with "esphome"
|
||||
def build_command(f):
|
||||
return (
|
||||
["esphome"]
|
||||
+ [arg for arg in argv[1:] if arg not in args.configuration]
|
||||
+ [str(f)]
|
||||
)
|
||||
for conf_path in args.configuration:
|
||||
conf_path = Path(conf_path)
|
||||
if any(conf_path.name == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
||||
continue
|
||||
|
||||
return run_multiple_configs(args.configuration, build_command)
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
# Single configuration
|
||||
conf_path = Path(args.configuration[0])
|
||||
if any(conf_path.name == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
||||
return 0
|
||||
# For logs command, skip updating external components
|
||||
skip_external = args.command == "logs"
|
||||
config = read_config(
|
||||
dict(args.substitution) if args.substitution else {},
|
||||
skip_external_update=skip_external,
|
||||
)
|
||||
if config is None:
|
||||
return 2
|
||||
CORE.config = config
|
||||
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
safe_print(f"Unknown command {args.command}")
|
||||
|
||||
# For logs command, skip updating external components
|
||||
skip_external = args.command == "logs"
|
||||
config = read_config(
|
||||
dict(args.substitution) if args.substitution else {},
|
||||
skip_external_update=skip_external,
|
||||
)
|
||||
if config is None:
|
||||
return 2
|
||||
CORE.config = config
|
||||
try:
|
||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
safe_print(f"Unknown command {args.command}")
|
||||
return 1
|
||||
|
||||
try:
|
||||
return POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
CORE.reset()
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -22,7 +22,7 @@ from .helpers import (
|
||||
map_section_name,
|
||||
parse_symbol_line,
|
||||
)
|
||||
from .toolchain import find_tool, resolve_tool_path, run_tool
|
||||
from .toolchain import find_tool, run_tool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.platformio_api import IDEData
|
||||
@@ -132,12 +132,6 @@ class MemoryAnalyzer:
|
||||
readelf_path = readelf_path or idedata.readelf_path
|
||||
_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.readelf_path = readelf_path or "readelf"
|
||||
self.external_components = external_components or set()
|
||||
|
||||
@@ -9,61 +9,11 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::")
|
||||
# Maps standard section names to their various platform-specific variants
|
||||
# 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
|
||||
#
|
||||
# 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 = {
|
||||
".text": frozenset(
|
||||
[
|
||||
".text",
|
||||
".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",
|
||||
]
|
||||
),
|
||||
".text": frozenset([".text", ".iram"]),
|
||||
".rodata": frozenset([".rodata"]),
|
||||
".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss"
|
||||
".data": frozenset([".data", ".dram"]),
|
||||
}
|
||||
|
||||
# Section to ComponentMemory attribute mapping
|
||||
|
||||
@@ -94,13 +94,13 @@ def parse_symbol_line(line: str) -> tuple[str, str, int, str] | None:
|
||||
return None
|
||||
|
||||
# Find section, size, and name
|
||||
# Try each part as a potential section name
|
||||
for i, part in enumerate(parts):
|
||||
# Skip parts that are clearly flags, addresses, or other metadata
|
||||
# Sections start with '.' (standard ELF) or are known section names (Zephyr)
|
||||
if not part.startswith("."):
|
||||
continue
|
||||
|
||||
section = map_section_name(part)
|
||||
if not section:
|
||||
continue
|
||||
break
|
||||
|
||||
# Need at least size field after section
|
||||
if i + 1 >= len(parts):
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -18,82 +17,10 @@ TOOLCHAIN_PREFIXES = [
|
||||
"xtensa-lx106-elf-", # ESP8266
|
||||
"xtensa-esp32-elf-", # ESP32
|
||||
"xtensa-esp-elf-", # ESP32 (newer IDF)
|
||||
"arm-zephyr-eabi-", # nRF52/Zephyr SDK
|
||||
"arm-none-eabi-", # Generic ARM (RP2040, etc.)
|
||||
"", # 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(
|
||||
tool_name: str,
|
||||
objdump_path: str | None = None,
|
||||
@@ -101,8 +28,7 @@ def find_tool(
|
||||
"""Find a toolchain tool by name.
|
||||
|
||||
First tries to derive the tool path from objdump_path (if provided),
|
||||
then searches PlatformIO package directories (for cross-compile toolchains),
|
||||
and finally falls back to searching for platform-specific tools in PATH.
|
||||
then falls back to searching for platform-specific tools.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool (e.g., "objdump", "nm", "c++filt")
|
||||
@@ -121,13 +47,7 @@ def find_tool(
|
||||
_LOGGER.debug("Found %s at: %s", tool_name, potential_path)
|
||||
return potential_path
|
||||
|
||||
# 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)
|
||||
# Try platform-specific tools
|
||||
for prefix in TOOLCHAIN_PREFIXES:
|
||||
cmd = f"{prefix}{tool_name}"
|
||||
try:
|
||||
|
||||
@@ -69,7 +69,6 @@ from esphome.cpp_types import ( # noqa: F401
|
||||
JsonObjectConst,
|
||||
Parented,
|
||||
PollingComponent,
|
||||
StringRef,
|
||||
arduino_json_ns,
|
||||
bool_,
|
||||
const_char_ptr,
|
||||
|
||||
@@ -31,8 +31,7 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
|
||||
this->last_update_ = millis();
|
||||
if (state != this->current_state_) {
|
||||
auto prev_state = this->current_state_;
|
||||
ESP_LOGD(TAG, "'%s' >> %s (was %s)", this->get_name().c_str(),
|
||||
LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
|
||||
ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
|
||||
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
|
||||
this->current_state_ = state;
|
||||
// Single state callback - triggers check get_state() for specific states
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
#include "am43_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
||||
const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
|
||||
|
||||
std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
|
||||
char buf[64];
|
||||
memset(buf, 0, 64);
|
||||
for (int i = 0; i < len; i++)
|
||||
sprintf(&buf[i * 2], "%02x", data[i]);
|
||||
std::string ret = buf;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_battery_level_request() {
|
||||
uint8_t data = 0x1;
|
||||
return this->encode_(0xA2, &data, 1);
|
||||
@@ -64,9 +73,7 @@ Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length)
|
||||
memcpy(&this->packet_.data[7], data, length);
|
||||
this->packet_.length = length + 7;
|
||||
this->checksum_();
|
||||
char hex_buf[format_hex_size(sizeof(this->packet_.data))];
|
||||
ESP_LOGV("am43", "ENC(%d): 0x%s", this->packet_.length,
|
||||
format_hex_to(hex_buf, this->packet_.data, this->packet_.length));
|
||||
ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str());
|
||||
return &this->packet_;
|
||||
}
|
||||
|
||||
@@ -81,8 +88,7 @@ void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
|
||||
this->has_set_state_response_ = false;
|
||||
this->has_position_ = false;
|
||||
this->has_pin_response_ = false;
|
||||
char hex_buf[format_hex_size(24)]; // Max expected packet size
|
||||
ESP_LOGV("am43", "DEC(%d): 0x%s", length, format_hex_to(hex_buf, data, length));
|
||||
ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str());
|
||||
|
||||
if (length < 2 || data[0] != 0x9a)
|
||||
return;
|
||||
|
||||
@@ -18,31 +18,31 @@ AnovaPacket *AnovaCodec::clean_packet_() {
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_device_status_request() {
|
||||
this->current_query_ = READ_DEVICE_STATUS;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DEVICE_STATUS);
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_DEVICE_STATUS);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_target_temp_request() {
|
||||
this->current_query_ = READ_TARGET_TEMPERATURE;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_TARGET_TEMP);
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_TARGET_TEMP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_current_temp_request() {
|
||||
this->current_query_ = READ_CURRENT_TEMPERATURE;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_CURRENT_TEMP);
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_unit_request() {
|
||||
this->current_query_ = READ_UNIT;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_UNIT);
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_read_data_request() {
|
||||
this->current_query_ = READ_DATA;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DATA);
|
||||
sprintf((char *) this->packet_.data, "%s", CMD_READ_DATA);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
@@ -50,25 +50,25 @@ AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) {
|
||||
this->current_query_ = SET_TARGET_TEMPERATURE;
|
||||
if (this->fahrenheit_)
|
||||
temperature = ctof(temperature);
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TARGET_TEMP, temperature);
|
||||
sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
|
||||
this->current_query_ = SET_UNIT;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TEMP_UNIT, unit);
|
||||
sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_start_request() {
|
||||
this->current_query_ = START;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_START);
|
||||
sprintf((char *) this->packet_.data, CMD_START);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
AnovaPacket *AnovaCodec::get_stop_request() {
|
||||
this->current_query_ = STOP;
|
||||
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_STOP);
|
||||
sprintf((char *) this->packet_.data, CMD_STOP);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
|
||||
@@ -1712,16 +1712,17 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
|
||||
}
|
||||
|
||||
// Create null-terminated state for callback (parse_number needs null-termination)
|
||||
// HA state max length is 255 characters, but attributes can be much longer
|
||||
// Use stack buffer for common case (states), heap fallback for large attributes
|
||||
size_t state_len = msg.state.size();
|
||||
SmallBufferWithHeapFallback<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
|
||||
char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
|
||||
if (state_len > 0) {
|
||||
memcpy(state_buf, msg.state.c_str(), state_len);
|
||||
// HA state max length is 255, so 256 byte buffer covers all cases
|
||||
char state_buf[256];
|
||||
size_t copy_len = msg.state.size();
|
||||
if (copy_len >= sizeof(state_buf)) {
|
||||
copy_len = sizeof(state_buf) - 1; // Truncate to leave space for null terminator
|
||||
}
|
||||
state_buf[state_len] = '\0';
|
||||
it.callback(StringRef(state_buf, state_len));
|
||||
if (copy_len > 0) {
|
||||
memcpy(state_buf, msg.state.c_str(), copy_len);
|
||||
}
|
||||
state_buf[copy_len] = '\0';
|
||||
it.callback(StringRef(state_buf, copy_len));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -241,10 +241,8 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
if (obj->is_internal()) \
|
||||
return; \
|
||||
for (auto &c : this->clients_) { \
|
||||
if (c->flags_.state_subscription) \
|
||||
c->send_##entity_name##_state(obj); \
|
||||
} \
|
||||
for (auto &c : this->clients_) \
|
||||
c->send_##entity_name##_state(obj); \
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -323,10 +321,8 @@ API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
|
||||
void APIServer::on_event(event::Event *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_) {
|
||||
if (c->flags_.state_subscription)
|
||||
c->send_event(obj);
|
||||
}
|
||||
for (auto &c : this->clients_)
|
||||
c->send_event(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -335,10 +331,8 @@ void APIServer::on_event(event::Event *obj) {
|
||||
void APIServer::on_update(update::UpdateEntity *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_) {
|
||||
if (c->flags_.state_subscription)
|
||||
c->send_update_state(obj);
|
||||
}
|
||||
for (auto &c : this->clients_)
|
||||
c->send_update_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -558,10 +552,8 @@ bool APIServer::clear_noise_psk(bool make_active) {
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto &client : this->clients_) {
|
||||
if (!client->flags_.remove && client->is_authenticated()) {
|
||||
if (!client->flags_.remove && client->is_authenticated())
|
||||
client->send_time_request();
|
||||
return; // Only request from one client to avoid clock conflicts
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -48,14 +48,14 @@ uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size
|
||||
}
|
||||
uint32_t field_length = res->as_uint32();
|
||||
ptr += consumed;
|
||||
if (field_length > static_cast<size_t>(end - ptr)) {
|
||||
if (ptr + field_length > end) {
|
||||
return count; // Out of bounds
|
||||
}
|
||||
ptr += field_length;
|
||||
break;
|
||||
}
|
||||
case WIRE_TYPE_FIXED32: { // 32-bit - skip 4 bytes
|
||||
if (end - ptr < 4) {
|
||||
if (ptr + 4 > end) {
|
||||
return count;
|
||||
}
|
||||
ptr += 4;
|
||||
@@ -110,7 +110,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
}
|
||||
uint32_t field_length = res->as_uint32();
|
||||
ptr += consumed;
|
||||
if (field_length > static_cast<size_t>(end - ptr)) {
|
||||
if (ptr + field_length > end) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
@@ -121,7 +121,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
break;
|
||||
}
|
||||
case WIRE_TYPE_FIXED32: { // 32-bit
|
||||
if (end - ptr < 4) {
|
||||
if (ptr + 4 > end) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -158,14 +158,12 @@ void ATM90E32Component::setup() {
|
||||
|
||||
if (this->enable_offset_calibration_) {
|
||||
// Initialize flash storage for offset calibrations
|
||||
uint32_t o_hash = fnv1_hash("_offset_calibration_");
|
||||
o_hash = fnv1_hash_extend(o_hash, this->cs_summary_);
|
||||
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_);
|
||||
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
||||
this->restore_offset_calibrations_();
|
||||
|
||||
// Initialize flash storage for power offset calibrations
|
||||
uint32_t po_hash = fnv1_hash("_power_offset_calibration_");
|
||||
po_hash = fnv1_hash_extend(po_hash, this->cs_summary_);
|
||||
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_);
|
||||
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
||||
this->restore_power_offset_calibrations_();
|
||||
} else {
|
||||
@@ -185,8 +183,7 @@ void ATM90E32Component::setup() {
|
||||
|
||||
if (this->enable_gain_calibration_) {
|
||||
// Initialize flash storage for gain calibration
|
||||
uint32_t g_hash = fnv1_hash("_gain_calibration_");
|
||||
g_hash = fnv1_hash_extend(g_hash, this->cs_summary_);
|
||||
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_);
|
||||
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
||||
this->restore_gain_calibrations_();
|
||||
|
||||
|
||||
@@ -185,16 +185,18 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (str_endswith_ignore_case(url, ".wav")) {
|
||||
std::string url_string = str_lower_case(url);
|
||||
|
||||
if (str_endswith(url_string, ".wav")) {
|
||||
file_type = AudioFileType::WAV;
|
||||
}
|
||||
#ifdef USE_AUDIO_MP3_SUPPORT
|
||||
else if (str_endswith_ignore_case(url, ".mp3")) {
|
||||
else if (str_endswith(url_string, ".mp3")) {
|
||||
file_type = AudioFileType::MP3;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AUDIO_FLAC_SUPPORT
|
||||
else if (str_endswith_ignore_case(url, ".flac")) {
|
||||
else if (str_endswith(url_string, ".flac")) {
|
||||
file_type = AudioFileType::FLAC;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -44,7 +44,7 @@ bool BinarySensor::set_new_state(const optional<bool> &new_state) {
|
||||
#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_binary_sensor_update(this);
|
||||
#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 false;
|
||||
|
||||
@@ -44,7 +44,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
||||
web_server_base.WebServerBase
|
||||
),
|
||||
cv.Optional(CONF_COMPRESSION, default="gzip"): cv.one_of("gzip", "br"),
|
||||
cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on(
|
||||
|
||||
@@ -81,8 +81,8 @@ void CCS811Component::setup() {
|
||||
bootloader_version, application_version);
|
||||
if (this->version_ != nullptr) {
|
||||
char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room
|
||||
buf_append_printf(version, sizeof(version), 0, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15),
|
||||
(application_version >> 8 & 15), (application_version >> 4 & 15), application_version);
|
||||
sprintf(version, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15), (application_version >> 8 & 15),
|
||||
(application_version >> 4 & 15), application_version);
|
||||
ESP_LOGD(TAG, "publishing version state: %s", version);
|
||||
this->version_->publish_state(version);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pi
|
||||
|
||||
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); }
|
||||
size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const {
|
||||
return buf_append_printf(buffer, len, 0, "EXIO%u via CH422G", this->pin_);
|
||||
return snprintf(buffer, len, "EXIO%u via CH422G", this->pin_);
|
||||
}
|
||||
void CH422GGPIOPin::set_flags(gpio::Flags flags) {
|
||||
flags_ = flags;
|
||||
|
||||
@@ -436,7 +436,7 @@ void Climate::save_state_() {
|
||||
}
|
||||
|
||||
void Climate::publish_state() {
|
||||
ESP_LOGD(TAG, "'%s' >>", this->name_.c_str());
|
||||
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
|
||||
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->tilt = clamp(this->tilt, 0.0f, 1.0f);
|
||||
|
||||
ESP_LOGD(TAG, "'%s' >>", this->name_.c_str());
|
||||
ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
if (traits.get_supports_position()) {
|
||||
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
|
||||
|
||||
@@ -76,6 +76,7 @@ class CS5460AComponent : public Component,
|
||||
void restart() { restart_(); }
|
||||
|
||||
void setup() override;
|
||||
void loop() override {}
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -207,24 +207,20 @@ void CSE7766Component::parse_data_() {
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
{
|
||||
// Buffer: 7 + 15 + 33 + 15 + 25 = 95 chars max + null, rounded to 128 for safety margin.
|
||||
// Float sizes with %.4f can be up to 11 chars for large values (e.g., 999999.9999).
|
||||
char buf[128];
|
||||
size_t pos = buf_append_printf(buf, sizeof(buf), 0, "Parsed:");
|
||||
std::string buf = "Parsed:";
|
||||
if (have_voltage) {
|
||||
pos = buf_append_printf(buf, sizeof(buf), pos, " V=%.4fV", voltage);
|
||||
buf += str_sprintf(" V=%fV", voltage);
|
||||
}
|
||||
if (have_current) {
|
||||
pos = buf_append_printf(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f,
|
||||
calculated_current * 1000.0f);
|
||||
buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f);
|
||||
}
|
||||
if (have_power) {
|
||||
pos = buf_append_printf(buf, sizeof(buf), pos, " P=%.4fW", power);
|
||||
buf += str_sprintf(" P=%fW", power);
|
||||
}
|
||||
if (energy != 0.0f) {
|
||||
buf_append_printf(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses);
|
||||
buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses);
|
||||
}
|
||||
ESP_LOGVV(TAG, "%s", buf);
|
||||
ESP_LOGVV(TAG, "%s", buf.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -258,9 +258,8 @@ bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
}
|
||||
|
||||
char buf[DAIKIN_STATE_FRAME_SIZE * 3 + 1] = {0};
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i < DAIKIN_STATE_FRAME_SIZE; i++) {
|
||||
pos = buf_append_printf(buf, sizeof(buf), pos, "%02x ", frame[i]);
|
||||
sprintf(buf, "%s%02x ", buf, frame[i]);
|
||||
}
|
||||
ESP_LOGD(TAG, "FRAME %s", buf);
|
||||
|
||||
@@ -350,9 +349,8 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
|
||||
valid_daikin_frame = true;
|
||||
size_t bytes_count = data.size() / 2 / 8;
|
||||
size_t buf_size = bytes_count * 3 + 1;
|
||||
std::unique_ptr<char[]> buf(new char[buf_size]()); // value-initialize (zero-fill)
|
||||
size_t buf_pos = 0;
|
||||
std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]);
|
||||
buf[0] = '\0';
|
||||
for (size_t i = 0; i < bytes_count; i++) {
|
||||
uint8_t byte = 0;
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
@@ -363,19 +361,19 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
buf_pos = buf_append_printf(buf.get(), buf_size, buf_pos, "%02x ", byte);
|
||||
sprintf(buf.get(), "%s%02x ", buf.get(), byte);
|
||||
}
|
||||
ESP_LOGD(TAG, "WHOLE FRAME %s size: %d", buf.get(), data.size());
|
||||
}
|
||||
if (!valid_daikin_frame) {
|
||||
char sbuf[16 * 10 + 1] = {0};
|
||||
size_t sbuf_pos = 0;
|
||||
char sbuf[16 * 10 + 1];
|
||||
sbuf[0] = '\0';
|
||||
for (size_t j = 0; j < static_cast<size_t>(data.size()); j++) {
|
||||
if ((j - 2) % 16 == 0) {
|
||||
if (j > 0) {
|
||||
ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf);
|
||||
}
|
||||
sbuf_pos = 0;
|
||||
sbuf[0] = '\0';
|
||||
}
|
||||
char type_ch = ' ';
|
||||
// debug_tolerance = 25%
|
||||
@@ -403,10 +401,9 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
type_ch = '0';
|
||||
|
||||
if (abs(data[j]) > 100000) {
|
||||
sbuf_pos = buf_append_printf(sbuf, sizeof(sbuf), sbuf_pos, "%-5d[%c] ", data[j] > 0 ? 99999 : -99999, type_ch);
|
||||
sprintf(sbuf, "%s%-5d[%c] ", sbuf, data[j] > 0 ? 99999 : -99999, type_ch);
|
||||
} else {
|
||||
sbuf_pos =
|
||||
buf_append_printf(sbuf, sizeof(sbuf), sbuf_pos, "%-5d[%c] ", (int) (round(data[j] / 10.) * 10), type_ch);
|
||||
sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch);
|
||||
}
|
||||
if (j + 1 == static_cast<size_t>(data.size())) {
|
||||
ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf);
|
||||
|
||||
@@ -44,7 +44,7 @@ void DallasTemperatureSensor::update() {
|
||||
|
||||
this->send_command_(DALLAS_COMMAND_START_CONVERSION);
|
||||
|
||||
this->set_timeout(this->get_address_name().c_str(), this->millis_to_wait_for_conversion_(), [this] {
|
||||
this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] {
|
||||
if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
|
||||
@@ -30,7 +30,7 @@ void DateEntity::publish_state() {
|
||||
return;
|
||||
}
|
||||
this->set_has_state(true);
|
||||
ESP_LOGD(TAG, "'%s' >> %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_date_update(this);
|
||||
|
||||
@@ -45,8 +45,8 @@ void DateTimeEntity::publish_state() {
|
||||
return;
|
||||
}
|
||||
this->set_has_state(true);
|
||||
ESP_LOGD(TAG, "'%s' >> %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, this->month_,
|
||||
this->day_, this->hour_, this->minute_, this->second_);
|
||||
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
|
||||
this->month_, this->day_, this->hour_, this->minute_, this->second_);
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_datetime_update(this);
|
||||
|
||||
@@ -26,7 +26,8 @@ void TimeEntity::publish_state() {
|
||||
return;
|
||||
}
|
||||
this->set_has_state(true);
|
||||
ESP_LOGD(TAG, "'%s' >> %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, this->second_);
|
||||
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
|
||||
this->second_);
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_time_update(this);
|
||||
|
||||
@@ -30,7 +30,7 @@ void DebugComponent::dump_config() {
|
||||
|
||||
char device_info_buffer[DEVICE_INFO_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
|
||||
size_t pos = buf_append_printf(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION);
|
||||
size_t pos = buf_append(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION);
|
||||
|
||||
this->free_heap_ = get_free_heap_();
|
||||
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/macros.h"
|
||||
#include <span>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
@@ -19,7 +25,40 @@ namespace debug {
|
||||
static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256;
|
||||
static constexpr size_t RESET_REASON_BUFFER_SIZE = 128;
|
||||
|
||||
// buf_append_printf is now provided by esphome/core/helpers.h
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM)
|
||||
// Format strings must be wrapped with PSTR() macro
|
||||
inline size_t buf_append_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) {
|
||||
if (pos >= size) {
|
||||
return size;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int written = vsnprintf_P(buf + pos, size - pos, fmt, args);
|
||||
va_end(args);
|
||||
if (written < 0) {
|
||||
return pos; // encoding error
|
||||
}
|
||||
return std::min(pos + static_cast<size_t>(written), size);
|
||||
}
|
||||
#define buf_append(buf, size, pos, fmt, ...) buf_append_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__)
|
||||
#else
|
||||
/// Safely append formatted string to buffer, returning new position (capped at size)
|
||||
__attribute__((format(printf, 4, 5))) inline size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt,
|
||||
...) {
|
||||
if (pos >= size) {
|
||||
return size;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int written = vsnprintf(buf + pos, size - pos, fmt, args);
|
||||
va_end(args);
|
||||
if (written < 0) {
|
||||
return pos; // encoding error
|
||||
}
|
||||
return std::min(pos + static_cast<size_t>(written), size);
|
||||
}
|
||||
#endif
|
||||
|
||||
class DebugComponent : public PollingComponent {
|
||||
public:
|
||||
@@ -35,11 +74,8 @@ class DebugComponent : public PollingComponent {
|
||||
#ifdef USE_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; }
|
||||
#if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
|
||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
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
|
||||
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
|
||||
#ifdef USE_ESP32
|
||||
@@ -61,11 +97,8 @@ class DebugComponent : public PollingComponent {
|
||||
|
||||
sensor::Sensor *free_sensor_{nullptr};
|
||||
sensor::Sensor *block_sensor_{nullptr};
|
||||
#if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
|
||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
sensor::Sensor *fragmentation_sensor_{nullptr};
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
sensor::Sensor *min_free_sensor_{nullptr};
|
||||
#endif
|
||||
sensor::Sensor *loop_time_sensor_{nullptr};
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -173,8 +173,8 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
|
||||
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
|
||||
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
|
||||
pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
||||
flash_mode);
|
||||
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
||||
flash_mode);
|
||||
#endif
|
||||
|
||||
esp_chip_info_t info;
|
||||
@@ -182,71 +182,60 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
const char *model = ESPHOME_VARIANT;
|
||||
|
||||
// Build features string
|
||||
pos = buf_append_printf(buf, size, pos, "|Chip: %s Features:", model);
|
||||
pos = buf_append(buf, size, pos, "|Chip: %s Features:", model);
|
||||
bool first_feature = true;
|
||||
for (const auto &feature : CHIP_FEATURES) {
|
||||
if (info.features & feature.bit) {
|
||||
pos = buf_append_printf(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name);
|
||||
pos = buf_append(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name);
|
||||
first_feature = false;
|
||||
info.features &= ~feature.bit;
|
||||
}
|
||||
}
|
||||
if (info.features != 0) {
|
||||
pos = buf_append_printf(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features);
|
||||
pos = buf_append(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features);
|
||||
}
|
||||
ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision);
|
||||
pos = buf_append_printf(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision);
|
||||
pos = buf_append(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision);
|
||||
|
||||
uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000;
|
||||
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
|
||||
pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
|
||||
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
|
||||
|
||||
// Framework detection
|
||||
#ifdef USE_ARDUINO
|
||||
ESP_LOGD(TAG, "Framework: Arduino");
|
||||
pos = buf_append_printf(buf, size, pos, "|Framework: Arduino");
|
||||
pos = buf_append(buf, size, pos, "|Framework: Arduino");
|
||||
#elif defined(USE_ESP32)
|
||||
ESP_LOGD(TAG, "Framework: ESP-IDF");
|
||||
pos = buf_append_printf(buf, size, pos, "|Framework: ESP-IDF");
|
||||
pos = buf_append(buf, size, pos, "|Framework: ESP-IDF");
|
||||
#else
|
||||
ESP_LOGW(TAG, "Framework: UNKNOWN");
|
||||
pos = buf_append_printf(buf, size, pos, "|Framework: UNKNOWN");
|
||||
pos = buf_append(buf, size, pos, "|Framework: UNKNOWN");
|
||||
#endif
|
||||
|
||||
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
|
||||
pos = buf_append_printf(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version());
|
||||
pos = buf_append(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version());
|
||||
|
||||
uint8_t mac[6];
|
||||
get_mac_address_raw(mac);
|
||||
ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
pos = buf_append_printf(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3],
|
||||
mac[4], mac[5]);
|
||||
pos = buf_append(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4],
|
||||
mac[5]);
|
||||
|
||||
char reason_buffer[RESET_REASON_BUFFER_SIZE];
|
||||
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||
pos = buf_append_printf(buf, size, pos, "|Reset: %s", reset_reason);
|
||||
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason);
|
||||
|
||||
const char *wakeup_cause = get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||
pos = buf_append_printf(buf, size, pos, "|Wakeup: %s", wakeup_cause);
|
||||
pos = buf_append(buf, size, pos, "|Wakeup: %s", wakeup_cause);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void DebugComponent::update_platform_() {
|
||||
#ifdef USE_SENSOR
|
||||
uint32_t max_alloc = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
|
||||
if (this->block_sensor_ != nullptr) {
|
||||
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);
|
||||
}
|
||||
this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL));
|
||||
}
|
||||
if (this->psram_sensor_ != nullptr) {
|
||||
this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
||||
|
||||
@@ -53,8 +53,8 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
|
||||
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
|
||||
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
|
||||
pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
||||
flash_mode);
|
||||
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
||||
flash_mode);
|
||||
|
||||
#if !defined(CLANG_TIDY)
|
||||
char reason_buffer[RESET_REASON_BUFFER_SIZE];
|
||||
@@ -77,15 +77,15 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
chip_id, ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), boot_version, boot_mode, cpu_freq, flash_chip_id,
|
||||
reset_reason, ESP.getResetInfo().c_str());
|
||||
|
||||
pos = buf_append_printf(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id);
|
||||
pos = buf_append_printf(buf, size, pos, "|SDK: %s", ESP.getSdkVersion());
|
||||
pos = buf_append_printf(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str());
|
||||
pos = buf_append_printf(buf, size, pos, "|Boot: %u", boot_version);
|
||||
pos = buf_append_printf(buf, size, pos, "|Mode: %u", boot_mode);
|
||||
pos = buf_append_printf(buf, size, pos, "|CPU: %u", cpu_freq);
|
||||
pos = buf_append_printf(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id);
|
||||
pos = buf_append_printf(buf, size, pos, "|Reset: %s", reset_reason);
|
||||
pos = buf_append_printf(buf, size, pos, "|%s", ESP.getResetInfo().c_str());
|
||||
pos = buf_append(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id);
|
||||
pos = buf_append(buf, size, pos, "|SDK: %s", ESP.getSdkVersion());
|
||||
pos = buf_append(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str());
|
||||
pos = buf_append(buf, size, pos, "|Boot: %u", boot_version);
|
||||
pos = buf_append(buf, size, pos, "|Mode: %u", boot_mode);
|
||||
pos = buf_append(buf, size, pos, "|CPU: %u", cpu_freq);
|
||||
pos = buf_append(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id);
|
||||
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason);
|
||||
pos = buf_append(buf, size, pos, "|%s", ESP.getResetInfo().c_str());
|
||||
#endif
|
||||
|
||||
return pos;
|
||||
|
||||
@@ -36,12 +36,12 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id,
|
||||
lt_get_board_code(), flash_kib, ram_kib, reset_reason);
|
||||
|
||||
pos = buf_append_printf(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10);
|
||||
pos = buf_append_printf(buf, size, pos, "|Reset Reason: %s", reset_reason);
|
||||
pos = buf_append_printf(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name());
|
||||
pos = buf_append_printf(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id);
|
||||
pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib);
|
||||
pos = buf_append_printf(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib);
|
||||
pos = buf_append(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10);
|
||||
pos = buf_append(buf, size, pos, "|Reset Reason: %s", reset_reason);
|
||||
pos = buf_append(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name());
|
||||
pos = buf_append(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id);
|
||||
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib);
|
||||
pos = buf_append(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib);
|
||||
|
||||
return pos;
|
||||
}
|
||||
@@ -51,9 +51,6 @@ void DebugComponent::update_platform_() {
|
||||
if (this->block_sensor_ != nullptr) {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
|
||||
uint32_t cpu_freq = rp2040.f_cpu();
|
||||
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq);
|
||||
pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
|
||||
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ static size_t append_reset_reason(char *buf, size_t size, size_t pos, bool set,
|
||||
return pos;
|
||||
}
|
||||
if (pos > 0) {
|
||||
pos = buf_append_printf(buf, size, pos, ", ");
|
||||
pos = buf_append(buf, size, pos, ", ");
|
||||
}
|
||||
return buf_append_printf(buf, size, pos, "%s", reason);
|
||||
return buf_append(buf, size, pos, "%s", reason);
|
||||
}
|
||||
|
||||
static inline uint32_t read_mem_u32(uintptr_t addr) {
|
||||
@@ -140,7 +140,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
const char *supply_status =
|
||||
(nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage.";
|
||||
ESP_LOGD(TAG, "Main supply status: %s", supply_status);
|
||||
pos = buf_append_printf(buf, size, pos, "|Main supply status: %s", supply_status);
|
||||
pos = buf_append(buf, size, pos, "|Main supply status: %s", supply_status);
|
||||
|
||||
// Regulator stage 0
|
||||
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) {
|
||||
@@ -172,16 +172,16 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
reg0_voltage = "???V";
|
||||
}
|
||||
ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
||||
pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
||||
pos = buf_append(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Regulator stage 0: disabled");
|
||||
pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: disabled");
|
||||
pos = buf_append(buf, size, pos, "|Regulator stage 0: disabled");
|
||||
}
|
||||
|
||||
// Regulator stage 1
|
||||
const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO";
|
||||
ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type);
|
||||
pos = buf_append_printf(buf, size, pos, "|Regulator stage 1: %s", reg1_type);
|
||||
pos = buf_append(buf, size, pos, "|Regulator stage 1: %s", reg1_type);
|
||||
|
||||
// USB power state
|
||||
const char *usb_state;
|
||||
@@ -195,7 +195,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
usb_state = "disconnected";
|
||||
}
|
||||
ESP_LOGD(TAG, "USB power state: %s", usb_state);
|
||||
pos = buf_append_printf(buf, size, pos, "|USB power state: %s", usb_state);
|
||||
pos = buf_append(buf, size, pos, "|USB power state: %s", usb_state);
|
||||
|
||||
// Power-fail comparator
|
||||
bool enabled;
|
||||
@@ -300,14 +300,14 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
||||
pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
||||
pos = buf_append(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage);
|
||||
pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s", pof_voltage);
|
||||
pos = buf_append(buf, size, pos, "|Power-fail comparator: %s", pof_voltage);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Power-fail comparator: disabled");
|
||||
pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: disabled");
|
||||
pos = buf_append(buf, size, pos, "|Power-fail comparator: disabled");
|
||||
}
|
||||
|
||||
auto package = [](uint32_t value) {
|
||||
|
||||
@@ -11,24 +11,16 @@ from esphome.const import (
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_COUNTER,
|
||||
ICON_TIMER,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_RTL87XX,
|
||||
UNIT_BYTES,
|
||||
UNIT_HERTZ,
|
||||
UNIT_MILLISECOND,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
from . import ( # noqa: F401 pylint: disable=unused-import
|
||||
CONF_DEBUG_ID,
|
||||
FILTER_SOURCE_FILES,
|
||||
DebugComponent,
|
||||
)
|
||||
from . import CONF_DEBUG_ID, DebugComponent
|
||||
|
||||
DEPENDENCIES = ["debug"]
|
||||
|
||||
CONF_MIN_FREE = "min_free"
|
||||
CONF_PSRAM = "psram"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
@@ -46,14 +38,8 @@ CONFIG_SCHEMA = {
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_FRAGMENTATION): cv.All(
|
||||
cv.Any(
|
||||
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",
|
||||
),
|
||||
cv.only_on_esp8266,
|
||||
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_COUNTER,
|
||||
@@ -61,19 +47,6 @@ CONFIG_SCHEMA = {
|
||||
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(
|
||||
unit_of_measurement=UNIT_MILLISECOND,
|
||||
icon=ICON_TIMER,
|
||||
@@ -116,10 +89,6 @@ async def to_code(config):
|
||||
sens = await sensor.new_sensor(fragmentation_conf)
|
||||
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):
|
||||
sens = await sensor.new_sensor(loop_time_conf)
|
||||
cg.add(debug_component.set_loop_time_sensor(sens))
|
||||
|
||||
@@ -8,11 +8,7 @@ from esphome.const import (
|
||||
ICON_RESTART,
|
||||
)
|
||||
|
||||
from . import ( # noqa: F401 pylint: disable=unused-import
|
||||
CONF_DEBUG_ID,
|
||||
FILTER_SOURCE_FILES,
|
||||
DebugComponent,
|
||||
)
|
||||
from . import CONF_DEBUG_ID, DebugComponent
|
||||
|
||||
DEPENDENCIES = ["debug"]
|
||||
|
||||
|
||||
@@ -127,9 +127,7 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
||||
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
|
||||
this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
|
||||
} else if (min3 < 0 || max3 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
@@ -137,10 +135,7 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
||||
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
||||
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
|
||||
max2 / 0.15);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15);
|
||||
} else if (min4 < 0 || max4 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
@@ -150,10 +145,9 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
||||
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
|
||||
this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
|
||||
max2 / 0.15, min3 / 0.15, max3 / 0.15);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 "
|
||||
"%.0f %.0f %.0f %.0f %.0f %.0f",
|
||||
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15);
|
||||
} else {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
@@ -164,10 +158,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
||||
this->min4_ = min4 = round(min4 / 0.15) * 0.15;
|
||||
this->max4_ = max4 = round(max4 / 0.15) * 0.15;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15,
|
||||
min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15, max4 / 0.15);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 "
|
||||
"%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f",
|
||||
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15,
|
||||
max4 / 0.15);
|
||||
}
|
||||
|
||||
this->min1_ = min1;
|
||||
@@ -209,10 +203,7 @@ SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_af
|
||||
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
|
||||
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
|
||||
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
|
||||
// max 32: "setLatency "(11) + float(8) + " "(1) + float(8) + null, rounded to 32
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
|
||||
this->cmd_ = buf;
|
||||
this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
|
||||
};
|
||||
|
||||
uint8_t SetLatencyCommand::on_message(std::string &message) {
|
||||
|
||||
@@ -75,8 +75,8 @@ class SetLatencyCommand : public Command {
|
||||
class SensorCfgStartCommand : public Command {
|
||||
public:
|
||||
SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) {
|
||||
char tmp_cmd[20]; // "sensorCfgStart " (15) + "0/1" (1) + null = 17
|
||||
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "sensorCfgStart %d", startup_mode);
|
||||
char tmp_cmd[20] = {0};
|
||||
sprintf(tmp_cmd, "sensorCfgStart %d", startup_mode);
|
||||
cmd_ = std::string(tmp_cmd);
|
||||
}
|
||||
uint8_t on_message(std::string &message) override;
|
||||
@@ -142,8 +142,8 @@ class SensitivityCommand : public Command {
|
||||
SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) {
|
||||
if (sensitivity > 9)
|
||||
sensitivity_ = sensitivity = 9;
|
||||
char tmp_cmd[20]; // "setSensitivity " (15) + "0-9" (1) + null = 17
|
||||
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "setSensitivity %d", sensitivity);
|
||||
char tmp_cmd[20] = {0};
|
||||
sprintf(tmp_cmd, "setSensitivity %d", sensitivity);
|
||||
cmd_ = std::string(tmp_cmd);
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
@@ -25,13 +25,29 @@ dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
|
||||
Dsmr = dsmr_ns.class_("Dsmr", cg.Component, uart.UARTDevice)
|
||||
|
||||
|
||||
def _validate_key(value):
|
||||
value = cv.string_strict(value)
|
||||
parts = [value[i : i + 2] for i in range(0, len(value), 2)]
|
||||
if len(parts) != 16:
|
||||
raise cv.Invalid("Decryption key must consist of 16 hexadecimal numbers")
|
||||
parts_int = []
|
||||
if any(len(part) != 2 for part in parts):
|
||||
raise cv.Invalid("Decryption key must be format XX")
|
||||
for part in parts:
|
||||
try:
|
||||
parts_int.append(int(part, 16))
|
||||
except ValueError:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid("Decryption key must be hex values from 00 to FF")
|
||||
|
||||
return "".join(f"{part:02X}" for part in parts_int)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Dsmr),
|
||||
cv.Optional(CONF_DECRYPTION_KEY): lambda value: cv.bind_key(
|
||||
value, name="Decryption key"
|
||||
),
|
||||
cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
|
||||
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
|
||||
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
|
||||
cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "dsmr.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <AES.h>
|
||||
@@ -295,8 +294,8 @@ void Dsmr::dump_config() {
|
||||
DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, )
|
||||
}
|
||||
|
||||
void Dsmr::set_decryption_key(const char *decryption_key) {
|
||||
if (decryption_key == nullptr || decryption_key[0] == '\0') {
|
||||
void Dsmr::set_decryption_key(const std::string &decryption_key) {
|
||||
if (decryption_key.empty()) {
|
||||
ESP_LOGI(TAG, "Disabling decryption");
|
||||
this->decryption_key_.clear();
|
||||
if (this->crypt_telegram_ != nullptr) {
|
||||
@@ -306,15 +305,21 @@ void Dsmr::set_decryption_key(const char *decryption_key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parse_hex(decryption_key, this->decryption_key_, 16)) {
|
||||
ESP_LOGE(TAG, "Error, decryption key must be 32 hex characters");
|
||||
this->decryption_key_.clear();
|
||||
if (decryption_key.length() != 32) {
|
||||
ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
|
||||
return;
|
||||
}
|
||||
this->decryption_key_.clear();
|
||||
|
||||
ESP_LOGI(TAG, "Decryption key is set");
|
||||
// Verbose level prints decryption key
|
||||
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key);
|
||||
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());
|
||||
|
||||
char temp[3] = {0};
|
||||
for (int i = 0; i < 16; i++) {
|
||||
strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
|
||||
this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
|
||||
}
|
||||
|
||||
if (this->crypt_telegram_ == nullptr) {
|
||||
this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
|
||||
|
||||
@@ -63,7 +63,7 @@ class Dsmr : public Component, public uart::UARTDevice {
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_decryption_key(const char *decryption_key);
|
||||
void set_decryption_key(const std::string &decryption_key);
|
||||
void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
|
||||
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
|
||||
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
|
||||
|
||||
@@ -46,8 +46,6 @@ class ESPBTUUID {
|
||||
|
||||
esp_bt_uuid_t get_uuid() const;
|
||||
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string to_string() const;
|
||||
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
|
||||
|
||||
|
||||
@@ -193,18 +193,10 @@ void BLEClientBase::log_event_(const char *name) {
|
||||
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, name);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_gattc_lifecycle_event_(const char *name) {
|
||||
void BLEClientBase::log_gattc_event_(const char *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) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, status);
|
||||
}
|
||||
@@ -288,7 +280,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (!this->check_addr(param->open.remote_bda))
|
||||
return false;
|
||||
this->log_gattc_lifecycle_event_("OPEN");
|
||||
this->log_gattc_event_("OPEN");
|
||||
// conn_id was already set in ESP_GATTC_CONNECT_EVT
|
||||
this->service_count_ = 0;
|
||||
|
||||
@@ -339,7 +331,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
if (!this->check_addr(param->connect.remote_bda))
|
||||
return false;
|
||||
this->log_gattc_lifecycle_event_("CONNECT");
|
||||
this->log_gattc_event_("CONNECT");
|
||||
this->conn_id_ = param->connect.conn_id;
|
||||
// Start MTU negotiation immediately as recommended by ESP-IDF examples
|
||||
// (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in
|
||||
@@ -384,7 +376,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_CLOSE_EVT: {
|
||||
if (this->conn_id_ != param->close.conn_id)
|
||||
return false;
|
||||
this->log_gattc_lifecycle_event_("CLOSE");
|
||||
this->log_gattc_event_("CLOSE");
|
||||
this->release_services();
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
this->conn_id_ = UNSET_CONN_ID;
|
||||
@@ -412,7 +404,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
if (this->conn_id_ != param->search_cmpl.conn_id)
|
||||
return false;
|
||||
this->log_gattc_lifecycle_event_("SEARCH_CMPL");
|
||||
this->log_gattc_event_("SEARCH_CMPL");
|
||||
// For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
|
||||
// This balances performance with bandwidth usage after the critical discovery phase
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
@@ -439,35 +431,35 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_READ_DESCR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->log_gattc_data_event_("READ_DESCR");
|
||||
this->log_gattc_event_("READ_DESCR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->log_gattc_data_event_("WRITE_DESCR");
|
||||
this->log_gattc_event_("WRITE_DESCR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->log_gattc_data_event_("WRITE_CHAR");
|
||||
this->log_gattc_event_("WRITE_CHAR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (this->conn_id_ != param->read.conn_id)
|
||||
return false;
|
||||
this->log_gattc_data_event_("READ_CHAR");
|
||||
this->log_gattc_event_("READ_CHAR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (this->conn_id_ != param->notify.conn_id)
|
||||
return false;
|
||||
this->log_gattc_data_event_("NOTIFY");
|
||||
this->log_gattc_event_("NOTIFY");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->log_gattc_data_event_("REG_FOR_NOTIFY");
|
||||
this->log_gattc_event_("REG_FOR_NOTIFY");
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
// Client is responsible for flipping the descriptor value
|
||||
@@ -499,7 +491,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
esp_err_t status =
|
||||
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);
|
||||
ESP_LOGV(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
|
||||
ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
|
||||
if (status) {
|
||||
this->log_gattc_warning_("esp_ble_gattc_write_char_descr", status);
|
||||
}
|
||||
@@ -507,13 +499,13 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
this->log_gattc_data_event_("UNREG_FOR_NOTIFY");
|
||||
this->log_gattc_event_("UNREG_FOR_NOTIFY");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Unknown events logged at VERBOSE to avoid UART delays during time-sensitive operations
|
||||
ESP_LOGV(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event);
|
||||
// ideally would check all other events for matching conn_id
|
||||
ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -127,8 +127,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
// 6 bytes used, 2 bytes padding
|
||||
|
||||
void log_event_(const char *name);
|
||||
void log_gattc_lifecycle_event_(const char *name);
|
||||
void log_gattc_data_event_(const char *name);
|
||||
void log_gattc_event_(const char *name);
|
||||
void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
||||
const char *param_type);
|
||||
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
||||
|
||||
@@ -69,10 +69,7 @@ void Esp32HostedUpdate::setup() {
|
||||
// Get coprocessor version
|
||||
esp_hosted_coprocessor_fwver_t ver_info;
|
||||
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
|
||||
// 16 bytes: "255.255.255" (11 chars) + null + safety margin
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
|
||||
this->update_info_.current_version = buf;
|
||||
this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
|
||||
} else {
|
||||
this->update_info_.current_version = "unknown";
|
||||
}
|
||||
@@ -297,7 +294,8 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
|
||||
}
|
||||
|
||||
// Stream firmware to coprocessor while computing SHA256
|
||||
sha256::SHA256 hasher;
|
||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
hasher.init();
|
||||
|
||||
uint8_t buffer[CHUNK_SIZE];
|
||||
@@ -354,7 +352,8 @@ bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() {
|
||||
}
|
||||
|
||||
// Verify SHA256 before writing
|
||||
sha256::SHA256 hasher;
|
||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
hasher.init();
|
||||
hasher.add(this->firmware_data_, this->firmware_size_);
|
||||
hasher.calculate();
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
#include <Arduino.h>
|
||||
#include <core_esp8266_features.h>
|
||||
|
||||
extern "C" {
|
||||
#include <user_interface.h>
|
||||
}
|
||||
#include <Esp.h>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -20,19 +16,23 @@ void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
system_restart();
|
||||
ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
|
||||
// restart() doesn't always end execution
|
||||
while (true) { // NOLINT(clang-diagnostic-unreachable-code)
|
||||
yield();
|
||||
}
|
||||
}
|
||||
void arch_init() {}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() { system_soft_wdt_feed(); }
|
||||
void IRAM_ATTR HOT arch_feed_wdt() {
|
||||
ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) {
|
||||
return pgm_read_byte(addr); // NOLINT
|
||||
}
|
||||
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return esp_get_cycle_count(); }
|
||||
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() {
|
||||
return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
|
||||
|
||||
void force_link_symbols() {
|
||||
|
||||
@@ -99,7 +99,7 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
|
||||
}
|
||||
|
||||
size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const {
|
||||
return buf_append_printf(buffer, len, 0, "GPIO%u", this->pin_);
|
||||
return snprintf(buffer, len, "GPIO%u", this->pin_);
|
||||
}
|
||||
|
||||
bool ESP8266GPIOPin::digital_read() {
|
||||
|
||||
@@ -563,9 +563,11 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
|
||||
// [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
|
||||
|
||||
// CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
||||
// CRITICAL ESP32-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.
|
||||
sha256::SHA256 hasher;
|
||||
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
|
||||
// hardware SHA acceleration DMA operations.
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
|
||||
const size_t hex_size = hasher.get_size() * 2;
|
||||
const size_t nonce_len = hasher.get_size() / 4;
|
||||
@@ -637,9 +639,11 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
|
||||
const char *cnonce = nonce + hex_size;
|
||||
const char *response = cnonce + hex_size;
|
||||
|
||||
// CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
||||
// CRITICAL ESP32-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.
|
||||
sha256::SHA256 hasher;
|
||||
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
|
||||
// hardware SHA acceleration DMA operations.
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
|
||||
hasher.init();
|
||||
hasher.add(this->password_.c_str(), this->password_.length());
|
||||
|
||||
@@ -90,7 +90,9 @@ async def setup_event_core_(var, config, *, event_types: list[str]):
|
||||
|
||||
for conf in config.get(CONF_ON_EVENT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.StringRef, "event_type")], conf)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "event_type")], conf
|
||||
)
|
||||
|
||||
cg.add(var.set_event_types(event_types))
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ template<typename... Ts> class TriggerEventAction : public Action<Ts...>, public
|
||||
void play(const Ts &...x) override { this->parent_->trigger(this->event_type_.value(x...)); }
|
||||
};
|
||||
|
||||
class EventTrigger : public Trigger<StringRef> {
|
||||
class EventTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
EventTrigger(Event *event) {
|
||||
event->add_on_event_callback([this](StringRef event_type) { this->trigger(event_type); });
|
||||
event->add_on_event_callback([this](const std::string &event_type) { this->trigger(event_type); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ void Event::trigger(const std::string &event_type) {
|
||||
return;
|
||||
}
|
||||
this->last_event_type_ = found;
|
||||
ESP_LOGD(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_);
|
||||
this->event_callback_.call(StringRef(found));
|
||||
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_);
|
||||
this->event_callback_.call(event_type);
|
||||
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_event(this);
|
||||
#endif
|
||||
@@ -45,7 +45,7 @@ void Event::set_event_types(const std::vector<const char *> &event_types) {
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
|
||||
void Event::add_on_event_callback(std::function<void(StringRef event_type)> &&callback) {
|
||||
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {
|
||||
this->event_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
|
||||
@@ -70,10 +70,10 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
/// Check if an event has been triggered.
|
||||
bool has_event() const { return this->last_event_type_ != nullptr; }
|
||||
|
||||
void add_on_event_callback(std::function<void(StringRef event_type)> &&callback);
|
||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||
|
||||
protected:
|
||||
LazyCallbackManager<void(StringRef event_type)> event_callback_;
|
||||
LazyCallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
FixedVector<const char *> types_;
|
||||
|
||||
private:
|
||||
|
||||
@@ -160,7 +160,7 @@ void EZOSensor::loop() {
|
||||
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);
|
||||
ezo_command->command = command;
|
||||
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) {
|
||||
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
|
||||
char payload[24];
|
||||
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
|
||||
std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
|
||||
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
||||
}
|
||||
|
||||
void EZOSensor::set_address(uint8_t address) {
|
||||
if (address > 0 && address < 128) {
|
||||
// max 8: "I2C,"(4) + uint8(3) + null
|
||||
char payload[8];
|
||||
snprintf(payload, sizeof(payload), "I2C,%u", address);
|
||||
std::string payload = str_sprintf("I2C,%u", address);
|
||||
this->new_address_ = address;
|
||||
this->add_command_(payload, EzoCommandType::EZO_I2C);
|
||||
} 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::set_t(float value) {
|
||||
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
|
||||
char payload[16];
|
||||
snprintf(payload, sizeof(payload), "T,%0.2f", value);
|
||||
std::string payload = str_sprintf("T,%0.2f", value);
|
||||
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) {
|
||||
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
|
||||
char payload[16];
|
||||
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
|
||||
std::string payload = str_sprintf("Cal,%0.2f", value);
|
||||
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::set_led_state(bool on) { this->add_command_(on ? "L,1" : "L,0", EzoCommandType::EZO_LED); }
|
||||
|
||||
void EZOSensor::send_custom(const std::string &to_send) {
|
||||
this->add_command_(to_send.c_str(), EzoCommandType::EZO_CUSTOM);
|
||||
void EZOSensor::set_led_state(bool on) {
|
||||
std::string to_send = "L,";
|
||||
to_send += on ? "1" : "0";
|
||||
this->add_command_(to_send, EzoCommandType::EZO_LED);
|
||||
}
|
||||
|
||||
void EZOSensor::send_custom(const std::string &to_send) { this->add_command_(to_send, EzoCommandType::EZO_CUSTOM); }
|
||||
|
||||
} // namespace ezo
|
||||
} // namespace esphome
|
||||
|
||||
@@ -92,7 +92,7 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
|
||||
std::deque<std::unique_ptr<EzoCommand>> commands_;
|
||||
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);
|
||||
|
||||
|
||||
@@ -318,93 +318,90 @@ void EzoPMP::send_next_command_() {
|
||||
switch (this->next_command_) {
|
||||
// Read Commands
|
||||
case EZO_PMP_COMMAND_READ_DOSING: // Page 54
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,?");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "D,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "R");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "R");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE:
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "DC,?");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "DC,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_PAUSE_STATUS:
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "P,?");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "P,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED:
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "TV,?");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "TV,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED:
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "ATV,?");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "ATV,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS:
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,?");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Cal,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE:
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "PV,?");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "PV,?");
|
||||
break;
|
||||
|
||||
// Non-Read Commands
|
||||
|
||||
case EZO_PMP_COMMAND_FIND: // Find (page 52)
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Find");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Find");
|
||||
wait_time_for_command = 60000; // This command will block all updates for a minute
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,*");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "D,*");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Clear");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Clear");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,clear");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Cal,clear");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "P");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "P");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "X");
|
||||
command_buffer_length = sprintf((char *) command_buffer, "X");
|
||||
break;
|
||||
|
||||
// Non-Read commands with parameters
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
|
||||
command_buffer_length =
|
||||
snprintf((char *) command_buffer, sizeof(command_buffer), "D,%0.1f", this->next_command_volume_);
|
||||
command_buffer_length = sprintf((char *) command_buffer, "D,%0.1f", this->next_command_volume_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,%0.1f,%i",
|
||||
this->next_command_volume_, this->next_command_duration_);
|
||||
command_buffer_length =
|
||||
sprintf((char *) command_buffer, "D,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "DC,%0.1f,%i",
|
||||
this->next_command_volume_, this->next_command_duration_);
|
||||
command_buffer_length =
|
||||
sprintf((char *) command_buffer, "DC,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
|
||||
command_buffer_length =
|
||||
snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,%0.2f", this->next_command_volume_);
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Cal,%0.2f", this->next_command_volume_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73)
|
||||
command_buffer_length =
|
||||
snprintf((char *) command_buffer, sizeof(command_buffer), "I2C,%i", this->next_command_duration_);
|
||||
command_buffer_length = sprintf((char *) command_buffer, "I2C,%i", this->next_command_duration_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "%s", this->arbitrary_command_);
|
||||
command_buffer_length = sprintf((char *) command_buffer, this->arbitrary_command_, this->next_command_duration_);
|
||||
ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
|
||||
break;
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ FanSpeedSetTrigger = fan_ns.class_(
|
||||
"FanSpeedSetTrigger", automation.Trigger.template(cg.int_)
|
||||
)
|
||||
FanPresetSetTrigger = fan_ns.class_(
|
||||
"FanPresetSetTrigger", automation.Trigger.template(cg.StringRef)
|
||||
"FanPresetSetTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
|
||||
@@ -287,7 +287,7 @@ async def setup_fan_core_(var, config):
|
||||
await automation.build_automation(trigger, [(cg.int_, "x")], conf)
|
||||
for conf in config.get(CONF_ON_PRESET_SET, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.StringRef, "x")], conf)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
|
||||
async def register_fan(var, config):
|
||||
|
||||
@@ -208,7 +208,7 @@ class FanSpeedSetTrigger : public Trigger<int> {
|
||||
int last_speed_;
|
||||
};
|
||||
|
||||
class FanPresetSetTrigger : public Trigger<StringRef> {
|
||||
class FanPresetSetTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
FanPresetSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
@@ -216,7 +216,7 @@ class FanPresetSetTrigger : public Trigger<StringRef> {
|
||||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||
this->last_preset_mode_ = preset_mode;
|
||||
if (should_trigger) {
|
||||
this->trigger(preset_mode);
|
||||
this->trigger(std::string(preset_mode));
|
||||
}
|
||||
});
|
||||
this->last_preset_mode_ = state->get_preset_mode();
|
||||
|
||||
@@ -201,7 +201,7 @@ void Fan::publish_state() {
|
||||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
"'%s' >>\n"
|
||||
"'%s' - Sending state:\n"
|
||||
" State: %s",
|
||||
this->name_.c_str(), ONOFF(this->state));
|
||||
if (traits.supports_speed()) {
|
||||
|
||||
@@ -163,10 +163,9 @@ bool GDK101Component::read_fw_version_(uint8_t *data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// max 8: "255.255" (7 chars) + null
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf), "%d.%d", data[0], data[1]);
|
||||
this->fw_version_text_sensor_->publish_state(buf);
|
||||
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
|
||||
|
||||
this->fw_version_text_sensor_->publish_state(fw_version_str);
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
return true;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include "hmac_sha256.h"
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
||||
@@ -25,7 +26,9 @@ 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_hex(char *output) {
|
||||
format_hex_to(output, SHA256_DIGEST_SIZE * 2 + 1, this->digest_, SHA256_DIGEST_SIZE);
|
||||
for (size_t i = 0; i < SHA256_DIGEST_SIZE; i++) {
|
||||
sprintf(output + (i * 2), "%02x", this->digest_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool HmacSHA256::equals_bytes(const uint8_t *expected) {
|
||||
|
||||
@@ -97,7 +97,7 @@ void HomeassistantNumber::control(float value) {
|
||||
entity_value.key = VALUE_KEY;
|
||||
// Stack buffer - no heap allocation; %g produces shortest representation
|
||||
char value_buf[16];
|
||||
buf_append_printf(value_buf, sizeof(value_buf), 0, "%g", value);
|
||||
snprintf(value_buf, sizeof(value_buf), "%g", value);
|
||||
entity_value.value = StringRef(value_buf);
|
||||
|
||||
api::global_api_server->send_homeassistant_action(resp);
|
||||
|
||||
@@ -242,7 +242,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t max_length = this->max_response_buffer_size_;
|
||||
size_t content_length = container->content_length;
|
||||
size_t max_length = std::min(content_length, this->max_response_buffer_size_);
|
||||
|
||||
#ifdef USE_HTTP_REQUEST_RESPONSE
|
||||
if (this->capture_response_.value(x...)) {
|
||||
std::string response_body;
|
||||
|
||||
@@ -213,12 +213,18 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
||||
const uint32_t start = millis();
|
||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||
|
||||
this->feed_wdt();
|
||||
int read_len = esp_http_client_read(this->client_, (char *) buf, max_len);
|
||||
this->feed_wdt();
|
||||
if (read_len > 0) {
|
||||
this->bytes_read_ += read_len;
|
||||
int bufsize = std::min(max_len, this->content_length - this->bytes_read_);
|
||||
|
||||
if (bufsize == 0) {
|
||||
this->duration_ms += (millis() - start);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return read_len;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from esphome import automation, pins
|
||||
@@ -19,16 +18,13 @@ from esphome.const import (
|
||||
CONF_ROTATION,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
)
|
||||
from esphome.core import ID, EnumValue
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import add_class_to_obj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import boards, hub75_ns
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
CODEOWNERS = ["@stuartparmenter"]
|
||||
|
||||
@@ -124,51 +120,13 @@ PANEL_LAYOUTS = {
|
||||
}
|
||||
|
||||
Hub75ScanWiring = cg.global_ns.enum("Hub75ScanWiring", is_class=True)
|
||||
SCAN_WIRINGS = {
|
||||
SCAN_PATTERNS = {
|
||||
"STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN,
|
||||
"SCAN_1_4_16PX_HIGH": Hub75ScanWiring.SCAN_1_4_16PX_HIGH,
|
||||
"SCAN_1_8_32PX_HIGH": Hub75ScanWiring.SCAN_1_8_32PX_HIGH,
|
||||
"SCAN_1_8_40PX_HIGH": Hub75ScanWiring.SCAN_1_8_40PX_HIGH,
|
||||
"SCAN_1_8_64PX_HIGH": Hub75ScanWiring.SCAN_1_8_64PX_HIGH,
|
||||
"FOUR_SCAN_16PX_HIGH": Hub75ScanWiring.FOUR_SCAN_16PX_HIGH,
|
||||
"FOUR_SCAN_32PX_HIGH": Hub75ScanWiring.FOUR_SCAN_32PX_HIGH,
|
||||
"FOUR_SCAN_64PX_HIGH": Hub75ScanWiring.FOUR_SCAN_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)
|
||||
CLOCK_SPEEDS = {
|
||||
"8MHZ": Hub75ClockSpeed.HZ_8M,
|
||||
@@ -424,7 +382,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_LAYOUT_COLS): cv.positive_int,
|
||||
cv.Optional(CONF_LAYOUT): cv.enum(PANEL_LAYOUTS, upper=True, space="_"),
|
||||
# Panel hardware configuration
|
||||
cv.Optional(CONF_SCAN_WIRING): _validate_scan_wiring,
|
||||
cv.Optional(CONF_SCAN_WIRING): cv.enum(
|
||||
SCAN_PATTERNS, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_SHIFT_DRIVER): cv.enum(SHIFT_DRIVERS, upper=True),
|
||||
# Display configuration
|
||||
cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean,
|
||||
@@ -587,7 +547,7 @@ def _build_config_struct(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
add_idf_component(
|
||||
name="esphome/esp-hub75",
|
||||
ref="0.3.0",
|
||||
ref="0.2.2",
|
||||
)
|
||||
|
||||
# Set compile-time configuration via build flags (so external library sees them)
|
||||
|
||||
@@ -42,8 +42,8 @@ ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t
|
||||
}
|
||||
|
||||
ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len) const {
|
||||
SmallBufferWithHeapFallback<17> buffer_alloc(len + 1); // Most I2C writes are <= 16 bytes
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
SmallBufferWithHeapFallback<17> buffer_alloc; // Most I2C writes are <= 16 bytes
|
||||
uint8_t *buffer = buffer_alloc.get(len + 1);
|
||||
|
||||
buffer[0] = a_register;
|
||||
std::copy(data, data + len, buffer + 1);
|
||||
@@ -51,8 +51,8 @@ ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, siz
|
||||
}
|
||||
|
||||
ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len) const {
|
||||
SmallBufferWithHeapFallback<18> buffer_alloc(len + 2); // Most I2C writes are <= 16 bytes + 2 for register
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
SmallBufferWithHeapFallback<18> buffer_alloc; // Most I2C writes are <= 16 bytes + 2 for register
|
||||
uint8_t *buffer = buffer_alloc.get(len + 2);
|
||||
|
||||
buffer[0] = a_register >> 8;
|
||||
buffer[1] = a_register;
|
||||
|
||||
@@ -11,6 +11,22 @@
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
/// @brief Helper class for efficient buffer allocation - uses stack for small sizes, heap for large
|
||||
template<size_t STACK_SIZE> class SmallBufferWithHeapFallback {
|
||||
public:
|
||||
uint8_t *get(size_t size) {
|
||||
if (size <= STACK_SIZE) {
|
||||
return this->stack_buffer_;
|
||||
}
|
||||
this->heap_buffer_ = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
|
||||
return this->heap_buffer_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t stack_buffer_[STACK_SIZE];
|
||||
std::unique_ptr<uint8_t[]> heap_buffer_;
|
||||
};
|
||||
|
||||
/// @brief Error codes returned by I2CBus and I2CDevice methods
|
||||
enum ErrorCode {
|
||||
NO_ERROR = 0, ///< No error found during execution of method
|
||||
@@ -76,8 +92,8 @@ class I2CBus {
|
||||
total_len += read_buffers[i].len;
|
||||
}
|
||||
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc(total_len); // Most I2C reads are small
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C reads are small
|
||||
uint8_t *buffer = buffer_alloc.get(total_len);
|
||||
|
||||
auto err = this->write_readv(address, nullptr, 0, buffer, total_len);
|
||||
if (err != ERROR_OK)
|
||||
@@ -100,8 +116,8 @@ class I2CBus {
|
||||
total_len += write_buffers[i].len;
|
||||
}
|
||||
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc(total_len); // Most I2C writes are small
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C writes are small
|
||||
uint8_t *buffer = buffer_alloc.get(total_len);
|
||||
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
|
||||
@@ -665,10 +665,15 @@ async def write_image(config, all_frames=False):
|
||||
if is_svg_file(path):
|
||||
import resvg_py
|
||||
|
||||
resize = resize or (None, None)
|
||||
image_data = resvg_py.svg_to_bytes(
|
||||
svg_path=str(path), width=resize[0], height=resize[1], dpi=100
|
||||
)
|
||||
if resize:
|
||||
width, height = resize
|
||||
# resvg-py allows rendering by width/height directly
|
||||
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
|
||||
image = Image.open(io.BytesIO(image_data))
|
||||
|
||||
@@ -18,15 +18,7 @@ InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) {
|
||||
|
||||
InfraredCall &InfraredCall::set_raw_timings(const std::vector<int32_t> &timings) {
|
||||
this->raw_timings_ = &timings;
|
||||
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;
|
||||
this->packed_data_ = nullptr; // Clear packed if vector is set
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -34,8 +26,7 @@ InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t
|
||||
this->packed_data_ = data;
|
||||
this->packed_length_ = length;
|
||||
this->packed_count_ = count;
|
||||
this->raw_timings_ = nullptr;
|
||||
this->base64url_ptr_ = nullptr;
|
||||
this->raw_timings_ = nullptr; // Clear vector if packed is set
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -101,23 +92,6 @@ void Infrared::control(const InfraredCall &call) {
|
||||
call.get_packed_count());
|
||||
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_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 {
|
||||
// From vector (lambdas/automations)
|
||||
transmit_data->set_data(call.get_raw_timings());
|
||||
|
||||
@@ -28,29 +28,12 @@ class InfraredCall {
|
||||
|
||||
/// Set the carrier frequency in Hz
|
||||
InfraredCall &set_carrier_frequency(uint32_t frequency);
|
||||
|
||||
// ===== 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.
|
||||
/// Set the raw timings (positive = mark, negative = space)
|
||||
/// Note: The timings vector must outlive the InfraredCall (zero-copy reference)
|
||||
InfraredCall &set_raw_timings(const std::vector<int32_t> &timings);
|
||||
|
||||
/// 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.
|
||||
/// Set the raw timings from packed protobuf sint32 data (zero-copy from wire)
|
||||
/// Note: The data must outlive the InfraredCall
|
||||
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.)
|
||||
InfraredCall &set_repeat_count(uint32_t count);
|
||||
|
||||
@@ -59,18 +42,12 @@ class InfraredCall {
|
||||
|
||||
/// Get the 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)
|
||||
/// Get the raw timings (only valid if set via set_raw_timings, not packed)
|
||||
const std::vector<int32_t> &get_raw_timings() const { return *this->raw_timings_; }
|
||||
/// Check if raw timings have been set (any format)
|
||||
bool has_raw_timings() const {
|
||||
return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base64url_ptr_ != nullptr;
|
||||
}
|
||||
/// Check if raw timings have been set (either vector or packed)
|
||||
bool has_raw_timings() const { return this->raw_timings_ != nullptr || this->packed_data_ != nullptr; }
|
||||
/// Check if using packed data format
|
||||
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)
|
||||
const uint8_t *get_packed_data() const { return this->packed_data_; }
|
||||
uint16_t get_packed_length() const { return this->packed_length_; }
|
||||
@@ -82,11 +59,9 @@ class InfraredCall {
|
||||
uint32_t repeat_count_{1};
|
||||
Infrared *parent_;
|
||||
optional<uint32_t> carrier_frequency_;
|
||||
// Pointer to vector-based timings (caller-owned, must outlive perform())
|
||||
// Vector-based timings (for lambdas/automations)
|
||||
const std::vector<int32_t> *raw_timings_{nullptr};
|
||||
// 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())
|
||||
// Packed protobuf timings (for API zero-copy)
|
||||
const uint8_t *packed_data_{nullptr};
|
||||
uint16_t packed_length_{0};
|
||||
uint16_t packed_count_{0};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "light_json_schema.h"
|
||||
#include "color_mode.h"
|
||||
#include "light_output.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
@@ -9,32 +8,29 @@ namespace esphome::light {
|
||||
|
||||
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
|
||||
|
||||
// Get JSON string for color mode.
|
||||
// ColorMode enum values are sparse bitmasks (0, 1, 3, 7, 11, 19, 35, 39, 47, 51) which would
|
||||
// generate a large jump table. Converting to bit index (0-9) allows a compact switch.
|
||||
static ProgmemStr get_color_mode_json_str(ColorMode mode) {
|
||||
switch (ColorModeBitPolicy::to_bit(mode)) {
|
||||
case 1:
|
||||
return ESPHOME_F("onoff");
|
||||
case 2:
|
||||
return ESPHOME_F("brightness");
|
||||
case 3:
|
||||
return ESPHOME_F("white");
|
||||
case 4:
|
||||
return ESPHOME_F("color_temp");
|
||||
case 5:
|
||||
return ESPHOME_F("cwww");
|
||||
case 6:
|
||||
return ESPHOME_F("rgb");
|
||||
case 7:
|
||||
return ESPHOME_F("rgbw");
|
||||
case 8:
|
||||
return ESPHOME_F("rgbct");
|
||||
case 9:
|
||||
return ESPHOME_F("rgbww");
|
||||
default:
|
||||
return nullptr;
|
||||
// Get JSON string for color mode using linear search (avoids large switch jump table)
|
||||
static const char *get_color_mode_json_str(ColorMode mode) {
|
||||
// Parallel arrays: mode values and their corresponding strings
|
||||
// Uses less RAM than a switch jump table on sparse enum values
|
||||
static constexpr ColorMode MODES[] = {
|
||||
ColorMode::ON_OFF,
|
||||
ColorMode::BRIGHTNESS,
|
||||
ColorMode::WHITE,
|
||||
ColorMode::COLOR_TEMPERATURE,
|
||||
ColorMode::COLD_WARM_WHITE,
|
||||
ColorMode::RGB,
|
||||
ColorMode::RGB_WHITE,
|
||||
ColorMode::RGB_COLOR_TEMPERATURE,
|
||||
ColorMode::RGB_COLD_WARM_WHITE,
|
||||
};
|
||||
static constexpr const char *STRINGS[] = {
|
||||
"onoff", "brightness", "white", "color_temp", "cwww", "rgb", "rgbw", "rgbct", "rgbww",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(MODES) / sizeof(MODES[0]); i++) {
|
||||
if (MODES[i] == mode)
|
||||
return STRINGS[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||
@@ -48,7 +44,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||
auto values = state.remote_values;
|
||||
|
||||
const auto color_mode = values.get_color_mode();
|
||||
const auto *mode_str = get_color_mode_json_str(color_mode);
|
||||
const char *mode_str = get_color_mode_json_str(color_mode);
|
||||
if (mode_str != nullptr) {
|
||||
root[ESPHOME_F("color_mode")] = mode_str;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
@@ -45,16 +44,13 @@ void LightWaveRF::send_rx(const std::vector<uint8_t> &msg, uint8_t repeats, bool
|
||||
}
|
||||
|
||||
void LightWaveRF::print_msg_(uint8_t *msg, uint8_t len) {
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||
char buffer[65]; // max 10 entries * 6 chars + null
|
||||
char buffer[65];
|
||||
ESP_LOGD(TAG, " Received code (len:%i): ", len);
|
||||
|
||||
size_t pos = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
pos = buf_append_printf(buffer, sizeof(buffer), pos, "0x%02x, ", msg[i]);
|
||||
sprintf(&buffer[i * 6], "0x%02x, ", msg[i]);
|
||||
}
|
||||
ESP_LOGD(TAG, "[%s]", buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
void LightWaveRF::dump_config() {
|
||||
|
||||
@@ -28,14 +28,16 @@ const LogString *lock_state_to_string(LockState state) {
|
||||
Lock::Lock() : state(LOCK_STATE_NONE) {}
|
||||
LockCall Lock::make_call() { return LockCall(this); }
|
||||
|
||||
void Lock::set_state_(LockState state) {
|
||||
void Lock::lock() {
|
||||
auto call = this->make_call();
|
||||
call.set_state(state);
|
||||
call.set_state(LOCK_STATE_LOCKED);
|
||||
this->control(call);
|
||||
}
|
||||
void Lock::unlock() {
|
||||
auto call = this->make_call();
|
||||
call.set_state(LOCK_STATE_UNLOCKED);
|
||||
this->control(call);
|
||||
}
|
||||
|
||||
void Lock::lock() { this->set_state_(LOCK_STATE_LOCKED); }
|
||||
void Lock::unlock() { this->set_state_(LOCK_STATE_UNLOCKED); }
|
||||
void Lock::open() {
|
||||
if (traits.get_supports_open()) {
|
||||
ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str());
|
||||
@@ -50,7 +52,7 @@ void Lock::publish_state(LockState state) {
|
||||
|
||||
this->state = state;
|
||||
this->rtc_.save(&this->state);
|
||||
ESP_LOGD(TAG, "'%s' >> %s", this->name_.c_str(), LOG_STR_ARG(lock_state_to_string(state)));
|
||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), LOG_STR_ARG(lock_state_to_string(state)));
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_lock_update(this);
|
||||
|
||||
@@ -156,9 +156,6 @@ class Lock : public EntityBase {
|
||||
protected:
|
||||
friend LockCall;
|
||||
|
||||
/// Helper for lock/unlock convenience methods
|
||||
void set_state_(LockState state);
|
||||
|
||||
/** Perform the open latch action with hardware. This method is optional to implement
|
||||
* when creating a new lock.
|
||||
*
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
@@ -44,17 +43,8 @@ template<typename K, typename V> class Mapping {
|
||||
esph_log_e(TAG, "Key '%p' not found in mapping", key);
|
||||
} else if constexpr (std::is_same_v<K, std::string>) {
|
||||
esph_log_e(TAG, "Key '%s' not found in mapping", key.c_str());
|
||||
} else if constexpr (std::is_integral_v<K>) {
|
||||
char buf[24]; // enough for 64-bit integer
|
||||
if constexpr (std::is_unsigned_v<K>) {
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%" PRIu64, static_cast<uint64_t>(key));
|
||||
} else {
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, static_cast<int64_t>(key));
|
||||
}
|
||||
esph_log_e(TAG, "Key '%s' not found in mapping", buf);
|
||||
} else {
|
||||
// All supported key types are handled above - this should never be reached
|
||||
static_assert(sizeof(K) == 0, "Unsupported key type for Mapping error logging");
|
||||
esph_log_e(TAG, "Key '%s' not found in mapping", to_string(key).c_str());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this-
|
||||
bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||
size_t MAX6956GPIOPin::dump_summary(char *buffer, size_t len) const {
|
||||
return buf_append_printf(buffer, len, 0, "%u via Max6956", this->pin_);
|
||||
return snprintf(buffer, len, "%u via Max6956", this->pin_);
|
||||
}
|
||||
|
||||
} // namespace max6956
|
||||
|
||||
@@ -100,7 +100,7 @@ void MCP23016GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this
|
||||
bool MCP23016GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
void MCP23016GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||
size_t MCP23016GPIOPin::dump_summary(char *buffer, size_t len) const {
|
||||
return buf_append_printf(buffer, len, 0, "%u via MCP23016", this->pin_);
|
||||
return snprintf(buffer, len, "%u via MCP23016", this->pin_);
|
||||
}
|
||||
|
||||
} // namespace mcp23016
|
||||
|
||||
@@ -17,7 +17,7 @@ template<uint8_t N> void MCP23XXXGPIOPin<N>::digital_write(bool value) {
|
||||
this->parent_->digital_write(this->pin_, value != this->inverted_);
|
||||
}
|
||||
template<uint8_t N> size_t MCP23XXXGPIOPin<N>::dump_summary(char *buffer, size_t len) const {
|
||||
return buf_append_printf(buffer, len, 0, "%u via MCP23XXX", this->pin_);
|
||||
return snprintf(buffer, len, "%u via MCP23XXX", this->pin_);
|
||||
}
|
||||
|
||||
template class MCP23XXXGPIOPin<8>;
|
||||
|
||||
@@ -101,225 +101,4 @@ DriverChip(
|
||||
(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
|
||||
|
||||
@@ -224,9 +224,12 @@ class MipiSpi : public display::Display,
|
||||
this->madctl_ & MADCTL_BGR ? "BGR" : "RGB", DISPLAYPIXEL * 8, IS_BIG_ENDIAN ? "Big" : "Little");
|
||||
if (this->brightness_.has_value())
|
||||
esph_log_config(TAG, " Brightness: %u", this->brightness_.value());
|
||||
log_pin(TAG, " CS Pin: ", this->cs_);
|
||||
log_pin(TAG, " Reset Pin: ", this->reset_pin_);
|
||||
log_pin(TAG, " DC Pin: ", this->dc_pin_);
|
||||
if (this->cs_ != nullptr)
|
||||
esph_log_config(TAG, " CS Pin: %s", this->cs_->dump_summary().c_str());
|
||||
if (this->reset_pin_ != nullptr)
|
||||
esph_log_config(TAG, " Reset Pin: %s", this->reset_pin_->dump_summary().c_str());
|
||||
if (this->dc_pin_ != nullptr)
|
||||
esph_log_config(TAG, " DC Pin: %s", this->dc_pin_->dump_summary().c_str());
|
||||
esph_log_config(TAG,
|
||||
" SPI Mode: %d\n"
|
||||
" SPI Data rate: %dMHz\n"
|
||||
|
||||
@@ -26,3 +26,5 @@ ST7789V.extend(
|
||||
reset_pin=40,
|
||||
invert_colors=True,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -105,3 +105,6 @@ CO5300 = DriverChip(
|
||||
(WCE, 0x00),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -1,45 +1,10 @@
|
||||
from .ili import ILI9341, ILI9342, ST7789V
|
||||
from .ili import ILI9341
|
||||
|
||||
ILI9341.extend(
|
||||
# ESP32-2432S028 CYD board with Micro USB, has ILI9341 controller
|
||||
"ESP32-2432S028",
|
||||
data_rate="40MHz",
|
||||
cs_pin={"number": 15, "ignore_strapping_warning": True},
|
||||
dc_pin={"number": 2, "ignore_strapping_warning": True},
|
||||
cs_pin=15,
|
||||
dc_pin=2,
|
||||
)
|
||||
|
||||
ST7789V.extend(
|
||||
# ESP32-2432S028 CYD board with USB C + Micro USB, has ST7789V controller
|
||||
"ESP32-2432S028-7789",
|
||||
data_rate="40MHz",
|
||||
cs_pin={"number": 15, "ignore_strapping_warning": True},
|
||||
dc_pin={"number": 2, "ignore_strapping_warning": True},
|
||||
)
|
||||
|
||||
# fmt: off
|
||||
|
||||
ILI9342.extend(
|
||||
# ESP32-2432S028 CYD board with USB C + Micro USB, has ILI9342 controller
|
||||
"ESP32-2432S028-9342",
|
||||
data_rate="40MHz",
|
||||
cs_pin={"number": 15, "ignore_strapping_warning": True},
|
||||
dc_pin={"number": 2, "ignore_strapping_warning": True},
|
||||
initsequence=(
|
||||
(0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02), # Power Control A
|
||||
(0xCF, 0x00, 0xC1, 0x30), # Power Control B
|
||||
(0xE8, 0x85, 0x00, 0x78), # Driver timing control A
|
||||
(0xEA, 0x00, 0x00), # Driver timing control B
|
||||
(0xED, 0x64, 0x03, 0x12, 0x81), # Power on sequence control
|
||||
(0xF7, 0x20), # Pump ratio control
|
||||
(0xC0, 0x23), # Power Control 1
|
||||
(0xC1, 0x10), # Power Control 2
|
||||
(0xC5, 0x3E, 0x28), # VCOM Control 1
|
||||
(0xC7, 0x86), # VCOM Control 2
|
||||
(0xB1, 0x00, 0x1B), # Frame Rate Control
|
||||
(0xB6, 0x0A, 0xA2, 0x27, 0x00), # Display Function Control
|
||||
(0xF2, 0x00), # Enable 3G
|
||||
(0x26, 0x01), # Gamma Set
|
||||
(0xE0, 0x00, 0x0C, 0x11, 0x04, 0x11, 0x08, 0x37, 0x89, 0x4C, 0x06, 0x0C, 0x0A, 0x2E, 0x34, 0x0F), # Positive Gamma Correction
|
||||
(0xE1, 0x00, 0x0B, 0x11, 0x05, 0x13, 0x09, 0x33, 0x67, 0x48, 0x07, 0x0E, 0x0B, 0x23, 0x33, 0x0F), # Negative Gamma Correction
|
||||
)
|
||||
)
|
||||
models = {}
|
||||
|
||||
@@ -148,34 +148,6 @@ ILI9341 = DriverChip(
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# fmt: off
|
||||
|
||||
ILI9342 = DriverChip(
|
||||
"ILI9342",
|
||||
width=320,
|
||||
height=240,
|
||||
mirror_x=True,
|
||||
initsequence=(
|
||||
(0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02), # Power Control A
|
||||
(0xCF, 0x00, 0xC1, 0x30), # Power Control B
|
||||
(0xE8, 0x85, 0x00, 0x78), # Driver timing control A
|
||||
(0xEA, 0x00, 0x00), # Driver timing control B
|
||||
(0xED, 0x64, 0x03, 0x12, 0x81), # Power on sequence control
|
||||
(0xF7, 0x20), # Pump ratio control
|
||||
(0xC0, 0x23), # Power Control 1
|
||||
(0xC1, 0x10), # Power Control 2
|
||||
(0xC5, 0x3E, 0x28), # VCOM Control 1
|
||||
(0xC7, 0x86), # VCOM Control 2
|
||||
(0xB1, 0x00, 0x1B), # Frame Rate Control
|
||||
(0xB6, 0x0A, 0xA2, 0x27, 0x00), # Display Function Control
|
||||
(0xF2, 0x00), # Enable 3G
|
||||
(0x26, 0x01), # Gamma Set
|
||||
(0xE0, 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98, 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00), # Positive Gamma
|
||||
(0xE1, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00), # Negative Gamma
|
||||
),
|
||||
)
|
||||
|
||||
# M5Stack Core2 uses ILI9341 chip - mirror_x disabled for correct orientation
|
||||
ILI9341.extend(
|
||||
"M5CORE2",
|
||||
@@ -786,3 +758,5 @@ ST7796.extend(
|
||||
dc_pin=0,
|
||||
invert_colors=True,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -588,3 +588,5 @@ DriverChip(
|
||||
(0x29, 0x00),
|
||||
),
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -11,3 +11,5 @@ ST7789V.extend(
|
||||
dc_pin=21,
|
||||
reset_pin=18,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -56,3 +56,5 @@ ST7796.extend(
|
||||
backlight_pin=48,
|
||||
invert_colors=True,
|
||||
)
|
||||
|
||||
models = {}
|
||||
|
||||
@@ -271,31 +271,24 @@ class ServerRegister {
|
||||
|
||||
// Formats a raw value into a string representation based on the value type for debugging
|
||||
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) {
|
||||
case SensorValueType::U_WORD:
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::U_QWORD:
|
||||
case SensorValueType::U_QWORD_R:
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%" PRIu64, static_cast<uint64_t>(value));
|
||||
return buf;
|
||||
return std::to_string(static_cast<uint64_t>(value));
|
||||
case SensorValueType::S_WORD:
|
||||
case SensorValueType::S_DWORD:
|
||||
case SensorValueType::S_DWORD_R:
|
||||
case SensorValueType::S_QWORD:
|
||||
case SensorValueType::S_QWORD_R:
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, value);
|
||||
return buf;
|
||||
return std::to_string(value);
|
||||
case SensorValueType::FP32_R:
|
||||
case SensorValueType::FP32:
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
|
||||
return buf;
|
||||
return str_sprintf("%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
|
||||
default:
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, value);
|
||||
return buf;
|
||||
return std::to_string(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,20 +16,12 @@ void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
while ((items_left > 0) && index < data.size()) {
|
||||
uint8_t b = data[index];
|
||||
switch (this->encode_) {
|
||||
case RawEncoding::HEXBYTES: {
|
||||
// max 3: 2 hex digits + null
|
||||
char hex_buf[3];
|
||||
snprintf(hex_buf, sizeof(hex_buf), "%02x", b);
|
||||
output_str += hex_buf;
|
||||
case RawEncoding::HEXBYTES:
|
||||
output_str += str_snprintf("%02x", 2, b);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
case RawEncoding::COMMA:
|
||||
output_str += str_sprintf(index != this->offset ? ",%d" : "%d", b);
|
||||
break;
|
||||
}
|
||||
case RawEncoding::ANSI:
|
||||
if (b < 0x20)
|
||||
break;
|
||||
|
||||
@@ -154,7 +154,7 @@ void MPR121GPIOPin::digital_write(bool value) {
|
||||
}
|
||||
|
||||
size_t MPR121GPIOPin::dump_summary(char *buffer, size_t len) const {
|
||||
return buf_append_printf(buffer, len, 0, "ELE%u on MPR121", this->pin_);
|
||||
return snprintf(buffer, len, "ELE%u on MPR121", this->pin_);
|
||||
}
|
||||
|
||||
} // namespace mpr121
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <utility>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/version.h"
|
||||
@@ -67,13 +66,10 @@ void MQTTClientComponent::setup() {
|
||||
"esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); },
|
||||
2);
|
||||
|
||||
// Format topic on stack - subscribe() copies it
|
||||
// "esphome/ping/" (13) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
|
||||
constexpr size_t ping_topic_buffer_size = 13 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
|
||||
char ping_topic[ping_topic_buffer_size];
|
||||
buf_append_printf(ping_topic, sizeof(ping_topic), 0, "esphome/ping/%s", App.get_name().c_str());
|
||||
std::string topic = "esphome/ping/";
|
||||
topic.append(App.get_name());
|
||||
this->subscribe(
|
||||
ping_topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
|
||||
topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
|
||||
}
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
@@ -85,11 +81,8 @@ void MQTTClientComponent::send_device_info_() {
|
||||
if (!this->is_connected() or !this->is_discovery_ip_enabled()) {
|
||||
return;
|
||||
}
|
||||
// Format topic on stack to avoid heap allocation
|
||||
// "esphome/discover/" (17) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
|
||||
constexpr size_t topic_buffer_size = 17 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
|
||||
char topic[topic_buffer_size];
|
||||
buf_append_printf(topic, sizeof(topic), 0, "esphome/discover/%s", App.get_name().c_str());
|
||||
std::string topic = "esphome/discover/";
|
||||
topic.append(App.get_name());
|
||||
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
this->publish_json(
|
||||
@@ -403,12 +396,6 @@ void MQTTClientComponent::loop() {
|
||||
|
||||
this->last_connected_ = now;
|
||||
this->resubscribe_subscriptions_();
|
||||
|
||||
// Process pending resends for all MQTT components centrally
|
||||
// This is more efficient than each component polling in its own loop
|
||||
for (MQTTComponent *component : this->children_) {
|
||||
component->process_resend();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -513,49 +500,39 @@ bool MQTTClientComponent::publish(const std::string &topic, const std::string &p
|
||||
|
||||
bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos,
|
||||
bool retain) {
|
||||
return this->publish(topic.c_str(), payload, payload_length, qos, retain);
|
||||
return publish({.topic = topic, .payload = std::string(payload, payload_length), .qos = qos, .retain = retain});
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish(const MQTTMessage &message) {
|
||||
return this->publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos,
|
||||
message.retain);
|
||||
}
|
||||
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
|
||||
bool retain) {
|
||||
return this->publish_json(topic.c_str(), f, qos, retain);
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos,
|
||||
bool retain) {
|
||||
if (!this->is_connected()) {
|
||||
// critical components will re-transmit their messages
|
||||
return false;
|
||||
}
|
||||
size_t topic_len = strlen(topic);
|
||||
bool logging_topic = (topic_len == this->log_message_.topic.size()) &&
|
||||
(memcmp(this->log_message_.topic.c_str(), topic, topic_len) == 0);
|
||||
bool ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
|
||||
bool logging_topic = this->log_message_.topic == message.topic;
|
||||
bool ret = this->mqtt_backend_.publish(message);
|
||||
delay(0);
|
||||
if (!ret && !logging_topic && this->is_connected()) {
|
||||
delay(0);
|
||||
ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
|
||||
ret = this->mqtt_backend_.publish(message);
|
||||
delay(0);
|
||||
}
|
||||
|
||||
if (!logging_topic) {
|
||||
if (ret) {
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' retain=%d qos=%d)", topic, retain, qos);
|
||||
ESP_LOGVV(TAG, "Publish payload (len=%u): '%.*s'", payload_length, static_cast<int>(payload_length), payload);
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d qos=%d)", message.topic.c_str(), message.payload.c_str(),
|
||||
message.retain, message.qos);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", topic, payload_length);
|
||||
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", message.topic.c_str(),
|
||||
message.payload.length());
|
||||
this->status_momentary_warning("publish", 1000);
|
||||
}
|
||||
}
|
||||
return ret != 0;
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish_json(const char *topic, const json::json_build_t &f, uint8_t qos, bool retain) {
|
||||
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
|
||||
bool retain) {
|
||||
std::string message = json::build_json(f);
|
||||
return this->publish(topic, message.c_str(), message.length(), qos, retain);
|
||||
return this->publish(topic, message, qos, retain);
|
||||
}
|
||||
|
||||
void MQTTClientComponent::enable() {
|
||||
@@ -633,10 +610,18 @@ static bool topic_match(const char *message, const char *subscription) {
|
||||
}
|
||||
|
||||
void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) {
|
||||
for (auto &subscription : this->subscriptions_) {
|
||||
if (topic_match(topic.c_str(), subscription.topic.c_str()))
|
||||
subscription.callback(topic, payload);
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
// on ESP8266, this is called in lwIP/AsyncTCP task; some components do not like running
|
||||
// from a different task.
|
||||
this->defer([this, topic, payload]() {
|
||||
#endif
|
||||
for (auto &subscription : this->subscriptions_) {
|
||||
if (topic_match(topic.c_str(), subscription.topic.c_str()))
|
||||
subscription.callback(topic, payload);
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
// Setters
|
||||
@@ -650,8 +635,7 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this
|
||||
const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; }
|
||||
void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix, const std::string &check_topic_prefix) {
|
||||
if (App.is_name_add_mac_suffix_enabled() && (topic_prefix == check_topic_prefix)) {
|
||||
char buf[ESPHOME_DEVICE_NAME_MAX_LEN + 1];
|
||||
this->topic_prefix_ = str_sanitize_to(buf, App.get_name().c_str());
|
||||
this->topic_prefix_ = str_sanitize(App.get_name());
|
||||
} else {
|
||||
this->topic_prefix_ = topic_prefix;
|
||||
}
|
||||
|
||||
@@ -229,9 +229,6 @@ class MQTTClientComponent : public Component
|
||||
bool publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos = 0,
|
||||
bool retain = false);
|
||||
|
||||
/// Publish directly without creating MQTTMessage (avoids heap allocation for topic)
|
||||
bool publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos = 0, bool retain = false);
|
||||
|
||||
/** Construct and send a JSON MQTT message.
|
||||
*
|
||||
* @param topic The topic.
|
||||
@@ -240,9 +237,6 @@ class MQTTClientComponent : public Component
|
||||
*/
|
||||
bool publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false);
|
||||
|
||||
/// Publish JSON directly without heap allocation for topic
|
||||
bool publish_json(const char *topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false);
|
||||
|
||||
/// Setup the MQTT client, registering a bunch of callbacks and attempting to connect.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -48,8 +48,7 @@ void MQTTComponent::set_subscribe_qos(uint8_t qos) { this->subscribe_qos_ = qos;
|
||||
void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; }
|
||||
|
||||
std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const {
|
||||
char sanitized_name[ESPHOME_DEVICE_NAME_MAX_LEN + 1];
|
||||
str_sanitize_to(sanitized_name, App.get_name().c_str());
|
||||
std::string sanitized_name = str_sanitize(App.get_name());
|
||||
const char *comp_type = this->component_type();
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
StringRef object_id = this->get_default_object_id_to_(object_id_buf);
|
||||
@@ -61,7 +60,7 @@ std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discove
|
||||
p = append_char(p, '/');
|
||||
p = append_str(p, comp_type, strlen(comp_type));
|
||||
p = append_char(p, '/');
|
||||
p = append_str(p, sanitized_name, strlen(sanitized_name));
|
||||
p = append_str(p, sanitized_name.data(), sanitized_name.size());
|
||||
p = append_char(p, '/');
|
||||
p = append_str(p, object_id.c_str(), object_id.size());
|
||||
p = append_str(p, "/config", 7);
|
||||
@@ -190,7 +189,8 @@ bool MQTTComponent::send_discovery_() {
|
||||
StringRef object_id = this->get_default_object_id_to_(object_id_buf);
|
||||
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
||||
char friendly_name_hash[9];
|
||||
snprintf(friendly_name_hash, sizeof(friendly_name_hash), "%08" PRIx32, fnv1_hash(this->friendly_name_()));
|
||||
sprintf(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")
|
||||
// MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43
|
||||
char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11];
|
||||
@@ -308,12 +308,16 @@ void MQTTComponent::call_setup() {
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTComponent::process_resend() {
|
||||
// Called by MQTTClientComponent when connected to process pending resends
|
||||
// Note: is_internal() check not needed - internal components are never registered
|
||||
if (!this->resend_state_)
|
||||
void MQTTComponent::call_loop() {
|
||||
if (this->is_internal())
|
||||
return;
|
||||
|
||||
this->loop();
|
||||
|
||||
if (!this->resend_state_ || !this->is_connected_()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->resend_state_ = false;
|
||||
if (this->is_discovery_enabled()) {
|
||||
if (!this->send_discovery_()) {
|
||||
|
||||
@@ -81,6 +81,8 @@ class MQTTComponent : public Component {
|
||||
/// Override setup_ so that we can call send_discovery() when needed.
|
||||
void call_setup() override;
|
||||
|
||||
void call_loop() override;
|
||||
|
||||
void call_dump_config() override;
|
||||
|
||||
/// Send discovery info the Home Assistant, override this.
|
||||
@@ -131,9 +133,6 @@ class MQTTComponent : public Component {
|
||||
/// Internal method for the MQTT client base to schedule a resend of the state on reconnect.
|
||||
void schedule_resend_state();
|
||||
|
||||
/// Process pending resend if needed (called by MQTTClientComponent)
|
||||
void process_resend();
|
||||
|
||||
/** Send a MQTT message.
|
||||
*
|
||||
* @param topic The topic.
|
||||
|
||||
@@ -43,14 +43,6 @@ namespace network {
|
||||
/// Buffer size for IP address string (IPv6 max: 39 chars + null)
|
||||
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 {
|
||||
public:
|
||||
#ifdef USE_HOST
|
||||
@@ -60,17 +52,10 @@ struct IPAddress {
|
||||
}
|
||||
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; }
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use str_to() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string str() const {
|
||||
char buf[IP_ADDRESS_BUFFER_SIZE];
|
||||
this->str_to(buf);
|
||||
return buf;
|
||||
}
|
||||
std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); }
|
||||
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
||||
char *str_to(char *buf) const {
|
||||
inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE);
|
||||
return buf; // IPv4 only, no hex letters to lowercase
|
||||
return const_cast<char *>(inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE));
|
||||
}
|
||||
#else
|
||||
IPAddress() { ip_addr_set_zero(&ip_addr_); }
|
||||
@@ -149,20 +134,9 @@ struct IPAddress {
|
||||
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use str_to() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string str() const {
|
||||
char buf[IP_ADDRESS_BUFFER_SIZE];
|
||||
this->str_to(buf);
|
||||
return buf;
|
||||
}
|
||||
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
|
||||
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
||||
/// 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;
|
||||
}
|
||||
char *str_to(char *buf) const { return ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE); }
|
||||
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) {
|
||||
|
||||
@@ -11,12 +11,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, TimePeriod
|
||||
|
||||
from . import ( # noqa: F401 pylint: disable=unused-import
|
||||
FILTER_SOURCE_FILES,
|
||||
Nextion,
|
||||
nextion_ns,
|
||||
nextion_ref,
|
||||
)
|
||||
from . import Nextion, nextion_ns, nextion_ref
|
||||
from .base_component import (
|
||||
CONF_AUTO_WAKE_ON_TOUCH,
|
||||
CONF_COMMAND_SPACING,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "nextion.h"
|
||||
#include <cinttypes>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
@@ -1284,9 +1283,8 @@ void Nextion::check_pending_waveform_() {
|
||||
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
|
||||
: 255; // ADDT command can only send 255
|
||||
|
||||
char command[24]; // "addt " + uint8 + "," + uint8 + "," + uint8 + null = max 17 chars
|
||||
buf_append_printf(command, sizeof(command), 0, "addt %u,%u,%zu", component->get_component_id(),
|
||||
component->get_wave_channel_id(), buffer_to_send);
|
||||
std::string command = "addt " + to_string(component->get_component_id()) + "," +
|
||||
to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send);
|
||||
if (!this->send_command_(command)) {
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->waveform_queue_.pop_front();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user