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, and (with diagnostic_probes) the Last Device
9## Event sensor.
10
11import esphome.codegen as cg
12import esphome.config_validation as cv
13# Aliased so they cannot be mistaken for this package's own platform modules (see hub_names.py).
14from esphome.components import button as button_component
15from esphome.components import switch as switch_component
16from esphome.components import text_sensor as text_sensor_component
17from esphome.const import (
18 CONF_ID,
19 CONF_NAME,
20 ENTITY_CATEGORY_CONFIG,
21 ENTITY_CATEGORY_DIAGNOSTIC,
22)
23from esphome.core import ID
24
25from .hub_names import (
26 CONF_ACCEPT_FOREIGN_PAIRING,
27 CONF_ACCEPT_FOREIGN_PAIRING_SWITCH_ID,
28 CONF_DEVICE_EVENT_SENSOR_ID,
29 CONF_DIAGNOSTIC_PROBES,
30 CONF_DISCOVER_AND_PAIR_BUTTON,
31 CONF_DISCOVER_AND_PAIR_BUTTON_ID,
32 CONF_DISCOVER_AND_PAIR_RESULT_SENSOR_ID,
33 CONF_RECOVER_ONEWAY_KEY,
34 CONF_RECOVER_ONEWAY_KEY_SWITCH_ID,
35 CONF_SCAN_PAIRED_DEVICES_BUTTON,
36 CONF_SCAN_PAIRED_DEVICES_BUTTON_ID,
37 IOHomeAcceptForeignPairingSwitch,
38 IOHomeDeviceEventTextSensor,
39 IOHomeDiscoverButton,
40 IOHomePairingResultTextSensor,
41 IOHomeRecoverOneWayKeySwitch,
42 IOHomeScanPairedDevicesButton,
43)
44
45
46def _inject_hub_entity_id(config, *, flag_key, id_key, suffix, cls):
47 """Shared body for the hub-level entities gated by a bare boolean flag in the
48 `home_io_control:` block (accept_foreign_pairing, recover_oneway_key,
49 scan_paired_devices_button, discover_and_pair_button): declare the entity's ID during
50 validation, under the `{hub_id}_{suffix}` name, only when its flag is set. See
51 companion_id_base() in platform_common.py for why this must happen at validation time rather
52 than in to_code().
53 """
54 if not config[flag_key]:
55 return config
56 parent_id = config[CONF_ID]
57 base = parent_id.id if parent_id.id else "home_io_control"
58 config[id_key] = ID(f"{base}_{suffix}", is_declaration=True, type=cls)
59 return config
60
61
64 config,
65 flag_key=CONF_ACCEPT_FOREIGN_PAIRING,
66 id_key=CONF_ACCEPT_FOREIGN_PAIRING_SWITCH_ID,
67 suffix="accept_foreign_pairing_switch",
68 cls=IOHomeAcceptForeignPairingSwitch,
69 )
70
71
74 config,
75 flag_key=CONF_RECOVER_ONEWAY_KEY,
76 id_key=CONF_RECOVER_ONEWAY_KEY_SWITCH_ID,
77 suffix="recover_oneway_key_switch",
78 cls=IOHomeRecoverOneWayKeySwitch,
79 )
80
81
84 config,
85 flag_key=CONF_SCAN_PAIRED_DEVICES_BUTTON,
86 id_key=CONF_SCAN_PAIRED_DEVICES_BUTTON_ID,
87 suffix="scan_paired_devices_button",
88 cls=IOHomeScanPairedDevicesButton,
89 )
90
91
94 config,
95 flag_key=CONF_DISCOVER_AND_PAIR_BUTTON,
96 id_key=CONF_DISCOVER_AND_PAIR_BUTTON_ID,
97 suffix="discover_and_pair_button",
98 cls=IOHomeDiscoverButton,
99 )
100
101
103 """Second ID off the same flag: the button always ships with its "Last Pairing Result" sensor,
104 so both IDs are gated on CONF_DISCOVER_AND_PAIR_BUTTON. _inject_hub_entity_id() already no-ops
105 when the flag is false, so no extra guard is needed here.
106 """
108 config,
109 flag_key=CONF_DISCOVER_AND_PAIR_BUTTON,
110 id_key=CONF_DISCOVER_AND_PAIR_RESULT_SENSOR_ID,
111 suffix="pairing_result_sensor",
112 cls=IOHomePairingResultTextSensor,
113 )
114
115
117 """The "Last Device Event" sensor exists only with `diagnostic_probes: true`: it shows what the
118 device-event recorder saw, and that recorder is a diagnostic tool of the probe build.
119 """
121 config,
122 flag_key=CONF_DIAGNOSTIC_PROBES,
123 id_key=CONF_DEVICE_EVENT_SENSOR_ID,
124 suffix="device_event_sensor",
125 cls=IOHomeDeviceEventTextSensor,
126 )
127
128
129async def _create_hub_entity(schema, new_entity, entity_id, name, var):
130 """Create one hub-level entity from its declared ID and a fixed name, bound to the hub.
131
132 A bare {id, name} dict is run through the platform's entity schema plus COMPONENT_SCHEMA, so
133 it carries the entity/component defaults new_*()/register_component() require, instead of a
134 hand-assembled config dict of its own (tuning.py's _create_number()/_create_select() do the
135 same for the tuning entities).
136 """
137 entity_config = schema.extend(cv.COMPONENT_SCHEMA)({CONF_ID: entity_id, CONF_NAME: name})
138 entity = await new_entity(entity_config)
139 await cg.register_component(entity, entity_config)
140 cg.add(entity.set_parent(var))
141 return entity
142
143
144async def create_hub_arming_switch(config, var, *, cls, id_key, name):
145 """Create a hub-level arming switch (key extraction or key adoption).
146
147 ALWAYS_OFF is a security property, not a UX default: every switch built here arms a window
148 (foreign-key extraction or 1W key adoption) that must never come back armed after a reboot.
149 """
150 schema = switch_component.switch_schema(
151 cls,
152 default_restore_mode="ALWAYS_OFF", # never auto-arm after a reboot
153 entity_category=ENTITY_CATEGORY_CONFIG,
154 )
155 await _create_hub_entity(schema, switch_component.new_switch, config[id_key], name, var)
156
157
159 """Create the hub-level "Scan Paired Devices" button.
160
161 The `scan_paired_devices` native API action is registered independently in C++
162 (ManagementActions::register_actions()) and is unaffected by this key -- the button is an extra
163 trigger onto the same method, not a replacement.
164 """
165 schema = button_component.button_schema(IOHomeScanPairedDevicesButton, entity_category=ENTITY_CATEGORY_CONFIG)
166 await _create_hub_entity(
167 schema, button_component.new_button, config[CONF_SCAN_PAIRED_DEVICES_BUTTON_ID], "Scan Paired Devices", var
168 )
169
170
171async def create_discover_and_pair_button(config, var):
172 """Create the hub-level "Discover & Pair" button and its "Last Pairing Result" sensor.
173
174 The two are always created together: the sensor is the only place a pairing attempt's
175 machine-readable outcome ever appears, so a button without it would be a button whose result
176 you cannot read.
177
178 inherit_esphome_device() is deliberately NOT called: the hub's own config has no `device_id:`
179 slot for these to inherit -- see that function's docstring.
180 """
181 button_schema = button_component.button_schema(IOHomeDiscoverButton, entity_category=ENTITY_CATEGORY_CONFIG)
182 await _create_hub_entity(
183 button_schema, button_component.new_button, config[CONF_DISCOVER_AND_PAIR_BUTTON_ID], "Discover & Pair", var
184 )
185 sensor_schema = text_sensor_component.text_sensor_schema(
186 IOHomePairingResultTextSensor, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
187 )
188 await _create_hub_entity(
189 sensor_schema,
190 text_sensor_component.new_text_sensor,
191 config[CONF_DISCOVER_AND_PAIR_RESULT_SENSOR_ID],
192 "Last Pairing Result",
193 var,
194 )
195
196
197async def create_device_event_sensor(config, var):
198 """Create the hub-level "Last Device Event" diagnostic text sensor.
199
200 The value is `#<n> t=<uptime s> <SRC> <cmd> <payload hex>`; the counter makes a repeated
201 identical report a new state, which Home Assistant's history needs.
202 """
203 sensor_schema = text_sensor_component.text_sensor_schema(
204 IOHomeDeviceEventTextSensor,
205 entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
206 icon="mdi:radio-tower",
207 )
208 await _create_hub_entity(
209 sensor_schema,
210 text_sensor_component.new_text_sensor,
211 config[CONF_DEVICE_EVENT_SENSOR_ID],
212 "Last Device Event",
213 var,
214 )
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)
create_device_event_sensor(config, var)