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, the named-position companion buttons (favorite, ventilation), the
8## ``silent`` and ``travel_speed`` keys with their switch and select, the opt-in
9## ``rain_sensor_poll_interval`` key with its rain binary sensor, the opt-in ``sun_level_poll_interval``
10## and ``battery_level_poll_interval`` keys with their sensors, the opt-in ``status_refresh_interval``
11## key, and the opt-in ``battery`` key with its two battery sensors — stay here.
12
13import esphome.codegen as cg
14import esphome.config_validation as cv
15from esphome.components import binary_sensor, button, cover, select, sensor, switch, text_sensor
16from esphome.const import (
17 CONF_DEVICE_CLASS,
18 CONF_DISABLED_BY_DEFAULT,
19 CONF_ID,
20 CONF_NAME,
21 DEVICE_CLASS_BATTERY,
22 DEVICE_CLASS_MOISTURE,
23 ENTITY_CATEGORY_CONFIG,
24 ENTITY_CATEGORY_DIAGNOSTIC,
25 STATE_CLASS_MEASUREMENT,
26 UNIT_PERCENT,
27)
28from esphome.core import ID
29
30from . import home_io_control_ns
31from .hub_names import CONF_INVERT_POSITION
32from .hub_validators import (
33 DEFAULT_COVER_DEVICE_CLASS,
34 FEATURE_EXTENDED_POSITION_FRAME,
35 NAMED_POSITION_FEATURES,
36 device_features,
37 device_type_name,
38 validate_battery_level_poll_interval,
39 validate_rain_sensor_poll_interval,
40 validate_status_refresh_interval,
41 validate_sun_level_poll_interval,
42)
43from .platform_common import (
44 companion_entity_name,
45 companion_id_base,
46 create_companion_sensors,
47 deprecated_key,
48 inherit_esphome_device,
49 inject_companion_sensor_ids,
50 platform_schema_extension,
51 require_feature,
52 validate_device_model,
53 warn_if_experimental_kind,
54 wire_device_binding,
55 CONF_IO_DEVICE_ID,
56 CONF_DEVICE_TYPE,
57 CONF_HOME_IO_CONTROL_ID,
58 CONF_SUBTYPE,
59)
60
61DEPENDENCIES = ["home_io_control"]
62
63CONF_SILENT = "silent"
64CONF_SILENT_SWITCH_ID = "_silent_switch_id"
65CONF_TRAVEL_SPEED = "travel_speed"
66CONF_TRAVEL_SPEED_SELECT_ID = "_travel_speed_select_id"
67CONF_OPTIMISTIC_STATE = "optimistic_state"
68CONF_RAIN_SENSOR_POLL_INTERVAL = "rain_sensor_poll_interval"
69CONF_RAIN_SENSOR_ID = "_rain_sensor_id"
70CONF_SUN_LEVEL_POLL_INTERVAL = "sun_level_poll_interval"
71CONF_SUN_LEVEL_SENSOR_ID = "_sun_level_sensor_id"
72CONF_BATTERY_LEVEL_POLL_INTERVAL = "battery_level_poll_interval"
73CONF_BATTERY_LEVEL_SENSOR_ID = "_battery_level_sensor_id"
74CONF_STATUS_REFRESH_INTERVAL = "status_refresh_interval"
75CONF_BATTERY = "battery"
76CONF_BATTERY_LOW_SENSOR_ID = "_battery_low_sensor_id"
77CONF_BATTERY_SENSOR_ID = "_battery_sensor_id"
78
79# Internal config keys for the cover-only companion button IDs (injected by post-validator).
80CONF_FAVORITE_BUTTON_ID = "_favorite_button_id"
81CONF_VENT_BUTTON_ID = "_vent_button_id"
82CONF_PARTIAL_BUTTON_ID = "_partial_button_id"
83CONF_PEDESTRIAN_BUTTON_ID = "_pedestrian_button_id"
84
85IOHomeCover = home_io_control_ns.class_("IOHomeCover", cover.Cover, cg.Component)
86IOHomeCoverSilentSwitch = home_io_control_ns.class_(
87 "IOHomeCoverSilentSwitch", switch.Switch, cg.Component
88)
89IOHomeCoverTravelSpeedSelect = home_io_control_ns.class_(
90 "IOHomeCoverTravelSpeedSelect", select.Select, cg.Component
91)
92TravelSpeed = home_io_control_ns.enum("TravelSpeed", is_class=True)
93
94# The `travel_speed:` values, which are also the select's options in this order. The names are the
95# ones travel_speed_name() publishes in C++, so the select's state reads back as a valid YAML value.
96TRAVEL_SPEEDS = {
97 "default": TravelSpeed.MOTOR_DEFAULT,
98 "silent": TravelSpeed.SILENT,
99 "fast": TravelSpeed.FAST,
100}
101TRAVEL_SPEED_EXCLUSIVE_MESSAGE = (
102 "silent: and travel_speed: set the same thing; keep one "
103 "(silent: true is travel_speed: silent, silent: false is travel_speed: fast)"
104)
105IOHomeRainBinarySensor = home_io_control_ns.class_(
106 "IOHomeRainBinarySensor", binary_sensor.BinarySensor, cg.Component
107)
108IOHomeSunLevelSensor = home_io_control_ns.class_(
109 "IOHomeSunLevelSensor", sensor.Sensor, cg.Component
110)
111IOHomeBatteryLevelSensor = home_io_control_ns.class_(
112 "IOHomeBatteryLevelSensor", sensor.Sensor, cg.Component
113)
114IOHomeBatteryLowBinarySensor = home_io_control_ns.class_(
115 "IOHomeBatteryLowBinarySensor", binary_sensor.BinarySensor, cg.Component
116)
117IOHomeBatteryTextSensor = home_io_control_ns.class_(
118 "IOHomeBatteryTextSensor", text_sensor.TextSensor, cg.Component
119)
120# One C++ class backs both cover command companions; codegen sets which command each press
121# sends via set_command() (mirrors OneWayButtonAction in hub_names.py). A device-bound `button:`
122# entry is deliberately never the source — see the IOHomeOneWayCommandButton comment there.
123IOHomeCoverCommandButton = home_io_control_ns.class_(
124 "IOHomeCoverCommandButton", button.Button, cg.Component
125)
126CoverCommand = home_io_control_ns.enum("CoverCommand", is_class=True)
127
128# The companion button of each named position, keyed by the CoverCommand name (the keys of
129# hub_validators.NAMED_POSITION_FEATURES): (internal config key of the injected ID, ID suffix, entity
130# name suffix, disabled by default). A button exists for a cover whose kind has the position's bit.
131# The ID suffix and the name are Home Assistant entity ids, so they are pinned by the py-tests.
132NAMED_POSITION_BUTTONS = {
133 "FAVORITE": (CONF_FAVORITE_BUTTON_ID, "favorite_button", "Favorite Position", False),
134 "VENT": (CONF_VENT_BUTTON_ID, "vent_button", "Ventilation Position", False),
135 # Disabled by default: the pedestrian value is experimental (no public source), and a garage
136 # opener runs only the partial position stored in it (issue #163). The user enables them in
137 # Home Assistant.
138 "PEDESTRIAN": (CONF_PEDESTRIAN_BUTTON_ID, "pedestrian_button", "Pedestrian Position", True),
139 "PARTIAL": (CONF_PARTIAL_BUTTON_ID, "partial_button", "Partial Position", True),
140}
141
142
144 """The capability word of the declared io_device_type / io_subtype, or 0 when no type is declared."""
145 if CONF_DEVICE_TYPE not in config:
146 return 0
147 return device_features(config[CONF_DEVICE_TYPE], config.get(CONF_SUBTYPE, 0))
148
149
151 """Give a cover that declares io_device_type and sets no `device_class:` the kind's Home
152 Assistant class; an explicit value, including "", is kept."""
153 if CONF_DEVICE_CLASS not in config and CONF_DEVICE_TYPE in config:
154 default = DEFAULT_COVER_DEVICE_CLASS.get(device_type_name(config[CONF_DEVICE_TYPE]))
155 if default is not None:
156 config[CONF_DEVICE_CLASS] = default
157 return config
158
159
161 """Reject `silent:` or `travel_speed:` on a declared kind that takes no travel-speed parameter."""
162 for key in (CONF_SILENT, CONF_TRAVEL_SPEED):
163 require_feature(
164 config,
165 key,
166 key in config,
167 required=FEATURE_EXTENDED_POSITION_FRAME,
168 reason=f"this device takes no travel-speed parameter; remove {key}:",
169 )
170 return config
171
172
174 """Derive the silent-operation switch name from the parent cover name."""
175 return companion_entity_name(config, "Silent Operation")
176
177
179 """Derive the travel speed select name from the parent cover name."""
180 return companion_entity_name(config, "Travel Speed")
181
182
184 """Derive the rain binary sensor name from the parent cover name."""
185 return companion_entity_name(config, "Rain sensor")
186
187
189 """Derive the sun level sensor name from the parent cover name."""
190 return companion_entity_name(config, "Sun level")
191
192
194 """Derive the battery level (percent) sensor name from the parent cover name."""
195 return companion_entity_name(config, "Battery level")
196
197
199 """Derive the battery-low binary sensor name from the parent cover name."""
200 return companion_entity_name(config, "Battery Low")
201
202
204 """Derive the battery-level text sensor name from the parent cover name."""
205 return companion_entity_name(config, "Battery")
206
207
209 """Declare cover companion entity IDs during schema validation for StaticVector sizing.
210
211 The named-position buttons are cover-only and gated on the declared kind's capability bits, so
212 their injection stays here. The always-present companion sensor IDs are delegated to the
213 shared helper. See platform_common.companion_id_base() for the StaticVector rationale.
214 """
215 base = companion_id_base(config, CONF_ID)
216
217 # Named-position buttons (favourite, ventilation, ...): one per position the declared kind has.
218 features = _declared_features(config)
219 for key, (config_key, suffix, _name, _disabled) in NAMED_POSITION_BUTTONS.items():
220 if features & NAMED_POSITION_FEATURES[key]:
221 config[config_key] = ID(
222 f"{base}_{suffix}",
223 is_declaration=True,
224 type=IOHomeCoverCommandButton,
225 )
226
227 # Silent-operation toggle — only when the cover declares `silent:` at all. Declaring the key
228 # is what opts a cover into runtime control of its travel profile; a config that never mentions
229 # it gains no entity, and the YAML value is simply the boot state.
230 if CONF_SILENT in config:
231 config[CONF_SILENT_SWITCH_ID] = ID(
232 f"{base}_silent_switch",
233 is_declaration=True,
234 type=IOHomeCoverSilentSwitch,
235 )
236
237 # Travel speed select — likewise only when the cover declares `travel_speed:`, the three-way
238 # form of the same setting. The two keys are exclusive, so a cover has one entity or the other.
239 if CONF_TRAVEL_SPEED in config:
240 config[CONF_TRAVEL_SPEED_SELECT_ID] = ID(
241 f"{base}_travel_speed_select",
242 is_declaration=True,
243 type=IOHomeCoverTravelSpeedSelect,
244 )
245
246 # Rain binary sensor — only when the cover declares `rain_sensor_poll_interval:`. The key is
247 # the opt-in to the periodic limitation read, so a config without it gains no entity.
248 if CONF_RAIN_SENSOR_POLL_INTERVAL in config:
249 config[CONF_RAIN_SENSOR_ID] = ID(
250 f"{base}_rain_sensor",
251 is_declaration=True,
252 type=IOHomeRainBinarySensor,
253 )
254
255 # Sun level and battery level sensors — each only when the cover declares its poll interval,
256 # which is the opt-in to that poll (a subscription the device answers with the reading).
257 if CONF_SUN_LEVEL_POLL_INTERVAL in config:
258 config[CONF_SUN_LEVEL_SENSOR_ID] = ID(
259 f"{base}_sun_level_sensor",
260 is_declaration=True,
261 type=IOHomeSunLevelSensor,
262 )
263 if CONF_BATTERY_LEVEL_POLL_INTERVAL in config:
264 config[CONF_BATTERY_LEVEL_SENSOR_ID] = ID(
265 f"{base}_battery_level_sensor",
266 is_declaration=True,
267 type=IOHomeBatteryLevelSensor,
268 )
269
270 # Battery sensors — only with `battery: true`. The level is decoded from status replies the hub
271 # already receives, so the key adds entities but no radio traffic.
272 if config.get(CONF_BATTERY, False):
273 config[CONF_BATTERY_LOW_SENSOR_ID] = ID(
274 f"{base}_battery_low_sensor",
275 is_declaration=True,
276 type=IOHomeBatteryLowBinarySensor,
277 )
278 config[CONF_BATTERY_SENSOR_ID] = ID(
279 f"{base}_battery_sensor",
280 is_declaration=True,
281 type=IOHomeBatteryTextSensor,
282 )
283
284 # Companion diagnostic sensors — always generated (shared with other platforms).
285 return inject_companion_sensor_ids(config, CONF_ID)
286
287
289 """What a cover with `silent:` writes instead, and what changes in Home Assistant."""
290 speed = "silent" if config[CONF_SILENT] else "fast"
291 return (
292 f"Replace 'silent: {str(config[CONF_SILENT]).lower()}' with 'travel_speed: {speed}'. The "
293 f"'{silent_switch_name(config)}' switch is then replaced by a '{travel_speed_select_name(config)}' "
294 "select (default, silent, fast): update any automation or dashboard that uses the switch."
295 )
296
297
298CONFIG_SCHEMA = cv.All(
299 cover.cover_schema(IOHomeCover)
300 .extend(platform_schema_extension())
301 .extend({cv.Optional(CONF_INVERT_POSITION): cv.boolean})
302 .extend(
303 {
304 # Both set the travel speed: `silent:` two-way, `travel_speed:` three-way.
305 cv.Exclusive(
306 CONF_SILENT, "travel_speed", msg=TRAVEL_SPEED_EXCLUSIVE_MESSAGE
307 ): cv.boolean,
308 cv.Exclusive(
309 CONF_TRAVEL_SPEED, "travel_speed", msg=TRAVEL_SPEED_EXCLUSIVE_MESSAGE
310 ): cv.enum(TRAVEL_SPEEDS, lower=True),
311 }
312 )
313 .extend(
314 {
315 cv.Optional(
316 CONF_RAIN_SENSOR_POLL_INTERVAL
317 ): validate_rain_sensor_poll_interval
318 }
319 )
320 .extend(
321 {
322 cv.Optional(CONF_SUN_LEVEL_POLL_INTERVAL): validate_sun_level_poll_interval,
323 cv.Optional(
324 CONF_BATTERY_LEVEL_POLL_INTERVAL
325 ): validate_battery_level_poll_interval,
326 cv.Optional(CONF_STATUS_REFRESH_INTERVAL): validate_status_refresh_interval,
327 }
328 )
329 .extend({cv.Optional(CONF_BATTERY): cv.boolean})
330 .extend({cv.Optional(CONF_OPTIMISTIC_STATE, default=True): cv.boolean})
331 .extend(cv.COMPONENT_SCHEMA),
332 validate_device_model,
333 _default_device_class,
334 _require_travel_speed_kind,
335 deprecated_key(CONF_SILENT, _silent_replacement),
336 warn_if_experimental_kind,
337 _inject_companion_ids,
338)
339
340
341async def to_code(config):
342 var = await cover.new_cover(config)
343 await cg.register_component(var, config)
344
345 parent = await cg.get_variable(config[CONF_HOME_IO_CONTROL_ID])
346 await wire_device_binding(var, parent, config)
347
348 if CONF_INVERT_POSITION in config:
349 cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
350
351 cg.add(var.set_optimistic_state(config[CONF_OPTIMISTIC_STATE]))
352 if CONF_SILENT in config:
353 speed = TravelSpeed.SILENT if config[CONF_SILENT] else TravelSpeed.FAST
354 cg.add(var.set_travel_speed(speed))
355 if CONF_TRAVEL_SPEED in config:
356 cg.add(var.set_travel_speed(config[CONF_TRAVEL_SPEED]))
357
358 if CONF_SILENT_SWITCH_ID in config:
359 # Built through switch_schema()+COMPONENT_SCHEMA so it carries the entity/component
360 # defaults register_switch() requires, matching _create_accept_foreign_pairing_switch().
361 # Restore mode DISABLED on purpose: the YAML `silent:` value is the boot state, and
362 # setup() publishes it. Any restoring mode would either fight that or drive write_state()
363 # before the device is even registered.
364 silent_config = switch.switch_schema(
365 IOHomeCoverSilentSwitch,
366 default_restore_mode="DISABLED",
367 entity_category=ENTITY_CATEGORY_CONFIG,
368 ).extend(cv.COMPONENT_SCHEMA)(
369 inherit_esphome_device(
370 {
371 CONF_ID: config[CONF_SILENT_SWITCH_ID],
372 CONF_NAME: silent_switch_name(config),
373 },
374 config,
375 )
376 )
377 silent_switch = await switch.new_switch(silent_config)
378 await cg.register_component(silent_switch, silent_config)
379 cg.add(silent_switch.set_parent(parent))
380 cg.add(silent_switch.set_device_id(config[CONF_IO_DEVICE_ID]))
381
382 if CONF_TRAVEL_SPEED_SELECT_ID in config:
383 # Nothing restores it: the YAML `travel_speed:` value is the boot state, as for the switch
384 # above, and setup() publishes it.
385 speed_config = select.select_schema(
386 IOHomeCoverTravelSpeedSelect,
387 entity_category=ENTITY_CATEGORY_CONFIG,
388 ).extend(cv.COMPONENT_SCHEMA)(
389 inherit_esphome_device(
390 {
391 CONF_ID: config[CONF_TRAVEL_SPEED_SELECT_ID],
392 CONF_NAME: travel_speed_select_name(config),
393 },
394 config,
395 )
396 )
397 speed_select = await select.new_select(
398 speed_config, options=list(TRAVEL_SPEEDS)
399 )
400 await cg.register_component(speed_select, speed_config)
401 cg.add(speed_select.set_parent(parent))
402 cg.add(speed_select.set_device_id(config[CONF_IO_DEVICE_ID]))
403
404 if CONF_RAIN_SENSOR_ID in config:
405 cg.add(
406 var.set_rain_sensor_poll_interval(
407 config[CONF_RAIN_SENSOR_POLL_INTERVAL].total_milliseconds
408 )
409 )
410 rain_config = binary_sensor.binary_sensor_schema(
411 IOHomeRainBinarySensor, device_class=DEVICE_CLASS_MOISTURE
412 ).extend(cv.COMPONENT_SCHEMA)(
413 inherit_esphome_device(
414 {
415 CONF_ID: config[CONF_RAIN_SENSOR_ID],
416 CONF_NAME: rain_sensor_name(config),
417 },
418 config,
419 )
420 )
421 rain_sensor = await binary_sensor.new_binary_sensor(rain_config)
422 await cg.register_component(rain_sensor, rain_config)
423 cg.add(rain_sensor.set_parent(parent))
424 cg.add(rain_sensor.set_device_id(config[CONF_IO_DEVICE_ID]))
425
426 if CONF_SUN_LEVEL_SENSOR_ID in config:
427 cg.add(
428 var.set_sun_level_poll_interval(
429 config[CONF_SUN_LEVEL_POLL_INTERVAL].total_milliseconds
430 )
431 )
432 # No unit and no device class: the value is published raw until its unit is known.
434 config,
435 parent,
436 IOHomeSunLevelSensor,
437 CONF_SUN_LEVEL_SENSOR_ID,
438 sun_level_sensor_name(config),
439 icon="mdi:white-balance-sunny",
440 )
441
442 if CONF_BATTERY_LEVEL_SENSOR_ID in config:
443 cg.add(
444 var.set_battery_level_poll_interval(
445 config[CONF_BATTERY_LEVEL_POLL_INTERVAL].total_milliseconds
446 )
447 )
449 config,
450 parent,
451 IOHomeBatteryLevelSensor,
452 CONF_BATTERY_LEVEL_SENSOR_ID,
454 unit_of_measurement=UNIT_PERCENT,
455 device_class=DEVICE_CLASS_BATTERY,
456 )
457
458 if CONF_STATUS_REFRESH_INTERVAL in config:
459 cg.add(
460 var.set_status_refresh_interval(
461 config[CONF_STATUS_REFRESH_INTERVAL].total_milliseconds
462 )
463 )
464
465 if CONF_BATTERY_LOW_SENSOR_ID in config:
466 cg.add(var.set_battery_entities(True))
467 await _create_battery_sensors(config, parent)
468
469 for key, (config_key, _suffix, name, disabled) in NAMED_POSITION_BUTTONS.items():
470 if config_key not in config:
471 continue
472 button_config = inherit_esphome_device(
473 {
474 CONF_ID: config[config_key],
475 CONF_NAME: companion_entity_name(config, name),
476 CONF_DISABLED_BY_DEFAULT: disabled,
477 },
478 config,
479 )
480 position_button = await button.new_button(button_config)
481 await cg.register_component(position_button, button_config)
482 cg.add(position_button.set_parent(parent))
483 cg.add(position_button.set_device_id(config[CONF_IO_DEVICE_ID]))
484 cg.add(position_button.set_command(getattr(CoverCommand, key)))
485
486 await create_companion_sensors(config, parent)
487
488
489async def _create_reading_sensor(config, parent, cls, id_key, name, **schema_kwargs):
490 """Create one polled reading sensor (sun level, battery level) bound to the cover's device."""
491 sensor_config = sensor.sensor_schema(
492 cls,
493 accuracy_decimals=0,
494 state_class=STATE_CLASS_MEASUREMENT,
495 **schema_kwargs,
496 ).extend(cv.COMPONENT_SCHEMA)(
497 inherit_esphome_device(
498 {
499 CONF_ID: config[id_key],
500 CONF_NAME: name,
501 },
502 config,
503 )
504 )
505 reading_sensor = await sensor.new_sensor(sensor_config)
506 await cg.register_component(reading_sensor, sensor_config)
507 cg.add(reading_sensor.set_parent(parent))
508 cg.add(reading_sensor.set_device_id(config[CONF_IO_DEVICE_ID]))
509
510
511async def _create_battery_sensors(config, parent):
512 """Create the two `battery: true` companions.
513
514 Battery Low is the enabled one: a `device_class: battery` binary sensor is Home Assistant's own
515 low-battery signal. Battery (full/normal/low) is diagnostic and disabled by default; ESPHome
516 text sensors carry no battery device class, so nothing generic reads it.
517 """
518 low_config = binary_sensor.binary_sensor_schema(
519 IOHomeBatteryLowBinarySensor,
520 device_class=DEVICE_CLASS_BATTERY,
521 entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
522 ).extend(cv.COMPONENT_SCHEMA)(
523 inherit_esphome_device(
524 {
525 CONF_ID: config[CONF_BATTERY_LOW_SENSOR_ID],
526 CONF_NAME: battery_low_sensor_name(config),
527 },
528 config,
529 )
530 )
531 low_sensor = await binary_sensor.new_binary_sensor(low_config)
532 await cg.register_component(low_sensor, low_config)
533 cg.add(low_sensor.set_parent(parent))
534 cg.add(low_sensor.set_device_id(config[CONF_IO_DEVICE_ID]))
535
536 level_config = text_sensor.text_sensor_schema(
537 IOHomeBatteryTextSensor,
538 entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
539 icon="mdi:battery",
540 ).extend(cv.COMPONENT_SCHEMA)(
541 inherit_esphome_device(
542 {
543 CONF_ID: config[CONF_BATTERY_SENSOR_ID],
544 CONF_NAME: battery_sensor_name(config),
545 CONF_DISABLED_BY_DEFAULT: True,
546 },
547 config,
548 )
549 )
550 level_sensor = await text_sensor.new_text_sensor(level_config)
551 await cg.register_component(level_sensor, level_config)
552 cg.add(level_sensor.set_parent(parent))
553 cg.add(level_sensor.set_device_id(config[CONF_IO_DEVICE_ID]))
battery_sensor_name(config)
Definition cover.py:203
battery_level_sensor_name(config)
Definition cover.py:193
_silent_replacement(config)
Definition cover.py:288
rain_sensor_name(config)
Definition cover.py:183
battery_low_sensor_name(config)
Definition cover.py:198
_create_battery_sensors(config, parent)
Definition cover.py:511
_default_device_class(config)
Definition cover.py:150
_create_reading_sensor(config, parent, cls, id_key, name, **schema_kwargs)
Definition cover.py:489
silent_switch_name(config)
Definition cover.py:173
_declared_features(config)
Definition cover.py:143
travel_speed_select_name(config)
Definition cover.py:178
_require_travel_speed_kind(config)
Definition cover.py:160
sun_level_sensor_name(config)
Definition cover.py:188
_inject_companion_ids(config)
Definition cover.py:208