Home Assistant Unofficial Reference 2024.12.1
device_tracker.py
Go to the documentation of this file.
1 """Support for THOMSON routers."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import re
7 import telnetlib # pylint: disable=deprecated-module
8 
9 import voluptuous as vol
10 
12  DOMAIN as DEVICE_TRACKER_DOMAIN,
13  PLATFORM_SCHEMA as DEVICE_TRACKER_PLATFORM_SCHEMA,
14  DeviceScanner,
15 )
16 from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
17 from homeassistant.core import HomeAssistant
19 from homeassistant.helpers.typing import ConfigType
20 
21 _LOGGER = logging.getLogger(__name__)
22 
23 _DEVICES_REGEX = re.compile(
24  r"(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s"
25  r"(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+"
26  r"(?P<status>([^\s]+))\s+"
27  r"(?P<type>([^\s]+))\s+"
28  r"(?P<intf>([^\s]+))\s+"
29  r"(?P<hwintf>([^\s]+))\s+"
30  r"(?P<host>([^\s]+))"
31 )
32 
33 PLATFORM_SCHEMA = DEVICE_TRACKER_PLATFORM_SCHEMA.extend(
34  {
35  vol.Required(CONF_HOST): cv.string,
36  vol.Required(CONF_PASSWORD): cv.string,
37  vol.Required(CONF_USERNAME): cv.string,
38  }
39 )
40 
41 
42 def get_scanner(hass: HomeAssistant, config: ConfigType) -> ThomsonDeviceScanner | None:
43  """Validate the configuration and return a THOMSON scanner."""
44  scanner = ThomsonDeviceScanner(config[DEVICE_TRACKER_DOMAIN])
45 
46  return scanner if scanner.success_init else None
47 
48 
49 class ThomsonDeviceScanner(DeviceScanner):
50  """Class which queries a router running THOMSON firmware."""
51 
52  def __init__(self, config):
53  """Initialize the scanner."""
54  self.hosthost = config[CONF_HOST]
55  self.usernameusername = config[CONF_USERNAME]
56  self.passwordpassword = config[CONF_PASSWORD]
57  self.last_resultslast_results = {}
58 
59  # Test the router is accessible.
60  data = self.get_thomson_dataget_thomson_data()
61  self.success_initsuccess_init = data is not None
62 
63  def scan_devices(self):
64  """Scan for new devices and return a list with found device IDs."""
65  self._update_info_update_info()
66  return [client["mac"] for client in self.last_resultslast_results]
67 
68  def get_device_name(self, device):
69  """Return the name of the given device or None if we don't know."""
70  if not self.last_resultslast_results:
71  return None
72  for client in self.last_resultslast_results:
73  if client["mac"] == device:
74  return client["host"]
75  return None
76 
77  def _update_info(self):
78  """Ensure the information from the THOMSON router is up to date.
79 
80  Return boolean if scanning successful.
81  """
82  if not self.success_initsuccess_init:
83  return False
84 
85  _LOGGER.debug("Checking ARP")
86  if not (data := self.get_thomson_dataget_thomson_data()):
87  return False
88 
89  # Flag C stands for CONNECTED
90  active_clients = [
91  client for client in data.values() if client["status"].find("C") != -1
92  ]
93  self.last_resultslast_results = active_clients
94  return True
95 
96  def get_thomson_data(self):
97  """Retrieve data from THOMSON and return parsed result."""
98  try:
99  telnet = telnetlib.Telnet(self.hosthost)
100  telnet.read_until(b"Username : ")
101  telnet.write((self.usernameusername + "\r\n").encode("ascii"))
102  telnet.read_until(b"Password : ")
103  telnet.write((self.passwordpassword + "\r\n").encode("ascii"))
104  telnet.read_until(b"=>")
105  telnet.write(b"hostmgr list\r\n")
106  devices_result = telnet.read_until(b"=>").split(b"\r\n")
107  telnet.write(b"exit\r\n")
108  except EOFError:
109  _LOGGER.exception("Unexpected response from router")
110  return None
111  except ConnectionRefusedError:
112  _LOGGER.exception("Connection refused by router. Telnet enabled?")
113  return None
114 
115  devices = {}
116  for device in devices_result:
117  if match := _DEVICES_REGEX.search(device.decode("utf-8")):
118  devices[match.group("ip")] = {
119  "ip": match.group("ip"),
120  "mac": match.group("mac").upper(),
121  "host": match.group("host"),
122  "status": match.group("status"),
123  }
124  return devices
ThomsonDeviceScanner|None get_scanner(HomeAssistant hass, ConfigType config)