1 """Support for the Meraki CMX location service."""
3 from __future__
import annotations
5 from http
import HTTPStatus
9 import voluptuous
as vol
12 PLATFORM_SCHEMA
as DEVICE_TRACKER_PLATFORM_SCHEMA,
21 CONF_VALIDATOR =
"validator"
22 CONF_SECRET =
"secret"
24 ACCEPTED_VERSIONS = [
"2.0",
"2.1"]
27 _LOGGER = logging.getLogger(__name__)
30 PLATFORM_SCHEMA = DEVICE_TRACKER_PLATFORM_SCHEMA.extend(
31 {vol.Required(CONF_VALIDATOR): cv.string, vol.Required(CONF_SECRET): cv.string}
38 async_see: AsyncSeeCallback,
39 discovery_info: DiscoveryInfoType |
None =
None,
41 """Set up an endpoint for the Meraki tracker."""
42 hass.http.register_view(
MerakiView(config, async_see))
48 """View to handle Meraki requests."""
54 def __init__(self, config: ConfigType, async_see: AsyncSeeCallback) ->
None:
55 """Initialize Meraki URL endpoints."""
58 self.
secretsecret = config[CONF_SECRET]
60 async
def get(self, request):
61 """Meraki message received as GET."""
64 async
def post(self, request):
65 """Meraki CMX message received."""
67 data = await request.json()
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
86 _LOGGER.debug(
"Processing %s", data[
"type"])
87 if not data[
"data"][
"observations"]:
88 _LOGGER.debug(
"No observations found")
90 self.
_handle_handle(request.app[KEY_HASS], data)
95 for i
in data[
"data"][
"observations"]:
96 data[
"data"][
"secret"] =
"hidden"
98 lat = i[
"location"][
"lat"]
99 lng = i[
"location"][
"lng"]
101 accuracy =
int(
float(i[
"location"][
"unc"]))
106 _LOGGER.debug(
"clientMac: %s", mac)
108 if lat ==
"NaN" or lng ==
"NaN":
109 _LOGGER.debug(
"No coordinates received, skipping location for: %s", mac)
113 gps_location = (lat, lng)
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(
132 source_type=SourceType.ROUTER,
133 gps_accuracy=accuracy,
def _handle(self, hass, data)
None __init__(self, ConfigType config, AsyncSeeCallback async_see)
bool async_setup_scanner(HomeAssistant hass, ConfigType config, AsyncSeeCallback async_see, DiscoveryInfoType|None discovery_info=None)