Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Support for Motionblinds using their WLAN API."""
2 
3 from __future__ import annotations
4 
5 from motionblinds import DEVICE_TYPES_GATEWAY, DEVICE_TYPES_WIFI, MotionGateway
6 from motionblinds.motion_blinds import MotionBlind
7 
8 from homeassistant.core import CALLBACK_TYPE
9 from homeassistant.helpers import device_registry as dr
10 from homeassistant.helpers.device_registry import DeviceInfo
11 from homeassistant.helpers.event import async_call_later
12 from homeassistant.helpers.update_coordinator import CoordinatorEntity
13 
14 from .const import (
15  ATTR_AVAILABLE,
16  DEFAULT_GATEWAY_NAME,
17  DOMAIN,
18  KEY_GATEWAY,
19  MANUFACTURER,
20  UPDATE_INTERVAL_MOVING,
21  UPDATE_INTERVAL_MOVING_WIFI,
22 )
23 from .coordinator import DataUpdateCoordinatorMotionBlinds
24 from .gateway import device_name
25 
26 
27 class MotionCoordinatorEntity(CoordinatorEntity[DataUpdateCoordinatorMotionBlinds]):
28  """Representation of a Motionblind entity."""
29 
30  _attr_has_entity_name = True
31 
32  def __init__(
33  self,
34  coordinator: DataUpdateCoordinatorMotionBlinds,
35  blind: MotionGateway | MotionBlind,
36  ) -> None:
37  """Initialize the entity."""
38  super().__init__(coordinator)
39 
40  self._blind_blind = blind
41  self._api_lock_api_lock = coordinator.api_lock
42 
43  self._requesting_position_requesting_position: CALLBACK_TYPE | None = None
44  self._previous_positions_previous_positions: list[int | dict | None] = []
45 
46  if blind.device_type in DEVICE_TYPES_WIFI:
47  self._update_interval_moving_update_interval_moving = UPDATE_INTERVAL_MOVING_WIFI
48  else:
49  self._update_interval_moving_update_interval_moving = UPDATE_INTERVAL_MOVING
50 
51  if blind.device_type in DEVICE_TYPES_GATEWAY:
52  gateway = blind
53  else:
54  gateway = blind._gateway # noqa: SLF001
55  if gateway.firmware is not None:
56  sw_version = f"{gateway.firmware}, protocol: {gateway.protocol}"
57  else:
58  sw_version = f"Protocol: {gateway.protocol}"
59 
60  if blind.device_type in DEVICE_TYPES_GATEWAY:
61  self._attr_device_info_attr_device_info = DeviceInfo(
62  connections={(dr.CONNECTION_NETWORK_MAC, blind.mac)},
63  identifiers={(DOMAIN, blind.mac)},
64  manufacturer=MANUFACTURER,
65  name=DEFAULT_GATEWAY_NAME,
66  model="Wi-Fi bridge",
67  sw_version=sw_version,
68  )
69  elif blind.device_type in DEVICE_TYPES_WIFI:
70  self._attr_device_info_attr_device_info = DeviceInfo(
71  connections={(dr.CONNECTION_NETWORK_MAC, blind.mac)},
72  identifiers={(DOMAIN, blind.mac)},
73  manufacturer=MANUFACTURER,
74  model=blind.blind_type,
75  name=device_name(blind),
76  sw_version=sw_version,
77  hw_version=blind.wireless_name,
78  )
79  else:
80  self._attr_device_info_attr_device_info = DeviceInfo(
81  identifiers={(DOMAIN, blind.mac)},
82  manufacturer=MANUFACTURER,
83  model=blind.blind_type,
84  name=device_name(blind),
85  via_device=(DOMAIN, blind._gateway.mac), # noqa: SLF001
86  hw_version=blind.wireless_name,
87  )
88 
89  @property
90  def available(self) -> bool:
91  """Return True if entity is available."""
92  if self.coordinator.data is None:
93  return False
94 
95  gateway_available = self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]
96  if not gateway_available or self._blind_blind.device_type in DEVICE_TYPES_GATEWAY:
97  return gateway_available
98 
99  return self.coordinator.data[self._blind_blind.mac][ATTR_AVAILABLE]
100 
101  async def async_added_to_hass(self) -> None:
102  """Subscribe to multicast pushes and register signal handler."""
103  self._blind_blind.Register_callback(self.unique_id, self.schedule_update_ha_state)
104  await super().async_added_to_hass()
105 
106  async def async_will_remove_from_hass(self) -> None:
107  """Unsubscribe when removed."""
108  self._blind_blind.Remove_callback(self.unique_id)
109  await super().async_will_remove_from_hass()
110 
111  async def async_scheduled_update_request(self, *_) -> None:
112  """Request a state update from the blind at a scheduled point in time."""
113  # add the last position to the list and keep the list at max 2 items
114  self._previous_positions_previous_positions.append(self._blind_blind.position)
115  if len(self._previous_positions_previous_positions) > 2:
116  del self._previous_positions_previous_positions[: len(self._previous_positions_previous_positions) - 2]
117 
118  async with self._api_lock_api_lock:
119  await self.hasshass.async_add_executor_job(self._blind_blind.Update_trigger)
120 
121  self.coordinator.async_update_listeners()
122 
123  if len(self._previous_positions_previous_positions) < 2 or not all(
124  self._blind_blind.position == prev_position
125  for prev_position in self._previous_positions_previous_positions
126  ):
127  # keep updating the position @self._update_interval_moving until the position does not change.
128  self._requesting_position_requesting_position = async_call_later(
129  self.hasshass,
130  self._update_interval_moving_update_interval_moving,
131  self.async_scheduled_update_requestasync_scheduled_update_request,
132  )
133  else:
134  self._previous_positions_previous_positions = []
135  self._requesting_position_requesting_position = None
136 
137  async def async_request_position_till_stop(self, delay: int | None = None) -> None:
138  """Request the position of the blind every self._update_interval_moving seconds until it stops moving."""
139  if delay is None:
140  delay = self._update_interval_moving_update_interval_moving
141 
142  self._previous_positions_previous_positions = []
143  if self._blind_blind.position is None:
144  return
145  if self._requesting_position_requesting_position is not None:
146  self._requesting_position_requesting_position()
147 
148  self._requesting_position_requesting_position = async_call_later(
149  self.hasshass, delay, self.async_scheduled_update_requestasync_scheduled_update_request
150  )
None __init__(self, DataUpdateCoordinatorMotionBlinds coordinator, MotionGateway|MotionBlind blind)
Definition: entity.py:36
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
Definition: event.py:1597