Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Base AndroidTV Entity."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Awaitable, Callable, Coroutine
6 import functools
7 import logging
8 from typing import Any, Concatenate
9 
10 from androidtv.exceptions import LockNotAcquiredException
11 
12 from homeassistant.const import (
13  ATTR_CONNECTIONS,
14  ATTR_IDENTIFIERS,
15  ATTR_MANUFACTURER,
16  ATTR_MODEL,
17  ATTR_SW_VERSION,
18  CONF_HOST,
19  CONF_NAME,
20 )
21 from homeassistant.exceptions import ServiceValidationError
22 from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
23 from homeassistant.helpers.entity import Entity
24 
25 from . import (
26  ADB_PYTHON_EXCEPTIONS,
27  ADB_TCP_EXCEPTIONS,
28  AndroidTVConfigEntry,
29  get_androidtv_mac,
30 )
31 from .const import DEVICE_ANDROIDTV, DOMAIN
32 
33 PREFIX_ANDROIDTV = "Android TV"
34 PREFIX_FIRETV = "Fire TV"
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], Awaitable[_R]]
39 type _ReturnFuncType[_T, **_P, _R] = Callable[
40  Concatenate[_T, _P], Coroutine[Any, Any, _R | None]
41 ]
42 
43 
44 def adb_decorator[_ADBDeviceT: AndroidTVEntity, **_P, _R](
45  override_available: bool = False,
46 ) -> Callable[[_FuncType[_ADBDeviceT, _P, _R]], _ReturnFuncType[_ADBDeviceT, _P, _R]]:
47  """Wrap ADB methods and catch exceptions.
48 
49  Allows for overriding the available status of the ADB connection via the
50  `override_available` parameter.
51  """
52 
53  def _adb_decorator(
54  func: _FuncType[_ADBDeviceT, _P, _R],
55  ) -> _ReturnFuncType[_ADBDeviceT, _P, _R]:
56  """Wrap the provided ADB method and catch exceptions."""
57 
58  @functools.wraps(func)
59  async def _adb_exception_catcher(
60  self: _ADBDeviceT, *args: _P.args, **kwargs: _P.kwargs
61  ) -> _R | None:
62  """Call an ADB-related method and catch exceptions."""
63  if not self.available and not override_available:
64  return None
65 
66  try:
67  return await func(self, *args, **kwargs)
68  except LockNotAcquiredException:
69  # If the ADB lock could not be acquired, skip this command
70  _LOGGER.debug(
71  (
72  "ADB command %s not executed because the connection is"
73  " currently in use"
74  ),
75  func.__name__,
76  )
77  return None
78  except self.exceptions as err:
79  if self.available:
80  _LOGGER.error(
81  (
82  "Failed to execute an ADB command. ADB connection re-"
83  "establishing attempt in the next update. Error: %s"
84  ),
85  err,
86  )
87 
88  await self.aftv.adb_close()
89  self._attr_available = False
90  return None
91  except ServiceValidationError:
92  # Service validation error is thrown because raised by remote services
93  raise
94  except Exception as err: # noqa: BLE001
95  # An unforeseen exception occurred. Close the ADB connection so that
96  # it doesn't happen over and over again.
97  if self.available:
98  _LOGGER.error(
99  (
100  "Unexpected exception executing an ADB command. ADB connection"
101  " re-establishing attempt in the next update. Error: %s"
102  ),
103  err,
104  )
105 
106  await self.aftv.adb_close()
107  self._attr_available = False
108  return None
109 
110  return _adb_exception_catcher
111 
112  return _adb_decorator
113 
114 
116  """Defines a base AndroidTV entity."""
117 
118  _attr_has_entity_name = True
119 
120  def __init__(self, entry: AndroidTVConfigEntry) -> None:
121  """Initialize the AndroidTV base entity."""
122  self.aftvaftv = entry.runtime_data.aftv
123  self._attr_unique_id_attr_unique_id = entry.unique_id
124  self._entry_runtime_data_entry_runtime_data = entry.runtime_data
125 
126  device_class = self.aftvaftv.DEVICE_CLASS
127  device_type = (
128  PREFIX_ANDROIDTV if device_class == DEVICE_ANDROIDTV else PREFIX_FIRETV
129  )
130  # CONF_NAME may be present in entry.data for configuration imported from YAML
131  device_name = entry.data.get(
132  CONF_NAME, f"{device_type} {entry.data[CONF_HOST]}"
133  )
134  info = self.aftvaftv.device_properties
135  model = info.get(ATTR_MODEL)
136  self._attr_device_info_attr_device_info = DeviceInfo(
137  model=f"{model} ({device_type})" if model else device_type,
138  name=device_name,
139  )
140  if self.unique_idunique_id:
141  self._attr_device_info_attr_device_info[ATTR_IDENTIFIERS] = {(DOMAIN, self.unique_idunique_id)}
142  if manufacturer := info.get(ATTR_MANUFACTURER):
143  self._attr_device_info_attr_device_info[ATTR_MANUFACTURER] = manufacturer
144  if sw_version := info.get(ATTR_SW_VERSION):
145  self._attr_device_info_attr_device_info[ATTR_SW_VERSION] = sw_version
146  if mac := get_androidtv_mac(info):
147  self._attr_device_info_attr_device_info[ATTR_CONNECTIONS] = {(CONNECTION_NETWORK_MAC, mac)}
148 
149  # ADB exceptions to catch
150  if not self.aftvaftv.adb_server_ip:
151  # Using "adb_shell" (Python ADB implementation)
152  self.exceptionsexceptions = ADB_PYTHON_EXCEPTIONS
153  else:
154  # Using "pure-python-adb" (communicate with ADB server)
155  self.exceptionsexceptions = ADB_TCP_EXCEPTIONS
None __init__(self, AndroidTVConfigEntry entry)
Definition: entity.py:120
str|None get_androidtv_mac(dict[str, Any] dev_props)
Definition: __init__.py:85