mirror of
https://github.com/esphome/esphome.git
synced 2026-09-29 16:00:23 +00:00
[core][sensor][binary_sensor][text_sensor][button][switch][number][select][debug][xiaomi_rtcgq02lm][tfluna][ld2412] Bind sub entity config and parent once with sub_* helpers (#19754)
This commit is contained in:
@@ -322,14 +322,23 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
var = await switch.new_switch(config)
|
||||
```
|
||||
|
||||
- **Optional child entities of a hub:** use `new_sub_sensor`, `new_sub_binary_sensor` or
|
||||
`new_sub_text_sensor`. Each creates the entity only when its key is configured and passes it to
|
||||
the setter. Always name the setter explicitly; never build it with `getattr` and an f-string.
|
||||
- **Optional child entities of a hub:** bind the config once with `sensor.sub_sensors(config)` (or
|
||||
`sub_binary_sensors`, `sub_text_sensors`, `sub_buttons`, `sub_switches`, `sub_numbers`,
|
||||
`sub_selects` in their domains), adding `parent=hub` for entities that derive from `Parented<T>`,
|
||||
then make one call per key, even when there is only one. A call creates the entity only when its key
|
||||
is configured, passes it to the setter and returns it (or `None`); extra arguments such as
|
||||
`min_value` or `options` go on the call. Always name the setter explicitly on the object that owns
|
||||
it, never with `getattr` and an f-string, and keep that variable short (`var` for the component
|
||||
itself, `hub` for one fetched with `cg.get_variable`) so the calls fit on one line. Loops whose
|
||||
setter also takes an index, such as `set_gate_threshold(x, n)`, stay as they are.
|
||||
```python
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await sensor.new_sub_sensor(config, CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensor.new_sub_sensor(config, CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
buttons = button.sub_buttons(config, parent=var)
|
||||
await buttons(CONF_RESTART, var.set_restart_button)
|
||||
```
|
||||
|
||||
* **Automations (Triggers, Actions, Conditions):**
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from collections.abc import Callable
|
||||
from logging import getLogger
|
||||
|
||||
from esphome import automation, core
|
||||
@@ -62,16 +61,16 @@ from esphome.const import (
|
||||
DEVICE_CLASS_VIBRATION,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
new_sub_entity,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.types import ConfigType, Expression, SafeExpType
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -635,14 +634,11 @@ async def new_binary_sensor(config, *args):
|
||||
return var
|
||||
|
||||
|
||||
async def new_sub_binary_sensor(
|
||||
config: ConfigType,
|
||||
key: str,
|
||||
setter: Callable[[MockObj], Expression],
|
||||
*args: SafeExpType,
|
||||
) -> MockObj | None:
|
||||
"""Create the binary sensor configured under key, if any, and pass it to setter."""
|
||||
return await new_sub_entity(new_binary_sensor, config, key, setter, *args)
|
||||
def sub_binary_sensors(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_binary_sensor."""
|
||||
return SubEntities(new_binary_sensor, config, parent)
|
||||
|
||||
|
||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
|
||||
|
||||
@@ -16,8 +16,9 @@ from esphome.const import (
|
||||
DEVICE_CLASS_RESTART,
|
||||
DEVICE_CLASS_UPDATE,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
@@ -114,6 +115,13 @@ async def new_button(config: ConfigType, *args: SafeExpType) -> MockObj:
|
||||
return var
|
||||
|
||||
|
||||
def sub_buttons(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_button."""
|
||||
return SubEntities(new_button, config, parent)
|
||||
|
||||
|
||||
BUTTON_PRESS_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Button),
|
||||
|
||||
@@ -106,20 +106,13 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
hub = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
|
||||
await sensor.new_sub_sensor(config, CONF_FREE, debug_component.set_free_sensor)
|
||||
await sensor.new_sub_sensor(config, CONF_BLOCK, debug_component.set_block_sensor)
|
||||
await sensor.new_sub_sensor(
|
||||
config, CONF_FRAGMENTATION, debug_component.set_fragmentation_sensor
|
||||
)
|
||||
await sensor.new_sub_sensor(
|
||||
config, CONF_MIN_FREE, debug_component.set_min_free_sensor
|
||||
)
|
||||
await sensor.new_sub_sensor(
|
||||
config, CONF_LOOP_TIME, debug_component.set_loop_time_sensor
|
||||
)
|
||||
await sensor.new_sub_sensor(config, CONF_PSRAM, debug_component.set_psram_sensor)
|
||||
await sensor.new_sub_sensor(
|
||||
config, CONF_CPU_FREQUENCY, debug_component.set_cpu_frequency_sensor
|
||||
)
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_FREE, hub.set_free_sensor)
|
||||
await sensors(CONF_BLOCK, hub.set_block_sensor)
|
||||
await sensors(CONF_FRAGMENTATION, hub.set_fragmentation_sensor)
|
||||
await sensors(CONF_MIN_FREE, hub.set_min_free_sensor)
|
||||
await sensors(CONF_LOOP_TIME, hub.set_loop_time_sensor)
|
||||
await sensors(CONF_PSRAM, hub.set_psram_sensor)
|
||||
await sensors(CONF_CPU_FREQUENCY, hub.set_cpu_frequency_sensor)
|
||||
|
||||
@@ -31,11 +31,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
hub = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
|
||||
await text_sensor.new_sub_text_sensor(
|
||||
config, CONF_DEVICE, debug_component.set_device_info_sensor
|
||||
)
|
||||
await text_sensor.new_sub_text_sensor(
|
||||
config, CONF_RESET_REASON, debug_component.set_reset_reason_sensor
|
||||
)
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_DEVICE, hub.set_device_info_sensor)
|
||||
await text_sensors(CONF_RESET_REASON, hub.set_reset_reason_sensor)
|
||||
|
||||
@@ -50,24 +50,12 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if dynamic_background_correction_status_config := config.get(
|
||||
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS
|
||||
):
|
||||
sens = await binary_sensor.new_binary_sensor(
|
||||
dynamic_background_correction_status_config
|
||||
)
|
||||
cg.add(
|
||||
LD2412_component.set_dynamic_background_correction_status_binary_sensor(
|
||||
sens
|
||||
)
|
||||
)
|
||||
if has_target_config := config.get(CONF_HAS_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_target_config)
|
||||
cg.add(LD2412_component.set_target_binary_sensor(sens))
|
||||
if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_moving_target_config)
|
||||
cg.add(LD2412_component.set_moving_target_binary_sensor(sens))
|
||||
if has_still_target_config := config.get(CONF_HAS_STILL_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_still_target_config)
|
||||
cg.add(LD2412_component.set_still_target_binary_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(
|
||||
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS,
|
||||
hub.set_dynamic_background_correction_status_binary_sensor,
|
||||
)
|
||||
await binary_sensors(CONF_HAS_TARGET, hub.set_target_binary_sensor)
|
||||
await binary_sensors(CONF_HAS_MOVING_TARGET, hub.set_moving_target_binary_sensor)
|
||||
await binary_sensors(CONF_HAS_STILL_TARGET, hub.set_still_target_binary_sensor)
|
||||
|
||||
@@ -56,22 +56,12 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if factory_reset_config := config.get(CONF_FACTORY_RESET):
|
||||
b = await button.new_button(factory_reset_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_factory_reset_button(b))
|
||||
if query_params_config := config.get(CONF_QUERY_PARAMS):
|
||||
b = await button.new_button(query_params_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_query_button(b))
|
||||
if restart_config := config.get(CONF_RESTART):
|
||||
b = await button.new_button(restart_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_restart_button(b))
|
||||
if start_dynamic_background_correction_config := config.get(
|
||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION
|
||||
):
|
||||
b = await button.new_button(start_dynamic_background_correction_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_start_dynamic_background_correction_button(b))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
await buttons(CONF_QUERY_PARAMS, hub.set_query_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
await buttons(
|
||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION,
|
||||
hub.set_start_dynamic_background_correction_button,
|
||||
)
|
||||
|
||||
@@ -87,42 +87,45 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if light_threshold_config := config.get(CONF_LIGHT_THRESHOLD):
|
||||
n = await number.new_number(
|
||||
light_threshold_config, min_value=0, max_value=255, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_light_threshold_number(n))
|
||||
if max_distance_gate_config := config.get(CONF_MAX_DISTANCE_GATE):
|
||||
n = await number.new_number(
|
||||
max_distance_gate_config, min_value=2, max_value=13, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_max_distance_gate_number(n))
|
||||
if min_distance_gate_config := config.get(CONF_MIN_DISTANCE_GATE):
|
||||
n = await number.new_number(
|
||||
min_distance_gate_config, min_value=1, max_value=12, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_min_distance_gate_number(n))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
numbers = number.sub_numbers(config, parent=hub)
|
||||
await numbers(
|
||||
CONF_LIGHT_THRESHOLD,
|
||||
hub.set_light_threshold_number,
|
||||
min_value=0,
|
||||
max_value=255,
|
||||
step=1,
|
||||
)
|
||||
await numbers(
|
||||
CONF_MAX_DISTANCE_GATE,
|
||||
hub.set_max_distance_gate_number,
|
||||
min_value=2,
|
||||
max_value=13,
|
||||
step=1,
|
||||
)
|
||||
await numbers(
|
||||
CONF_MIN_DISTANCE_GATE,
|
||||
hub.set_min_distance_gate_number,
|
||||
min_value=1,
|
||||
max_value=12,
|
||||
step=1,
|
||||
)
|
||||
for x in range(14):
|
||||
if gate_conf := config.get(f"gate_{x}"):
|
||||
move_config = gate_conf[CONF_MOVE_THRESHOLD]
|
||||
n = cg.new_Pvariable(move_config[CONF_ID])
|
||||
await number.register_number(
|
||||
n, move_config, min_value=0, max_value=100, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_gate_move_threshold_number(x, n))
|
||||
n = await number.new_number(move_config, min_value=0, max_value=100, step=1)
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_move_threshold_number(x, n))
|
||||
still_config = gate_conf[CONF_STILL_THRESHOLD]
|
||||
n = cg.new_Pvariable(still_config[CONF_ID])
|
||||
await number.register_number(
|
||||
n, still_config, min_value=0, max_value=100, step=1
|
||||
n = await number.new_number(
|
||||
still_config, min_value=0, max_value=100, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_gate_still_threshold_number(x, n))
|
||||
if timeout_config := config.get(CONF_TIMEOUT):
|
||||
n = await number.new_number(timeout_config, min_value=0, max_value=900, step=1)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_timeout_number(n))
|
||||
await cg.register_parented(n, hub)
|
||||
cg.add(hub.set_gate_still_threshold_number(x, n))
|
||||
await numbers(
|
||||
CONF_TIMEOUT,
|
||||
hub.set_timeout_number,
|
||||
min_value=0,
|
||||
max_value=900,
|
||||
step=1,
|
||||
)
|
||||
|
||||
@@ -50,36 +50,34 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if baud_rate_config := config.get(CONF_BAUD_RATE):
|
||||
s = await select.new_select(
|
||||
baud_rate_config,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_baud_rate_select(s))
|
||||
if distance_resolution_config := config.get(CONF_DISTANCE_RESOLUTION):
|
||||
s = await select.new_select(
|
||||
distance_resolution_config, options=["0.2m", "0.5m", "0.75m"]
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_distance_resolution_select(s))
|
||||
if light_function_config := config.get(CONF_LIGHT_FUNCTION):
|
||||
s = await select.new_select(
|
||||
light_function_config, options=["off", "below", "above"]
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_light_function_select(s))
|
||||
if out_pin_level_config := config.get(CONF_OUT_PIN_LEVEL):
|
||||
s = await select.new_select(out_pin_level_config, options=["low", "high"])
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_out_pin_level_select(s))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
selects = select.sub_selects(config, parent=hub)
|
||||
await selects(
|
||||
CONF_BAUD_RATE,
|
||||
hub.set_baud_rate_select,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
await selects(
|
||||
CONF_DISTANCE_RESOLUTION,
|
||||
hub.set_distance_resolution_select,
|
||||
options=["0.2m", "0.5m", "0.75m"],
|
||||
)
|
||||
await selects(
|
||||
CONF_LIGHT_FUNCTION,
|
||||
hub.set_light_function_select,
|
||||
options=["off", "below", "above"],
|
||||
)
|
||||
await selects(
|
||||
CONF_OUT_PIN_LEVEL,
|
||||
hub.set_out_pin_level_select,
|
||||
options=["low", "high"],
|
||||
)
|
||||
|
||||
@@ -158,30 +158,19 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if detection_distance_config := config.get(CONF_DETECTION_DISTANCE):
|
||||
sens = await sensor.new_sensor(detection_distance_config)
|
||||
cg.add(LD2412_component.set_detection_distance_sensor(sens))
|
||||
if light_config := config.get(CONF_LIGHT):
|
||||
sens = await sensor.new_sensor(light_config)
|
||||
cg.add(LD2412_component.set_light_sensor(sens))
|
||||
if moving_distance_config := config.get(CONF_MOVING_DISTANCE):
|
||||
sens = await sensor.new_sensor(moving_distance_config)
|
||||
cg.add(LD2412_component.set_moving_target_distance_sensor(sens))
|
||||
if moving_energy_config := config.get(CONF_MOVING_ENERGY):
|
||||
sens = await sensor.new_sensor(moving_energy_config)
|
||||
cg.add(LD2412_component.set_moving_target_energy_sensor(sens))
|
||||
if still_distance_config := config.get(CONF_STILL_DISTANCE):
|
||||
sens = await sensor.new_sensor(still_distance_config)
|
||||
cg.add(LD2412_component.set_still_target_distance_sensor(sens))
|
||||
if still_energy_config := config.get(CONF_STILL_ENERGY):
|
||||
sens = await sensor.new_sensor(still_energy_config)
|
||||
cg.add(LD2412_component.set_still_target_energy_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_DETECTION_DISTANCE, hub.set_detection_distance_sensor)
|
||||
await sensors(CONF_LIGHT, hub.set_light_sensor)
|
||||
await sensors(CONF_MOVING_DISTANCE, hub.set_moving_target_distance_sensor)
|
||||
await sensors(CONF_MOVING_ENERGY, hub.set_moving_target_energy_sensor)
|
||||
await sensors(CONF_STILL_DISTANCE, hub.set_still_target_distance_sensor)
|
||||
await sensors(CONF_STILL_ENERGY, hub.set_still_target_energy_sensor)
|
||||
for x in range(14):
|
||||
if gate_conf := config.get(f"gate_{x}"):
|
||||
if move_config := gate_conf.get(CONF_MOVE_ENERGY):
|
||||
sens = await sensor.new_sensor(move_config)
|
||||
cg.add(LD2412_component.set_gate_move_sensor(x, sens))
|
||||
cg.add(hub.set_gate_move_sensor(x, sens))
|
||||
if still_config := gate_conf.get(CONF_STILL_ENERGY):
|
||||
sens = await sensor.new_sensor(still_config)
|
||||
cg.add(LD2412_component.set_gate_still_sensor(x, sens))
|
||||
cg.add(hub.set_gate_still_sensor(x, sens))
|
||||
|
||||
@@ -37,12 +37,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if bluetooth_config := config.get(CONF_BLUETOOTH):
|
||||
s = await switch.new_switch(bluetooth_config)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_bluetooth_switch(s))
|
||||
if engineering_mode_config := config.get(CONF_ENGINEERING_MODE):
|
||||
s = await switch.new_switch(engineering_mode_config)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_engineering_mode_switch(s))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
switches = switch.sub_switches(config, parent=hub)
|
||||
await switches(CONF_BLUETOOTH, hub.set_bluetooth_switch)
|
||||
await switches(CONF_ENGINEERING_MODE, hub.set_engineering_mode_switch)
|
||||
|
||||
@@ -28,10 +28,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await text_sensor.new_text_sensor(version_config)
|
||||
cg.add(LD2412_component.set_version_text_sensor(sens))
|
||||
if mac_address_config := config.get(CONF_MAC_ADDRESS):
|
||||
sens = await text_sensor.new_text_sensor(mac_address_config)
|
||||
cg.add(LD2412_component.set_mac_text_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_VERSION, hub.set_version_text_sensor)
|
||||
await text_sensors(CONF_MAC_ADDRESS, hub.set_mac_text_sensor)
|
||||
|
||||
@@ -79,16 +79,18 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WIND_DIRECTION,
|
||||
DEVICE_CLASS_WIND_SPEED,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.config import UNIT_OF_MEASUREMENT_MAX_LENGTH
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
setup_unit_of_measurement,
|
||||
)
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEVICE_CLASSES = [
|
||||
@@ -325,6 +327,13 @@ async def new_number(config, *args, min_value: float, max_value: float, step: fl
|
||||
return var
|
||||
|
||||
|
||||
def sub_numbers(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_number."""
|
||||
return SubEntities(new_number, config, parent)
|
||||
|
||||
|
||||
NUMBER_IN_RANGE_CONDITION_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Number),
|
||||
|
||||
@@ -20,12 +20,14 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.cpp_generator import MockObjClass, TemplateArguments
|
||||
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArguments
|
||||
from esphome.cpp_types import global_ns
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -123,6 +125,13 @@ async def new_select(config, *args, options: list[str]):
|
||||
return var
|
||||
|
||||
|
||||
def sub_selects(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_select."""
|
||||
return SubEntities(new_select, config, parent)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
async def to_code(config):
|
||||
cg.add_global(select_ns.using)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
import math
|
||||
|
||||
@@ -112,11 +111,11 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WIND_SPEED,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.config import UNIT_OF_MEASUREMENT_MAX_LENGTH
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
new_sub_entity,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
@@ -124,7 +123,7 @@ from esphome.core.entity_helpers import (
|
||||
)
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigType, Expression, SafeExpType
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -1016,14 +1015,11 @@ async def new_sensor(config, *args):
|
||||
return var
|
||||
|
||||
|
||||
async def new_sub_sensor(
|
||||
config: ConfigType,
|
||||
key: str,
|
||||
setter: Callable[[MockObj], Expression],
|
||||
*args: SafeExpType,
|
||||
) -> MockObj | None:
|
||||
"""Create the sensor configured under key, if any, and pass it to setter."""
|
||||
return await new_sub_entity(new_sensor, config, key, setter, *args)
|
||||
def sub_sensors(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_sensor."""
|
||||
return SubEntities(new_sensor, config, parent)
|
||||
|
||||
|
||||
SENSOR_IN_RANGE_CONDITION_SCHEMA = cv.All(
|
||||
|
||||
@@ -20,14 +20,16 @@ from esphome.const import (
|
||||
DEVICE_CLASS_OUTLET,
|
||||
DEVICE_CLASS_SWITCH,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -173,6 +175,13 @@ async def new_switch(config, *args):
|
||||
return var
|
||||
|
||||
|
||||
def sub_switches(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_switch."""
|
||||
return SubEntities(new_switch, config, parent)
|
||||
|
||||
|
||||
SWITCH_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Switch),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
@@ -22,16 +20,16 @@ from esphome.const import (
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
new_sub_entity,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.types import ConfigType, Expression, SafeExpType
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import Registry
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
@@ -235,14 +233,11 @@ async def new_text_sensor(config, *args):
|
||||
return var
|
||||
|
||||
|
||||
async def new_sub_text_sensor(
|
||||
config: ConfigType,
|
||||
key: str,
|
||||
setter: Callable[[MockObj], Expression],
|
||||
*args: SafeExpType,
|
||||
) -> MockObj | None:
|
||||
"""Create the text sensor configured under key, if any, and pass it to setter."""
|
||||
return await new_sub_entity(new_text_sensor, config, key, setter, *args)
|
||||
def sub_text_sensors(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_text_sensor."""
|
||||
return SubEntities(new_text_sensor, config, parent)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -61,12 +61,7 @@ FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
tfluna_component = await cg.get_variable(config[CONF_TFLUNA_ID])
|
||||
if factory_reset_config := config.get(CONF_FACTORY_RESET):
|
||||
b = await button.new_button(factory_reset_config)
|
||||
await cg.register_parented(b, config[CONF_TFLUNA_ID])
|
||||
cg.add(tfluna_component.set_reset_button(b))
|
||||
if restart_config := config.get(CONF_RESTART):
|
||||
b = await button.new_button(restart_config)
|
||||
await cg.register_parented(b, config[CONF_TFLUNA_ID])
|
||||
cg.add(tfluna_component.set_restart_button(b))
|
||||
hub = await cg.get_variable(config[CONF_TFLUNA_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_reset_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
|
||||
@@ -60,16 +60,9 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
tfluna_component = await cg.get_variable(config[CONF_TFLUNA_ID])
|
||||
if distance_config := config.get(CONF_DISTANCE):
|
||||
sens = await sensor.new_sensor(distance_config)
|
||||
cg.add(tfluna_component.set_distance_sensor(sens))
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(tfluna_component.set_temperature_sensor(sens))
|
||||
if signal_strength_config := config.get(CONF_SIGNAL_STRENGTH):
|
||||
sens = await sensor.new_sensor(signal_strength_config)
|
||||
cg.add(tfluna_component.set_signal_strength_sensor(sens))
|
||||
if timestamp_config := config.get(CONF_TIMESTAMP):
|
||||
sens = await sensor.new_sensor(timestamp_config)
|
||||
cg.add(tfluna_component.set_timestamp_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_TFLUNA_ID])
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_DISTANCE, hub.set_distance_sensor)
|
||||
await sensors(CONF_TEMPERATURE, hub.set_temperature_sensor)
|
||||
await sensors(CONF_SIGNAL_STRENGTH, hub.set_signal_strength_sensor)
|
||||
await sensors(CONF_TIMESTAMP, hub.set_timestamp_sensor)
|
||||
|
||||
@@ -17,7 +17,6 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
tfluna_component = await cg.get_variable(config[CONF_TFLUNA_ID])
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await text_sensor.new_text_sensor(version_config)
|
||||
cg.add(tfluna_component.set_version_text_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_TFLUNA_ID])
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_VERSION, hub.set_version_text_sensor)
|
||||
|
||||
@@ -47,14 +47,11 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
hub = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if await binary_sensor.new_sub_binary_sensor(
|
||||
config, CONF_MOTION, parent.set_motion
|
||||
):
|
||||
cg.add(parent.set_motion_timeout(config[CONF_MOTION][CONF_TIMEOUT]))
|
||||
await binary_sensor.new_sub_binary_sensor(config, CONF_LIGHT, parent.set_light)
|
||||
if await binary_sensor.new_sub_binary_sensor(
|
||||
config, CONF_BUTTON, parent.set_button
|
||||
):
|
||||
cg.add(parent.set_button_timeout(config[CONF_BUTTON][CONF_TIMEOUT]))
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
if await binary_sensors(CONF_MOTION, hub.set_motion):
|
||||
cg.add(hub.set_motion_timeout(config[CONF_MOTION][CONF_TIMEOUT]))
|
||||
await binary_sensors(CONF_LIGHT, hub.set_light)
|
||||
if await binary_sensors(CONF_BUTTON, hub.set_button):
|
||||
cg.add(hub.set_button_timeout(config[CONF_BUTTON][CONF_TIMEOUT]))
|
||||
|
||||
@@ -31,6 +31,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
hub = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
await sensor.new_sub_sensor(config, CONF_BATTERY_LEVEL, parent.set_battery_level)
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_BATTERY_LEVEL, hub.set_battery_level)
|
||||
|
||||
@@ -468,23 +468,32 @@ async def _setup_entity_impl(var: MockObj, config: ConfigType, platform: str) ->
|
||||
config[_KEY_ICON_IDX] = icon_idx
|
||||
|
||||
|
||||
async def new_sub_entity(
|
||||
new_entity: Callable[..., Awaitable[MockObj]],
|
||||
config: ConfigType,
|
||||
key: str,
|
||||
setter: Callable[[MockObj], Expression],
|
||||
*args: SafeExpType,
|
||||
**kwargs: Any,
|
||||
) -> MockObj | None:
|
||||
"""Create the entity configured under key, if any, and pass it to setter.
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SubEntities:
|
||||
"""Create the child entity configured under key, set parent (if bound) and pass it to setter.
|
||||
|
||||
Returns None only when key is not configured, so the result can be used directly as a condition.
|
||||
Extra arguments go to new_entity. Returns None only when key is absent, so the result can be
|
||||
used directly as a condition.
|
||||
"""
|
||||
if (conf := config.get(key)) is None:
|
||||
return None
|
||||
var = await new_entity(conf, *args, **kwargs)
|
||||
add(setter(var))
|
||||
return var
|
||||
|
||||
new_entity: Callable[..., Awaitable[MockObj]]
|
||||
config: ConfigType
|
||||
parent: MockObj | ID | None = None
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
key: str,
|
||||
setter: Callable[[MockObj], Expression],
|
||||
*args: SafeExpType,
|
||||
**kwargs: Any,
|
||||
) -> MockObj | None:
|
||||
if (conf := self.config.get(key)) is None:
|
||||
return None
|
||||
var = await self.new_entity(conf, *args, **kwargs)
|
||||
if self.parent is not None:
|
||||
await cg.register_parented(var, self.parent)
|
||||
add(setter(var))
|
||||
return var
|
||||
|
||||
|
||||
def inherit_property_from(property_to_inherit, parent_id_property, transform=None):
|
||||
|
||||
@@ -1239,39 +1239,58 @@ async def test_finalize_comment_sanitization(
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_sub_entity_creates_and_sets(
|
||||
setup_test_environment: list[str],
|
||||
async def test_sub_entities_creates_and_sets(
|
||||
monkeypatch: pytest.MonkeyPatch, setup_test_environment: list[str]
|
||||
) -> None:
|
||||
"""A configured key, even with an empty config, creates the entity and passes it to the setter."""
|
||||
sub_config: dict[str, Any] = {}
|
||||
created = MockObj("child")
|
||||
"""A configured key creates the entity, sets the parent before the setter and returns it."""
|
||||
calls: list[tuple[Any, ...]] = []
|
||||
created = MockObj("child")
|
||||
|
||||
async def new_entity(conf: dict[str, Any], *args: Any, **kwargs: Any) -> MockObj:
|
||||
calls.append((conf, args, kwargs))
|
||||
return created
|
||||
|
||||
parent = MockObj("parent", "->")
|
||||
result = await entity_helpers.new_sub_entity(
|
||||
new_entity, {"child": sub_config}, "child", parent.set_child, 1, step=2
|
||||
)
|
||||
async def fake_register_parented(var: MockObj, parent: MockObj) -> None:
|
||||
setup_test_environment.append(f"parent {var} {parent}")
|
||||
|
||||
assert result is created
|
||||
assert calls == [(sub_config, (1,), {"step": 2})]
|
||||
assert setup_test_environment == ["parent->set_child(child)"]
|
||||
monkeypatch.setattr(entity_helpers.cg, "register_parented", fake_register_parented)
|
||||
hub = MockObj("hub", "->")
|
||||
sub = entity_helpers.SubEntities(new_entity, {"child": {}}, parent=hub)
|
||||
|
||||
assert await sub("child", hub.set_child, 1, step=2) is created
|
||||
assert calls == [({}, (1,), {"step": 2})]
|
||||
assert setup_test_environment == ["parent child hub", "hub->set_child(child)"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_sub_entity_missing_key(setup_test_environment: list[str]) -> None:
|
||||
async def test_sub_entities_missing_key(setup_test_environment: list[str]) -> None:
|
||||
"""A missing key creates nothing and emits no code."""
|
||||
|
||||
async def new_entity(conf: dict[str, Any], *args: Any) -> MockObj:
|
||||
async def new_entity(conf: dict[str, Any]) -> MockObj:
|
||||
raise AssertionError("must not be called")
|
||||
|
||||
parent = MockObj("parent", "->")
|
||||
result = await entity_helpers.new_sub_entity(
|
||||
new_entity, {}, "child", parent.set_child
|
||||
)
|
||||
hub = MockObj("hub", "->")
|
||||
sub = entity_helpers.SubEntities(new_entity, {})
|
||||
|
||||
assert result is None
|
||||
assert await sub("child", hub.set_child) is None
|
||||
assert setup_test_environment == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sub_entities_without_parent(
|
||||
monkeypatch: pytest.MonkeyPatch, setup_test_environment: list[str]
|
||||
) -> None:
|
||||
"""Without a bound parent, register_parented is not called."""
|
||||
|
||||
async def fail_register_parented(var: MockObj, parent: MockObj) -> None:
|
||||
raise AssertionError("must not be called")
|
||||
|
||||
async def new_entity(conf: dict[str, Any]) -> MockObj:
|
||||
return MockObj("child")
|
||||
|
||||
monkeypatch.setattr(entity_helpers.cg, "register_parented", fail_register_parented)
|
||||
hub = MockObj("hub", "->")
|
||||
sub = entity_helpers.SubEntities(new_entity, {"child": {CONF_ID: "child"}})
|
||||
|
||||
assert await sub("child", hub.set_child) is not None
|
||||
assert setup_test_environment == ["hub->set_child(child)"]
|
||||
|
||||
Reference in New Issue
Block a user