Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
hub_entities.py
Go to the documentation of this file.
1## @file
2## @brief Hub-level entities created from boolean flags in the ``home_io_control:`` block.
3## @ingroup hioc_codegen
4##
5## Declares their IDs during validation (ADR 0009: an ID first created in to_code() is
6## silently dropped at runtime) and creates them in to_code(): the foreign-pairing and
7## 1W-key-recovery arming switches, the Scan Paired Devices button, and the Discover &
8## Pair button with its Last Pairing Result sensor.
9
10import esphome.codegen as cg
11import esphome.config_validation as cv
12# Aliased so they cannot be mistaken for this package's own platform modules (see hub_names.py).
13from esphome.components import button as button_component
14from esphome.components import switch as switch_component
15from esphome.components import text_sensor as text_sensor_component
16from esphome.const import (
17 CONF_ID,
18 CONF_NAME,
19 ENTITY_CATEGORY_CONFIG,
20 ENTITY_CATEGORY_DIAGNOSTIC,
21)
22from esphome.core import ID
23
24from .hub_names import (
25 CONF_ACCEPT_FOREIGN_PAIRING,
26 CONF_ACCEPT_FOREIGN_PAIRING_SWITCH_ID,
27 CONF_DISCOVER_AND_PAIR_BUTTON,
28 CONF_DISCOVER_AND_PAIR_BUTTON_ID,
29 CONF_DISCOVER_AND_PAIR_RESULT_SENSOR_ID,
30 CONF_RECOVER_ONEWAY_KEY,
31 CONF_RECOVER_ONEWAY_KEY_SWITCH_ID,
32 CONF_SCAN_PAIRED_DEVICES_BUTTON,
33 CONF_SCAN_PAIRED_DEVICES_BUTTON_ID,
34 IOHomeAcceptForeignPairingSwitch,
35 IOHomeDiscoverButton,
36 IOHomePairingResultTextSensor,
37 IOHomeRecoverOneWayKeySwitch,
38 IOHomeScanPairedDevicesButton,
39)
40
41
42def _inject_hub_entity_id(config, *, flag_key, id_key, suffix, cls):
43 """Shared body for the hub-level entities gated by a bare boolean flag in the
44 `home_io_control:` block (accept_foreign_pairing, recover_oneway_key,
45 scan_paired_devices_button, discover_and_pair_button): declare the entity's ID during
46 validation, under the `{hub_id}_{suffix}` name, only when its flag is set. See
47 companion_id_base() in platform_common.py for why this must happen at validation time rather
48 than in to_code().
49 """
50 if not config[flag_key]:
51 return config
52 parent_id = config[CONF_ID]
53 base = parent_id.id if parent_id.id else "home_io_control"
54 config[id_key] = ID(f"{base}_{suffix}", is_declaration=True, type=cls)
55 return config
56
57
60 config,
61 flag_key=CONF_ACCEPT_FOREIGN_PAIRING,
62 id_key=CONF_ACCEPT_FOREIGN_PAIRING_SWITCH_ID,
63 suffix="accept_foreign_pairing_switch",
64 cls=IOHomeAcceptForeignPairingSwitch,
65 )
66
67
70 config,
71 flag_key=CONF_RECOVER_ONEWAY_KEY,
72 id_key=CONF_RECOVER_ONEWAY_KEY_SWITCH_ID,
73 suffix="recover_oneway_key_switch",
74 cls=IOHomeRecoverOneWayKeySwitch,
75 )
76
77
80 config,
81 flag_key=CONF_SCAN_PAIRED_DEVICES_BUTTON,
82 id_key=CONF_SCAN_PAIRED_DEVICES_BUTTON_ID,
83 suffix="scan_paired_devices_button",
84 cls=IOHomeScanPairedDevicesButton,
85 )
86
87
90 config,
91 flag_key=CONF_DISCOVER_AND_PAIR_BUTTON,
92 id_key=CONF_DISCOVER_AND_PAIR_BUTTON_ID,
93 suffix="discover_and_pair_button",
94 cls=IOHomeDiscoverButton,
95 )
96
97
99 """Second ID off the same flag: the button always ships with its "Last Pairing Result" sensor,
100 so both IDs are gated on CONF_DISCOVER_AND_PAIR_BUTTON. _inject_hub_entity_id() already no-ops
101 when the flag is false, so no extra guard is needed here.
102 """
104 config,
105 flag_key=CONF_DISCOVER_AND_PAIR_BUTTON,
106 id_key=CONF_DISCOVER_AND_PAIR_RESULT_SENSOR_ID,
107 suffix="pairing_result_sensor",
108 cls=IOHomePairingResultTextSensor,
109 )
110
111
112async def _create_hub_entity(schema, new_entity, entity_id, name, var):
113 """Create one hub-level entity from its declared ID and a fixed name, bound to the hub.
114
115 A bare {id, name} dict is run through the platform's entity schema plus COMPONENT_SCHEMA, so
116 it carries the entity/component defaults new_*()/register_component() require, instead of a
117 hand-assembled config dict of its own (tuning.py's _create_number()/_create_select() do the
118 same for the tuning entities).
119 """
120 entity_config = schema.extend(cv.COMPONENT_SCHEMA)({CONF_ID: entity_id, CONF_NAME: name})
121 entity = await new_entity(entity_config)
122 await cg.register_component(entity, entity_config)
123 cg.add(entity.set_parent(var))
124 return entity
125
126
127async def create_hub_arming_switch(config, var, *, cls, id_key, name):
128 """Create a hub-level arming switch (key extraction or key adoption).
129
130 ALWAYS_OFF is a security property, not a UX default: every switch built here arms a window
131 (foreign-key extraction or 1W key adoption) that must never come back armed after a reboot.
132 """
133 schema = switch_component.switch_schema(
134 cls,
135 default_restore_mode="ALWAYS_OFF", # never auto-arm after a reboot
136 entity_category=ENTITY_CATEGORY_CONFIG,
137 )
138 await _create_hub_entity(schema, switch_component.new_switch, config[id_key], name, var)
139
140
142 """Create the hub-level "Scan Paired Devices" button.
143
144 The `scan_paired_devices` native API action is registered independently in C++
145 (ManagementActions::register_actions()) and is unaffected by this key -- the button is an extra
146 trigger onto the same method, not a replacement.
147 """
148 schema = button_component.button_schema(IOHomeScanPairedDevicesButton, entity_category=ENTITY_CATEGORY_CONFIG)
149 await _create_hub_entity(
150 schema, button_component.new_button, config[CONF_SCAN_PAIRED_DEVICES_BUTTON_ID], "Scan Paired Devices", var
151 )
152
153
154async def create_discover_and_pair_button(config, var):
155 """Create the hub-level "Discover & Pair" button and its "Last Pairing Result" sensor.
156
157 The two are always created together: the sensor is the only place a pairing attempt's
158 machine-readable outcome ever appears, so a button without it would be a button whose result
159 you cannot read.
160
161 inherit_esphome_device() is deliberately NOT called: the hub's own config has no `device_id:`
162 slot for these to inherit -- see that function's docstring. This is the one behaviour the
163 deprecated `button:` platform (button.py) had that this flag form cannot reproduce.
164 """
165 button_schema = button_component.button_schema(IOHomeDiscoverButton, entity_category=ENTITY_CATEGORY_CONFIG)
166 await _create_hub_entity(
167 button_schema, button_component.new_button, config[CONF_DISCOVER_AND_PAIR_BUTTON_ID], "Discover & Pair", var
168 )
169 sensor_schema = text_sensor_component.text_sensor_schema(
170 IOHomePairingResultTextSensor, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
171 )
172 await _create_hub_entity(
173 sensor_schema,
174 text_sensor_component.new_text_sensor,
175 config[CONF_DISCOVER_AND_PAIR_RESULT_SENSOR_ID],
176 "Last Pairing Result",
177 var,
178 )
create_scan_paired_devices_button(config, var)
create_hub_arming_switch(config, var, *, cls, id_key, name)
_inject_hub_entity_id(config, *, flag_key, id_key, suffix, cls)
inject_discover_and_pair_result_sensor_id(config)
create_discover_and_pair_button(config, var)
inject_accept_foreign_pairing_switch_id(config)
_create_hub_entity(schema, new_entity, entity_id, name, var)