Compare commits

..
Author SHA1 Message Date
J. Nick Koston f228b21746 Merge remote-tracking branch 'origin/yaml-edit-apply' into cli-rotate-key 2026-10-26 04:40:28 -10:00
J. Nick Koston 970885b79b Merge remote-tracking branch 'origin/dev' into yaml-edit-apply
# Conflicts:
#	esphome/yaml_util.py
2026-10-26 04:40:20 -10:00
J. Nick Koston 73564dbebd A quoted include path keeps its hash in the shared-configuration scan 2026-10-17 10:37:14 -10:00
J. Nick Koston 4165dbb534 A comment needs whitespace before its hash in a secret name, old_key takes its secret from the ota block it lands on 2026-10-13 20:16:38 -10:00
J. Nick Koston 3bc9f74f46 The snapshot now carries the mode 2026-10-13 09:55:05 -10:00
J. Nick Koston 0dd27c5d08 Show the report an interrupted restore carries 2026-10-13 09:55:05 -10:00
J. Nick Koston 84b0f35b91 Keep the edit from the point the device accepted the session 2026-10-13 09:55:05 -10:00
J. Nick Koston 2aa3677bbf Advise by what the probe found: unreachable, another key, no encryption, a device error 2026-10-13 09:55:05 -10:00
J. Nick Koston c2fbcf0f24 Refuse a key that is not on a line of its own, name the sharers of an include that gets old_key 2026-10-13 09:55:05 -10:00
J. Nick Koston afbd2ab7b1 [cli] Add rotate-key to change the OTA encryption key over the air
Locates the current key on its yaml or secrets.yaml line, writes the new
one with the editor from #20602, keeps the replaced key as old_key, checks
the device with the probe from #20601, builds, uploads with the current key
and confirms with the new one; anything short of a reached device puts the
files back.
2026-10-13 09:55:04 -10:00
J. Nick Koston 3c65c55df0 Place a rebuilt mapping only when the key is on its value's line 2026-10-13 09:54:56 -10:00
J. Nick Koston 7704ad9384 Restore the mode from before the edit, finish the restore after any exception and raise an exit again with the report 2026-10-13 09:39:35 -10:00
J. Nick Koston 50fdc8e85b Reload a symlinked file by the path the loader read it by 2026-10-13 09:30:56 -10:00
J. Nick Koston 3e6324445b Match the restore report as text; a Windows path is no regex 2026-10-13 07:28:38 -10:00
J. Nick Koston e0e5cbdc27 Register a value written to a secrets file, keep the registry through own_documents, finish the restore after any failure and raise an interrupt again with the report 2026-10-13 07:28:38 -10:00
J. Nick Koston 870b80e463 Keep the secret registry on the reload, keep the order of insertions under one line, refuse an incomplete document set 2026-10-13 07:28:38 -10:00
J. Nick Koston 899e88be72 [core] Let the yaml editor insert lines, write with a rollback and restore
apply_line_edits rewrites the located lines in place, reloads every touched
file and drops the validated caches, undoing everything on any failure;
restore_files puts the originals back only over what was written. A key in
a mapping merge_config rebuilt is placed by its value's line.
2026-10-13 07:28:38 -10:00
134 changed files with 4195 additions and 1561 deletions

No files matched your search

+38
View File
@@ -2233,6 +2233,12 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
return 0
def command_rotate_key(args: ArgsProtocol, config: ConfigType) -> int | None:
from esphome.cli.rotate_key import command_rotate_key as run
return run(args, config)
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
from esphome.cli.rename import command_rename as run
@@ -2259,6 +2265,7 @@ POST_CONFIG_ACTIONS = {
"clean-mqtt": command_clean_mqtt,
"idedata": command_idedata,
"rename": command_rename,
"rotate-key": command_rotate_key,
"discover": command_discover,
"analyze-memory": command_analyze_memory,
"bundle": command_bundle,
@@ -2656,6 +2663,37 @@ def parse_args(argv):
)
parser_rename.add_argument("name", help="The new name for the device.", type=str)
parser_rotate_key = subparsers.add_parser(
"rotate-key",
help=(
"Change the OTA encryption key over the air: write a new key to the "
"yaml, build with it, upload with the current key, confirm."
),
parents=[mqtt_options],
)
parser_rotate_key.add_argument(
"configuration", help="Your YAML configuration file.", nargs=1
)
parser_rotate_key.add_argument(
"--device",
action="append",
help=ARGUMENT_HELP_DEVICE,
)
parser_rotate_key.add_argument(
"--prompt-new-key",
action="store_true",
help=(
"Ask for the new key instead of generating one; read without echo, "
"or as one line from stdin when that is not a terminal."
),
)
parser_rotate_key.add_argument(
"-y",
"--yes",
action="store_true",
help="Do not ask for confirmation when the api encryption key changes too.",
)
parser_analyze_memory = subparsers.add_parser(
"analyze-memory",
help="Analyze memory usage by component.",
+1 -30
View File
@@ -1,18 +1,15 @@
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass, field
import logging
import math
import string
from typing import Any
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_ABOVE,
CONF_ALL,
CONF_ANY,
CONF_AUTOMATION_ID,
CONF_BELOW,
CONF_CONDITION,
CONF_COUNT,
CONF_ELSE,
@@ -366,25 +363,6 @@ class ApplyField:
return ApplyCall(target, ((self.conf_key, self.type_, self.const_fn),))
def _in_range_low(config: ConfigType, value: float) -> str:
return "-INFINITY" if math.isnan(value) else str(cg.safe_exp(value))
def _in_range_high(config: ConfigType, value: float) -> str:
# With no lower bound, an unset upper bound stays NaN so the check is false, as before.
if math.isnan(value):
return "NAN" if math.isnan(config[CONF_ABOVE]) else "INFINITY"
return str(cg.safe_exp(value))
# ``above <= state <= below`` on the parent's float ``state``; register with ``IN_RANGE_DEFAULTS``.
IN_RANGE_CHECK = ApplyCall(
"state >= {} && {parent}->state <= {}",
((CONF_ABOVE, cg.float_, _in_range_low), (CONF_BELOW, cg.float_, _in_range_high)),
)
IN_RANGE_DEFAULTS = {CONF_ABOVE: math.nan, CONF_BELOW: math.nan}
@dataclass(frozen=True)
class ApplyBytes:
"""A byte payload key forwarded as ``target``, whose ``{}`` stands for ``data, len``.
@@ -713,11 +691,7 @@ async def _render_check(
def register_apply_condition(
name: str,
schema: cv.Schema,
check: str | ApplyCall,
id_key: str = CONF_ID,
defaults: ConfigType | None = None,
name: str, schema: cv.Schema, check: str | ApplyCall, id_key: str = CONF_ID
) -> None:
"""Register a condition that is one expression on its parent, with no C++ class.
@@ -726,7 +700,6 @@ def register_apply_condition(
against config values, all of which must be present. Write ``== false`` to negate.
A ``cg.std_string`` constant stays in flash on ESP8266 and supports only ``==`` or ``!=``
against a ``std::string`` or ``StringRef`` member; other string types are plain literals.
``defaults`` fills absent keys at code generation only, so the validated config is unchanged.
Generates one static predicate and an ``ApplyCondition`` templated on it.
"""
call = check if isinstance(check, ApplyCall) else ApplyCall(check)
@@ -741,8 +714,6 @@ def register_apply_condition(
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
if defaults:
config = {**defaults, **config}
parent = await _apply_parent(config, id_key)
lambda_args = _apply_lambda_args(args)
values = _apply_values(config, members)
+285
View File
@@ -0,0 +1,285 @@
"""``esphome rotate-key``."""
from __future__ import annotations
import argparse
from pathlib import Path
import sys
from esphome import espota2
from esphome.components.noise import (
generate_encryption_key,
static_encryption_key,
validate_encryption_key,
)
from esphome.config_validation import Invalid
from esphome.const import CONF_API, CONF_PASSWORD, CONF_PORT
from esphome.core import CORE, EsphomeError
from esphome.espota2 import ProbeResult
from esphome.log import AnsiFore, color
from esphome.ota_key_edit import locate_key_edits, old_key_edit, with_sharers
from esphome.types import ConfigType
from esphome.upload_targets import PortType, get_port_type
from esphome.util import (
ESPHOME_COMMAND,
read_secret_line,
run_external_process,
safe_input,
safe_print,
)
from esphome.yaml_edit import RestoreError, Snapshot, apply_line_edits, restore_files
PRECHECK_TIMEOUT = 15.0
# What to do when the device does not accept the key in the yaml before anything is written
PRECHECK_ADVICE = {
ProbeResult.UNREACHABLE: (
"The device could not be reached; check that it is online, then try again."
),
ProbeResult.REJECTED: (
"The device runs a different key than the yaml. Install the current "
"configuration first, presenting its key with --prompt-ota-key, then rotate."
),
ProbeResult.NOT_OFFERED: (
"The device does not offer encryption: it runs a firmware older than "
"ESPHome 2026.9.0 or without a key. Install the current configuration "
"first, then rotate."
),
ProbeResult.DEVICE_ERROR: (
"The device answered with an error (see the warning above); fix that first."
),
}
def command_rotate_key(args: argparse.Namespace, config: ConfigType) -> int | None:
"""Write a new key, build with it, upload with the current one, confirm."""
from esphome.__main__ import (
Purpose,
_esphome_ota_conf,
_resolve_network_devices,
choose_upload_log_host,
)
def fail(message: str) -> int:
safe_print(color(AnsiFore.BOLD_RED, message))
return 1
ota_conf = _esphome_ota_conf(config)
if ota_conf is None:
return fail("rotate-key needs the esphome OTA platform in the configuration")
old_key = static_encryption_key(ota_conf)
if old_key is None:
return fail(
"rotate-key needs 'encryption:' under the esphome OTA platform with a "
"key in the yaml; a key provisioned at runtime is rotated from Home "
"Assistant"
)
if CORE.is_host:
return fail("rotate-key is for devices updated over the air")
old_key = str(old_key)
remote_port = int(ota_conf[CONF_PORT])
devices = choose_upload_log_host(
default=args.device, check_default=None, purpose=Purpose.UPLOADING
)
if get_port_type(devices[0]) in (PortType.SERIAL, PortType.BOOTSEL):
return fail(
"rotate-key works over the air; on a serial connection change the "
"key in the yaml and run 'esphome run' instead"
)
network_devices = _resolve_network_devices(devices, config, args)
if args.prompt_new_key:
new_key = read_secret_line("New OTA encryption key: ")
else:
new_key = generate_encryption_key()
try:
validate_encryption_key(new_key)
except Invalid as err:
return fail(f"Invalid new key: {err}")
if new_key == old_key:
return fail("The new key is the same as the current one")
try:
edits = locate_key_edits(old_key, new_key)
# The previous key stays in the config as old_key so an install still
# reaches the device if it ends up running either key
edits += old_key_edit(old_key)
unchecked = with_sharers(edits)
except EsphomeError as err:
return fail(str(err))
warnings = []
# With the recommended shape the ota inherits the api key, so rotating
# it changes what Home Assistant connects with
if str(static_encryption_key(config.get(CONF_API) or {})) == old_key:
warnings.append(
"The api encryption key changes too; Home Assistant will ask for "
"the new key after the install."
)
shared: dict[str, set[str]] = {}
for edit in edits:
what = f"secret '{edit.secret}'" if edit.secret else edit.path.name
for path in edit.shared_with:
shared.setdefault(
str(path.relative_to(CORE.config_dir.resolve())), set()
).add(what)
if unchecked:
warnings.append(
"Could not check every configuration for shared use: "
+ "; ".join(unchecked)
)
if shared:
warnings.append(
"Other configurations share what is rewritten: "
+ "; ".join(
f"{p} ({', '.join(sorted(w))})" for p, w in sorted(shared.items())
)
+ ". Their devices keep the previous key until their next install; "
"make sure those configurations carry it as old_key."
)
if warnings:
for warning in warnings:
safe_print(color(AnsiFore.BOLD_YELLOW, warning))
if not args.yes:
if not sys.stdin.isatty():
return fail("Confirm with --yes when there is no terminal")
if safe_input("Continue? [y/N] ").strip().lower() not in ("y", "yes"):
return 1
safe_print("Checking that the device accepts the current key...")
found = espota2.probe_ota_key(
network_devices,
remote_port,
old_key,
timeout=PRECHECK_TIMEOUT,
retry_rejected=False,
)
if found is not ProbeResult.ACCEPTED:
return fail(PRECHECK_ADVICE[found])
# Both keys are shown only when a file could not be put back
keys = f"Previous key: {old_key}\nNew key: {new_key}"
try:
originals = apply_line_edits(edits)
except RestoreError as err:
safe_print(keys)
return fail(str(err))
except EsphomeError as err:
return fail(str(err))
except KeyboardInterrupt as err:
if notes := getattr(err, "__notes__", None): # a rollback that failed too
safe_print(keys)
return fail("Interrupted; " + "; ".join(notes))
return fail("Interrupted before the upload")
# From here every exit restores or reports, so nothing sits outside the try
uploaded = False
try:
safe_print(
"Wrote the new key to "
+ ", ".join(color(AnsiFore.CYAN, str(p)) for p in originals)
)
# Global options go before the subcommand; the compile parser is strict
cli_args = ["--dashboard"] if CORE.dashboard else []
if args.toolchain:
cli_args += ["--toolchain", str(args.toolchain)]
for key, value in args.substitution or []:
cli_args += ["-s", key, value]
cli_args += ["compile", str(CORE.config_path)]
if run_external_process(*ESPHOME_COMMAND, *cli_args) != 0:
return fail("Compiling with the new key failed")
def image_sent() -> None:
# The device accepted the session and may commit from here on;
# the edited config reaches it on either key
nonlocal uploaded
uploaded = True
rc, _ = espota2.run_ota(
network_devices,
remote_port,
ota_conf.get(CONF_PASSWORD),
CORE.firmware_bin,
noise_psk=old_key,
plaintext_fallback=False,
on_upload_start=image_sent,
)
if rc != 0:
return fail(
"Uploading with the current key failed. The configuration keeps "
"the new key with the previous one as old_key, so the next "
"install reaches the device whichever key it runs."
if uploaded
else "The upload did not reach the device."
)
safe_print("Waiting for the device to come back with the new key...")
found = espota2.probe_ota_key(network_devices, remote_port, new_key)
if found is not ProbeResult.ACCEPTED:
return fail(
(
"The device still answers with the previous key."
if found is ProbeResult.REJECTED
else "The device did not come back to confirm the new key."
)
+ " The configuration keeps the new key, with the previous one as "
"old_key, so the next install reaches the device either way."
)
except KeyboardInterrupt:
return fail(
"Interrupted; the device's key was not confirmed"
if uploaded
else "Interrupted before the upload"
)
finally:
# Before the upload nothing changed on the device; after it the device
# most likely runs the new key, and old_key covers the other case
if uploaded:
safe_print(f"New OTA encryption key: {color(AnsiFore.CYAN, new_key)}")
else:
_restore(originals, keys)
safe_print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
return 0
def _restore(originals: dict[Path, Snapshot], keys: str) -> None:
"""Put the previous key back, and drop the images a build made with the
new key; both keys are shown when a step fails."""
files = ", ".join(str(p) for p in originals)
try:
restore_files(originals)
except (EsphomeError, KeyboardInterrupt) as err:
# An interrupted restore carries its report as a note
why = str(err) or "; ".join(getattr(err, "__notes__", ()))
safe_print(color(AnsiFore.BOLD_RED, why or f"Interrupted restoring {files}"))
safe_print(keys)
else:
safe_print(color(AnsiFore.BOLD_YELLOW, f"Restored the previous key in {files}"))
build_dir = CORE.firmware_bin.parent
failed: list[str] = []
images: list[Path] = []
try:
images = [p for s in (".bin", ".uf2") for p in build_dir.glob(f"*{s}")]
for image in images: # every one is tried, whatever the others did
try:
image.unlink()
except OSError as err:
failed.append(f"{image}: {err}")
except (OSError, KeyboardInterrupt) as err:
failed.append(f"{build_dir}: {str(err) or 'interrupted'}")
if failed:
safe_print(
color(
AnsiFore.BOLD_RED,
"Could not remove images that may hold the new key: "
+ "; ".join(failed),
)
)
safe_print(keys)
elif images:
safe_print(
color(
AnsiFore.BOLD_YELLOW,
f"Removed the images in {build_dir}, which may hold the new key; "
"compile again before the next upload.",
)
)
+27 -1
View File
@@ -33,9 +33,35 @@ _CACHE_VERSION = 1
_LAMBDA_KEY = "__esphome_lambda__"
_CACHE_SUFFIX = ".validated.json"
def compiled_config_path(config_filename: str) -> Path:
"""Path to the cached validated config alongside the storage sidecar."""
return CORE.data_dir / "storage" / f"{config_filename}.validated.json"
return CORE.data_dir / "storage" / f"{config_filename}{_CACHE_SUFFIX}"
def invalidate_compiled_config() -> None:
"""Drop every cache in the storage dir after a change to a file the
mtime check cannot see, such as secrets.yaml or an include; other
configurations may share that file."""
storage = compiled_config_path(CORE.config_filename).parent
try:
caches = [p for p in storage.iterdir() if p.name.endswith(_CACHE_SUFFIX)]
except FileNotFoundError:
return
except OSError as err:
raise EsphomeError(
f"Could not list the validated config caches in {storage}: {err}"
) from err
for path in caches:
try:
path.unlink(missing_ok=True)
except OSError as err:
# A stale cache would present the old key on the next upload
raise EsphomeError(
f"Could not remove the validated config cache {path}: {err}"
) from err
def save_compiled_config(config: ConfigType) -> None:
+13 -5
View File
@@ -6,11 +6,19 @@ namespace esphome::as3935_i2c {
ESPHOME_LOG_TAG(TAG, "as3935_i2c");
void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
const uint8_t value = bits << start_pos;
// Bits of value outside mask were ORed in before, so they stay part of the update
// Also called at runtime, so a transient bus error only warns, like read_register()
if (!this->update_register(reg, mask | value, value)) {
ESP_LOGW(TAG, "Failed to update register 0x%02X", reg);
uint8_t write_reg;
if (!this->read_byte(reg, &write_reg)) {
this->mark_failed();
ESP_LOGW(TAG, "read_byte failed - increase log level for more details!");
return;
}
write_reg &= (~mask);
write_reg |= (bits << start_pos);
if (!this->write_byte(reg, write_reg)) {
ESP_LOGW(TAG, "write_byte failed - increase log level for more details!");
return;
}
}
+8 -2
View File
@@ -240,11 +240,17 @@ bool AS7341Component::write_register_bit(uint8_t address, bool value, uint8_t bi
}
bool AS7341Component::set_register_bit(uint8_t address, uint8_t bit_position) {
return this->update_register(address, 1 << bit_position, 1 << bit_position);
uint8_t data;
this->read_byte(address, &data);
data |= (1 << bit_position);
return this->write_byte(address, data);
}
bool AS7341Component::clear_register_bit(uint8_t address, uint8_t bit_position) {
return this->update_register(address, 1 << bit_position, 0);
uint8_t data;
this->read_byte(address, &data);
data &= ~(1 << bit_position);
return this->write_byte(address, data);
}
} // namespace esphome::as7341
+6 -1
View File
@@ -264,7 +264,12 @@ bool AS734XComponent::select_bank_(uint8_t reg) {
}
bool AS734XComponent::update_bits_(uint8_t reg, uint8_t mask, bool value) {
return this->select_bank_(reg) && this->update_register(reg, mask, value ? mask : 0);
uint8_t data = 0;
if (!this->select_bank_(reg) || !this->read_byte(reg, &data)) {
return false;
}
data = value ? (data | mask) : (data & ~mask);
return this->write_byte(reg, data);
}
// The exposure is applied before every measurement, so a gain, ATIME or ASTEP changed at runtime
+2 -1
View File
@@ -108,7 +108,8 @@ CONFIG_SCHEMA = cv.Schema(
def _final_validate(config: ConfigType) -> ConfigType:
hub = fv.get_declaring_config(config[CONF_AS734X_ID])
full = fv.full_config.get()
hub = full.get_config_for_path(full.get_path_for_id(config[CONF_AS734X_ID])[:-1])
if hub[CONF_TYPE] != "AS7341":
return config
for key in _BAND_KEYS:
-1
View File
@@ -29,7 +29,6 @@ from esphome.components.libretiny.const import (
)
from esphome.core import CORE
from esphome.cpp_generator import MockObj
from esphome.stacktrace import process_crash_handler_line as process_stacktrace # noqa: F401
from esphome.types import ConfigType
from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS
+30 -5
View File
@@ -139,24 +139,49 @@ bool BME680Component::setup_registers_() {
this->calibration_.ambient_temperature = 25; // prime ambient temperature
// Config register
if (!this->update_register(BME680_REGISTER_CONFIG, 0b00011100, (this->iir_filter_ & 0b111) << 2)) {
uint8_t config_register;
if (!this->read_byte(BME680_REGISTER_CONFIG, &config_register)) {
return false;
}
config_register &= ~0b00011100;
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->write_byte(BME680_REGISTER_CONFIG, config_register)) {
return false;
}
// Humidity control register
if (!this->update_register(BME680_REGISTER_CONTROL_HUMIDITY, 0b00000111, this->humidity_oversampling_ & 0b111)) {
uint8_t hum_control;
if (!this->read_byte(BME680_REGISTER_CONTROL_HUMIDITY, &hum_control)) {
return false;
}
hum_control &= ~0b00000111;
hum_control |= this->humidity_oversampling_ & 0b111;
if (!this->write_byte(BME680_REGISTER_CONTROL_HUMIDITY, hum_control)) {
return false;
}
// Gas 1 control register: run gas, heater profile 0
if (!this->update_register(BME680_REGISTER_CONTROL_GAS1, 0b00011111, 1 << 4)) {
// Gas 1 control register
uint8_t gas1_control;
if (!this->read_byte(BME680_REGISTER_CONTROL_GAS1, &gas1_control)) {
return false;
}
gas1_control &= ~0b00011111;
gas1_control |= 1 << 4;
gas1_control |= 0; // profile 0
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS1, gas1_control)) {
return false;
}
const bool heat_off = this->heater_temperature_ == 0 || this->heater_duration_ == 0;
// Gas 0 control register
if (!this->update_register(BME680_REGISTER_CONTROL_GAS0, 0b00001000, heat_off << 3)) {
uint8_t gas0_control;
if (!this->read_byte(BME680_REGISTER_CONTROL_GAS0, &gas0_control)) {
return false;
}
gas0_control &= ~0b00001000;
gas0_control |= heat_off << 3;
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) {
return false;
}
+3 -1
View File
@@ -66,7 +66,9 @@ CONFIG_SCHEMA = cv.typed_schema(
def _final_validate(config: ConfigType) -> None:
if config[CONF_TYPE] not in _MAGNETIC_FIELDS:
return
bmi270_config = fv.get_declaring_config(config[CONF_BMI270_ID])
full_config = fv.full_config.get()
bmi270_path = full_config.get_path_for_id(config[CONF_BMI270_ID])[:-1]
bmi270_config = full_config.get_config_for_path(bmi270_path)
if bmi270_config.get(CONF_AUX_DEVICE) != "BMM150":
raise cv.Invalid(
"This sensor requires the parent bmi270's 'aux_device' to be set to 'BMM150'",
+2 -2
View File
@@ -7,7 +7,6 @@ from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_UPDATE_INTERVAL, CONF_WATCHDOG
from esphome.core import CORE, ID, EsphomeError, Lambda, TimePeriod
import esphome.final_validate as fv
from esphome.types import ConfigType, TemplateArgsType
CODEOWNERS = ["@Rapsssito"]
@@ -360,7 +359,8 @@ async def bq25186_configure_to_code(
parent = await cg.get_variable(config[CONF_ID])
# Options the action leaves out fall back to the component's configuration
parent_config = fv.get_declaring_config(config[CONF_ID], CORE.config)
path = CORE.config.get_path_for_id(config[CONF_ID])[:-1]
parent_config = CORE.config.get_config_for_path(path)
merged = {**parent_config, **config}
try:
_check_watchdog(merged)
+7 -4
View File
@@ -28,10 +28,13 @@ bool BQ25186Component::write_register_(uint8_t reg, uint8_t value) {
}
bool BQ25186Component::update_register_(uint8_t reg, uint8_t mask, uint8_t value) {
if (this->update_register(reg, mask, value))
return true;
ESP_LOGW(TAG, "Failed to update register 0x%02X", reg);
return false;
uint8_t reg_value;
if (!this->read_byte(reg, &reg_value)) {
ESP_LOGW(TAG, "Failed to read register 0x%02X", reg);
return false;
}
reg_value = (reg_value & ~mask) | (value & mask);
return this->write_register_(reg, reg_value);
}
uint8_t BQ25186Component::expected_register_(size_t index) const {
+1 -1
View File
@@ -71,12 +71,12 @@ class BQ25186Component : public PollingComponent, public i2c::I2CDevice {
protected:
bool write_register_(uint8_t reg, uint8_t value);
bool update_register_(uint8_t reg, uint8_t mask, uint8_t value);
uint8_t expected_register_(size_t index) const;
bool config_matches_(const BQ25186StatusRegisters &regs) const;
#ifdef USE_BQ25186_PG_GPO
void check_pg_gpo_(const BQ25186StatusRegisters &status, bool &pending);
#endif
bool update_register_(uint8_t reg, uint8_t mask, uint8_t value);
#ifdef BQ25186_BINARY_SENSOR_COUNT
StaticVector<BQ25186BinarySensor *, BQ25186_BINARY_SENSOR_COUNT> binary_sensors_;
+4 -2
View File
@@ -42,8 +42,10 @@ void CAP1188Component::finish_setup_() {
}
// Set sensitivity
// The threshold replaces the upper nibble and is ORed into the lower one, as before
this->update_register(CAP1188_SENSITVITY, 0xF0 | this->touch_threshold_, this->touch_threshold_);
uint8_t sensitivity = 0;
this->read_byte(CAP1188_SENSITVITY, &sensitivity);
sensitivity = sensitivity & 0x0f;
this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_);
// Allow multiple touches
this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_);
@@ -3,7 +3,8 @@ from collections.abc import Callable
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_MODE, CONF_MODEL
from esphome.const import CONF_ID, CONF_MODE, CONF_MODEL
from esphome.core import ID
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -58,11 +59,16 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
)
def get_hub_config(root: ConfigType, hub_id: ID) -> ConfigType:
"""Return the configuration of the hub with the given id."""
return next(conf for conf in root[DOMAIN] if conf[CONF_ID] == hub_id)
def final_validate_mode(keys: dict[str, str]) -> Callable[[ConfigType], None]:
"""Reject entity keys that need a different hub mode; `keys` maps each key to the mode it needs."""
def validator(config: ConfigType) -> None:
hub = fv.get_declaring_config(config[CONF_DFROBOT_SEN0609_ID])
hub = get_hub_config(fv.full_config.get(), config[CONF_DFROBOT_SEN0609_ID])
for key, mode in keys.items():
if key in config and hub[CONF_MODE] != mode:
raise cv.Invalid(
@@ -2,7 +2,17 @@ import esphome.codegen as cg
from esphome.components import button
from esphome.components.const import CONF_QUERY_PARAMS
import esphome.config_validation as cv
from esphome.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
from esphome.const import (
CONF_FACTORY_RESET,
CONF_ID,
CONF_RESTART,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_DATABASE,
ICON_RESTART,
ICON_RESTART_ALERT,
)
from esphome.types import ConfigType
from .. import CONF_DFROBOT_SEN0609_ID, DFRobotSEN0609Component, dfrobot_sen0609_ns
@@ -13,11 +23,23 @@ ButtonAction = dfrobot_sen0609_ns.enum("ButtonAction")
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_DFROBOT_SEN0609_ID): cv.use_id(DFRobotSEN0609Component),
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
DFRobotSEN0609Button
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
DFRobotSEN0609Button,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_QUERY_PARAMS): button.button_schema(
DFRobotSEN0609Button,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_DATABASE,
),
cv.Optional(CONF_RESTART): button.button_schema(
DFRobotSEN0609Button,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_RESTART,
),
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(DFRobotSEN0609Button),
cv.Optional(CONF_RESTART): button.restart_button_schema(DFRobotSEN0609Button),
}
@@ -132,9 +132,9 @@ void DFRobotSEN0609Component::dump_config() {
LOG_BINARY_SENSOR(" ", "HasTarget", this->has_target_binary_sensor_);
#endif
#ifdef USE_SENSOR
LOG_SENSOR_WITH_DEDUP(" ", "TargetDistance", this->target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "TargetSpeed", this->target_speed_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "TargetEnergy", this->target_energy_sensor_);
LOG_SENSOR(" ", "TargetDistance", this->target_distance_sensor_);
LOG_SENSOR(" ", "TargetSpeed", this->target_speed_sensor_);
LOG_SENSOR(" ", "TargetEnergy", this->target_energy_sensor_);
#endif
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
@@ -324,18 +324,17 @@ void DFRobotSEN0609Component::log_bad_frame_(const LogString *problem, const cha
}
#ifdef USE_SENSOR
static void publish_deduplicated(sensor::SensorWithDedup<float> &sens, float value) {
if (std::isnan(value)) {
sens.publish_state_unknown();
} else {
sens.publish_state_if_not_dup(value);
}
static void publish_deduplicated(sensor::Sensor *sens, Deduplicator<float> &dedup, float value) {
if (sens == nullptr)
return;
if (std::isnan(value) ? dedup.next_unknown() : dedup.next(value))
sens->publish_state(value);
}
void DFRobotSEN0609Component::publish_target_(float distance, float speed, float energy) {
publish_deduplicated(this->target_distance_sensor_, distance);
publish_deduplicated(this->target_speed_sensor_, speed);
publish_deduplicated(this->target_energy_sensor_, energy);
publish_deduplicated(this->target_distance_sensor_, this->distance_dedup_, distance);
publish_deduplicated(this->target_speed_sensor_, this->speed_dedup_, speed);
publish_deduplicated(this->target_energy_sensor_, this->energy_dedup_, energy);
}
#endif
@@ -7,7 +7,7 @@
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor_with_dedup.h"
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
@@ -103,9 +103,9 @@ class DFRobotSEN0609Component final : public Component, public uart::UARTDevice
SUB_BINARY_SENSOR(has_target)
#endif
#ifdef USE_SENSOR
SUB_SENSOR_WITH_DEDUP(target_distance, float)
SUB_SENSOR_WITH_DEDUP(target_speed, float)
SUB_SENSOR_WITH_DEDUP(target_energy, float)
SUB_SENSOR(target_distance)
SUB_SENSOR(target_speed)
SUB_SENSOR(target_energy)
#endif
#ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(version)
@@ -175,6 +175,9 @@ class DFRobotSEN0609Component final : public Component, public uart::UARTDevice
#endif
#ifdef USE_SENSOR
void publish_target_(float distance, float speed, float energy);
Deduplicator<float> distance_dedup_;
Deduplicator<float> speed_dedup_;
Deduplicator<float> energy_dedup_;
#endif
#ifdef USE_NUMBER
@@ -18,7 +18,6 @@ from esphome.const import (
UNIT_SECOND,
)
from esphome.core import CORE
import esphome.final_validate as fv
from esphome.types import ConfigType
from .. import (
@@ -29,6 +28,7 @@ from .. import (
DFRobotSEN0609Component,
dfrobot_sen0609_ns,
final_validate_mode,
get_hub_config,
)
DFRobotSEN0609Number = dfrobot_sen0609_ns.class_("DFRobotSEN0609Number", number.Number)
@@ -181,7 +181,7 @@ FINAL_VALIDATE_SCHEMA = final_validate_mode(
async def to_code(config: ConfigType) -> None:
hub_id = config[CONF_DFROBOT_SEN0609_ID]
hub = await cg.get_variable(hub_id)
max_range = MAX_RANGES[fv.get_declaring_config(hub_id, CORE.config)[CONF_MODEL]]
max_range = MAX_RANGES[get_hub_config(CORE.config, hub_id)[CONF_MODEL]]
numbers = number.sub_numbers(config, parent=hub)
for s in SETTINGS:
await numbers(
+4 -2
View File
@@ -28,7 +28,7 @@ from esphome.const import (
SCHEDULER_DONT_RUN,
)
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
from esphome.final_validate import full_config, get_declaring_config
from esphome.final_validate import full_config
from esphome.types import ConfigType
DOMAIN = "display"
@@ -225,7 +225,9 @@ def get_display_metadata(display_id: ID) -> DisplayMetaData:
return meta
# No metadata found, display driver may not yet support it.
# Read the raw config to populate the returned data
disp_config = get_declaring_config(display_id)
global_config = full_config.get()
path = global_config.get_path_for_id(display_id)[:-1]
disp_config = global_config.get_config_for_path(path)
dimensions = disp_config.get(CONF_DIMENSIONS, (0, 0))
if isinstance(dimensions, dict):
dimensions = (dimensions.get(CONF_WIDTH, 0), dimensions.get(CONF_HEIGHT, 0))
@@ -1,6 +1,5 @@
#include "ds248x_one_wire_bus.h"
#include "ds248x.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::ds248x {
@@ -152,7 +151,11 @@ uint64_t DS248xOneWireBus::search_int() {
}
// Update address based on direction taken
assign_bits(address, bit_mask, dir_taken);
if (dir_taken) {
address |= bit_mask;
} else {
address &= ~bit_mask;
}
}
// Search successful
+3 -1
View File
@@ -32,7 +32,9 @@ CONFIG_SCHEMA = cv.Schema(
def _final_validate(config: ConfigType) -> None:
"""Validate that the channel is within the parent's channel count."""
parent_config = fv.get_declaring_config(config[CONF_DS248X_ID])
fconf = fv.full_config.get()
path = fconf.get_path_for_id(config[CONF_DS248X_ID])[:-1]
parent_config = fconf.get_config_for_path(path)
channel_count = get_channel_count(parent_config)
channel = config[CONF_CHANNEL]
+25 -18
View File
@@ -50,7 +50,7 @@ void ES7210::setup() {
ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC34_HPF1_REG21, 0x2a));
// Secondary I2S mode settings
ES7210_ERROR_FAILED(this->update_register(ES7210_MODE_CONFIG_REG08, 0x01, 0x00));
ES7210_ERROR_FAILED(this->es7210_update_reg_bit_(ES7210_MODE_CONFIG_REG08, 0x01, 0x00));
// Configure analog power
ES7210_ERROR_FAILED(this->write_byte(ES7210_ANALOG_REG40, 0xC3));
@@ -141,39 +141,39 @@ bool ES7210::configure_sample_rate_() {
bool ES7210::configure_mic_gain_() {
for (uint8_t i = 0; i < 4; ++i) {
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00));
}
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0xff));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0xff));
// Configure mic 1
ES7210_ERROR_CHECK(this->update_register(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00));
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC1_GAIN_REG43, 0x10, 0x10));
ES7210_ERROR_CHECK(
this->update_register(ES7210_MIC1_GAIN_REG43, 0x0f, this->es7210_gain_reg_value_(this->channel_gains_[0])));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x0f,
this->es7210_gain_reg_value_(this->channel_gains_[0])));
// Configure mic 2
ES7210_ERROR_CHECK(this->update_register(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00));
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC2_GAIN_REG44, 0x10, 0x10));
ES7210_ERROR_CHECK(
this->update_register(ES7210_MIC2_GAIN_REG44, 0x0f, this->es7210_gain_reg_value_(this->channel_gains_[1])));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x0f,
this->es7210_gain_reg_value_(this->channel_gains_[1])));
// Configure mic 3
// MIC3 uses the ADC3/4 and MIC3/4 clock domains (bits 2 and 4), not the MIC1/2 domains.
ES7210_ERROR_CHECK(this->update_register(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00));
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC3_GAIN_REG45, 0x10, 0x10));
ES7210_ERROR_CHECK(
this->update_register(ES7210_MIC3_GAIN_REG45, 0x0f, this->es7210_gain_reg_value_(this->channel_gains_[2])));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x0f,
this->es7210_gain_reg_value_(this->channel_gains_[2])));
// Configure mic 4
ES7210_ERROR_CHECK(this->update_register(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00));
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC4_GAIN_REG46, 0x10, 0x10));
ES7210_ERROR_CHECK(
this->update_register(ES7210_MIC4_GAIN_REG46, 0x0f, this->es7210_gain_reg_value_(this->channel_gains_[3])));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x0f,
this->es7210_gain_reg_value_(this->channel_gains_[3])));
return true;
}
@@ -227,4 +227,11 @@ bool ES7210::configure_i2s_format_() {
return true;
}
bool ES7210::es7210_update_reg_bit_(uint8_t reg_addr, uint8_t update_bits, uint8_t data) {
uint8_t regv;
ES7210_ERROR_CHECK(this->read_byte(reg_addr, &regv));
regv = (regv & (~update_bits)) | (update_bits & data);
return this->write_byte(reg_addr, regv);
}
} // namespace esphome::es7210
+7
View File
@@ -40,6 +40,13 @@ class ES7210 final : public audio_adc::AudioAdc, public Component, public i2c::I
float mic_gain() override { return this->channel_gains_[0]; };
protected:
/// @brief Updates an I2C registry address by modifying the current state
/// @param reg_addr I2C register address
/// @param update_bits Mask of allowed bits to be modified
/// @param data Bit values to be written
/// @return True if successful, false otherwise
bool es7210_update_reg_bit_(uint8_t reg_addr, uint8_t update_bits, uint8_t data);
/// @brief Convert floating point mic gain value to register value
/// @param mic_gain Gain value to convert
/// @return Corresponding register value for specified gain
+5 -3
View File
@@ -1,9 +1,9 @@
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components.audio_dac import AudioDac
from esphome.components.speaker import DOMAIN as SPEAKER_DOMAIN, get_audio_dac_ids
from esphome.components.speaker import DOMAIN as SPEAKER_DOMAIN
import esphome.config_validation as cv
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID
from esphome.const import CONF_AUDIO_DAC, CONF_BITS_PER_SAMPLE, CONF_ID
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -30,8 +30,10 @@ def _final_validate(config: ConfigType) -> None:
# Check all speaker configurations for ones that reference this es8156
speaker_configs = full_config.get(SPEAKER_DOMAIN, [])
for speaker_config in speaker_configs:
audio_dac_id = speaker_config.get(CONF_AUDIO_DAC)
if (
config[CONF_ID] in get_audio_dac_ids(speaker_config)
audio_dac_id is not None
and audio_dac_id == config[CONF_ID]
and (bits_per_sample := speaker_config.get(CONF_BITS_PER_SAMPLE))
is not None
and bits_per_sample > 24
+13 -2
View File
@@ -91,9 +91,20 @@ float ES8156::volume() {
}
bool ES8156::set_mute_state_(bool mute_state) {
uint8_t reg13;
this->is_muted_ = mute_state;
const uint8_t mask = BIT(1) | BIT(2);
return this->update_register(ES8156_REG13_DAC_MUTE, mask, mute_state ? mask : 0);
if (!this->read_byte(ES8156_REG13_DAC_MUTE, &reg13)) {
return false;
}
ESP_LOGV(TAG, "Read ES8156_REG13_DAC_MUTE: %u", reg13);
assign_bits(reg13, BIT(1) | BIT(2), mute_state);
ESP_LOGV(TAG, "Setting ES8156_REG13_DAC_MUTE to %u (muted: %s)", reg13, YESNO(mute_state));
return this->write_byte(ES8156_REG13_DAC_MUTE, reg13);
}
} // namespace esphome::es8156
+35 -11
View File
@@ -138,9 +138,12 @@ bool ES8311::configure_clock_() {
}
// Register 0x02
ES8311_ERROR_CHECK(
this->update_register(ES8311_REG02_CLK_MANAGER, 0xF8,
((coefficient->pre_div - 1) << 5) | (encode_pre_mult(coefficient->pre_mult) << 3)));
uint8_t reg02;
ES8311_ERROR_CHECK(this->read_byte(ES8311_REG02_CLK_MANAGER, &reg02));
reg02 &= 0x07;
reg02 |= (coefficient->pre_div - 1) << 5;
reg02 |= encode_pre_mult(coefficient->pre_mult) << 3;
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG02_CLK_MANAGER, reg02));
// Register 0x03
const uint8_t reg03 = (coefficient->fs_mode << 6) | coefficient->adc_osr;
@@ -154,13 +157,23 @@ bool ES8311::configure_clock_() {
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG05_CLK_MANAGER, reg05));
// Register 0x06
uint8_t reg06 = coefficient->bclk_div < 19 ? coefficient->bclk_div - 1 : coefficient->bclk_div;
if (this->sclk_inverted_)
reg06 |= BIT(5);
ES8311_ERROR_CHECK(this->update_register(ES8311_REG06_CLK_MANAGER, BIT(5) | 0x1F, reg06));
uint8_t reg06;
ES8311_ERROR_CHECK(this->read_byte(ES8311_REG06_CLK_MANAGER, &reg06));
assign_bits(reg06, BIT(5), this->sclk_inverted_);
reg06 &= 0xE0;
if (coefficient->bclk_div < 19) {
reg06 |= (coefficient->bclk_div - 1) << 0;
} else {
reg06 |= (coefficient->bclk_div) << 0;
}
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG06_CLK_MANAGER, reg06));
// Register 0x07
ES8311_ERROR_CHECK(this->update_register(ES8311_REG07_CLK_MANAGER, 0x3F, coefficient->lrck_h));
uint8_t reg07;
ES8311_ERROR_CHECK(this->read_byte(ES8311_REG07_CLK_MANAGER, &reg07));
reg07 &= 0xC0;
reg07 |= coefficient->lrck_h << 0;
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG07_CLK_MANAGER, reg07));
// Register 0x08
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG08_CLK_MANAGER, coefficient->lrck_l));
@@ -171,7 +184,10 @@ bool ES8311::configure_clock_() {
bool ES8311::configure_format_() {
// Configure I2S mode and format
ES8311_ERROR_CHECK(this->update_register(ES8311_REG00_RESET, BIT(6), 0));
uint8_t reg00;
ES8311_ERROR_CHECK(this->read_byte(ES8311_REG00_RESET, &reg00));
reg00 &= 0xBF;
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG00_RESET, reg00));
// Configure SDP in resolution
uint8_t reg09 = calculate_resolution_value(this->resolution_in_);
@@ -200,9 +216,17 @@ bool ES8311::configure_mic_() {
}
bool ES8311::set_mute_state_(bool mute_state) {
uint8_t reg31;
this->is_muted_ = mute_state;
const uint8_t mask = BIT(6) | BIT(5);
return this->update_register(ES8311_REG31_DAC, mask, mute_state ? mask : 0);
if (!this->read_byte(ES8311_REG31_DAC, &reg31)) {
return false;
}
assign_bits(reg31, BIT(6) | BIT(5), mute_state);
return this->write_byte(ES8311_REG31_DAC, reg31);
}
} // namespace esphome::es8311
@@ -17,20 +17,16 @@ void IPAddressEthernetInfo::on_ip_state(const network::IPAddresses &ips, const n
char buf[network::IP_ADDRESS_BUFFER_SIZE];
ips[0].str_to(buf);
this->publish_state(buf);
#ifdef ETHERNET_INFO_IP_SENSOR_COUNT
uint8_t sensor = 0;
for (const auto &ip : ips) {
if (!ip.is_set())
continue;
if (sensor == ETHERNET_INFO_IP_SENSOR_COUNT)
break;
if (this->ip_sensors_[sensor] != nullptr) {
ip.str_to(buf);
this->ip_sensors_[sensor]->publish_state(buf);
if (ip.is_set()) {
if (this->ip_sensors_[sensor] != nullptr) {
ip.str_to(buf);
this->ip_sensors_[sensor]->publish_state(buf);
}
sensor++;
}
sensor++;
}
#endif
}
void DNSAddressEthernetInfo::setup() { ethernet::global_eth_component->add_ip_state_listener(this); }
@@ -18,19 +18,14 @@ class IPAddressEthernetInfo final : public Component,
void setup() override;
void dump_config() override;
#ifdef ETHERNET_INFO_IP_SENSOR_COUNT
void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; }
#endif
// EthernetIPStateListener interface
void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
const network::IPAddress &dns2) override;
protected:
#ifdef ETHERNET_INFO_IP_SENSOR_COUNT
// Sized to the highest configured address_N
std::array<text_sensor::TextSensor *, ETHERNET_INFO_IP_SENSOR_COUNT> ip_sensors_{};
#endif
std::array<text_sensor::TextSensor *, 5> ip_sensors_{};
};
class DNSAddressEthernetInfo final : public Component,
@@ -12,7 +12,6 @@ from esphome.types import ConfigType
DEPENDENCIES = ["ethernet"]
ethernet_info_ns = cg.esphome_ns.namespace("ethernet_info")
_request_ip_sensor_slot = cg.slot_counter("ETHERNET_INFO_IP_SENSOR_COUNT")
IPAddressEthernetInfo = ethernet_info_ns.class_(
"IPAddressEthernetInfo", text_sensor.TextSensor, cg.Component
@@ -58,13 +57,10 @@ async def to_code(config: ConfigType) -> None:
if conf := config.get(CONF_IP_ADDRESS):
ip_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS])
await cg.register_component(ip_info, config[CONF_IP_ADDRESS])
indexes = [x for x in range(5) if f"address_{x}" in conf]
# One shared array size: the largest highest index of any entry, so add_ip_sensors(x) stays in range
for _ in range(max(indexes, default=-1) + 1):
_request_ip_sensor_slot(str(ip_info))
for x in indexes:
sens = await text_sensor.new_text_sensor(conf[f"address_{x}"])
cg.add(ip_info.add_ip_sensors(x, sens))
for x in range(5):
if sensor_conf := conf.get(f"address_{x}"):
sens = await text_sensor.new_text_sensor(sensor_conf)
cg.add(ip_info.add_ip_sensors(x, sens))
if conf := config.get(CONF_DNS_ADDRESS):
dns_info = await text_sensor.new_text_sensor(conf)
await cg.register_component(dns_info, conf)
+15 -37
View File
@@ -23,7 +23,10 @@ CONF_REQUEST_VOLTAGE = "request_voltage"
fusb302b_ns = cg.esphome_ns.namespace("fusb302b")
FUSB302B = fusb302b_ns.class_("FUSB302B", cg.Component, i2c.I2CDevice)
PdState = fusb302b_ns.enum("PdState", is_class=True)
ConnectForwarder = fusb302b_ns.class_("ConnectForwarder")
DisconnectForwarder = fusb302b_ns.class_("DisconnectForwarder")
PowerReadyForwarder = fusb302b_ns.class_("PowerReadyForwarder")
ErrorForwarder = fusb302b_ns.class_("ErrorForwarder")
# USB PD fixed supply voltages are 5, 9, 12, 15 and 20 V; the highest one that does not exceed the
# requested voltage is chosen.
@@ -47,43 +50,18 @@ CONFIG_SCHEMA = cv.All(
cv.only_on_esp32,
)
_STATE_PARAMS = [(PdState, "state"), (PdState, "previous")]
_CONNECTED = "fusb302b::is_connected_state"
# Each state trigger fires on the change described by its filter.
_STATE_TRIGGERS = (
# A source is attached, including after recovering from a communication error.
(CONF_ON_CONNECT, f"{_CONNECTED}(state) && !{_CONNECTED}(previous)"),
# The source is removed, or is no longer known to be attached because communication failed.
(CONF_ON_DISCONNECT, f"!{_CONNECTED}(state) && {_CONNECTED}(previous)"),
# Negotiation is finished: a new PD contract is in place, or the source does not speak PD.
(
CONF_ON_POWER_READY,
(
"state == fusb302b::PdState::PD_STATE_EXPLICIT_CONTRACT"
" || state == fusb302b::PdState::PD_STATE_PD_TIMEOUT"
),
),
# Communication with the chip fails.
(
CONF_ON_ERROR,
(
"state == fusb302b::PdState::PD_STATE_ERROR"
" && previous != fusb302b::PdState::PD_STATE_ERROR"
),
),
)
_CALLBACK_AUTOMATIONS = (
*(
automation.CallbackAutomation(
key,
"add_on_state_callback",
params=_STATE_PARAMS,
forward=[],
when=when,
)
for key, when in _STATE_TRIGGERS
automation.CallbackAutomation(
CONF_ON_CONNECT, "add_on_state_callback", forwarder=ConnectForwarder
),
automation.CallbackAutomation(
CONF_ON_DISCONNECT, "add_on_state_callback", forwarder=DisconnectForwarder
),
automation.CallbackAutomation(
CONF_ON_POWER_READY, "add_on_state_callback", forwarder=PowerReadyForwarder
),
automation.CallbackAutomation(
CONF_ON_ERROR, "add_on_state_callback", forwarder=ErrorForwarder
),
automation.CallbackAutomation(
CONF_ON_REQUEST_FAILED, "add_on_request_failed_callback"
+55
View File
@@ -0,0 +1,55 @@
#pragma once
#include <type_traits>
#include "esphome/core/automation.h"
#include "pd.h"
namespace esphome::fusb302b {
/// Triggers when a source is attached, including after recovering from a communication error.
struct ConnectForwarder {
Automation<> *automation;
void operator()(PdState state, PdState previous) const {
if (is_connected_state(state) && !is_connected_state(previous))
this->automation->trigger();
}
};
/// Triggers when the source is removed, or is no longer known to be attached because communication failed.
struct DisconnectForwarder {
Automation<> *automation;
void operator()(PdState state, PdState previous) const {
if (!is_connected_state(state) && is_connected_state(previous))
this->automation->trigger();
}
};
/// Triggers when negotiation is finished: a new PD contract is in place, or the source does not speak PD.
struct PowerReadyForwarder {
Automation<> *automation;
void operator()(PdState state, PdState /*previous*/) const {
if (state == PdState::PD_STATE_EXPLICIT_CONTRACT || state == PdState::PD_STATE_PD_TIMEOUT)
this->automation->trigger();
}
};
/// Triggers when communication with the chip fails.
struct ErrorForwarder {
Automation<> *automation;
void operator()(PdState state, PdState previous) const {
if (state == PdState::PD_STATE_ERROR && previous != PdState::PD_STATE_ERROR)
this->automation->trigger();
}
};
static_assert(sizeof(ConnectForwarder) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<ConnectForwarder>);
static_assert(sizeof(DisconnectForwarder) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<DisconnectForwarder>);
static_assert(sizeof(PowerReadyForwarder) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<PowerReadyForwarder>);
static_assert(sizeof(ErrorForwarder) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<ErrorForwarder>);
} // namespace esphome::fusb302b
+6 -2
View File
@@ -265,8 +265,12 @@ bool FUSB302B::init_chip_() {
return false;
// Let the chip retransmit unacknowledged messages up to 3 times
if (!this->update_register(FUSB_CONTROL3, FUSB_CONTROL3_N_RETRIES_MASK | FUSB_CONTROL3_AUTO_RETRY,
(3 << FUSB_CONTROL3_N_RETRIES_SHIFT) | FUSB_CONTROL3_AUTO_RETRY))
uint8_t control3 = 0;
if (!this->read_byte(FUSB_CONTROL3, &control3))
return false;
control3 =
(control3 & ~FUSB_CONTROL3_N_RETRIES_MASK) | (3 << FUSB_CONTROL3_N_RETRIES_SHIFT) | FUSB_CONTROL3_AUTO_RETRY;
if (!this->write_byte(FUSB_CONTROL3, control3))
return false;
if (!this->write_byte(FUSB_POWER, FUSB_POWER_ALL))
+3 -1
View File
@@ -63,7 +63,9 @@ CONFIG_SCHEMA = cv.Schema(
def _validate_model(config: ConfigType) -> None:
climate_conf = fv.get_declaring_config(config[CONF_GREE_ID])
full_config = fv.full_config.get()
climate_path = full_config.get_path_for_id(config[CONF_GREE_ID])[:-1]
climate_conf = full_config.get_config_for_path(climate_path)
if climate_conf[CONF_MODEL] not in SUPPORTED_MODELS:
raise cv.Invalid(
"Gree switches are only supported for the "
+3 -1
View File
@@ -52,10 +52,12 @@ CONFIG_SCHEMA = cv.Schema(
def _final_validate(config) -> None:
full_config = fv.full_config.get()
for switch_type in [CONF_BEEPER, CONF_QUIET_MODE]:
# Check switches that are only supported for HonClimate
if config.get(switch_type):
climate_conf = fv.get_declaring_config(config[CONF_HAIER_ID])
climate_path = full_config.get_path_for_id(config[CONF_HAIER_ID])[:-1]
climate_conf = full_config.get_config_for_path(climate_path)
protocol_type = climate_conf.get(CONF_PROTOCOL)
if protocol_type.casefold() != PROTOCOL_HON.casefold():
raise cv.Invalid(
@@ -3,8 +3,9 @@ import esphome.codegen as cg
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_PASSWORD, CONF_URL, CONF_USERNAME
from esphome.core import coroutine_with_priority
from esphome.core import ID, coroutine_with_priority
from esphome.coroutine import CoroPriority
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
from .. import CONF_HTTP_REQUEST_ID, HttpRequestComponent, http_request_ns
@@ -20,6 +21,9 @@ CONF_MD5_URL = "md5_url"
OtaHttpRequestComponent = http_request_ns.class_(
"OtaHttpRequestComponent", OTAComponent
)
OtaHttpRequestComponentFlashAction = http_request_ns.class_(
"OtaHttpRequestComponentFlashAction", automation.Action
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
@@ -64,13 +68,38 @@ OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
)
automation.register_apply_action(
@automation.register_action(
"ota.http_request.flash",
OtaHttpRequestComponentFlashAction,
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA,
automation.ApplyField(CONF_MD5_URL, "set_md5_url", cg.std_string),
automation.ApplyField(CONF_MD5, "set_md5", cg.std_string),
automation.ApplyField(CONF_PASSWORD, "set_password", cg.std_string),
automation.ApplyField(CONF_USERNAME, "set_username", cg.std_string),
automation.ApplyField(CONF_URL, "set_url", cg.std_string),
automation.ApplyCall("flash()"),
synchronous=True,
)
async def ota_http_request_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if md5_url := config.get(CONF_MD5_URL):
template_ = await cg.templatable(md5_url, args, cg.std_string)
cg.add(var.set_md5_url(template_))
if md5_str := config.get(CONF_MD5):
template_ = await cg.templatable(md5_str, args, cg.std_string)
cg.add(var.set_md5(template_))
if password_str := config.get(CONF_PASSWORD):
template_ = await cg.templatable(password_str, args, cg.std_string)
cg.add(var.set_password(template_))
if username_str := config.get(CONF_USERNAME):
template_ = await cg.templatable(username_str, args, cg.std_string)
cg.add(var.set_username(template_))
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
cg.add(var.set_url(template_))
return var
@@ -0,0 +1,40 @@
#pragma once
#include "ota_http_request.h"
#include "esphome/core/automation.h"
namespace esphome::http_request {
template<typename... Ts> class OtaHttpRequestComponentFlashAction final : public Action<Ts...> {
public:
OtaHttpRequestComponentFlashAction(OtaHttpRequestComponent *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(std::string, md5_url)
TEMPLATABLE_VALUE(std::string, md5)
TEMPLATABLE_VALUE(std::string, password)
TEMPLATABLE_VALUE(std::string, url)
TEMPLATABLE_VALUE(std::string, username)
void play(const Ts &...x) override {
if (this->md5_url_.has_value()) {
this->parent_->set_md5_url(this->md5_url_.value(x...));
}
if (this->md5_.has_value()) {
this->parent_->set_md5(this->md5_.value(x...));
}
if (this->password_.has_value()) {
this->parent_->set_password(this->password_.value(x...));
}
if (this->username_.has_value()) {
this->parent_->set_username(this->username_.value(x...));
}
this->parent_->set_url(this->url_.value(x...));
this->parent_->flash();
// Normally never reached due to reboot
}
protected:
OtaHttpRequestComponent *parent_;
};
} // namespace esphome::http_request
+6 -3
View File
@@ -1,7 +1,6 @@
#include "htu21d.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome::htu21d {
@@ -113,7 +112,11 @@ void HTU21DComponent::set_heater(bool status) {
this->status_set_warning();
return;
}
assign_bits(raw_heater, 1 << HTU21D_REG_HTRE_BIT, status);
if (status) {
raw_heater |= (1 << HTU21D_REG_HTRE_BIT);
} else {
raw_heater &= ~(1 << HTU21D_REG_HTRE_BIT);
}
if (this->write_register(HTU21D_WRITERHT_REG_CMD, &raw_heater, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
@@ -36,7 +36,7 @@ void I2SAudioSpeakerBase::setup() {
return;
}
// Initialize volume control. When audio_dac is configured, this sets every DAC's volume and mute state.
// Initialize volume control. When audio_dac is configured, this sets the DAC volume and mute state.
// When no audio_dac is configured, this initializes software volume control.
this->set_volume(this->volume_);
this->set_mute_state(this->mute_state_);
@@ -101,7 +101,9 @@ void I2SAudioSpeakerBase::loop() {
this->state_ = speaker::STATE_RUNNING;
#ifdef USE_AUDIO_DAC
// The task raises TASK_RUNNING only after enabling the channel, so the clocks are already running
this->notify_audio_dacs_started_();
if (this->audio_dac_ != nullptr) {
this->audio_dac_->on_audio_started();
}
#endif // USE_AUDIO_DAC
}
if (event_group_bits & SpeakerEventGroupBits::TASK_STOPPING) {
@@ -202,7 +204,7 @@ void I2SAudioSpeakerBase::set_mute_state(bool mute_state) {
void I2SAudioSpeakerBase::post_software_gain_(uint32_t rate_samples) {
#ifdef USE_AUDIO_DAC
if (this->has_audio_dac_()) {
if (this->audio_dac_ != nullptr) {
return; // Hardware volume; the ramp stays at unity
}
#endif // USE_AUDIO_DAC
@@ -425,7 +427,7 @@ void I2SAudioSpeakerBase::drain_lockstep_(uint32_t extra_frames) {
void I2SAudioSpeakerBase::apply_software_volume_(uint8_t *data, size_t bytes_read) {
#ifdef USE_AUDIO_DAC
if (this->has_audio_dac_()) {
if (this->audio_dac_ != nullptr) {
return; // Hardware volume; the ramp is never targeted
}
#endif // USE_AUDIO_DAC
+3 -1
View File
@@ -42,7 +42,9 @@ def _final_validate(config: dict[str, Any]) -> None:
# Get the transmitter configuration
transmitter_id = config[CONF_REMOTE_TRANSMITTER_ID]
transmitter_config = fv.get_declaring_config(transmitter_id)
full_config = fv.full_config.get()
transmitter_path = full_config.get_path_for_id(transmitter_id)[:-1]
transmitter_config = full_config.get_config_for_path(transmitter_path)
# Check if carrier_duty_percent set to 0 or 100
# Note: remote_transmitter schema requires this field and validates 1-100%,
@@ -35,7 +35,11 @@ def _final_validate(config: ConfigType) -> None:
if CONF_REMOTE_TRANSMITTER_ID not in config:
return
transmitter_config = fv.get_declaring_config(config[CONF_REMOTE_TRANSMITTER_ID])
full_config = fv.full_config.get()
transmitter_path = full_config.get_path_for_id(config[CONF_REMOTE_TRANSMITTER_ID])[
:-1
]
transmitter_config = full_config.get_config_for_path(transmitter_path)
duty_percent = transmitter_config.get(CONF_CARRIER_DUTY_PERCENT)
if duty_percent is not None and duty_percent != 100:
+27 -5
View File
@@ -2,7 +2,17 @@ import esphome.codegen as cg
from esphome.components import button
from esphome.components.const import CONF_QUERY_PARAMS
import esphome.config_validation as cv
from esphome.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
from esphome.const import (
CONF_FACTORY_RESET,
CONF_ID,
CONF_RESTART,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_DATABASE,
ICON_RESTART,
ICON_RESTART_ALERT,
)
from esphome.types import ConfigType
from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns
@@ -14,11 +24,23 @@ RestartButton = ld2410_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
FactoryResetButton
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
FactoryResetButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_RESTART): button.button_schema(
RestartButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_RESTART,
),
cv.Optional(CONF_QUERY_PARAMS): button.button_schema(
QueryButton,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_DATABASE,
),
cv.Optional(CONF_RESTART): button.restart_button_schema(RestartButton),
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(QueryButton),
}
+8 -8
View File
@@ -195,17 +195,17 @@ void LD2410Component::dump_config() {
#endif
#ifdef USE_SENSOR
ESP_LOGCONFIG(TAG, "Sensors:");
LOG_SENSOR_WITH_DEDUP(" ", "Light", this->light_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "DetectionDistance", this->detection_distance_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "MovingTargetDistance", this->moving_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "MovingTargetEnergy", this->moving_target_energy_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "StillTargetDistance", this->still_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "StillTargetEnergy", this->still_target_energy_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "Light", this->light_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "DetectionDistance", this->detection_distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetDistance", this->moving_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetEnergy", this->moving_target_energy_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetDistance", this->still_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetEnergy", this->still_target_energy_sensor_);
for (auto &s : this->gate_move_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "GateMove", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateMove", s);
}
for (auto &s : this->gate_still_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "GateStill", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateStill", s);
}
#endif
#ifdef USE_TEXT_SENSOR
+2 -2
View File
@@ -129,8 +129,8 @@ class LD2410Component final : public Component, public uart::UARTDevice {
std::array<number::Number *, TOTAL_GATES> gate_still_threshold_numbers_{};
#endif
#ifdef USE_SENSOR
std::array<sensor::SensorWithDedup<uint8_t>, TOTAL_GATES> gate_move_sensors_{};
std::array<sensor::SensorWithDedup<uint8_t>, TOTAL_GATES> gate_still_sensors_{};
std::array<SensorWithDedup<uint8_t>, TOTAL_GATES> gate_move_sensors_{};
std::array<SensorWithDedup<uint8_t>, TOTAL_GATES> gate_still_sensors_{};
#endif
};
+1 -1
View File
@@ -79,7 +79,7 @@ void LD2410SComponent::dump_config() {
LOG_BINARY_SENSOR(" ", "Has Target", this->target_binary_sensor_);
#endif
#ifdef USE_SENSOR
LOG_SENSOR_WITH_DEDUP(" ", "Distance", this->distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "Distance", this->distance_sensor_);
#endif
#ifdef USE_LD2410S_VERSION
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
+21 -4
View File
@@ -6,8 +6,13 @@ from esphome.const import (
CONF_FACTORY_RESET,
CONF_ID,
CONF_RESTART,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_DATABASE,
ICON_PULSE,
ICON_RESTART,
ICON_RESTART_ALERT,
)
from esphome.types import ConfigType
@@ -24,11 +29,23 @@ CONF_START_DYNAMIC_BACKGROUND_CORRECTION = "start_dynamic_background_correction"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
FactoryResetButton
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
FactoryResetButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_QUERY_PARAMS): button.button_schema(
QueryButton,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_DATABASE,
),
cv.Optional(CONF_RESTART): button.button_schema(
RestartButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_RESTART,
),
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(QueryButton),
cv.Optional(CONF_RESTART): button.restart_button_schema(RestartButton),
cv.Optional(CONF_START_DYNAMIC_BACKGROUND_CORRECTION): button.button_schema(
StartDynamicBackgroundCorrectionButton,
entity_category=ENTITY_CATEGORY_CONFIG,
+109 -56
View File
@@ -9,7 +9,6 @@
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/progmem.h"
#include <algorithm>
#include <cmath>
@@ -18,6 +17,23 @@ namespace esphome::ld2412 {
ESPHOME_LOG_TAG(TAG, "ld2412");
enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1,
BAUD_RATE_19200 = 2,
BAUD_RATE_38400 = 3,
BAUD_RATE_57600 = 4,
BAUD_RATE_115200 = 5,
BAUD_RATE_230400 = 6,
BAUD_RATE_256000 = 7,
BAUD_RATE_460800 = 8,
};
enum DistanceResolution : uint8_t {
DISTANCE_RESOLUTION_0_2 = 0x03,
DISTANCE_RESOLUTION_0_5 = 0x01,
DISTANCE_RESOLUTION_0_75 = 0x00,
};
enum LightFunction : uint8_t {
LIGHT_FUNCTION_OFF = 0x00,
LIGHT_FUNCTION_BELOW = 0x01,
@@ -66,18 +82,77 @@ enum AckData : uint8_t {
ACK_PAYLOAD = 10,
};
// Index + 1 is the BaudRate value; the select options use the same order
PROGMEM_STRING_TABLE(BaudRateStrings, "9600", "19200", "38400", "57600", "115200", "230400", "256000", "460800");
static_assert(BAUD_RATE_9600 == 1 && BAUD_RATE_460800 == BaudRateStrings::COUNT, "BaudRate is index + 1");
// Same order as DISTANCE_RESOLUTIONS
PROGMEM_STRING_TABLE(DistanceResolutionStrings, "0.2m", "0.5m", "0.75m");
// The light function select options are in enum order
static_assert(LIGHT_FUNCTION_OFF == 0 && LIGHT_FUNCTION_BELOW == 1 && LIGHT_FUNCTION_ABOVE == 2,
"LightFunction is the select option index");
// Device values of the out pin level select options, in option order
static constexpr uint8_t OUT_PIN_LEVELS[] PROGMEM = {OUT_PIN_LEVEL_LOW, OUT_PIN_LEVEL_HIGH};
// Memory-efficient lookup tables
struct StringToUint8 {
const char *str;
const uint8_t value;
};
static constexpr uint32_t BAUD_RATES[] PROGMEM = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800};
struct Uint8ToString {
const uint8_t value;
const char *str;
};
constexpr StringToUint8 BAUD_RATES_BY_STR[] = {
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
};
constexpr StringToUint8 DISTANCE_RESOLUTIONS_BY_STR[] = {
{"0.2m", DISTANCE_RESOLUTION_0_2},
{"0.5m", DISTANCE_RESOLUTION_0_5},
{"0.75m", DISTANCE_RESOLUTION_0_75},
};
constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[] = {
{DISTANCE_RESOLUTION_0_2, "0.2m"},
{DISTANCE_RESOLUTION_0_5, "0.5m"},
{DISTANCE_RESOLUTION_0_75, "0.75m"},
};
constexpr StringToUint8 LIGHT_FUNCTIONS_BY_STR[] = {
{"off", LIGHT_FUNCTION_OFF},
{"below", LIGHT_FUNCTION_BELOW},
{"above", LIGHT_FUNCTION_ABOVE},
};
constexpr Uint8ToString LIGHT_FUNCTIONS_BY_UINT[] = {
{LIGHT_FUNCTION_OFF, "off"},
{LIGHT_FUNCTION_BELOW, "below"},
{LIGHT_FUNCTION_ABOVE, "above"},
};
constexpr StringToUint8 OUT_PIN_LEVELS_BY_STR[] = {
{"low", OUT_PIN_LEVEL_LOW},
{"high", OUT_PIN_LEVEL_HIGH},
};
constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = {
{OUT_PIN_LEVEL_LOW, "low"},
{OUT_PIN_LEVEL_HIGH, "high"},
};
constexpr uint32_t BAUD_RATES[] = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800};
// Helper functions for lookups
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const char *str) {
for (const auto &entry : arr) {
if (strcmp(str, entry.str) == 0) {
return entry.value;
}
}
return 0xFF; // Not found
}
template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t value) {
for (const auto &entry : arr) {
if (value == entry.value) {
return entry.str;
}
}
return ""; // Not found
}
static constexpr uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Default used when number component is not defined
// Commands
@@ -145,17 +220,17 @@ void LD2412Component::dump_config() {
#endif
#ifdef USE_SENSOR
ESP_LOGCONFIG(TAG, "Sensors:");
LOG_SENSOR_WITH_DEDUP(" ", "Light", this->light_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "DetectionDistance", this->detection_distance_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "MovingTargetDistance", this->moving_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "MovingTargetEnergy", this->moving_target_energy_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "StillTargetDistance", this->still_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "StillTargetEnergy", this->still_target_energy_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "Light", this->light_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "DetectionDistance", this->detection_distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetDistance", this->moving_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetEnergy", this->moving_target_energy_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetDistance", this->still_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetEnergy", this->still_target_energy_sensor_);
for (auto &s : this->gate_still_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "GateStill", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateStill", s);
}
for (auto &s : this->gate_move_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "GateMove", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateMove", s);
}
#endif
#ifdef USE_TEXT_SENSOR
@@ -449,16 +524,11 @@ void LD2412Component::handle_ack_data_() {
if (this->buffer_pos_ < 11 + HEADER_FOOTER_SIZE) {
break; // too short to hold the resolution
}
const auto index = ld24xx::find_index(DISTANCE_RESOLUTIONS, this->buffer_data_[10]);
if (!index) {
ESP_LOGW(TAG, "Unknown distance resolution %u", this->buffer_data_[10]);
break;
}
ESP_LOGV(TAG, "Distance resolution: %s",
LOG_STR_ARG(DistanceResolutionStrings::get_log_str(*index, DistanceResolutionStrings::LAST_INDEX)));
const auto *distance_resolution = find_str(DISTANCE_RESOLUTIONS_BY_UINT, this->buffer_data_[10]);
ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution);
#ifdef USE_SELECT
if (this->distance_resolution_select_ != nullptr) {
this->distance_resolution_select_->publish_state(*index);
this->distance_resolution_select_->publish_state(distance_resolution);
}
#endif
break;
@@ -470,10 +540,11 @@ void LD2412Component::handle_ack_data_() {
}
this->light_function_ = this->buffer_data_[10];
this->light_threshold_ = this->buffer_data_[11];
ESP_LOGV(TAG, "Light function: %u, threshold: %u", this->light_function_, this->light_threshold_);
const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
ESP_LOGV(TAG, "Light function: %s, threshold: %u", light_function_str, this->light_threshold_);
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr) {
this->light_function_select_->publish_state(static_cast<size_t>(this->light_function_));
this->light_function_select_->publish_state(light_function_str);
}
#endif
#ifdef USE_NUMBER
@@ -590,10 +661,9 @@ void LD2412Component::handle_ack_data_() {
*/
this->out_pin_level_ = this->buffer_data_[14];
#ifdef USE_SELECT
const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
if (this->out_pin_level_select_ != nullptr) {
if (const auto index = ld24xx::find_index(OUT_PIN_LEVELS, this->out_pin_level_)) {
this->out_pin_level_select_->publish_state(*index);
}
this->out_pin_level_select_->publish_state(out_pin_level_str);
}
#endif
#endif
@@ -647,29 +717,15 @@ void LD2412Component::set_bluetooth(bool enable) {
}
void LD2412Component::set_distance_resolution(const char *state) {
const int16_t index = progmem_string_table_find<DistanceResolutionStrings>(state, strlen(state));
if (index >= 0) {
this->set_distance_resolution(static_cast<DistanceResolution>(progmem_read_byte(&DISTANCE_RESOLUTIONS[index])));
}
}
void LD2412Component::set_distance_resolution(DistanceResolution resolution) {
this->set_config_mode_(true);
const uint8_t cmd_value[6] = {resolution, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t cmd_value[6] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00, 0x00, 0x00, 0x00, 0x00};
this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, sizeof(cmd_value));
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
}
void LD2412Component::set_baud_rate(const char *state) {
const int16_t index = progmem_string_table_find<BaudRateStrings>(state, strlen(state));
if (index >= 0) {
this->set_baud_rate(static_cast<BaudRate>(index + 1));
}
}
void LD2412Component::set_baud_rate(BaudRate rate) {
this->set_config_mode_(true);
const uint8_t cmd_value[2] = {rate, 0x00};
const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00};
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value));
this->set_timeout(200, [this]() { this->restart_(); });
}
@@ -751,10 +807,9 @@ void LD2412Component::set_basic_config() {
#endif
#ifdef USE_SELECT
if (this->out_pin_level_select_ != nullptr) {
const auto index = this->out_pin_level_select_->active_index();
if (!index)
if (!this->out_pin_level_select_->has_state())
return;
out_pin_level = progmem_read_byte(&OUT_PIN_LEVELS[*index]);
out_pin_level = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str());
}
#endif
@@ -826,10 +881,8 @@ void LD2412Component::set_light_out_control() {
}
#endif
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr) {
if (const auto index = this->light_function_select_->active_index()) {
this->light_function_ = *index;
}
if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option().c_str());
}
#endif
uint8_t value[2] = {this->light_function_, this->light_threshold_};
+2 -25
View File
@@ -36,27 +36,6 @@ using namespace ld24xx;
static constexpr uint8_t MAX_LINE_LENGTH = 54; // Max characters for serial buffer
static constexpr uint8_t TOTAL_GATES = 14; // Total number of gates supported by the LD2412
enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1,
BAUD_RATE_19200 = 2,
BAUD_RATE_38400 = 3,
BAUD_RATE_57600 = 4,
BAUD_RATE_115200 = 5,
BAUD_RATE_230400 = 6,
BAUD_RATE_256000 = 7,
BAUD_RATE_460800 = 8,
};
enum DistanceResolution : uint8_t {
DISTANCE_RESOLUTION_0_2 = 0x03,
DISTANCE_RESOLUTION_0_5 = 0x01,
DISTANCE_RESOLUTION_0_75 = 0x00,
};
// Device values of the distance resolution select options, in option order; in flash on ESP8266
static constexpr uint8_t DISTANCE_RESOLUTIONS[] PROGMEM = {DISTANCE_RESOLUTION_0_2, DISTANCE_RESOLUTION_0_5,
DISTANCE_RESOLUTION_0_75};
class LD2412Component final : public Component, public uart::UARTDevice {
#ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(dynamic_background_correction_status)
@@ -124,9 +103,7 @@ class LD2412Component final : public Component, public uart::UARTDevice {
void restart_and_read_all_info();
void set_bluetooth(bool enable);
void set_distance_resolution(const char *state);
void set_distance_resolution(DistanceResolution resolution);
void set_baud_rate(const char *state);
void set_baud_rate(BaudRate rate);
void factory_reset();
void start_dynamic_background_correction();
@@ -172,8 +149,8 @@ class LD2412Component final : public Component, public uart::UARTDevice {
GateThresholds gate_still_thresholds_;
#endif
#ifdef USE_SENSOR
std::array<sensor::SensorWithDedup<uint8_t>, TOTAL_GATES> gate_move_sensors_{};
std::array<sensor::SensorWithDedup<uint8_t>, TOTAL_GATES> gate_still_sensors_{};
std::array<SensorWithDedup<uint8_t>, TOTAL_GATES> gate_move_sensors_{};
std::array<SensorWithDedup<uint8_t>, TOTAL_GATES> gate_still_sensors_{};
#endif
};
@@ -52,7 +52,6 @@ CONFIG_SCHEMA = {
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2412_ID])
selects = select.sub_selects(config, parent=hub)
# Option lists follow the order of the C++ enums and tables; the hub maps by index
await selects(
CONF_BAUD_RATE,
hub.set_baud_rate_select,
@@ -4,7 +4,7 @@ namespace esphome::ld2412 {
void BaudRateSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_baud_rate(static_cast<BaudRate>(index + 1));
this->parent_->set_baud_rate(this->option_at(index));
}
} // namespace esphome::ld2412
@@ -4,8 +4,7 @@ namespace esphome::ld2412 {
void DistanceResolutionSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_distance_resolution(
static_cast<DistanceResolution>(progmem_read_byte(&DISTANCE_RESOLUTIONS[index])));
this->parent_->set_distance_resolution(this->option_at(index));
}
} // namespace esphome::ld2412
+21 -4
View File
@@ -1,7 +1,16 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
from esphome.const import (
CONF_FACTORY_RESET,
CONF_ID,
CONF_RESTART,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_RESTART,
ICON_RESTART_ALERT,
)
from esphome.types import ConfigType
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
@@ -12,10 +21,18 @@ RestartButton = ld2450_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
FactoryResetButton
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
FactoryResetButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_RESTART): button.button_schema(
RestartButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_RESTART,
),
cv.Optional(CONF_RESTART): button.restart_button_schema(RestartButton),
}
+12 -12
View File
@@ -154,35 +154,35 @@ void LD2450Component::dump_config() {
#endif
#ifdef USE_SENSOR
ESP_LOGCONFIG(TAG, "Sensors:");
LOG_SENSOR_WITH_DEDUP(" ", "MovingTargetCount", this->moving_target_count_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "StillTargetCount", this->still_target_count_sensor_);
LOG_SENSOR_WITH_DEDUP(" ", "TargetCount", this->target_count_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetCount", this->moving_target_count_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetCount", this->still_target_count_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetCount", this->target_count_sensor_);
for (auto &s : this->move_x_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "TargetX", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetX", s);
}
for (auto &s : this->move_y_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "TargetY", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetY", s);
}
for (auto &s : this->move_angle_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "TargetAngle", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetAngle", s);
}
for (auto &s : this->move_distance_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "TargetDistance", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetDistance", s);
}
for (auto &s : this->move_resolution_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "TargetResolution", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetResolution", s);
}
for (auto &s : this->move_speed_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "TargetSpeed", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetSpeed", s);
}
for (auto &s : this->zone_target_count_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "ZoneTargetCount", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "ZoneTargetCount", s);
}
for (auto &s : this->zone_moving_target_count_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "ZoneMovingTargetCount", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "ZoneMovingTargetCount", s);
}
for (auto &s : this->zone_still_target_count_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "ZoneStillTargetCount", s);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "ZoneStillTargetCount", s);
}
#endif
#ifdef USE_TEXT_SENSOR
+9 -9
View File
@@ -213,15 +213,15 @@ class LD2450Component : public Component, public uart::UARTDevice {
ZoneOfNumbers zone_numbers_[MAX_ZONES];
#endif
#ifdef USE_SENSOR
std::array<sensor::SensorWithDedup<int16_t>, MAX_TARGETS> move_x_sensors_{};
std::array<sensor::SensorWithDedup<int16_t>, MAX_TARGETS> move_y_sensors_{};
std::array<sensor::SensorWithDedup<int16_t>, MAX_TARGETS> move_speed_sensors_{};
std::array<sensor::SensorWithDedup<float>, MAX_TARGETS> move_angle_sensors_{};
std::array<sensor::SensorWithDedup<uint16_t>, MAX_TARGETS> move_distance_sensors_{};
std::array<sensor::SensorWithDedup<uint16_t>, MAX_TARGETS> move_resolution_sensors_{};
std::array<sensor::SensorWithDedup<uint8_t>, MAX_ZONES> zone_target_count_sensors_{};
std::array<sensor::SensorWithDedup<uint8_t>, MAX_ZONES> zone_still_target_count_sensors_{};
std::array<sensor::SensorWithDedup<uint8_t>, MAX_ZONES> zone_moving_target_count_sensors_{};
std::array<SensorWithDedup<int16_t>, MAX_TARGETS> move_x_sensors_{};
std::array<SensorWithDedup<int16_t>, MAX_TARGETS> move_y_sensors_{};
std::array<SensorWithDedup<int16_t>, MAX_TARGETS> move_speed_sensors_{};
std::array<SensorWithDedup<float>, MAX_TARGETS> move_angle_sensors_{};
std::array<SensorWithDedup<uint16_t>, MAX_TARGETS> move_distance_sensors_{};
std::array<SensorWithDedup<uint16_t>, MAX_TARGETS> move_resolution_sensors_{};
std::array<SensorWithDedup<uint8_t>, MAX_ZONES> zone_target_count_sensors_{};
std::array<SensorWithDedup<uint8_t>, MAX_ZONES> zone_still_target_count_sensors_{};
std::array<SensorWithDedup<uint8_t>, MAX_ZONES> zone_moving_target_count_sensors_{};
#endif
#ifdef USE_TEXT_SENSOR
std::array<text_sensor::TextSensor *, MAX_TARGETS> direction_text_sensors_{};
+5 -5
View File
@@ -71,12 +71,12 @@ void LD2460Component::dump_config() {
: this->installation_mode_ == InstallationMode::INSTALLATION_MODE_TOP ? LOG_STR_LITERAL("top")
: LOG_STR_LITERAL("side"));
#ifdef USE_SENSOR
LOG_SENSOR_WITH_DEDUP(" ", "TargetCount", this->target_count_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetCount", this->target_count_sensor_);
for (const auto &t : this->target_sensors_) {
LOG_SENSOR_WITH_DEDUP(" ", "TargetX", t.x);
LOG_SENSOR_WITH_DEDUP(" ", "TargetY", t.y);
LOG_SENSOR_WITH_DEDUP(" ", "TargetDistance", t.distance);
LOG_SENSOR_WITH_DEDUP(" ", "TargetAngle", t.angle);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetX", t.x);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetY", t.y);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetDistance", t.distance);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "TargetAngle", t.angle);
}
#endif
}
+1 -1
View File
@@ -48,7 +48,7 @@ class LD2460Component : public Component, public uart::UARTDevice {
#ifdef USE_SENSOR
// INT16_MIN marks a slot with no published coordinates, so the next report always publishes
struct TargetSensors {
sensor::SensorWithDedup<float> x, y, distance, angle;
ld24xx::SensorWithDedup<float> x, y, distance, angle;
int16_t last_x{INT16_MIN};
int16_t last_y{INT16_MIN};
};
+8
View File
@@ -14,6 +14,11 @@
#include "esphome/components/sensor/sensor_with_dedup.h"
#endif
#define LOG_SENSOR_WITH_DEDUP_SAFE(tag, name, sensor) \
if ((sensor).has_sensor()) { \
LOG_SENSOR(tag, name, (sensor).get_sensor()); \
}
#define SAFE_PUBLISH_SENSOR(sensor, value) (sensor).publish_state_if_not_dup(value)
#define SAFE_PUBLISH_SENSOR_UNKNOWN(sensor) (sensor).publish_state_unknown()
@@ -124,4 +129,7 @@ void feed_frame_byte(uint8_t (&buffer)[N], uint8_t &pos, uint8_t byte, const uin
}
}
#ifdef USE_SENSOR
template<typename T> using SensorWithDedup = sensor::SensorWithDedup<T>;
#endif
} // namespace esphome::ld24xx
@@ -101,7 +101,8 @@ def final_validate(config: ConfigType) -> None:
)
if config.get(CONF_WAKE):
hub_config = fv.get_declaring_config(hub_id, full_config)
hub_path = full_config.get_path_for_id(hub_id)
hub_config = full_config.get_config_for_path(hub_path[:-1])
if hub_config.get(CONF_WAKEUP_PIN) is None:
raise cv.Invalid(
f"{CONF_WAKE} requires {CONF_WAKEUP_PIN} on the parent ld6002b component",
-14
View File
@@ -57,9 +57,6 @@ from .const import (
)
_LOGGER = logging.getLogger(__name__)
# Fault handlers the crash handler wraps (BK7238, BK7231N)
BK_TRAP_HANDLERS = ("bk_trap_udef", "bk_trap_pabt", "bk_trap_dabt")
CODEOWNERS = ["@kuba2k2"]
AUTO_LOAD = ["preferences"]
DOMAIN = "libretiny"
@@ -554,13 +551,6 @@ async def component_to_code(config):
# RAM-executable output section and prints a post-link placement summary.
if FAMILY_COMPONENT[config[CONF_FAMILY]] != COMPONENT_BK72XX:
cg.add_platformio_option("extra_scripts", ["pre:patch_linker.py"])
if config[CONF_FAMILY] in (FAMILY_BK7231N, FAMILY_BK7238):
# Crash handler (crash_handler.cpp, patch_noinit.py.script)
cg.add_define("USE_LIBRETINY_CRASH_HANDLER")
cg.add_define("USE_CRASH_HANDLER")
for trap in BK_TRAP_HANDLERS:
cg.add_build_flag(f"-Wl,--wrap={trap}")
cg.add_platformio_option("extra_scripts", ["pre:patch_noinit.py"])
# Match the 1-byte enums of the LN882H SDK's prebuilt WiFi library.
if FAMILY_COMPONENT[config[CONF_FAMILY]] == COMPONENT_LN882X:
cg.add_build_unflag("-fno-short-enums")
@@ -668,8 +658,4 @@ def copy_files() -> None:
script_dir / "scons_dont_inherit.py.script",
CORE.relative_build_path("scons_dont_inherit.py"),
)
copy_file_if_changed(
script_dir / "patch_noinit.py.script",
CORE.relative_build_path("patch_noinit.py"),
)
copy_ccache_script()
@@ -1,217 +0,0 @@
#ifdef USE_BK72XX
#include "esphome/core/defines.h"
#ifdef USE_LIBRETINY_CRASH_HANDLER
#include "crash_handler.h"
#include "core.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <cinttypes>
#include <cstddef>
#include <cstdint>
#include <cstdio>
extern "C" {
#include <arm_mcu_pub.h> // MCU_REG_BACKUP_SP_*
#include <intc.h> // struct arm_registers
}
// On a fault the SDK (boot_handlers.S, PUSH_SVC_REG) builds a struct arm_registers frame and branches
// to bk_trap_udef/pabt/dabt, which record the crash type, print a dump sdk_silent hides and spin for
// the watchdog. The wrappers (-Wl,--wrap) first save the fault location and a stack scan for the
// next boot. pc is the exception LR; PUSH_SVC_REG stores pc and spsr at these word offsets.
static_assert(sizeof(arm_registers) == 18 * sizeof(uint32_t), "SDK exception frame changed size");
static_assert(offsetof(arm_registers, pc) == 15 * 4 && offsetof(arm_registers, spsr) == 16 * 4,
"SDK exception frame layout changed");
static_assert(LT_HW_WATCHDOG, "the crash reboot relies on the hardware watchdog");
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
extern "C" {
void __real_bk_trap_udef(arm_registers *regs);
void __real_bk_trap_pabt(arm_registers *regs);
void __real_bk_trap_dabt(arm_registers *regs);
// Bounds of the app's flash image and its ITCM code (linker symbols).
extern char _vector_start[];
extern char _data_flash_begin[];
extern char _itcmcode_ram_begin[];
extern char _itcmcode_ram_end[];
}
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
namespace esphome::libretiny {
ESPHOME_LOG_TAG(TAG, "libretiny.crash");
// 0xDEAD sentinel, then the record layout version.
static constexpr uint32_t CRASH_MAGIC_V1 = 0xDEAD0001;
static constexpr uint32_t CRASH_REASON_UNDEFINED = 1;
static constexpr uint32_t CRASH_REASON_PREFETCH_ABORT = 2;
static constexpr uint32_t CRASH_REASON_DATA_ABORT = 3;
PROGMEM_STRING_TABLE(CrashReasonStrings, "Unknown", "Undefined instruction", "Prefetch abort", "Data abort");
// With Wi-Fi up the boot-time watchdog no longer fires: any interrupts-off spin hangs until power is
// cycled (BK7238 and BK7231N; cause not in the open SDK). Re-armed here, long enough for the SDK's
// register dump when sdk_silent is off.
static constexpr uint32_t CRASH_REBOOT_WDT_MS = 500;
static constexpr size_t MAX_BACKTRACE = 8;
// Words scanned above sp for return addresses.
static constexpr size_t STACK_SCAN_WORDS = 64;
// SRAM: 192 KB on the BK7231N, 256 KB on the BK7238.
static constexpr uint32_t SRAM_START = 0x00400000;
#ifdef USE_LIBRETINY_VARIANT_BK7231N
static constexpr uint32_t SRAM_END = 0x00430000;
#else
static constexpr uint32_t SRAM_END = 0x00440000;
#endif
static constexpr uint32_t CPSR_MODE_MASK = 0x1F;
static constexpr uint32_t CPSR_MODE_USR = 0x10;
static constexpr uint32_t CPSR_MODE_FIQ = 0x11;
static constexpr uint32_t CPSR_MODE_IRQ = 0x12;
static constexpr uint32_t CPSR_MODE_SVC = 0x13;
static constexpr uint32_t CPSR_MODE_ABT = 0x17;
static constexpr uint32_t CPSR_MODE_UND = 0x1B;
static constexpr uint32_t CPSR_MODE_SYS = 0x1F;
static constexpr uint32_t CPSR_THUMB = 1u << 5;
struct CrashRecord {
uint32_t magic;
uint32_t reason;
uint32_t pc;
uint32_t lr;
uint32_t sp;
uint32_t backtrace[MAX_BACKTRACE];
};
// Kept in ITCM, see patch_noinit.py.script.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
__attribute__((section(".noinit"))) static CrashRecord s_record;
static bool s_record_read = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_record_valid = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
bool crash_handler_has_data() {
if (!s_record_read) {
s_record_read = true;
s_record_valid = s_record.magic == CRASH_MAGIC_V1;
// Clear the magic so the record is reported once.
s_record.magic = 0;
}
return s_record_valid;
}
// One ESP_LOGE per line: the CLI decodes per line and partial output still shows.
void crash_handler_log() {
if (!crash_handler_has_data())
return;
ESP_LOGE(TAG, "*** CRASH DETECTED ON PREVIOUS BOOT ***");
ESP_LOGE(TAG, " Reason: %s", LOG_STR_ARG(CrashReasonStrings::get_log_str(s_record.reason, 0)));
ESP_LOGE(TAG, " PC: 0x%08" PRIX32 " (fault location)", s_record.pc);
ESP_LOGE(TAG, " LR: 0x%08" PRIX32 " (return address)", s_record.lr);
ESP_LOGE(TAG, " SP: 0x%08" PRIX32, s_record.sp);
uint8_t count = 0;
for (; count < MAX_BACKTRACE && s_record.backtrace[count] != 0; count++) {
ESP_LOGE(TAG, " BT%d: 0x%08" PRIX32 " (stack backtrace)", count, s_record.backtrace[count]);
}
// addr2line hint with every address
char hint[160];
int pos = snprintf(hint, sizeof(hint), "Use: addr2line -pfiaC -e raw_firmware.elf 0x%08" PRIX32 " 0x%08" PRIX32,
s_record.pc, s_record.lr);
for (uint8_t i = 0; i < count && pos < (int) sizeof(hint) - 12; i++) {
pos += snprintf(hint + pos, sizeof(hint) - pos, " 0x%08" PRIX32, s_record.backtrace[i]);
}
ESP_LOGE(TAG, "%s", hint);
}
// --- Crash time: exception mode, interrupts masked, a few dozen bytes of stack: no printing, no
// large locals.
static inline bool is_code_addr(uint32_t val) {
uint32_t addr = val & ~1u; // clear the Thumb bit of a return address
return (addr >= (uint32_t) _vector_start && addr < (uint32_t) _data_flash_begin) ||
(addr >= (uint32_t) _itcmcode_ram_begin && addr < (uint32_t) _itcmcode_ram_end);
}
// PUSH_ALL_ARM_REG (boot_handlers.S) saves every mode's registers at the arm_mcu_pub.h addresses
// before bk_trap_*; lr (r14) follows sp (r13).
struct SavedBank {
uint32_t mode;
uint32_t sp_addr;
};
static constexpr SavedBank SAVED_BANKS[] = {
{CPSR_MODE_SYS, MCU_REG_BACKUP_SP_SYS}, {CPSR_MODE_IRQ, MCU_REG_BACKUP_SP_IRQ},
{CPSR_MODE_FIQ, MCU_REG_BACKUP_SP_FIQ}, {CPSR_MODE_ABT, MCU_REG_BACKUP_SP_ABT},
{CPSR_MODE_UND, MCU_REG_BACKUP_SP_UND}, {CPSR_MODE_SVC, MCU_REG_BACKUP_SP_SVC},
};
// sp and lr stay 0 for an unknown mode (corrupt spsr).
static void read_saved_sp_lr(uint32_t mode, uint32_t *sp, uint32_t *lr) {
for (const auto &bank : SAVED_BANKS) {
if (bank.mode != mode)
continue;
const auto *regs = (const volatile uint32_t *) bank.sp_addr; // NOLINT(performance-no-int-to-ptr)
*sp = regs[0];
*lr = regs[1];
return;
}
}
static void record_crash(arm_registers *regs, uint32_t reason, uint32_t pc_offset) {
uint32_t mode = regs->spsr & CPSR_MODE_MASK;
if (mode == CPSR_MODE_USR)
mode = CPSR_MODE_SYS; // same register bank
uint32_t sp = 0;
uint32_t lr = 0;
read_saved_sp_lr(mode, &sp, &lr);
s_record.reason = reason;
s_record.pc = regs->pc - pc_offset;
s_record.lr = lr;
s_record.sp = sp;
// Return addresses above sp give the backtrace.
uint32_t count = 0;
if (sp >= SRAM_START && sp < SRAM_END && (sp & 3) == 0) {
auto *scan = (const uint32_t *) sp; // NOLINT(performance-no-int-to-ptr)
auto *end = (const uint32_t *) SRAM_END; // NOLINT(performance-no-int-to-ptr)
for (size_t i = 0; i < STACK_SCAN_WORDS && scan < end && count < MAX_BACKTRACE; i++, scan++) {
uint32_t val = *scan;
if (is_code_addr(val) && val != lr && (val & ~1u) != s_record.pc)
s_record.backtrace[count++] = val;
}
}
if (count < MAX_BACKTRACE)
s_record.backtrace[count] = 0; // terminator, the reader stops at the first 0
s_record.magic = CRASH_MAGIC_V1;
lt_wdt_enable(CRASH_REBOOT_WDT_MS);
}
// The exception LR is past the fault: one instruction (undefined), 4 bytes (prefetch), 8 (data abort).
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
extern "C" void __wrap_bk_trap_udef(arm_registers *regs) {
record_crash(regs, CRASH_REASON_UNDEFINED, (regs->spsr & CPSR_THUMB) ? 2 : 4);
__real_bk_trap_udef(regs);
}
extern "C" void __wrap_bk_trap_pabt(arm_registers *regs) {
record_crash(regs, CRASH_REASON_PREFETCH_ABORT, 4);
__real_bk_trap_pabt(regs);
}
extern "C" void __wrap_bk_trap_dabt(arm_registers *regs) {
record_crash(regs, CRASH_REASON_DATA_ABORT, 8);
__real_bk_trap_dabt(regs);
}
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
} // namespace esphome::libretiny
#endif // USE_LIBRETINY_CRASH_HANDLER
#endif // USE_BK72XX
@@ -1,20 +0,0 @@
#pragma once
#ifdef USE_BK72XX
#include "esphome/core/defines.h"
#ifdef USE_LIBRETINY_CRASH_HANDLER
namespace esphome::libretiny {
/// Log crash data if a crash was detected on previous boot.
void crash_handler_log();
/// Returns true if crash data was found this boot, reading it first if needed.
bool crash_handler_has_data();
} // namespace esphome::libretiny
#endif // USE_LIBRETINY_CRASH_HANDLER
#endif // USE_BK72XX
@@ -149,11 +149,6 @@ COMPONENT_CODEOWNERS = {
"ln882x": ["@lamauny"],
}
# Extra imports per component; the CLI looks up process_stacktrace on the platform package.
COMPONENT_EXTRA_IMPORTS = {
"bk72xx": "from esphome.stacktrace import process_crash_handler_line as process_stacktrace # noqa: F401",
}
def subst(code: str, key: str, value: str) -> str:
return code.replace(f"{{{key}}}", value)
@@ -230,8 +225,6 @@ def write_component_code(
import_lines = "\n".join(
f"from .{m} import {', '.join(sorted(v))}" for m, v in imports.items() if v
)
if extra := COMPONENT_EXTRA_IMPORTS.get(component):
import_lines = f"{extra}\n{import_lines}"
code = subst_many(
code,
("IMPORTS", import_lines),
@@ -1,79 +0,0 @@
# pylint: disable=E0602
Import("env") # noqa
import os
import re
import subprocess
# Adds a .noinit section for the crash record (crash_handler.cpp) to the
# BK7238 / BK7231N linker script, in ITCM after .itcm.code: ITCM survives
# the watchdog reset and the SDK startup only copies .itcm.code there, while
# the bootloader clears at least 128 KB of SRAM and the SDK's saved registers
# on every boot (measured on a BK7238). The LMA stays in ITCM. BK7231T/Q/7251
# have no ITCM region, so libretiny/__init__.py loads this for two families.
_MARKER = "/* esphome .noinit */"
_ITCM_CODE_END = re.compile(r"(\}\s*>\s*itcm\s+AT\s*>\s*flash[^\n]*\n)")
_NOINIT_BLOCK = (
"\t.noinit (NOLOAD) : " + _MARKER + "\n"
"\t{\n"
"\t\t. = ALIGN(8);\n"
"\t\tKEEP(*(.noinit .noinit.*))\n"
"\t} > itcm AT> itcm\n"
)
def _pre_link(target, source, env):
build_dir = env.subst("$BUILD_DIR")
found = False
for name in os.listdir(build_dir):
if not name.endswith(".ld"):
continue
path = os.path.join(build_dir, name)
with open(path, "r", encoding="utf-8") as fh:
content = fh.read()
if _MARKER in content:
found = True
continue
new_content = _ITCM_CODE_END.sub(
lambda m: m.group(1) + _NOINIT_BLOCK, content, count=1
)
if new_content == content:
continue
with open(path, "w", encoding="utf-8") as fh:
fh.write(new_content)
print("ESPHome: patched {} (.noinit section)".format(name))
found = True
if not found:
raise RuntimeError(
"ESPHome: no .ld in {} got the .noinit section; the crash record "
"would not survive a reboot. Update the regex in "
"patch_noinit.py.script for this LibreTiny version.".format(build_dir)
)
ITCM_START = 0x003F0000
ITCM_END = 0x00400000
def _post_link(target, source, env):
"""Fail unless the linked ELF keeps .noinit in ITCM (stale marker, renamed script)."""
elf = env.subst("$BUILD_DIR/${PROGNAME}.elf")
objdump = re.sub(r"(gcc-)?nm$", "objdump", env.subst("$NM")) # same toolchain prefix
headers = subprocess.check_output([objdump, "-h", elf], text=True)
for line in headers.splitlines():
fields = line.split()
if len(fields) > 3 and fields[1] == ".noinit":
vma = int(fields[3], 16)
if ITCM_START <= vma < ITCM_END:
return
raise RuntimeError(
"ESPHome: .noinit landed at 0x{:08x}, outside ITCM; the crash "
"record would not survive a reboot.".format(vma)
)
raise RuntimeError("ESPHome: the linked ELF has no .noinit section.")
# LibreTiny renders the .ld into $BUILD_DIR after this script loads; patch at link time.
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", _pre_link)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", _post_link)
+4 -2
View File
@@ -380,7 +380,8 @@ def _final_validate(config: ConfigType) -> None:
for ref in refs:
try:
light_config = fv.get_declaring_config(ref.light_id, fconf)
light_path = fconf.get_path_for_id(ref.light_id)[:-1]
light_config = fconf.get_config_for_path(light_path)
except KeyError:
# Light ID not found — ID validation will have already reported this
continue
@@ -397,7 +398,8 @@ def _final_validate(config: ConfigType) -> None:
for ref in cycle_refs:
try:
light_config = fv.get_declaring_config(ref.light_id, fconf)
light_path = fconf.get_path_for_id(ref.light_id)[:-1]
light_config = fconf.get_config_for_path(light_path)
except KeyError:
continue
+2 -2
View File
@@ -34,7 +34,6 @@ from esphome.const import (
)
from esphome.core import CORE, ID, EsphomeError, Lambda
from esphome.cpp_generator import MockObj, TemplateArgsType
import esphome.final_validate as fv
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType
@@ -254,7 +253,8 @@ def _resolve_effect_index(config: ConfigType, original_name: str) -> int:
from . import effect_not_found_message, find_effect_index
light_id = config[CONF_ID]
light_config = fv.get_declaring_config(light_id, CORE.config)
light_path = CORE.config.get_path_for_id(light_id)[:-1]
light_config = CORE.config.get_config_for_path(light_path)
effects = light_config.get(CONF_EFFECTS, [])
index = find_effect_index(effects, original_name)
if index is not None:
+7 -2
View File
@@ -1,7 +1,6 @@
#pragma once
#include "light_color_values.h"
#include "esphome/core/helpers.h"
#include "esphome/core/string_ref.h"
namespace esphome {
@@ -230,7 +229,13 @@ class LightCall {
inline bool get_save_() { return (this->flags_ & FLAG_SAVE) != 0; }
// Helper to set flag - defaults to true for common case
void set_flag_(FieldFlags flag, bool value = true) ESPHOME_ALWAYS_INLINE { assign_bits(this->flags_, flag, value); }
void set_flag_(FieldFlags flag, bool value = true) ESPHOME_ALWAYS_INLINE {
if (value) {
this->flags_ |= flag;
} else {
this->flags_ &= ~flag;
}
}
// Helper to clear flag - reduces code size for common case
void clear_flag_(FieldFlags flag) ESPHOME_ALWAYS_INLINE { this->flags_ &= ~flag; }
@@ -2,9 +2,6 @@
#include "logger.h"
#include "esphome/components/libretiny/core.h"
#ifdef USE_LIBRETINY_CRASH_HANDLER
#include "esphome/components/libretiny/crash_handler.h"
#endif
namespace esphome::logger {
@@ -55,9 +52,6 @@ void Logger::pre_setup() {
global_logger = this;
ESP_LOGI(TAG, "Log initialized");
#ifdef USE_LIBRETINY_CRASH_HANDLER
libretiny::crash_handler_log();
#endif
}
const LogString *Logger::get_uart_selection_() {
@@ -74,11 +68,6 @@ const LogString *Logger::get_uart_selection_() {
}
}
#ifdef USE_LIBRETINY_CRASH_HANDLER
bool crash_log_pending() { return libretiny::crash_handler_has_data(); }
void log_crash_to_client() { libretiny::crash_handler_log(); }
#endif
} // namespace esphome::logger
#endif // USE_LIBRETINY
@@ -1,7 +1,6 @@
#include "media_player.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
@@ -78,12 +77,19 @@ const char *media_player_command_to_string(MediaPlayerCommand command) {
}
void MediaPlayerTraits::set_supports_pause(bool supports_pause) {
assign_bits(this->feature_flags_, MediaPlayerEntityFeature::PAUSE | MediaPlayerEntityFeature::PLAY, supports_pause);
if (supports_pause) {
this->feature_flags_ |= MediaPlayerEntityFeature::PAUSE | MediaPlayerEntityFeature::PLAY;
} else {
this->feature_flags_ &= ~(MediaPlayerEntityFeature::PAUSE | MediaPlayerEntityFeature::PLAY);
}
}
void MediaPlayerTraits::set_supports_turn_off_on(bool supports_turn_off_on) {
assign_bits(this->feature_flags_, MediaPlayerEntityFeature::TURN_OFF | MediaPlayerEntityFeature::TURN_ON,
supports_turn_off_on);
if (supports_turn_off_on) {
this->feature_flags_ |= MediaPlayerEntityFeature::TURN_OFF | MediaPlayerEntityFeature::TURN_ON;
} else {
this->feature_flags_ &= ~(MediaPlayerEntityFeature::TURN_OFF | MediaPlayerEntityFeature::TURN_ON);
}
}
void MediaPlayerCall::validate_() {
@@ -41,7 +41,9 @@ CONFIG_SCHEMA = cv.All(
def _final_validate(config: ConfigType) -> None:
display_config = fv.get_declaring_config(config[CONF_DISPLAY_ID])
full_config = fv.full_config.get()
display_path = full_config.get_path_for_id(config[CONF_DISPLAY_ID])[:-1]
display_config = full_config.get_config_for_path(display_path)
if CONF_BRIGHTNESS not in display_config:
model = display_config[CONF_MODEL]
if model == "CUSTOM":
@@ -310,7 +310,9 @@ def migrate_custom_command(config: ConfigType) -> None:
frame = config.get(CONF_CUSTOM_COMMAND)
if frame is None:
return
controller = fv.get_declaring_config(config[CONF_MODBUS_CONTROLLER_ID])
fconf = fv.full_config.get()
path = fconf.get_path_for_id(config[CONF_MODBUS_CONTROLLER_ID])[:-1]
controller = fconf.get_config_for_path(path)
# the controller's DEVICE address (from modbus_device_schema)
address = controller[CONF_ADDRESS]
if frame[0] != address:
@@ -340,7 +342,9 @@ def _reject_continuous_write_custom_pdu(config: ConfigType) -> None:
pdu = config.get(CONF_CUSTOM_PDU)
if pdu is None or not modbus.is_function_code_write(pdu[0]):
return
controller = fv.get_declaring_config(config[CONF_MODBUS_CONTROLLER_ID])
fconf = fv.full_config.get()
path = fconf.get_path_for_id(config[CONF_MODBUS_CONTROLLER_ID])[:-1]
controller = fconf.get_config_for_path(path)
if controller.get(CONF_CONTINUOUS) is True:
raise cv.Invalid(
f"a '{CONF_CUSTOM_PDU}' with a write function code (0x{pdu[0] & 0x7F:02X}) can't be polled "
@@ -355,7 +359,9 @@ def _reject_broadcastable_custom_pdu(config: ConfigType) -> None:
pdu = config.get(CONF_CUSTOM_PDU)
if pdu is None or not modbus.is_function_code_broadcastable(pdu[0]):
return
controller = fv.get_declaring_config(config[CONF_MODBUS_CONTROLLER_ID])
fconf = fv.full_config.get()
path = fconf.get_path_for_id(config[CONF_MODBUS_CONTROLLER_ID])[:-1]
controller = fconf.get_config_for_path(path)
if (
controller.get(CONF_ADDRESS) == modbus.BROADCAST_ADDRESS
and controller.get(modbus.CONF_ALLOW_BROADCAST_READ) is True
@@ -378,7 +384,9 @@ def validate_custom_pdu_item(config: ConfigType) -> None:
def _reject_write_option_off_broadcast(config: ConfigType) -> None:
if not any(config.get(key) is True for key in modbus.broadcast_only_option_keys()):
return
controller = fv.get_declaring_config(config[CONF_MODBUS_CONTROLLER_ID])
fconf = fv.full_config.get()
path = fconf.get_path_for_id(config[CONF_MODBUS_CONTROLLER_ID])[:-1]
controller = fconf.get_config_for_path(path)
if controller.get(CONF_ADDRESS) != modbus.BROADCAST_ADDRESS:
raise cv.Invalid(
f"'{modbus.CONF_EXPECT_BROADCAST_WRITE_RESPONSE}' only applies when the "
+3 -1
View File
@@ -50,7 +50,9 @@ MAX_RECOMMENDED_UPDATE_INTERVAL_MS = 100
def get_motion_config(motion_id: ID) -> dict:
"""Look up the (already-validated) config of the motion hub referenced by motion_id."""
return fv.get_declaring_config(motion_id)
full_config = fv.full_config.get()
motion_path = full_config.get_path_for_id(motion_id)[:-1]
return full_config.get_config_for_path(motion_path)
def check_update_interval(motion_id: ID, feature_name: str) -> None:
+2 -1
View File
@@ -67,7 +67,8 @@ def _final_validate(config: ConfigType) -> None:
"Max touch channel must equal the highest binary sensor channel or be removed for auto calculation",
path=[CONF_MAX_TOUCH_CHANNEL],
)
this_config = fv.get_declaring_config(config[CONF_ID], fconf)
path = fconf.get_path_for_id(config[CONF_ID])[:-1]
this_config = fconf.get_config_for_path(path)
this_config[CONF_MAX_TOUCH_CHANNEL] = max_touch_channel
+5 -2
View File
@@ -3,7 +3,6 @@
#include <cstdint>
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::mpr121 {
@@ -85,7 +84,11 @@ bool MPR121Component::digital_read(uint8_t pin) { return (this->gpio_input_ & (1
void MPR121Component::digital_write(uint8_t pin, bool value) {
const uint8_t ionum = pin - 4;
assign_bits(this->gpio_output_, 1 << ionum, value);
if (value) {
this->gpio_output_ |= (1 << ionum);
} else {
this->gpio_output_ &= ~(1 << ionum);
}
this->flush_gpio_();
}
+33 -6
View File
@@ -32,22 +32,49 @@ bool MPU6050Component::setup_registers_() {
}
ESP_LOGV(TAG, " Setting up Power Management");
// Clock source X-Gyro, sleep off, temperature on
if (!this->update_register(MPU6050_REGISTER_POWER_MANAGEMENT_1,
0b111 | (1 << MPU6050_BIT_SLEEP_ENABLED) | (1 << MPU6050_BIT_TEMPERATURE_DISABLED),
MPU6050_CLOCK_SOURCE_X_GYRO)) {
// Setup power management
uint8_t power_management;
if (!this->read_byte(MPU6050_REGISTER_POWER_MANAGEMENT_1, &power_management)) {
return false;
}
ESP_LOGV(TAG, " Input power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management));
// Set clock source - X-Gyro
power_management &= 0b11111000;
power_management |= MPU6050_CLOCK_SOURCE_X_GYRO;
// Disable sleep
power_management &= ~(1 << MPU6050_BIT_SLEEP_ENABLED);
// Enable temperature
power_management &= ~(1 << MPU6050_BIT_TEMPERATURE_DISABLED);
ESP_LOGV(TAG, " Output power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management));
if (!this->write_byte(MPU6050_REGISTER_POWER_MANAGEMENT_1, power_management)) {
return false;
}
ESP_LOGV(TAG, " Setting up Gyro Config");
// Set scale - 2000DPS
if (!this->update_register(MPU6050_REGISTER_GYRO_CONFIG, 0b00011000, MPU6050_SCALE_2000_DPS << 3)) {
uint8_t gyro_config;
if (!this->read_byte(MPU6050_REGISTER_GYRO_CONFIG, &gyro_config)) {
return false;
}
ESP_LOGV(TAG, " Input gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config));
gyro_config &= 0b11100111;
gyro_config |= MPU6050_SCALE_2000_DPS << 3;
ESP_LOGV(TAG, " Output gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config));
if (!this->write_byte(MPU6050_REGISTER_GYRO_CONFIG, gyro_config)) {
return false;
}
ESP_LOGV(TAG, " Setting up Accel Config");
// Set range - 2G
return this->update_register(MPU6050_REGISTER_ACCEL_CONFIG, 0b00011000, MPU6050_RANGE_2G << 3);
uint8_t accel_config;
if (!this->read_byte(MPU6050_REGISTER_ACCEL_CONFIG, &accel_config)) {
return false;
}
ESP_LOGV(TAG, " Input accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config));
accel_config &= 0b11100111;
accel_config |= (MPU6050_RANGE_2G << 3);
ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config));
return this->write_byte(MPU6050_REGISTER_ACCEL_CONFIG, accel_config);
}
void MPU6050Component::dump_config() {
ESP_LOGCONFIG(TAG, "MPU6050:");
+33 -6
View File
@@ -36,22 +36,49 @@ bool MPU6886Component::setup_registers_() {
}
ESP_LOGV(TAG, " Setting up Power Management");
// Clock source X-Gyro, sleep off, temperature on
if (!this->update_register(MPU6886_REGISTER_POWER_MANAGEMENT_1,
0b111 | (1 << MPU6886_BIT_SLEEP_ENABLED) | (1 << MPU6886_BIT_TEMPERATURE_DISABLED),
MPU6886_CLOCK_SOURCE_X_GYRO)) {
// Setup power management
uint8_t power_management;
if (!this->read_byte(MPU6886_REGISTER_POWER_MANAGEMENT_1, &power_management)) {
return false;
}
ESP_LOGV(TAG, " Input power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management));
// Set clock source - X-Gyro
power_management &= 0b11111000;
power_management |= MPU6886_CLOCK_SOURCE_X_GYRO;
// Disable sleep
power_management &= ~(1 << MPU6886_BIT_SLEEP_ENABLED);
// Enable temperature
power_management &= ~(1 << MPU6886_BIT_TEMPERATURE_DISABLED);
ESP_LOGV(TAG, " Output power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management));
if (!this->write_byte(MPU6886_REGISTER_POWER_MANAGEMENT_1, power_management)) {
return false;
}
ESP_LOGV(TAG, " Setting up Gyroscope Config");
// Set scale - 2000DPS
if (!this->update_register(MPU6886_REGISTER_GYRO_CONFIG, 0b00011000, MPU6886_SCALE_2000_DPS << 3)) {
uint8_t gyro_config;
if (!this->read_byte(MPU6886_REGISTER_GYRO_CONFIG, &gyro_config)) {
return false;
}
ESP_LOGV(TAG, " Input gyroscope_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config));
gyro_config &= 0b11100111;
gyro_config |= MPU6886_SCALE_2000_DPS << 3;
ESP_LOGV(TAG, " Output gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config));
if (!this->write_byte(MPU6886_REGISTER_GYRO_CONFIG, gyro_config)) {
return false;
}
ESP_LOGV(TAG, " Setting up Accelerometer Config");
// Set range - 2G
return this->update_register(MPU6886_REGISTER_ACCEL_CONFIG, 0b00011000, MPU6886_RANGE_2G << 3);
uint8_t accel_config;
if (!this->read_byte(MPU6886_REGISTER_ACCEL_CONFIG, &accel_config)) {
return false;
}
ESP_LOGV(TAG, " Input accelerometer_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config));
accel_config &= 0b11100111;
accel_config |= (MPU6886_RANGE_2G << 3);
ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config));
return this->write_byte(MPU6886_REGISTER_ACCEL_CONFIG, accel_config);
}
void MPU6886Component::dump_config() {
+15 -5
View File
@@ -168,6 +168,11 @@ ValueRangeTrigger = number_ns.class_(
"ValueRangeTrigger", automation.Trigger.template(cg.float_), cg.Component
)
# Conditions
NumberInRangeCondition = number_ns.class_(
"NumberInRangeCondition", automation.Condition
)
NumberMode = number_ns.enum("NumberMode")
# Schema default that also matches the C++ initializer in number_traits.h; codegen
@@ -342,12 +347,17 @@ NUMBER_IN_RANGE_CONDITION_SCHEMA = cv.All(
)
automation.register_apply_condition(
"number.in_range",
NUMBER_IN_RANGE_CONDITION_SCHEMA,
automation.IN_RANGE_CHECK,
defaults=automation.IN_RANGE_DEFAULTS,
@automation.register_condition(
"number.in_range", NumberInRangeCondition, NUMBER_IN_RANGE_CONDITION_SCHEMA
)
async def number_in_range_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(condition_id, template_arg, paren)
cg.add_optional(var.set_min, config.get(CONF_ABOVE))
cg.add_optional(var.set_max, config.get(CONF_BELOW))
return var
@coroutine_with_priority(CoroPriority.CORE)
+23
View File
@@ -26,4 +26,27 @@ class ValueRangeTrigger final : public Trigger<float>, public Component {
TemplatableFn<float, float> max_{[](float) -> float { return NAN; }};
};
template<typename... Ts> class NumberInRangeCondition final : public Condition<Ts...> {
public:
NumberInRangeCondition(Number *parent) : parent_(parent) {}
void set_min(float min) { this->min_ = min; }
void set_max(float max) { this->max_ = max; }
bool check(const Ts &...x) override {
const float state = this->parent_->state;
if (std::isnan(this->min_)) {
return state <= this->max_;
} else if (std::isnan(this->max_)) {
return state >= this->min_;
} else {
return this->min_ <= state && state <= this->max_;
}
}
protected:
Number *parent_;
float min_{NAN};
float max_{NAN};
};
} // namespace esphome::number
@@ -48,7 +48,9 @@ def _final_validate(config: ConfigType) -> None:
if config[CONF_TYPE] != CONF_STATUS:
# Only run this validation if a status sensor is being configured
return
transport_config = fv.get_declaring_config(config[CONF_TRANSPORT_ID])
full_config = fv.full_config.get()
transport_path = full_config.get_path_for_id(config[CONF_TRANSPORT_ID])[:-1]
transport_config = full_config.get_config_for_path(transport_path)
if transport_config[CONF_PING_PONG_ENABLE] and any(
CONF_ENCRYPTION in p
for p in transport_config[CONF_PROVIDERS]
+4 -1
View File
@@ -35,8 +35,11 @@ def validate_from_to(value: ConfigType) -> ConfigType:
def validate_segment(config: ConfigType) -> None:
fconf = fv.full_config.get()
if CONF_ID in config: # only validate addressable segments
segment_light_config = fv.get_declaring_config(config[CONF_ID])
path = fconf.get_path_for_id(config[CONF_ID])[:-1]
segment_light_config = fconf.get_config_for_path(path)
if CONF_NUM_LEDS in segment_light_config:
segment_len = segment_light_config[CONF_NUM_LEDS]
@@ -46,7 +46,11 @@ class ProvisioningManager : public Component {
void set_source_provisioned(uint8_t source, bool provisioned) {
if (source >= MAX_SOURCES)
return;
assign_bits(this->provisioned_mask_, 1UL << source, provisioned);
if (provisioned) {
this->provisioned_mask_ |= (1UL << source);
} else {
this->provisioned_mask_ &= ~(1UL << source);
}
}
// True once every registered source is provisioned. Config validation guarantees
+47 -1
View File
@@ -1,7 +1,9 @@
from collections.abc import Callable
import logging
from pathlib import Path
import re
from string import ascii_letters, digits
import subprocess
from typing import Any
import esphome.codegen as cg
@@ -33,7 +35,6 @@ from esphome.core import (
from esphome.core.config import BOARD_MAX_LENGTH
from esphome.helpers import copy_file_if_changed, read_file, write_file_if_changed
from esphome.platformio.toolchain import copy_ccache_script
from esphome.stacktrace import process_crash_handler_line as process_stacktrace # noqa: F401
from esphome.storage_json import StorageJSON
from esphome.types import ConfigType
@@ -655,3 +656,48 @@ def copy_files() -> None:
path = CORE.relative_src_path("esphome.h")
content = read_file(path).rstrip("\n")
write_file_if_changed(path, content + '\n#include "pio_includes.h"\n')
# RP2040 crash handler stacktrace decoding
# Matches output from esphome/components/rp2/crash_handler.cpp
_CRASH_RE = re.compile(r"CRASH DETECTED ON PREVIOUS BOOT")
_CRASH_ADDR_RE = re.compile(
r"(?:PC|LR|BT\d):\s+(0x[0-9a-fA-F]{8})\s+\((?:fault location|return address|stack backtrace)\)"
)
def _addr2line(tool: str, elf: Path, addr: str) -> str:
try:
result = subprocess.run(
[tool, "-pfiaC", "-e", str(elf), addr],
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except (OSError, subprocess.CalledProcessError):
return f"{addr} (decode failed)"
def process_stacktrace(config: ConfigType, line: str, backtrace_state: bool) -> bool:
"""Decode RP2040 crash handler output using addr2line."""
if _CRASH_RE.search(line):
_LOGGER.error("RP2040 crash detected - decoding addresses")
return True
if backtrace_state:
if match := _CRASH_ADDR_RE.search(line):
from esphome.platformio.toolchain import get_idedata
idedata = get_idedata(config)
if idedata.addr2line_path:
elf = idedata.firmware_elf_path
if elf.exists():
decoded = _addr2line(idedata.addr2line_path, elf, match.group(1))
_LOGGER.error(" %s => %s", match.group(1), decoded)
# Stop backtrace state after addr2line hint (last line of crash dump)
if "addr2line" in line:
return False
return backtrace_state
+11 -5
View File
@@ -323,6 +323,7 @@ ToNTCResistanceFilter = sensor_ns.class_("ToNTCResistanceFilter", Filter)
ToNTCTemperatureFilter = sensor_ns.class_("ToNTCTemperatureFilter", Filter)
RHCorrectionFilter = sensor_ns.class_("RHCorrectionFilter", Filter)
CalibratePolynomialFilter = sensor_ns.class_("CalibratePolynomialFilter", Filter)
SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter)
ClampFilter = sensor_ns.class_("ClampFilter", Filter)
RoundFilter = sensor_ns.class_("RoundFilter", Filter)
RoundMultipleFilter = sensor_ns.class_("RoundMultipleFilter", Filter)
@@ -1123,12 +1124,17 @@ SENSOR_IN_RANGE_CONDITION_SCHEMA = cv.All(
)
automation.register_apply_condition(
"sensor.in_range",
SENSOR_IN_RANGE_CONDITION_SCHEMA,
automation.IN_RANGE_CHECK,
defaults=automation.IN_RANGE_DEFAULTS,
@automation.register_condition(
"sensor.in_range", SensorInRangeCondition, SENSOR_IN_RANGE_CONDITION_SCHEMA
)
async def sensor_in_range_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(condition_id, template_arg, paren)
cg.add_optional(var.set_min, config.get(CONF_ABOVE))
cg.add_optional(var.set_max, config.get(CONF_BELOW))
return var
def validate_ntc_calibration_parameter(value):
+23
View File
@@ -58,4 +58,27 @@ class ValueRangeTrigger final : public Trigger<float>, public Component {
TemplatableFn<float, float> max_{[](float) -> float { return NAN; }};
};
template<typename... Ts> class SensorInRangeCondition final : public Condition<Ts...> {
public:
SensorInRangeCondition(Sensor *parent) : parent_(parent) {}
void set_min(float min) { this->min_ = min; }
void set_max(float max) { this->max_ = max; }
bool check(const Ts &...x) override {
const float state = this->parent_->state;
if (std::isnan(this->min_)) {
return state <= this->max_;
} else if (std::isnan(this->max_)) {
return state >= this->min_;
} else {
return this->min_ <= state && state <= this->max_;
}
}
protected:
Sensor *parent_;
float min_{NAN};
float max_{NAN};
};
} // namespace esphome::sensor
@@ -62,7 +62,11 @@ int BSDSocketImpl::setblocking(bool blocking) {
if (fl < 0) {
return fl;
}
assign_bits(fl, O_NONBLOCK, !blocking);
if (blocking) {
fl &= ~O_NONBLOCK;
} else {
fl |= O_NONBLOCK;
}
return ::fcntl(this->fd_, F_SETFL, fl);
}
@@ -52,7 +52,11 @@ int LwIPSocketImpl::setblocking(bool blocking) {
if (fl < 0) {
return fl;
}
assign_bits(fl, O_NONBLOCK, !blocking);
if (blocking) {
fl &= ~O_NONBLOCK;
} else {
fl |= O_NONBLOCK;
}
return lwip_fcntl(this->fd_, F_SETFL, fl);
}
+2 -35
View File
@@ -21,24 +21,8 @@ PlayAction = speaker_ns.class_(
)
def get_audio_dac_ids(config):
"""Return the audio DAC ids of a speaker config: none, the single id, or the list."""
audio_dac_ids = config.get(CONF_AUDIO_DAC)
if audio_dac_ids is None:
return []
return audio_dac_ids if isinstance(audio_dac_ids, list) else [audio_dac_ids]
async def setup_speaker_core_(var, config):
if audio_dacs := get_audio_dac_ids(config):
dacs = [await cg.get_variable(dac_id) for dac_id in audio_dacs]
table = cg.shared_progmem_array(
"speaker_audio_dacs",
audio_dac.AudioDac.operator("ptr"),
cg.ArrayInitializer(*dacs),
constexpr=False,
)
cg.add(var.set_audio_dacs(table, len(dacs)))
await cg.add_variable(var.set_audio_dac, config.get(CONF_AUDIO_DAC))
async def register_speaker(var, config):
@@ -47,26 +31,9 @@ async def register_speaker(var, config):
await setup_speaker_core_(var, config)
def _unique_audio_dacs(value):
# a repeated DAC would get every volume, mute and audio start write twice
if len(set(value)) != len(value):
raise cv.Invalid("Each audio_dac can only be listed once")
return value
SPEAKER_SCHEMA = cv.Schema.extend(audio.AUDIO_COMPONENT_SCHEMA).extend(
{
# one id as before (kept as a single value), or a list of DACs fed by this speaker's audio output that all
# get the same volume and mute
# the list comes first so a bad list reports its own error
cv.Optional(CONF_AUDIO_DAC): cv.Any(
cv.All(
[cv.use_id(audio_dac.AudioDac)],
cv.Length(min=1),
_unique_audio_dacs,
),
cv.use_id(audio_dac.AudioDac),
),
cv.Optional(CONF_AUDIO_DAC): cv.use_id(audio_dac.AudioDac),
}
)
+14 -33
View File
@@ -69,15 +69,15 @@ class Speaker {
bool is_stopped() const { return this->state_ == STATE_STOPPED; }
// Volume and mute are independent: changing one never alters the other's stored state. Volume control is
// handled by the configured audio dac components, every one of them getting the same volume and mute. Individual
// speaker components can override and implement in software if an audio dac isn't available.
// handled by a configured audio dac component. Individual speaker components can override and implement in
// software if an audio dac isn't available.
virtual void set_volume(float volume) {
this->volume_ = volume;
#ifdef USE_AUDIO_DAC
for (auto *audio_dac : this->audio_dacs_) {
audio_dac->set_volume(volume);
if (this->audio_dac_ != nullptr) {
this->audio_dac_->set_volume(volume);
this->apply_audio_dac_mute_();
}
this->apply_audio_dac_mute_();
#endif
};
virtual float get_volume() { return this->volume_; }
@@ -91,14 +91,7 @@ class Speaker {
virtual bool get_mute_state() { return this->mute_state_; }
#ifdef USE_AUDIO_DAC
/// Sets a single audio dac (nullptr for none), replacing any list; keeps its own copy of the pointer.
void set_audio_dac(audio_dac::AudioDac *audio_dac) {
this->audio_dacs_.assign_copy(&audio_dac, audio_dac != nullptr ? 1 : 0);
}
/// Codegen only: the table must outlive this speaker. Every DAC listed is fed by this speaker's audio output.
void set_audio_dacs(audio_dac::AudioDac *const *audio_dacs, size_t count) {
this->audio_dacs_.assign_static(audio_dacs, count);
}
void set_audio_dac(audio_dac::AudioDac *audio_dac) { this->audio_dac_ = audio_dac; }
#endif
void set_audio_stream_info(const audio::AudioStreamInfo &audio_stream_info) {
@@ -121,25 +114,14 @@ class Speaker {
bool is_silent_() const { return this->mute_state_ || this->volume_ < SILENT_VOLUME_THRESHOLD; }
#ifdef USE_AUDIO_DAC
/// @brief Whether volume and mute are handled in hardware by at least one audio dac.
bool has_audio_dac_() const { return !this->audio_dacs_.empty(); }
/// @brief Uses the audio dacs' mute as the silence mechanism, since a dac's minimum volume is often audible.
/// @brief Uses the audio dac's mute as the silence mechanism, since a dac's minimum volume is often audible.
void apply_audio_dac_mute_() {
const bool silent = this->is_silent_();
for (auto *audio_dac : this->audio_dacs_) {
if (silent) {
audio_dac->set_mute_on();
} else {
audio_dac->set_mute_off();
}
}
}
/// @brief Tells every audio dac that its audio clocks are running; call from the main loop.
void notify_audio_dacs_started_() {
for (auto *audio_dac : this->audio_dacs_) {
audio_dac->on_audio_started();
if (this->audio_dac_ == nullptr)
return;
if (this->is_silent_()) {
this->audio_dac_->set_mute_on();
} else {
this->audio_dac_->set_mute_off();
}
}
#endif
@@ -150,8 +132,7 @@ class Speaker {
float volume_{1.0f};
#ifdef USE_AUDIO_DAC
// all audio dacs fed by this speaker's audio output (eg two amplifiers on one I2S bus); empty without an audio dac
ConstVector<audio_dac::AudioDac *, true> audio_dacs_;
audio_dac::AudioDac *audio_dac_{nullptr};
#endif
CallbackManager<void(uint32_t, int64_t)> audio_output_callback_{};
+30 -11
View File
@@ -153,6 +153,7 @@ void SX1509Component::pin_mode(uint8_t pin, gpio::Flags flags) {
void SX1509Component::setup_led_driver(uint8_t pin) {
uint16_t temp_word = 0;
uint8_t temp_byte = 0;
this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word);
temp_word |= (1 << pin);
@@ -161,11 +162,16 @@ void SX1509Component::setup_led_driver(uint8_t pin) {
this->ddr_mask_ &= ~(1 << pin); // 0=output
this->write_byte_16(REG_DIR_B, this->ddr_mask_);
// Internal 2MHz oscillator: set bit 6, clear bit 5
this->update_register(REG_CLOCK, (1 << 6) | (1 << 5), 1 << 6);
this->read_byte(REG_CLOCK, &temp_byte);
temp_byte |= (1 << 6); // Internal 2MHz oscillator part 1 (set bit 6)
temp_byte &= ~(1 << 5); // Internal 2MHz oscillator part 2 (clear bit 5)
this->write_byte(REG_CLOCK, temp_byte);
// Linear mode for banks A (bit 3) and B (bit 7); LED driver clock ClkX of all IOs (bits 6:4)
this->update_register(REG_MISC, (1 << 7) | (1 << 3) | 0x70, 0x70);
this->read_byte(REG_MISC, &temp_byte);
temp_byte &= ~(1 << 7); // set linear mode bank B
temp_byte &= ~(1 << 3); // set linear mode bank A
temp_byte |= 0x70; // Frequency of the LED Driver clock ClkX of all IOs:
this->write_byte(REG_MISC, temp_byte);
this->read_byte_16(REG_LED_DRIVER_ENABLE_B, &temp_word);
temp_word |= (1 << pin);
@@ -188,7 +194,11 @@ void SX1509Component::clock_(uint8_t osc_source, uint8_t osc_pin_function, uint8
this->clk_x_ = 2000000;
osc_divider = (osc_divider & 0b111) << 4; // 3-bit value, bits 6:4
this->update_register(REG_MISC, 0b111 << 4, osc_divider);
uint8_t reg_misc = 0;
this->read_byte(REG_MISC, &reg_misc);
reg_misc &= ~(0b111 << 4);
reg_misc |= osc_divider;
this->write_byte(REG_MISC, reg_misc);
}
void SX1509Component::setup_keypad_() {
@@ -202,11 +212,15 @@ void SX1509Component::setup_keypad_() {
this->ddr_mask_ |= (1 << i);
this->write_byte_16(REG_DIR_B, this->ddr_mask_);
const uint8_t row_bits = (1 << this->rows_) - 1;
this->update_register(REG_OPEN_DRAIN_A, row_bits, row_bits);
this->read_byte(REG_OPEN_DRAIN_A, &temp_byte);
for (int i = 0; i < this->rows_; i++)
temp_byte |= (1 << i);
this->write_byte(REG_OPEN_DRAIN_A, temp_byte);
const uint8_t col_bits = (1 << this->cols_) - 1;
this->update_register(REG_PULL_UP_B, col_bits, col_bits);
this->read_byte(REG_PULL_UP_B, &temp_byte);
for (int i = 0; i < this->cols_; i++)
temp_byte |= (1 << i);
this->write_byte(REG_PULL_UP_B, temp_byte);
if (debounce_time_ >= scan_time_) {
debounce_time_ = scan_time_ >> 1; // Force debounce_time to be less than scan_time
@@ -235,8 +249,13 @@ uint16_t SX1509Component::read_key_data() {
void SX1509Component::set_debounce_config_(uint8_t config_value) {
// First make sure clock is configured
this->update_register(REG_MISC, 1 << 4, 1 << 4); // Just default to no divider if not set
this->update_register(REG_CLOCK, 1 << 6, 1 << 6); // default to internal osc.
uint8_t temp_byte = 0;
this->read_byte(REG_MISC, &temp_byte);
temp_byte |= (1 << 4); // Just default to no divider if not set
this->write_byte(REG_MISC, temp_byte);
this->read_byte(REG_CLOCK, &temp_byte);
temp_byte |= (1 << 6); // default to internal osc.
this->write_byte(REG_CLOCK, temp_byte);
config_value &= 0b111; // 3-bit value
this->write_byte(REG_DEBOUNCE_CONFIG, config_value);
+7 -4
View File
@@ -45,10 +45,13 @@ bool SY6970Component::write_register_(uint8_t reg, uint8_t value) {
}
bool SY6970Component::update_register_(uint8_t reg, uint8_t mask, uint8_t value) {
if (this->update_register(reg, mask, value))
return true;
ESP_LOGW(TAG, "Failed to update register 0x%02X", reg);
return false;
uint8_t reg_value;
if (!this->read_byte(reg, &reg_value)) {
ESP_LOGW(TAG, "Failed to read register 0x%02X for update", reg);
return false;
}
reg_value = (reg_value & ~mask) | (value & mask);
return this->write_register_(reg, reg_value);
}
void SY6970Component::setup() {
+6 -4
View File
@@ -159,10 +159,12 @@ bool TAS2780::select_page_(uint8_t page) {
}
bool TAS2780::update_bits_(uint8_t reg, uint8_t mask, uint8_t value) {
if (this->select_page_(TAS2780_PAGE_0) && this->update_register(reg, mask, value))
return true;
ESP_LOGW(TAG, "Failed to update register 0x%02X", reg);
return false;
uint8_t current;
if (!this->select_page_(TAS2780_PAGE_0) || !this->read_byte(reg, &current)) {
ESP_LOGE(TAG, "Failed to read register 0x%02X", reg);
return false;
}
return this->write_byte(reg, (current & ~mask) | (value & mask));
}
bool TAS2780::init_() {
+3 -1
View File
@@ -56,7 +56,9 @@ CONFIG_SCHEMA = cv.Schema(
def _final_validate(config: ConfigType) -> ConfigType:
hub_conf = fv.get_declaring_config(config[CONF_TAS58XX_ID])
fconf = fv.full_config.get()
hub_path = fconf.get_path_for_id(config[CONF_TAS58XX_ID])
hub_conf = fconf.get_config_for_path(hub_path[:-1])
if hub_conf[CONF_MODEL] == DAC_TAS5825M:
return config
@@ -65,7 +65,9 @@ CONFIG_SCHEMA = cv.All(
def _final_validate(config: ConfigType) -> None:
"""Resolve the key to the number the device reports, checked against the keypad's matrix."""
keypad = fv.get_declaring_config(config[CONF_KEYPAD_ID])
full_config = fv.full_config.get()
keypad_path = full_config.get_path_for_id(config[CONF_KEYPAD_ID])[:-1]
keypad = full_config.get_config_for_path(keypad_path)
rows = keypad.get(CONF_ROWS, 0)
columns = keypad.get(CONF_COLUMNS, 0)
+22 -3
View File
@@ -1,7 +1,16 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_FACTORY_RESET, CONF_RESTART
from esphome.const import (
CONF_ADDRESS,
CONF_FACTORY_RESET,
CONF_RESTART,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_RESTART,
ICON_RESTART_ALERT,
)
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -14,8 +23,18 @@ RestartButton = tfluna_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_TFLUNA_ID): cv.use_id(TFLunaComponent),
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(ResetButton),
cv.Optional(CONF_RESTART): button.restart_button_schema(RestartButton),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
ResetButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_RESTART): button.button_schema(
RestartButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_RESTART,
),
}
@@ -59,7 +59,9 @@ CONFIG_SCHEMA = cv.Schema(
def _final_validate(config: ConfigType) -> ConfigType:
if CONF_ACTUAL_CURRENT not in config:
return config
if fv.get_declaring_config(config[CONF_TMC2209_ID]).get(CONF_ANALOG_CURRENT_SCALE):
full_config = fv.full_config.get()
path = full_config.get_path_for_id(config[CONF_TMC2209_ID])[:-1]
if full_config.get_config_for_path(path).get(CONF_ANALOG_CURRENT_SCALE):
raise cv.Invalid(
f"'{CONF_ACTUAL_CURRENT}' can't be used with '{CONF_ANALOG_CURRENT_SCALE}', "
"the current then depends on VREF",
+2 -1
View File
@@ -56,7 +56,8 @@ _validate_unique = unique_child("number")
def _final_validate(config: ConfigType) -> ConfigType:
_validate_unique(config)
parent = fv.get_declaring_config(config[CONF_TMP102_ID])
full = fv.full_config.get()
parent = full.get_config_for_path(full.get_path_for_id(config[CONF_TMP102_ID])[:-1])
add_number_parent(config[CONF_TMP102_ID], parent.get(CONF_EXTENDED_MODE, False))
return config
@@ -162,7 +162,11 @@ void HostUartComponent::setup() {
break;
};
// Set stop bits
assign_bits(options.c_cflag, CSTOPB, this->stop_bits_ == 2);
if (this->stop_bits_ == 2) {
options.c_cflag |= CSTOPB;
} else {
options.c_cflag &= ~CSTOPB;
}
cfsetispeed(&options, baud);
cfsetospeed(&options, baud);
tcsetattr(this->file_descriptor_, TCSANOW, &options);
+8 -2
View File
@@ -17,8 +17,14 @@ void UFireECComponent::setup() {
ESP_LOGI(TAG, "Found ufire_ec board version 0x%02X", version);
// Write option for temperature adjustments
const bool compensate = this->temperature_sensor_ != nullptr || this->temperature_sensor_external_ != nullptr;
this->update_register(REGISTER_CONFIG, CONFIG_TEMP_COMPENSATION, compensate ? CONFIG_TEMP_COMPENSATION : 0);
uint8_t config;
this->read_byte(REGISTER_CONFIG, &config);
if (this->temperature_sensor_ == nullptr && this->temperature_sensor_external_ == nullptr) {
config &= ~CONFIG_TEMP_COMPENSATION;
} else {
config |= CONFIG_TEMP_COMPENSATION;
}
this->write_byte(REGISTER_CONFIG, config);
// Update temperature compensation
this->set_compensation_(this->temperature_compensation_);
+8 -2
View File
@@ -18,8 +18,14 @@ void UFireISEComponent::setup() {
ESP_LOGI(TAG, "Found uFire_ise board version 0x%02X", version);
// Write option for temperature adjustments
const bool compensate = this->temperature_sensor_ != nullptr || this->temperature_sensor_external_ != nullptr;
this->update_register(REGISTER_CONFIG, CONFIG_TEMP_COMPENSATION, compensate ? CONFIG_TEMP_COMPENSATION : 0);
uint8_t config;
this->read_byte(REGISTER_CONFIG, &config);
if (this->temperature_sensor_ == nullptr && this->temperature_sensor_external_ == nullptr) {
config &= ~CONFIG_TEMP_COMPENSATION;
} else {
config |= CONFIG_TEMP_COMPENSATION;
}
this->write_byte(REGISTER_CONFIG, config);
}
void UFireISEComponent::update() {
@@ -5,7 +5,6 @@
#include "esphome/core/application.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/progmem.h"
#include "esphome/core/helpers.h"
#include <cmath>
#include <cstring>
@@ -60,13 +59,21 @@ WaterHeaterCall &WaterHeaterCall::set_target_temperature_high(float temperature)
}
WaterHeaterCall &WaterHeaterCall::set_away(bool away) {
assign_bits(this->state_, WATER_HEATER_STATE_AWAY, away);
if (away) {
this->state_ |= WATER_HEATER_STATE_AWAY;
} else {
this->state_ &= ~WATER_HEATER_STATE_AWAY;
}
this->state_mask_ |= WATER_HEATER_STATE_AWAY;
return *this;
}
WaterHeaterCall &WaterHeaterCall::set_on(bool on) {
assign_bits(this->state_, WATER_HEATER_STATE_ON, on);
if (on) {
this->state_ |= WATER_HEATER_STATE_ON;
} else {
this->state_ &= ~WATER_HEATER_STATE_ON;
}
this->state_mask_ |= WATER_HEATER_STATE_ON;
return *this;
}
Loaded 100 of 134 files, more files were not shown because too many files have changed in this diff. Show more