Home Assistant Unofficial Reference 2024.12.1
router.py
Go to the documentation of this file.
1 """The Keenetic Client class."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from datetime import timedelta
7 import logging
8 
9 from ndms2_client import Client, ConnectionException, Device, TelnetConnection
10 from ndms2_client.client import RouterInfo
11 
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.const import (
14  CONF_HOST,
15  CONF_PASSWORD,
16  CONF_PORT,
17  CONF_SCAN_INTERVAL,
18  CONF_USERNAME,
19 )
20 from homeassistant.core import HomeAssistant
21 from homeassistant.exceptions import ConfigEntryNotReady
22 from homeassistant.helpers.device_registry import DeviceInfo
23 from homeassistant.helpers.dispatcher import async_dispatcher_send
24 from homeassistant.helpers.event import async_call_later
25 import homeassistant.util.dt as dt_util
26 
27 from .const import (
28  CONF_CONSIDER_HOME,
29  CONF_INCLUDE_ARP,
30  CONF_INCLUDE_ASSOCIATED,
31  CONF_INTERFACES,
32  CONF_TRY_HOTSPOT,
33  DOMAIN,
34 )
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 
40  """Keenetic client Object."""
41 
42  def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
43  """Initialize the Client."""
44  self.hasshass = hass
45  self.config_entryconfig_entry = config_entry
46  self._last_devices_last_devices: dict[str, Device] = {}
47  self._router_info_router_info: RouterInfo | None = None
48  self._connection_connection: TelnetConnection | None = None
49  self._client_client: Client | None = None
50  self._cancel_periodic_update_cancel_periodic_update: Callable | None = None
51  self._available_available = False
52  self._progress_progress = None
53  self._tracked_interfaces_tracked_interfaces = set(config_entry.options[CONF_INTERFACES])
54 
55  @property
56  def client(self):
57  """Read-only accessor for the client connection."""
58  return self._client_client
59 
60  @property
61  def last_devices(self):
62  """Read-only accessor for last_devices."""
63  return self._last_devices_last_devices
64 
65  @property
66  def host(self):
67  """Return the host of this hub."""
68  return self.config_entryconfig_entry.data[CONF_HOST]
69 
70  @property
71  def device_info(self) -> DeviceInfo:
72  """Return the host of this hub."""
73  return DeviceInfo(
74  identifiers={(DOMAIN, f"router-{self.config_entry.entry_id}")},
75  manufacturer=self.manufacturermanufacturer,
76  model=self.modelmodel,
77  name=self.namename,
78  sw_version=self.firmwarefirmware,
79  )
80 
81  @property
82  def name(self):
83  """Return the name of the hub."""
84  return self._router_info_router_info.name if self._router_info_router_info else self.hosthost
85 
86  @property
87  def model(self):
88  """Return the model of the hub."""
89  return self._router_info_router_info.model if self._router_info_router_info else None
90 
91  @property
92  def firmware(self):
93  """Return the firmware of the hub."""
94  return self._router_info_router_info.fw_version if self._router_info_router_info else None
95 
96  @property
97  def manufacturer(self):
98  """Return the firmware of the hub."""
99  return self._router_info_router_info.manufacturer if self._router_info_router_info else None
100 
101  @property
102  def available(self):
103  """Return if the hub is connected."""
104  return self._available_available
105 
106  @property
108  """Config entry option defining number of seconds from last seen to away."""
109  return timedelta(seconds=self.config_entryconfig_entry.options[CONF_CONSIDER_HOME])
110 
111  @property
113  """Tracked interfaces."""
114  return self._tracked_interfaces_tracked_interfaces
115 
116  @property
117  def signal_update(self):
118  """Event specific per router entry to signal updates."""
119  return f"keenetic-update-{self.config_entry.entry_id}"
120 
121  async def request_update(self):
122  """Request an update."""
123  if self._progress_progress is not None:
124  await self._progress_progress
125  return
126 
127  self._progress_progress = self.hasshass.async_create_task(self.async_updateasync_update())
128  await self._progress_progress
129 
130  self._progress_progress = None
131 
132  async def async_update(self):
133  """Update devices information."""
134  await self.hasshass.async_add_executor_job(self._update_devices_update_devices)
135  async_dispatcher_send(self.hasshass, self.signal_updatesignal_update)
136 
137  async def async_setup(self):
138  """Set up the connection."""
139  self._connection_connection = TelnetConnection(
140  self.config_entryconfig_entry.data[CONF_HOST],
141  self.config_entryconfig_entry.data[CONF_PORT],
142  self.config_entryconfig_entry.data[CONF_USERNAME],
143  self.config_entryconfig_entry.data[CONF_PASSWORD],
144  )
145  self._client_client = Client(self._connection_connection)
146 
147  try:
148  await self.hasshass.async_add_executor_job(self._update_router_info_update_router_info)
149  except ConnectionException as error:
150  raise ConfigEntryNotReady from error
151 
152  async def async_update_data(_now):
153  await self.request_updaterequest_update()
154  self._cancel_periodic_update_cancel_periodic_update = async_call_later(
155  self.hasshass,
156  self.config_entryconfig_entry.options[CONF_SCAN_INTERVAL],
157  async_update_data,
158  )
159 
160  await async_update_data(dt_util.utcnow())
161 
162  async def async_teardown(self):
163  """Teardown up the connection."""
164  if self._cancel_periodic_update_cancel_periodic_update:
165  self._cancel_periodic_update_cancel_periodic_update()
166  self._connection_connection.disconnect()
167 
169  try:
170  self._router_info_router_info = self._client_client.get_router_info()
171  self._available_available = True
172  except Exception:
173  self._available_available = False
174  raise
175 
176  def _update_devices(self):
177  """Get ARP from keenetic router."""
178  _LOGGER.debug("Fetching devices from router")
179 
180  try:
181  _response = self._client_client.get_devices(
182  try_hotspot=self.config_entryconfig_entry.options[CONF_TRY_HOTSPOT],
183  include_arp=self.config_entryconfig_entry.options[CONF_INCLUDE_ARP],
184  include_associated=self.config_entryconfig_entry.options[CONF_INCLUDE_ASSOCIATED],
185  )
186  self._last_devices_last_devices = {
187  dev.mac: dev
188  for dev in _response
189  if dev.interface in self._tracked_interfaces_tracked_interfaces
190  }
191  _LOGGER.debug("Successfully fetched data from router: %s", str(_response))
192  self._router_info_router_info = self._client_client.get_router_info()
193  self._available_available = True
194 
195  except ConnectionException:
196  _LOGGER.error("Error fetching data from router")
197  self._available_available = False
None __init__(self, HomeAssistant hass, ConfigEntry config_entry)
Definition: router.py:42
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193
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