Backend support for importing waypoints from owntracks as HA zones

This commit is contained in:
NMA 2016-08-12 14:48:28 +05:30
parent 1a327d682d
commit 75e6ed87d6
2 changed files with 66 additions and 26 deletions

View file

@ -12,6 +12,7 @@ from collections import defaultdict
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
from homeassistant.const import STATE_HOME from homeassistant.const import STATE_HOME
from homeassistant.util import convert, slugify from homeassistant.util import convert, slugify
from homeassistant.components import zone
DEPENDENCIES = ['mqtt'] DEPENDENCIES = ['mqtt']
@ -22,17 +23,19 @@ BEACON_DEV_ID = 'beacon'
LOCATION_TOPIC = 'owntracks/+/+' LOCATION_TOPIC = 'owntracks/+/+'
EVENT_TOPIC = 'owntracks/+/+/event' EVENT_TOPIC = 'owntracks/+/+/event'
WAYPOINT_TOPIC = 'owntracks/{}/+/waypoint'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
LOCK = threading.Lock() LOCK = threading.Lock()
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user'
def setup_scanner(hass, config, see): def setup_scanner(hass, config, see):
"""Setup an OwnTracks tracker.""" """Setup an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY) max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import_user = config.get(CONF_WAYPOINT_IMPORT_USER)
def validate_payload(payload, data_type): def validate_payload(payload, data_type):
"""Validate OwnTracks payload.""" """Validate OwnTracks payload."""
@ -47,6 +50,7 @@ def setup_scanner(hass, config, see):
'because of missing or malformatted data: %s', 'because of missing or malformatted data: %s',
data_type, data) data_type, data)
return None return None
if data_type != 'waypoints':
if max_gps_accuracy is not None and \ if max_gps_accuracy is not None and \
convert(data.get('acc'), float, 0.0) > max_gps_accuracy: convert(data.get('acc'), float, 0.0) > max_gps_accuracy:
_LOGGER.debug('Skipping %s update because expected GPS ' _LOGGER.debug('Skipping %s update because expected GPS '
@ -105,9 +109,9 @@ def setup_scanner(hass, config, see):
def enter_event(): def enter_event():
"""Execute enter event.""" """Execute enter event."""
zone = hass.states.get("zone.{}".format(location)) _zone = hass.states.get("zone.{}".format(location))
with LOCK: with LOCK:
if zone is None and data.get('t') == 'b': if _zone is None and data.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile # Not a HA zone, and a beacon so assume mobile
beacons = MOBILE_BEACONS_ACTIVE[dev_id] beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location not in beacons: if location not in beacons:
@ -119,7 +123,7 @@ def setup_scanner(hass, config, see):
if location not in regions: if location not in regions:
regions.append(location) regions.append(location)
_LOGGER.info("Enter region %s", location) _LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, location, zone) _set_gps_from_zone(kwargs, location, _zone)
see(**kwargs) see(**kwargs)
see_beacons(dev_id, kwargs) see_beacons(dev_id, kwargs)
@ -134,8 +138,8 @@ def setup_scanner(hass, config, see):
if new_region: if new_region:
# Exit to previous region # Exit to previous region
zone = hass.states.get("zone.{}".format(new_region)) _zone = hass.states.get("zone.{}".format(new_region))
_set_gps_from_zone(kwargs, new_region, zone) _set_gps_from_zone(kwargs, new_region, _zone)
_LOGGER.info("Exit to %s", new_region) _LOGGER.info("Exit to %s", new_region)
see(**kwargs) see(**kwargs)
see_beacons(dev_id, kwargs) see_beacons(dev_id, kwargs)
@ -167,6 +171,23 @@ def setup_scanner(hass, config, see):
data['event']) data['event'])
return return
def owntracks_waypoint_update(topic, payload, qos):
"""List of waypoints published by a user."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typewaypoints
data = validate_payload(payload, 'waypoints')
if not data:
return
wayps = data['waypoints']
_LOGGER.info("Got %d waypoints from %s", len(wayps), topic)
for wayp in wayps:
name = wayp['desc']
lat = wayp['lat']
lon = wayp['lon']
rad = wayp['rad']
zone.add_zone(hass, name, lat, lon, rad)
def see_beacons(dev_id, kwargs_param): def see_beacons(dev_id, kwargs_param):
"""Set active beacons to the current location.""" """Set active beacons to the current location."""
kwargs = kwargs_param.copy() kwargs = kwargs_param.copy()
@ -180,6 +201,10 @@ def setup_scanner(hass, config, see):
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1) mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1) mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
if waypoint_import_user is not None:
mqtt.subscribe(hass, WAYPOINT_TOPIC.format(waypoint_import_user),
owntracks_waypoint_update, 1)
return True return True
@ -200,12 +225,12 @@ def _parse_see_args(topic, data):
return dev_id, kwargs return dev_id, kwargs
def _set_gps_from_zone(kwargs, location, zone): def _set_gps_from_zone(kwargs, location, _zone):
"""Set the see parameters from the zone parameters.""" """Set the see parameters from the zone parameters."""
if zone is not None: if _zone is not None:
kwargs['gps'] = ( kwargs['gps'] = (
zone.attributes['latitude'], _zone.attributes['latitude'],
zone.attributes['longitude']) _zone.attributes['longitude'])
kwargs['gps_accuracy'] = zone.attributes['radius'] kwargs['gps_accuracy'] = _zone.attributes['radius']
kwargs['location_name'] = location kwargs['location_name'] = location
return kwargs return kwargs

View file

@ -27,7 +27,10 @@ ATTR_PASSIVE = 'passive'
DEFAULT_PASSIVE = False DEFAULT_PASSIVE = False
ICON_HOME = 'mdi:home' ICON_HOME = 'mdi:home'
ICON_IMPORT = 'mdi:import'
entities = set()
_LOGGER = logging.getLogger(__name__)
def active_zone(hass, latitude, longitude, radius=0): def active_zone(hass, latitude, longitude, radius=0):
"""Find the active zone for given latitude, longitude.""" """Find the active zone for given latitude, longitude."""
@ -70,7 +73,6 @@ def in_zone(zone, latitude, longitude, radius=0):
def setup(hass, config): def setup(hass, config):
"""Setup zone.""" """Setup zone."""
entities = set()
for key in extract_domain_configs(config, DOMAIN): for key in extract_domain_configs(config, DOMAIN):
entries = config[key] entries = config[key]
@ -90,7 +92,7 @@ def setup(hass, config):
'Each zone needs a latitude and longitude.') 'Each zone needs a latitude and longitude.')
continue continue
zone = Zone(hass, name, latitude, longitude, radius, icon, passive) zone = Zone(hass, name, latitude, longitude, radius, icon, passive, False)
zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name,
entities) entities)
zone.update_ha_state() zone.update_ha_state()
@ -98,18 +100,30 @@ def setup(hass, config):
if ENTITY_ID_HOME not in entities: if ENTITY_ID_HOME not in entities:
zone = Zone(hass, hass.config.location_name, hass.config.latitude, zone = Zone(hass, hass.config.location_name, hass.config.latitude,
hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, False) hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, False, False)
zone.entity_id = ENTITY_ID_HOME zone.entity_id = ENTITY_ID_HOME
zone.update_ha_state() zone.update_ha_state()
return True return True
# Add a zone to the existing set
def add_zone(hass, name, latitude, longitude, radius):
_LOGGER.info("Adding new zone %s", name)
if name not in entities:
zone = Zone(hass, name, latitude, longitude, radius, ICON_IMPORT,
False, True)
zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name,
entities)
zone.update_ha_state()
entities.add(zone.entity_id)
else:
_LOGGER.info("Zone already exists")
class Zone(Entity): class Zone(Entity):
"""Representation of a Zone.""" """Representation of a Zone."""
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, hass, name, latitude, longitude, radius, icon, passive): def __init__(self, hass, name, latitude, longitude, radius, icon, passive, imported):
"""Initialize the zone.""" """Initialize the zone."""
self.hass = hass self.hass = hass
self._name = name self._name = name
@ -118,6 +132,7 @@ class Zone(Entity):
self._radius = radius self._radius = radius
self._icon = icon self._icon = icon
self._passive = passive self._passive = passive
self._imported = imported
@property @property
def name(self): def name(self):