Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
cover.py
Go to the documentation of this file.
1## @file
2## @brief ESPHome cover platform schema and code generation.
3## @ingroup hioc_codegen
4##
5## Bridges the YAML ``cover:`` platform declaration to the runtime IOHomeCover entity.
6## Shared device-binding logic lives in platform_common.py; cover-specific extras — the
7## ``invert_position`` option and the favorite/ventilation companion buttons — stay here.
8
9import esphome.codegen as cg
10import esphome.config_validation as cv
11from esphome.components import button, cover, switch
12from esphome.const import (
13 CONF_DISABLED_BY_DEFAULT,
14 CONF_ID,
15 CONF_NAME,
16 ENTITY_CATEGORY_CONFIG,
17)
18from esphome.core import ID
19
20from . import home_io_control_ns
21from .platform_common import (
22 companion_id_base,
23 create_companion_sensors,
24 inherit_esphome_device,
25 inject_companion_sensor_ids,
26 platform_schema_extension,
27 wire_device_binding,
28 CONF_IO_DEVICE_ID,
29 CONF_DEVICE_TYPE,
30 CONF_HOME_IO_CONTROL_ID,
31)
32
33DEPENDENCIES = ["home_io_control"]
34
35CONF_INVERT_POSITION = "invert_position"
36CONF_SILENT = "silent"
37CONF_SILENT_SWITCH_ID = "_silent_switch_id"
38CONF_OPTIMISTIC_STATE = "optimistic_state"
39
40# Internal config keys for the cover-only companion button IDs (injected by post-validator).
41CONF_FAVORITE_BUTTON_ID = "_favorite_button_id"
42CONF_VENT_BUTTON_ID = "_vent_button_id"
43
44IOHomeCover = home_io_control_ns.class_("IOHomeCover", cover.Cover, cg.Component)
45IOHomeCoverSilentSwitch = home_io_control_ns.class_(
46 "IOHomeCoverSilentSwitch", switch.Switch, cg.Component
47)
48# One C++ class backs both cover command companions; codegen sets which command each press
49# sends via set_command() (mirrors OneWayButtonAction in __init__.py). A device-bound `button:`
50# entry is deliberately never the source — see the IOHomeOneWayCommandButton comment there.
51IOHomeCoverCommandButton = home_io_control_ns.class_(
52 "IOHomeCoverCommandButton", button.Button, cg.Component
53)
54CoverCommand = home_io_control_ns.enum("CoverCommand", is_class=True)
55
56# Device types that support 0-100% position control (maps to DeviceCapabilityClass::COVER in C++).
57# Used to decide whether a favorite-position button companion should be generated.
58POSITION_CONTROL_DEVICE_TYPES = {
59 0x01, # venetian_blind
60 0x02, # roller_shutter
61 0x03, # awning
62 0x04, # window_opener
63 0x05, # garage_opener
64 0x07, # gate_opener
65 0x08, # rolling_door_opener
66 0x0A, # blind
67 0x0B, # screen
68 0x0D, # dual_shutter
69 0x10, # horizontal_awning
70 0x11, # external_venetian_blind
71 0x12, # louvre_blind
72 0x13, # curtain_track
73 0x18, # swinging_shutter
74}
75
76
78 """Check if the given device type value supports 0-100% position control."""
79 return value in POSITION_CONTROL_DEVICE_TYPES
80
81
82# Device types that support the ventilation position command.
83# These are window-type actuators that can move to a predefined vent opening.
84VENT_DEVICE_TYPES = {
85 0x04, # window_opener
86 0x14, # ventilation_point
87}
88
89
91 """Check if the given device type value supports the ventilation command."""
92 return value in VENT_DEVICE_TYPES
93
94
96 """Derive the favorite-position button name from the parent cover name."""
97 base_name = config.get(CONF_NAME, "")
98 if base_name:
99 return f"{base_name} Favorite Position"
100 return "Favorite Position"
101
102
104 """Derive the ventilation-position button name from the parent cover name."""
105 base_name = config.get(CONF_NAME, "")
106 if base_name:
107 return f"{base_name} Ventilation Position"
108 return "Ventilation Position"
109
110
112 """Derive the silent-operation switch name from the parent cover name."""
113 base_name = config.get(CONF_NAME, "")
114 if base_name:
115 return f"{base_name} Silent Operation"
116 return "Silent Operation"
117
118
120 """Declare cover companion entity IDs during schema validation for StaticVector sizing.
121
122 The favorite and ventilation buttons are cover-only and gated on device capability, so
123 their injection stays here. The always-present companion sensor IDs are delegated to the
124 shared helper. See platform_common.companion_id_base() for the StaticVector rationale.
125 """
126 base = companion_id_base(config, CONF_ID)
127
128 # Favorite-position button — only for position-capable device types.
129 if CONF_DEVICE_TYPE in config and device_supports_position_control(
130 config[CONF_DEVICE_TYPE]
131 ):
132 config[CONF_FAVORITE_BUTTON_ID] = ID(
133 f"{base}_favorite_button",
134 is_declaration=True,
135 type=IOHomeCoverCommandButton,
136 )
137
138 # Ventilation-position button — only for window-type device types.
139 if CONF_DEVICE_TYPE in config and device_supports_vent(config[CONF_DEVICE_TYPE]):
140 config[CONF_VENT_BUTTON_ID] = ID(
141 f"{base}_vent_button",
142 is_declaration=True,
143 type=IOHomeCoverCommandButton,
144 )
145
146 # Silent-operation toggle — only when the cover declares `silent:` at all. Declaring the key
147 # is what opts a cover into runtime control of its travel profile; a config that never mentions
148 # it gains no entity, and the YAML value is simply the boot state.
149 if CONF_SILENT in config:
150 config[CONF_SILENT_SWITCH_ID] = ID(
151 f"{base}_silent_switch",
152 is_declaration=True,
153 type=IOHomeCoverSilentSwitch,
154 )
155
156 # Companion diagnostic sensors — always generated (shared with other platforms).
157 return inject_companion_sensor_ids(config, CONF_ID)
158
159
160CONFIG_SCHEMA = cv.All(
161 cover.cover_schema(IOHomeCover)
162 .extend(platform_schema_extension())
163 .extend({cv.Optional(CONF_INVERT_POSITION): cv.boolean})
164 .extend({cv.Optional(CONF_SILENT): cv.boolean})
165 .extend({cv.Optional(CONF_OPTIMISTIC_STATE, default=True): cv.boolean})
166 .extend(cv.COMPONENT_SCHEMA),
167 _inject_companion_ids,
168)
169
170
171async def to_code(config):
172 var = await cover.new_cover(config)
173 await cg.register_component(var, config)
174
175 parent = await cg.get_variable(config[CONF_HOME_IO_CONTROL_ID])
176 await wire_device_binding(var, parent, config)
177
178 if CONF_INVERT_POSITION in config:
179 cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
180
181 cg.add(var.set_optimistic_state(config[CONF_OPTIMISTIC_STATE]))
182 if CONF_SILENT in config:
183 cg.add(var.set_silent(config[CONF_SILENT]))
184
185 if CONF_SILENT_SWITCH_ID in config:
186 # Built through switch_schema()+COMPONENT_SCHEMA so it carries the entity/component
187 # defaults register_switch() requires, matching _create_accept_foreign_pairing_switch().
188 # Restore mode DISABLED on purpose: the YAML `silent:` value is the boot state, and
189 # setup() publishes it. Any restoring mode would either fight that or drive write_state()
190 # before the device is even registered.
191 silent_config = switch.switch_schema(
192 IOHomeCoverSilentSwitch,
193 default_restore_mode="DISABLED",
194 entity_category=ENTITY_CATEGORY_CONFIG,
195 ).extend(cv.COMPONENT_SCHEMA)(
196 inherit_esphome_device(
197 {
198 CONF_ID: config[CONF_SILENT_SWITCH_ID],
199 CONF_NAME: silent_switch_name(config),
200 },
201 config,
202 )
203 )
204 silent_switch = await switch.new_switch(silent_config)
205 await cg.register_component(silent_switch, silent_config)
206 cg.add(silent_switch.set_parent(parent))
207 cg.add(silent_switch.set_device_id(config[CONF_IO_DEVICE_ID]))
208
209 if CONF_FAVORITE_BUTTON_ID in config:
210 favorite_config = inherit_esphome_device(
211 {
212 CONF_ID: config[CONF_FAVORITE_BUTTON_ID],
213 CONF_NAME: favorite_button_name(config),
214 CONF_DISABLED_BY_DEFAULT: False,
215 },
216 config,
217 )
218 favorite = await button.new_button(favorite_config)
219 await cg.register_component(favorite, favorite_config)
220 cg.add(favorite.set_parent(parent))
221 cg.add(favorite.set_device_id(config[CONF_IO_DEVICE_ID]))
222 cg.add(favorite.set_command(CoverCommand.FAVORITE))
223
224 if CONF_VENT_BUTTON_ID in config:
225 vent_config = inherit_esphome_device(
226 {
227 CONF_ID: config[CONF_VENT_BUTTON_ID],
228 CONF_NAME: vent_button_name(config),
229 CONF_DISABLED_BY_DEFAULT: False,
230 },
231 config,
232 )
233 vent = await button.new_button(vent_config)
234 await cg.register_component(vent, vent_config)
235 cg.add(vent.set_parent(parent))
236 cg.add(vent.set_device_id(config[CONF_IO_DEVICE_ID]))
237 cg.add(vent.set_command(CoverCommand.VENT))
238
239 await create_companion_sensors(config, parent)
_inject_companion_ids(config)
Definition cover.py:119
silent_switch_name(config)
Definition cover.py:111
vent_button_name(config)
Definition cover.py:103
device_supports_position_control(value)
Definition cover.py:77
device_supports_vent(value)
Definition cover.py:90
favorite_button_name(config)
Definition cover.py:95