[device_tracker] Use home zone GPS coordinates for router based devices. (#4852)

This commit is contained in:
Lewis Juggins 2017-01-07 21:09:07 +00:00 committed by Johann Kellerman
parent ca4a857532
commit 41ef6228be
2 changed files with 105 additions and 11 deletions

View File

@ -70,6 +70,10 @@ ATTR_LOCATION_NAME = 'location_name'
ATTR_GPS = 'gps' ATTR_GPS = 'gps'
ATTR_BATTERY = 'battery' ATTR_BATTERY = 'battery'
ATTR_ATTRIBUTES = 'attributes' ATTR_ATTRIBUTES = 'attributes'
ATTR_SOURCE_TYPE = 'source_type'
SOURCE_TYPE_GPS = 'gps'
SOURCE_TYPE_ROUTER = 'router'
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL): cv.time_period, vol.Optional(CONF_SCAN_INTERVAL): cv.time_period,
@ -234,17 +238,19 @@ class DeviceTracker(object):
def see(self, mac: str=None, dev_id: str=None, host_name: str=None, def see(self, mac: str=None, dev_id: str=None, host_name: str=None,
location_name: str=None, gps: GPSType=None, gps_accuracy=None, location_name: str=None, gps: GPSType=None, gps_accuracy=None,
battery: str=None, attributes: dict=None): battery: str=None, attributes: dict=None,
source_type: str=SOURCE_TYPE_GPS):
"""Notify the device tracker that you see a device.""" """Notify the device tracker that you see a device."""
self.hass.add_job( self.hass.add_job(
self.async_see(mac, dev_id, host_name, location_name, gps, self.async_see(mac, dev_id, host_name, location_name, gps,
gps_accuracy, battery, attributes) gps_accuracy, battery, attributes, source_type)
) )
@asyncio.coroutine @asyncio.coroutine
def async_see(self, mac: str=None, dev_id: str=None, host_name: str=None, def async_see(self, mac: str=None, dev_id: str=None, host_name: str=None,
location_name: str=None, gps: GPSType=None, location_name: str=None, gps: GPSType=None,
gps_accuracy=None, battery: str=None, attributes: dict=None): gps_accuracy=None, battery: str=None, attributes: dict=None,
source_type: str=SOURCE_TYPE_GPS):
"""Notify the device tracker that you see a device. """Notify the device tracker that you see a device.
This method is a coroutine. This method is a coroutine.
@ -262,7 +268,8 @@ class DeviceTracker(object):
if device: if device:
yield from device.async_seen(host_name, location_name, gps, yield from device.async_seen(host_name, location_name, gps,
gps_accuracy, battery, attributes) gps_accuracy, battery, attributes,
source_type)
if device.track: if device.track:
yield from device.async_update_ha_state() yield from device.async_update_ha_state()
return return
@ -277,7 +284,8 @@ class DeviceTracker(object):
self.mac_to_dev[mac] = device self.mac_to_dev[mac] = device
yield from device.async_seen(host_name, location_name, gps, yield from device.async_seen(host_name, location_name, gps,
gps_accuracy, battery, attributes) gps_accuracy, battery, attributes,
source_type)
if device.track: if device.track:
yield from device.async_update_ha_state() yield from device.async_update_ha_state()
@ -381,6 +389,9 @@ class Device(Entity):
self.away_hide = hide_if_away self.away_hide = hide_if_away
self.vendor = vendor self.vendor = vendor
self.source_type = None
self._attributes = {} self._attributes = {}
@property @property
@ -401,7 +412,9 @@ class Device(Entity):
@property @property
def state_attributes(self): def state_attributes(self):
"""Return the device state attributes.""" """Return the device state attributes."""
attr = {} attr = {
ATTR_SOURCE_TYPE: self.source_type
}
if self.gps: if self.gps:
attr[ATTR_LATITUDE] = self.gps[0] attr[ATTR_LATITUDE] = self.gps[0]
@ -426,12 +439,13 @@ class Device(Entity):
@asyncio.coroutine @asyncio.coroutine
def async_seen(self, host_name: str=None, location_name: str=None, def async_seen(self, host_name: str=None, location_name: str=None,
gps: GPSType=None, gps_accuracy=0, battery: str=None, gps: GPSType=None, gps_accuracy=0, battery: str=None,
attributes: dict=None): attributes: dict=None, source_type: str=SOURCE_TYPE_GPS):
"""Mark the device as seen.""" """Mark the device as seen."""
self.source_type = source_type
self.last_seen = dt_util.utcnow() self.last_seen = dt_util.utcnow()
self.host_name = host_name self.host_name = host_name
self.location_name = location_name self.location_name = location_name
self.gps_accuracy = gps_accuracy or 0
if battery: if battery:
self.battery = battery self.battery = battery
if attributes: if attributes:
@ -442,7 +456,10 @@ class Device(Entity):
if gps is not None: if gps is not None:
try: try:
self.gps = float(gps[0]), float(gps[1]) self.gps = float(gps[0]), float(gps[1])
self.gps_accuracy = gps_accuracy or 0
except (ValueError, TypeError, IndexError): except (ValueError, TypeError, IndexError):
self.gps = None
self.gps_accuracy = 0
_LOGGER.warning('Could not parse gps value for %s: %s', _LOGGER.warning('Could not parse gps value for %s: %s',
self.dev_id, gps) self.dev_id, gps)
@ -467,7 +484,7 @@ class Device(Entity):
return return
elif self.location_name: elif self.location_name:
self._state = self.location_name self._state = self.location_name
elif self.gps is not None: elif self.gps is not None and self.source_type == SOURCE_TYPE_GPS:
zone_state = zone.async_active_zone( zone_state = zone.async_active_zone(
self.hass, self.gps[0], self.gps[1], self.gps_accuracy) self.hass, self.gps[0], self.gps[1], self.gps_accuracy)
if zone_state is None: if zone_state is None:
@ -476,9 +493,9 @@ class Device(Entity):
self._state = STATE_HOME self._state = STATE_HOME
else: else:
self._state = zone_state.name self._state = zone_state.name
elif self.stale(): elif self.stale():
self._state = STATE_NOT_HOME self._state = STATE_NOT_HOME
self.gps = None
self.last_update_home = False self.last_update_home = False
else: else:
self._state = STATE_HOME self._state = STATE_HOME
@ -637,7 +654,20 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType,
else: else:
host_name = yield from scanner.async_get_device_name(mac) host_name = yield from scanner.async_get_device_name(mac)
seen.add(mac) seen.add(mac)
hass.async_add_job(async_see_device(mac=mac, host_name=host_name))
kwargs = {
'mac': mac,
'host_name': host_name,
'source_type': SOURCE_TYPE_ROUTER
}
zone_home = hass.states.get(zone.ENTITY_ID_HOME)
if zone_home:
kwargs['gps'] = [zone_home.attributes[ATTR_LATITUDE],
zone_home.attributes[ATTR_LONGITUDE]]
kwargs['gps_accuracy'] = 0
hass.async_add_job(async_see_device(**kwargs))
async_track_time_interval(hass, async_device_tracker_scan, interval) async_track_time_interval(hass, async_device_tracker_scan, interval)
hass.async_add_job(async_device_tracker_scan, None) hass.async_add_job(async_device_tracker_scan, None)

View File

@ -8,6 +8,7 @@ from unittest.mock import call, patch
from datetime import datetime, timedelta from datetime import datetime, timedelta
import os import os
from homeassistant.components import zone
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.bootstrap import setup_component from homeassistant.bootstrap import setup_component
from homeassistant.loader import get_component from homeassistant.loader import get_component
@ -541,8 +542,71 @@ class TestComponentsDeviceTracker(unittest.TestCase):
self.assertEqual(attrs['longitude'], 0.8) self.assertEqual(attrs['longitude'], 0.8)
self.assertEqual(attrs['test'], 'test') self.assertEqual(attrs['test'], 'test')
self.assertEqual(attrs['gps_accuracy'], 1) self.assertEqual(attrs['gps_accuracy'], 1)
self.assertEqual(attrs['source_type'], 'gps')
self.assertEqual(attrs['number'], 1) self.assertEqual(attrs['number'], 1)
def test_see_passive_zone_state(self):
"""Test that the device tracker sets gps for passive trackers."""
register_time = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
scan_time = datetime(2015, 9, 15, 23, 1, tzinfo=dt_util.UTC)
with assert_setup_component(1, zone.DOMAIN):
zone_info = {
'name': 'Home',
'latitude': 1,
'longitude': 2,
'radius': 250,
'passive': False
}
setup_component(self.hass, zone.DOMAIN, {
'zone': zone_info
})
scanner = get_component('device_tracker.test').SCANNER
scanner.reset()
scanner.come_home('dev1')
with patch('homeassistant.components.device_tracker.dt_util.utcnow',
return_value=register_time):
with assert_setup_component(1, device_tracker.DOMAIN):
assert setup_component(self.hass, device_tracker.DOMAIN, {
device_tracker.DOMAIN: {
CONF_PLATFORM: 'test',
device_tracker.CONF_CONSIDER_HOME: 59,
}})
state = self.hass.states.get('device_tracker.dev1')
attrs = state.attributes
self.assertEqual(STATE_HOME, state.state)
self.assertEqual(state.object_id, 'dev1')
self.assertEqual(state.name, 'dev1')
self.assertEqual(attrs.get('friendly_name'), 'dev1')
self.assertEqual(attrs.get('latitude'), 1)
self.assertEqual(attrs.get('longitude'), 2)
self.assertEqual(attrs.get('gps_accuracy'), 0)
self.assertEqual(attrs.get('source_type'),
device_tracker.SOURCE_TYPE_ROUTER)
scanner.leave_home('dev1')
with patch('homeassistant.components.device_tracker.dt_util.utcnow',
return_value=scan_time):
fire_time_changed(self.hass, scan_time)
self.hass.block_till_done()
state = self.hass.states.get('device_tracker.dev1')
attrs = state.attributes
self.assertEqual(STATE_NOT_HOME, state.state)
self.assertEqual(state.object_id, 'dev1')
self.assertEqual(state.name, 'dev1')
self.assertEqual(attrs.get('friendly_name'), 'dev1')
self.assertEqual(attrs.get('latitude'), None)
self.assertEqual(attrs.get('longitude'), None)
self.assertEqual(attrs.get('gps_accuracy'), None)
self.assertEqual(attrs.get('source_type'),
device_tracker.SOURCE_TYPE_ROUTER)
@patch('homeassistant.components.device_tracker._LOGGER.warning') @patch('homeassistant.components.device_tracker._LOGGER.warning')
def test_see_failures(self, mock_warning): def test_see_failures(self, mock_warning):
"""Test that the device tracker see failures.""" """Test that the device tracker see failures."""