Home Assistant Unofficial Reference 2024.12.1
device_tracker.py
Go to the documentation of this file.
1 """Support for the Meraki CMX location service."""
2 
3 from __future__ import annotations
4 
5 from http import HTTPStatus
6 import json
7 import logging
8 
9 import voluptuous as vol
10 
12  PLATFORM_SCHEMA as DEVICE_TRACKER_PLATFORM_SCHEMA,
13  AsyncSeeCallback,
14  SourceType,
15 )
16 from homeassistant.components.http import KEY_HASS, HomeAssistantView
17 from homeassistant.core import HomeAssistant, callback
19 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
20 
21 CONF_VALIDATOR = "validator"
22 CONF_SECRET = "secret"
23 URL = "/api/meraki"
24 ACCEPTED_VERSIONS = ["2.0", "2.1"]
25 
26 
27 _LOGGER = logging.getLogger(__name__)
28 
29 
30 PLATFORM_SCHEMA = DEVICE_TRACKER_PLATFORM_SCHEMA.extend(
31  {vol.Required(CONF_VALIDATOR): cv.string, vol.Required(CONF_SECRET): cv.string}
32 )
33 
34 
36  hass: HomeAssistant,
37  config: ConfigType,
38  async_see: AsyncSeeCallback,
39  discovery_info: DiscoveryInfoType | None = None,
40 ) -> bool:
41  """Set up an endpoint for the Meraki tracker."""
42  hass.http.register_view(MerakiView(config, async_see))
43 
44  return True
45 
46 
47 class MerakiView(HomeAssistantView):
48  """View to handle Meraki requests."""
49 
50  url = URL
51  name = "api:meraki"
52  requires_auth = False
53 
54  def __init__(self, config: ConfigType, async_see: AsyncSeeCallback) -> None:
55  """Initialize Meraki URL endpoints."""
56  self.async_seeasync_see = async_see
57  self.validatorvalidator = config[CONF_VALIDATOR]
58  self.secretsecret = config[CONF_SECRET]
59 
60  async def get(self, request):
61  """Meraki message received as GET."""
62  return self.validatorvalidator
63 
64  async def post(self, request):
65  """Meraki CMX message received."""
66  try:
67  data = await request.json()
68  except ValueError:
69  return self.json_message("Invalid JSON", HTTPStatus.BAD_REQUEST)
70  _LOGGER.debug("Meraki Data from Post: %s", json.dumps(data))
71  if not data.get("secret", False):
72  _LOGGER.error("The secret is invalid")
73  return self.json_message("No secret", HTTPStatus.UNPROCESSABLE_ENTITY)
74  if data["secret"] != self.secretsecret:
75  _LOGGER.error("Invalid Secret received from Meraki")
76  return self.json_message("Invalid secret", HTTPStatus.UNPROCESSABLE_ENTITY)
77  if data["version"] not in ACCEPTED_VERSIONS:
78  _LOGGER.error("Invalid API version: %s", data["version"])
79  return self.json_message("Invalid version", HTTPStatus.UNPROCESSABLE_ENTITY)
80  _LOGGER.debug("Valid Secret")
81  if data["type"] not in ("DevicesSeen", "BluetoothDevicesSeen"):
82  _LOGGER.error("Unknown Device %s", data["type"])
83  return self.json_message(
84  "Invalid device type", HTTPStatus.UNPROCESSABLE_ENTITY
85  )
86  _LOGGER.debug("Processing %s", data["type"])
87  if not data["data"]["observations"]:
88  _LOGGER.debug("No observations found")
89  return None
90  self._handle_handle(request.app[KEY_HASS], data)
91  return None
92 
93  @callback
94  def _handle(self, hass, data):
95  for i in data["data"]["observations"]:
96  data["data"]["secret"] = "hidden"
97 
98  lat = i["location"]["lat"]
99  lng = i["location"]["lng"]
100  try:
101  accuracy = int(float(i["location"]["unc"]))
102  except ValueError:
103  accuracy = 0
104 
105  mac = i["clientMac"]
106  _LOGGER.debug("clientMac: %s", mac)
107 
108  if lat == "NaN" or lng == "NaN":
109  _LOGGER.debug("No coordinates received, skipping location for: %s", mac)
110  gps_location = None
111  accuracy = None
112  else:
113  gps_location = (lat, lng)
114 
115  attrs = {}
116  if i.get("os", False):
117  attrs["os"] = i["os"]
118  if i.get("manufacturer", False):
119  attrs["manufacturer"] = i["manufacturer"]
120  if i.get("ipv4", False):
121  attrs["ipv4"] = i["ipv4"]
122  if i.get("ipv6", False):
123  attrs["ipv6"] = i["ipv6"]
124  if i.get("seenTime", False):
125  attrs["seenTime"] = i["seenTime"]
126  if i.get("ssid", False):
127  attrs["ssid"] = i["ssid"]
128  hass.async_create_task(
129  self.async_seeasync_see(
130  gps=gps_location,
131  mac=mac,
132  source_type=SourceType.ROUTER,
133  gps_accuracy=accuracy,
134  attributes=attrs,
135  )
136  )
None __init__(self, ConfigType config, AsyncSeeCallback async_see)
bool async_setup_scanner(HomeAssistant hass, ConfigType config, AsyncSeeCallback async_see, DiscoveryInfoType|None discovery_info=None)