Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
button.py
Go to the documentation of this file.
1## @file
2## @brief Deprecated `button:` platform for the pairing button -- use
3## `home_io_control.discover_and_pair_button: true` instead.
4## @ingroup hioc_codegen
5##
6## Exposes the same Home Assistant button entity and companion "Last Pairing Result" diagnostic
7## text sensor that `home_io_control.discover_and_pair_button: true` now creates directly (see
8## hub_entities.py's `create_discover_and_pair_button()`). Kept working, with a deprecation warning,
9## for configs that predate that flag -- see `_warn_deprecated_platform()` below and ADR 0036.
10
11import logging
12
13import esphome.codegen as cg
14from esphome.components import button, text_sensor
15import esphome.config_validation as cv
16import esphome.final_validate as fv
17from esphome.const import (
18 CONF_DISABLED_BY_DEFAULT,
19 CONF_ENTITY_CATEGORY,
20 CONF_ID,
21 CONF_NAME,
22 ENTITY_CATEGORY_CONFIG,
23 ENTITY_CATEGORY_DIAGNOSTIC,
24)
25from esphome.core import ID
26
27from . import (
28 IOHomeControlComponent,
29 IOHomeDiscoverButton,
30 IOHomePairingResultTextSensor,
31 CONF_DISCOVER_AND_PAIR_BUTTON,
32 CONF_HOME_IO_CONTROL_ID,
33 inherit_esphome_device,
34)
35
36_LOGGER = logging.getLogger(__name__)
37
38DEPENDENCIES = ["home_io_control"]
39
40# Internal config key for the companion pairing-result sensor ID (injected by post-validator).
41CONF_PAIRING_RESULT_SENSOR_ID = "_pairing_result_sensor_id"
42
43
45 """Declare the companion pairing-result sensor ID during schema validation.
46
47 ESPHome 2026.x sizes its runtime component vector from the number of component IDs
48 known at the end of schema validation — before to_code() runs. A companion entity
49 created only in to_code() is not counted and silently drops at runtime. See the
50 identical pattern (and its full rationale) in platform_common.py::companion_id_base().
51 """
52 from esphome.helpers import sanitize
53
54 parent_id = config[CONF_ID]
55 base = parent_id.id if parent_id.id else sanitize(config.get(CONF_NAME, "")).lower()
56 config[CONF_PAIRING_RESULT_SENSOR_ID] = ID(
57 f"{base}_pairing_result_sensor",
58 is_declaration=True,
59 type=IOHomePairingResultTextSensor,
60 )
61 return config
62
63
65 """Warn that this platform is superseded by home_io_control.discover_and_pair_button.
66
67 Placed FIRST in CONFIG_SCHEMA's cv.All() chain so the warning still reaches a user whose entry
68 also has an unrelated validation error. ESPHome runs each entry's schema exactly once per
69 `esphome config|compile|run`, so this fires once per legacy entry per invocation -- no dedupe
70 flag needed. A validator rather than to_code()'s first line (ESPHome's own precedent for a
71 whole-component deprecation) so `esphome config` and the dashboard's validate-only pass show it
72 too, and to match this component's existing convention of warning from validators.
73 """
74 _LOGGER.warning(
75 "The 'home_io_control' button platform is deprecated. Set "
76 "'discover_and_pair_button: true' in the 'home_io_control:' block instead and delete this "
77 "'button:' entry; the hub then creates the 'Discover & Pair' button and its 'Last Pairing "
78 "Result' sensor itself. This entry still works, but will become a hard error in a future "
79 "release."
80 )
81 return config
82
83
84CONFIG_SCHEMA = cv.All(
85 _warn_deprecated_platform,
86 button.button_schema(
87 IOHomeDiscoverButton,
88 entity_category=ENTITY_CATEGORY_CONFIG,
89 )
90 .extend(
91 {
92 cv.GenerateID(CONF_HOME_IO_CONTROL_ID): cv.use_id(
93 IOHomeControlComponent
94 ),
95 }
96 )
97 .extend(cv.COMPONENT_SCHEMA),
98 _inject_pairing_result_sensor_id,
99)
100
101
103 """Reject having both this legacy entry and the hub's own discover_and_pair_button: true.
104
105 Rejected unconditionally, regardless of this entry's own name/device_id -- by default both
106 create a button named "Discover & Pair" on the same device, which ESPHome's entity-duplicate
107 validator refuses, but it only notices inside the hub's to_code(), where the resulting
108 cv.Invalid surfaces as an uncaught traceback rather than a validation message. A renamed or
109 re-homed entry could in principle coexist without colliding, but forcing the choice here keeps
110 the guard simple and the migration unambiguous: one pairing trigger, not "two unless you were
111 careful". Platform modules get their own FINAL_VALIDATE_SCHEMA slot, so this does not touch
112 __init__.py's (already used for address-collision detection).
113 """
114 hub_config = fv.full_config.get().get("home_io_control") or {}
115 if isinstance(hub_config, dict) and hub_config.get(CONF_DISCOVER_AND_PAIR_BUTTON):
116 raise cv.Invalid(
117 "Both 'home_io_control.discover_and_pair_button: true' and a legacy "
118 "'button: - platform: home_io_control' entry are configured. Delete this 'button:' "
119 "entry -- the hub flag replaces it and creates the same button and sensor itself."
120 )
121 return config
122
123
124FINAL_VALIDATE_SCHEMA = _reject_duplicate_with_hub_flag
125
126
127async def to_code(config):
128 var = cg.new_Pvariable(config[CONF_ID])
129 await cg.register_component(var, config)
130 await button.register_button(var, config)
131
132 parent = await cg.get_variable(config[CONF_HOME_IO_CONTROL_ID])
133 cg.add(var.set_parent(parent))
134
135 result_sensor_config = inherit_esphome_device(
136 {
137 CONF_ID: config[CONF_PAIRING_RESULT_SENSOR_ID],
138 CONF_NAME: "Last Pairing Result",
139 CONF_DISABLED_BY_DEFAULT: False,
140 CONF_ENTITY_CATEGORY: ENTITY_CATEGORY_DIAGNOSTIC,
141 },
142 config,
143 )
144 result_sensor = await text_sensor.new_text_sensor(result_sensor_config)
145 await cg.register_component(result_sensor, result_sensor_config)
146 cg.add(result_sensor.set_parent(parent))
_inject_pairing_result_sensor_id(config)
Definition button.py:44
_warn_deprecated_platform(config)
Definition button.py:64
_reject_duplicate_with_hub_flag(config)
Definition button.py:102