mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
Merge branch 'esp32-pio-pch' into nrf52-pch
This commit is contained in:
+30
-35
@@ -193,9 +193,10 @@ jobs:
|
||||
needs:
|
||||
- common
|
||||
# PR-branch cache saves are invisible to other PRs, so dev pushes seed
|
||||
# the shared entry test-esp8266-native restores. Only dev: the composite
|
||||
# action saves nowhere else, so a beta/release push would download the
|
||||
# toolchain and discard it.
|
||||
# the shared entry the component matrix, the memory impact jobs and
|
||||
# test-esp8266-native restore. Only dev: the composite action saves
|
||||
# nowhere else, so a beta/release push would download the toolchain and
|
||||
# discard it.
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
@@ -399,14 +400,9 @@ jobs:
|
||||
matrix:
|
||||
bucket: ${{ fromJson(needs.determine-jobs.outputs.integration-test-buckets) }}
|
||||
env:
|
||||
# What the cache steps persist; libdeps is excluded (keyed per xdist
|
||||
# worker and env, it never crosses runs).
|
||||
INTEGRATION_PIO_CACHE_PATH: |
|
||||
~/.esphome-integration-tests/platformio/platforms
|
||||
~/.esphome-integration-tests/platformio/packages
|
||||
~/.esphome-integration-tests/platformio/appstate.json
|
||||
~/.esphome-integration-tests/platformio/.cache
|
||||
~/.esphome-integration-tests/platformio/.esphome.pio.stamp.json
|
||||
# Registry libraries (noise-c, libsodium, ArduinoJson, lvgl) the host builds
|
||||
# download, shared per xdist worker by tests/integration/conftest.py
|
||||
INTEGRATION_LIBRARY_CACHE_PATH: ~/.esphome-integration-tests/pio_components
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -426,14 +422,13 @@ jobs:
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Restore integration PlatformIO cache
|
||||
# Native platform + toolchain installed by shared_platformio_cache in
|
||||
# tests/integration/conftest.py; a miss self-heals, so no restore-keys.
|
||||
id: pio-cache
|
||||
- name: Restore integration library cache
|
||||
# A miss or a changed pin self-heals with a download, so no restore-keys
|
||||
id: library-cache
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_PIO_CACHE_PATH }}
|
||||
key: integration-pio-v1-${{ runner.os }}-py${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt', 'tests/integration/fixtures/cache_init.yaml', 'esphome/components/host/__init__.py') }}
|
||||
path: ${{ env.INTEGRATION_LIBRARY_CACHE_PATH }}
|
||||
key: integration-libraries-v1-${{ runner.os }}-${{ hashFiles('esphome/components/json/__init__.py', 'esphome/components/noise/__init__.py', 'esphome/components/lvgl/__init__.py', 'esphome/components/improv_base/__init__.py') }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
@@ -501,16 +496,16 @@ jobs:
|
||||
# A full cron period of margin for the weekly refresh
|
||||
retention-days: 14
|
||||
- name: Print ccache statistics
|
||||
# esphome stores the PlatformIO ccache under the machine-global cache
|
||||
# dir (see _ccache_env() in esphome/platformio/toolchain.py).
|
||||
run: CCACHE_DIR="$HOME/.cache/esphome/platformio-ccache" ccache -s
|
||||
- name: Save integration PlatformIO cache
|
||||
# esphome stores the host build's ccache under the machine-global
|
||||
# cache dir (see get_build_env() in esphome/host/toolchain.py).
|
||||
run: CCACHE_DIR="$HOME/.cache/esphome/host/ccache" ccache -s
|
||||
- name: Save integration library cache
|
||||
# Bucket 0 only; the others would race the same immutable key.
|
||||
if: success() && (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write')) && strategy.job-index == 0 && steps.pio-cache.outputs.cache-hit != 'true'
|
||||
if: success() && (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write')) && strategy.job-index == 0 && steps.library-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_PIO_CACHE_PATH }}
|
||||
key: ${{ steps.pio-cache.outputs.cache-primary-key }}
|
||||
path: ${{ env.INTEGRATION_LIBRARY_CACHE_PATH }}
|
||||
key: ${{ steps.library-cache.outputs.cache-primary-key }}
|
||||
|
||||
import-time:
|
||||
name: Check import esphome.__main__ time
|
||||
@@ -1127,6 +1122,11 @@ jobs:
|
||||
uses: ./.github/actions/cache-sdk-nrf
|
||||
with:
|
||||
restore-only: true
|
||||
- name: Cache the native ESP8266 toolchain
|
||||
# Only batches whose test platforms include esp8266; never saves
|
||||
# here, it reuses the install the dev seed job cached.
|
||||
if: matrix.batch.needs_arduino8266
|
||||
uses: ./.github/actions/cache-arduino8266
|
||||
- name: Validate and compile components with intelligent grouping
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -1481,12 +1481,9 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
- name: Cache the native ESP8266 toolchain
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && fromJSON(needs.determine-jobs.outputs.memory_impact).needs_arduino8266
|
||||
uses: ./.github/actions/cache-arduino8266
|
||||
|
||||
- name: Build, compile, and analyze memory
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
@@ -1571,11 +1568,9 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache platformio
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
- name: Cache the native ESP8266 toolchain
|
||||
if: fromJSON(needs.determine-jobs.outputs.memory_impact).needs_arduino8266
|
||||
uses: ./.github/actions/cache-arduino8266
|
||||
- name: Build, compile, and analyze memory
|
||||
id: extract
|
||||
run: |
|
||||
|
||||
@@ -322,6 +322,25 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
var = await switch.new_switch(config)
|
||||
```
|
||||
|
||||
- **Optional child entities of a hub:** bind the config once with `sensor.sub_sensors(config)` (or
|
||||
`sub_binary_sensors`, `sub_text_sensors`, `sub_buttons`, `sub_switches`, `sub_numbers`,
|
||||
`sub_selects` in their domains), adding `parent=hub` for entities that derive from `Parented<T>`,
|
||||
then make one call per key, even when there is only one. A call creates the entity only when its key
|
||||
is configured, passes it to the setter and returns it (or `None`); extra arguments such as
|
||||
`min_value` or `options` go on the call. Always name the setter explicitly on the object that owns
|
||||
it, never with `getattr` and an f-string, and keep that variable short (`var` for the component
|
||||
itself, `hub` for one fetched with `cg.get_variable`) so the calls fit on one line. Loops whose
|
||||
setter also takes an index, such as `set_gate_threshold(x, n)`, stay as they are.
|
||||
```python
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
buttons = button.sub_buttons(config, parent=var)
|
||||
await buttons(CONF_RESTART, var.set_restart_button)
|
||||
```
|
||||
|
||||
* **Automations (Triggers, Actions, Conditions):**
|
||||
|
||||
Automations have three building blocks: **Triggers** (fire when something happens), **Actions** (do something), and **Conditions** (check if something is true).
|
||||
|
||||
@@ -125,6 +125,7 @@ esphome/components/combination/* @Cat-Ion @kahrendt
|
||||
esphome/components/const/* @esphome/core
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/copy/* @OttoWinter
|
||||
esphome/components/counter/* @clydebarrow
|
||||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
esphome/components/cse7761/* @berfenger
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.16.0
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.17.0
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
+14
-23
@@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, Protocol
|
||||
# cause them to be loaded before external components are processed, resulting
|
||||
# in the built-in version being used instead of the external component one.
|
||||
from esphome import const, platform_hooks
|
||||
from esphome.build_helpers.native import native_backend
|
||||
from esphome.build_helpers.native import analysis_backend, native_backend
|
||||
from esphome.const import (
|
||||
ALLOWED_NAME_CHARS,
|
||||
ARGUMENT_HELP_DEVICE,
|
||||
@@ -1714,26 +1714,12 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
if CORE.is_host:
|
||||
_LOGGER.info(
|
||||
"Successfully compiled program to path '%s'", _host_program_path(config)
|
||||
)
|
||||
_LOGGER.info("Successfully compiled program to path '%s'", CORE.firmware_bin)
|
||||
else:
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
return 0
|
||||
|
||||
|
||||
def _host_program_path(config: ConfigType) -> str:
|
||||
"""Return the compiled host ELF path."""
|
||||
if CORE.using_toolchain_esp_idf:
|
||||
from esphome.espidf import toolchain
|
||||
|
||||
return str(toolchain.get_elf_path())
|
||||
from esphome.platformio.toolchain import get_idedata
|
||||
|
||||
# Memoized by compile_program's own call; this is a dict lookup
|
||||
return str(get_idedata(config).firmware_elf_path)
|
||||
|
||||
|
||||
def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
# Get devices, resolving special identifiers like OTA
|
||||
devices = choose_upload_log_host(
|
||||
@@ -1778,7 +1764,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
if CORE.is_host:
|
||||
program_path = _host_program_path(config)
|
||||
program_path = str(CORE.firmware_bin)
|
||||
_LOGGER.info("Running program from path '%s'", program_path)
|
||||
return run_external_process(program_path)
|
||||
|
||||
@@ -2015,8 +2001,8 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
from esphome.analyze_memory.ram_strings import RamStringsAnalyzer
|
||||
|
||||
# Refuse an unsupported toolchain before paying for a full compile
|
||||
native_toolchain = native_backend()
|
||||
if native_toolchain is None and not CORE.using_toolchain_platformio:
|
||||
analysis_toolchain = analysis_backend()
|
||||
if analysis_toolchain is None and not CORE.using_toolchain_platformio:
|
||||
_LOGGER.error(
|
||||
"analyze-memory is not supported with the '%s' toolchain on %s; "
|
||||
"re-run with --toolchain platformio",
|
||||
@@ -2024,6 +2010,11 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
CORE.target_platform,
|
||||
)
|
||||
return 1
|
||||
if (
|
||||
check_supported := getattr(analysis_toolchain, "check_analysis_supported", None)
|
||||
) is not None:
|
||||
# Raises with the reason; before the compile, not after it
|
||||
check_supported()
|
||||
|
||||
# Always compile to ensure fresh data (fast if no changes - just relinks)
|
||||
exit_code = write_cpp(config)
|
||||
@@ -2036,9 +2027,9 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
|
||||
# Get idedata for analysis
|
||||
idedata = None
|
||||
if native_toolchain is not None:
|
||||
objdump = native_toolchain.get_objdump_path()
|
||||
readelf = native_toolchain.get_readelf_path()
|
||||
if analysis_toolchain is not None:
|
||||
objdump = analysis_toolchain.get_objdump_path()
|
||||
readelf = analysis_toolchain.get_readelf_path()
|
||||
for tool in (objdump, readelf):
|
||||
if not tool.is_file():
|
||||
# The analyzer would silently fall back to host
|
||||
@@ -2052,7 +2043,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
objdump_path = str(objdump)
|
||||
readelf_path = str(readelf)
|
||||
|
||||
firmware_elf = native_toolchain.get_elf_path()
|
||||
firmware_elf = analysis_toolchain.get_elf_path()
|
||||
if not firmware_elf.is_file():
|
||||
# The analyzer swallows tool failures, so a missing ELF would
|
||||
# produce an exit-0 zeroed report
|
||||
|
||||
+33
-15
@@ -2,7 +2,9 @@
|
||||
|
||||
Bundled names build straight from the framework tree; everything else goes
|
||||
through ``esphome.platformio.library``. Mirrors ``lib_ldf_mode=off``: each
|
||||
library builds its own archive; all include dirs join one global path.
|
||||
library builds its own archive; all include dirs join one global path. The
|
||||
host build reuses it without a framework tree: nothing is bundled there and
|
||||
every name resolves from the registry.
|
||||
|
||||
Deviations from PlatformIO: flat-layout libraries get the recursive default
|
||||
source filter; ``dot_a_linkage`` is honored; bundled libraries never run a
|
||||
@@ -342,13 +344,24 @@ def _check_unfulfilled_provides(
|
||||
|
||||
|
||||
def resolve_libraries(
|
||||
framework_path: Path, *, pio_platform: str, board_mcu: str, cache_key: str
|
||||
framework_path: Path | None,
|
||||
*,
|
||||
pio_platform: str,
|
||||
board_mcu: str,
|
||||
cache_key: str,
|
||||
framework: str | None = "arduino",
|
||||
manifest_optional: bool = False,
|
||||
) -> list[ArduinoLibrary]:
|
||||
"""Resolve every ``cg.add_library()`` entry into an :class:`ArduinoLibrary`.
|
||||
|
||||
``pio_platform``/``board_mcu`` filter manifests the way PlatformIO would
|
||||
for that core (e.g. ``espressif8266``/``esp8266``); ``cache_key`` keys the
|
||||
shared converter's download cache.
|
||||
shared converter's download cache. ``framework`` is the manifest
|
||||
framework token the compatibility check warns about; None skips it.
|
||||
A None ``framework_path`` means no core-bundled libraries exist (the
|
||||
host build): every name resolves from the registry.
|
||||
``manifest_optional`` accepts libraries without a manifest, built with
|
||||
PlatformIO's default layout.
|
||||
|
||||
The returned list is not topologically sorted, so the caller must link
|
||||
the archives inside one ``--start-group``/``--end-group`` pair (the
|
||||
@@ -359,18 +372,22 @@ def resolve_libraries(
|
||||
# PlatformIO's lib_ignore covers framework-bundled libraries too; the
|
||||
# shared converter only filters the registry/git ones.
|
||||
lib_ignore = lib_ignore_set()
|
||||
# Exact directory names keep membership case-sensitive everywhere
|
||||
# (an is_dir() probe would match "wire" on macOS/Windows and build
|
||||
# the bundled Wire twice)
|
||||
libraries_dir = framework_path / "libraries"
|
||||
if not libraries_dir.is_dir():
|
||||
# A registry fallback would fail later with a misleading
|
||||
# package-not-found error per bundled name
|
||||
raise EsphomeError(
|
||||
f"{libraries_dir} is missing; the framework install may be "
|
||||
"incomplete (run 'esphome clean-all')"
|
||||
bundled_dir_names: frozenset[str] = frozenset()
|
||||
if framework_path is not None:
|
||||
# Exact directory names keep membership case-sensitive everywhere
|
||||
# (an is_dir() probe would match "wire" on macOS/Windows and build
|
||||
# the bundled Wire twice)
|
||||
libraries_dir = framework_path / "libraries"
|
||||
if not libraries_dir.is_dir():
|
||||
# A registry fallback would fail later with a misleading
|
||||
# package-not-found error per bundled name
|
||||
raise EsphomeError(
|
||||
f"{libraries_dir} is missing; the framework install may be "
|
||||
"incomplete (run 'esphome clean-all')"
|
||||
)
|
||||
bundled_dir_names = frozenset(
|
||||
p.name for p in libraries_dir.iterdir() if p.is_dir()
|
||||
)
|
||||
bundled_dir_names = frozenset(p.name for p in libraries_dir.iterdir() if p.is_dir())
|
||||
|
||||
def _provided(name: object) -> bool:
|
||||
return _is_safe_library_name(name) and name in bundled_dir_names
|
||||
@@ -497,12 +514,13 @@ def resolve_libraries(
|
||||
|
||||
backend = LibraryBackend(
|
||||
platform=pio_platform,
|
||||
framework="arduino",
|
||||
framework=framework,
|
||||
emit=_emit,
|
||||
cache_key=cache_key,
|
||||
# The walk must not resolve bundled names from the registry;
|
||||
# _add_bundled_dependencies adds them after emit
|
||||
provides=_provided,
|
||||
manifest_optional=manifest_optional,
|
||||
)
|
||||
if external:
|
||||
convert_libraries(external, backend)
|
||||
|
||||
@@ -17,9 +17,8 @@ import os
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple
|
||||
|
||||
from esphome.build_helpers.ccache import ccache_defaults_env
|
||||
from esphome.build_helpers.ccache import ccache_env
|
||||
from esphome.build_helpers.ninja import find_ninja
|
||||
from esphome.build_helpers.pch import ccache_pch_env
|
||||
from esphome.build_helpers.tools_cache import ARDUINO8266_TOOLS_CACHE, tools_cache_path
|
||||
from esphome.core import EsphomeError, Version
|
||||
from esphome.framework_helpers import str_to_lst_of_str
|
||||
@@ -144,19 +143,5 @@ def get_build_env(toolchain_path: Path, ccache: str | None) -> dict[str, str]:
|
||||
*filter(None, env.get("PATH", "").split(os.pathsep)),
|
||||
]
|
||||
env["PATH"] = os.pathsep.join(parts)
|
||||
env.update(ccache_env(ccache))
|
||||
return env
|
||||
|
||||
|
||||
def ccache_env(ccache: str | None) -> dict[str, str]:
|
||||
"""Return ccache settings for the build subprocess (not os.environ).
|
||||
|
||||
``ccache`` is the pre-resolved binary (resolve_ccache_path), or None
|
||||
when disabled. Values the user already set in the environment are
|
||||
respected.
|
||||
"""
|
||||
if ccache is None:
|
||||
return {}
|
||||
env = ccache_defaults_env(get_arduino8266_tools_path() / "ccache")
|
||||
env.update(ccache_pch_env())
|
||||
env.update(ccache_env(ccache, ARDUINO8266_TOOLS_CACHE))
|
||||
return env
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from esphome.build_helpers.ccache import resolve_ccache_path
|
||||
from esphome.build_helpers.ccache import resolve_absolute_ccache_path
|
||||
from esphome.build_helpers.native import warn_ignored_platformio_options
|
||||
from esphome.build_helpers.ninja import refresh_compile_commands
|
||||
from esphome.const import (
|
||||
CONF_COMPILE_PROCESS_LIMIT,
|
||||
CONF_ESPHOME,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.helpers import write_file
|
||||
from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -28,20 +28,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
_MAX_RAM_SIZE = 81920
|
||||
|
||||
|
||||
def _warn_ignored_platformio_options() -> None:
|
||||
"""Warn for component-added platformio options the native build drops."""
|
||||
from esphome.core.config import NATIVE_ARDUINO_CONSUMED_PIO_OPTIONS
|
||||
|
||||
consumed = NATIVE_ARDUINO_CONSUMED_PIO_OPTIONS
|
||||
for key in sorted(CORE.platformio_options or {}):
|
||||
if key not in consumed:
|
||||
_LOGGER.warning(
|
||||
"platformio_options->%s is ignored when building with the "
|
||||
"native 'arduino' toolchain",
|
||||
key,
|
||||
)
|
||||
|
||||
|
||||
_RAM_SECTIONS = (".data", ".rodata", ".bss")
|
||||
_FLASH_SECTIONS = (".irom0.text", ".text", ".text1", ".data", ".rodata")
|
||||
|
||||
@@ -84,21 +70,17 @@ def get_readelf_path() -> Path:
|
||||
def run_compile(config: ConfigType, verbose: bool) -> int:
|
||||
from esphome.arduino8266 import framework
|
||||
from esphome.build_gen import arduino8266 as build_gen
|
||||
from esphome.core.config import NATIVE_ARDUINO_CONSUMED_PIO_OPTIONS
|
||||
|
||||
_warn_ignored_platformio_options()
|
||||
warn_ignored_platformio_options(NATIVE_ARDUINO_CONSUMED_PIO_OPTIONS)
|
||||
paths = framework.check_and_install(CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION])
|
||||
# Resolved once: the probe is not free and three consumers need it
|
||||
ccache = resolve_ccache_path()
|
||||
build_gen.write_project(paths, ccache)
|
||||
ccache = resolve_absolute_ccache_path()
|
||||
ninja_changed = build_gen.write_project(paths, ccache)
|
||||
|
||||
build_dir = get_build_dir()
|
||||
env = framework.get_build_env(paths.toolchain, ccache)
|
||||
|
||||
# The DB is a pure function of build.ninja; regenerate when it is older
|
||||
compdb = build_dir / "compile_commands.json"
|
||||
ninja_file = build_dir / "build.ninja"
|
||||
if not compdb.is_file() or compdb.stat().st_mtime < ninja_file.stat().st_mtime:
|
||||
_write_compile_commands(paths.ninja, build_dir, env)
|
||||
refresh_compile_commands(paths.ninja, build_dir, env, ninja_changed)
|
||||
|
||||
cmd = [str(paths.ninja)]
|
||||
if verbose:
|
||||
@@ -140,42 +122,6 @@ def run_compile(config: ConfigType, verbose: bool) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _write_compile_commands(
|
||||
ninja_path: Path, build_dir: Path, env: dict[str, str]
|
||||
) -> None:
|
||||
compdb = build_dir / "compile_commands.json"
|
||||
result = subprocess.run(
|
||||
[str(ninja_path), "-C", str(build_dir), "-t", "compdb", "c", "cxx", "asm"],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
close_fds=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
# Drop any stale database so consumers (IDE integration, clang-tidy,
|
||||
# the memory analyzer) can't silently read outdated data.
|
||||
compdb.unlink(missing_ok=True)
|
||||
raise EsphomeError(f"Could not generate compile_commands.json: {result.stderr}")
|
||||
try:
|
||||
entries = json.loads(result.stdout)
|
||||
except ValueError as err:
|
||||
compdb.unlink(missing_ok=True)
|
||||
raise EsphomeError(
|
||||
f"ninja produced an unparsable compile database: {err} "
|
||||
f"(output starts {result.stdout[:120]!r})"
|
||||
) from err
|
||||
if not entries:
|
||||
# compdb exits 0 with [] for unknown rule names; a renamed compile
|
||||
# rule must fail the build, not silently strand every consumer
|
||||
compdb.unlink(missing_ok=True)
|
||||
raise EsphomeError(
|
||||
"ninja produced an empty compile database; the generator's rule "
|
||||
"names no longer match"
|
||||
)
|
||||
write_file(compdb, result.stdout)
|
||||
|
||||
|
||||
def _parse_app_size(build_dir: Path, paths: InstalledPaths) -> int | None:
|
||||
"""Read the app flash budget (irom0_0_seg length) from the linker script."""
|
||||
from esphome.build_gen.arduino8266 import get_flash_ld_path
|
||||
@@ -253,7 +199,7 @@ def get_idedata(ccache: str | None = None) -> dict | None:
|
||||
|
||||
# A disabled ccache resolves to None without spawning anything, so
|
||||
# re-resolving here costs nothing when the caller has no answer
|
||||
launcher = ccache or resolve_ccache_path()
|
||||
launcher = ccache or resolve_absolute_ccache_path()
|
||||
return load_or_build_idedata(
|
||||
get_build_dir() / "compile_commands.json",
|
||||
get_elf_path(),
|
||||
|
||||
+26
-13
@@ -345,14 +345,28 @@ async def _apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str:
|
||||
|
||||
|
||||
def _apply_lambda_args(args: TemplateArgsType) -> TemplateArgsType:
|
||||
# Must match ApplyAction::ApplyFn and ApplyCondition::CheckFn exactly for the function
|
||||
# pointer conversion.
|
||||
# The generated function's parameters; a std::string arg is never copied.
|
||||
return [
|
||||
(cg.RawExpression(f"const std::remove_cvref_t<{cg.safe_exp(t)}> &"), arg)
|
||||
for t, arg in args
|
||||
]
|
||||
|
||||
|
||||
def _apply_function(
|
||||
id_: ID,
|
||||
return_type: SafeExpType,
|
||||
template_arg: cg.TemplateArguments,
|
||||
lambda_args: TemplateArgsType,
|
||||
statements: list[str],
|
||||
) -> MockObj:
|
||||
"""Emit the generated function and declare ``id_`` as the ``ApplyAction`` or
|
||||
``ApplyCondition`` templated on it, so ``play()`` calls it directly."""
|
||||
fn = cg.static_function(
|
||||
f"esphome__{id_.id}__fn", return_type, lambda_args, statements
|
||||
)
|
||||
return cg.new_Pvariable(id_, cg.TemplateArguments(fn, *template_arg))
|
||||
|
||||
|
||||
async def _render_values(
|
||||
name: str,
|
||||
target: str,
|
||||
@@ -403,8 +417,8 @@ def register_apply_action(
|
||||
) -> None:
|
||||
"""Register an action that only forwards config values to its parent, with no C++ class.
|
||||
|
||||
Generates one stateless function for ``ApplyAction<Ts...>``: the parent (read from
|
||||
``id_key``) and constants are baked in, lambdas are called inline with the trigger args.
|
||||
Generates one static function with the parent (read from ``id_key``) and constants baked
|
||||
in, lambdas called inline with the trigger args, and an ``ApplyAction`` templated on it.
|
||||
A constant that is an id (``cv.use_id`` under ``cv.templatable``) is the object it names.
|
||||
With ``call`` every statement targets the call object ``auto apply_call = parent->call()``,
|
||||
and ``apply_call.perform()`` is appended.
|
||||
@@ -449,10 +463,9 @@ def register_apply_action(
|
||||
*statements,
|
||||
"apply_call.perform();",
|
||||
]
|
||||
apply_lambda = LambdaExpression(
|
||||
["\n".join(statements)], lambda_args, capture="", return_type=cg.void
|
||||
return _apply_function(
|
||||
action_id, cg.void, template_arg, lambda_args, statements
|
||||
)
|
||||
return cg.new_Pvariable(action_id, template_arg, apply_lambda)
|
||||
|
||||
register_action(name, ApplyAction, schema, synchronous=True)(builder)
|
||||
|
||||
@@ -466,7 +479,7 @@ def register_apply_condition(
|
||||
``ApplyCall`` such as ``ApplyCall("state == {}", ((CONF_STATE, cg.bool_),))`` compares
|
||||
against config values, all of which must be present. Write ``== false`` to negate.
|
||||
String constants are plain literals, so compare a ``std::string`` or ``StringRef`` member.
|
||||
Generates one stateless function for ``ApplyCondition<Ts...>``.
|
||||
Generates one static predicate and an ``ApplyCondition`` templated on it.
|
||||
"""
|
||||
call = check if isinstance(check, ApplyCall) else ApplyCall(check)
|
||||
members = call.members
|
||||
@@ -492,13 +505,13 @@ def register_apply_condition(
|
||||
lambda_args,
|
||||
compare=True,
|
||||
)
|
||||
check_lambda = LambdaExpression(
|
||||
[f"return {parent}->{call.target.format(*exprs)};"],
|
||||
return _apply_function(
|
||||
condition_id,
|
||||
cg.bool_,
|
||||
template_arg,
|
||||
lambda_args,
|
||||
capture="",
|
||||
return_type=cg.bool_,
|
||||
[f"return {parent}->{call.target.format(*exprs)};"],
|
||||
)
|
||||
return cg.new_Pvariable(condition_id, template_arg, check_lambda)
|
||||
|
||||
register_condition(name, ApplyCondition, schema)(builder)
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ from the build flags with the same precedence as the PlatformIO builder.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Collection
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
import logging
|
||||
@@ -23,7 +22,6 @@ from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, NamedTuple
|
||||
|
||||
from esphome.arduino8266.framework import toolchain_tool
|
||||
@@ -34,6 +32,15 @@ from esphome.build_helpers.ninja import (
|
||||
quote_path as _q,
|
||||
shell_token as _shell_token,
|
||||
)
|
||||
from esphome.build_helpers.ninja_gen import (
|
||||
ar_rule_lines,
|
||||
collect_sources,
|
||||
compile_edges,
|
||||
compile_rule_lines,
|
||||
library_edges,
|
||||
pch_rule_lines,
|
||||
tool_lines,
|
||||
)
|
||||
from esphome.build_helpers.pch import (
|
||||
PCH_CORE_HEADER,
|
||||
PCH_HEADER_NAME,
|
||||
@@ -61,12 +68,9 @@ from esphome.components.esp8266.const import (
|
||||
KEY_SCANF_FLOAT,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.framework_helpers import (
|
||||
get_project_cxx_compile_flags,
|
||||
strip_win_long_path_prefix,
|
||||
)
|
||||
from esphome.framework_helpers import get_project_cxx_compile_flags
|
||||
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||
from esphome.platformio.library import SOURCE_KIND_FOR_SUFFIX, lex_build_flags
|
||||
from esphome.platformio.library import lex_build_flags
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.arduino8266.framework import InstalledPaths
|
||||
@@ -658,14 +662,6 @@ _PLAIN_LINKER_PREFIXES = ("-T", "-Xlinker")
|
||||
_PLAIN_DRIVER_LINK_PREFIXES = ("-fuse-ld=", "--specs=", "-specs=")
|
||||
|
||||
|
||||
def _collect_sources(root: Path, exclude: Collection[str] = frozenset()) -> list[Path]:
|
||||
return sorted(
|
||||
p
|
||||
for p in root.rglob("*")
|
||||
if p.suffix in SOURCE_KIND_FOR_SUFFIX and p.name not in exclude
|
||||
)
|
||||
|
||||
|
||||
def _stat_sig(path: Path) -> str:
|
||||
"""Size and mtime cache-stamp signature for one input file.
|
||||
|
||||
@@ -883,41 +879,6 @@ def _generate_testing_flash_ld(
|
||||
)
|
||||
|
||||
|
||||
def _ninja_compile_edges(
|
||||
lines: list[str],
|
||||
sources: list[Path],
|
||||
root: Path,
|
||||
group: str,
|
||||
flags: str = "",
|
||||
cxx_override: tuple[str, str] | None = None,
|
||||
) -> list[str]:
|
||||
"""Emit compile edges for ``sources``; return the object paths.
|
||||
|
||||
``cxx_override`` is a (flags, implicit-dep) pair applied to C++ edges
|
||||
only, replacing ``flags`` (used for the precompiled header).
|
||||
"""
|
||||
objects = []
|
||||
for src in sources:
|
||||
rel = src.relative_to(root).as_posix()
|
||||
obj = f"obj/{group}/{rel}.o"
|
||||
escaped_obj = _e(obj)
|
||||
kind = SOURCE_KIND_FOR_SUFFIX[src.suffix]
|
||||
override = cxx_override if kind == "cxx" and cxx_override else None
|
||||
implicit = f" | {override[1]}" if override else ""
|
||||
lines.append(f"build {escaped_obj}: {kind} {_e(src)}{implicit}")
|
||||
edge_flags = override[0] if override else flags
|
||||
if edge_flags:
|
||||
lines.append(f" flags = {edge_flags}")
|
||||
# Escaped once here: the returned paths only ever appear in build
|
||||
# statements (archive/link inputs), which use ninja escaping.
|
||||
objects.append(escaped_obj)
|
||||
return objects
|
||||
|
||||
|
||||
def _common_parent(paths: list[Path]) -> Path:
|
||||
return Path(os.path.commonpath([str(p.parent) for p in paths]))
|
||||
|
||||
|
||||
class _FlagSets(NamedTuple):
|
||||
cflags: list[str]
|
||||
cxxflags: list[str]
|
||||
@@ -1104,55 +1065,15 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
+ ["stdc++-exc" if config.exceptions else "stdc++", "m", "c", "gcc"]
|
||||
)
|
||||
|
||||
build_tool = Path(__file__).parent / "build_tool.py"
|
||||
|
||||
# $in/$out stay unquoted: ninja escapes its built-in path variables
|
||||
# itself; only literal paths need _q().
|
||||
lines = [
|
||||
"# Auto-generated by ESPHome",
|
||||
"ninja_required_version = 1.5",
|
||||
f"cc = {_q(toolchain_tool(paths.toolchain, 'gcc'))}",
|
||||
f"cxx = {_q(toolchain_tool(paths.toolchain, 'g++'))}",
|
||||
# The NSIS launcher starts Python with a \\?\ extended-length path
|
||||
# that cmd.exe cannot spawn; same strip every other emitted binary
|
||||
# path gets
|
||||
f"python = {_q(strip_win_long_path_prefix(sys.executable))}",
|
||||
f"buildtool = {_q(build_tool)}",
|
||||
f"ccache = {_q(ccache) if ccache else ''}",
|
||||
"",
|
||||
# Rule names match SOURCE_KIND_FOR_SUFFIX values (c, cxx, asm, aspp)
|
||||
"rule c",
|
||||
" command = $ccache $cc -MMD -MF $out.d $cflags $flags -c $in -o $out",
|
||||
" depfile = $out.d",
|
||||
" deps = gcc",
|
||||
" description = CC $out",
|
||||
"rule cxx",
|
||||
" command = $ccache $cxx -MMD -MF $out.d $cxxflags $flags -c $in -o $out",
|
||||
" depfile = $out.d",
|
||||
" deps = gcc",
|
||||
" description = CXX $out",
|
||||
"rule aspp",
|
||||
" command = $ccache $cc -MMD -MF $out.d -x assembler-with-cpp $asflags $flags -c $in -o $out",
|
||||
" depfile = $out.d",
|
||||
" deps = gcc",
|
||||
" description = AS $out",
|
||||
# No $ccache: the .gch is compiled once per build dir and ccache
|
||||
# cannot cache it usefully (its bytes embed build-dir paths)
|
||||
"rule pch",
|
||||
" command = $cxx -MMD -MF $out.d -x c++-header $cxxflags $flags -c $in -o $out",
|
||||
" depfile = $out.d",
|
||||
" deps = gcc",
|
||||
" description = PCH $out",
|
||||
# Plain assembler, as SCons's ASCOM: no preprocessor, so no
|
||||
# depfile and no $flags (defines/includes) either
|
||||
"rule asm",
|
||||
" command = $ccache $cc -x assembler $asflags -c $in -o $out",
|
||||
" description = AS $out",
|
||||
"rule ar",
|
||||
f" command = $python $buildtool ar {_q(toolchain_tool(paths.toolchain, 'ar'))} $out $out.rsp",
|
||||
" rspfile = $out.rsp",
|
||||
" rspfile_content = $in_newline",
|
||||
" description = AR $out",
|
||||
*tool_lines(
|
||||
(toolchain_tool(paths.toolchain, "gcc"),),
|
||||
(toolchain_tool(paths.toolchain, "g++"),),
|
||||
ccache,
|
||||
),
|
||||
*compile_rule_lines(),
|
||||
*ar_rule_lines(toolchain_tool(paths.toolchain, "ar")),
|
||||
*pch_rule_lines(),
|
||||
"rule link",
|
||||
" command = $cxx -o $out $linkflags @$out.rsp $libdirflags -Wl,--start-group $archives $libflags -Wl,--end-group",
|
||||
" rspfile = $out.rsp",
|
||||
@@ -1182,16 +1103,15 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
core_exclude |= _CORE_EXCLUDE_WAVEFORM
|
||||
|
||||
archives = []
|
||||
direct_objs: list[str] = []
|
||||
# variant_dir existence was already enforced with the include dirs
|
||||
variant_sources = _collect_sources(variant_dir)
|
||||
variant_sources = collect_sources(variant_dir)
|
||||
if variant_sources:
|
||||
objs = _ninja_compile_edges(lines, variant_sources, variant_dir, "variant")
|
||||
objs = compile_edges(lines, variant_sources, variant_dir, "variant")
|
||||
lines.append(f"build libFrameworkArduinoVariant.a: ar {' '.join(objs)}")
|
||||
archives.append("libFrameworkArduinoVariant.a")
|
||||
|
||||
core_objs = _ninja_compile_edges(
|
||||
lines, _collect_sources(core_dir, core_exclude), core_dir, "core"
|
||||
core_objs = compile_edges(
|
||||
lines, collect_sources(core_dir, core_exclude), core_dir, "core"
|
||||
)
|
||||
if not core_objs:
|
||||
# An empty archive would link into a wall of undefined references
|
||||
@@ -1202,32 +1122,8 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
lines.append(f"build libFrameworkArduino.a: ar {' '.join(core_objs)}")
|
||||
archives.append("libFrameworkArduino.a")
|
||||
|
||||
for lib in libraries:
|
||||
if not lib.sources:
|
||||
# Header-only libraries are legitimate; the log makes an empty
|
||||
# srcFilter or broken tree traceable before link errors do.
|
||||
_LOGGER.debug(
|
||||
"Library %s has no source files; contributing includes only",
|
||||
lib.name,
|
||||
)
|
||||
continue
|
||||
lib_root = _common_parent(lib.sources)
|
||||
objs = _ninja_compile_edges(
|
||||
lines,
|
||||
lib.sources,
|
||||
lib_root,
|
||||
f"lib/{lib.name}",
|
||||
flags=" ".join(_shell_token(f) for f in lib.flags),
|
||||
)
|
||||
if not lib.lib_archive:
|
||||
# libArchive: false / dot_a_linkage=false: hand the objects to
|
||||
# the linker directly so unreferenced-but-required symbols
|
||||
# (exception handlers, weak overrides) survive
|
||||
direct_objs.extend(objs)
|
||||
continue
|
||||
archive = f"lib{lib.name}.a"
|
||||
lines.append(f"build {_e(archive)}: ar {' '.join(objs)}")
|
||||
archives.append(archive)
|
||||
lib_archives, direct_objs = library_edges(lines, libraries)
|
||||
archives += lib_archives
|
||||
|
||||
# One source of truth with the PlatformIO path: esp8266/__init__ pins
|
||||
# build_src_flags (the throw_stubs force-include); -include paths
|
||||
@@ -1348,9 +1244,9 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
mark_pch_emitted()
|
||||
else:
|
||||
pch_disabled_degraded()
|
||||
src_objs = _ninja_compile_edges(
|
||||
src_objs = compile_edges(
|
||||
lines,
|
||||
_collect_sources(src_dir),
|
||||
collect_sources(src_dir),
|
||||
src_dir,
|
||||
"src",
|
||||
flags="$srcflags",
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
"""Native ninja build generator for the host platform.
|
||||
|
||||
Emits ``build.ninja`` under ``.pioenvs/<name>/``: every source in the
|
||||
generated ``src/`` tree plus the resolved registry libraries compiles with
|
||||
the machine's compiler and links into ``program``, the name PlatformIO's
|
||||
native platform produced. Build flags route the way SCons's ``ParseFlags``
|
||||
did under PlatformIO: ``-D``/``-I``/``-std=``/``-W`` shapes reach the
|
||||
compile lines only, ``-l``/``-L``/``-Wl,`` the link line only, everything
|
||||
else both.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from esphome.build_helpers.ninja import escape as _e, quote_path as _q, shell_token
|
||||
from esphome.build_helpers.ninja_gen import (
|
||||
PATH_ARG_FLAGS,
|
||||
Flag,
|
||||
anchor_path_flag,
|
||||
ar_rule_lines,
|
||||
collect_sources,
|
||||
compile_edges,
|
||||
compile_rule_lines,
|
||||
library_edges,
|
||||
tool_lines,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.framework_helpers import get_project_cxx_compile_flags
|
||||
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||
from esphome.host.toolchain import PROGRAM_NAME, HostCompilers, find_tool, get_build_dir
|
||||
from esphome.platformio.library import lex_build_flags
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.arduino.library import ArduinoLibrary
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# The PlatformIO platform the host built under; registry manifests declare
|
||||
# compatibility against it, as lib_compat_mode=strict checked before
|
||||
PIO_PLATFORM = "native"
|
||||
# Namespaces the shared library download cache (pio_components/host/)
|
||||
LIBRARY_CACHE_KEY = "host"
|
||||
|
||||
# Flag shapes that only the compiler understands; dropped from the link line
|
||||
_COMPILE_ONLY_PREFIXES = ("-D", "-U", "-I", "-std=", "-W", *PATH_ARG_FLAGS)
|
||||
# Flag shapes that only the linker consumes; inert on a -c compile line
|
||||
_LINK_ONLY_PREFIXES = ("-l", "-L", "-Wl,")
|
||||
# Link-only flags whose argument is the next token
|
||||
_LINK_ONLY_ARG_FLAGS = ("-framework", "-Xlinker", "-z")
|
||||
|
||||
|
||||
def parse_flags(entries: Iterable[str], owner: str) -> list[Flag]:
|
||||
"""Lex build flag entries into flags, each with its argument.
|
||||
|
||||
Entries are a set, so their order is not the user's: a flag and the
|
||||
argument it takes as the next token must share one entry.
|
||||
"""
|
||||
flags: list[Flag] = []
|
||||
for entry in entries:
|
||||
it = iter(lex_build_flags(entry, owner))
|
||||
for tok in it:
|
||||
if tok not in PATH_ARG_FLAGS and tok not in _LINK_ONLY_ARG_FLAGS:
|
||||
flags.append((tok,))
|
||||
continue
|
||||
arg = next(it, None)
|
||||
# A path never starts with "-"; that is the next flag
|
||||
if arg is None or (tok in PATH_ARG_FLAGS and arg.startswith("-")):
|
||||
raise EsphomeError(
|
||||
f"{owner} build flags have '{tok}' with no argument; write "
|
||||
f"the flag and its argument as one entry"
|
||||
)
|
||||
flags.append((tok, arg))
|
||||
return flags
|
||||
|
||||
|
||||
def split_flags(flags: list[Flag]) -> tuple[list[Flag], list[Flag]]:
|
||||
"""Route build flags to the compile and link lines."""
|
||||
compile_flags: list[Flag] = []
|
||||
link_flags: list[Flag] = []
|
||||
for flag in flags:
|
||||
name = flag[0]
|
||||
if len(flag) > 1:
|
||||
(compile_flags if name in PATH_ARG_FLAGS else link_flags).append(flag)
|
||||
elif name.startswith(_LINK_ONLY_PREFIXES):
|
||||
# Checked before the compile prefixes: -Wl, would match -W
|
||||
link_flags.append(flag)
|
||||
elif name.startswith(_COMPILE_ONLY_PREFIXES):
|
||||
compile_flags.append(flag)
|
||||
else:
|
||||
# -g, -O, -f*, -m*, -pthread, --coverage: both lines, as SCons
|
||||
compile_flags.append(flag)
|
||||
link_flags.append(flag)
|
||||
return compile_flags, link_flags
|
||||
|
||||
|
||||
def _is_std(flag: Flag) -> bool:
|
||||
return flag[0].startswith("-std=")
|
||||
|
||||
|
||||
def _is_cxx_std(flag: Flag) -> bool:
|
||||
return _is_std(flag) and "++" in flag[0]
|
||||
|
||||
|
||||
def _anchored_flags(entries: Iterable[str], owner: str) -> list[Flag]:
|
||||
build_path = Path(CORE.build_path)
|
||||
return [
|
||||
anchor_path_flag(flag, build_path)
|
||||
for flag in parse_flags(sorted(entries), owner)
|
||||
]
|
||||
|
||||
|
||||
def _flag_lists() -> tuple[list[str], list[str], list[str]]:
|
||||
"""The C, C++, and link flag lists (raw tokens), build_unflags applied.
|
||||
|
||||
``cg.set_cpp_standard`` wins over any ``-std=`` in the build flags for
|
||||
C++ compiles, as PlatformIO's unflag of every other standard did; C
|
||||
compiles never see a C++ standard.
|
||||
"""
|
||||
# The funnel warns and drops empty glued arguments (-D "") itself
|
||||
compile_flags, link_flags = split_flags(
|
||||
_anchored_flags(CORE.build_flags, "esphome")
|
||||
)
|
||||
cflags = [f for f in compile_flags if not _is_cxx_std(f)]
|
||||
cxx_std = CORE.cpp_standard
|
||||
cxxflags = [f for f in compile_flags if not (cxx_std and _is_std(f))]
|
||||
if cxx_std:
|
||||
cxxflags.insert(0, (f"-std={cxx_std}",))
|
||||
cxxflags += [(tok,) for tok in get_project_cxx_compile_flags()]
|
||||
|
||||
# A flag is removed whole, with its argument, as PlatformIO did
|
||||
unflags = set(_anchored_flags(CORE.build_unflags, "esphome build_unflags"))
|
||||
# An unflag that hits nothing (a typo, or -DUSE_FOO against
|
||||
# -DUSE_FOO=1) must be visible, since the user believes the flag is
|
||||
# gone while it still drives the build
|
||||
if unmatched := sorted(unflags - set(cflags) - set(cxxflags) - set(link_flags)):
|
||||
_LOGGER.warning(
|
||||
"build_unflags entries matched no build flag: %s",
|
||||
", ".join(" ".join(flag) for flag in unmatched),
|
||||
)
|
||||
|
||||
def keep(flags: list[Flag]) -> list[str]:
|
||||
return [tok for flag in flags if flag not in unflags for tok in flag]
|
||||
|
||||
return keep(cflags), keep(cxxflags), keep(link_flags)
|
||||
|
||||
|
||||
def _resolve_host_libraries() -> list[ArduinoLibrary]:
|
||||
"""Every ``cg.add_library()`` entry, fetched from the registry.
|
||||
|
||||
The host has no framework, so nothing is bundled and no framework
|
||||
compatibility check applies; the platform check keeps the strict
|
||||
manifest gate PlatformIO's native platform enforced. Manifest-less
|
||||
libraries (a bare git checkout) build with PlatformIO's default
|
||||
layout, as they did under its native platform.
|
||||
"""
|
||||
if not CORE.platformio_libraries:
|
||||
return []
|
||||
from esphome.arduino.library import resolve_libraries
|
||||
|
||||
return resolve_libraries(
|
||||
None,
|
||||
pio_platform=PIO_PLATFORM,
|
||||
board_mcu="host",
|
||||
cache_key=LIBRARY_CACHE_KEY,
|
||||
framework=None,
|
||||
manifest_optional=True,
|
||||
)
|
||||
|
||||
|
||||
def _file_macro_maps(build_dir: Path) -> list[str]:
|
||||
"""Flags that keep ``__FILE__`` relative to the build path.
|
||||
|
||||
PlatformIO compiled ``src/x.cpp`` from the build path, and tools name
|
||||
things after that spelling (CodSpeed's benchmark ids). Here a source
|
||||
reaches the compiler by its absolute path, or relative to the build
|
||||
directory when ccache rewrites it.
|
||||
"""
|
||||
build_path = Path(CORE.build_path)
|
||||
prefixes = (build_path, Path(os.path.relpath(build_path, build_dir)))
|
||||
return [
|
||||
shell_token(f"-fmacro-prefix-map={prefix}{os.sep}=", force=True)
|
||||
for prefix in prefixes
|
||||
]
|
||||
|
||||
|
||||
def write_project(compilers: HostCompilers, ccache: str | None) -> bool:
|
||||
"""Write the ninja build for the current configuration.
|
||||
|
||||
``ccache`` is the caller's already-resolved binary (None when disabled).
|
||||
Returns True when ``build.ninja`` changed, so the caller can skip work
|
||||
derived purely from it (the compile database) on unchanged builds.
|
||||
"""
|
||||
build_dir = get_build_dir()
|
||||
mkdir_p(build_dir)
|
||||
src_dir = CORE.relative_src_path()
|
||||
if not src_dir.is_dir():
|
||||
# Generated project state, not install state: clean-all would not help
|
||||
raise EsphomeError(f"Generated source directory {src_dir} is missing")
|
||||
|
||||
cflags, cxxflags, link_flags = _flag_lists()
|
||||
libraries = _resolve_host_libraries()
|
||||
|
||||
include_dirs = [src_dir]
|
||||
for lib in libraries:
|
||||
include_dirs += lib.include_dirs
|
||||
includes = [f"-I{_q(d)}" for d in include_dirs]
|
||||
includes += _file_macro_maps(build_dir)
|
||||
|
||||
# SCons's link line: $LINKFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS, so
|
||||
# -L and -l trail the objects while every other link token leads
|
||||
lib_dirs = [Path(t[2:]) for t in link_flags if t.startswith("-L")]
|
||||
libs = [t for t in link_flags if t.startswith("-l")]
|
||||
linkflags = [shell_token(t) for t in link_flags if not t.startswith(("-L", "-l"))]
|
||||
for lib in libraries:
|
||||
lib_dirs += lib.link_dirs
|
||||
libs += [f"-l{name}" for name in lib.link_libs]
|
||||
linkflags += [shell_token(f) for f in lib.link_flags]
|
||||
|
||||
# PlatformIO's ASPPCOM passes only -D/-I user flags to assembly
|
||||
asflags = [t for t in cflags if t.startswith(("-D", "-I"))]
|
||||
|
||||
lines = [
|
||||
*tool_lines(compilers.cc, compilers.cxx, ccache),
|
||||
*compile_rule_lines(),
|
||||
"rule link",
|
||||
" command = $cxx -o $out $linkflags @$out.rsp $archives $libdirflags $libflags",
|
||||
" rspfile = $out.rsp",
|
||||
" rspfile_content = $in_newline",
|
||||
" description = LINK $out",
|
||||
]
|
||||
if any(lib.sources and lib.lib_archive for lib in libraries):
|
||||
# Resolved only when an archive is built, so a system without
|
||||
# binutils still links a library-free configuration
|
||||
lines += ar_rule_lines(find_tool("AR", ("ar",)))
|
||||
lines += [
|
||||
"",
|
||||
f"cflags = {' '.join([*map(shell_token, cflags), *includes])}",
|
||||
f"cxxflags = {' '.join([*map(shell_token, cxxflags), *includes])}",
|
||||
f"asflags = {' '.join([*map(shell_token, asflags), *includes])}",
|
||||
f"linkflags = {' '.join(linkflags)}",
|
||||
f"libdirflags = {' '.join(f'-L{_q(d)}' for d in lib_dirs)}",
|
||||
f"libflags = {' '.join(shell_token(lib) for lib in libs)}",
|
||||
"",
|
||||
]
|
||||
|
||||
archives, direct_objs = library_edges(lines, libraries)
|
||||
|
||||
src_objs = compile_edges(lines, collect_sources(src_dir), src_dir, "src")
|
||||
if not src_objs:
|
||||
raise EsphomeError(f"No source files found under {src_dir}")
|
||||
|
||||
# Archives are not topologically sorted; GNU ld needs the group to
|
||||
# resolve references between them. ld64 loads archives iteratively and
|
||||
# rejects the option, so macOS lists them bare.
|
||||
archive_tokens = [shell_token(a) for a in archives]
|
||||
if archive_tokens and sys.platform != "darwin":
|
||||
archive_tokens = ["-Wl,--start-group", *archive_tokens, "-Wl,--end-group"]
|
||||
lines.append(
|
||||
f"build {PROGRAM_NAME}: link {' '.join(src_objs + direct_objs)} | "
|
||||
f"{' '.join(_e(a) for a in archives)}"
|
||||
)
|
||||
lines.append(f" archives = {' '.join(archive_tokens)}")
|
||||
lines.append(f"default {PROGRAM_NAME}")
|
||||
lines.append("")
|
||||
|
||||
return write_file_if_changed(build_dir / "build.ninja", "\n".join(lines))
|
||||
@@ -73,6 +73,15 @@ def resolve_ccache_path() -> str | None:
|
||||
return ccache
|
||||
|
||||
|
||||
def resolve_absolute_ccache_path() -> str | None:
|
||||
"""``resolve_ccache_path`` for the ninja backends, which run their
|
||||
commands from the build directory, where a relative path is lost."""
|
||||
from esphome.build_helpers.ninja import absolute_tool
|
||||
|
||||
ccache = resolve_ccache_path()
|
||||
return absolute_tool(ccache) if ccache else None
|
||||
|
||||
|
||||
def ccache_defaults_env(cache_dir: Path) -> dict[str, str]:
|
||||
"""Default ``CCACHE_*`` values for a build subprocess (not os.environ).
|
||||
|
||||
@@ -89,11 +98,11 @@ def ccache_defaults_env(cache_dir: Path) -> dict[str, str]:
|
||||
"CORE.build_path must be set before constructing the build environment"
|
||||
)
|
||||
defaults = {
|
||||
"CCACHE_DIR": str(cache_dir),
|
||||
# ccache expands $VAR in its settings; $$ is a literal $
|
||||
"CCACHE_DIR": str(cache_dir).replace("$", "$$"),
|
||||
"CCACHE_NOHASHDIR": "true",
|
||||
"CCACHE_DEPEND": "1",
|
||||
# A user value wins via the filter below
|
||||
"CCACHE_BASEDIR": effective_ccache_basedir(),
|
||||
"CCACHE_BASEDIR": str(Path(CORE.build_path).resolve()).replace("$", "$$"),
|
||||
}
|
||||
return {k: v for k, v in defaults.items() if k not in os.environ}
|
||||
|
||||
@@ -108,3 +117,20 @@ def effective_ccache_basedir() -> str:
|
||||
return raw
|
||||
# Unset or degenerate ("", "/", relative): fall back to the build path
|
||||
return str(Path(CORE.build_path).resolve())
|
||||
|
||||
|
||||
def ccache_env(ccache: str | None, tools_cache: tuple[str, str]) -> dict[str, str]:
|
||||
"""The ccache settings for a build subprocess (not os.environ).
|
||||
|
||||
``ccache`` is the pre-resolved binary (resolve_ccache_path), or None when
|
||||
disabled; ``tools_cache`` is the backend's tools cache spec, which holds
|
||||
its ccache dir.
|
||||
"""
|
||||
if ccache is None:
|
||||
return {}
|
||||
from esphome.build_helpers.pch import ccache_pch_env
|
||||
from esphome.build_helpers.tools_cache import tools_cache_path
|
||||
|
||||
env = ccache_defaults_env(tools_cache_path(*tools_cache) / "ccache")
|
||||
env.update(ccache_pch_env())
|
||||
return env
|
||||
|
||||
@@ -7,16 +7,21 @@ platform component package or the backend itself.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Collection
|
||||
import importlib
|
||||
import logging
|
||||
from types import ModuleType
|
||||
|
||||
from esphome.const import Toolchain
|
||||
from esphome.core import CORE, EsphomeError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Native build backend per (target platform, toolchain)
|
||||
NATIVE_TOOLCHAIN_MODULES = {
|
||||
("esp32", Toolchain.ESP_IDF): "esphome.espidf.toolchain",
|
||||
("esp8266", Toolchain.ARDUINO): "esphome.arduino8266.toolchain",
|
||||
("host", Toolchain.HOST): "esphome.host.toolchain",
|
||||
}
|
||||
|
||||
|
||||
@@ -32,3 +37,38 @@ def native_backend() -> ModuleType | None:
|
||||
f"module for platform {CORE.target_platform}"
|
||||
)
|
||||
return importlib.import_module(module_path)
|
||||
|
||||
|
||||
# Binutils and the linked image for memory analysis, for toolchains that build
|
||||
# without PlatformIO but have no native build backend (which supplies them)
|
||||
ANALYSIS_TOOLCHAIN_MODULES = {
|
||||
("nrf52", Toolchain.SDK_NRF): "esphome.components.nrf52.toolchain",
|
||||
}
|
||||
|
||||
|
||||
def analysis_backend() -> ModuleType | None:
|
||||
"""The module giving objdump, readelf and the ELF of a non-PlatformIO build.
|
||||
|
||||
None means PlatformIO's idedata supplies them (or nothing can).
|
||||
"""
|
||||
if (native := native_backend()) is not None:
|
||||
return native
|
||||
module_path = ANALYSIS_TOOLCHAIN_MODULES.get((CORE.target_platform, CORE.toolchain))
|
||||
return importlib.import_module(module_path) if module_path else None
|
||||
|
||||
|
||||
def warn_ignored_platformio_options(consumed: Collection[str]) -> None:
|
||||
"""Warn for component-added platformio options a native build drops.
|
||||
|
||||
User-supplied keys were already routed or warned about by
|
||||
``core/config.py``; what survives into ``CORE.platformio_options`` came
|
||||
from ``cg.add_platformio_option`` calls in components.
|
||||
"""
|
||||
for key in sorted(CORE.platformio_options or {}):
|
||||
if key not in consumed:
|
||||
_LOGGER.warning(
|
||||
"platformio_options->%s is ignored when building with the "
|
||||
"native '%s' toolchain",
|
||||
key,
|
||||
CORE.toolchain.value,
|
||||
)
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.framework_helpers import strip_win_long_path_prefix, tool_version_runs
|
||||
from esphome.helpers import write_file_if_changed
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -23,11 +26,26 @@ def _ninja_runs(binary: str) -> bool:
|
||||
)
|
||||
|
||||
|
||||
# Compile rule names the generators emit; ninja's compdb tool is asked for
|
||||
# exactly these, so a renamed rule fails the build instead of stranding idedata
|
||||
COMPILE_RULES = ("c", "cxx", "aspp", "asm")
|
||||
|
||||
|
||||
def absolute_tool(tool: str | Path) -> str:
|
||||
"""A tool path that still resolves from the build directory.
|
||||
|
||||
``shutil.which`` returns a relative path for a relative PATH entry, and
|
||||
ninja runs the commands from ``.pioenvs/<name>``. Symlinks are kept:
|
||||
ccache's compiler links depend on the name they are called by.
|
||||
"""
|
||||
return strip_win_long_path_prefix(str(Path(tool).absolute()))
|
||||
|
||||
|
||||
def find_ninja() -> Path:
|
||||
"""Locate the ninja binary: a runnable PATH hit first, else the ninja
|
||||
PyPI wheel."""
|
||||
if binary := shutil.which("ninja"):
|
||||
binary = strip_win_long_path_prefix(binary)
|
||||
binary = absolute_tool(binary)
|
||||
if _ninja_runs(binary):
|
||||
return Path(binary)
|
||||
import_error: ImportError | None = None
|
||||
@@ -49,8 +67,18 @@ def find_ninja() -> Path:
|
||||
|
||||
|
||||
def escape(value: Path | str) -> str:
|
||||
"""Escape a path or token for a ninja file."""
|
||||
return str(value).replace("$", "$$").replace(":", "$:").replace(" ", "$ ")
|
||||
"""Escape a path or token for a ninja file.
|
||||
|
||||
ninja has no escape for ``|`` or a line break in a path, so those fail
|
||||
here by name instead of producing a build file ninja misreads.
|
||||
"""
|
||||
text = str(value)
|
||||
if bad := next((c for c in "|\r\n" if c in text), None):
|
||||
raise EsphomeError(
|
||||
f"Path {text!r} contains {bad!r}, which a ninja build file cannot "
|
||||
"express; rename the file or directory"
|
||||
)
|
||||
return text.replace("$", "$$").replace(":", "$:").replace(" ", "$ ")
|
||||
|
||||
|
||||
def quote_arg(tok: str) -> str:
|
||||
@@ -90,3 +118,65 @@ def shell_token(tok: str, force: bool = False) -> str:
|
||||
def quote_path(value: Path | str) -> str:
|
||||
"""Force-quote a path for the ninja command line (shell/CreateProcess)."""
|
||||
return shell_token(str(value), force=True)
|
||||
|
||||
|
||||
def refresh_compile_commands(
|
||||
ninja_path: Path, build_dir: Path, env: dict[str, str], ninja_changed: bool
|
||||
) -> None:
|
||||
"""Regenerate the compile DB (a pure function of build.ninja) when stale.
|
||||
|
||||
Freshness rides a stamp: the DB itself is written through
|
||||
write_file_if_changed (its mtime feeds the idedata cache), so a
|
||||
regeneration with identical content would stay "stale" forever. An
|
||||
interrupted previous run may have rewritten the manifest without
|
||||
regenerating the DB, hence the mtime comparison.
|
||||
"""
|
||||
compdb = build_dir / "compile_commands.json"
|
||||
compdb_stamp = build_dir / ".compile_commands.stamp"
|
||||
ninja_file = build_dir / "build.ninja"
|
||||
if (
|
||||
ninja_changed
|
||||
or not compdb.is_file()
|
||||
or not compdb_stamp.is_file()
|
||||
or compdb_stamp.stat().st_mtime < ninja_file.stat().st_mtime
|
||||
):
|
||||
write_compile_commands(ninja_path, build_dir, env)
|
||||
compdb_stamp.touch()
|
||||
|
||||
|
||||
def write_compile_commands(
|
||||
ninja_path: Path, build_dir: Path, env: dict[str, str]
|
||||
) -> None:
|
||||
compdb = build_dir / "compile_commands.json"
|
||||
result = subprocess.run(
|
||||
[str(ninja_path), "-C", str(build_dir), "-t", "compdb", *COMPILE_RULES],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
close_fds=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
# Drop any stale database so consumers (IDE integration, clang-tidy,
|
||||
# the memory analyzer) can't silently read outdated data
|
||||
compdb.unlink(missing_ok=True)
|
||||
raise EsphomeError(f"Could not generate compile_commands.json: {result.stderr}")
|
||||
try:
|
||||
entries = json.loads(result.stdout)
|
||||
except ValueError as err:
|
||||
compdb.unlink(missing_ok=True)
|
||||
raise EsphomeError(
|
||||
f"ninja produced an unparsable compile database: {err} "
|
||||
f"(output starts {result.stdout[:120]!r})"
|
||||
) from err
|
||||
if not entries:
|
||||
# compdb exits 0 with [] for unknown rule names; a renamed compile
|
||||
# rule must fail the build, not silently strand every consumer
|
||||
compdb.unlink(missing_ok=True)
|
||||
raise EsphomeError(
|
||||
"ninja produced an empty compile database; the generator's rule "
|
||||
"names no longer match"
|
||||
)
|
||||
# write_file_if_changed keeps the mtime stable on no-op builds so the
|
||||
# idedata cache stays valid
|
||||
write_file_if_changed(compdb, result.stdout)
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
"""The parts of a ``build.ninja`` every native backend emits the same way.
|
||||
|
||||
Rule names match ``SOURCE_KIND_FOR_SUFFIX`` values (c, cxx, asm, aspp) and
|
||||
``ninja.COMPILE_RULES``, which the compile database is asked for.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Collection, Sequence
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from esphome.build_helpers.ninja import (
|
||||
escape as _e,
|
||||
quote_path as _q,
|
||||
shell_token as _shell_token,
|
||||
)
|
||||
from esphome.framework_helpers import strip_win_long_path_prefix
|
||||
from esphome.platformio.library import SOURCE_KIND_FOR_SUFFIX
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.arduino.library import ArduinoLibrary
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_BUILD_TOOL = Path(__file__).parent.parent / "build_gen" / "build_tool.py"
|
||||
|
||||
|
||||
def collect_sources(root: Path, exclude: Collection[str] = frozenset()) -> list[Path]:
|
||||
return sorted(
|
||||
p
|
||||
for p in root.rglob("*")
|
||||
if p.suffix in SOURCE_KIND_FOR_SUFFIX and p.name not in exclude
|
||||
)
|
||||
|
||||
|
||||
def common_parent(paths: list[Path]) -> Path:
|
||||
return Path(os.path.commonpath([str(p.parent) for p in paths]))
|
||||
|
||||
|
||||
def _command(words: Sequence[Path | str]) -> str:
|
||||
program, *args = words
|
||||
return " ".join([_q(program), *(_shell_token(str(a)) for a in args)])
|
||||
|
||||
|
||||
def tool_lines(
|
||||
cc: Sequence[Path | str], cxx: Sequence[Path | str], ccache: str | None
|
||||
) -> list[str]:
|
||||
"""The file header: the compilers and the helper tools as variables.
|
||||
|
||||
A compiler is its program followed by any arguments it always takes.
|
||||
"""
|
||||
return [
|
||||
"# Auto-generated by ESPHome",
|
||||
"ninja_required_version = 1.5",
|
||||
f"cc = {_command(cc)}",
|
||||
f"cxx = {_command(cxx)}",
|
||||
# The NSIS launcher starts Python with a \\?\ extended-length path
|
||||
# that cmd.exe cannot spawn; same strip every other emitted binary
|
||||
# path gets
|
||||
f"python = {_q(strip_win_long_path_prefix(sys.executable))}",
|
||||
f"buildtool = {_q(_BUILD_TOOL)}",
|
||||
f"ccache = {_q(ccache) if ccache else ''}",
|
||||
"",
|
||||
]
|
||||
|
||||
|
||||
def compile_rule_lines() -> list[str]:
|
||||
"""The compile rules; ``$own_includes`` is empty unless an edge sets it."""
|
||||
return [
|
||||
"rule c",
|
||||
" command = $ccache $cc -MMD -MF $out.d $own_includes $cflags $flags -c $in -o $out",
|
||||
" depfile = $out.d",
|
||||
" deps = gcc",
|
||||
" description = CC $out",
|
||||
"rule cxx",
|
||||
" command = $ccache $cxx -MMD -MF $out.d $own_includes $cxxflags $flags -c $in -o $out",
|
||||
" depfile = $out.d",
|
||||
" deps = gcc",
|
||||
" description = CXX $out",
|
||||
"rule aspp",
|
||||
" command = $ccache $cc -MMD -MF $out.d -x assembler-with-cpp $own_includes $asflags $flags -c $in -o $out",
|
||||
" depfile = $out.d",
|
||||
" deps = gcc",
|
||||
" description = AS $out",
|
||||
# Plain assembler, as SCons's ASCOM: no preprocessor, so no
|
||||
# depfile and no $flags (defines/includes) either
|
||||
"rule asm",
|
||||
" command = $ccache $cc -x assembler $asflags -c $in -o $out",
|
||||
" description = AS $out",
|
||||
]
|
||||
|
||||
|
||||
def pch_rule_lines() -> list[str]:
|
||||
"""The precompiled header rule, for a generator that emits one."""
|
||||
return [
|
||||
# No $ccache: the .gch is compiled once per build dir and ccache
|
||||
# cannot cache it usefully (its bytes embed build-dir paths)
|
||||
"rule pch",
|
||||
" command = $cxx -MMD -MF $out.d -x c++-header $cxxflags $flags -c $in -o $out",
|
||||
" depfile = $out.d",
|
||||
" deps = gcc",
|
||||
" description = PCH $out",
|
||||
]
|
||||
|
||||
|
||||
def ar_rule_lines(ar: Path | str) -> list[str]:
|
||||
return [
|
||||
"rule ar",
|
||||
f" command = $python $buildtool ar {_q(ar)} $out $out.rsp",
|
||||
" rspfile = $out.rsp",
|
||||
" rspfile_content = $in_newline",
|
||||
" description = AR $out",
|
||||
]
|
||||
|
||||
|
||||
def compile_edges(
|
||||
lines: list[str],
|
||||
sources: list[Path],
|
||||
root: Path,
|
||||
group: str,
|
||||
flags: str = "",
|
||||
own_includes: str = "",
|
||||
cxx_override: tuple[str, str] | None = None,
|
||||
) -> list[str]:
|
||||
"""Emit compile edges for ``sources``; return the object paths.
|
||||
|
||||
``cxx_override`` is a (flags, implicit-dep) pair applied to C++ edges
|
||||
only, replacing ``flags`` (used for the precompiled header).
|
||||
"""
|
||||
objects = []
|
||||
for src in sources:
|
||||
rel = src.relative_to(root).as_posix()
|
||||
obj = f"obj/{group}/{rel}.o"
|
||||
escaped_obj = _e(obj)
|
||||
kind = SOURCE_KIND_FOR_SUFFIX[src.suffix]
|
||||
override = cxx_override if kind == "cxx" else None
|
||||
implicit = f" | {override[1]}" if override else ""
|
||||
lines.append(f"build {escaped_obj}: {kind} {_e(src)}{implicit}")
|
||||
if own_includes:
|
||||
lines.append(f" own_includes = {own_includes}")
|
||||
if edge_flags := override[0] if override else flags:
|
||||
lines.append(f" flags = {edge_flags}")
|
||||
# Escaped once here: the returned paths only ever appear in build
|
||||
# statements (archive/link inputs), which use ninja escaping
|
||||
objects.append(escaped_obj)
|
||||
return objects
|
||||
|
||||
|
||||
def library_edges(
|
||||
lines: list[str], libraries: list[ArduinoLibrary]
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""Emit every library's compile and archive edges.
|
||||
|
||||
Returns the archive names and the objects that link directly. A
|
||||
library's own include dirs lead its compile lines, as PlatformIO searched
|
||||
them first: the include path is one global list, so another library's
|
||||
header of the same name would shadow them.
|
||||
"""
|
||||
archives: list[str] = []
|
||||
direct_objs: list[str] = []
|
||||
for lib in libraries:
|
||||
if not lib.sources:
|
||||
# Header-only libraries are legitimate; the log makes an empty
|
||||
# srcFilter or broken tree traceable before link errors do
|
||||
_LOGGER.debug(
|
||||
"Library %s has no source files; contributing includes only",
|
||||
lib.name,
|
||||
)
|
||||
continue
|
||||
objs = compile_edges(
|
||||
lines,
|
||||
lib.sources,
|
||||
common_parent(lib.sources),
|
||||
f"lib/{lib.name}",
|
||||
flags=" ".join(_shell_token(f) for f in lib.flags),
|
||||
own_includes=" ".join(f"-I{_q(d)}" for d in lib.include_dirs),
|
||||
)
|
||||
if not lib.lib_archive:
|
||||
# libArchive: false / dot_a_linkage=false: hand the objects to
|
||||
# the linker directly so unreferenced-but-required symbols
|
||||
# (exception handlers, weak overrides) survive
|
||||
direct_objs.extend(objs)
|
||||
continue
|
||||
archive = f"lib{lib.name}.a"
|
||||
lines.append(f"build {_e(archive)}: ar {' '.join(objs)}")
|
||||
archives.append(archive)
|
||||
return archives, direct_objs
|
||||
|
||||
|
||||
# One build flag: the flag, plus its argument when that is a separate token
|
||||
Flag = tuple[str, ...]
|
||||
|
||||
# Flags whose path operand is the next token; gcc also takes it glued on
|
||||
PATH_ARG_FLAGS = ("-include", "-imacros", "-isystem", "-iquote", "-idirafter")
|
||||
# Flags whose path operand is glued on
|
||||
PATH_PREFIXES = ("-I", "-L", *PATH_ARG_FLAGS)
|
||||
|
||||
|
||||
def _anchor(path: str, base: Path) -> str:
|
||||
if not path or Path(path).is_absolute():
|
||||
return path
|
||||
return str(base / path)
|
||||
|
||||
|
||||
def anchor_path_flag(flag: Flag, base: Path) -> Flag:
|
||||
"""Anchor a flag's relative path operand at ``base``.
|
||||
|
||||
PlatformIO ran the compiler from the build path; ninja runs it from
|
||||
``.pioenvs/<name>``, where a relative operand would point elsewhere.
|
||||
"""
|
||||
name, *args = flag
|
||||
if args:
|
||||
if name in PATH_ARG_FLAGS:
|
||||
return (name, _anchor(args[0], base))
|
||||
return flag
|
||||
for prefix in PATH_PREFIXES:
|
||||
if name.startswith(prefix):
|
||||
return (prefix + _anchor(name[len(prefix) :], base),)
|
||||
return flag
|
||||
@@ -33,4 +33,11 @@ def tools_cache_path(env_var: str, subdir: str) -> Path:
|
||||
IDF_TOOLS_CACHE = ("ESPHOME_ESP_IDF_PREFIX", "idf")
|
||||
SDK_NRF_TOOLS_CACHE = ("ESPHOME_SDK_NRF_PREFIX", "sdk-nrf")
|
||||
ARDUINO8266_TOOLS_CACHE = ("ESPHOME_ARDUINO8266_PREFIX", "arduino8266")
|
||||
TOOLS_CACHE_SPECS = (IDF_TOOLS_CACHE, SDK_NRF_TOOLS_CACHE, ARDUINO8266_TOOLS_CACHE)
|
||||
# The host backend installs nothing; the entry only holds its ccache dir
|
||||
HOST_TOOLS_CACHE = ("ESPHOME_HOST_PREFIX", "host")
|
||||
TOOLS_CACHE_SPECS = (
|
||||
IDF_TOOLS_CACHE,
|
||||
SDK_NRF_TOOLS_CACHE,
|
||||
ARDUINO8266_TOOLS_CACHE,
|
||||
HOST_TOOLS_CACHE,
|
||||
)
|
||||
|
||||
@@ -42,6 +42,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
||||
set_cpp_standard,
|
||||
statement,
|
||||
static_const_array,
|
||||
static_function,
|
||||
templatable,
|
||||
variable,
|
||||
with_local_variable,
|
||||
|
||||
@@ -85,20 +85,11 @@ async def wave_base_to_code(var: MockObj, config: ConfigType) -> None:
|
||||
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if config_humidity := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(config_humidity)
|
||||
cg.add(var.set_humidity(sens))
|
||||
if config_temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(config_temperature)
|
||||
cg.add(var.set_temperature(sens))
|
||||
if config_pressure := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(config_pressure)
|
||||
cg.add(var.set_pressure(sens))
|
||||
if config_tvoc := config.get(CONF_TVOC):
|
||||
sens = await sensor.new_sensor(config_tvoc)
|
||||
cg.add(var.set_tvoc(sens))
|
||||
if config_battery_voltage := config.get(CONF_BATTERY_VOLTAGE):
|
||||
sens = await sensor.new_sensor(config_battery_voltage)
|
||||
cg.add(var.set_battery_voltage(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
await sensors(CONF_PRESSURE, var.set_pressure)
|
||||
await sensors(CONF_TVOC, var.set_tvoc)
|
||||
await sensors(CONF_BATTERY_VOLTAGE, var.set_battery_voltage)
|
||||
if config_battery_update_interval := config.get(CONF_BATTERY_UPDATE_INTERVAL):
|
||||
cg.add(var.set_battery_update_interval(config_battery_update_interval))
|
||||
|
||||
@@ -87,16 +87,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await airthings_wave_base.wave_base_to_code(var, config)
|
||||
|
||||
if config_radon := config.get(CONF_RADON):
|
||||
sens = await sensor.new_sensor(config_radon)
|
||||
cg.add(var.set_radon(sens))
|
||||
if config_radon_long_term := config.get(CONF_RADON_LONG_TERM):
|
||||
sens = await sensor.new_sensor(config_radon_long_term)
|
||||
cg.add(var.set_radon_long_term(sens))
|
||||
if config_co2 := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(config_co2)
|
||||
cg.add(var.set_co2(sens))
|
||||
if config_illuminance := config.get(CONF_ILLUMINANCE):
|
||||
sens = await sensor.new_sensor(config_illuminance)
|
||||
cg.add(var.set_illuminance(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_RADON, var.set_radon)
|
||||
await sensors(CONF_RADON_LONG_TERM, var.set_radon_long_term)
|
||||
await sensors(CONF_CO2, var.set_co2)
|
||||
await sensors(CONF_ILLUMINANCE, var.set_illuminance)
|
||||
cg.add(var.set_device_type(config[CONF_DEVICE_TYPE]))
|
||||
|
||||
@@ -85,18 +85,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature(sens))
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity(sens))
|
||||
if battery_level_config := config.get(CONF_BATTERY_LEVEL):
|
||||
sens = await sensor.new_sensor(battery_level_config)
|
||||
cg.add(var.set_battery_level(sens))
|
||||
if battery_voltage_config := config.get(CONF_BATTERY_VOLTAGE):
|
||||
sens = await sensor.new_sensor(battery_voltage_config)
|
||||
cg.add(var.set_battery_voltage(sens))
|
||||
if signal_strength_config := config.get(CONF_SIGNAL_STRENGTH):
|
||||
sens = await sensor.new_sensor(signal_strength_config)
|
||||
cg.add(var.set_signal_strength(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity)
|
||||
await sensors(CONF_BATTERY_LEVEL, var.set_battery_level)
|
||||
await sensors(CONF_BATTERY_VOLTAGE, var.set_battery_voltage)
|
||||
await sensors(CONF_SIGNAL_STRENGTH, var.set_signal_strength)
|
||||
|
||||
@@ -129,30 +129,15 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
if voltage_config := config.get(CONF_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if current_config := config.get(CONF_CURRENT):
|
||||
sens = await sensor.new_sensor(current_config)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if power_config := config.get(CONF_POWER):
|
||||
sens = await sensor.new_sensor(power_config)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
if reactive_power_config := config.get(CONF_REACTIVE_POWER):
|
||||
sens = await sensor.new_sensor(reactive_power_config)
|
||||
cg.add(var.set_reactive_power_sensor(sens))
|
||||
if power_factor_config := config.get(CONF_POWER_FACTOR):
|
||||
sens = await sensor.new_sensor(power_factor_config)
|
||||
cg.add(var.set_power_factor_sensor(sens))
|
||||
if forward_active_energy_config := config.get(CONF_FORWARD_ACTIVE_ENERGY):
|
||||
sens = await sensor.new_sensor(forward_active_energy_config)
|
||||
cg.add(var.set_forward_active_energy_sensor(sens))
|
||||
if reverse_active_energy_config := config.get(CONF_REVERSE_ACTIVE_ENERGY):
|
||||
sens = await sensor.new_sensor(reverse_active_energy_config)
|
||||
cg.add(var.set_reverse_active_energy_sensor(sens))
|
||||
if frequency_config := config.get(CONF_FREQUENCY):
|
||||
sens = await sensor.new_sensor(frequency_config)
|
||||
cg.add(var.set_freq_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
await sensors(CONF_REACTIVE_POWER, var.set_reactive_power_sensor)
|
||||
await sensors(CONF_POWER_FACTOR, var.set_power_factor_sensor)
|
||||
await sensors(CONF_FORWARD_ACTIVE_ENERGY, var.set_forward_active_energy_sensor)
|
||||
await sensors(CONF_REVERSE_ACTIVE_ENERGY, var.set_reverse_active_energy_sensor)
|
||||
await sensors(CONF_FREQUENCY, var.set_freq_sensor)
|
||||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||
cg.add(var.set_meter_constant(config[CONF_METER_CONSTANT]))
|
||||
cg.add(var.set_pl_const(config[CONF_PL_CONST]))
|
||||
|
||||
@@ -61,14 +61,16 @@ from esphome.const import (
|
||||
DEVICE_CLASS_VIBRATION,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -632,6 +634,13 @@ async def new_binary_sensor(config, *args):
|
||||
return var
|
||||
|
||||
|
||||
def sub_binary_sensors(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_binary_sensor."""
|
||||
return SubEntities(new_binary_sensor, config, parent)
|
||||
|
||||
|
||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(BinarySensor),
|
||||
|
||||
@@ -150,15 +150,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
if frequency_config := config.get(CONF_FREQUENCY):
|
||||
sens = await sensor.new_sensor(frequency_config)
|
||||
cg.add(var.set_frequency_sensor(sens))
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
if voltage_config := config.get(CONF_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_FREQUENCY, var.set_frequency_sensor)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
|
||||
for i in range(6):
|
||||
if channel_config := config.get(f"{CONF_CHANNEL}_{i + 1}"):
|
||||
@@ -172,10 +167,5 @@ async def to_code(config: ConfigType) -> None:
|
||||
sens = await sensor.new_sensor(energy_config)
|
||||
cg.add(getattr(var, f"set_energy_{i + 1}_sensor")(sens))
|
||||
|
||||
if total_power_config := config.get(CONF_TOTAL_POWER):
|
||||
sens = await sensor.new_sensor(total_power_config)
|
||||
cg.add(var.set_total_power_sensor(sens))
|
||||
|
||||
if total_energy_config := config.get(CONF_TOTAL_ENERGY):
|
||||
sens = await sensor.new_sensor(total_energy_config)
|
||||
cg.add(var.set_total_energy_sensor(sens))
|
||||
await sensors(CONF_TOTAL_POWER, var.set_total_power_sensor)
|
||||
await sensors(CONF_TOTAL_ENERGY, var.set_total_energy_sensor)
|
||||
|
||||
@@ -94,27 +94,12 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if voltage_config := config.get(CONF_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if current_1_config := config.get(CONF_CURRENT_1):
|
||||
sens = await sensor.new_sensor(current_1_config)
|
||||
cg.add(var.set_current_sensor_1(sens))
|
||||
if current_2_config := config.get(CONF_CURRENT_2):
|
||||
sens = await sensor.new_sensor(current_2_config)
|
||||
cg.add(var.set_current_sensor_2(sens))
|
||||
if active_power_1_config := config.get(CONF_ACTIVE_POWER_1):
|
||||
sens = await sensor.new_sensor(active_power_1_config)
|
||||
cg.add(var.set_power_sensor_1(sens))
|
||||
if active_power_2_config := config.get(CONF_ACTIVE_POWER_2):
|
||||
sens = await sensor.new_sensor(active_power_2_config)
|
||||
cg.add(var.set_power_sensor_2(sens))
|
||||
if energy_1_config := config.get(CONF_ENERGY_1):
|
||||
sens = await sensor.new_sensor(energy_1_config)
|
||||
cg.add(var.set_energy_sensor_1(sens))
|
||||
if energy_2_config := config.get(CONF_ENERGY_2):
|
||||
sens = await sensor.new_sensor(energy_2_config)
|
||||
cg.add(var.set_energy_sensor_2(sens))
|
||||
if energy_total_config := config.get(CONF_ENERGY_TOTAL):
|
||||
sens = await sensor.new_sensor(energy_total_config)
|
||||
cg.add(var.set_energy_sensor_sum(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_CURRENT_1, var.set_current_sensor_1)
|
||||
await sensors(CONF_CURRENT_2, var.set_current_sensor_2)
|
||||
await sensors(CONF_ACTIVE_POWER_1, var.set_power_sensor_1)
|
||||
await sensors(CONF_ACTIVE_POWER_2, var.set_power_sensor_2)
|
||||
await sensors(CONF_ENERGY_1, var.set_energy_sensor_1)
|
||||
await sensors(CONF_ENERGY_2, var.set_energy_sensor_2)
|
||||
await sensors(CONF_ENERGY_TOTAL, var.set_energy_sensor_sum)
|
||||
|
||||
@@ -231,24 +231,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if voltage_config := config.get(CONF_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if current_config := config.get(CONF_CURRENT):
|
||||
sens = await sensor.new_sensor(current_config)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if power_config := config.get(CONF_POWER):
|
||||
sens = await sensor.new_sensor(power_config)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
if energy_config := config.get(CONF_ENERGY):
|
||||
sens = await sensor.new_sensor(energy_config)
|
||||
cg.add(var.set_energy_sensor(sens))
|
||||
if internal_temperature_config := config.get(CONF_INTERNAL_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(internal_temperature_config)
|
||||
cg.add(var.set_internal_temperature_sensor(sens))
|
||||
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(external_temperature_config)
|
||||
cg.add(var.set_external_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
await sensors(CONF_ENERGY, var.set_energy_sensor)
|
||||
await sensors(CONF_INTERNAL_TEMPERATURE, var.set_internal_temperature_sensor)
|
||||
await sensors(CONF_EXTERNAL_TEMPERATURE, var.set_external_temperature_sensor)
|
||||
|
||||
# enable legacy mode
|
||||
cg.add(var.set_legacy_mode(config.get(CONF_LEGACY_MODE)))
|
||||
|
||||
@@ -101,21 +101,12 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if voltage_config := config.get(CONF_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if current_config := config.get(CONF_CURRENT):
|
||||
sens = await sensor.new_sensor(current_config)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if power_config := config.get(CONF_POWER):
|
||||
sens = await sensor.new_sensor(power_config)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
if energy_config := config.get(CONF_ENERGY):
|
||||
sens = await sensor.new_sensor(energy_config)
|
||||
cg.add(var.set_energy_sensor(sens))
|
||||
if frequency_config := config.get(CONF_FREQUENCY):
|
||||
sens = await sensor.new_sensor(frequency_config)
|
||||
cg.add(var.set_frequency_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
await sensors(CONF_ENERGY, var.set_energy_sensor)
|
||||
await sensors(CONF_FREQUENCY, var.set_frequency_sensor)
|
||||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||
cg.add(var.set_address(config[CONF_ADDRESS]))
|
||||
cg.add(var.set_reset(config[CONF_RESET]))
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.components.uart import (
|
||||
uart_ns,
|
||||
)
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
from esphome.components.zephyr_ble_server import request_ble_l2cap_mtu
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DEBUG,
|
||||
@@ -61,6 +62,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
zephyr_add_prj_conf("BT_NUS", True)
|
||||
request_ble_l2cap_mtu(247) # DLE sweet spot: one NUS frame per LL PDU
|
||||
expose_log = config[CONF_TYPE] == CONF_LOGS
|
||||
cg.add(var.set_expose_log(expose_log))
|
||||
if expose_log:
|
||||
|
||||
@@ -90,20 +90,14 @@ async def to_code_base(config: ConfigType) -> MockObj:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING]))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
if await sensors(CONF_TEMPERATURE, var.set_temperature_sensor):
|
||||
oversampling = config[CONF_TEMPERATURE][CONF_OVERSAMPLING]
|
||||
cg.add(var.set_temperature_oversampling(oversampling))
|
||||
if await sensors(CONF_PRESSURE, var.set_pressure_sensor):
|
||||
cg.add(var.set_pressure_oversampling(config[CONF_PRESSURE][CONF_OVERSAMPLING]))
|
||||
if await sensors(CONF_HUMIDITY, var.set_humidity_sensor):
|
||||
cg.add(var.set_humidity_oversampling(config[CONF_HUMIDITY][CONF_OVERSAMPLING]))
|
||||
|
||||
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))
|
||||
|
||||
|
||||
@@ -72,18 +72,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await setup_bthome_mithermometer(var, config)
|
||||
|
||||
if temp_sens := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temp_sens)
|
||||
cg.add(var.set_temperature(sens))
|
||||
if humi_sens := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humi_sens)
|
||||
cg.add(var.set_humidity(sens))
|
||||
if batl_sens := config.get(CONF_BATTERY_LEVEL):
|
||||
sens = await sensor.new_sensor(batl_sens)
|
||||
cg.add(var.set_battery_level(sens))
|
||||
if batv_sens := config.get(CONF_BATTERY_VOLTAGE):
|
||||
sens = await sensor.new_sensor(batv_sens)
|
||||
cg.add(var.set_battery_voltage(sens))
|
||||
if sgnl_sens := config.get(CONF_SIGNAL_STRENGTH):
|
||||
sens = await sensor.new_sensor(sgnl_sens)
|
||||
cg.add(var.set_signal_strength(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity)
|
||||
await sensors(CONF_BATTERY_LEVEL, var.set_battery_level)
|
||||
await sensors(CONF_BATTERY_VOLTAGE, var.set_battery_voltage)
|
||||
await sensors(CONF_SIGNAL_STRENGTH, var.set_signal_strength)
|
||||
|
||||
@@ -16,8 +16,9 @@ from esphome.const import (
|
||||
DEVICE_CLASS_RESTART,
|
||||
DEVICE_CLASS_UPDATE,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
@@ -114,6 +115,13 @@ async def new_button(config: ConfigType, *args: SafeExpType) -> MockObj:
|
||||
return var
|
||||
|
||||
|
||||
def sub_buttons(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_button."""
|
||||
return SubEntities(new_button, config, parent)
|
||||
|
||||
|
||||
BUTTON_PRESS_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Button),
|
||||
|
||||
@@ -15,6 +15,7 @@ CONF_CLIMATE_ID = "climate_id"
|
||||
CONF_CO2_EQUIVALENT = "co2_equivalent"
|
||||
CONF_COLOR_DEPTH = "color_depth"
|
||||
CONF_COLUMNS = "columns"
|
||||
CONF_CONVERSION_RATE = "conversion_rate"
|
||||
CONF_CRC_ENABLE = "crc_enable"
|
||||
CONF_DATA_BITS = "data_bits"
|
||||
CONF_DESCRIPTION = "description"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "counter_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::counter {
|
||||
|
||||
static const char *const TAG = "counter";
|
||||
|
||||
void CounterSensor::setup() {
|
||||
if (this->restore_) {
|
||||
this->pref_ = this->make_entity_preference<int64_t>();
|
||||
this->pref_.load(&this->value_);
|
||||
}
|
||||
this->publish_state(static_cast<float>(this->value_));
|
||||
}
|
||||
|
||||
void CounterSensor::set_value(int64_t value) {
|
||||
this->value_ = value;
|
||||
this->publish_state(static_cast<float>(value));
|
||||
if (this->restore_)
|
||||
this->pref_.save(&this->value_);
|
||||
}
|
||||
|
||||
void CounterSensor::dump_config() {
|
||||
LOG_SENSOR("", "Counter", this);
|
||||
ESP_LOGCONFIG(TAG, " Restore: %s", YESNO(this->restore_));
|
||||
}
|
||||
|
||||
} // namespace esphome::counter
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome::counter {
|
||||
|
||||
/// A counter held as a 64-bit integer. The published sensor state is a float, so it is exact only up to 2^24.
|
||||
class CounterSensor final : public sensor::Sensor, public Component {
|
||||
public:
|
||||
/// The counter starts at initial_value unless a stored value is restored.
|
||||
CounterSensor(bool restore, int64_t initial_value) : value_(initial_value), restore_(restore) {}
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
// restore value before `on_boot` automations run
|
||||
float get_setup_priority() const override { return setup_priority::DATA + 50.0f; }
|
||||
|
||||
/// Increment by one each time the given sensor publishes a state.
|
||||
void count_updates_from(sensor::Sensor *source) {
|
||||
source->add_on_state_callback([this](float) { this->increment(); });
|
||||
}
|
||||
|
||||
/// Increment by one each time the given binary sensor changes to true.
|
||||
template<typename T> void count_true_from(T *source) {
|
||||
source->add_on_state_callback([this](bool state) {
|
||||
if (state)
|
||||
this->increment();
|
||||
});
|
||||
}
|
||||
|
||||
void set_value(int64_t value);
|
||||
/// overflow on addition of signed numbers is undefined - use the well defined unsigned version
|
||||
void increment(int64_t amount = 1) {
|
||||
this->set_value(static_cast<int64_t>(static_cast<uint64_t>(this->value_) + static_cast<uint64_t>(amount)));
|
||||
}
|
||||
|
||||
protected:
|
||||
ESPPreferenceObject pref_;
|
||||
int64_t value_;
|
||||
bool restore_;
|
||||
};
|
||||
|
||||
} // namespace esphome::counter
|
||||
@@ -0,0 +1,85 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BINARY_SENSOR,
|
||||
CONF_ID,
|
||||
CONF_INITIAL_VALUE,
|
||||
CONF_RESTORE,
|
||||
CONF_SENSOR,
|
||||
CONF_VALUE,
|
||||
ICON_COUNTER,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
counter_ns = cg.esphome_ns.namespace("counter")
|
||||
CounterSensor = counter_ns.class_("CounterSensor", sensor.Sensor, cg.Component)
|
||||
|
||||
# The lowest value is left out because its C++ literal cannot be written portably.
|
||||
INT64_MAX = 2**63 - 1
|
||||
COUNTER_RANGE = cv.int_range(min=-INT64_MAX, max=INT64_MAX)
|
||||
COUNTER_VALUE = cv.templatable(COUNTER_RANGE)
|
||||
|
||||
|
||||
def _not_own_source(config: ConfigType) -> ConfigType:
|
||||
if (source := config.get(CONF_SENSOR)) is not None and source.id == config[
|
||||
CONF_ID
|
||||
].id:
|
||||
raise cv.Invalid("A counter cannot count its own updates", [CONF_SENSOR])
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
CounterSensor,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=0,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_RESTORE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_INITIAL_VALUE, default=0): COUNTER_RANGE,
|
||||
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
_not_own_source,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID], config[CONF_RESTORE], config[CONF_INITIAL_VALUE]
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
if (source := config.get(CONF_SENSOR)) is not None:
|
||||
cg.add(var.count_updates_from(await cg.get_variable(source)))
|
||||
if (source := config.get(CONF_BINARY_SENSOR)) is not None:
|
||||
cg.add(var.count_true_from(await cg.get_variable(source)))
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
"counter.set_value",
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(CounterSensor),
|
||||
cv.Required(CONF_VALUE): COUNTER_VALUE,
|
||||
},
|
||||
key=CONF_VALUE,
|
||||
),
|
||||
automation.ApplyField(CONF_VALUE, "set_value", cg.int64),
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"counter.increment",
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(CounterSensor),
|
||||
cv.Optional(CONF_VALUE, default=1): COUNTER_VALUE,
|
||||
}
|
||||
),
|
||||
automation.ApplyField(CONF_VALUE, "increment", cg.int64),
|
||||
)
|
||||
@@ -98,24 +98,11 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if voltage_config := config.get(CONF_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if current_config := config.get(CONF_CURRENT):
|
||||
sens = await sensor.new_sensor(current_config)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if power_config := config.get(CONF_POWER):
|
||||
sens = await sensor.new_sensor(power_config)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
if energy_config := config.get(CONF_ENERGY):
|
||||
sens = await sensor.new_sensor(energy_config)
|
||||
cg.add(var.set_energy_sensor(sens))
|
||||
if apparent_power_config := config.get(CONF_APPARENT_POWER):
|
||||
sens = await sensor.new_sensor(apparent_power_config)
|
||||
cg.add(var.set_apparent_power_sensor(sens))
|
||||
if reactive_power_config := config.get(CONF_REACTIVE_POWER):
|
||||
sens = await sensor.new_sensor(reactive_power_config)
|
||||
cg.add(var.set_reactive_power_sensor(sens))
|
||||
if power_factor_config := config.get(CONF_POWER_FACTOR):
|
||||
sens = await sensor.new_sensor(power_factor_config)
|
||||
cg.add(var.set_power_factor_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
await sensors(CONF_ENERGY, var.set_energy_sensor)
|
||||
await sensors(CONF_APPARENT_POWER, var.set_apparent_power_sensor)
|
||||
await sensors(CONF_REACTIVE_POWER, var.set_reactive_power_sensor)
|
||||
await sensors(CONF_POWER_FACTOR, var.set_power_factor_sensor)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.nrf52.framework import include_west_project
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
@@ -51,7 +52,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
zephyr_add_prj_conf("HWINFO", True)
|
||||
# gdb thread support
|
||||
zephyr_add_prj_conf("DEBUG_THREAD_INFO", True)
|
||||
# RTT
|
||||
# RTT sources live in the segger project
|
||||
include_west_project("segger")
|
||||
zephyr_add_prj_conf("USE_SEGGER_RTT", True)
|
||||
zephyr_add_prj_conf("RTT_CONSOLE", True)
|
||||
zephyr_add_prj_conf("LOG", True)
|
||||
|
||||
@@ -106,32 +106,13 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
hub = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
|
||||
if free_conf := config.get(CONF_FREE):
|
||||
sens = await sensor.new_sensor(free_conf)
|
||||
cg.add(debug_component.set_free_sensor(sens))
|
||||
|
||||
if block_conf := config.get(CONF_BLOCK):
|
||||
sens = await sensor.new_sensor(block_conf)
|
||||
cg.add(debug_component.set_block_sensor(sens))
|
||||
|
||||
if fragmentation_conf := config.get(CONF_FRAGMENTATION):
|
||||
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))
|
||||
|
||||
if psram_conf := config.get(CONF_PSRAM):
|
||||
sens = await sensor.new_sensor(psram_conf)
|
||||
cg.add(debug_component.set_psram_sensor(sens))
|
||||
|
||||
if cpu_freq_conf := config.get(CONF_CPU_FREQUENCY):
|
||||
sens = await sensor.new_sensor(cpu_freq_conf)
|
||||
cg.add(debug_component.set_cpu_frequency_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_FREE, hub.set_free_sensor)
|
||||
await sensors(CONF_BLOCK, hub.set_block_sensor)
|
||||
await sensors(CONF_FRAGMENTATION, hub.set_fragmentation_sensor)
|
||||
await sensors(CONF_MIN_FREE, hub.set_min_free_sensor)
|
||||
await sensors(CONF_LOOP_TIME, hub.set_loop_time_sensor)
|
||||
await sensors(CONF_PSRAM, hub.set_psram_sensor)
|
||||
await sensors(CONF_CPU_FREQUENCY, hub.set_cpu_frequency_sensor)
|
||||
|
||||
@@ -31,11 +31,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
hub = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
|
||||
if CONF_DEVICE in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_DEVICE])
|
||||
cg.add(debug_component.set_device_info_sensor(sens))
|
||||
if CONF_RESET_REASON in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_RESET_REASON])
|
||||
cg.add(debug_component.set_reset_reason_sensor(sens))
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_DEVICE, hub.set_device_info_sensor)
|
||||
await text_sensors(CONF_RESET_REASON, hub.set_reset_reason_sensor)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome.components.const import CONF_CONVERSION_RATE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INVERTED, CONF_RESOLUTION
|
||||
from esphome.types import ConfigType
|
||||
@@ -13,7 +14,6 @@ MULTI_CONF = True
|
||||
CONF_PWM = "pwm"
|
||||
CONF_DIVIDER = "divider"
|
||||
CONF_DAC = "dac"
|
||||
CONF_CONVERSION_RATE = "conversion_rate"
|
||||
|
||||
CONF_EMC2101_ID = "emc2101_id"
|
||||
|
||||
|
||||
@@ -31,12 +31,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
hub = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_PUMP_STATE in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_PUMP_STATE])
|
||||
cg.add(parent.set_is_dosing(sens))
|
||||
|
||||
if CONF_IS_PAUSED in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_IS_PAUSED])
|
||||
cg.add(parent.set_is_paused(sens))
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(CONF_PUMP_STATE, hub.set_is_dosing)
|
||||
await binary_sensors(CONF_IS_PAUSED, hub.set_is_paused)
|
||||
|
||||
@@ -76,28 +76,12 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
hub = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_CURRENT_VOLUME_DOSED in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CURRENT_VOLUME_DOSED])
|
||||
cg.add(parent.set_current_volume_dosed(sens))
|
||||
|
||||
if CONF_LAST_VOLUME_REQUESTED in config:
|
||||
sens = await sensor.new_sensor(config[CONF_LAST_VOLUME_REQUESTED])
|
||||
cg.add(parent.set_last_volume_requested(sens))
|
||||
|
||||
if CONF_TOTAL_VOLUME_DOSED in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL_VOLUME_DOSED])
|
||||
cg.add(parent.set_total_volume_dosed(sens))
|
||||
|
||||
if CONF_ABSOLUTE_TOTAL_VOLUME_DOSED in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ABSOLUTE_TOTAL_VOLUME_DOSED])
|
||||
cg.add(parent.set_absolute_total_volume_dosed(sens))
|
||||
|
||||
if CONF_PUMP_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PUMP_VOLTAGE])
|
||||
cg.add(parent.set_pump_voltage(sens))
|
||||
|
||||
if CONF_MAX_FLOW_RATE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_MAX_FLOW_RATE])
|
||||
cg.add(parent.set_max_flow_rate(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_CURRENT_VOLUME_DOSED, hub.set_current_volume_dosed)
|
||||
await sensors(CONF_LAST_VOLUME_REQUESTED, hub.set_last_volume_requested)
|
||||
await sensors(CONF_TOTAL_VOLUME_DOSED, hub.set_total_volume_dosed)
|
||||
await sensors(CONF_ABSOLUTE_TOTAL_VOLUME_DOSED, hub.set_absolute_total_volume_dosed)
|
||||
await sensors(CONF_PUMP_VOLTAGE, hub.set_pump_voltage)
|
||||
await sensors(CONF_MAX_FLOW_RATE, hub.set_max_flow_rate)
|
||||
|
||||
@@ -24,12 +24,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
hub = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_DOSING_MODE in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_DOSING_MODE])
|
||||
cg.add(parent.set_dosing_mode(sens))
|
||||
|
||||
if CONF_CALIBRATION_STATUS in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_CALIBRATION_STATUS])
|
||||
cg.add(parent.set_calibration_status(sens))
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_DOSING_MODE, hub.set_dosing_mode)
|
||||
await text_sensors(CONF_CALIBRATION_STATUS, hub.set_calibration_status)
|
||||
|
||||
@@ -177,33 +177,14 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
cg.add(var.set_protocol_version(config[CONF_PROTOCOL_VERSION]))
|
||||
|
||||
if CONF_INVERTER_STATUS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS])
|
||||
cg.add(var.set_inverter_status_sensor(sens))
|
||||
|
||||
if CONF_FREQUENCY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
|
||||
cg.add(var.set_grid_frequency_sensor(sens))
|
||||
|
||||
if CONF_ACTIVE_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER])
|
||||
cg.add(var.set_grid_active_power_sensor(sens))
|
||||
|
||||
if CONF_PV_ACTIVE_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PV_ACTIVE_POWER])
|
||||
cg.add(var.set_pv_active_power_sensor(sens))
|
||||
|
||||
if CONF_ENERGY_PRODUCTION_DAY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY])
|
||||
cg.add(var.set_today_production_sensor(sens))
|
||||
|
||||
if CONF_TOTAL_ENERGY_PRODUCTION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION])
|
||||
cg.add(var.set_total_energy_production_sensor(sens))
|
||||
|
||||
if CONF_INVERTER_MODULE_TEMP in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP])
|
||||
cg.add(var.set_inverter_module_temp_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_INVERTER_STATUS, var.set_inverter_status_sensor)
|
||||
await sensors(CONF_FREQUENCY, var.set_grid_frequency_sensor)
|
||||
await sensors(CONF_ACTIVE_POWER, var.set_grid_active_power_sensor)
|
||||
await sensors(CONF_PV_ACTIVE_POWER, var.set_pv_active_power_sensor)
|
||||
await sensors(CONF_ENERGY_PRODUCTION_DAY, var.set_today_production_sensor)
|
||||
await sensors(CONF_TOTAL_ENERGY_PRODUCTION, var.set_total_energy_production_sensor)
|
||||
await sensors(CONF_INVERTER_MODULE_TEMP, var.set_inverter_module_temp_sensor)
|
||||
|
||||
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||
if phase not in config:
|
||||
|
||||
@@ -229,65 +229,24 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await modbus.register_modbus_client_device(var, config)
|
||||
|
||||
if CONF_FREQUENCY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
|
||||
cg.add(var.set_frequency_sensor(sens))
|
||||
|
||||
if CONF_ACTIVE_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER])
|
||||
cg.add(var.set_active_power_sensor(sens))
|
||||
|
||||
if CONF_REACTIVE_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_REACTIVE_POWER])
|
||||
cg.add(var.set_reactive_power_sensor(sens))
|
||||
|
||||
if CONF_ENERGY_PRODUCTION_DAY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY])
|
||||
cg.add(var.set_today_production_sensor(sens))
|
||||
|
||||
if CONF_TOTAL_ENERGY_PRODUCTION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION])
|
||||
cg.add(var.set_total_energy_production_sensor(sens))
|
||||
|
||||
if CONF_TOTAL_GENERATION_TIME in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL_GENERATION_TIME])
|
||||
cg.add(var.set_total_generation_time_sensor(sens))
|
||||
|
||||
if CONF_TODAY_GENERATION_TIME in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TODAY_GENERATION_TIME])
|
||||
cg.add(var.set_today_generation_time_sensor(sens))
|
||||
|
||||
if CONF_INVERTER_MODULE_TEMP in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP])
|
||||
cg.add(var.set_inverter_module_temp_sensor(sens))
|
||||
|
||||
if CONF_INVERTER_INNER_TEMP in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_INNER_TEMP])
|
||||
cg.add(var.set_inverter_inner_temp_sensor(sens))
|
||||
|
||||
if CONF_INVERTER_BUS_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_BUS_VOLTAGE])
|
||||
cg.add(var.set_inverter_bus_voltage_sensor(sens))
|
||||
|
||||
if CONF_INSULATION_OF_PV_N_TO_GROUND in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INSULATION_OF_PV_N_TO_GROUND])
|
||||
cg.add(var.set_insulation_pv_n_to_ground_sensor(sens))
|
||||
|
||||
if CONF_GFCI_VALUE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_GFCI_VALUE])
|
||||
cg.add(var.set_gfci_value_sensor(sens))
|
||||
|
||||
if CONF_DCI_OF_R in config:
|
||||
sens = await sensor.new_sensor(config[CONF_DCI_OF_R])
|
||||
cg.add(var.set_dci_of_r_sensor(sens))
|
||||
|
||||
if CONF_DCI_OF_S in config:
|
||||
sens = await sensor.new_sensor(config[CONF_DCI_OF_S])
|
||||
cg.add(var.set_dci_of_s_sensor(sens))
|
||||
|
||||
if CONF_DCI_OF_T in config:
|
||||
sens = await sensor.new_sensor(config[CONF_DCI_OF_T])
|
||||
cg.add(var.set_dci_of_t_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_FREQUENCY, var.set_frequency_sensor)
|
||||
await sensors(CONF_ACTIVE_POWER, var.set_active_power_sensor)
|
||||
await sensors(CONF_REACTIVE_POWER, var.set_reactive_power_sensor)
|
||||
await sensors(CONF_ENERGY_PRODUCTION_DAY, var.set_today_production_sensor)
|
||||
await sensors(CONF_TOTAL_ENERGY_PRODUCTION, var.set_total_energy_production_sensor)
|
||||
await sensors(CONF_TOTAL_GENERATION_TIME, var.set_total_generation_time_sensor)
|
||||
await sensors(CONF_TODAY_GENERATION_TIME, var.set_today_generation_time_sensor)
|
||||
await sensors(CONF_INVERTER_MODULE_TEMP, var.set_inverter_module_temp_sensor)
|
||||
await sensors(CONF_INVERTER_INNER_TEMP, var.set_inverter_inner_temp_sensor)
|
||||
await sensors(CONF_INVERTER_BUS_VOLTAGE, var.set_inverter_bus_voltage_sensor)
|
||||
await sensors(
|
||||
CONF_INSULATION_OF_PV_N_TO_GROUND, var.set_insulation_pv_n_to_ground_sensor
|
||||
)
|
||||
await sensors(CONF_GFCI_VALUE, var.set_gfci_value_sensor)
|
||||
await sensors(CONF_DCI_OF_R, var.set_dci_of_r_sensor)
|
||||
await sensors(CONF_DCI_OF_S, var.set_dci_of_s_sensor)
|
||||
await sensors(CONF_DCI_OF_T, var.set_dci_of_t_sensor)
|
||||
|
||||
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||
if phase not in config:
|
||||
|
||||
@@ -107,18 +107,11 @@ async def to_code(config: ConfigType) -> None:
|
||||
cf1 = await cg.gpio_pin_expression(config[CONF_CF1_PIN])
|
||||
cg.add(var.set_cf1_pin(cf1))
|
||||
|
||||
if CONF_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_VOLTAGE])
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if CONF_CURRENT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CURRENT])
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if CONF_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_POWER])
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
if CONF_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ENERGY])
|
||||
cg.add(var.set_energy_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
await sensors(CONF_ENERGY, var.set_energy_sensor)
|
||||
cg.add(var.set_current_resistor(config[CONF_CURRENT_RESISTOR]))
|
||||
cg.add(var.set_voltage_divider(config[CONF_VOLTAGE_DIVIDER]))
|
||||
cg.add(var.set_initial_mode(INITIAL_MODES[config[CONF_INITIAL_MODE]]))
|
||||
|
||||
@@ -92,17 +92,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_PM_1_0 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PM_1_0])
|
||||
cg.add(var.set_pm_1_0_sensor(sens))
|
||||
|
||||
if CONF_PM_2_5 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PM_2_5])
|
||||
cg.add(var.set_pm_2_5_sensor(sens))
|
||||
|
||||
if CONF_PM_10_0 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PM_10_0])
|
||||
cg.add(var.set_pm_10_0_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_PM_1_0, var.set_pm_1_0_sensor)
|
||||
await sensors(CONF_PM_2_5, var.set_pm_2_5_sensor)
|
||||
await sensors(CONF_PM_10_0, var.set_pm_10_0_sensor)
|
||||
|
||||
# Remove before 2026.12.0
|
||||
if CONF_AQI in config:
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "hoermann_hcp.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::hoermann_hcp {
|
||||
@@ -60,6 +63,61 @@ static bool is_moving(DoorState state) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
// The command byte of a status poll. Only its answer can carry a request.
|
||||
static constexpr uint8_t STATUS_COMMAND = 0x03;
|
||||
// A status answer with this code in the low byte of its second register asks the bus controller for a value,
|
||||
// named in the high byte of the third.
|
||||
static constexpr uint8_t ANSWER_REQUEST = 0x22;
|
||||
static constexpr uint8_t REQUEST_SERIAL = 0x05;
|
||||
static constexpr uint8_t REQUEST_FIRMWARE = 0x06;
|
||||
// Each request goes out in one answer, up to this many times, this far apart.
|
||||
static constexpr uint8_t IDENTITY_MAX_ATTEMPTS = 3;
|
||||
static constexpr uint32_t IDENTITY_RETRY_MS = 30000;
|
||||
// The value comes back as a payload transfer: this command in the low byte of the first command register, a sub
|
||||
// code in the high byte of the second, the payload from the third on.
|
||||
static constexpr uint8_t TRANSFER_COMMAND = 0x04;
|
||||
static constexpr uint8_t TRANSFER_SUB_SERIAL = 0x0C;
|
||||
static constexpr uint8_t TRANSFER_SUB_FIRMWARE = 0x0D;
|
||||
static constexpr uint8_t TRANSFER_ACK = 0xFD;
|
||||
static constexpr size_t TRANSFER_PAYLOAD_REG = 2;
|
||||
// Marks the first half of the serial number in the counter byte, and is not part of the count.
|
||||
static constexpr uint8_t COUNTER_FIRST_HALF = 0x80;
|
||||
// Older motors (index B1 seen) send the whole serial number in one frame, without the half marker. It is as long
|
||||
// as a second half, so one copy path serves both.
|
||||
static constexpr size_t SERIAL_SINGLE_FRAME_REGS = 6;
|
||||
static_assert(SERIAL_SINGLE_FRAME_REGS == SERIAL_SECOND_HALF_REGS, "one copy path serves both serial frames");
|
||||
|
||||
// Registers hold two payload bytes each, high byte first.
|
||||
static void copy_payload(const modbus::RegisterValues ®isters, size_t count, char *out) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const uint16_t value = registers[TRANSFER_PAYLOAD_REG + i];
|
||||
out[2 * i] = static_cast<char>(value >> 8);
|
||||
out[2 * i + 1] = static_cast<char>(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Length of the printable text at the start, without trailing spaces. The padding after it varies.
|
||||
static size_t text_length(const char *text, size_t len) {
|
||||
size_t at = 0;
|
||||
while (at < len && text[at] >= 0x20 && text[at] <= 0x7E)
|
||||
at++;
|
||||
while (at > 0 && text[at - 1] == ' ')
|
||||
at--;
|
||||
return at;
|
||||
}
|
||||
|
||||
static void terminate_text(char *text, size_t len) { text[text_length(text, len)] = '\0'; }
|
||||
|
||||
// dump_config() is replayed to remote log clients, so they see the outcome of the exchange at boot.
|
||||
static void log_identity_value(text_sensor::TextSensor *sensor) {
|
||||
if (sensor != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Value: %s",
|
||||
sensor->has_state() ? sensor->get_state().c_str() : LOG_STR_LITERAL("not received"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void HoermannHcp::update() {
|
||||
const uint32_t now = millis();
|
||||
// Time out the connection flag if the bus controller stopped polling.
|
||||
@@ -91,6 +149,9 @@ void HoermannHcp::update() {
|
||||
ESP_LOGW(TAG, "Door did not report the lamp changing, giving up on the toggle");
|
||||
this->forget_light_toggles_();
|
||||
}
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
this->publish_identity_();
|
||||
#endif
|
||||
if (this->changed_) {
|
||||
this->changed_ = false;
|
||||
this->state_callback_.call();
|
||||
@@ -102,6 +163,12 @@ void HoermannHcp::dump_config() {
|
||||
"Hoermann HCP bridge:\n"
|
||||
" Modbus server address: 0x%02X",
|
||||
this->get_address());
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
LOG_TEXT_SENSOR(" ", "Serial Number", this->serial_number_text_sensor_);
|
||||
log_identity_value(this->serial_number_text_sensor_);
|
||||
LOG_TEXT_SENSOR(" ", "Firmware Version", this->version_text_sensor_);
|
||||
log_identity_value(this->version_text_sensor_);
|
||||
#endif
|
||||
}
|
||||
|
||||
modbus::ResponseStatus HoermannHcp::on_read_holding_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
@@ -113,6 +180,14 @@ modbus::ResponseStatus HoermannHcp::on_read_holding_registers(uint16_t start_add
|
||||
|
||||
this->record_response_();
|
||||
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
// Acknowledge the transfer taken by the write half of this frame.
|
||||
if (this->transfer_answer_counter_ != NO_TRANSFER_ANSWER) {
|
||||
this->push_transfer_answer_(registers, number_of_registers);
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
// 0x17 read half: STATE_REG is read back right after COMMAND_REG was written, so echo the stored message
|
||||
// counter (high byte) and command (low byte). The read length identifies which internal block is requested.
|
||||
const uint16_t counter = this->command_reg_value_ & 0xFF00;
|
||||
@@ -125,6 +200,9 @@ modbus::ResponseStatus HoermannHcp::on_read_holding_registers(uint16_t start_add
|
||||
registers.push_back(static_cast<uint16_t>(0x0001 | command));
|
||||
this->push_command_registers_(registers);
|
||||
push_zeros(registers, 4);
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
this->add_identity_request_(registers, command);
|
||||
#endif
|
||||
break;
|
||||
case 2:
|
||||
// Empty command request.
|
||||
@@ -156,6 +234,9 @@ modbus::ResponseStatus HoermannHcp::on_write_registers(uint16_t start_address,
|
||||
// command byte back from STATE_REG. The hub always runs the write before the read within one request.
|
||||
this->record_response_();
|
||||
this->command_reg_value_ = registers[0];
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
this->transfer_answer_counter_ = this->take_identity_transfer_(registers);
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -217,6 +298,155 @@ void HoermannHcp::push_command_registers_(modbus::RegisterValues ®isters) {
|
||||
registers.push_back(command->released_value_2);
|
||||
}
|
||||
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
void HoermannHcp::add_identity_request_(modbus::RegisterValues ®isters, uint16_t command) {
|
||||
if (static_cast<uint8_t>(this->command_reg_value_) != STATUS_COMMAND)
|
||||
return;
|
||||
// Like Hoermann's own bus accessory, only after one ordinary answer.
|
||||
if (this->identity_phase_ == IdentityPhase::IDENTITY_PHASE_IDLE) {
|
||||
if (this->serial_number_text_sensor_ != nullptr || this->version_text_sensor_ != nullptr)
|
||||
this->arm_identity_request_(IdentityPhase::IDENTITY_PHASE_SERIAL);
|
||||
return;
|
||||
}
|
||||
// Uses the registers of a key press, so it waits while one is pending.
|
||||
if (this->next_command_ != nullptr || registers[2] != 0 || registers[3] != 0 || !this->take_identity_request_())
|
||||
return;
|
||||
registers[1] = static_cast<uint16_t>(ANSWER_REQUEST | command);
|
||||
registers[2] = encode_uint16(this->identity_request_(), 0);
|
||||
}
|
||||
|
||||
void HoermannHcp::arm_identity_request_(IdentityPhase phase) {
|
||||
this->identity_phase_ = phase;
|
||||
this->identity_attempts_ = 0;
|
||||
}
|
||||
|
||||
bool HoermannHcp::take_identity_request_() {
|
||||
const uint8_t request = this->identity_request_();
|
||||
if (request == 0)
|
||||
return false;
|
||||
const uint32_t now = millis();
|
||||
if (this->identity_attempts_ != 0 && now - this->identity_asked_at_ <= IDENTITY_RETRY_MS)
|
||||
return false;
|
||||
if (this->identity_attempts_ >= IDENTITY_MAX_ATTEMPTS) {
|
||||
this->identity_unanswered_ = request;
|
||||
if (request == REQUEST_SERIAL) {
|
||||
// Still ask for the firmware version, and drop a leftover first half.
|
||||
this->serial_number_[0] = '\0';
|
||||
this->arm_identity_request_(IdentityPhase::IDENTITY_PHASE_FIRMWARE);
|
||||
} else {
|
||||
this->identity_phase_ = IdentityPhase::IDENTITY_PHASE_DONE;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
this->identity_attempts_++;
|
||||
this->identity_asked_at_ = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t HoermannHcp::take_identity_transfer_(const modbus::RegisterValues ®isters) {
|
||||
if (this->identity_phase_ == IdentityPhase::IDENTITY_PHASE_IDLE || registers.size() < TRANSFER_PAYLOAD_REG ||
|
||||
static_cast<uint8_t>(registers[0]) != TRANSFER_COMMAND)
|
||||
return NO_TRANSFER_ANSWER;
|
||||
const uint8_t counter = static_cast<uint8_t>(registers[0] >> 8);
|
||||
const uint8_t sub_code = static_cast<uint8_t>(registers[1] >> 8);
|
||||
if (sub_code != TRANSFER_SUB_SERIAL && sub_code != TRANSFER_SUB_FIRMWARE)
|
||||
return NO_TRANSFER_ANSWER;
|
||||
// Acknowledged whether kept or not, as Hoermann's own bus accessory does. What was not kept is asked for again.
|
||||
const uint8_t answer = counter & ~COUNTER_FIRST_HALF;
|
||||
// Only an answer to the request that went out is kept.
|
||||
const uint8_t request = sub_code == TRANSFER_SUB_SERIAL ? REQUEST_SERIAL : REQUEST_FIRMWARE;
|
||||
if (this->identity_attempts_ == 0 || this->identity_request_() != request)
|
||||
return answer;
|
||||
|
||||
const size_t payload_regs = registers.size() - TRANSFER_PAYLOAD_REG;
|
||||
if (request == REQUEST_FIRMWARE) {
|
||||
const size_t regs = std::min(payload_regs, FIRMWARE_REGS);
|
||||
copy_payload(registers, regs, this->firmware_version_);
|
||||
if (regs == FIRMWARE_REGS && text_length(this->firmware_version_, 2 * regs) != 0) {
|
||||
terminate_text(this->firmware_version_, 2 * regs);
|
||||
// Clear an earlier unreadable answer, so this one is shown.
|
||||
this->firmware_unreadable_ = false;
|
||||
this->identity_phase_ = IdentityPhase::IDENTITY_PHASE_DONE;
|
||||
return answer;
|
||||
}
|
||||
// Left as received for update() to log.
|
||||
this->firmware_unreadable_ = true;
|
||||
this->firmware_unreadable_len_ = 2 * regs;
|
||||
// A short answer is asked for again; unreadable text would come back the same.
|
||||
if (regs == FIRMWARE_REGS)
|
||||
this->identity_phase_ = IdentityPhase::IDENTITY_PHASE_DONE;
|
||||
return answer;
|
||||
}
|
||||
if ((counter & COUNTER_FIRST_HALF) != 0) {
|
||||
// Even a half too short to keep says the number comes in two halves; a first half already kept stays kept.
|
||||
if (this->identity_phase_ == IdentityPhase::IDENTITY_PHASE_SERIAL)
|
||||
this->identity_phase_ = IdentityPhase::IDENTITY_PHASE_SERIAL_SPLIT;
|
||||
if (payload_regs >= SERIAL_FIRST_HALF_REGS) {
|
||||
copy_payload(registers, SERIAL_FIRST_HALF_REGS, this->serial_number_);
|
||||
this->identity_phase_ = IdentityPhase::IDENTITY_PHASE_SERIAL_SECOND_HALF;
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
// Without a half marker seen, the frame is the whole number; after one, it can only be the second half.
|
||||
if (this->identity_phase_ == IdentityPhase::IDENTITY_PHASE_SERIAL_SPLIT || payload_regs < SERIAL_SINGLE_FRAME_REGS)
|
||||
return answer;
|
||||
const size_t at =
|
||||
this->identity_phase_ == IdentityPhase::IDENTITY_PHASE_SERIAL_SECOND_HALF ? 2 * SERIAL_FIRST_HALF_REGS : 0;
|
||||
copy_payload(registers, SERIAL_SINGLE_FRAME_REGS, this->serial_number_ + at);
|
||||
terminate_text(this->serial_number_, at + 2 * SERIAL_SINGLE_FRAME_REGS);
|
||||
this->serial_unreadable_ = this->serial_number_[0] == '\0';
|
||||
this->arm_identity_request_(IdentityPhase::IDENTITY_PHASE_FIRMWARE);
|
||||
return answer;
|
||||
}
|
||||
|
||||
void HoermannHcp::push_transfer_answer_(modbus::RegisterValues ®isters, uint16_t number_of_registers) {
|
||||
const uint16_t answer[] = {encode_uint16(this->transfer_answer_counter_, 0),
|
||||
encode_uint16(TRANSFER_COMMAND, TRANSFER_ACK)};
|
||||
this->transfer_answer_counter_ = NO_TRANSFER_ANSWER;
|
||||
for (uint16_t i = 0; i < number_of_registers; i++)
|
||||
registers.push_back(i < 2 ? answer[i] : 0x0000);
|
||||
}
|
||||
|
||||
void HoermannHcp::publish_identity_() {
|
||||
// Buffers are cleared once published, so each value goes out once. The serial number is whole once the firmware
|
||||
// version is requested.
|
||||
if (this->serial_number_text_sensor_ != nullptr && this->identity_request_() != REQUEST_SERIAL &&
|
||||
this->serial_number_[0] != '\0') {
|
||||
this->serial_number_text_sensor_->publish_state(this->serial_number_);
|
||||
this->serial_number_[0] = '\0';
|
||||
}
|
||||
if (this->serial_unreadable_) {
|
||||
this->serial_unreadable_ = false;
|
||||
ESP_LOGW(TAG, "Unreadable serial number");
|
||||
}
|
||||
// Checked first: the buffer then holds raw bytes, not text.
|
||||
if (this->firmware_unreadable_) {
|
||||
this->firmware_unreadable_ = false;
|
||||
const uint8_t len = this->firmware_unreadable_len_;
|
||||
// All zeros: the motor does not report a version (index B1 seen).
|
||||
if (len == 2 * FIRMWARE_REGS &&
|
||||
std::all_of(this->firmware_version_, this->firmware_version_ + len, [](char c) { return c == '\0'; })) {
|
||||
ESP_LOGD(TAG, "Motor does not report its firmware version");
|
||||
} else {
|
||||
char hex[format_hex_size(2 * FIRMWARE_REGS)];
|
||||
ESP_LOGW(TAG, "Unreadable firmware version (%u bytes): %s", len,
|
||||
format_hex_to(hex, reinterpret_cast<const uint8_t *>(this->firmware_version_), len));
|
||||
}
|
||||
this->firmware_version_[0] = '\0';
|
||||
}
|
||||
if (this->version_text_sensor_ != nullptr && this->firmware_version_[0] != '\0') {
|
||||
this->version_text_sensor_->publish_state(this->firmware_version_);
|
||||
this->firmware_version_[0] = '\0';
|
||||
}
|
||||
if (this->identity_unanswered_ != 0) {
|
||||
ESP_LOGW(TAG, "No usable %s received",
|
||||
this->identity_unanswered_ == REQUEST_SERIAL ? LOG_STR_LITERAL("serial number")
|
||||
: LOG_STR_LITERAL("firmware version"));
|
||||
this->identity_unanswered_ = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void HoermannHcp::on_position_reg_(uint16_t value) {
|
||||
// Low byte: current position.
|
||||
const uint8_t position = static_cast<uint8_t>(value);
|
||||
@@ -371,6 +601,9 @@ void HoermannHcp::set_valid_(bool valid) {
|
||||
this->forget_light_toggles_();
|
||||
// The lamp can be switched at the door while the bus is quiet, so what was last read is no longer trusted.
|
||||
this->set_light_seen_(false);
|
||||
// The same holds for the door. The next broadcast is decoded even if it repeats the last one.
|
||||
this->door_state_seen_ = false;
|
||||
this->prev_state_reg_ = 0xFFFF;
|
||||
this->short_broadcast_logged_ = false;
|
||||
}
|
||||
|
||||
@@ -410,6 +643,11 @@ void HoermannHcp::forget_light_toggles_() {
|
||||
}
|
||||
|
||||
void HoermannHcp::set_door_state_(DoorState state) {
|
||||
// The first state may equal the default, so being seen is a change of its own.
|
||||
if (!this->door_state_seen_) {
|
||||
this->door_state_seen_ = true;
|
||||
this->changed_ = true;
|
||||
}
|
||||
if (this->door_state_ == state)
|
||||
return;
|
||||
this->door_state_ = state;
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::hoermann_hcp {
|
||||
|
||||
@@ -21,6 +25,28 @@ enum class DoorState : uint8_t {
|
||||
STOPPED,
|
||||
};
|
||||
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
// Payload registers of each value, two bytes each.
|
||||
static constexpr size_t SERIAL_FIRST_HALF_REGS = 7;
|
||||
static constexpr size_t SERIAL_SECOND_HALF_REGS = 6;
|
||||
static constexpr size_t FIRMWARE_REGS = 6;
|
||||
// Counter of the transfer the read half of the frame acknowledges. Real counters have the 0x80 half marker
|
||||
// stripped, so this value never occurs.
|
||||
static constexpr uint8_t NO_TRANSFER_ANSWER = 0xFF;
|
||||
|
||||
// Where the identity exchange stands. The low nibble is the code the motor is asked with, 0 while nothing is
|
||||
// outstanding. Like Hoermann's own bus accessory, the first status answer stays ordinary. Older motors (index B1
|
||||
// seen) send the whole serial number in one frame, without the half marker.
|
||||
enum class IdentityPhase : uint8_t {
|
||||
IDENTITY_PHASE_IDLE = 0x00,
|
||||
IDENTITY_PHASE_DONE = 0x10,
|
||||
IDENTITY_PHASE_SERIAL = 0x05, // A plain frame is the whole serial number.
|
||||
IDENTITY_PHASE_SERIAL_SPLIT = 0x15, // A half marker was seen, so a plain frame can only be the second half.
|
||||
IDENTITY_PHASE_SERIAL_SECOND_HALF = 0x25, // The first half is in.
|
||||
IDENTITY_PHASE_FIRMWARE = 0x06,
|
||||
};
|
||||
#endif
|
||||
|
||||
// A HCP command is a simulated key press: the pressed value is presented to the bus controller, then after a
|
||||
// short delay the released value. Each half also carries a second register, which names the buttons that do
|
||||
// not fit into the first.
|
||||
@@ -35,6 +61,12 @@ struct HoermannHcpCommand {
|
||||
};
|
||||
|
||||
class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice {
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
// The motor is asked for these only when one of them is configured.
|
||||
SUB_TEXT_SENSOR(serial_number)
|
||||
SUB_TEXT_SENSOR(version)
|
||||
#endif
|
||||
|
||||
public:
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
@@ -63,6 +95,9 @@ class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice {
|
||||
bool toggle_light();
|
||||
|
||||
DoorState get_door_state() const { return this->door_state_; }
|
||||
// False until a broadcast has carried a state the door is known to report. Bus traffic alone makes the
|
||||
// connection valid, so get_door_state() would still be its default.
|
||||
bool is_door_state_known() const { return this->door_state_seen_; }
|
||||
float get_current_position() const { return this->current_position_; }
|
||||
bool is_valid() const { return this->valid_; }
|
||||
bool is_light_on() const { return this->light_on_; }
|
||||
@@ -95,6 +130,21 @@ class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice {
|
||||
void on_position_reg_(uint16_t value);
|
||||
void on_state_reg_(uint16_t value);
|
||||
void on_light_reg_(uint16_t value);
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
// Puts a due request into a status answer.
|
||||
void add_identity_request_(modbus::RegisterValues ®isters, uint16_t command);
|
||||
void arm_identity_request_(IdentityPhase phase);
|
||||
uint8_t identity_request_() const { return static_cast<uint8_t>(this->identity_phase_) & 0x0F; }
|
||||
// True when the request is due, counting the attempt. Gives up after the last one.
|
||||
bool take_identity_request_();
|
||||
// Takes a value the motor hands over as a payload transfer. Returns the counter to acknowledge it with, kept or
|
||||
// not, or NO_TRANSFER_ANSWER when the frame was something else.
|
||||
uint8_t take_identity_transfer_(const modbus::RegisterValues ®isters);
|
||||
// Acknowledges the transfer taken by the write half of the same frame.
|
||||
void push_transfer_answer_(modbus::RegisterValues ®isters, uint16_t number_of_registers);
|
||||
// Runs from update(), outside the bus callbacks.
|
||||
void publish_identity_();
|
||||
#endif
|
||||
|
||||
void set_valid_(bool valid);
|
||||
void set_door_state_(DoorState state);
|
||||
@@ -145,7 +195,24 @@ class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice {
|
||||
bool changed_{false};
|
||||
bool light_on_{false};
|
||||
bool light_seen_{false};
|
||||
bool door_state_seen_{false};
|
||||
bool short_broadcast_logged_{false};
|
||||
|
||||
#ifdef USE_HOERMANN_HCP_IDENTITY
|
||||
uint32_t identity_asked_at_{0};
|
||||
IdentityPhase identity_phase_{IdentityPhase::IDENTITY_PHASE_IDLE};
|
||||
uint8_t identity_attempts_{0};
|
||||
// The request given up on, for update() to report.
|
||||
uint8_t identity_unanswered_{0};
|
||||
uint8_t transfer_answer_counter_{NO_TRANSFER_ANSWER};
|
||||
// Length of an unreadable firmware version left in firmware_version_.
|
||||
uint8_t firmware_unreadable_len_{0};
|
||||
// A serial number arrived without text, for update() to log.
|
||||
bool serial_unreadable_{false};
|
||||
bool firmware_unreadable_{false};
|
||||
char serial_number_[2 * (SERIAL_FIRST_HALF_REGS + SERIAL_SECOND_HALF_REGS) + 1]{};
|
||||
char firmware_version_[2 * FIRMWARE_REGS + 1]{};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace esphome::hoermann_hcp
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_VERSION, ENTITY_CATEGORY_DIAGNOSTIC, ICON_CHIP
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_HOERMANN_HCP_ID, HoermannHcp, hoermann_hcp_ns
|
||||
|
||||
DEPENDENCIES = ["hoermann_hcp"]
|
||||
|
||||
CONF_DOOR_STATE = "door_state"
|
||||
CONF_SERIAL_NUMBER = "serial_number"
|
||||
|
||||
HoermannHcpDoorStateTextSensor = hoermann_hcp_ns.class_(
|
||||
"HoermannHcpDoorStateTextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_HOERMANN_HCP_ID): cv.use_id(HoermannHcp),
|
||||
cv.Optional(CONF_DOOR_STATE): text_sensor.text_sensor_schema(
|
||||
HoermannHcpDoorStateTextSensor, icon="mdi:garage"
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.Optional(CONF_SERIAL_NUMBER): text_sensor.text_sensor_schema(
|
||||
icon="mdi:data-matrix", entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
icon=ICON_CHIP, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.has_at_least_one_key(CONF_DOOR_STATE, CONF_SERIAL_NUMBER, CONF_VERSION),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_HOERMANN_HCP_ID])
|
||||
if (conf := config.get(CONF_DOOR_STATE)) is not None:
|
||||
var = await text_sensor.new_text_sensor(conf, parent)
|
||||
await cg.register_component(var, conf)
|
||||
# Only the identity sensors need the exchange with the motor compiled in.
|
||||
if CONF_SERIAL_NUMBER in config or CONF_VERSION in config:
|
||||
cg.add_define("USE_HOERMANN_HCP_IDENTITY")
|
||||
if (conf := config.get(CONF_SERIAL_NUMBER)) is not None:
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(parent.set_serial_number_text_sensor(sens))
|
||||
if (conf := config.get(CONF_VERSION)) is not None:
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(parent.set_version_text_sensor(sens))
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "hoermann_hcp_text_sensor.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::hoermann_hcp {
|
||||
|
||||
static const char *const TAG = "hoermann_hcp.text_sensor";
|
||||
|
||||
// Indexed by DoorState. Each fits the 15 characters std::string keeps inline, so publishing never allocates.
|
||||
PROGMEM_STRING_TABLE(DoorStateStrings, "Open", "Opening", "Closed", "Closing", "Half open", "Moving to vent",
|
||||
"Vent position", "Moving to half", "Stopped");
|
||||
static_assert(DoorStateStrings::COUNT == static_cast<size_t>(DoorState::STOPPED) + 1,
|
||||
"DoorStateStrings must list every DoorState in order");
|
||||
|
||||
void HoermannHcpDoorStateTextSensor::setup() {
|
||||
this->parent_->add_on_state_callback([this]() { this->update_from_state_(); });
|
||||
// A state decoded before the callback was added would otherwise wait for the next change.
|
||||
this->update_from_state_();
|
||||
}
|
||||
|
||||
void HoermannHcpDoorStateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Hoermann HCP Door State", this); }
|
||||
|
||||
void HoermannHcpDoorStateTextSensor::update_from_state_() {
|
||||
// The last state stays while the bus controller is gone, and is published again once it is back.
|
||||
if (!this->parent_->is_valid()) {
|
||||
this->published_ = false;
|
||||
return;
|
||||
}
|
||||
if (!this->parent_->is_door_state_known())
|
||||
return;
|
||||
// Any hub change runs this, so only a new door state is published.
|
||||
const DoorState state = this->parent_->get_door_state();
|
||||
if (this->published_ && state == this->published_state_)
|
||||
return;
|
||||
this->published_ = true;
|
||||
this->published_state_ = state;
|
||||
char text[16];
|
||||
ESPHOME_strncpy_P(text,
|
||||
reinterpret_cast<ESPHOME_PGM_P>(
|
||||
DoorStateStrings::get_progmem_str(static_cast<uint8_t>(state), DoorStateStrings::LAST_INDEX)),
|
||||
sizeof(text));
|
||||
text[sizeof(text) - 1] = '\0';
|
||||
this->publish_state(text);
|
||||
}
|
||||
|
||||
} // namespace esphome::hoermann_hcp
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "../hoermann_hcp.h"
|
||||
|
||||
namespace esphome::hoermann_hcp {
|
||||
|
||||
class HoermannHcpDoorStateTextSensor : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
explicit HoermannHcpDoorStateTextSensor(HoermannHcp *parent) : parent_(parent) {}
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void update_from_state_();
|
||||
|
||||
HoermannHcp *const parent_;
|
||||
DoorState published_state_{DoorState::CLOSED};
|
||||
bool published_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::hoermann_hcp
|
||||
@@ -1,4 +1,5 @@
|
||||
from esphome.build_helpers.pch import PCH_PREFIX_HEADER, pch_enabled, pch_extra_scripts
|
||||
import sys
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -9,9 +10,9 @@ from esphome.const import (
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_HOST,
|
||||
ThreadModel,
|
||||
Toolchain,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.platformio.toolchain import copy_ccache_script, copy_pch_script
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .const import KEY_HOST
|
||||
@@ -38,7 +39,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_MAC_ADDRESS, default="98:35:69:ab:f6:79"): cv.mac_address,
|
||||
}
|
||||
),
|
||||
cv.require_platformio_toolchain("host"),
|
||||
# The host builds with the machine's own compiler through ninja; there
|
||||
# is no PlatformIO backend, so a CLI --toolchain must name this one
|
||||
cv.resolve_toolchain("host", (Toolchain.HOST,), Toolchain.HOST),
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
@@ -49,25 +52,23 @@ async def to_code(config: ConfigType) -> None:
|
||||
# The prefs file finds stored preferences by key, so key migration is possible
|
||||
cg.add_define("USE_PREFERENCE_KEY_LOOKUP")
|
||||
cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts)
|
||||
cg.add_build_flag("-std=gnu++20")
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_define("ESPHOME_BOARD", "host")
|
||||
cg.add_define("ESPHOME_VARIANT", "HOST")
|
||||
cg.add_define(ThreadModel.MULTI_ATOMICS)
|
||||
cg.add_platformio_option("platform", "platformio/native")
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
cg.add_platformio_option("extra_scripts", ["pre:ccache.py", *pch_extra_scripts()])
|
||||
if pch_enabled():
|
||||
# Curated prefix for the pch (the script folds it plus defines.h):
|
||||
# host has no framework force-includes, and the per-TU cost is the
|
||||
# STL closure behind the core headers. Measured -43% compile CPU.
|
||||
# Gated so ESPHOME_PCH_ENABLE=0 restores the strict view. When the
|
||||
# .gch fails to build or load, the force-include stays and every TU
|
||||
# parses the closure as text: correct, but slower than no pch.
|
||||
cg.add_platformio_option("build_src_flags", f"-include {PCH_PREFIX_HEADER}")
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files() -> None:
|
||||
copy_ccache_script()
|
||||
copy_pch_script()
|
||||
# Called by __main__.compile_program; True means this platform built the
|
||||
# program itself instead of falling through to the PlatformIO toolchain.
|
||||
def run_compile(args: object, config: ConfigType) -> bool:
|
||||
from esphome.host import toolchain
|
||||
|
||||
if sys.platform == "win32":
|
||||
# Here, not in validation: `esphome config` must keep working
|
||||
raise EsphomeError(
|
||||
"The host platform uses POSIX interfaces and does not build on "
|
||||
"Windows; use WSL or a Linux or macOS machine"
|
||||
)
|
||||
if toolchain.run_compile(config, CORE.verbose) != 0:
|
||||
raise EsphomeError("Host build failed")
|
||||
return True
|
||||
|
||||
@@ -80,18 +80,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT]))
|
||||
cg.add(var.set_max_voltage_v(config[CONF_MAX_VOLTAGE]))
|
||||
|
||||
if CONF_BUS_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE])
|
||||
cg.add(var.set_bus_voltage_sensor(sens))
|
||||
|
||||
if CONF_SHUNT_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_SHUNT_VOLTAGE])
|
||||
cg.add(var.set_shunt_voltage_sensor(sens))
|
||||
|
||||
if CONF_CURRENT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CURRENT])
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
|
||||
if CONF_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_POWER])
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_BUS_VOLTAGE, var.set_bus_voltage_sensor)
|
||||
await sensors(CONF_SHUNT_VOLTAGE, var.set_shunt_voltage_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
|
||||
@@ -133,18 +133,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
cg.add(var.set_adc_avg_samples(config[CONF_ADC_AVERAGING]))
|
||||
|
||||
if CONF_BUS_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE])
|
||||
cg.add(var.set_bus_voltage_sensor(sens))
|
||||
|
||||
if CONF_SHUNT_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_SHUNT_VOLTAGE])
|
||||
cg.add(var.set_shunt_voltage_sensor(sens))
|
||||
|
||||
if CONF_CURRENT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CURRENT])
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
|
||||
if CONF_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_POWER])
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_BUS_VOLTAGE, var.set_bus_voltage_sensor)
|
||||
await sensors(CONF_SHUNT_VOLTAGE, var.set_shunt_voltage_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
|
||||
@@ -225,38 +225,13 @@ async def setup_ina2xx(var: MockObj, config: ConfigType) -> None:
|
||||
cg.add(var.set_adc_time_shunt_voltage(adc_time_config))
|
||||
cg.add(var.set_adc_time_die_temperature(adc_time_config))
|
||||
|
||||
if conf := config.get(CONF_SHUNT_VOLTAGE):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_shunt_voltage_sensor(sens))
|
||||
|
||||
if conf := config.get(CONF_BUS_VOLTAGE):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_bus_voltage_sensor(sens))
|
||||
|
||||
if conf := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_die_temperature_sensor(sens))
|
||||
|
||||
if conf := config.get(CONF_CURRENT):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
|
||||
if conf := config.get(CONF_POWER):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
|
||||
if conf := config.get(CONF_ENERGY):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_energy_sensor_wh(sens))
|
||||
|
||||
if conf := config.get(CONF_ENERGY_JOULES):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_energy_sensor_j(sens))
|
||||
|
||||
if conf := config.get(CONF_CHARGE):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_charge_sensor_ah(sens))
|
||||
|
||||
if conf := config.get(CONF_CHARGE_COULOMBS):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_charge_sensor_c(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_SHUNT_VOLTAGE, var.set_shunt_voltage_sensor)
|
||||
await sensors(CONF_BUS_VOLTAGE, var.set_bus_voltage_sensor)
|
||||
await sensors(CONF_TEMPERATURE, var.set_die_temperature_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
await sensors(CONF_ENERGY, var.set_energy_sensor_wh)
|
||||
await sensors(CONF_ENERGY_JOULES, var.set_energy_sensor_j)
|
||||
await sensors(CONF_CHARGE, var.set_charge_sensor_ah)
|
||||
await sensors(CONF_CHARGE_COULOMBS, var.set_charge_sensor_c)
|
||||
|
||||
@@ -48,16 +48,11 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if has_target_config := config.get(CONF_HAS_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_target_config)
|
||||
cg.add(ld2410_component.set_target_binary_sensor(sens))
|
||||
if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_moving_target_config)
|
||||
cg.add(ld2410_component.set_moving_target_binary_sensor(sens))
|
||||
if has_still_target_config := config.get(CONF_HAS_STILL_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_still_target_config)
|
||||
cg.add(ld2410_component.set_still_target_binary_sensor(sens))
|
||||
if out_pin_presence_status_config := config.get(CONF_OUT_PIN_PRESENCE_STATUS):
|
||||
sens = await binary_sensor.new_binary_sensor(out_pin_presence_status_config)
|
||||
cg.add(ld2410_component.set_out_pin_presence_status_binary_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(CONF_HAS_TARGET, hub.set_target_binary_sensor)
|
||||
await binary_sensors(CONF_HAS_MOVING_TARGET, hub.set_moving_target_binary_sensor)
|
||||
await binary_sensors(CONF_HAS_STILL_TARGET, hub.set_still_target_binary_sensor)
|
||||
await binary_sensors(
|
||||
CONF_OUT_PIN_PRESENCE_STATUS, hub.set_out_pin_presence_status_binary_sensor
|
||||
)
|
||||
|
||||
@@ -46,16 +46,8 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if factory_reset_config := config.get(CONF_FACTORY_RESET):
|
||||
b = await button.new_button(factory_reset_config)
|
||||
await cg.register_parented(b, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_factory_reset_button(b))
|
||||
if restart_config := config.get(CONF_RESTART):
|
||||
b = await button.new_button(restart_config)
|
||||
await cg.register_parented(b, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_restart_button(b))
|
||||
if query_params_config := config.get(CONF_QUERY_PARAMS):
|
||||
b = await button.new_button(query_params_config)
|
||||
await cg.register_parented(b, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_query_button(b))
|
||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
await buttons(CONF_QUERY_PARAMS, hub.set_query_button)
|
||||
|
||||
@@ -87,31 +87,32 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if timeout_config := config.get(CONF_TIMEOUT):
|
||||
n = await number.new_number(
|
||||
timeout_config, min_value=0, max_value=65535, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_timeout_number(n))
|
||||
if max_move_distance_gate_config := config.get(CONF_MAX_MOVE_DISTANCE_GATE):
|
||||
n = await number.new_number(
|
||||
max_move_distance_gate_config, min_value=2, max_value=8, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_max_move_distance_gate_number(n))
|
||||
if max_still_distance_gate_config := config.get(CONF_MAX_STILL_DISTANCE_GATE):
|
||||
n = await number.new_number(
|
||||
max_still_distance_gate_config, min_value=2, max_value=8, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_max_still_distance_gate_number(n))
|
||||
if light_threshold_config := config.get(CONF_LIGHT_THRESHOLD):
|
||||
n = await number.new_number(
|
||||
light_threshold_config, min_value=0, max_value=255, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_light_threshold_number(n))
|
||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
numbers = number.sub_numbers(config, parent=hub)
|
||||
await numbers(
|
||||
CONF_TIMEOUT, hub.set_timeout_number, min_value=0, max_value=65535, step=1
|
||||
)
|
||||
await numbers(
|
||||
CONF_MAX_MOVE_DISTANCE_GATE,
|
||||
hub.set_max_move_distance_gate_number,
|
||||
min_value=2,
|
||||
max_value=8,
|
||||
step=1,
|
||||
)
|
||||
await numbers(
|
||||
CONF_MAX_STILL_DISTANCE_GATE,
|
||||
hub.set_max_still_distance_gate_number,
|
||||
min_value=2,
|
||||
max_value=8,
|
||||
step=1,
|
||||
)
|
||||
await numbers(
|
||||
CONF_LIGHT_THRESHOLD,
|
||||
hub.set_light_threshold_number,
|
||||
min_value=0,
|
||||
max_value=255,
|
||||
step=1,
|
||||
)
|
||||
for x in range(9):
|
||||
if gate_conf := config.get(f"g{x}"):
|
||||
move_config = gate_conf[CONF_MOVE_THRESHOLD]
|
||||
@@ -119,13 +120,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
await number.register_number(
|
||||
n, move_config, min_value=0, max_value=100, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_gate_move_threshold_number(x, n))
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_move_threshold_number(x, n))
|
||||
|
||||
still_config = gate_conf[CONF_STILL_THRESHOLD]
|
||||
n = cg.new_Pvariable(still_config[CONF_ID], x)
|
||||
await number.register_number(
|
||||
n, still_config, min_value=0, max_value=100, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_gate_still_threshold_number(x, n))
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_still_threshold_number(x, n))
|
||||
|
||||
@@ -50,36 +50,32 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if distance_resolution_config := config.get(CONF_DISTANCE_RESOLUTION):
|
||||
s = await select.new_select(
|
||||
distance_resolution_config, options=["0.2m", "0.75m"]
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_distance_resolution_select(s))
|
||||
if out_pin_level_config := config.get(CONF_OUT_PIN_LEVEL):
|
||||
s = await select.new_select(out_pin_level_config, options=["low", "high"])
|
||||
await cg.register_parented(s, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_out_pin_level_select(s))
|
||||
if light_function_config := config.get(CONF_LIGHT_FUNCTION):
|
||||
s = await select.new_select(
|
||||
light_function_config, options=["off", "below", "above"]
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_light_function_select(s))
|
||||
if baud_rate_config := config.get(CONF_BAUD_RATE):
|
||||
s = await select.new_select(
|
||||
baud_rate_config,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_baud_rate_select(s))
|
||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
selects = select.sub_selects(config, parent=hub)
|
||||
await selects(
|
||||
CONF_DISTANCE_RESOLUTION,
|
||||
hub.set_distance_resolution_select,
|
||||
options=["0.2m", "0.75m"],
|
||||
)
|
||||
await selects(
|
||||
CONF_OUT_PIN_LEVEL, hub.set_out_pin_level_select, options=["low", "high"]
|
||||
)
|
||||
await selects(
|
||||
CONF_LIGHT_FUNCTION,
|
||||
hub.set_light_function_select,
|
||||
options=["off", "below", "above"],
|
||||
)
|
||||
await selects(
|
||||
CONF_BAUD_RATE,
|
||||
hub.set_baud_rate_select,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -157,30 +157,19 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if moving_distance_config := config.get(CONF_MOVING_DISTANCE):
|
||||
sens = await sensor.new_sensor(moving_distance_config)
|
||||
cg.add(ld2410_component.set_moving_target_distance_sensor(sens))
|
||||
if still_distance_config := config.get(CONF_STILL_DISTANCE):
|
||||
sens = await sensor.new_sensor(still_distance_config)
|
||||
cg.add(ld2410_component.set_still_target_distance_sensor(sens))
|
||||
if moving_energy_config := config.get(CONF_MOVING_ENERGY):
|
||||
sens = await sensor.new_sensor(moving_energy_config)
|
||||
cg.add(ld2410_component.set_moving_target_energy_sensor(sens))
|
||||
if still_energy_config := config.get(CONF_STILL_ENERGY):
|
||||
sens = await sensor.new_sensor(still_energy_config)
|
||||
cg.add(ld2410_component.set_still_target_energy_sensor(sens))
|
||||
if light_config := config.get(CONF_LIGHT):
|
||||
sens = await sensor.new_sensor(light_config)
|
||||
cg.add(ld2410_component.set_light_sensor(sens))
|
||||
if detection_distance_config := config.get(CONF_DETECTION_DISTANCE):
|
||||
sens = await sensor.new_sensor(detection_distance_config)
|
||||
cg.add(ld2410_component.set_detection_distance_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_MOVING_DISTANCE, hub.set_moving_target_distance_sensor)
|
||||
await sensors(CONF_STILL_DISTANCE, hub.set_still_target_distance_sensor)
|
||||
await sensors(CONF_MOVING_ENERGY, hub.set_moving_target_energy_sensor)
|
||||
await sensors(CONF_STILL_ENERGY, hub.set_still_target_energy_sensor)
|
||||
await sensors(CONF_LIGHT, hub.set_light_sensor)
|
||||
await sensors(CONF_DETECTION_DISTANCE, hub.set_detection_distance_sensor)
|
||||
for x in range(9):
|
||||
if gate_conf := config.get(f"g{x}"):
|
||||
if move_config := gate_conf.get(CONF_MOVE_ENERGY):
|
||||
sens = await sensor.new_sensor(move_config)
|
||||
cg.add(ld2410_component.set_gate_move_sensor(x, sens))
|
||||
cg.add(hub.set_gate_move_sensor(x, sens))
|
||||
if still_config := gate_conf.get(CONF_STILL_ENERGY):
|
||||
sens = await sensor.new_sensor(still_config)
|
||||
cg.add(ld2410_component.set_gate_still_sensor(x, sens))
|
||||
cg.add(hub.set_gate_still_sensor(x, sens))
|
||||
|
||||
@@ -37,12 +37,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if engineering_mode_config := config.get(CONF_ENGINEERING_MODE):
|
||||
s = await switch.new_switch(engineering_mode_config)
|
||||
await cg.register_parented(s, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_engineering_mode_switch(s))
|
||||
if bluetooth_config := config.get(CONF_BLUETOOTH):
|
||||
s = await switch.new_switch(bluetooth_config)
|
||||
await cg.register_parented(s, config[CONF_LD2410_ID])
|
||||
cg.add(ld2410_component.set_bluetooth_switch(s))
|
||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
switches = switch.sub_switches(config, parent=hub)
|
||||
await switches(CONF_ENGINEERING_MODE, hub.set_engineering_mode_switch)
|
||||
await switches(CONF_BLUETOOTH, hub.set_bluetooth_switch)
|
||||
|
||||
@@ -28,10 +28,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await text_sensor.new_text_sensor(version_config)
|
||||
cg.add(ld2410_component.set_version_text_sensor(sens))
|
||||
if mac_address_config := config.get(CONF_MAC_ADDRESS):
|
||||
sens = await text_sensor.new_text_sensor(mac_address_config)
|
||||
cg.add(ld2410_component.set_mac_text_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_VERSION, hub.set_version_text_sensor)
|
||||
await text_sensors(CONF_MAC_ADDRESS, hub.set_mac_text_sensor)
|
||||
|
||||
@@ -50,24 +50,12 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if dynamic_background_correction_status_config := config.get(
|
||||
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS
|
||||
):
|
||||
sens = await binary_sensor.new_binary_sensor(
|
||||
dynamic_background_correction_status_config
|
||||
)
|
||||
cg.add(
|
||||
LD2412_component.set_dynamic_background_correction_status_binary_sensor(
|
||||
sens
|
||||
)
|
||||
)
|
||||
if has_target_config := config.get(CONF_HAS_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_target_config)
|
||||
cg.add(LD2412_component.set_target_binary_sensor(sens))
|
||||
if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_moving_target_config)
|
||||
cg.add(LD2412_component.set_moving_target_binary_sensor(sens))
|
||||
if has_still_target_config := config.get(CONF_HAS_STILL_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_still_target_config)
|
||||
cg.add(LD2412_component.set_still_target_binary_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(
|
||||
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS,
|
||||
hub.set_dynamic_background_correction_status_binary_sensor,
|
||||
)
|
||||
await binary_sensors(CONF_HAS_TARGET, hub.set_target_binary_sensor)
|
||||
await binary_sensors(CONF_HAS_MOVING_TARGET, hub.set_moving_target_binary_sensor)
|
||||
await binary_sensors(CONF_HAS_STILL_TARGET, hub.set_still_target_binary_sensor)
|
||||
|
||||
@@ -56,22 +56,12 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if factory_reset_config := config.get(CONF_FACTORY_RESET):
|
||||
b = await button.new_button(factory_reset_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_factory_reset_button(b))
|
||||
if query_params_config := config.get(CONF_QUERY_PARAMS):
|
||||
b = await button.new_button(query_params_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_query_button(b))
|
||||
if restart_config := config.get(CONF_RESTART):
|
||||
b = await button.new_button(restart_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_restart_button(b))
|
||||
if start_dynamic_background_correction_config := config.get(
|
||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION
|
||||
):
|
||||
b = await button.new_button(start_dynamic_background_correction_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_start_dynamic_background_correction_button(b))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
await buttons(CONF_QUERY_PARAMS, hub.set_query_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
await buttons(
|
||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION,
|
||||
hub.set_start_dynamic_background_correction_button,
|
||||
)
|
||||
|
||||
@@ -87,42 +87,45 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if light_threshold_config := config.get(CONF_LIGHT_THRESHOLD):
|
||||
n = await number.new_number(
|
||||
light_threshold_config, min_value=0, max_value=255, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_light_threshold_number(n))
|
||||
if max_distance_gate_config := config.get(CONF_MAX_DISTANCE_GATE):
|
||||
n = await number.new_number(
|
||||
max_distance_gate_config, min_value=2, max_value=13, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_max_distance_gate_number(n))
|
||||
if min_distance_gate_config := config.get(CONF_MIN_DISTANCE_GATE):
|
||||
n = await number.new_number(
|
||||
min_distance_gate_config, min_value=1, max_value=12, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_min_distance_gate_number(n))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
numbers = number.sub_numbers(config, parent=hub)
|
||||
await numbers(
|
||||
CONF_LIGHT_THRESHOLD,
|
||||
hub.set_light_threshold_number,
|
||||
min_value=0,
|
||||
max_value=255,
|
||||
step=1,
|
||||
)
|
||||
await numbers(
|
||||
CONF_MAX_DISTANCE_GATE,
|
||||
hub.set_max_distance_gate_number,
|
||||
min_value=2,
|
||||
max_value=13,
|
||||
step=1,
|
||||
)
|
||||
await numbers(
|
||||
CONF_MIN_DISTANCE_GATE,
|
||||
hub.set_min_distance_gate_number,
|
||||
min_value=1,
|
||||
max_value=12,
|
||||
step=1,
|
||||
)
|
||||
for x in range(14):
|
||||
if gate_conf := config.get(f"gate_{x}"):
|
||||
move_config = gate_conf[CONF_MOVE_THRESHOLD]
|
||||
n = cg.new_Pvariable(move_config[CONF_ID])
|
||||
await number.register_number(
|
||||
n, move_config, min_value=0, max_value=100, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_gate_move_threshold_number(x, n))
|
||||
n = await number.new_number(move_config, min_value=0, max_value=100, step=1)
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_move_threshold_number(x, n))
|
||||
still_config = gate_conf[CONF_STILL_THRESHOLD]
|
||||
n = cg.new_Pvariable(still_config[CONF_ID])
|
||||
await number.register_number(
|
||||
n, still_config, min_value=0, max_value=100, step=1
|
||||
n = await number.new_number(
|
||||
still_config, min_value=0, max_value=100, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_gate_still_threshold_number(x, n))
|
||||
if timeout_config := config.get(CONF_TIMEOUT):
|
||||
n = await number.new_number(timeout_config, min_value=0, max_value=900, step=1)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_timeout_number(n))
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_still_threshold_number(x, n))
|
||||
await numbers(
|
||||
CONF_TIMEOUT,
|
||||
hub.set_timeout_number,
|
||||
min_value=0,
|
||||
max_value=900,
|
||||
step=1,
|
||||
)
|
||||
|
||||
@@ -50,36 +50,34 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if baud_rate_config := config.get(CONF_BAUD_RATE):
|
||||
s = await select.new_select(
|
||||
baud_rate_config,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_baud_rate_select(s))
|
||||
if distance_resolution_config := config.get(CONF_DISTANCE_RESOLUTION):
|
||||
s = await select.new_select(
|
||||
distance_resolution_config, options=["0.2m", "0.5m", "0.75m"]
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_distance_resolution_select(s))
|
||||
if light_function_config := config.get(CONF_LIGHT_FUNCTION):
|
||||
s = await select.new_select(
|
||||
light_function_config, options=["off", "below", "above"]
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_light_function_select(s))
|
||||
if out_pin_level_config := config.get(CONF_OUT_PIN_LEVEL):
|
||||
s = await select.new_select(out_pin_level_config, options=["low", "high"])
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_out_pin_level_select(s))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
selects = select.sub_selects(config, parent=hub)
|
||||
await selects(
|
||||
CONF_BAUD_RATE,
|
||||
hub.set_baud_rate_select,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
await selects(
|
||||
CONF_DISTANCE_RESOLUTION,
|
||||
hub.set_distance_resolution_select,
|
||||
options=["0.2m", "0.5m", "0.75m"],
|
||||
)
|
||||
await selects(
|
||||
CONF_LIGHT_FUNCTION,
|
||||
hub.set_light_function_select,
|
||||
options=["off", "below", "above"],
|
||||
)
|
||||
await selects(
|
||||
CONF_OUT_PIN_LEVEL,
|
||||
hub.set_out_pin_level_select,
|
||||
options=["low", "high"],
|
||||
)
|
||||
|
||||
@@ -158,30 +158,19 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if detection_distance_config := config.get(CONF_DETECTION_DISTANCE):
|
||||
sens = await sensor.new_sensor(detection_distance_config)
|
||||
cg.add(LD2412_component.set_detection_distance_sensor(sens))
|
||||
if light_config := config.get(CONF_LIGHT):
|
||||
sens = await sensor.new_sensor(light_config)
|
||||
cg.add(LD2412_component.set_light_sensor(sens))
|
||||
if moving_distance_config := config.get(CONF_MOVING_DISTANCE):
|
||||
sens = await sensor.new_sensor(moving_distance_config)
|
||||
cg.add(LD2412_component.set_moving_target_distance_sensor(sens))
|
||||
if moving_energy_config := config.get(CONF_MOVING_ENERGY):
|
||||
sens = await sensor.new_sensor(moving_energy_config)
|
||||
cg.add(LD2412_component.set_moving_target_energy_sensor(sens))
|
||||
if still_distance_config := config.get(CONF_STILL_DISTANCE):
|
||||
sens = await sensor.new_sensor(still_distance_config)
|
||||
cg.add(LD2412_component.set_still_target_distance_sensor(sens))
|
||||
if still_energy_config := config.get(CONF_STILL_ENERGY):
|
||||
sens = await sensor.new_sensor(still_energy_config)
|
||||
cg.add(LD2412_component.set_still_target_energy_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_DETECTION_DISTANCE, hub.set_detection_distance_sensor)
|
||||
await sensors(CONF_LIGHT, hub.set_light_sensor)
|
||||
await sensors(CONF_MOVING_DISTANCE, hub.set_moving_target_distance_sensor)
|
||||
await sensors(CONF_MOVING_ENERGY, hub.set_moving_target_energy_sensor)
|
||||
await sensors(CONF_STILL_DISTANCE, hub.set_still_target_distance_sensor)
|
||||
await sensors(CONF_STILL_ENERGY, hub.set_still_target_energy_sensor)
|
||||
for x in range(14):
|
||||
if gate_conf := config.get(f"gate_{x}"):
|
||||
if move_config := gate_conf.get(CONF_MOVE_ENERGY):
|
||||
sens = await sensor.new_sensor(move_config)
|
||||
cg.add(LD2412_component.set_gate_move_sensor(x, sens))
|
||||
cg.add(hub.set_gate_move_sensor(x, sens))
|
||||
if still_config := gate_conf.get(CONF_STILL_ENERGY):
|
||||
sens = await sensor.new_sensor(still_config)
|
||||
cg.add(LD2412_component.set_gate_still_sensor(x, sens))
|
||||
cg.add(hub.set_gate_still_sensor(x, sens))
|
||||
|
||||
@@ -37,12 +37,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if bluetooth_config := config.get(CONF_BLUETOOTH):
|
||||
s = await switch.new_switch(bluetooth_config)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_bluetooth_switch(s))
|
||||
if engineering_mode_config := config.get(CONF_ENGINEERING_MODE):
|
||||
s = await switch.new_switch(engineering_mode_config)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_engineering_mode_switch(s))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
switches = switch.sub_switches(config, parent=hub)
|
||||
await switches(CONF_BLUETOOTH, hub.set_bluetooth_switch)
|
||||
await switches(CONF_ENGINEERING_MODE, hub.set_engineering_mode_switch)
|
||||
|
||||
@@ -28,10 +28,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await text_sensor.new_text_sensor(version_config)
|
||||
cg.add(LD2412_component.set_version_text_sensor(sens))
|
||||
if mac_address_config := config.get(CONF_MAC_ADDRESS):
|
||||
sens = await text_sensor.new_text_sensor(mac_address_config)
|
||||
cg.add(LD2412_component.set_mac_text_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_VERSION, hub.set_version_text_sensor)
|
||||
await text_sensors(CONF_MAC_ADDRESS, hub.set_mac_text_sensor)
|
||||
|
||||
@@ -27,8 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if CONF_HAS_TARGET in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET])
|
||||
cg.add(var.set_presence_sensor(sens))
|
||||
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
cg.add(ld2420.register_listener(var))
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(CONF_HAS_TARGET, var.set_presence_sensor)
|
||||
hub = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
cg.add(hub.register_listener(var))
|
||||
|
||||
@@ -52,20 +52,9 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2420_component = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
if apply_config := config.get(CONF_APPLY_CONFIG):
|
||||
b = await button.new_button(apply_config)
|
||||
await cg.register_parented(b, config[CONF_LD2420_ID])
|
||||
cg.add(ld2420_component.set_apply_config_button(b))
|
||||
if revert_config := config.get(CONF_REVERT_CONFIG):
|
||||
b = await button.new_button(revert_config)
|
||||
await cg.register_parented(b, config[CONF_LD2420_ID])
|
||||
cg.add(ld2420_component.set_revert_config_button(b))
|
||||
if restart_config := config.get(CONF_RESTART_MODULE):
|
||||
b = await button.new_button(restart_config)
|
||||
await cg.register_parented(b, config[CONF_LD2420_ID])
|
||||
cg.add(ld2420_component.set_restart_module_button(b))
|
||||
if factory_reset := config.get(CONF_FACTORY_RESET):
|
||||
b = await button.new_button(factory_reset)
|
||||
await cg.register_parented(b, config[CONF_LD2420_ID])
|
||||
cg.add(ld2420_component.set_factory_reset_button(b))
|
||||
hub = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_APPLY_CONFIG, hub.set_apply_config_button)
|
||||
await buttons(CONF_REVERT_CONFIG, hub.set_revert_config_button)
|
||||
await buttons(CONF_RESTART_MODULE, hub.set_restart_module_button)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
|
||||
@@ -115,56 +115,65 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2420_component = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
if gate_timeout_config := config.get(CONF_PRESENCE_TIMEOUT):
|
||||
n = await number.new_number(
|
||||
gate_timeout_config, min_value=0, max_value=255, step=5
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_timeout_number(n))
|
||||
if min_distance_gate_config := config.get(CONF_MIN_GATE_DISTANCE):
|
||||
n = await number.new_number(
|
||||
min_distance_gate_config, min_value=0, max_value=15, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_min_gate_distance_number(n))
|
||||
if max_distance_gate_config := config.get(CONF_MAX_GATE_DISTANCE):
|
||||
n = await number.new_number(
|
||||
max_distance_gate_config, min_value=1, max_value=15, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_max_gate_distance_number(n))
|
||||
if gate_move_sensitivity_config := config.get(CONF_GATE_MOVE_SENSITIVITY):
|
||||
n = await number.new_number(
|
||||
gate_move_sensitivity_config, min_value=0.05, max_value=1, step=0.025
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_move_sensitivity_factor_number(n))
|
||||
if gate_still_sensitivity_config := config.get(CONF_GATE_STILL_SENSITIVITY):
|
||||
n = await number.new_number(
|
||||
gate_still_sensitivity_config, min_value=0.05, max_value=1, step=0.025
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_still_sensitivity_factor_number(n))
|
||||
hub = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
numbers = number.sub_numbers(config, parent=hub)
|
||||
await numbers(
|
||||
CONF_PRESENCE_TIMEOUT,
|
||||
hub.set_gate_timeout_number,
|
||||
min_value=0,
|
||||
max_value=255,
|
||||
step=5,
|
||||
)
|
||||
await numbers(
|
||||
CONF_MIN_GATE_DISTANCE,
|
||||
hub.set_min_gate_distance_number,
|
||||
min_value=0,
|
||||
max_value=15,
|
||||
step=1,
|
||||
)
|
||||
await numbers(
|
||||
CONF_MAX_GATE_DISTANCE,
|
||||
hub.set_max_gate_distance_number,
|
||||
min_value=1,
|
||||
max_value=15,
|
||||
step=1,
|
||||
)
|
||||
await numbers(
|
||||
CONF_GATE_MOVE_SENSITIVITY,
|
||||
hub.set_gate_move_sensitivity_factor_number,
|
||||
min_value=0.05,
|
||||
max_value=1,
|
||||
step=0.025,
|
||||
)
|
||||
await numbers(
|
||||
CONF_GATE_STILL_SENSITIVITY,
|
||||
hub.set_gate_still_sensitivity_factor_number,
|
||||
min_value=0.05,
|
||||
max_value=1,
|
||||
step=0.025,
|
||||
)
|
||||
if config.get(CONF_GATE_SELECT):
|
||||
if gate_number := config.get(CONF_GATE_SELECT):
|
||||
n = await number.new_number(gate_number, min_value=0, max_value=15, step=1)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_select_number(n))
|
||||
await numbers(
|
||||
CONF_GATE_SELECT,
|
||||
hub.set_gate_select_number,
|
||||
min_value=0,
|
||||
max_value=15,
|
||||
step=1,
|
||||
)
|
||||
if gate_still_threshold := config.get(CONF_STILL_THRESHOLD):
|
||||
n = cg.new_Pvariable(gate_still_threshold[CONF_ID])
|
||||
await number.register_number(
|
||||
n, gate_still_threshold, min_value=0, max_value=65535, step=25
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_still_threshold_numbers(0, n))
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_still_threshold_numbers(0, n))
|
||||
if gate_move_threshold := config.get(CONF_MOVE_THRESHOLD):
|
||||
n = cg.new_Pvariable(gate_move_threshold[CONF_ID])
|
||||
await number.register_number(
|
||||
n, gate_move_threshold, min_value=0, max_value=65535, step=25
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_move_threshold_numbers(0, n))
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_move_threshold_numbers(0, n))
|
||||
else:
|
||||
for x in range(16):
|
||||
if gate_conf := config.get(f"gate_{x}"):
|
||||
@@ -173,13 +182,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
await number.register_number(
|
||||
n, move_config, min_value=0, max_value=65535, step=25
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_move_threshold_numbers(x, n))
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_move_threshold_numbers(x, n))
|
||||
|
||||
still_config = gate_conf[CONF_STILL_THRESHOLD]
|
||||
n = cg.new_Pvariable(still_config[CONF_ID], x)
|
||||
await number.register_number(
|
||||
n, still_config, min_value=0, max_value=65535, step=25
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_still_threshold_numbers(x, n))
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_still_threshold_numbers(x, n))
|
||||
|
||||
@@ -25,11 +25,8 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2420_component = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
if operating_mode_config := config.get(CONF_OPERATING_MODE):
|
||||
sel = await select.new_select(
|
||||
operating_mode_config,
|
||||
options=CONF_SELECTS,
|
||||
)
|
||||
await cg.register_parented(sel, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_operating_mode_select(sel))
|
||||
hub = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
selects = select.sub_selects(config, parent=hub)
|
||||
await selects(
|
||||
CONF_OPERATING_MODE, hub.set_operating_mode_select, options=CONF_SELECTS
|
||||
)
|
||||
|
||||
@@ -34,11 +34,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if CONF_MOVING_DISTANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE])
|
||||
cg.add(var.set_distance_sensor(sens))
|
||||
if CONF_GATE_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_GATE_ENERGY])
|
||||
cg.add(var.set_energy_sensor(sens))
|
||||
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
cg.add(ld2420.register_listener(var))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_MOVING_DISTANCE, var.set_distance_sensor)
|
||||
await sensors(CONF_GATE_ENERGY, var.set_energy_sensor)
|
||||
hub = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
cg.add(hub.register_listener(var))
|
||||
|
||||
@@ -28,8 +28,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if CONF_FW_VERSION in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_FW_VERSION])
|
||||
cg.add(var.set_fw_version_text_sensor(sens))
|
||||
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
cg.add(ld2420.register_listener(var))
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_FW_VERSION, var.set_fw_version_text_sensor)
|
||||
hub = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
cg.add(hub.register_listener(var))
|
||||
|
||||
@@ -41,13 +41,8 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if has_target_config := config.get(CONF_HAS_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_target_config)
|
||||
cg.add(ld2450_component.set_target_binary_sensor(sens))
|
||||
if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_moving_target_config)
|
||||
cg.add(ld2450_component.set_moving_target_binary_sensor(sens))
|
||||
if has_still_target_config := config.get(CONF_HAS_STILL_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_still_target_config)
|
||||
cg.add(ld2450_component.set_still_target_binary_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(CONF_HAS_TARGET, hub.set_target_binary_sensor)
|
||||
await binary_sensors(CONF_HAS_MOVING_TARGET, hub.set_moving_target_binary_sensor)
|
||||
await binary_sensors(CONF_HAS_STILL_TARGET, hub.set_still_target_binary_sensor)
|
||||
|
||||
@@ -37,12 +37,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if factory_reset_config := config.get(CONF_FACTORY_RESET):
|
||||
b = await button.new_button(factory_reset_config)
|
||||
await cg.register_parented(b, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_factory_reset_button(b))
|
||||
if restart_config := config.get(CONF_RESTART):
|
||||
b = await button.new_button(restart_config)
|
||||
await cg.register_parented(b, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_restart_button(b))
|
||||
hub = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
|
||||
@@ -80,16 +80,15 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if presence_timeout_config := config.get(CONF_PRESENCE_TIMEOUT):
|
||||
n = await number.new_number(
|
||||
presence_timeout_config,
|
||||
min_value=0,
|
||||
max_value=3600,
|
||||
step=1,
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_presence_timeout_number(n))
|
||||
hub = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
numbers = number.sub_numbers(config, parent=hub)
|
||||
await numbers(
|
||||
CONF_PRESENCE_TIMEOUT,
|
||||
hub.set_presence_timeout_number,
|
||||
min_value=0,
|
||||
max_value=3600,
|
||||
step=1,
|
||||
)
|
||||
for zone_num in range(MAX_ZONES):
|
||||
if zone_conf := config.get(f"zone_{zone_num + 1}"):
|
||||
zone_x1_config = zone_conf.get(CONF_X1)
|
||||
@@ -97,27 +96,27 @@ async def to_code(config: ConfigType) -> None:
|
||||
await number.register_number(
|
||||
x1, zone_x1_config, min_value=-4860, max_value=4860, step=1
|
||||
)
|
||||
await cg.register_parented(x1, config[CONF_LD2450_ID])
|
||||
await cg.register_parented(x1, hub)
|
||||
|
||||
zone_y1_config = zone_conf.get(CONF_Y1)
|
||||
y1 = cg.new_Pvariable(zone_y1_config[CONF_ID], zone_num)
|
||||
await number.register_number(
|
||||
y1, zone_y1_config, min_value=0, max_value=7560, step=1
|
||||
)
|
||||
await cg.register_parented(y1, config[CONF_LD2450_ID])
|
||||
await cg.register_parented(y1, hub)
|
||||
|
||||
zone_x2_config = zone_conf.get(CONF_X2)
|
||||
x2 = cg.new_Pvariable(zone_x2_config[CONF_ID], zone_num)
|
||||
await number.register_number(
|
||||
x2, zone_x2_config, min_value=-4860, max_value=4860, step=1
|
||||
)
|
||||
await cg.register_parented(x2, config[CONF_LD2450_ID])
|
||||
await cg.register_parented(x2, hub)
|
||||
|
||||
zone_y2_config = zone_conf.get(CONF_Y2)
|
||||
y2 = cg.new_Pvariable(zone_y2_config[CONF_ID], zone_num)
|
||||
await number.register_number(
|
||||
y2, zone_y2_config, min_value=0, max_value=7560, step=1
|
||||
)
|
||||
await cg.register_parented(y2, config[CONF_LD2450_ID])
|
||||
await cg.register_parented(y2, hub)
|
||||
|
||||
cg.add(ld2450_component.set_zone_numbers(zone_num, x1, y1, x2, y2))
|
||||
cg.add(hub.set_zone_numbers(zone_num, x1, y1, x2, y2))
|
||||
|
||||
@@ -33,31 +33,28 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if baud_rate_config := config.get(CONF_BAUD_RATE):
|
||||
s = await select.new_select(
|
||||
baud_rate_config,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_baud_rate_select(s))
|
||||
if zone_type_config := config.get(CONF_ZONE_TYPE):
|
||||
s = await select.new_select(
|
||||
zone_type_config,
|
||||
options=[
|
||||
"Disabled",
|
||||
"Detection",
|
||||
"Filter",
|
||||
],
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_zone_type_select(s))
|
||||
hub = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
selects = select.sub_selects(config, parent=hub)
|
||||
await selects(
|
||||
CONF_BAUD_RATE,
|
||||
hub.set_baud_rate_select,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
await selects(
|
||||
CONF_ZONE_TYPE,
|
||||
hub.set_zone_type_select,
|
||||
options=[
|
||||
"Disabled",
|
||||
"Detection",
|
||||
"Filter",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -228,47 +228,40 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
hub = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
|
||||
if target_count_config := config.get(CONF_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(target_count_config)
|
||||
cg.add(ld2450_component.set_target_count_sensor(sens))
|
||||
|
||||
if still_target_count_config := config.get(CONF_STILL_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(still_target_count_config)
|
||||
cg.add(ld2450_component.set_still_target_count_sensor(sens))
|
||||
|
||||
if moving_target_count_config := config.get(CONF_MOVING_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(moving_target_count_config)
|
||||
cg.add(ld2450_component.set_moving_target_count_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TARGET_COUNT, hub.set_target_count_sensor)
|
||||
await sensors(CONF_STILL_TARGET_COUNT, hub.set_still_target_count_sensor)
|
||||
await sensors(CONF_MOVING_TARGET_COUNT, hub.set_moving_target_count_sensor)
|
||||
for n in range(MAX_TARGETS):
|
||||
if target_conf := config.get(f"target_{n + 1}"):
|
||||
if x_config := target_conf.get(CONF_X):
|
||||
sens = await sensor.new_sensor(x_config)
|
||||
cg.add(ld2450_component.set_move_x_sensor(n, sens))
|
||||
cg.add(hub.set_move_x_sensor(n, sens))
|
||||
if y_config := target_conf.get(CONF_Y):
|
||||
sens = await sensor.new_sensor(y_config)
|
||||
cg.add(ld2450_component.set_move_y_sensor(n, sens))
|
||||
cg.add(hub.set_move_y_sensor(n, sens))
|
||||
if speed_config := target_conf.get(CONF_SPEED):
|
||||
sens = await sensor.new_sensor(speed_config)
|
||||
cg.add(ld2450_component.set_move_speed_sensor(n, sens))
|
||||
cg.add(hub.set_move_speed_sensor(n, sens))
|
||||
if angle_config := target_conf.get(CONF_ANGLE):
|
||||
sens = await sensor.new_sensor(angle_config)
|
||||
cg.add(ld2450_component.set_move_angle_sensor(n, sens))
|
||||
cg.add(hub.set_move_angle_sensor(n, sens))
|
||||
if distance_config := target_conf.get(CONF_DISTANCE):
|
||||
sens = await sensor.new_sensor(distance_config)
|
||||
cg.add(ld2450_component.set_move_distance_sensor(n, sens))
|
||||
cg.add(hub.set_move_distance_sensor(n, sens))
|
||||
if resolution_config := target_conf.get(CONF_RESOLUTION):
|
||||
sens = await sensor.new_sensor(resolution_config)
|
||||
cg.add(ld2450_component.set_move_resolution_sensor(n, sens))
|
||||
cg.add(hub.set_move_resolution_sensor(n, sens))
|
||||
for n in range(MAX_ZONES):
|
||||
if zone_config := config.get(f"zone_{n + 1}"):
|
||||
if target_count_config := zone_config.get(CONF_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(target_count_config)
|
||||
cg.add(ld2450_component.set_zone_target_count_sensor(n, sens))
|
||||
cg.add(hub.set_zone_target_count_sensor(n, sens))
|
||||
if still_target_count_config := zone_config.get(CONF_STILL_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(still_target_count_config)
|
||||
cg.add(ld2450_component.set_zone_still_target_count_sensor(n, sens))
|
||||
cg.add(hub.set_zone_still_target_count_sensor(n, sens))
|
||||
if moving_target_count_config := zone_config.get(CONF_MOVING_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(moving_target_count_config)
|
||||
cg.add(ld2450_component.set_zone_moving_target_count_sensor(n, sens))
|
||||
cg.add(hub.set_zone_moving_target_count_sensor(n, sens))
|
||||
|
||||
@@ -37,12 +37,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if bluetooth_config := config.get(CONF_BLUETOOTH):
|
||||
s = await switch.new_switch(bluetooth_config)
|
||||
await cg.register_parented(s, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_bluetooth_switch(s))
|
||||
if multi_target_config := config.get(CONF_MULTI_TARGET):
|
||||
s = await switch.new_switch(multi_target_config)
|
||||
await cg.register_parented(s, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_multi_target_switch(s))
|
||||
hub = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
switches = switch.sub_switches(config, parent=hub)
|
||||
await switches(CONF_BLUETOOTH, hub.set_bluetooth_switch)
|
||||
await switches(CONF_MULTI_TARGET, hub.set_multi_target_switch)
|
||||
|
||||
@@ -51,16 +51,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await text_sensor.new_text_sensor(version_config)
|
||||
cg.add(ld2450_component.set_version_text_sensor(sens))
|
||||
if mac_address_config := config.get(CONF_MAC_ADDRESS):
|
||||
sens = await text_sensor.new_text_sensor(mac_address_config)
|
||||
cg.add(ld2450_component.set_mac_text_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_VERSION, hub.set_version_text_sensor)
|
||||
await text_sensors(CONF_MAC_ADDRESS, hub.set_mac_text_sensor)
|
||||
for n in range(MAX_TARGETS):
|
||||
if (direction_conf := config.get(f"target_{n + 1}")) and (
|
||||
direction_config := direction_conf.get(CONF_DIRECTION)
|
||||
):
|
||||
sens = await text_sensor.new_text_sensor(direction_config)
|
||||
cg.add(ld2450_component.set_direction_text_sensor(n, sens))
|
||||
cg.add(hub.set_direction_text_sensor(n, sens))
|
||||
|
||||
@@ -70,6 +70,7 @@ from .types import ( # noqa: F401
|
||||
AddressableLightState,
|
||||
ChannelColors,
|
||||
ColorMode,
|
||||
GammaTable,
|
||||
LightOutput,
|
||||
LightState,
|
||||
LightStateRTCState,
|
||||
@@ -85,6 +86,8 @@ CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
DOMAIN = "light"
|
||||
CONF_GAMMA_TABLE_ID = "gamma_table_id"
|
||||
CONF_RESUME_EFFECT = "resume_effect"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -140,18 +143,29 @@ def generate_gamma_table(gamma_correct: float) -> list[HexInt]:
|
||||
return [HexInt(int(round(i / 255.0 * 65535))) for i in range(256)]
|
||||
|
||||
|
||||
def _get_or_create_gamma_table(gamma_correct):
|
||||
def gamma_table_initializer(gamma_correct: float) -> str:
|
||||
"""C++ initializer for a light::GammaTable: the lookup table, then gamma * 100."""
|
||||
lut = ", ".join(f"0x{int(v):04X}" for v in generate_gamma_table(gamma_correct))
|
||||
# gamma_x100 is a uint16_t; platforms that redefine gamma_correct leave it unbounded, so saturate here
|
||||
return f"{{{{{lut}}}, {min(0xFFFF, round(gamma_correct * 100))}}}"
|
||||
|
||||
|
||||
def _get_or_create_gamma_table(gamma_correct: float, table_id: ID) -> cg.RawExpression:
|
||||
data = _get_data()
|
||||
if gamma_correct in data.gamma_tables:
|
||||
return data.gamma_tables[gamma_correct]
|
||||
|
||||
forward = generate_gamma_table(gamma_correct)
|
||||
|
||||
gamma_str = f"{gamma_correct}".replace(".", "_")
|
||||
fwd_id = ID(f"gamma_{gamma_str}_fwd", is_declaration=True, type=cg.uint16)
|
||||
fwd_arr = cg.progmem_array(fwd_id, forward)
|
||||
data.gamma_tables[gamma_correct] = fwd_arr
|
||||
return fwd_arr
|
||||
# table_id is generated and resolved against every declared ID, so it can't collide with a
|
||||
# YAML ID; lights sharing a gamma reuse the first light's table.
|
||||
cg.add(
|
||||
cg.RawStatement(
|
||||
f"static constexpr light::GammaTable {table_id} PROGMEM = "
|
||||
f"{gamma_table_initializer(gamma_correct)};"
|
||||
)
|
||||
)
|
||||
table = cg.RawExpression(f"&{table_id}")
|
||||
data.gamma_tables[gamma_correct] = table
|
||||
return table
|
||||
|
||||
|
||||
def find_effect_index(effects: list, effect_name: str) -> int | None:
|
||||
@@ -357,6 +371,7 @@ LIGHT_SCHEMA = (
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LightState),
|
||||
cv.GenerateID(CONF_GAMMA_TABLE_ID): cv.declare_id(GammaTable),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTJSONLightComponent
|
||||
),
|
||||
@@ -380,6 +395,7 @@ LIGHT_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INITIAL_STATE): LIGHT_STATE_SCHEMA,
|
||||
cv.Optional(CONF_RESUME_EFFECT, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -510,6 +526,9 @@ async def setup_light_core_(light_var, config, output_var):
|
||||
# runtime actually understands.
|
||||
initial_state_config = config.get(CONF_INITIAL_STATE)
|
||||
initial_statements = await _initial_state_statements(initial_state_config)
|
||||
if config[CONF_RESUME_EFFECT]:
|
||||
cg.add_define("USE_LIGHT_RESUME_EFFECT")
|
||||
cg.add(light_var.set_resume_effect(True))
|
||||
|
||||
restore_mode = config.get(CONF_RESTORE_MODE)
|
||||
restore_state_config = config.get(CONF_RESTORE_STATE)
|
||||
@@ -553,14 +572,14 @@ async def setup_light_core_(light_var, config, output_var):
|
||||
) is not None and flash_transition_length != cv.time_period(
|
||||
DEFAULT_FLASH_TRANSITION_LENGTH
|
||||
):
|
||||
cg.add_define("USE_LIGHT_FLASH_TRANSITION_LENGTH")
|
||||
cg.add(light_var.set_flash_transition_length(flash_transition_length))
|
||||
# Setting an interval opts this light in and compiles the feature in
|
||||
if (interval := config.get(CONF_TRANSITION_STATE_PUBLISH_INTERVAL)) is not None:
|
||||
cg.add(light_var.set_transition_state_publish_interval(interval))
|
||||
cg.add_define("USE_LIGHT_TRANSITION_PUBLISH_INTERVAL")
|
||||
if (gamma_correct := config.get(CONF_GAMMA_CORRECT)) is not None:
|
||||
cg.add(light_var.set_gamma_correct(gamma_correct))
|
||||
fwd_arr = _get_or_create_gamma_table(gamma_correct)
|
||||
fwd_arr = _get_or_create_gamma_table(gamma_correct, config[CONF_GAMMA_TABLE_ID])
|
||||
cg.add(light_var.set_gamma_table(fwd_arr))
|
||||
cg.add_define("USE_LIGHT_GAMMA_LUT")
|
||||
effects = await cg.build_registry_list(
|
||||
|
||||
@@ -545,12 +545,18 @@ async def addressable_flicker_effect_to_code(config, effect_id):
|
||||
return var
|
||||
|
||||
|
||||
# LightState stores the active effect index in a uint16_t
|
||||
MAX_EFFECTS = 65535
|
||||
|
||||
|
||||
def validate_effects(allowed_effects):
|
||||
@schema_extractor("effects")
|
||||
def validator(value):
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return (allowed_effects, EFFECTS_REGISTRY)
|
||||
|
||||
if isinstance(value, list) and len(value) > MAX_EFFECTS:
|
||||
raise cv.Invalid(f"A light supports at most {MAX_EFFECTS} effects")
|
||||
value = cv.validate_registry("effect", EFFECTS_REGISTRY)(value)
|
||||
errors = []
|
||||
names = set()
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
namespace esphome::light {
|
||||
|
||||
/// A gamma curve as codegen emits it into PROGMEM: the lookup table and the gamma it was built from
|
||||
struct GammaTable {
|
||||
uint16_t lut[256];
|
||||
uint16_t gamma_x100;
|
||||
};
|
||||
|
||||
/// Binary search a monotonically increasing uint16[256] PROGMEM table.
|
||||
/// Returns the largest index where table[index] <= target.
|
||||
inline uint8_t gamma_table_reverse_search(const uint16_t *table, uint16_t target) {
|
||||
|
||||
@@ -193,6 +193,11 @@ LightColorValues LightCall::validate_() {
|
||||
auto *name = this->parent_->get_name().c_str();
|
||||
auto traits = this->parent_->get_traits();
|
||||
|
||||
#ifdef USE_LIGHT_RESUME_EFFECT
|
||||
// Snapshot before the adjustments below add flags of their own
|
||||
const bool plain_turn_on = this->has_state() && this->state_ && (this->flags_ & ~STATE_ONLY_FLAGS_MASK) == 0;
|
||||
#endif // USE_LIGHT_RESUME_EFFECT
|
||||
|
||||
// Color mode check
|
||||
if (this->has_color_mode() && !traits.supports_color_mode(this->color_mode_)) {
|
||||
ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_)));
|
||||
@@ -334,6 +339,15 @@ LightColorValues LightCall::validate_() {
|
||||
// validate transition length/flash length/effect not used at the same time
|
||||
bool supports_transition = color_mode & ColorCapability::BRIGHTNESS;
|
||||
|
||||
#ifdef USE_LIGHT_RESUME_EFFECT
|
||||
// A plain turn-on from off brings back the effect that was running when the light was turned off
|
||||
if (this->parent_->resume_effect_ && plain_turn_on && !this->parent_->remote_values.is_on() &&
|
||||
this->parent_->previous_effect_index_ != 0) {
|
||||
this->effect_ = this->parent_->previous_effect_index_;
|
||||
this->set_flag_(FLAG_HAS_EFFECT);
|
||||
}
|
||||
#endif // USE_LIGHT_RESUME_EFFECT
|
||||
|
||||
// If effect is already active, remove effect start
|
||||
if (this->has_effect_() && this->effect_ == this->parent_->active_effect_index_) {
|
||||
this->clear_flag_(FLAG_HAS_EFFECT);
|
||||
@@ -376,6 +390,11 @@ LightColorValues LightCall::validate_() {
|
||||
// Reason: When user turns off the light in frontend, the effect should also stop
|
||||
bool target_state = this->has_state() ? this->state_ : v.is_on();
|
||||
if (!this->has_flash_() && !target_state) {
|
||||
#ifdef USE_LIGHT_RESUME_EFFECT
|
||||
// Remember what was running, including no effect, when a lit light is explicitly turned off
|
||||
if (this->parent_->resume_effect_ && explicit_turn_off_request && this->parent_->remote_values.is_on())
|
||||
this->parent_->previous_effect_index_ = this->parent_->active_effect_index_;
|
||||
#endif // USE_LIGHT_RESUME_EFFECT
|
||||
if (this->has_effect_()) {
|
||||
log_invalid_parameter(name, LOG_STR("cannot start effect when turning off"));
|
||||
this->clear_flag_(FLAG_HAS_EFFECT);
|
||||
|
||||
@@ -215,6 +215,8 @@ class LightCall {
|
||||
FLAG_SAVE = 1 << 15,
|
||||
};
|
||||
static constexpr uint16_t CLAMP_FLAGS_MASK = 0x00FFu; // bits 0-7
|
||||
// Flags a plain turn-on may carry; any other flag means the caller asked for something specific
|
||||
static constexpr uint16_t STATE_ONLY_FLAGS_MASK = FLAG_HAS_STATE | FLAG_PUBLISH | FLAG_SAVE;
|
||||
|
||||
inline bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; }
|
||||
inline bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; }
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "light_output.h"
|
||||
#include "transformers.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace esphome::light {
|
||||
|
||||
static const char *const TAG = "light";
|
||||
@@ -94,10 +96,17 @@ void LightState::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str());
|
||||
auto traits = this->get_traits();
|
||||
if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) {
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
// Read the stored gamma * 100 directly so dump_config does not pull in get_gamma_correct()
|
||||
const unsigned gamma_x100 =
|
||||
this->gamma_table_ != nullptr ? progmem_read_uint16(&this->gamma_table_->gamma_x100) : 0;
|
||||
#else
|
||||
const unsigned gamma_x100 = 0;
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Default Transition Length: %.1fs\n"
|
||||
" Gamma Correct: %.2f",
|
||||
this->default_transition_length_ / 1e3f, this->gamma_correct_);
|
||||
" Gamma Correct: %u.%02u",
|
||||
this->default_transition_length_ / 1e3f, gamma_x100 / 100, gamma_x100 % 100);
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
// The define is build wide; only lights that set the option have an interval
|
||||
if (this->transition_state_publish_interval_ != 0) {
|
||||
@@ -296,6 +305,14 @@ void LightState::current_values_as_ct(float *color_temperature, float *white_bri
|
||||
*white_brightness = this->gamma_correct_lut(*white_brightness);
|
||||
}
|
||||
|
||||
float LightState::get_gamma_correct() const {
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
if (this->gamma_table_ != nullptr)
|
||||
return progmem_read_uint16(&this->gamma_table_->gamma_x100) * 0.01f;
|
||||
#endif // USE_LIGHT_GAMMA_LUT
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
float LightState::gamma_correct_lut(float value) const {
|
||||
if (value <= 0.0f)
|
||||
@@ -307,10 +324,10 @@ float LightState::gamma_correct_lut(float value) const {
|
||||
float scaled = value * 255.0f;
|
||||
auto idx = static_cast<uint8_t>(scaled);
|
||||
if (idx >= 255)
|
||||
return progmem_read_uint16(&this->gamma_table_[255]) / 65535.0f;
|
||||
return progmem_read_uint16(&this->gamma_table_->lut[255]) / 65535.0f;
|
||||
float frac = scaled - idx;
|
||||
float a = progmem_read_uint16(&this->gamma_table_[idx]);
|
||||
float b = progmem_read_uint16(&this->gamma_table_[idx + 1]);
|
||||
float a = progmem_read_uint16(&this->gamma_table_->lut[idx]);
|
||||
float b = progmem_read_uint16(&this->gamma_table_->lut[idx + 1]);
|
||||
return (a + frac * (b - a)) / 65535.0f;
|
||||
}
|
||||
float LightState::gamma_uncorrect_lut(float value) const {
|
||||
@@ -321,12 +338,12 @@ float LightState::gamma_uncorrect_lut(float value) const {
|
||||
if (this->gamma_table_ == nullptr)
|
||||
return value;
|
||||
uint16_t target = static_cast<uint16_t>(value * 65535.0f);
|
||||
uint8_t lo = gamma_table_reverse_search(this->gamma_table_, target);
|
||||
uint8_t lo = gamma_table_reverse_search(this->gamma_table_->lut, target);
|
||||
if (lo >= 255)
|
||||
return 1.0f;
|
||||
// Interpolate between lo and lo+1
|
||||
uint16_t a = progmem_read_uint16(&this->gamma_table_[lo]);
|
||||
uint16_t b = progmem_read_uint16(&this->gamma_table_[lo + 1]);
|
||||
uint16_t a = progmem_read_uint16(&this->gamma_table_->lut[lo]);
|
||||
uint16_t b = progmem_read_uint16(&this->gamma_table_->lut[lo + 1]);
|
||||
if (b == a)
|
||||
return lo / 255.0f;
|
||||
float frac = static_cast<float>(target - a) / static_cast<float>(b - a);
|
||||
@@ -335,11 +352,14 @@ float LightState::gamma_uncorrect_lut(float value) const {
|
||||
#endif // USE_LIGHT_GAMMA_LUT
|
||||
|
||||
void LightState::start_effect_(uint32_t effect_index) {
|
||||
// An external add_effects() can exceed the codegen cap; ignore an index the uint16_t can't hold
|
||||
if (effect_index > std::numeric_limits<uint16_t>::max())
|
||||
return;
|
||||
this->stop_effect_();
|
||||
if (effect_index == 0)
|
||||
return;
|
||||
|
||||
this->active_effect_index_ = effect_index;
|
||||
this->active_effect_index_ = static_cast<uint16_t>(effect_index);
|
||||
auto *effect = this->get_active_effect_();
|
||||
effect->start_internal();
|
||||
// Enable loop while effect is active
|
||||
@@ -435,7 +455,7 @@ void LightState::save_remote_values_() {
|
||||
saved.color_temp = this->remote_values.get_color_temperature();
|
||||
saved.cold_white = this->remote_values.get_cold_white();
|
||||
saved.warm_white = this->remote_values.get_warm_white();
|
||||
saved.effect = this->active_effect_index_;
|
||||
saved.effect = static_cast<uint32_t>(this->active_effect_index_); // the saved layout stays uint32_t
|
||||
this->rtc_.save(&saved);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#include "light_call.h"
|
||||
#include "esp_color_correction.h"
|
||||
#include "light_color_values.h"
|
||||
#include "light_effect.h"
|
||||
#include "light_traits.h"
|
||||
@@ -151,15 +152,28 @@ class LightState : public EntityBase, public Component {
|
||||
}
|
||||
uint32_t get_default_transition_length() const { return this->default_transition_length_; }
|
||||
|
||||
/// Set the flash transition length
|
||||
#ifdef USE_LIGHT_FLASH_TRANSITION_LENGTH
|
||||
/// Set the flash transition length; only compiled in when a light configures one
|
||||
void set_flash_transition_length(uint32_t flash_transition_length) {
|
||||
this->flash_transition_length_ = flash_transition_length;
|
||||
}
|
||||
uint32_t get_flash_transition_length() const { return this->flash_transition_length_; }
|
||||
#else
|
||||
// Remove before 2027.4.0
|
||||
ESPDEPRECATED("set_flash_transition_length() does nothing unless flash_transition_length is set in YAML. Removed in "
|
||||
"2027.4.0",
|
||||
"2026.10.0")
|
||||
void set_flash_transition_length(uint32_t flash_transition_length) {}
|
||||
uint32_t get_flash_transition_length() const { return 0; }
|
||||
#endif
|
||||
|
||||
/// Set the gamma correction factor
|
||||
void set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
|
||||
float get_gamma_correct() const { return this->gamma_correct_; }
|
||||
// Remove before 2027.4.0
|
||||
ESPDEPRECATED("set_gamma_correct() does nothing; gamma is fixed at build time by gamma_correct in YAML. Removed in "
|
||||
"2027.4.0",
|
||||
"2026.10.0")
|
||||
void set_gamma_correct(float gamma_correct) {}
|
||||
/// The gamma correction factor, read from the entry after the gamma lookup table; 0 without one
|
||||
float get_gamma_correct() const;
|
||||
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
void set_transition_state_publish_interval(uint32_t transition_state_publish_interval) {
|
||||
@@ -169,11 +183,11 @@ class LightState : public EntityBase, public Component {
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
/// Set pre-computed gamma forward lookup table (256-entry uint16 PROGMEM array)
|
||||
void set_gamma_table(const uint16_t *forward) { this->gamma_table_ = forward; }
|
||||
/// Set the pre-computed PROGMEM gamma curve
|
||||
void set_gamma_table(const GammaTable *table) { this->gamma_table_ = table; }
|
||||
|
||||
/// Get the forward gamma lookup table
|
||||
const uint16_t *get_gamma_table() const { return this->gamma_table_; }
|
||||
/// Get the forward gamma lookup table, 256 PROGMEM entries
|
||||
const uint16_t *get_gamma_table() const { return this->gamma_table_ != nullptr ? this->gamma_table_->lut : nullptr; }
|
||||
|
||||
/// Apply gamma correction using the pre-computed forward LUT
|
||||
float gamma_correct_lut(float value) const;
|
||||
@@ -194,6 +208,11 @@ class LightState : public EntityBase, public Component {
|
||||
/// Set whether this light persists its state to preferences at all.
|
||||
void set_save_enabled(bool save_enabled) { this->save_enabled_ = save_enabled; }
|
||||
|
||||
#ifdef USE_LIGHT_RESUME_EFFECT
|
||||
/// Set whether a plain turn-on restores the effect that was active when the light was turned off.
|
||||
void set_resume_effect(bool resume_effect) { this->resume_effect_ = resume_effect; }
|
||||
#endif // USE_LIGHT_RESUME_EFFECT
|
||||
|
||||
/// Return whether the light has any effects that meet the trait requirements.
|
||||
bool supports_effects() const { return !this->effects_.empty(); }
|
||||
|
||||
@@ -362,32 +381,40 @@ class LightState : public EntityBase, public Component {
|
||||
/// Values live in flash as function body; no per-instance data storage beyond this pointer.
|
||||
void (*state_callback_)(LightStateRTCState &, bool restored){nullptr};
|
||||
|
||||
/// Value for storing the index of the currently active effect. 0 if no effect is active
|
||||
uint32_t active_effect_index_{};
|
||||
/// Default transition length for all transitions in ms.
|
||||
uint32_t default_transition_length_{};
|
||||
#ifdef USE_LIGHT_FLASH_TRANSITION_LENGTH
|
||||
/// Transition length to use for flash transitions.
|
||||
uint32_t flash_transition_length_{}; // Keep in sync with DEFAULT_FLASH_TRANSITION_LENGTH in __init__.py
|
||||
#endif
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
uint32_t transition_state_publish_interval_{0};
|
||||
uint32_t last_transition_state_publish_{0};
|
||||
#endif
|
||||
/// Gamma correction factor for the light.
|
||||
float gamma_correct_{};
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
const uint16_t *gamma_table_{nullptr};
|
||||
const GammaTable *gamma_table_{nullptr};
|
||||
#endif // USE_LIGHT_GAMMA_LUT
|
||||
|
||||
/// 1-based index of the active effect, 0 if none; codegen caps effects at MAX_EFFECTS in effects.py
|
||||
uint16_t active_effect_index_{};
|
||||
#ifdef USE_LIGHT_RESUME_EFFECT
|
||||
/// The effect index that was active when the light was last turned off; shares the active index's word
|
||||
uint16_t previous_effect_index_{0};
|
||||
#endif // USE_LIGHT_RESUME_EFFECT
|
||||
/// Whether the light value should be written in the next cycle.
|
||||
bool next_write_{true};
|
||||
bool next_write_{true}; // a plain bool: it is the most written flag, and still shares the index's word
|
||||
// for effects, true if a transformer (transition) is active.
|
||||
bool is_transformer_active_{false};
|
||||
bool is_transformer_active_ : 1 {false};
|
||||
/// Whether this light persists its state to preferences at all.
|
||||
bool save_enabled_{false};
|
||||
bool save_enabled_ : 1 {false};
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
/// True while the active transformer publishes current_values on an interval from loop().
|
||||
bool transition_publish_enabled_{false};
|
||||
bool transition_publish_enabled_ : 1 {false};
|
||||
#endif
|
||||
#ifdef USE_LIGHT_RESUME_EFFECT
|
||||
/// Whether a plain turn-on restores the effect that was active when the light was turned off.
|
||||
bool resume_effect_ : 1 {false};
|
||||
#endif // USE_LIGHT_RESUME_EFFECT
|
||||
};
|
||||
|
||||
} // namespace esphome::light
|
||||
|
||||
@@ -4,6 +4,7 @@ import esphome.codegen as cg
|
||||
# Base
|
||||
light_ns = cg.esphome_ns.namespace("light")
|
||||
LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component)
|
||||
GammaTable = light_ns.struct("GammaTable")
|
||||
LightStateRef = LightState.operator("ref")
|
||||
AddressableLightState = light_ns.class_("AddressableLightState", LightState)
|
||||
LightOutput = light_ns.class_("LightOutput")
|
||||
|
||||
@@ -219,29 +219,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if als_config := config.get(CONF_AMBIENT_LIGHT):
|
||||
sens = await sensor.new_sensor(als_config)
|
||||
cg.add(var.set_ambient_light_sensor(sens))
|
||||
|
||||
if infrared_cnt_config := config.get(CONF_INFRARED_COUNTS):
|
||||
sens = await sensor.new_sensor(infrared_cnt_config)
|
||||
cg.add(var.set_infrared_counts_sensor(sens))
|
||||
|
||||
if full_spect_cnt_config := config.get(CONF_FULL_SPECTRUM_COUNTS):
|
||||
sens = await sensor.new_sensor(full_spect_cnt_config)
|
||||
cg.add(var.set_full_spectrum_counts_sensor(sens))
|
||||
|
||||
if act_gain_config := config.get(CONF_ACTUAL_GAIN):
|
||||
sens = await sensor.new_sensor(act_gain_config)
|
||||
cg.add(var.set_actual_gain_sensor(sens))
|
||||
|
||||
if act_itime_config := config.get(CONF_ACTUAL_INTEGRATION_TIME):
|
||||
sens = await sensor.new_sensor(act_itime_config)
|
||||
cg.add(var.set_actual_integration_time_sensor(sens))
|
||||
|
||||
if prox_cnt_config := config.get(CONF_PS_COUNTS):
|
||||
sens = await sensor.new_sensor(prox_cnt_config)
|
||||
cg.add(var.set_proximity_counts_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_AMBIENT_LIGHT, var.set_ambient_light_sensor)
|
||||
await sensors(CONF_INFRARED_COUNTS, var.set_infrared_counts_sensor)
|
||||
await sensors(CONF_FULL_SPECTRUM_COUNTS, var.set_full_spectrum_counts_sensor)
|
||||
await sensors(CONF_ACTUAL_GAIN, var.set_actual_gain_sensor)
|
||||
await sensors(CONF_ACTUAL_INTEGRATION_TIME, var.set_actual_integration_time_sensor)
|
||||
await sensors(CONF_PS_COUNTS, var.set_proximity_counts_sensor)
|
||||
|
||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUFFER_SIZE,
|
||||
CONF_BUILD_FLAGS,
|
||||
CONF_ESPHOME,
|
||||
CONF_GROUP,
|
||||
CONF_ID,
|
||||
@@ -169,11 +170,17 @@ def generate_lv_conf_h():
|
||||
all_defines = set(
|
||||
df.LV_DEFINES + tuple(f"LV_USE_{w.upper()}" for w in WIDGET_TYPES)
|
||||
)
|
||||
build_flags = (
|
||||
CORE.config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS).get("build_flags", [])
|
||||
esphome_config = CORE.config[CONF_ESPHOME]
|
||||
# User build flags come from esphome->build_flags and from the deprecated
|
||||
# esphome->platformio_options->build_flags (a string or a list).
|
||||
# Remove before 2026.12.0
|
||||
|
||||
pio_build_flags = esphome_config.get(CONF_PLATFORMIO_OPTIONS, {}).get(
|
||||
CONF_BUILD_FLAGS, []
|
||||
)
|
||||
if not isinstance(build_flags, list):
|
||||
build_flags = [build_flags]
|
||||
if not isinstance(pio_build_flags, list):
|
||||
pio_build_flags = [pio_build_flags]
|
||||
build_flags = [*esphome_config.get(CONF_BUILD_FLAGS, []), *pio_build_flags]
|
||||
# Extract define names from build flags like '-DLV_USE_CHART=1', '-D LV_USE_CHART',
|
||||
# or multiple defines in one string.
|
||||
define_pattern = r'-D\s*([A-Z_][A-Z0-9_]*)(?:=[^\s\'"\]]*)?'
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#ifdef USE_LVGL_ANIMATION
|
||||
#include "lvgl_esphome.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::lvgl {
|
||||
|
||||
@@ -110,17 +111,8 @@ template<size_t DATA_SIZE, bool AUTO_START = false> class LvAnimation : public C
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (this->state_ > AnimationState::STOPPED)
|
||||
this->stop();
|
||||
if (this->duration_ == 0)
|
||||
if (!this->prepare_())
|
||||
return;
|
||||
// evaluate any lambdas
|
||||
for (size_t i = 0; i != DATA_SIZE; i++) {
|
||||
this->data_from_[i] = this->from_[i].value();
|
||||
this->data_to_[i] = this->to_[i].value();
|
||||
}
|
||||
this->start_time_ = millis();
|
||||
this->state_ = AnimationState::STARTED;
|
||||
this->loop();
|
||||
this->start_callback_.call();
|
||||
}
|
||||
@@ -143,29 +135,26 @@ template<size_t DATA_SIZE, bool AUTO_START = false> class LvAnimation : public C
|
||||
if (this->state_ == AnimationState::STOPPED)
|
||||
return;
|
||||
uint32_t elapsed = millis() - this->start_time_;
|
||||
float progress = static_cast<float>(elapsed) / static_cast<float>(this->duration_);
|
||||
float progress = clamp_at_most(elapsed / static_cast<float>(this->duration_), 1.0f);
|
||||
switch (this->state_) {
|
||||
case AnimationState::STARTED:
|
||||
if (elapsed < this->start_delay_)
|
||||
return;
|
||||
this->state_ = AnimationState::RUNNING;
|
||||
this->start_time_ = millis();
|
||||
elapsed = 0;
|
||||
progress = 0.0f;
|
||||
break;
|
||||
case AnimationState::RUNNING:
|
||||
if (progress >= 1.0f) {
|
||||
progress = 1.0f;
|
||||
this->stop();
|
||||
if (this->loop_)
|
||||
this->start();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// state here is RUNNING.
|
||||
for (auto *timing : this->timings_) {
|
||||
progress = timing->map_progress(progress);
|
||||
// avoid overshooting
|
||||
progress = clamp_at_most(timing->map_progress(progress), 1.0f);
|
||||
}
|
||||
lv_coord_t data[DATA_SIZE];
|
||||
for (size_t i = 0; i != DATA_SIZE; i++) {
|
||||
@@ -173,6 +162,12 @@ template<size_t DATA_SIZE, bool AUTO_START = false> class LvAnimation : public C
|
||||
roundf(this->data_from_[i] + static_cast<lv_coord_t>(this->data_to_[i] - this->data_from_[i]) * progress));
|
||||
}
|
||||
this->update_callback_(data);
|
||||
if (elapsed >= this->duration_) {
|
||||
this->stop();
|
||||
// Restart without drawing the first frame now, so the final frame is not overwritten before it is shown
|
||||
if (this->loop_ && this->prepare_())
|
||||
this->start_callback_.call();
|
||||
}
|
||||
}
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR - 20.0; }
|
||||
@@ -187,6 +182,22 @@ template<size_t DATA_SIZE, bool AUTO_START = false> class LvAnimation : public C
|
||||
template<typename F> void add_on_stop_callback(F &&callback) { this->stop_callback_.add(std::forward<F>(callback)); }
|
||||
|
||||
protected:
|
||||
// Arms a new run. Returns false if there is nothing to run.
|
||||
bool prepare_() {
|
||||
if (this->state_ > AnimationState::STOPPED)
|
||||
this->stop();
|
||||
if (this->duration_ == 0)
|
||||
return false;
|
||||
// evaluate any lambdas
|
||||
for (size_t i = 0; i != DATA_SIZE; i++) {
|
||||
this->data_from_[i] = this->from_[i].value();
|
||||
this->data_to_[i] = this->to_[i].value();
|
||||
}
|
||||
this->start_time_ = millis();
|
||||
this->state_ = AnimationState::STARTED;
|
||||
return true;
|
||||
}
|
||||
|
||||
void (*const update_callback_)(const lv_coord_t *data);
|
||||
LazyCallbackManager<void()> start_callback_{};
|
||||
LazyCallbackManager<void()> stop_callback_{};
|
||||
|
||||
@@ -246,20 +246,18 @@ class MipiSpi : public display::Display,
|
||||
this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len);
|
||||
this->disable();
|
||||
} else if constexpr (BUS_TYPE == BUS_TYPE_OCTAL) {
|
||||
// Toggle D/C only while holding the bus; on boards where D/C doubles as
|
||||
// another bus signal, driving it while another device owns the bus
|
||||
// corrupts that device's transfer.
|
||||
this->enable();
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_cmd_addr_data(0, 0, 0, 0, &cmd, 1, 8);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->disable();
|
||||
// hold the bus between command and data to avoid a glitch on the D/C line
|
||||
if (len != 0) {
|
||||
this->enable();
|
||||
this->write_cmd_addr_data(0, 0, 0, 0, bytes, len, 8);
|
||||
this->disable();
|
||||
}
|
||||
this->disable();
|
||||
} else if constexpr (BUS_TYPE == BUS_TYPE_SINGLE) {
|
||||
// Toggle D/C only while holding the bus; works around a quirk in the CoreS3 and W5500 ethernet combination.
|
||||
// See https://github.com/esphome/esphome/pull/18529
|
||||
this->enable();
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(cmd);
|
||||
@@ -271,12 +269,14 @@ class MipiSpi : public display::Display,
|
||||
this->disable();
|
||||
}
|
||||
} else if constexpr (BUS_TYPE == BUS_TYPE_SINGLE_16) {
|
||||
this->enable();
|
||||
// DC must be stable before CS as the clock is gated by CS
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
this->write_byte(cmd);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->disable();
|
||||
this->dc_pin_->digital_write(true);
|
||||
for (size_t i = 0; i != len; i++) {
|
||||
// must enable and disable for each byte based on empirical testing
|
||||
this->enable();
|
||||
this->write_byte(0);
|
||||
this->write_byte(bytes[i]);
|
||||
|
||||
@@ -159,21 +159,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
) is not None:
|
||||
cg.add(var.set_min_signal_quality(minimum_signal_quality))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_LEVEL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_LEVEL])
|
||||
cg.add(var.set_level(sens))
|
||||
if CONF_DISTANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_DISTANCE])
|
||||
cg.add(var.set_distance(sens))
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(var.set_battery_level(sens))
|
||||
if CONF_SIGNAL_QUALITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_SIGNAL_QUALITY])
|
||||
cg.add(var.set_signal_quality(sens))
|
||||
if CONF_IGNORED_READS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_IGNORED_READS])
|
||||
cg.add(var.set_ignored_reads(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
await sensors(CONF_LEVEL, var.set_level)
|
||||
await sensors(CONF_DISTANCE, var.set_distance)
|
||||
await sensors(CONF_BATTERY_LEVEL, var.set_battery_level)
|
||||
await sensors(CONF_SIGNAL_QUALITY, var.set_signal_quality)
|
||||
await sensors(CONF_IGNORED_READS, var.set_ignored_reads)
|
||||
|
||||
@@ -108,7 +108,7 @@ void MQTTBackendESP32::loop() {
|
||||
if ((now - this->last_dropped_log_time_) >= DROP_LOG_INTERVAL_MS) {
|
||||
uint16_t dropped = this->mqtt_queue_.get_and_reset_dropped_count();
|
||||
if (dropped > 0) {
|
||||
ESP_LOGW(TAG, "Dropped %u messages (%us)", dropped, DROP_LOG_INTERVAL_MS / 1000);
|
||||
ESP_LOGW(TAG, "Dropped %u messages (%" PRIu32 "s)", dropped, DROP_LOG_INTERVAL_MS / 1000);
|
||||
}
|
||||
this->last_dropped_log_time_ = now;
|
||||
}
|
||||
|
||||
@@ -2,56 +2,64 @@
|
||||
#include "nci_message.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome::nfc {
|
||||
|
||||
static const char *const TAG = "NciMessage";
|
||||
|
||||
NciMessage::NciMessage(const uint8_t message_type, const std::vector<uint8_t> &payload) {
|
||||
NciMessage::NciMessage(const uint8_t message_type, const std::span<const uint8_t> payload) {
|
||||
this->set_message(message_type, payload);
|
||||
}
|
||||
|
||||
NciMessage::NciMessage(const uint8_t message_type, const std::initializer_list<uint8_t> payload)
|
||||
: NciMessage(message_type, std::span<const uint8_t>(payload.begin(), payload.size())) {}
|
||||
|
||||
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid) {
|
||||
this->reset();
|
||||
this->set_header(message_type, gid, oid);
|
||||
}
|
||||
|
||||
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
|
||||
const std::vector<uint8_t> &payload) {
|
||||
const std::span<const uint8_t> payload) {
|
||||
this->set_message(message_type, gid, oid, payload);
|
||||
}
|
||||
|
||||
NciMessage::NciMessage(const std::vector<uint8_t> &raw_packet) { this->nci_message_ = raw_packet; };
|
||||
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
|
||||
const std::initializer_list<uint8_t> payload)
|
||||
: NciMessage(message_type, gid, oid, std::span<const uint8_t>(payload.begin(), payload.size())) {}
|
||||
|
||||
std::vector<uint8_t> NciMessage::encode() {
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
|
||||
std::vector<uint8_t> message = this->nci_message_;
|
||||
return message;
|
||||
NciMessage::NciMessage(const std::span<const uint8_t> raw_packet) {
|
||||
this->nci_message_.assign(raw_packet.begin(), raw_packet.end());
|
||||
}
|
||||
|
||||
void NciMessage::reset() { this->nci_message_ = {0, 0, 0}; }
|
||||
|
||||
uint8_t NciMessage::get_message_type() const {
|
||||
return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK;
|
||||
std::span<const uint8_t> NciMessage::encode() {
|
||||
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
|
||||
return this->nci_message_;
|
||||
}
|
||||
|
||||
uint8_t NciMessage::get_gid() const { return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK; }
|
||||
void NciMessage::reset() {
|
||||
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
|
||||
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = 0;
|
||||
this->nci_message_[NCI_PKT_OID_OFFSET] = 0;
|
||||
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = 0;
|
||||
}
|
||||
|
||||
uint8_t NciMessage::get_oid() const { return this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK; }
|
||||
uint8_t NciMessage::get_message_type() const { return this->nci_message_[NCI_PKT_MT_GID_OFFSET] & NCI_PKT_MT_MASK; }
|
||||
|
||||
uint8_t NciMessage::get_gid() const { return this->nci_message_[NCI_PKT_MT_GID_OFFSET] & NCI_PKT_GID_MASK; }
|
||||
|
||||
uint8_t NciMessage::get_oid() const { return this->nci_message_[NCI_PKT_OID_OFFSET] & NCI_PKT_OID_MASK; }
|
||||
|
||||
uint8_t NciMessage::get_payload_size(const bool recompute) {
|
||||
if (!this->nci_message_.empty()) {
|
||||
if (recompute) {
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
|
||||
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
|
||||
}
|
||||
return this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET];
|
||||
return this->nci_message_[NCI_PKT_LENGTH_OFFSET];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t NciMessage::get_simple_status_response() const {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) {
|
||||
return this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET];
|
||||
if (this->nci_message_.size() > NCI_PKT_PAYLOAD_OFFSET) {
|
||||
return this->nci_message_[NCI_PKT_PAYLOAD_OFFSET];
|
||||
}
|
||||
return STATUS_FAILED;
|
||||
}
|
||||
@@ -63,104 +71,112 @@ uint8_t NciMessage::get_message_byte(const uint8_t offset) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> &NciMessage::get_message() { return this->nci_message_; }
|
||||
std::span<const uint8_t> NciMessage::get_payload() const {
|
||||
if (this->nci_message_.size() <= NCI_PKT_HEADER_SIZE) {
|
||||
return {};
|
||||
}
|
||||
return {this->nci_message_.data() + NCI_PKT_HEADER_SIZE, this->nci_message_.size() - NCI_PKT_HEADER_SIZE};
|
||||
}
|
||||
|
||||
bool NciMessage::has_payload() const { return this->nci_message_.size() > nfc::NCI_PKT_HEADER_SIZE; }
|
||||
bool NciMessage::has_payload() const { return this->nci_message_.size() > NCI_PKT_HEADER_SIZE; }
|
||||
|
||||
bool NciMessage::message_type_is(const uint8_t message_type) const {
|
||||
if (!this->nci_message_.empty()) {
|
||||
return message_type == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK);
|
||||
return message_type == (this->nci_message_[NCI_PKT_MT_GID_OFFSET] & NCI_PKT_MT_MASK);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NciMessage::message_length_is(const uint8_t message_length, const bool recompute) {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_LENGTH_OFFSET) {
|
||||
if (this->nci_message_.size() > NCI_PKT_LENGTH_OFFSET) {
|
||||
if (recompute) {
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
|
||||
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
|
||||
}
|
||||
return message_length == this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET];
|
||||
return message_length == this->nci_message_[NCI_PKT_LENGTH_OFFSET];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NciMessage::gid_is(const uint8_t gid) const {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_MT_GID_OFFSET) {
|
||||
return gid == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK);
|
||||
if (this->nci_message_.size() > NCI_PKT_MT_GID_OFFSET) {
|
||||
return gid == (this->nci_message_[NCI_PKT_MT_GID_OFFSET] & NCI_PKT_GID_MASK);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NciMessage::oid_is(const uint8_t oid) const {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_OID_OFFSET) {
|
||||
return oid == (this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK);
|
||||
if (this->nci_message_.size() > NCI_PKT_OID_OFFSET) {
|
||||
return oid == (this->nci_message_[NCI_PKT_OID_OFFSET] & NCI_PKT_OID_MASK);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NciMessage::simple_status_response_is(const uint8_t response) const {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) {
|
||||
return response == this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET];
|
||||
if (this->nci_message_.size() > NCI_PKT_PAYLOAD_OFFSET) {
|
||||
return response == this->nci_message_[NCI_PKT_PAYLOAD_OFFSET];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NciMessage::set_header(const uint8_t message_type, const uint8_t gid, const uint8_t oid) {
|
||||
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
if (this->nci_message_.size() < NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
|
||||
}
|
||||
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] =
|
||||
(message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK);
|
||||
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
|
||||
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = (message_type & NCI_PKT_MT_MASK) | (gid & NCI_PKT_GID_MASK);
|
||||
this->nci_message_[NCI_PKT_OID_OFFSET] = oid & NCI_PKT_OID_MASK;
|
||||
}
|
||||
|
||||
void NciMessage::set_message(const uint8_t message_type, const std::vector<uint8_t> &payload) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = message_type & nfc::NCI_PKT_MT_MASK;
|
||||
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = 0;
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
|
||||
this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end());
|
||||
void NciMessage::set_message(const uint8_t message_type, const std::span<const uint8_t> payload) {
|
||||
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
|
||||
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = message_type & NCI_PKT_MT_MASK;
|
||||
this->nci_message_[NCI_PKT_OID_OFFSET] = 0;
|
||||
this->append(payload);
|
||||
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
|
||||
}
|
||||
|
||||
void NciMessage::set_message(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
|
||||
const std::vector<uint8_t> &payload) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] =
|
||||
(message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK);
|
||||
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
|
||||
this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end());
|
||||
const std::span<const uint8_t> payload) {
|
||||
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
|
||||
this->set_header(message_type, gid, oid);
|
||||
this->append(payload);
|
||||
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
|
||||
}
|
||||
|
||||
void NciMessage::set_message_type(const uint8_t message_type) {
|
||||
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
if (this->nci_message_.size() < NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
|
||||
}
|
||||
auto mt_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_MT_MASK;
|
||||
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = mt_masked | (message_type & nfc::NCI_PKT_MT_MASK);
|
||||
auto mt_masked = this->nci_message_[NCI_PKT_MT_GID_OFFSET] & ~NCI_PKT_MT_MASK;
|
||||
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = mt_masked | (message_type & NCI_PKT_MT_MASK);
|
||||
}
|
||||
|
||||
void NciMessage::set_gid(const uint8_t gid) {
|
||||
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
if (this->nci_message_.size() < NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
|
||||
}
|
||||
auto gid_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_GID_MASK;
|
||||
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = gid_masked | (gid & nfc::NCI_PKT_GID_MASK);
|
||||
auto gid_masked = this->nci_message_[NCI_PKT_MT_GID_OFFSET] & ~NCI_PKT_GID_MASK;
|
||||
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = gid_masked | (gid & NCI_PKT_GID_MASK);
|
||||
}
|
||||
|
||||
void NciMessage::set_oid(const uint8_t oid) {
|
||||
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
if (this->nci_message_.size() < NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
|
||||
}
|
||||
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
|
||||
this->nci_message_[NCI_PKT_OID_OFFSET] = oid & NCI_PKT_OID_MASK;
|
||||
}
|
||||
|
||||
void NciMessage::set_payload(const std::vector<uint8_t> &payload) {
|
||||
std::vector<uint8_t> message(this->nci_message_.begin(), this->nci_message_.begin() + nfc::NCI_PKT_HEADER_SIZE);
|
||||
|
||||
message.insert(message.end(), payload.begin(), payload.end());
|
||||
message[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
|
||||
this->nci_message_ = message;
|
||||
void NciMessage::set_payload(const std::span<const uint8_t> payload) {
|
||||
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
|
||||
this->append(payload);
|
||||
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
|
||||
}
|
||||
|
||||
void NciMessage::append(const std::span<const uint8_t> data) {
|
||||
for (const uint8_t byte : data) {
|
||||
this->nci_message_.push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
void NciMessage::set_payload_size(const uint8_t size) { this->nci_message_.resize(NCI_PKT_HEADER_SIZE + size); }
|
||||
|
||||
} // namespace esphome::nfc
|
||||
|
||||
@@ -3,19 +3,30 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
#include <span>
|
||||
|
||||
namespace esphome::nfc {
|
||||
|
||||
// An NCI packet is a three-byte header followed by up to 255 payload bytes
|
||||
static constexpr size_t NCI_PKT_MAX_PAYLOAD_SIZE = 255;
|
||||
static constexpr size_t NCI_PKT_MAX_SIZE = 3 + NCI_PKT_MAX_PAYLOAD_SIZE;
|
||||
|
||||
/// One NCI packet, held in a fixed buffer so building and reading messages never allocates
|
||||
class NciMessage {
|
||||
public:
|
||||
NciMessage() {}
|
||||
NciMessage(uint8_t message_type, const std::vector<uint8_t> &payload);
|
||||
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid);
|
||||
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector<uint8_t> &payload);
|
||||
NciMessage(const std::vector<uint8_t> &raw_packet);
|
||||
using Buffer = StaticVector<uint8_t, NCI_PKT_MAX_SIZE>;
|
||||
|
||||
std::vector<uint8_t> encode();
|
||||
NciMessage() { this->reset(); }
|
||||
NciMessage(uint8_t message_type, std::span<const uint8_t> payload);
|
||||
NciMessage(uint8_t message_type, std::initializer_list<uint8_t> payload);
|
||||
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid);
|
||||
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, std::span<const uint8_t> payload);
|
||||
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, std::initializer_list<uint8_t> payload);
|
||||
explicit NciMessage(std::span<const uint8_t> raw_packet);
|
||||
|
||||
/// Stamps the payload length into the header and returns the packet ready to send
|
||||
std::span<const uint8_t> encode();
|
||||
void reset();
|
||||
|
||||
uint8_t get_message_type() const;
|
||||
@@ -24,7 +35,10 @@ class NciMessage {
|
||||
uint8_t get_payload_size(bool recompute = false);
|
||||
uint8_t get_simple_status_response() const;
|
||||
uint8_t get_message_byte(uint8_t offset) const;
|
||||
std::vector<uint8_t> &get_message();
|
||||
Buffer &get_message() { return this->nci_message_; }
|
||||
const Buffer &get_message() const { return this->nci_message_; }
|
||||
/// The payload bytes that follow the header
|
||||
std::span<const uint8_t> get_payload() const;
|
||||
|
||||
bool has_payload() const;
|
||||
bool message_type_is(uint8_t message_type) const;
|
||||
@@ -34,15 +48,25 @@ class NciMessage {
|
||||
bool simple_status_response_is(uint8_t response) const;
|
||||
|
||||
void set_header(uint8_t message_type, uint8_t gid, uint8_t oid);
|
||||
void set_message(uint8_t message_type, const std::vector<uint8_t> &payload);
|
||||
void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector<uint8_t> &payload);
|
||||
void set_message(uint8_t message_type, std::span<const uint8_t> payload);
|
||||
void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, std::span<const uint8_t> payload);
|
||||
void set_message_type(uint8_t message_type);
|
||||
void set_gid(uint8_t gid);
|
||||
void set_oid(uint8_t oid);
|
||||
void set_payload(const std::vector<uint8_t> &payload);
|
||||
void set_payload(std::span<const uint8_t> payload);
|
||||
void set_payload(std::initializer_list<uint8_t> payload) {
|
||||
this->set_payload(std::span<const uint8_t>(payload.begin(), payload.size()));
|
||||
}
|
||||
/// Appends bytes to the payload; bytes that do not fit are dropped
|
||||
void append(std::span<const uint8_t> data);
|
||||
void append(std::initializer_list<uint8_t> data) {
|
||||
this->append(std::span<const uint8_t>(data.begin(), data.size()));
|
||||
}
|
||||
/// Sets the packet size to the header plus `size` payload bytes, for a bus driver filling the buffer directly
|
||||
void set_payload_size(uint8_t size);
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> nci_message_{0, 0, 0}; // three bytes, MT/PBF/GID, OID, payload length/size
|
||||
Buffer nci_message_; // MT/PBF/GID, OID, payload length, then the payload
|
||||
};
|
||||
|
||||
} // namespace esphome::nfc
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace esphome::nfc {
|
||||
|
||||
static const char *const TAG = "nfc.ndef_message";
|
||||
|
||||
NdefMessage::NdefMessage(std::vector<uint8_t> &data) {
|
||||
NdefMessage::NdefMessage(const std::span<const uint8_t> data) {
|
||||
ESP_LOGV(TAG, "Building NdefMessage with %zu bytes", data.size());
|
||||
size_t index = 0;
|
||||
while (index < data.size()) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user