Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Platform for Husqvarna Automower base entity."""
2 
3 import asyncio
4 from collections.abc import Awaitable, Callable, Coroutine
5 import functools
6 import logging
7 from typing import TYPE_CHECKING, Any
8 
9 from aioautomower.exceptions import ApiException
10 from aioautomower.model import MowerActivities, MowerAttributes, MowerStates, WorkArea
11 
12 from homeassistant.core import callback
13 from homeassistant.exceptions import HomeAssistantError
14 from homeassistant.helpers.device_registry import DeviceInfo
15 from homeassistant.helpers.update_coordinator import CoordinatorEntity
16 
17 from . import AutomowerDataUpdateCoordinator
18 from .const import DOMAIN, EXECUTION_TIME_DELAY
19 
20 _LOGGER = logging.getLogger(__name__)
21 
22 ERROR_ACTIVITIES = (
23  MowerActivities.STOPPED_IN_GARDEN,
24  MowerActivities.UNKNOWN,
25  MowerActivities.NOT_APPLICABLE,
26 )
27 ERROR_STATES = [
28  MowerStates.FATAL_ERROR,
29  MowerStates.ERROR,
30  MowerStates.ERROR_AT_POWER_UP,
31  MowerStates.NOT_APPLICABLE,
32  MowerStates.UNKNOWN,
33  MowerStates.STOPPED,
34  MowerStates.OFF,
35 ]
36 
37 
38 @callback
39 def _check_error_free(mower_attributes: MowerAttributes) -> bool:
40  """Check if the mower has any errors."""
41  return (
42  mower_attributes.mower.state not in ERROR_STATES
43  or mower_attributes.mower.activity not in ERROR_ACTIVITIES
44  )
45 
46 
47 @callback
48 def _work_area_translation_key(work_area_id: int, key: str) -> str:
49  """Return the translation key."""
50  if work_area_id == 0:
51  return f"my_lawn_{key}"
52  return f"work_area_{key}"
53 
54 
56  poll_after_sending: bool = False,
57 ) -> Callable[
58  [Callable[..., Awaitable[Any]]], Callable[..., Coroutine[Any, Any, None]]
59 ]:
60  """Handle exceptions while sending a command and optionally refresh coordinator."""
61 
62  def decorator(
63  func: Callable[..., Awaitable[Any]],
64  ) -> Callable[..., Coroutine[Any, Any, None]]:
65  @functools.wraps(func)
66  async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
67  try:
68  await func(self, *args, **kwargs)
69  except ApiException as exception:
70  raise HomeAssistantError(
71  translation_domain=DOMAIN,
72  translation_key="command_send_failed",
73  translation_placeholders={"exception": str(exception)},
74  ) from exception
75  else:
76  if poll_after_sending:
77  # As there are no updates from the websocket for this attribute,
78  # we need to wait until the command is executed and then poll the API.
79  await asyncio.sleep(EXECUTION_TIME_DELAY)
80  await self.coordinator.async_request_refresh()
81 
82  return wrapper
83 
84  return decorator
85 
86 
87 class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]):
88  """Defining the Automower base Entity."""
89 
90  _attr_has_entity_name = True
91 
92  def __init__(
93  self,
94  mower_id: str,
95  coordinator: AutomowerDataUpdateCoordinator,
96  ) -> None:
97  """Initialize AutomowerEntity."""
98  super().__init__(coordinator)
99  self.mower_idmower_id = mower_id
100  self._attr_device_info_attr_device_info = DeviceInfo(
101  identifiers={(DOMAIN, mower_id)},
102  manufacturer="Husqvarna",
103  model=self.mower_attributesmower_attributes.system.model.removeprefix(
104  "HUSQVARNA "
105  ).removeprefix("Husqvarna "),
106  name=self.mower_attributesmower_attributes.system.name,
107  serial_number=self.mower_attributesmower_attributes.system.serial_number,
108  suggested_area="Garden",
109  )
110 
111  @property
112  def mower_attributes(self) -> MowerAttributes:
113  """Get the mower attributes of the current mower."""
114  return self.coordinator.data[self.mower_idmower_id]
115 
116 
118  """Replies available when the mower is connected."""
119 
120  @property
121  def available(self) -> bool:
122  """Return True if the device is available."""
123  return super().available and self.mower_attributesmower_attributes.metadata.connected
124 
125 
127  """Replies available when the mower is connected and not in error state."""
128 
129  @property
130  def available(self) -> bool:
131  """Return True if the device is available."""
132  return super().available and _check_error_free(self.mower_attributesmower_attributes)
133 
134 
136  """Base entity for work work areas."""
137 
138  def __init__(
139  self,
140  mower_id: str,
141  coordinator: AutomowerDataUpdateCoordinator,
142  work_area_id: int,
143  ) -> None:
144  """Initialize AutomowerEntity."""
145  super().__init__(mower_id, coordinator)
146  self.work_area_idwork_area_id = work_area_id
147 
148  @property
149  def work_areas(self) -> dict[int, WorkArea]:
150  """Get the work areas from the mower attributes."""
151  if TYPE_CHECKING:
152  assert self.mower_attributesmower_attributes.work_areas is not None
153  return self.mower_attributesmower_attributes.work_areas
154 
155  @property
156  def work_area_attributes(self) -> WorkArea:
157  """Get the work area attributes of the current work area."""
158  return self.work_areaswork_areas[self.work_area_idwork_area_id]
159 
160  @property
161  def available(self) -> bool:
162  """Return True if the work area is available and the mower has no errors."""
163  return super().available and self.work_area_idwork_area_id in self.work_areaswork_areas
164 
165 
167  """Base entity work work areas with control function."""
None __init__(self, str mower_id, AutomowerDataUpdateCoordinator coordinator)
Definition: entity.py:96
None __init__(self, str mower_id, AutomowerDataUpdateCoordinator coordinator, int work_area_id)
Definition: entity.py:143
str _work_area_translation_key(int work_area_id, str key)
Definition: entity.py:48
bool _check_error_free(MowerAttributes mower_attributes)
Definition: entity.py:39
Callable[[Callable[..., Awaitable[Any]]], Callable[..., Coroutine[Any, Any, None]]] handle_sending_exception(bool poll_after_sending=False)
Definition: entity.py:59