Home Assistant Unofficial Reference 2024.12.1
device_tracker.py
Go to the documentation of this file.
1 """Support for Tomato routers."""
2 
3 from __future__ import annotations
4 
5 from http import HTTPStatus
6 import json
7 import logging
8 import re
9 
10 import requests
11 import voluptuous as vol
12 
14  DOMAIN as DEVICE_TRACKER_DOMAIN,
15  PLATFORM_SCHEMA as DEVICE_TRACKER_PLATFORM_SCHEMA,
16  DeviceScanner,
17 )
18 from homeassistant.const import (
19  CONF_HOST,
20  CONF_PASSWORD,
21  CONF_PORT,
22  CONF_SSL,
23  CONF_USERNAME,
24  CONF_VERIFY_SSL,
25 )
26 from homeassistant.core import HomeAssistant
28 from homeassistant.helpers.typing import ConfigType
29 
30 CONF_HTTP_ID = "http_id"
31 
32 _LOGGER = logging.getLogger(__name__)
33 
34 PLATFORM_SCHEMA = DEVICE_TRACKER_PLATFORM_SCHEMA.extend(
35  {
36  vol.Required(CONF_HOST): cv.string,
37  vol.Optional(CONF_PORT): cv.port,
38  vol.Optional(CONF_SSL, default=False): cv.boolean,
39  vol.Optional(CONF_VERIFY_SSL, default=True): vol.Any(cv.boolean, cv.isfile),
40  vol.Required(CONF_PASSWORD): cv.string,
41  vol.Required(CONF_USERNAME): cv.string,
42  vol.Required(CONF_HTTP_ID): cv.string,
43  }
44 )
45 
46 
47 def get_scanner(hass: HomeAssistant, config: ConfigType) -> TomatoDeviceScanner:
48  """Validate the configuration and returns a Tomato scanner."""
49  return TomatoDeviceScanner(config[DEVICE_TRACKER_DOMAIN])
50 
51 
52 class TomatoDeviceScanner(DeviceScanner):
53  """Class which queries a wireless router running Tomato firmware."""
54 
55  def __init__(self, config):
56  """Initialize the scanner."""
57  host, http_id = config[CONF_HOST], config[CONF_HTTP_ID]
58  port = config.get(CONF_PORT)
59  username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
60  self.ssl, self.verify_sslverify_ssl = config[CONF_SSL], config[CONF_VERIFY_SSL]
61  if port is None:
62  port = 443 if self.ssl else 80
63 
64  protocol = "https" if self.ssl else "http"
65  self.reqreq = requests.Request(
66  "POST",
67  f"{protocol}://{host}:{port}/update.cgi",
68  data={"_http_id": http_id, "exec": "devlist"},
69  auth=requests.auth.HTTPBasicAuth(username, password),
70  ).prepare()
71 
72  self.parse_api_patternparse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
73 
74  self.last_resultslast_results = {"wldev": [], "dhcpd_lease": []}
75 
76  self.success_initsuccess_init = self._update_tomato_info_update_tomato_info()
77 
78  def scan_devices(self):
79  """Scan for new devices and return a list with found device IDs."""
80  self._update_tomato_info_update_tomato_info()
81 
82  return [item[1] for item in self.last_resultslast_results["wldev"]]
83 
84  def get_device_name(self, device):
85  """Return the name of the given device or None if we don't know."""
86  filter_named = [
87  item[0] for item in self.last_resultslast_results["dhcpd_lease"] if item[2] == device
88  ]
89 
90  if not filter_named or not filter_named[0]:
91  return None
92 
93  return filter_named[0]
94 
96  """Ensure the information from the Tomato router is up to date.
97 
98  Return boolean if scanning successful.
99  """
100  _LOGGER.debug("Scanning")
101 
102  try:
103  if self.ssl:
104  response = requests.Session().send(
105  self.reqreq, timeout=60, verify=self.verify_sslverify_ssl
106  )
107  else:
108  response = requests.Session().send(self.reqreq, timeout=60)
109 
110  # Calling and parsing the Tomato api here. We only need the
111  # wldev and dhcpd_lease values.
112  if response.status_code == HTTPStatus.OK:
113  for param, value in self.parse_api_patternparse_api_pattern.findall(response.text):
114  if param in ("wldev", "dhcpd_lease"):
115  self.last_resultslast_results[param] = json.loads(value.replace("'", '"'))
116  return True
117 
118  if response.status_code == HTTPStatus.UNAUTHORIZED:
119  # Authentication error
120  _LOGGER.exception(
121  "Failed to authenticate, please check your username and password"
122  )
123  return False
124 
125  except requests.exceptions.ConnectionError:
126  # We get this if we could not connect to the router or
127  # an invalid http_id was supplied.
128  _LOGGER.exception(
129  "Failed to connect to the router or invalid http_id supplied"
130  )
131  return False
132 
133  except requests.exceptions.Timeout:
134  # We get this if we could not connect to the router or
135  # an invalid http_id was supplied.
136  _LOGGER.exception("Connection to the router timed out")
137  return False
138 
139  except ValueError:
140  # If JSON decoder could not parse the response.
141  _LOGGER.exception("Failed to parse response from router")
142  return False
TomatoDeviceScanner get_scanner(HomeAssistant hass, ConfigType config)