Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split out gpslogger into a separate component and platform #20044

Merged
merged 4 commits into from Jan 14, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
100 changes: 12 additions & 88 deletions homeassistant/components/device_tracker/gpslogger.py
Expand Up @@ -5,104 +5,28 @@
https://home-assistant.io/components/device_tracker.gpslogger/
"""
import logging
from hmac import compare_digest

from aiohttp.web import Request, HTTPUnauthorized
import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_PASSWORD, HTTP_UNPROCESSABLE_ENTITY
)
from homeassistant.components.http import (
CONF_API_PASSWORD, HomeAssistantView
)
# pylint: disable=unused-import
from homeassistant.components.device_tracker import ( # NOQA
DOMAIN, PLATFORM_SCHEMA
)
from homeassistant.components.gpslogger import TRACKER_UPDATE
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.typing import HomeAssistantType, ConfigType

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = ['http']

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PASSWORD): cv.string,
})
DEPENDENCIES = ['gpslogger']


async def async_setup_scanner(hass: HomeAssistantType, config: ConfigType,
async_see, discovery_info=None):
"""Set up an endpoint for the GPSLogger application."""
hass.http.register_view(GPSLoggerView(async_see, config))

return True


class GPSLoggerView(HomeAssistantView):
"""View to handle GPSLogger requests."""

url = '/api/gpslogger'
name = 'api:gpslogger'

def __init__(self, async_see, config):
"""Initialize GPSLogger url endpoints."""
self.async_see = async_see
self._password = config.get(CONF_PASSWORD)
# this component does not require external authentication if
# password is set
self.requires_auth = self._password is None

async def get(self, request: Request):
"""Handle for GPSLogger message received as GET."""
hass = request.app['hass']
data = request.query

if self._password is not None:
authenticated = CONF_API_PASSWORD in data and compare_digest(
self._password,
data[CONF_API_PASSWORD]
)
if not authenticated:
raise HTTPUnauthorized()

if 'latitude' not in data or 'longitude' not in data:
return ('Latitude and longitude not specified.',
HTTP_UNPROCESSABLE_ENTITY)

if 'device' not in data:
_LOGGER.error("Device id not specified")
return ('Device id not specified.',
HTTP_UNPROCESSABLE_ENTITY)

device = data['device'].replace('-', '')
gps_location = (data['latitude'], data['longitude'])
accuracy = 200
battery = -1

if 'accuracy' in data:
accuracy = int(float(data['accuracy']))
if 'battery' in data:
battery = float(data['battery'])

attrs = {}
if 'speed' in data:
attrs['speed'] = float(data['speed'])
if 'direction' in data:
attrs['direction'] = float(data['direction'])
if 'altitude' in data:
attrs['altitude'] = float(data['altitude'])
if 'provider' in data:
attrs['provider'] = data['provider']
if 'activity' in data:
attrs['activity'] = data['activity']

hass.async_create_task(self.async_see(
"""Set up an endpoint for the GPSLogger device tracker."""
async def _set_location(device, gps_location, battery, accuracy, attrs):
"""Fire HA event to set location."""
await async_see(
dev_id=device,
gps=gps_location, battery=battery,
gps=gps_location,
battery=battery,
gps_accuracy=accuracy,
attributes=attrs
))
)

return 'Setting location for {}'.format(device)
async_dispatcher_connect(hass, TRACKER_UPDATE, _set_location)
return True
114 changes: 114 additions & 0 deletions homeassistant/components/gpslogger/__init__.py
@@ -0,0 +1,114 @@
"""
Support for GPSLogger.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/gpslogger/
"""
import logging
from hmac import compare_digest

import voluptuous as vol
from aiohttp.web_exceptions import HTTPUnauthorized
from aiohttp.web_request import Request

import homeassistant.helpers.config_validation as cv
from homeassistant.components.http import HomeAssistantView, CONF_API_PASSWORD
from homeassistant.const import CONF_PASSWORD, HTTP_UNPROCESSABLE_ENTITY
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send

_LOGGER = logging.getLogger(__name__)

DOMAIN = 'gpslogger'
DEPENDENCIES = ['http']

CONFIG_SCHEMA = vol.Schema({
vol.Optional(DOMAIN): vol.Schema({
vol.Optional(CONF_PASSWORD): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)

URL = '/api/{}'.format(DOMAIN)

TRACKER_UPDATE = '{}_tracker_update'.format(DOMAIN)


async def async_setup(hass, hass_config):
"""Set up the GPSLogger component."""
config = hass_config[DOMAIN]
hass.http.register_view(GPSLoggerView(config))

hass.async_create_task(
async_load_platform(hass, 'device_tracker', DOMAIN, {}, hass_config)
)
return True


class GPSLoggerView(HomeAssistantView):
"""View to handle GPSLogger requests."""

url = URL
name = 'api:gpslogger'

def __init__(self, config):
"""Initialize GPSLogger url endpoints."""
self._password = config.get(CONF_PASSWORD)
# this component does not require external authentication if
# password is set
self.requires_auth = self._password is None

async def get(self, request: Request):
"""Handle for GPSLogger message received as GET."""
hass = request.app['hass']
data = request.query

if self._password is not None:
authenticated = CONF_API_PASSWORD in data and compare_digest(
self._password,
data[CONF_API_PASSWORD]
)
if not authenticated:
raise HTTPUnauthorized()

if 'latitude' not in data or 'longitude' not in data:
return ('Latitude and longitude not specified.',
HTTP_UNPROCESSABLE_ENTITY)

if 'device' not in data:
_LOGGER.error("Device id not specified")
return ('Device id not specified.',
HTTP_UNPROCESSABLE_ENTITY)

device = data['device'].replace('-', '')
gps_location = (data['latitude'], data['longitude'])
accuracy = 200
battery = -1

if 'accuracy' in data:
accuracy = int(float(data['accuracy']))
if 'battery' in data:
battery = float(data['battery'])

attrs = {}
if 'speed' in data:
attrs['speed'] = float(data['speed'])
if 'direction' in data:
attrs['direction'] = float(data['direction'])
if 'altitude' in data:
attrs['altitude'] = float(data['altitude'])
if 'provider' in data:
attrs['provider'] = data['provider']
if 'activity' in data:
attrs['activity'] = data['activity']

async_dispatcher_send(
hass,
TRACKER_UPDATE,
device,
gps_location,
battery,
accuracy,
attrs
)

return 'Setting location for {}'.format(device)
1 change: 1 addition & 0 deletions tests/components/gpslogger/__init__.py
@@ -0,0 +1 @@
"""Tests for the GPSLogger component."""
116 changes: 116 additions & 0 deletions tests/components/gpslogger/test_init.py
@@ -0,0 +1,116 @@
"""The tests the for GPSLogger device tracker platform."""
from unittest.mock import patch

import pytest

from homeassistant.components import zone
from homeassistant.components.device_tracker import \
DOMAIN as DEVICE_TRACKER_DOMAIN
from homeassistant.components.gpslogger import URL, DOMAIN
from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY, \
STATE_HOME, STATE_NOT_HOME
from homeassistant.setup import async_setup_component

HOME_LATITUDE = 37.239622
HOME_LONGITUDE = -115.815811


def _url(data=None):
"""Generate URL."""
data = data or {}
data = "&".join(["{}={}".format(name, value) for
name, value in data.items()])
return "{}?{}".format(URL, data)


@pytest.fixture(autouse=True)
def mock_dev_track(mock_device_tracker_conf):
"""Mock device tracker config loading."""
pass


@pytest.fixture
def gpslogger_client(loop, hass, hass_client):
"""Locative mock client."""
assert loop.run_until_complete(async_setup_component(
hass, DOMAIN, {
DOMAIN: {}
}))

with patch('homeassistant.components.device_tracker.update_config'):
yield loop.run_until_complete(hass_client())


@pytest.fixture(autouse=True)
def setup_zones(loop, hass):
"""Set up Zone config in HA."""
assert loop.run_until_complete(async_setup_component(
hass, zone.DOMAIN, {
'zone': {
'name': 'Home',
'latitude': HOME_LATITUDE,
'longitude': HOME_LONGITUDE,
'radius': 100,
}}))


async def test_missing_data(gpslogger_client):
"""Test missing data."""
data = {
'latitude': 1.0,
'longitude': 1.1,
'device': '123',
'id': 'Home',
}

# No data
req = await gpslogger_client.get(_url({}))
assert req.status == HTTP_UNPROCESSABLE_ENTITY

# No latitude
copy = data.copy()
del copy['latitude']
req = await gpslogger_client.get(_url(copy))
assert req.status == HTTP_UNPROCESSABLE_ENTITY

# No device
copy = data.copy()
del copy['device']
req = await gpslogger_client.get(_url(copy))
assert req.status == HTTP_UNPROCESSABLE_ENTITY


async def test_enter_and_exit(hass, gpslogger_client):
"""Test when there is a known zone."""
data = {
'latitude': HOME_LATITUDE,
'longitude': HOME_LONGITUDE,
'device': '123',
}

# Enter the Home
req = await gpslogger_client.get(_url(data))
await hass.async_block_till_done()
assert req.status == HTTP_OK
state_name = hass.states.get('{}.{}'.format(DEVICE_TRACKER_DOMAIN,
data['device'])).state
assert STATE_HOME == state_name

# Enter Home again
req = await gpslogger_client.get(_url(data))
await hass.async_block_till_done()
assert req.status == HTTP_OK
state_name = hass.states.get('{}.{}'.format(DEVICE_TRACKER_DOMAIN,
data['device'])).state
assert STATE_HOME == state_name

data['longitude'] = 0
data['latitude'] = 0

# Enter Somewhere else
req = await gpslogger_client.get(_url(data))
await hass.async_block_till_done()
assert req.status == HTTP_OK
state_name = hass.states.get('{}.{}'.format(DEVICE_TRACKER_DOMAIN,
data['device'])).state
assert STATE_NOT_HOME == state_name