mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 17:57:55 +00:00
Meraki AP Device tracker (#10971)
* Device tracker for meraki AP * styles fix * fix again * again * and again :) * fix hide if away * docs and optimization * tests and fixes * styles * styles * styles * styles * styles fix. Hope last * clear track new * changes * fix accuracy error and requested changes * remove meraki from .coveragerc * tests and minor changes * remove location
This commit is contained in:
parent
c13b510ba3
commit
e66268dffe
116
homeassistant/components/device_tracker/meraki.py
Normal file
116
homeassistant/components/device_tracker/meraki.py
Normal file
@ -0,0 +1,116 @@
|
||||
"""
|
||||
Support for the Meraki CMX location service.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.meraki/
|
||||
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
|
||||
import voluptuous as vol
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (HTTP_BAD_REQUEST, HTTP_UNPROCESSABLE_ENTITY)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.components.device_tracker import (
|
||||
PLATFORM_SCHEMA, SOURCE_TYPE_ROUTER)
|
||||
|
||||
CONF_VALIDATOR = 'validator'
|
||||
CONF_SECRET = 'secret'
|
||||
DEPENDENCIES = ['http']
|
||||
URL = '/api/meraki'
|
||||
VERSION = '2.0'
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_VALIDATOR): cv.string,
|
||||
vol.Required(CONF_SECRET): cv.string
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
"""Set up an endpoint for the Meraki tracker."""
|
||||
hass.http.register_view(
|
||||
MerakiView(config, async_see))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class MerakiView(HomeAssistantView):
|
||||
"""View to handle Meraki requests."""
|
||||
|
||||
url = URL
|
||||
name = 'api:meraki'
|
||||
|
||||
def __init__(self, config, async_see):
|
||||
"""Initialize Meraki URL endpoints."""
|
||||
self.async_see = async_see
|
||||
self.validator = config[CONF_VALIDATOR]
|
||||
self.secret = config[CONF_SECRET]
|
||||
|
||||
@asyncio.coroutine
|
||||
def get(self, request):
|
||||
"""Meraki message received as GET."""
|
||||
return self.validator
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request):
|
||||
"""Meraki CMX message received."""
|
||||
try:
|
||||
data = yield from request.json()
|
||||
except ValueError:
|
||||
return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)
|
||||
_LOGGER.debug("Meraki Data from Post: %s", json.dumps(data))
|
||||
if not data.get('secret', False):
|
||||
_LOGGER.error("secret invalid")
|
||||
return self.json_message('No secret', HTTP_UNPROCESSABLE_ENTITY)
|
||||
if data['secret'] != self.secret:
|
||||
_LOGGER.error("Invalid Secret received from Meraki")
|
||||
return self.json_message('Invalid secret',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
elif data['version'] != VERSION:
|
||||
_LOGGER.error("Invalid API version: %s", data['version'])
|
||||
return self.json_message('Invalid version',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
else:
|
||||
_LOGGER.debug('Valid Secret')
|
||||
if data['type'] not in ('DevicesSeen', 'BluetoothDevicesSeen'):
|
||||
_LOGGER.error("Unknown Device %s", data['type'])
|
||||
return self.json_message('Invalid device type',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
_LOGGER.debug("Processing %s", data['type'])
|
||||
if len(data["data"]["observations"]) == 0:
|
||||
_LOGGER.debug("No observations found")
|
||||
return
|
||||
self._handle(request.app['hass'], data)
|
||||
|
||||
@callback
|
||||
def _handle(self, hass, data):
|
||||
for i in data["data"]["observations"]:
|
||||
data["data"]["secret"] = "hidden"
|
||||
mac = i["clientMac"]
|
||||
_LOGGER.debug("clientMac: %s", mac)
|
||||
attrs = {}
|
||||
if i.get('os', False):
|
||||
attrs['os'] = i['os']
|
||||
if i.get('manufacturer', False):
|
||||
attrs['manufacturer'] = i['manufacturer']
|
||||
if i.get('ipv4', False):
|
||||
attrs['ipv4'] = i['ipv4']
|
||||
if i.get('ipv6', False):
|
||||
attrs['ipv6'] = i['ipv6']
|
||||
if i.get('seenTime', False):
|
||||
attrs['seenTime'] = i['seenTime']
|
||||
if i.get('ssid', False):
|
||||
attrs['ssid'] = i['ssid']
|
||||
hass.async_add_job(self.async_see(
|
||||
mac=mac,
|
||||
source_type=SOURCE_TYPE_ROUTER,
|
||||
attributes=attrs
|
||||
))
|
139
tests/components/device_tracker/test_meraki.py
Normal file
139
tests/components/device_tracker/test_meraki.py
Normal file
@ -0,0 +1,139 @@
|
||||
"""The tests the for Meraki device tracker."""
|
||||
import asyncio
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
import pytest
|
||||
from homeassistant.components.device_tracker.meraki import (
|
||||
CONF_VALIDATOR, CONF_SECRET)
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.components.device_tracker as device_tracker
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.components.device_tracker.meraki import URL
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def meraki_client(loop, hass, test_client):
|
||||
"""Meraki mock client."""
|
||||
assert loop.run_until_complete(async_setup_component(
|
||||
hass, device_tracker.DOMAIN, {
|
||||
device_tracker.DOMAIN: {
|
||||
CONF_PLATFORM: 'meraki',
|
||||
CONF_VALIDATOR: 'validator',
|
||||
CONF_SECRET: 'secret'
|
||||
|
||||
}
|
||||
}))
|
||||
|
||||
with patch('homeassistant.components.device_tracker.update_config'):
|
||||
yield loop.run_until_complete(test_client(hass.http.app))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_invalid_or_missing_data(meraki_client):
|
||||
"""Test validator with invalid or missing data."""
|
||||
req = yield from meraki_client.get(URL)
|
||||
text = yield from req.text()
|
||||
assert req.status == 200
|
||||
assert text == 'validator'
|
||||
|
||||
req = yield from meraki_client.post(URL, data=b"invalid")
|
||||
text = yield from req.json()
|
||||
assert req.status == 400
|
||||
assert text['message'] == 'Invalid JSON'
|
||||
|
||||
req = yield from meraki_client.post(URL, data=b"{}")
|
||||
text = yield from req.json()
|
||||
assert req.status == 422
|
||||
assert text['message'] == 'No secret'
|
||||
|
||||
data = {
|
||||
"version": "1.0",
|
||||
"secret": "secret"
|
||||
}
|
||||
req = yield from meraki_client.post(URL, data=json.dumps(data))
|
||||
text = yield from req.json()
|
||||
assert req.status == 422
|
||||
assert text['message'] == 'Invalid version'
|
||||
|
||||
data = {
|
||||
"version": "2.0",
|
||||
"secret": "invalid"
|
||||
}
|
||||
req = yield from meraki_client.post(URL, data=json.dumps(data))
|
||||
text = yield from req.json()
|
||||
assert req.status == 422
|
||||
assert text['message'] == 'Invalid secret'
|
||||
|
||||
data = {
|
||||
"version": "2.0",
|
||||
"secret": "secret",
|
||||
"type": "InvalidType"
|
||||
}
|
||||
req = yield from meraki_client.post(URL, data=json.dumps(data))
|
||||
text = yield from req.json()
|
||||
assert req.status == 422
|
||||
assert text['message'] == 'Invalid device type'
|
||||
|
||||
data = {
|
||||
"version": "2.0",
|
||||
"secret": "secret",
|
||||
"type": "BluetoothDevicesSeen",
|
||||
"data": {
|
||||
"observations": []
|
||||
}
|
||||
}
|
||||
req = yield from meraki_client.post(URL, data=json.dumps(data))
|
||||
assert req.status == 200
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_data_will_be_saved(hass, meraki_client):
|
||||
"""Test with valid data."""
|
||||
data = {
|
||||
"version": "2.0",
|
||||
"secret": "secret",
|
||||
"type": "DevicesSeen",
|
||||
"data": {
|
||||
"observations": [
|
||||
{
|
||||
"location": {
|
||||
"lat": "51.5355157",
|
||||
"lng": "21.0699035",
|
||||
"unc": "46.3610585",
|
||||
},
|
||||
"seenTime": "2016-09-12T16:23:13Z",
|
||||
"ssid": 'ssid',
|
||||
"os": 'HA',
|
||||
"ipv6": '2607:f0d0:1002:51::4/64',
|
||||
"clientMac": "00:26:ab:b8:a9:a4",
|
||||
"seenEpoch": "147369739",
|
||||
"rssi": "20",
|
||||
"manufacturer": "Seiko Epson"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"lat": "51.5355357",
|
||||
"lng": "21.0699635",
|
||||
"unc": "46.3610585",
|
||||
},
|
||||
"seenTime": "2016-09-12T16:21:13Z",
|
||||
"ssid": 'ssid',
|
||||
"os": 'HA',
|
||||
"ipv4": '192.168.0.1',
|
||||
"clientMac": "00:26:ab:b8:a9:a5",
|
||||
"seenEpoch": "147369750",
|
||||
"rssi": "20",
|
||||
"manufacturer": "Seiko Epson"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
req = yield from meraki_client.post(URL, data=json.dumps(data))
|
||||
assert req.status == 200
|
||||
state_name = hass.states.get('{}.{}'.format('device_tracker',
|
||||
'0026abb8a9a4')).state
|
||||
assert 'home' == state_name
|
||||
|
||||
state_name = hass.states.get('{}.{}'.format('device_tracker',
|
||||
'0026abb8a9a5')).state
|
||||
assert 'home' == state_name
|
Loading…
x
Reference in New Issue
Block a user