Home Assistant Unofficial Reference 2024.12.1
router.py
Go to the documentation of this file.
1 """Represent the Netgear router and its devices."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from datetime import timedelta
7 import logging
8 from typing import Any
9 
10 from pynetgear import Netgear
11 
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.const import (
14  CONF_HOST,
15  CONF_PASSWORD,
16  CONF_PORT,
17  CONF_SSL,
18  CONF_USERNAME,
19 )
20 from homeassistant.core import HomeAssistant
21 from homeassistant.helpers import device_registry as dr
22 from homeassistant.util import dt as dt_util
23 
24 from .const import (
25  CONF_CONSIDER_HOME,
26  DEFAULT_CONSIDER_HOME,
27  DEFAULT_NAME,
28  DOMAIN,
29  MODE_ROUTER,
30  MODELS_V2,
31 )
32 from .errors import CannotLoginException
33 
34 _LOGGER = logging.getLogger(__name__)
35 
36 
37 def get_api(
38  password: str,
39  host: str | None = None,
40  username: str | None = None,
41  port: int | None = None,
42  ssl: bool = False,
43 ) -> Netgear:
44  """Get the Netgear API and login to it."""
45  api: Netgear = Netgear(password, host, username, port, ssl)
46 
47  if not api.login_try_port():
48  raise CannotLoginException
49 
50  return api
51 
52 
54  """Representation of a Netgear router."""
55 
56  def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
57  """Initialize a Netgear router."""
58  assert entry.unique_id
59  self.hasshass = hass
60  self.entryentry = entry
61  self.entry_identry_id = entry.entry_id
62  self.unique_idunique_id = entry.unique_id
63  self._host: str = entry.data[CONF_HOST]
64  self._port: int = entry.data[CONF_PORT]
65  self._ssl: bool = entry.data[CONF_SSL]
66  self._username_username = entry.data.get(CONF_USERNAME)
67  self._password_password = entry.data[CONF_PASSWORD]
68 
69  self._info_info = None
70  self.modelmodel = ""
71  self.modemode = MODE_ROUTER
72  self.device_namedevice_name = ""
73  self.firmware_versionfirmware_version = ""
74  self.hardware_versionhardware_version = ""
75  self.serial_numberserial_number = ""
76 
77  self.track_devicestrack_devices = True
78  self.method_versionmethod_version = 1
79  consider_home_int = entry.options.get(
80  CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds()
81  )
82  self._consider_home_consider_home = timedelta(seconds=consider_home_int)
83 
84  self.apiapi: Netgear = None
85  self.api_lockapi_lock = asyncio.Lock()
86 
87  self.devices: dict[str, Any] = {}
88 
89  def _setup(self) -> bool:
90  """Set up a Netgear router sync portion."""
91  self.apiapi = get_api(
92  self._password_password,
93  self._host,
94  self._username_username,
95  self._port,
96  self._ssl,
97  )
98 
99  self._info_info = self.apiapi.get_info()
100  if self._info_info is None:
101  return False
102 
103  self.device_namedevice_name = self._info_info.get("DeviceName", DEFAULT_NAME)
104  self.modelmodel = self._info_info.get("ModelName")
105  self.firmware_versionfirmware_version = self._info_info.get("Firmwareversion")
106  self.hardware_versionhardware_version = self._info_info.get("Hardwareversion")
107  self.serial_numberserial_number = self._info_info["SerialNumber"]
108  self.modemode = self._info_info.get("DeviceMode", MODE_ROUTER)
109 
110  enabled_entries = [
111  entry
112  for entry in self.hasshass.config_entries.async_entries(DOMAIN)
113  if entry.disabled_by is None
114  ]
115  self.track_devicestrack_devices = self.modemode == MODE_ROUTER or len(enabled_entries) == 1
116  _LOGGER.debug(
117  "Netgear track_devices = '%s', device mode '%s'",
118  self.track_devicestrack_devices,
119  self.modemode,
120  )
121 
122  for model in MODELS_V2:
123  if self.modelmodel.startswith(model):
124  self.method_versionmethod_version = 2
125 
126  if self.method_versionmethod_version == 2 and self.track_devicestrack_devices:
127  if not self.apiapi.get_attached_devices_2():
128  _LOGGER.error(
129  (
130  "Netgear Model '%s' in MODELS_V2 list, but failed to get"
131  " attached devices using V2"
132  ),
133  self.modelmodel,
134  )
135  self.method_versionmethod_version = 1
136 
137  return True
138 
139  async def async_setup(self) -> bool:
140  """Set up a Netgear router."""
141  async with self.api_lockapi_lock:
142  if not await self.hasshass.async_add_executor_job(self._setup_setup):
143  return False
144 
145  # set already known devices to away instead of unavailable
146  if self.track_devicestrack_devices:
147  device_registry = dr.async_get(self.hasshass)
148  devices = dr.async_entries_for_config_entry(device_registry, self.entry_identry_id)
149  for device_entry in devices:
150  if device_entry.via_device_id is None:
151  continue # do not add the router itself
152 
153  device_mac = dict(device_entry.connections)[dr.CONNECTION_NETWORK_MAC]
154  self.devices[device_mac] = {
155  "mac": device_mac,
156  "name": device_entry.name,
157  "active": False,
158  "last_seen": dt_util.utcnow() - timedelta(days=365),
159  "device_model": None,
160  "device_type": None,
161  "type": None,
162  "link_rate": None,
163  "signal": None,
164  "ip": None,
165  "ssid": None,
166  "conn_ap_mac": None,
167  "allow_or_block": None,
168  }
169 
170  return True
171 
172  async def async_get_attached_devices(self) -> list:
173  """Get the devices connected to the router."""
174  if self.method_versionmethod_version == 1:
175  async with self.api_lockapi_lock:
176  return await self.hasshass.async_add_executor_job(
177  self.apiapi.get_attached_devices
178  )
179 
180  async with self.api_lockapi_lock:
181  return await self.hasshass.async_add_executor_job(
182  self.apiapi.get_attached_devices_2
183  )
184 
185  async def async_update_device_trackers(self, now=None) -> bool:
186  """Update Netgear devices."""
187  new_device = False
188  ntg_devices = await self.async_get_attached_devicesasync_get_attached_devices()
189  now = dt_util.utcnow()
190 
191  if ntg_devices is None:
192  return new_device
193 
194  if _LOGGER.isEnabledFor(logging.DEBUG):
195  _LOGGER.debug("Netgear scan result: \n%s", ntg_devices)
196 
197  for ntg_device in ntg_devices:
198  if ntg_device.mac is None:
199  continue
200 
201  device_mac = dr.format_mac(ntg_device.mac)
202 
203  if not self.devices.get(device_mac):
204  new_device = True
205 
206  # ntg_device is a namedtuple from the collections module that needs conversion to a dict through ._asdict method
207  self.devices[device_mac] = ntg_device._asdict()
208  self.devices[device_mac]["mac"] = device_mac
209  self.devices[device_mac]["last_seen"] = now
210 
211  for device in self.devices.values():
212  device["active"] = now - device["last_seen"] <= self._consider_home_consider_home
213 
214  if new_device:
215  _LOGGER.debug("Netgear tracker: new device found")
216 
217  return new_device
218 
219  async def async_get_traffic_meter(self) -> dict[str, Any] | None:
220  """Get the traffic meter data of the router."""
221  async with self.api_lockapi_lock:
222  return await self.hasshass.async_add_executor_job(self.apiapi.get_traffic_meter)
223 
224  async def async_get_speed_test(self) -> dict[str, Any] | None:
225  """Perform a speed test and get the results from the router."""
226  async with self.api_lockapi_lock:
227  return await self.hasshass.async_add_executor_job(
228  self.apiapi.get_new_speed_test_result
229  )
230 
231  async def async_get_link_status(self) -> dict[str, Any] | None:
232  """Check the ethernet link status of the router."""
233  async with self.api_lockapi_lock:
234  return await self.hasshass.async_add_executor_job(self.apiapi.check_ethernet_link)
235 
236  async def async_allow_block_device(self, mac: str, allow_block: str) -> None:
237  """Allow or block a device connected to the router."""
238  async with self.api_lockapi_lock:
239  await self.hasshass.async_add_executor_job(
240  self.apiapi.allow_block_device, mac, allow_block
241  )
242 
243  async def async_get_utilization(self) -> dict[str, Any] | None:
244  """Get the system information about utilization of the router."""
245  async with self.api_lockapi_lock:
246  return await self.hasshass.async_add_executor_job(self.apiapi.get_system_info)
247 
248  async def async_reboot(self) -> None:
249  """Reboot the router."""
250  async with self.api_lockapi_lock:
251  await self.hasshass.async_add_executor_job(self.apiapi.reboot)
252 
253  async def async_check_new_firmware(self) -> dict[str, Any] | None:
254  """Check for new firmware of the router."""
255  async with self.api_lockapi_lock:
256  return await self.hasshass.async_add_executor_job(self.apiapi.check_new_firmware)
257 
258  async def async_update_new_firmware(self) -> None:
259  """Update the router to the latest firmware."""
260  async with self.api_lockapi_lock:
261  await self.hasshass.async_add_executor_job(self.apiapi.update_new_firmware)
262 
263  @property
264  def port(self) -> int:
265  """Port used by the API."""
266  return self.apiapi.port
267 
268  @property
269  def ssl(self) -> bool:
270  """SSL used by the API."""
271  return self.apiapi.ssl
dict[str, Any]|None async_get_speed_test(self)
Definition: router.py:224
None async_allow_block_device(self, str mac, str allow_block)
Definition: router.py:236
dict[str, Any]|None async_get_link_status(self)
Definition: router.py:231
None __init__(self, HomeAssistant hass, ConfigEntry entry)
Definition: router.py:56
dict[str, Any]|None async_get_traffic_meter(self)
Definition: router.py:219
dict[str, Any]|None async_check_new_firmware(self)
Definition: router.py:253
dict[str, Any]|None async_get_utilization(self)
Definition: router.py:243
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
dict[str, Any]|None get_info(HomeAssistant hass)
Definition: coordinator.py:69
Netgear get_api(str password, str|None host=None, str|None username=None, int|None port=None, bool ssl=False)
Definition: router.py:43