1 """Class to hold all cover accessories."""
6 from pyhap.const
import (
8 CATEGORY_GARAGE_DOOR_OPENER,
10 CATEGORY_WINDOW_COVERING,
12 from pyhap.service
import Service
13 from pyhap.util
import callback
as pyhap_callback
16 ATTR_CURRENT_POSITION,
17 ATTR_CURRENT_TILT_POSITION,
20 DOMAIN
as COVER_DOMAIN,
26 ATTR_SUPPORTED_FEATURES,
29 SERVICE_SET_COVER_POSITION,
30 SERVICE_SET_COVER_TILT_POSITION,
36 EventStateChangedData,
43 from .accessories
import TYPES, HomeAccessory
45 ATTR_OBSTRUCTION_DETECTED,
46 CHAR_CURRENT_DOOR_STATE,
47 CHAR_CURRENT_POSITION,
48 CHAR_CURRENT_TILT_ANGLE,
50 CHAR_OBSTRUCTION_DETECTED,
52 CHAR_TARGET_DOOR_STATE,
54 CHAR_TARGET_TILT_ANGLE,
55 CONF_LINKED_OBSTRUCTION_SENSOR,
60 HK_POSITION_GOING_TO_MAX,
61 HK_POSITION_GOING_TO_MIN,
66 SERV_GARAGE_DOOR_OPENER,
71 DOOR_CURRENT_HASS_TO_HK = {
72 CoverState.OPEN: HK_DOOR_OPEN,
73 CoverState.CLOSED: HK_DOOR_CLOSED,
74 CoverState.OPENING: HK_DOOR_OPENING,
75 CoverState.CLOSING: HK_DOOR_CLOSING,
84 DOOR_TARGET_HASS_TO_HK = {
85 CoverState.OPEN: HK_DOOR_OPEN,
86 CoverState.CLOSED: HK_DOOR_CLOSED,
87 CoverState.OPENING: HK_DOOR_OPEN,
88 CoverState.CLOSING: HK_DOOR_CLOSED,
91 MOVING_STATES = {CoverState.OPENING, CoverState.CLOSING}
93 _LOGGER = logging.getLogger(__name__)
96 @TYPES.register("GarageDoorOpener")
98 """Generate a Garage Door Opener accessory for a cover entity.
100 The cover entity must be in the 'garage' device class
101 and support no more than open, close, and stop.
105 """Initialize a GarageDoorOpener accessory object."""
106 super().
__init__(*args, category=CATEGORY_GARAGE_DOOR_OPENER)
110 serv_garage_door = self.add_preload_service(SERV_GARAGE_DOOR_OPENER)
112 CHAR_CURRENT_DOOR_STATE, value=0
115 CHAR_TARGET_DOOR_STATE, value=0, setter_callback=self.
set_stateset_state
118 CHAR_OBSTRUCTION_DETECTED, value=
False
132 """Handle accessory driver started event.
134 Run inside the Home Assistant event loop.
137 self._subscriptions.append(
142 job_type=HassJobType.Callback,
150 self, event: Event[EventStateChangedData]
152 """Handle state change event listener callback."""
157 """Handle linked obstruction sensor state change to update HomeKit value."""
161 detected = new_state.state == STATE_ON
167 "%s: Set linked obstruction %s sensor to %d",
174 """Change garage state if call came from HomeKit."""
175 _LOGGER.debug(
"%s: Set state to %d", self.
entity_identity_id, value)
177 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
178 if value == HK_DOOR_OPEN:
182 elif value == HK_DOOR_CLOSED:
189 """Update cover state after state changed."""
190 hass_state: CoverState = new_state.state
191 target_door_state = DOOR_TARGET_HASS_TO_HK.get(hass_state)
192 current_door_state = DOOR_CURRENT_HASS_TO_HK.get(hass_state)
194 if ATTR_OBSTRUCTION_DETECTED
in new_state.attributes:
195 obstruction_detected = (
196 new_state.attributes[ATTR_OBSTRUCTION_DETECTED]
is True
200 if target_door_state
is not None:
202 if current_door_state
is not None:
207 """Generate a base Window accessory for a cover entity.
209 This class is used for WindowCoveringBasic and
213 def __init__(self, *args: Any, category: int, service: Service) ->
None:
214 """Initialize a OpeningDeviceBase accessory object."""
215 super().
__init__(*args, category=category)
218 self.features: int = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
222 self.
charschars.append(CHAR_HOLD_POSITION)
223 self.
_supports_tilt_supports_tilt = self.features & CoverEntityFeature.SET_TILT_POSITION
226 self.
charschars.extend([CHAR_TARGET_TILT_ANGLE, CHAR_CURRENT_TILT_ANGLE])
232 CHAR_HOLD_POSITION, setter_callback=self.
set_stopset_stop
237 CHAR_TARGET_TILT_ANGLE, setter_callback=self.
set_tiltset_tilt
240 CHAR_CURRENT_TILT_ANGLE, value=0
244 """Stop the cover motion from HomeKit."""
248 COVER_DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: self.
entity_identity_id}
252 """Set tilt to value if call came from HomeKit."""
253 _LOGGER.debug(
"%s: Set tilt to %d", self.
entity_identity_id, value)
257 value = round((value + 90) / 180.0 * 100.0)
259 params = {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_TILT_POSITION: value}
262 COVER_DOMAIN, SERVICE_SET_COVER_TILT_POSITION, params, value
267 """Update cover position and tilt after state changed."""
271 current_tilt = new_state.attributes.get(ATTR_CURRENT_TILT_POSITION)
272 if not isinstance(current_tilt, (float, int)):
276 current_tilt = (current_tilt / 100.0 * 180.0) - 90.0
277 current_tilt =
int(current_tilt)
283 """Generate a Window/WindowOpening accessory for a cover entity.
285 The cover entity must support: set_cover_position.
288 def __init__(self, *args: Any, category: int, service: Service) ->
None:
289 """Initialize a WindowCovering accessory object."""
290 super().
__init__(*args, category=category, service=service)
294 CHAR_CURRENT_POSITION, value=0
296 target_args: dict[str, Any] = {
"value": 0}
297 if self.features & CoverEntityFeature.SET_POSITION:
298 target_args[
"setter_callback"] = self.
move_covermove_cover
305 "%s does not support setting position, current position will be"
310 target_args[
"properties"] = {PROP_MIN_VALUE: 0, PROP_MAX_VALUE: 0}
313 CHAR_TARGET_POSITION, **target_args
316 CHAR_POSITION_STATE, value=HK_POSITION_STOPPED
321 """Move cover to value if call came from HomeKit."""
322 _LOGGER.debug(
"%s: Set position to %d", self.
entity_identity_id, value)
323 params = {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_POSITION: value}
324 self.
async_call_serviceasync_call_service(COVER_DOMAIN, SERVICE_SET_COVER_POSITION, params, value)
328 """Update cover position and tilt after state changed."""
329 current_position = new_state.attributes.get(ATTR_CURRENT_POSITION)
330 if isinstance(current_position, (float, int)):
331 current_position =
int(current_position)
335 if new_state.state
not in MOVING_STATES:
344 @TYPES.register("Door")
346 """Generate a Door accessory for a cover entity.
348 The entity must support: set_cover_position.
352 """Initialize a Door accessory object."""
353 super().
__init__(*args, category=CATEGORY_DOOR, service=SERV_DOOR)
356 @TYPES.register("Window")
358 """Generate a Window accessory for a cover entity with WINDOW device class.
360 The entity must support: set_cover_position.
364 """Initialize a Window accessory object."""
365 super().
__init__(*args, category=CATEGORY_WINDOW, service=SERV_WINDOW)
368 @TYPES.register("WindowCovering")
370 """Generate a WindowCovering accessory for a cover entity.
372 The entity must support: set_cover_position.
376 """Initialize a WindowCovering accessory object."""
378 *args, category=CATEGORY_WINDOW_COVERING, service=SERV_WINDOW_COVERING
382 @TYPES.register("WindowCoveringBasic")
384 """Generate a Window accessory for a cover entity.
386 The cover entity must support: open_cover, close_cover,
387 stop_cover (optional).
391 """Initialize a WindowCoveringBasic accessory object."""
393 *args, category=CATEGORY_WINDOW_COVERING, service=SERV_WINDOW_COVERING
398 CHAR_CURRENT_POSITION, value=0
401 CHAR_TARGET_POSITION, value=0, setter_callback=self.
move_covermove_cover
404 CHAR_POSITION_STATE, value=HK_POSITION_STOPPED
409 """Move cover to value if call came from HomeKit."""
410 _LOGGER.debug(
"%s: Set position to %d", self.
entity_identity_id, value)
418 service, position = (SERVICE_OPEN_COVER, 100)
420 service, position = (SERVICE_CLOSE_COVER, 0)
422 service, position = (SERVICE_STOP_COVER, 50)
424 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
433 """Update cover position after state changed."""
434 position_mapping = {CoverState.OPEN: 100, CoverState.CLOSED: 0}
435 _state: CoverState = new_state.state
436 hk_position = position_mapping.get(_state)
437 if hk_position
is not None:
438 is_moving = _state
in MOVING_STATES
452 """Convert hass state to homekit position state."""
453 if state == CoverState.OPENING:
454 return HK_POSITION_GOING_TO_MAX
455 if state == CoverState.CLOSING:
456 return HK_POSITION_GOING_TO_MIN
457 return HK_POSITION_STOPPED
None async_call_service(self, str domain, str service, dict[str, Any]|None service_data, Any|None value=None)
None async_update_state(self, State new_state)
None __init__(self, *Any args)
None _async_update_obstruction_state(self, State|None new_state)
None _async_update_obstruction_event(self, Event[EventStateChangedData] event)
None set_state(self, int value)
None async_update_state(self, State new_state)
linked_obstruction_sensor
char_obstruction_detected
None __init__(self, *Any args)
None __init__(self, *Any args, int category, Service service)
None set_tilt(self, float value)
None set_stop(self, int value)
None async_update_state(self, State new_state)
None async_update_state(self, State new_state)
None move_cover(self, int value)
None __init__(self, *Any args, int category, Service service)
None move_cover(self, int value)
None async_update_state(self, State new_state)
None __init__(self, *Any args)
None __init__(self, *Any args)
None __init__(self, *Any args)
web.Response get(self, web.Request request, str config_key)
int _hass_state_to_position_start(str state)
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)