mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
[device_tracker] Use home zone GPS coordinates for router based devices. (#4852)
This commit is contained in:
parent
ca4a857532
commit
41ef6228be
@ -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)
|
||||||
|
@ -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."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user