""" Support for the Geofency platform. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.geofency/ """ import asyncio from functools import partial import logging import voluptuous as vol from homeassistant.components.device_tracker import PLATFORM_SCHEMA from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME) import homeassistant.helpers.config_validation as cv from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['http'] ATTR_CURRENT_LATITUDE = 'currentLatitude' ATTR_CURRENT_LONGITUDE = 'currentLongitude' BEACON_DEV_PREFIX = 'beacon' CONF_MOBILE_BEACONS = 'mobile_beacons' LOCATION_ENTRY = '1' LOCATION_EXIT = '0' URL = '/api/geofency' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_MOBILE_BEACONS): vol.All( cv.ensure_list, [cv.string]), }) def setup_scanner(hass, config, see, discovery_info=None): """Set up an endpoint for the Geofency application.""" mobile_beacons = config.get(CONF_MOBILE_BEACONS) or [] hass.http.register_view(GeofencyView(see, mobile_beacons)) return True class GeofencyView(HomeAssistantView): """View to handle Geofency requests.""" url = URL name = 'api:geofency' def __init__(self, see, mobile_beacons): """Initialize Geofency url endpoints.""" self.see = see self.mobile_beacons = [slugify(beacon) for beacon in mobile_beacons] @asyncio.coroutine def post(self, request): """Handle Geofency requests.""" data = yield from request.post() hass = request.app['hass'] data = self._validate_data(data) if not data: return ("Invalid data", HTTP_UNPROCESSABLE_ENTITY) if self._is_mobile_beacon(data): return (yield from self._set_location(hass, data, None)) if data['entry'] == LOCATION_ENTRY: location_name = data['name'] else: location_name = STATE_NOT_HOME if ATTR_CURRENT_LATITUDE in data: data[ATTR_LATITUDE] = data[ATTR_CURRENT_LATITUDE] data[ATTR_LONGITUDE] = data[ATTR_CURRENT_LONGITUDE] return (yield from self._set_location(hass, data, location_name)) @staticmethod def _validate_data(data): """Validate POST payload.""" data = data.copy() required_attributes = ['address', 'device', 'entry', 'latitude', 'longitude', 'name'] valid = True for attribute in required_attributes: if attribute not in data: valid = False _LOGGER.error("'%s' not specified in message", attribute) if not valid: return False data['address'] = data['address'].replace('\n', ' ') data['device'] = slugify(data['device']) data['name'] = slugify(data['name']) gps_attributes = [ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_CURRENT_LATITUDE, ATTR_CURRENT_LONGITUDE] for attribute in gps_attributes: if attribute in data: data[attribute] = float(data[attribute]) return data def _is_mobile_beacon(self, data): """Check if we have a mobile beacon.""" return 'beaconUUID' in data and data['name'] in self.mobile_beacons @staticmethod def _device_name(data): """Return name of device tracker.""" if 'beaconUUID' in data: return "{}_{}".format(BEACON_DEV_PREFIX, data['name']) return data['device'] @asyncio.coroutine def _set_location(self, hass, data, location_name): """Fire HA event to set location.""" device = self._device_name(data) yield from hass.async_add_job( partial(self.see, dev_id=device, gps=(data[ATTR_LATITUDE], data[ATTR_LONGITUDE]), location_name=location_name, attributes=data)) return "Setting location for {}".format(device)